Skip to content

Add CJK font fallback coverage#1552

Open
Leask wants to merge 6 commits into
coredevices:mainfrom
Leask:codex/cjk-font-support
Open

Add CJK font fallback coverage#1552
Leask wants to merge 6 commits into
coredevices:mainfrom
Leask:codex/cjk-font-support

Conversation

@Leask

@Leask Leask commented Jun 17, 2026

Copy link
Copy Markdown

Summary

  • Add a generated CJK fallback font resource using NotoSansCJKsc-DemiLight.otf and a seed-based notification/calendar coverage list.
  • Register that resource as the extended system font fallback, leaving the existing compact internal fallback as the primary path for common text.
  • Teach font resource generation to track characterList inputs, so the generated PBF rebuilds when the CJK coverage seed changes.
  • Add focused tests for fallback registration, CJK codepoint coverage, generated PBF glyph samples, source font coverage, and resource-map dependency tracking.
  • Align the QEMU big-platform SRAM layout with qemu-pebble's 480 KiB Cortex-M33 SRAM map, which is required for reliable emulator validation.
  • Add a QEMU-only test cjk notification console command for manual notification UI smoke testing.

Design

The CJK resource is intentionally an extended fallback instead of replacing the existing built-in fallback. This keeps the normal ASCII/Latin path compact while allowing notification and calendar text to resolve missing Simplified Chinese, Traditional Chinese, and Japanese glyphs through the generated PBF.

The coverage list is seed-based rather than full pan-CJK. That keeps the resource within the normal pbpack budget while covering realistic notification/calendar text and the smoke-test strings used for Simplified Chinese, Traditional Chinese, Japanese Kanji, Hiragana, and Katakana.

The QEMU SRAM change is separate from text rendering, but it is needed for the validation path. qemu-pebble's M33 machines expose 480 KiB SRAM, while the previous linker layout assumed 511 KiB and could place memory past the mapped RAM.

Screenshot

QEMU CJK notification smoke test

Testing

  • .venv/bin/python -m unittest tools.tests.test_cjk_font_fallback
  • .venv/bin/python ./waf build
  • .venv/bin/python ./waf test -M test_text_resources --no_images --show_output
  • .venv/bin/python ./waf test -M 'test_text_layout|test_word_iterator' --no_images --show_output
  • QEMU manual smoke test: ./pbl qemu --qemu-decoration none, then ./pbl console and test cjk notification; verified Simplified Chinese, Traditional Chinese, Japanese Kanji, Hiragana, and Katakana render without tofu boxes, as shown above.

Build budget after the change:

  • Firmware: 1584152 / 4194304 bytes
  • Resources: 1045154 / 2097152 bytes

Load a generated CJK extension for the internal fallback font so dynamic text
that misses the primary font can render common Chinese and Japanese
notification/calendar content.

Use Noto Sans CJK and a dedicated codepoint list rather than mutating the
Chinese language pack list. Add tests for the CJK resource map, font glue,
codepoint coverage, source font support, generated PBF glyph indexes, and Waf
dependency tracking.

Also track generated-font characterList files as Waf task inputs for TTF, OTF,
and BDF resources so codepoint changes rebuild the generated PBF resource.

Co-authored-by: GPT-5 Codex <codex@openai.com>
Signed-off-by: Leask Wong <i@leaskh.com>
@Leask Leask requested a review from gmarull as a code owner June 17, 2026 19:28
Copilot AI review requested due to automatic review settings June 17, 2026 19:28
@Leask Leask requested a review from jplexer as a code owner June 17, 2026 19:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR adds an extended CJK fallback font resource (with an explicit codepoint list) and wiring to ensure the font/resource pipeline and runtime fallback path can load it, plus a QEMU-only console command and tests to validate the setup.

Changes:

  • Register a new FONT_FALLBACK_INTERNAL_EXTENDED font resource backed by a CJK font + large codepoint list.
  • Update the font resource generator to track characterList JSON files as build sources for non-PBF fonts.
  • Add tooling tests and a QEMU console command to validate CJK notification rendering and fallback behavior.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tools/tests/test_cjk_font_fallback.py Adds end-to-end tests validating resource map entries, fallback wiring, codepoint coverage, and generated PBF indexing.
