Skip to content

Leave a character the document font has in that font when substituting (#76) - #303

Merged
jakejackson1 merged 1 commit into
gravitypdffrom
fix/76-substitute-document-font-characters
Sep 22, 2026
Merged

jakejackson1 merged 1 commit into
gravitypdffrom
fix/76-substitute-document-font-characters

Conversation

@jakejackson1

Copy link
Copy Markdown
Member

Part of #76. Split out of #299, where it was the second commit. It fixes the CBDT, sbix and COLRv0 fonts already merged, so it doesn't need to wait for COLRv1.

Substitution no longer moves a character the document font has into a backup font.

What was wrong

A run moved to a backup font carried the spaces inside it along ($flag == 1 && $char == 32 in SubstituteCharsMB()). So a space between two emoji was the emoji font's own. Noto Color Emoji's space is as wide as an emoji, which left an emoji-sized blank after every space between emoji.

What changes

  • A space now ends the run, and stays in the document font.
  • The Plane 2 branch had the same clause, but it never applied: that branch also requires the character to be above U+1FFFF.
  • The core-font path, SubstituteCharsNonCore(), carried spaces the same way and gets the same rule.
  • That path also read fonttrans[$bsf] for a backup font that isn't a core font's name, which warned on every such font. The read is now guarded.
  • The CBDT, sbix and COLRv0 snapshots are refreshed. Their spaces between emoji are DejaVu's now.

What it costs

Every substituted run now ends at a space, so a run of words is one span per word, not one per paragraph.

  • The cbdt snapshot has 99 Tf where it had 17.
  • Each snapshot grows about 9 KB. DejaVu is now embedded to draw the spaces, where those documents otherwise had only emoji.
  • The same applies to non-emoji scripts, e.g. Korean text in a DejaVu document: its spaces are now DejaVu's.
  • To pay for the extra passes, SubstituteCharsMB() finds emoji as its scan reaches them (Emoji::clusterAt(), now public), instead of finding every emoji in the rest of the text on every pass. The output is the same. On a 2000-word paragraph that is 1376 ms → 292 ms for emoji and 2262 ms → 573 ms for Korean.

Tests

EmojiSubstitutionTest asserts the space splits the run, for a TrueType document font and for Helvetica.

Merging with #299

#299's color-emoji-colrv1 snapshot and #300's color-emoji-svg were made without this change. Whichever of #299 and this PR merges second needs a rebase and composer snapshot:update color-emoji-colrv1 (and color-emoji-svg on #300).

🤖 Generated with Claude Code

#76)

A run moved to a backup font carried the spaces inside it along, so a
space between two emoji was the emoji font's. Noto Color Emoji's space
is as wide as an emoji. The Plane 2 branch's space clause never applied,
since a space is never above U+1FFFF.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jakejackson1 jakejackson1 added bug Something isn't working create-upstream-pr labels Sep 22, 2026
@jakejackson1
jakejackson1 merged commit f6d375d into gravitypdf Sep 22, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working create-upstream-pr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant