Skip to content

feat(pdf): extend Unicode fallback to a font chain with Symbola (#75) - #76

Merged
sonatique merged 1 commit into
mainfrom
fix/issue-75-pdf-fallback-chain
Jun 21, 2026
Merged

feat(pdf): extend Unicode fallback to a font chain with Symbola (#75)#76
sonatique merged 1 commit into
mainfrom
fix/issue-75-pdf-fallback-chain

Conversation

@sonatique

Copy link
Copy Markdown
Owner

Fixes #75.

Problem

The PDF renderer embedded DejaVu Sans as its single Unicode fallback (#52) — the right primary choice (general text + common arrows/dingbats). But DejaVu doesn't cover the SMP symbol blocks, so e.g. 🛇 (U+1F6C7 🛇) still rendered as a single ? even after #72 fixed surrogate-pair handling. The Avalonia preview shows these via system fallback, so preview and PDF disagreed.

Fix

The fallback is now an ordered chain: primary embedded → DejaVu Sans → Symbola. FontForCodePoint already consulted the fallback candidates in order, so this adds Symbola (public domain, George Douros v8.00) as the second resource. Each codepoint routes to the first font in the chain that has a glyph:

  • Symbols DejaVu lacks but Symbola covers (U+1F6C7, U+1F6AB, U+26D4, U+29B8, …) now render instead of ?, matching the preview.
  • DejaVu stays the general/text fallback — it still wins for every glyph it covers (✓ ✗ ⇒ → • math), so existing output and metrics are unchanged.
  • Symbola is subset + embedded only when a glyph it uniquely provides is actually used, so documents without such symbols embed nothing extra.
  • A genuinely uncovered codepoint still collapses to a single missing-glyph indicator (per PDF: symbol font-fallback breaks on non-BMP chars (=> ??) and in any footnote-bearing block (=> ?) #72), never ??.

Why DejaVu-first (not "Symbola for everything")

Per the issue's explicit ask, this is additive — DejaVu remains primary. Routing common symbols through Symbola would change glyph metrics (→ wrapping/layout) across many existing documents, pull the larger Symbola subset into common docs, and use a less text-fitted glyph design — for a consistency gain that only applies when a single document mixes a DejaVu-only and a Symbola-only symbol.

Attribution

NOTICES.md gains the Symbola entry (public domain) and the previously-missing DejaVu entry in the PDF section. License/provenance verified from the font's name table ("Unicode Fonts for Ancient Scripts; George Douros; 2015 … Symbola is not a merchandise").

Size

Symbola.ttf adds 2.19 MB to the repo / AdocNet.Converters.Pdf package (accepted for full coverage). Output PDFs include only the subset actually used.

Tests

  • PdfFallbackChainTests (new): SMP symbols render via Symbola (no ?); the chain consults DejaVu before Symbola (both embedded when both are needed); Symbola is absent when no glyph needs it.
  • Repointed the PDF: symbol font-fallback breaks on non-BMP chars (=> ??) and in any footnote-bearing block (=> ?) #72 non-BMP test to a codepoint no bundled font covers (U+20000), since U+1F6C7 now renders.
  • Test-infra: the cross-target consistency check (MultiTargetConsistencyTests + ConsistencyHarness) now compares PDF object counts rather than byte length — embedded fonts are Flate-compressed and Deflate output differs between .NET runtimes (the unicode fixture now embeds Symbola), so total size isn't comparable; object count is a deterministic structural check.

Full Release suite green (0 failures; +3 tests). Related: #52 (DejaVu fallback), #72 (codepoint-aware fallback).

The single DejaVu Sans fallback (#52) covered text plus common arrows/dingbats
but not SMP symbol blocks, so e.g. U+1F6C7 still rendered as '?' even after #72
fixed the surrogate-pair handling — diverging from the Avalonia preview (which
uses system fallback).

The fallback is now an ordered chain: primary embedded -> DejaVu Sans -> Symbola.
FontForCodePoint already consulted FallbackCandidates in order, so this adds
Symbola (public domain, George Douros v8.00) as the second resource. Each
codepoint routes to the first font in the chain with a glyph, so symbols DejaVu
lacks but Symbola covers (U+1F6C7, U+1F6AB, U+26D4, U+29B8, …) now render instead
of '?'. DejaVu stays the general/text fallback; Symbola is consulted only for
glyphs DejaVu lacks and, like DejaVu, is subset + embedded only when used, so
documents without such symbols are unchanged. A genuinely uncovered codepoint
still collapses to a single missing-glyph indicator (#72).

NOTICES.md gains the Symbola attribution (and the previously-missing DejaVu entry
in the PDF section). Adds PdfFallbackChainTests (SMP symbols via Symbola, chain
ordering, on-demand embedding) and repoints the #72 non-BMP test to a codepoint
no bundled font covers.

Also makes the cross-target PDF consistency check compare object counts rather
than byte length: embedded fonts are Flate-compressed and Deflate output differs
between .NET runtimes, so total size is not comparable (the unicode fixture now
embeds Symbola).
@sonatique
sonatique merged commit cd6d42e into main Jun 21, 2026
4 checks passed
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.

PDF: extend Unicode fallback to a font chain (keep DejaVuSans primary; add symbol fallback for codepoints it lacks, e.g. U+1F6C7)

1 participant