Skip to content

Resolve font families without fontconfig - #2

Merged
omartelo merged 2 commits into
mainfrom
fix/fonts-without-fontconfig
Oct 19, 2026
Merged

omartelo merged 2 commits into
mainfrom
fix/fonts-without-fontconfig

Conversation

@omartelo

Copy link
Copy Markdown
Contributor

What

New, NewSplit and NewDiffView resolved a family name only through fontconfig's fc-match, so on Windows and macOS (and a Linux without fontconfig) they failed with ErrNoFontconfig.

  • Without fontconfig, the platform's font directories are scanned once per process and the family is matched by the name each font carries (name IDs 16/1, style from 17/2). The same fonts serve the fallback for characters the family lacks, CJK included.
    • Windows: %WINDIR%\Fonts, %LOCALAPPDATA%\Microsoft\Windows\Fonts
    • macOS: /System/Library/Fonts, /Library/Fonts, ~/Library/Fonts
    • Linux: the XDG font directories, used only when fc-match is missing
  • "monospace" maps to a per-platform list (Cascadia Mono, Consolas, Courier New on Windows; Menlo, Monaco, Courier New on macOS). The OS seams are build-tagged files (internal/glyphs/fontdirs_*.go). Pure Go, no new dependency.
  • Fonts inside a collection (.ttc) are now addressed by file and index, so Menlo's bold and italic work on macOS.
  • API: new ErrFontNotFound; ErrNoFontconfig is deprecated, since New no longer returns it.
  • Fix: fc-match's collection index was dropped, so every style of a family in a .ttc came from the file's first font. Now fcMatch asks for %{index}:%{file}.

Cost: the scan keeps only names (~180 KB), about 0.1 s for 1,400 fonts on the first New. A character no font covers costs about 0.1 s once per process. Limits are written down in docs/ceilings.md.

Tests

  • Public API, with fontconfig hidden and Go Mono installed in the user font directory: an installed family is found, and a missing one returns ErrFontNotFound. The first test fails on main.
  • Internal: each style comes from its own file, a missing style falls back to regular, and the fallback picks the first font covering a character. A hermetic fontconfig test builds a .ttc and checks that bold resolves to index 1; it fails without the fix.
  • gofmt, go vet ./... and go test ./... pass. The font tests also pass with -race and -count=20.
  • Cross-compile: linux and windows pass go build ./... and go vet ./.... darwin passes for every library package; cmd/godemirror-demo and internal/shot need cgo for Gio and fail just as they do on main.

Not run on real Windows or macOS hardware yet.

Not in this PR

The fontconfig fallback lookup (fc-list / fc-match -s) still uses only the file path. A covering font that is not the first in its .ttc is skipped and the character is drawn as a box.

On Windows, macOS and a Linux without fc-match, New, NewSplit and
NewDiffView read the fonts in the platform's font directories, match the
family by the name each font carries and fall back to them for characters
the family lacks. Add ErrFontNotFound and deprecate ErrNoFontconfig, which
New no longer returns.
fc-match names a font in a collection by file and index, and the index
was dropped, so every style of a family in a .ttc came from the file's
first font.
@omartelo
omartelo merged commit 6bcec7b into main Oct 19, 2026
1 check 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.

1 participant