Step 518: add icon and typography harmonization model
This commit is contained in:
@@ -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)
|
||||
|
||||
60
editor/src/IconTypographyHarmonizer.h
Normal file
60
editor/src/IconTypographyHarmonizer.h
Normal 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));
|
||||
}
|
||||
};
|
||||
125
editor/tests/step518_test.cpp
Normal file
125
editor/tests/step518_test.cpp
Normal 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;
|
||||
}
|
||||
34
progress.md
34
progress.md
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user