Skip to content

Replace hand-tuned kerning with HalfKern auto-kerning#21

Merged
jul-sh merged 18 commits intomainfrom
halfkern-auto-kerning
Mar 19, 2026
Merged

Replace hand-tuned kerning with HalfKern auto-kerning#21
jul-sh merged 18 commits intomainfrom
halfkern-auto-kerning

Conversation

@jul-sh
Copy link
Copy Markdown
Owner

@jul-sh jul-sh commented Mar 19, 2026

No description provided.

jul-sh added 2 commits March 19, 2026 01:04
Use behdad/halfkern's SDF-envelope overlap algorithm to compute
principled kern values for all Latin glyph pairs, replacing the
manually curated ~220-line kern table. Kerns are injected as GPOS
PairPos Format 2 (class-based) for compact representation.

- Vendor halfkern as a git submodule at scripts/halfkern
- Add scripts/autokern.py wrapping HalfKern for the fix_fonts pipeline
- Monkey-patch ctypes.CDLL to resolve Nix store library paths (macOS SIP)
- Add pycairo, numpy, scipy, scikit-fmm, cairo, freetype to flake.nix
- Cover Basic Latin, Latin-1, Latin Extended-A/B, dashes, quotes,
  ellipsis, and euro sign (~540 candidate codepoints)
- Mono fonts (isFixedPitch) are correctly skipped
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 975d0cf32e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/autokern.py
Comment on lines +165 to +166
import cairoft as halfkern_cairoft
import kern_pair as hk
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Initialize the HalfKern submodule before importing it

This import assumes scripts/halfkern has been populated, but the change only adds a gitlink; a fresh clone using the current README.md:15 command leaves that directory empty until git submodule update --init is run. In that state the first proportional font processed by add_auto_kerning() hits ModuleNotFoundError: cairoft here and make build exits, so the new dependency needs to be fetched as part of the normal build path.

Useful? React with 👍 / 👎.

Comment thread scripts/autokern.py
Comment on lines +121 to +124
(0x0020, 0x007E), # Basic Latin (space through tilde)
(0x00A0, 0x00FF), # Latin-1 Supplement (À-ÿ, common accented)
(0x0100, 0x017F), # Latin Extended-A (Ā-ſ)
(0x0180, 0x024F), # Latin Extended-B (ƀ-ɏ)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expand _KERN_RANGES beyond U+024F

Because _candidate_codepoints() only accepts U+0020–024F plus a few punctuation blocks, HalfKern never sees any supported glyph above U+024F. That drops kerning for characters the old tables explicitly handled, including Vietnamese extended Latin (, , etc.) and Greek/Cyrillic forms, so proportional text in those scripts will regress to unkerned spacing after this switch.

Useful? React with 👍 / 👎.

jul-sh added 16 commits March 19, 2026 01:35
Pillow's default layout engine ignores GPOS kern tables. Switch all
ImageFont.truetype calls to use Layout.RAQM so the specimen images
reflect the actual kerning. Regenerate images.
The autokern module imports from the vendored halfkern submodule.
Without submodules: true, the directory is empty in CI and cairoft
fails to import.
…ion)

The kern feature was appended to the GPOS FeatureList but not
registered in any Script/LangSys FeatureIndex, so HarfBuzz and
other shapers never discovered it. Now register the kern feature
in every script's DefaultLangSys and all LangSysRecords.

Regenerate specimen images — kerning differences now visible.
- negative_only=False: allow loosening kerns for cramped pairs
- threshold_units 10→5: capture subtler adjustments
- min_kern_units -150→-250: trust HalfKern's SDF for tighter pairs
- Add max_kern_units=100 cap for positive kerns
The SDF calibration already targets correct inter-glyph spacing.
Halving on top of that just makes kerning less effective.
Switch HalfKern reduce mode from "sum" (averaged overlap) to "max"
(worst-case closest approach) for more optically even kerning. Also
restrict to negative-only kerns — only tightening, no widening.
Re-enable both tightening and loosening kerns (negative_only=False)
to let HalfKern adjust spacing in both directions where needed.
Instead of dropping positive kerns entirely, halve their values so
pairs that need more space get a gentle nudge rather than the full
computed widening. Negative (tightening) kerns remain at full strength.
More conservative loosening — keep only 30% of positive kern values
instead of 50%, while negative (tightening) kerns remain at full strength.
- Fix PycairoContext scoping bug in cairoft.py (broke on multi-font runs)
- Move Nix library resolution into cairoft.py, remove monkey-patches
- Add submodule initialization check in autokern.py
- Expand kern ranges: Greek, Cyrillic, Vietnamese, Latin Extended C/D/E
Thin glyphs like . , : ; ! have lots of whitespace so HalfKern
computes large positive kerns that widen spacing around them.
Skip positive kerns when either side is thin punctuation while
still allowing tightening (negative) kerns.
Suppress positive kerns around quote characters (' " ' ' " " etc.)
in addition to existing thin punctuation marks.
@jul-sh jul-sh merged commit aed58f5 into main Mar 19, 2026
2 checks passed
@jul-sh jul-sh deleted the halfkern-auto-kerning branch March 19, 2026 16:53
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