Skip to content

feat: optional explicit charCodes map in svgToOtf (+ deterministic output) - #64

Open
AndrewDongminYoo wants to merge 4 commits into
ScerIO:masterfrom
AndrewDongminYoo:feat/explicit-charcodes
Open

feat: optional explicit charCodes map in svgToOtf (+ deterministic output)#64
AndrewDongminYoo wants to merge 4 commits into
ScerIO:masterfrom
AndrewDongminYoo:feat/explicit-charcodes

Conversation

@AndrewDongminYoo

Copy link
Copy Markdown

What

Adds an optional charCodes parameter to svgToOtf so callers can assign an explicit codepoint to each glyph by name, instead of relying on the implicit sequential assignment. When omitted, behaviour is unchanged.

svgToOtf(
  svgMap: {'arrow-left': '<svg .../>'},
  charCodes: {'arrow-left': 0xE001}, // pin this glyph to U+E001
);

This enables downstream tools to keep a glyph's codepoint stable across regenerations (e.g. an append-only icon set that must not remap existing icons on every build).

Commits

  1. feat: optional explicit charCodes map in svgToOtf — the parameter, plus deterministic glyph sorting by codepoint so the emitted cmap order is stable.
  2. fix: deterministic font output — exposes MockableDateTime so the head (created/modified) and name (copyright year) tables can be pinned. Without this the engine stamps the wall clock into the font, so two builds of identical input produce different bytes — which breaks byte-level drift checks and reproducible builds.
  3. fix: harden explicit charCodes against invalid cmap generation — validates each explicit codepoint is inside the BMP (0x21..0xFFFE). The cmap format-4 subtable is BMP-only; a value above 0xFFFF is silently truncated by the 16-bit encoder (setUint16(0xF1234) == 0x1234), producing a malformed cmap. Rejects duplicates and glyphs missing from the map too.
  4. fix: reject surrogate codepoints — the UTF-16 surrogate block (0xD800..0xDFFF) sits inside the BMP range but is never a valid Unicode scalar value, so it is excluded explicitly.

Tests

New test/charcodes_test.dart (registered in test_all.dart) covers: explicit assignment, codepoint sorting, missing-glyph rejection, duplicate rejection, non-BMP rejection, and surrogate rejection. Full dart test suite passes; the touched files are dart analyze-clean (the repo's pre-existing example/ analyzer warnings are unchanged by this PR).

Provenance & scope

Extracted from a downstream fork built for a Flutter icon-font generator. The general engine capabilities (explicit codepoints, deterministic output, cmap safety) are what I'm proposing here. An icon-font-specific policy layer — restricting explicit codepoints to the Private Use Area to match the OS/2 ulUnicodeRange coverage that tool advertises — is intentionally kept downstream, since a general engine user may legitimately want other BMP blocks. Happy to split these commits into separate PRs or adjust scope however you prefer.

Route the name table's copyright year through MockableDateTime (head already
used it) and export the MockableDateTime hook so consumers can pin created/
modified/copyright dates to a fixed instant for reproducible builds.
Address code-review findings on the charCodes feature:
- Sort glyphs by assigned codepoint before building the cmap; the format
  4/12 segment generator assumes ascending order, so a non-ascending
  charCodes map previously produced an invalid font.
- Reject codepoints outside BMP 0x21..0xFFFE: the format-4 subtable is
  BMP-only and setUint16 silently truncates >0xFFFF, and 0x20/0xFFFF are
  the reserved space glyph and cmap terminator.
- Reject duplicate codepoints (overlapping cmap segments).
- Register charcodes_test in test_all so CI actually runs it.
- Normalize cascade formatting in touched encode paths.
The BMP range check accepted UTF-16 surrogates (0xD800..0xDFFF), which
are reserved and never valid Unicode scalar values; writing one into the
cmap yields a font consumers cannot look up. Exclude the surrogate block
explicitly and cover it with a regression test.
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