Step 518: add icon and typography harmonization model

This commit is contained in:
Bill
2026-02-17 09:00:59 -07:00
parent 992d2e905d
commit cced1be19f
4 changed files with 228 additions and 0 deletions

View File

@@ -3541,4 +3541,13 @@ target_link_libraries(step517_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step518_test tests/step518_test.cpp)
target_include_directories(step518_test PRIVATE src)
target_link_libraries(step518_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -0,0 +1,60 @@
#pragma once
// Step 518: Icon + Typography Contrast Harmonization
#include <string>
#include <vector>
#include <algorithm>
struct TypographyProfile {
float bodySizePx = 14.0f;
float headingSizePx = 20.0f;
float lineHeight = 1.4f;
float weightBody = 450.0f;
float weightHeading = 640.0f;
float textContrast = 1.0f;
};
struct IconProfile {
float strokePx = 1.5f;
float contrast = 1.0f;
float opticalSize = 16.0f;
};
class IconTypographyHarmonizer {
public:
static TypographyProfile darkSurfaceTypography(float density) {
float d = std::max(0.8f, std::min(1.4f, density));
TypographyProfile t;
t.bodySizePx = 14.0f * d;
t.headingSizePx = 20.0f * d;
t.lineHeight = 1.38f;
t.weightBody = 460.0f;
t.weightHeading = 650.0f;
t.textContrast = 6.4f;
return t;
}
static IconProfile iconProfileFor(float density, bool emphasized) {
float d = std::max(0.8f, std::min(1.4f, density));
IconProfile i;
i.strokePx = emphasized ? 1.9f * d : 1.5f * d;
i.contrast = emphasized ? 4.5f : 3.4f;
i.opticalSize = 16.0f * d;
return i;
}
static bool harmonized(const TypographyProfile& t, const IconProfile& i) {
return t.textContrast >= 4.5f && i.contrast >= 3.0f &&
i.strokePx >= 1.0f && t.headingSizePx > t.bodySizePx;
}
static float rhythmScore(const TypographyProfile& t,
const std::vector<float>& panelDensities) {
if (panelDensities.empty()) return 0.0f;
float spread = *std::max_element(panelDensities.begin(), panelDensities.end()) -
*std::min_element(panelDensities.begin(), panelDensities.end());
float base = 1.0f - std::min(1.0f, spread / 12.0f);
float typeBonus = (t.lineHeight >= 1.3f && t.lineHeight <= 1.6f) ? 0.2f : 0.0f;
return std::max(0.0f, std::min(1.0f, base + typeBonus));
}
};

View File

@@ -0,0 +1,125 @@
// Step 518: Icon + Typography Contrast Harmonization (12 tests)
#include "IconTypographyHarmonizer.h"
#include <iostream>
static int passed = 0, failed = 0;
#define TEST(name) { std::cout << " " << #name << "... "; }
#define PASS() { std::cout << "PASS\n"; ++passed; }
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
void test_typography_scales_with_density() {
TEST(typography_scales_with_density);
auto a = IconTypographyHarmonizer::darkSurfaceTypography(1.0f);
auto b = IconTypographyHarmonizer::darkSurfaceTypography(1.3f);
CHECK(b.bodySizePx > a.bodySizePx, "body size should scale up");
PASS();
}
void test_density_clamps_low() {
TEST(density_clamps_low);
auto a = IconTypographyHarmonizer::darkSurfaceTypography(0.2f);
auto b = IconTypographyHarmonizer::darkSurfaceTypography(0.8f);
CHECK(a.bodySizePx == b.bodySizePx, "density should clamp low");
PASS();
}
void test_density_clamps_high() {
TEST(density_clamps_high);
auto a = IconTypographyHarmonizer::darkSurfaceTypography(3.0f);
auto b = IconTypographyHarmonizer::darkSurfaceTypography(1.4f);
CHECK(a.bodySizePx == b.bodySizePx, "density should clamp high");
PASS();
}
void test_heading_larger_than_body() {
TEST(heading_larger_than_body);
auto t = IconTypographyHarmonizer::darkSurfaceTypography(1.0f);
CHECK(t.headingSizePx > t.bodySizePx, "heading should be larger");
PASS();
}
void test_text_contrast_meets_aa() {
TEST(text_contrast_meets_aa);
auto t = IconTypographyHarmonizer::darkSurfaceTypography(1.0f);
CHECK(t.textContrast >= 4.5f, "text contrast should meet AA");
PASS();
}
void test_icon_profile_emphasized_has_higher_stroke() {
TEST(icon_profile_emphasized_has_higher_stroke);
auto a = IconTypographyHarmonizer::iconProfileFor(1.0f, false);
auto b = IconTypographyHarmonizer::iconProfileFor(1.0f, true);
CHECK(b.strokePx > a.strokePx, "emphasized icon stroke should be higher");
PASS();
}
void test_icon_profile_has_min_contrast() {
TEST(icon_profile_has_min_contrast);
auto i = IconTypographyHarmonizer::iconProfileFor(1.0f, false);
CHECK(i.contrast >= 3.0f, "icon contrast should meet threshold");
PASS();
}
void test_harmonized_profile_passes() {
TEST(harmonized_profile_passes);
auto t = IconTypographyHarmonizer::darkSurfaceTypography(1.0f);
auto i = IconTypographyHarmonizer::iconProfileFor(1.0f, false);
CHECK(IconTypographyHarmonizer::harmonized(t, i), "harmonization should pass");
PASS();
}
void test_harmonized_fails_with_low_icon_contrast() {
TEST(harmonized_fails_with_low_icon_contrast);
auto t = IconTypographyHarmonizer::darkSurfaceTypography(1.0f);
IconProfile i = IconTypographyHarmonizer::iconProfileFor(1.0f, false);
i.contrast = 2.0f;
CHECK(!IconTypographyHarmonizer::harmonized(t, i), "harmonization should fail");
PASS();
}
void test_rhythm_score_high_for_consistent_panels() {
TEST(rhythm_score_high_for_consistent_panels);
auto t = IconTypographyHarmonizer::darkSurfaceTypography(1.0f);
float r = IconTypographyHarmonizer::rhythmScore(t, {12.0f, 13.0f, 12.5f});
CHECK(r > 0.7f, "rhythm should be high for consistent densities");
PASS();
}
void test_rhythm_score_low_for_wide_spread() {
TEST(rhythm_score_low_for_wide_spread);
auto t = IconTypographyHarmonizer::darkSurfaceTypography(1.0f);
float r = IconTypographyHarmonizer::rhythmScore(t, {4.0f, 20.0f, 30.0f});
CHECK(r < 0.5f, "rhythm should drop for wide spread");
PASS();
}
void test_rhythm_zero_when_no_panels() {
TEST(rhythm_zero_when_no_panels);
auto t = IconTypographyHarmonizer::darkSurfaceTypography(1.0f);
CHECK(IconTypographyHarmonizer::rhythmScore(t, {}) == 0.0f,
"empty panel list should produce zero score");
PASS();
}
int main() {
std::cout << "Step 518: Icon + Typography Contrast Harmonization\n";
test_typography_scales_with_density(); // 1
test_density_clamps_low(); // 2
test_density_clamps_high(); // 3
test_heading_larger_than_body(); // 4
test_text_contrast_meets_aa(); // 5
test_icon_profile_emphasized_has_higher_stroke(); // 6
test_icon_profile_has_min_contrast(); // 7
test_harmonized_profile_passes(); // 8
test_harmonized_fails_with_low_icon_contrast(); // 9
test_rhythm_score_high_for_consistent_panels(); // 10
test_rhythm_score_low_for_wide_spread(); // 11
test_rhythm_zero_when_no_panels(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -8317,3 +8317,37 @@ contrast thresholds, and DPI-stable geometry scaling.
- `editor/src/ControlLibraryRestyle.h` within header-size limit (`83` <= `600`)
- `editor/tests/step517_test.cpp` within test-file size guidance (`127` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
### Step 518: Icon + Typography Contrast Harmonization
**Status:** PASS (12/12 tests)
Implements icon and typography harmonization for dark surfaces with density-aware
sizing, stroke-weight balancing, contrast thresholds, and panel rhythm scoring
to reduce visual drift between dense and sparse UI regions.
**Files added:**
- `editor/src/IconTypographyHarmonizer.h` - harmonization module:
- density-aware typography profile synthesis
- icon stroke/contrast profile synthesis
- harmonization validity checks for text/icon contrast and sizing
- rhythm scoring for cross-panel density consistency
- `editor/tests/step518_test.cpp` - 12 tests covering:
- density scaling + clamp behavior
- heading/body sizing hierarchy
- text/icon contrast thresholds
- harmonization pass/fail behavior
- rhythm score behavior for consistent vs divergent panel densities
**Files modified:**
- `editor/CMakeLists.txt` - `step518_test` target
**Verification run:**
- `cmake -S editor -B editor/build-native` - PASS
- `cmake --build editor/build-native --target step518_test` - PASS
- `./editor/build-native/step518_test` - PASS (12/12)
- `./editor/build-native/step517_test` - PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/IconTypographyHarmonizer.h` within header-size limit (`60` <= `600`)
- `editor/tests/step518_test.cpp` within test-file size guidance (`125` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`