Add pair-kerning scaffolding and sidebearing scale#99
Open
terryspitz wants to merge 8 commits into
Open
Conversation
fb721f6 to
c4e2806
Compare
99f4946 to
d1ffa43
Compare
d1ffa43 to
060779f
Compare
Introduces a minimal kerning pipeline on top of the Proofs tab baselines: - New `Spacing` module with a curated override map for notorious pairs (AV, AT, To, Ty, Va, Vo, Wa, Ya, LT, fi, fl, rn, cl, ...). `pairKernInt` looks up the table; `pairKern` returns it as a float in glyph units. - Three new Axes fields: `opticalKerning` (wire for the follow-up bezier profile sampler; no-op today), `kerningTarget` (target minimum gap for that sampler), `sidebearingScale` (multiplier on the existing thickness + serif sidebearing padding so the whole font can be tightened or opened uniformly without retuning `tracking`). - `Font.width` multiplies the sidebearing term by `sidebearingScale`. Default value 1.0 keeps the rendered output bit-identical at defaults. - `Font.pairKern` and `Font.pairKerns` expose per-pair and per-string kerns. `Font.stringWidth` now includes kerns so string measurements match the rendered advance. `stringToSvgLineInternal` applies the kern to the *second* glyph of each pair, leaving the first and last glyph in a line unkerned. - FontTest.fs adds conservation-law tests: stringWidth = Σ advances + Σ kerns; unknown pairs return 0; italic axis does not change pair kerns (overrides live in the pre-shear frame); sidebearingScale scales the per-char sidebearing term linearly. Follow-up will add `GlyphProfile` with cubic-root bezier sampling and wire `opticalKerning` through `pairKern` for automatic gap-balancing. https://claude.ai/code/session_01NSaWc8pxLiqZYTpVodbciX
Visual review of the pre-kern proof baselines showed the original seeded values were too timid to be visible. Two changes: 1. Tighten diagonal/open-counter pairs aggressively (~1.5×): - A/V/W/Y/T/F/L/P/R/K with round and straight followers - f/r with tall/vertical followers - v/w/y/c/f with round followers Values like AV -50 → -75, To -40 → -55, rn -10 → -18. 2. Add positive overrides to open flat-sided slab pairs where two vertical strokes end up too close: "din", "Num", "HINDU", "NN", "mm", etc. Covers I/N/M/U/D/H/E/B uppercase and i/n/m/u/d/h/l/b lowercase. Typical +15-20 (upper) / +6-10 (lower) units. Plus fix the `Kerning_NoKernPairs_StringWidthUnchanged` test string to avoid pairs now present in the override map (MN was unexpectedly matched). Switched to `CGJOQSXZ` — none of these are present as the left or right side of any override. https://claude.ai/code/session_01NSaWc8pxLiqZYTpVodbciX
Per proof review: left of 'l' was crowded from almost every letter — add positive overrides for (*, l) at 8–14 units. Right of 'l' was the opposite — flip (l, *) to negative to tighten. Left of 'j' got too wide against non-descenders — add (non-descender, j) at -12 to -15. Bump din, dh, di, db to 12–14 to stop the slab-sequence feeling cramped.
Proofs tab renders via a CSS @font-face served by opentype.js, which did not previously include a kern table — so pair-kerning was invisible there. Expose Spacing.kerningOverrides from generateFontGlyphData as a {left, right, value}[] array and feed it into opentype.js's font.kerningPairs keyed by glyph index; opentype.js emits a `kern` table the browser honours for CSS-rendered text. Remove opticalKerning and kerningTarget axes — both were stubs with no implementation (they were scaffolding for a planned bezier-profile sampler that didn't land). Keeping them on the Axes surface was misleading in the UI. sidebearingScale stays since it is wired up.
The Tweens tab renders a single glyph per tween cell. sidebearingScale changes per-glyph advance width but nothing else on a single glyph — it only becomes visible when glyphs are laid out in sequence (like tracking and leading, already excluded). Drop the orphan baseline PNG the auto- commit workflow seeded before this exclusion.
The current default (1.0) leaves glyphs cramped on the proofs tab. Open each glyph's symmetric padding by 20% as a global tuning step before the manual kern overrides go in.
060779f to
e971f16
Compare
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.
Summary
Stacked on top of #98 (Proofs tab). Adds a minimal kerning pipeline so the
proof snapshots show the kerning diff as a reviewable visual change, both on
the SVG-rendered tabs and on the proofs tab (which uses a CSS web font).
Changes
src/generator/Spacing.fs(new) — curated override map for notoriouspairs (AV, AT, To, Ty, Va, Vo, Wa, Ya, LT, fi, fl, rn, …) plus
open-pairs for slab sequences (din, Num, NN, MM, UU) and targeted
fixes for
l/j(more room left ofl, less right ofl, tighterleft of
jagainst non-descenders).pairKernIntlooks up the table;pairKernreturns it as a float in glyph units.src/generator/Axes.fs— one new field:sidebearingScale— multiplier on the existing thickness + serifsidebearing padding; lets the whole font be tightened or opened
uniformly without retuning
tracking. Default1.0keeps outputbit-identical.
src/generator/Font.fs—widthmultiplies the sidebearing term bysidebearingScale.pairKern/pairKernsfor per-pair and per-string kerns.stringWidthnow includes kerns so measured width matches renderedadvance.
stringToSvgLineInternalapplies the kern to the second glyph ofeach pair. Every glyph except the first is kerned against its
predecessor; the first glyph has no predecessor so it lands at
offsetXunshifted.src/explorer/Api.fs—generateFontGlyphDatanow also returnskerningPairs: { left, right, value }[]so the web font can carrythe kerning.
web/src/fontExport.js— feeds those pairs into opentype.js asfont.kerningPairs, keyed by glyph index. opentype.js emits akerntable that modern browsers honour for CSS-rendered text, so the
proofs tab (which uses
@font-face) picks up the kerning.src/generator/tests/FontTest.fs— conservation-law tests:stringWidth = Σ advances + Σ kerns; unknown pairs return 0;italic axis does not change pair kerns (overrides live in the
pre-shear frame);
sidebearingScalescales the per-char sidebearinglinearly.
Test plan
dotnet test src/generator/tests/passes (new conservation testsgreen).
proofs-uppercaseandproofs-lowercasePlaywright snapshotsdiff cleanly — diff is tightened / opened spacing on seeded pairs
(AV, To, rn, fi, din,
*l,*j, …), no glyph corruption oroverlap.
review.
italic=0anditalic=0.3on the Proofs tab render balancedspacing on
AVATAR,To,fi,rn.https://claude.ai/code/session_01NSaWc8pxLiqZYTpVodbciX