tools/resources/resource_map/resource_generator_font.py Tracks characterList as a resolved source dependency for applicable font formats.
tools/font/fontgen.py Fixes file-handle management when loading codepoint lists.
src/fw/wscript Adjusts SRAM sizing behavior for “big platforms”, with special handling for QEMU.
src/fw/resource/system_resource.c Wires the system fallback font loader to include an “extended” fallback resource id.
src/fw/console/prompt_commands.h Exposes a QEMU-only test cjk notification prompt command.
src/fw/console/prompt_commands.c Implements the QEMU-only CJK notification smoke test command.
resources/normal/base/resource_map.json Registers the new extended fallback font resource.
resources/normal/base/lang/cjk_notification_codepoints.json Adds the CJK notification codepoint list used to subset the extended fallback font.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +53 to +71
font_ext = os.path.splitext(d.file)[-1].lower()
if d.character_list and font_ext in (".ttf", ".otf", ".bdf"):
source_path = resource_source_path
if os.path.isabs(source_path):
source_path = os.path.relpath(source_path, bld.path.abspath())
character_list_path = os.path.join(source_path, d.character_list)
if character_list_path.startswith(".."):
character_list_path = os.path.normpath(character_list_path)
else:
character_list_path = find_most_specific_filename(
bld, bld.env, bld.path, character_list_path
)
d.character_list = character_list_path
dependency_path = character_list_path
if os.path.isabs(dependency_path):
dependency_path = os.path.relpath(
dependency_path, bld.path.abspath()
)
d.sources.append(dependency_path)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed in 221e2dd: the characterList resolver now normalizes absolute paths first, checks the resolved path with commonpath against the resource source root, and keeps the fontgen open path separate from the waf dependency path. I also added tests for absolute in-tree paths, traversal outside the source root, and the resources-node build path that waf uses here.

Comment on lines +119 to +122
offset_table_format = "<"
offset_table_format += "L" if cp_bytes == 4 else "H"
offset_table_format += "H" if features & FEATURE_OFFSET_16 else "L"
offset_entry_size = struct.calcsize(offset_table_format)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Fixed in 221e2dd by validating cp_bytes explicitly and raising an AssertionError unless the generated PBF uses 2- or 4-byte codepoints. That keeps this parser from silently interpreting unexpected values as uint16 codepoints.

Comment thread tools/tests/test_cjk_font_fallback.py Outdated
root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.insert(0, root_dir)

import freetype # noqa: E402

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Fixed in 221e2dd by wrapping the freetype/fontgen-dependent imports and skipping only the tests that need font generation when freetype is unavailable. I verified both paths: the project venv runs all 10 tests, while the system Python without freetype collects the file successfully and reports skips instead of failing import.

Comment on lines +2 to +5
"codepoints": [
"164",
"167",
"168",

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Fixed in 221e2dd by converting the codepoint list to JSON numbers. The behavior is unchanged because fontgen already applies int(cp) while loading, but the source file is smaller and cheaper to parse.

Leask added 4 commits June 17, 2026 18:57
Match QEMU big-platform SRAM regions to the production memory layout so the
emulator boots with the same RAM split used by large boards.

This keeps the QEMU CJK notification smoke path from depending on a stale
memory shape.

Signed-off-by: Leask Wong <i@leaskh.com>
Add a QEMU-only console command that injects a notification containing Chinese
and Japanese text. The command gives reviewers a deterministic way to inspect
system fallback font rendering in the emulator.

Signed-off-by: Leask Wong <i@leaskh.com>
Refine the generated CJK fallback coverage and tests after review, keeping the
codepoint set data-driven and making the font-resource tests easier to reason
about.

Signed-off-by: Leask Wong <i@leaskh.com>
Apply targetPlatforms filtering during resource map builds so generated resource
definitions can be excluded from constrained boards.

Limit the generated CJK fallback extension to emery and gabbro platforms. This
keeps flint/asterix within its 1 MiB resource budget while preserving CJK
coverage on larger resource targets.

Signed-off-by: Leask Wong <i@leaskh.com>
@Leask Leask force-pushed the codex/cjk-font-support branch from 221e2dd to ab39948 Compare June 17, 2026 22:58

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab3994839f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

8242,
8243,
8251,
8451,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route symbol-range CJK glyphs through the extension

Adding U+2103 here does not make it render: prv_font_res_for_codepoint() only selects font_info->extension when !codepoint_is_latin(...) (src/fw/applib/graphics/text_resources.c:538), and codepoint_is_latin() treats U+2000–U+2BFF as Latin/symbols (src/fw/applib/fonts/codepoint.c:167). Since the base GOTHIC_14 fallback does not carry U+2103, CJK weather text such as 23℃ still falls through to the missing-glyph box despite being in this coverage file; either route missing base glyphs to the extension or keep these symbol-range glyphs in the base font.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, this was a real routing bug. Fixed in ab8082c by retrying the loaded font extension when the preferred base font resource does not contain the requested codepoint. That keeps base glyphs taking priority while allowing symbol-range fallback glyphs such as U+2103 to render from the CJK extension instead of falling through to the missing-glyph box.

I also added a focused regression test for a symbol-range glyph that is absent from the base font but present in an extension resource, and verified it with:

  • ./waf test -M test_text_resources --no_images --show_output
  • python3 -m unittest tools.tests.test_cjk_font_fallback

Retry a loaded font extension when the preferred base font resource does not
contain the requested codepoint. This lets symbol-range glyphs such as U+2103
render from the CJK fallback extension without adding them to the base font.

Add a regression test using an existing symbol-range fixture glyph that is
absent from GOTHIC_18 but present in an extension resource.

Signed-off-by: Leask Wong <i@leaskh.com>
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.

2 participants