Skip to content

feat(font): Japanese UI P5 PR B — CJK font converter + parity gtest - #2

Merged
mtskf merged 3 commits into
masterfrom
chore/cjk-ui-font-converter
Jun 17, 2026
Merged

feat(font): Japanese UI P5 PR B — CJK font converter + parity gtest#2
mtskf merged 3 commits into
masterfrom
chore/cjk-ui-font-converter

Conversation

@mtskf

@mtskf mtskf commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Scope — PR B (Tasks 2–3 of Japanese UI Support P5)

Builds the offline tooling and the converted 20px CJK UI font asset, plus a host gtest proving bit-perfect parity against the fork source. This is purely additive: it creates the new P5 artifacts and does not touch the existing P4 fallback machinery (deleted later in PR C). P4 and the new cjk_ui_20.{h,cpp} coexist after this PR by design.

Task 2 — Python converter + generated asset

  • scripts/convert_cjk_font_to_epdfontdata.py — converts the fork's 3-byte-row MSB-first PROGMEM font into standard EpdFontData 1-bit tight packing.
  • scripts/check_cjk_font_data.py — manual sanity tool (sampled bit comparison).
  • lib/EpdFont/builtinFonts/cjk_ui_20.{h,cpp} — generated & committed: 3420 glyphs, 169 240 bitmap bytes, 2405 intervals. Metrics: advanceY=24, ascender=16, descender=4, is2Bit=false; per-glyph advanceX = width<<4, top=16, height=20.
  • A sha256: of the input source is embedded in both outputs so drift against the (un-checked-in) fork source is detectable.

Task 3 — host gtest, bit-per-bit parity

  • test/cjk_font_parity/CjkFontParityTest.cpp + CMakeLists.txt, wired into test/CMakeLists.txt.
  • Decodes every covered codepoint from both the converted EpdFontData and an independently-extracted fork reference (scripts/extract_cjk_fork_reference.py, shared parser) and asserts pixel-identical bitmaps, plus metric-floor / glyph-count / advanceX==width<<4 invariants.

Necessary deviations from the plan (the plan's literal code fails against the real fork header)

  1. Parser strips C/C++ comments before tokenising. The fork annotates each glyph in CJK_UI_GLYPHS with a // U+XXXX (c) comment. Without stripping, (a) int('0021', 0) raises on the U+0021 text and (b) the literal } in the U+007D (}) comment truncates the brace-body capture (only 87 glyphs parsed). Stripping first yields the correct 3420×60 = 205 200 source bytes. Output format is unchanged from the plan.
  2. check_cjk_font_data.py bracket regex accepts a sized array ([169240], the generated output) in addition to [] (the fork source).
  3. Parity test CMakeLists guards on if(NOT EXISTS ${FORK_HEADER}) return(). CI's unit-tests job builds the full test suite via cmake -S test, but the fork source lives only at a local /tmp path (not checked in). Without the guard CI's build would break. The parity test is a local correctness gate; CI's drift guard is the embedded sha256: comment in the committed artifact.

Verification (host-side — this machine has no PlatformIO; pio run / clang-format deferred to CI)

  • Converter: Converting 3420 glyphs… 169240 bitmap bytes, 2405 intervals.
  • Re-run-and-diff drift check: zero diff vs a fresh regeneration.
  • check_cjk_font_data.py: all 22 sampled codepoints PASS.
  • gtest: all 4 cases green, AllCodepointsPixelPerfect over all 3420 codepoints. Full suite: 103/103 passed.
  • Non-vacuity confirmed: corrupting one bitmap byte turns AllCodepointsPixelPerfect red; restored via regeneration.
  • Generated .cpp compiles clean under -Wall -Wextra -pedantic -std=c++20. (lib/EpdFont/builtinFonts/ is excluded from clang-format and is not yet referenced by firmware, so no flash delta in this PR — the ~169 KB lands when the asset is wired in by a later PR.)

mtskf added 3 commits June 17, 2026 23:12
Convert the fork's cjk_ui_font_20.h (3-byte-row MSB-first PROGMEM) into the
standard EpdFontData 1-bit tight-packed format, emitting a checked-in
cjk_ui_20.{h,cpp} pair (3420 glyphs, 169240 bitmap bytes, 2405 intervals).

The parser strips C/C++ comments before tokenising: the fork annotates each
glyph in CJK_UI_GLYPHS with a `// U+XXXX (c)` comment, which otherwise injects
spurious tokens and (via the literal `}` in the U+007D comment) truncates the
brace-body capture. A sha256 of the input source is embedded in both outputs so
CI can detect drift against the (un-checked-in) fork source.
Decode every covered codepoint from both the converted EpdFontData and the fork
reference (extracted via the shared parser) and assert pixel-identical bitmaps,
plus metric-floor, glyph-count and advanceX==width<<4 invariants.

The fork source is not checked in, so the target is guarded with
`if(NOT EXISTS ${FORK_HEADER}) return()` — it runs as a local-only verification
and is skipped where the fork is absent (e.g. standard CI). CI's drift guard is
the sha256 comment in the committed artifact.
Address review findings on PR B's verification posture:

- Add CjkFontSelfTest (fork-independent, always built so it runs in CI):
  pins per-glyph metadata (height/top/left/advanceX/dataLength), bitmap
  dataOffset contiguity, and interval sorted/non-overlap/offset invariants.
  Pixel-only parity could not catch metadata or interval regressions, and the
  fork-gated parity test does not run in CI — this gives the committed artifact
  standing CI protection. Bit-per-bit pixel parity stays as the local fork-gated
  CjkFontParityTest.
- Replace assert-based mandatory checks (stripped under `python -O`) with
  -O-safe sys.exit checks via a shared validate_arrays() helper, used by both
  the converter and the parity-reference extractor; add an empty-array guard.
- Remove the redundant sample-only check_cjk_font_data.py — the exhaustive
  gtest fully subsumes it and it duplicated the parser.
- Correct the CMakeLists comment that overclaimed an automated CI sha256 drift
  guard (the sha256 is a marker for manual/converter-rerun drift detection).
- Fix clang-format wrap in CjkFontParityTest; reword a stale extractor comment
  and drop the unused kForkRef_NumGlyphs.
@mtskf
mtskf merged commit ec13e69 into master Jun 17, 2026
6 checks passed
@mtskf
mtskf deleted the chore/cjk-ui-font-converter branch June 17, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant