fix(text): resolve sans-serif to Helvetica on macOS, not a face with full-width Cyrillic - #354
Merged
Merged
Conversation
…full-width Cyrillic Homebrew's fontconfig answers sans-serif with Hiragino Sans on a Mac, whose Cyrillic, Greek, ellipsis and em dash are full-width, so map labels such as Мост were set as four em-wide cells. The advances were the font's own, and CoreText sets that face the same way. On darwin the pattern handed to fc-match now names Helvetica ahead of the generic, as browsers resolve it there, and CJK still falls back to Hiragino through the generic.
sidorares
pushed a commit
that referenced
this pull request
Sep 12, 2026
🤖 I have created a release *beep* *boop* --- ## [8.8.2](v8.8.1...v8.8.2) (2026-09-12) ### Bug Fixes * **gl:** ask CGL for a stencil buffer — every stencil test passed on XQuartz ([#353](#353)) ([4abff02](4abff02)) * **text:** resolve sans-serif to Helvetica on macOS, not a face with full-width Cyrillic ([#354](#354)) ([8af0711](8af0711)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A map label set with
app.fonts.layoutinsans-serifon the X11 backend came out as "М о с т": each Cyrillic letter followed by a gap. The report suspected a fallback face's advances being scaled with the primary face's units per em. They are not. What ntk draws is exactly what the face says; the face is the wrong one.What is going on
fc-matchon PATH,fc-match sans-serifanswers Hiragino Sans, a Japanese face. It covers Cyrillic itself, so no fallback happens at all.…and—to full-width forms: every advance in "Мост" is 1000 of its 1000 units per em. ntk's shaped advances equal the face's ownhmtxexactly, 22 px each at size 22, 88 px for the word, and CoreText sets the same word in the same face at exactly 88.0 px. Only the opt-inpaltfeature narrows them, and browsers do not turn it on.48-guessfamily.conf. Under it, asans-serifquery ranks every face with "Sans" in its name ahead of all of60-latin.conf's preferences, and Verdana, Arial and Helvetica come in 111th to 114th. Removing that one file from the config makes the same query answer Verdana, and XQuartz's own fontconfig 2.17.1, which ships without it, answers Verdana too.ήfell back to Comic Sans MS, the next "…Sans…" face on that list, and Hiragino's Greek, ellipsis and em dash were full-width as well.The fix
On darwin, the family list ntk hands
fc-matchnames Helvetica ahead ofsans-serif, which is what Safari, Chrome and Firefox resolvesans-serifto on macOS. The generic stays after it, so a character Helvetica lacks still falls back through fontconfig's own sans-serif list, and CJK still lands on Hiragino.serifandmonospaceresolve to PT Serif and Andale Mono there, which set Cyrillic at their own widths, so they are left alone, as is every other platform. The change is inpatternFor, which the prewarm and the synchronous lookup share, so the two still agree on the cache key.Rendered by this branch, headless: node-x11's in-process X server, the default font source,
TextLayout.drawinto a pixmap, read back withgetImageData.Before, on master: Hiragino Sans, "Мост" at a 22 px pitch,
ήfrom Comic Sans MS.After: Helvetica, "Мост" 52.6 px wide as its own advances say, while 橋 and 字 still come from Hiragino.
Tests
In
test/fontconfig.test.js:platformFamilies: the darwin mapping, a family list around the generic, case, and a list that already names Helvetica first;serifandmonospaceuntouched; Linux, FreeBSD and Windows untouched.fc-matchactually receives, through the prewarm and then the sync lookup, via a stub on the child's PATH:Helvetica,sans-serif:weight=80on macOS andsans-serif:weight=80elsewhere, so CI checks the Linux half.sans-serifresolves to Helvetica, "Мост" is one run as wide as Helvetica's own advances within a pixel and well under four em, and 字 falls back to a face that covers it. This one fails on master, where the face is Hiragino and the word is 88 px.Full suite on Xvfb: 1181 pass, 7 skip, 0 fail. CI runs on Linux, where none of this changes behaviour.