Add build-time kerning for swara pairs - #77
Open
mbaljeetsingh wants to merge 1 commit into
Open
Conversation
mbaljeetsingh
force-pushed
the
font/bhatkhande-kerning
branch
from
August 25, 2026 12:57
5733d4f to
06fbd4b
Compare
mbaljeetsingh
force-pushed
the
font/bhatkhande-kerning
branch
from
August 25, 2026 12:59
06fbd4b to
29de170
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.
The Bhatkhande faces are deliberately monospaced — every swara gets the same 1582/2048 advance so the notation grid stays tabular — but the glyph ink varies a lot per language: Gurmukhi ni overhangs its box while sa/re/ga leave ~0.1em of side bearing on each side. The result is that a run like
sssrreads spread out next to a naturally-touchingnnns, and the fonts ship no swara kerning to compensate. (We currently work around this at display time in kirtannotation.com / sangeetnotation.com by measuring the webfonts and injecting per-pair negative margins — kerning inside the font makes all of that unnecessary, in inputs, canvas, and print alike.)This PR adds a build-time kerning step:
bhatkhande/kerning.pymeasures the left/right side bearings of the 12 swara glyphs (s r g m p d n R G M D N) from each generated face's real outlines — after language glyph replacement, so each language's own letterforms drive the values — and adds a class-basedkernGPOS lookup with pair valuesmin(0, TARGET_GAP − (rsb(a) + lsb(b))), TARGET_GAP = 0.03em. Values are negative-only: pairs the font already draws tighter than the target (ni-ni's designed overlap) keep their spacing.bhatkhande/build.pycalls it between glyph replacement and save, so the existingscripts/buildflow picks it up with no new commands or dependencies (pure FontForge API).l/L, octaveu/U): a pair kern on the first glyph's advance drags the mark drawn after it off its base note, and a pair value on the second glyph makes shapers consume that glyph, losing every second kern in a run. With the chain (marks skipped via the ignore-marks flag, shift on the second glyph only), a join kerns across a mark while the mark stays glued to its note. The existing mark-centering pairs (r+l +100,M+u +190, …) are untouched. Strokes, dashes, digits, and arcs break the chain.Verified
kern(DFLT+latn), no positive values, the pre-existing mark-centering lookup intact, marks classified in GDEF.sssrtightens by 0.492em (exactly the predicted 1007/2048), 6- and 8-note runs kern at every join,nnnsis effectively unchanged (−0.006em), mark-carryingslslskerns across the komal marks with the marks staying glued (verified against the release rendering), andfont-feature-settings: "kern" 0restores the monospaced layout for consumers that need it.Build notes
scripts/buildafter merging (note that regenerating UFOs with current FontForge rewrites every.glif, format 1→2). Happy to push built TTFs on request.sungsit/fontforgeimage inscripts/buildno longer pulls with modern Docker (v1 manifest error). We built and verified withdebian:bookworm-slim+python3-fontforge(apt-get install python3-fontforge, thenpython3 ./build.py <lang>andscripts/convert.pyas inscripts/build). Happy to send a follow-up updatingscripts/build.The bigger question: default-on kerning vs the tabular grid
kernis on by default in browsers and LibreOffice, and kerned advances are pair-dependent — so plain-text layouts that stack notation lines and rely on the monospaced grid for matra-column alignment (the layout the docs moved to with the 3.1.0 dividers) will drift: HindiN+Nstays 1582 whiler+rbecomes 1050, and dividers/strokes keep full advances. Word is unaffected by default (its kerning is opt-in), and web apps that manage their own columns are fine — but anyone relying on character-grid alignment in a browser or LibreOffice would need kerning turned off.If you'd rather protect plain-text grids by default, the same pairs can ship under an opt-in feature (e.g.
ss01) instead ofkern, and consumers that want tight beats enable it explicitly (font-feature-settings: 'ss01' 1). The build code is identical either way; if you pickss01, the only asks are a stable, documented tag and a docs note that consumers must opt in.