Skip to content

Fix decomposed accents with standard fonts - #1798

Open
lbesecker195 wants to merge 1 commit into
foliojs:masterfrom
lbesecker195:fix/standard-font-nfd-text
Open

Fix decomposed accents with standard fonts#1798
lbesecker195 wants to merge 1 commit into
foliojs:masterfrom
lbesecker195:fix/standard-font-nfd-text

Conversation

@lbesecker195

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Bug fix for #1661.

With a standard font, text with decomposed accents (NFD) prints the wrong characters from the first accent onward. 'Text für'.normalize('NFD') is u followed by U+0308 COMBINING DIAERESIS, while the NFC form has a single ü.

AFMFont.encodeText writes each character's WinAnsi code with toString(16), and the codes are joined into one hex string. ü is fc, but U+0308 has no WinAnsi code, so it goes in as 308, an odd number of digits. Every byte after it is read from the wrong position. The issue's sample produces this on master:

NFC: [<54> 120 <65> 30 <78742066fc7220c974e9206e61ef76> 25 <65> 0] TJ
NFD: [<54> 120 <65> 30 <78742066753087220453017465301206e616930876> 25 <65> 0] TJ

StandardFont.encode() and StandardFont.widthOfString() now compose the text with normalize('NFC') first. WinAnsiEncoding has the precomposed Latin-1 letters but no combining marks, so this is what the encoding can actually represent. Measuring uses the same text, so wrapping and alignment agree with what is drawn. Before, the width of naïve in NFD came out narrower, because the combining mark measured as .notdef next to the narrower i.

Embedded fonts are unchanged. fontkit lays out combining marks itself.

Checklist:

  • Unit Tests
  • Documentation N/A
  • Update CHANGELOG.md
  • Ready to be merged

Tests

Two tests in tests/unit/text.spec.js:

  • Text für Été naïve in NFD produces the same content stream as in NFC, with fc, c9, e9 and ef bytes.
  • widthOfString('naïve') is the same for NFD and NFC.
yarn test
Without the standard.js change 507 passed, 2 failed (the new tests)
With it 509 passed, 60 files

yarn lint and prettier --check on the changed files pass.

Not changed here: a character with no WinAnsi code and no precomposed form, for example a combining mark on a letter that has no accented form in Latin-1, still produces a longer hex code and shifts the rest of the string. That needs a decision on what to emit for unencodable characters, so I kept this PR to the case in the issue.

🤖 Generated with Claude Code

Standard fonts encode each character as its WinAnsi code in hex. A
combining mark such as U+0308 has no WinAnsi code, so its character
code went into the hex string as three digits, and every character
after it was read from the wrong byte. Text in NFD, e.g. "u" followed
by a combining diaeresis, came out as unrelated characters.

Compose the text (NFC) before encoding and measuring it, so those
sequences use the precomposed letters that WinAnsi has.

Fixes foliojs#1661

Co-Authored-By: Claude Opus 5 <noreply@anthropic.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.

1 participant