Skip to content

Add build-time kerning for swara pairs - #77

Open
mbaljeetsingh wants to merge 1 commit into
omenad:developfrom
mbaljeetsingh:font/bhatkhande-kerning
Open

Add build-time kerning for swara pairs#77
mbaljeetsingh wants to merge 1 commit into
omenad:developfrom
mbaljeetsingh:font/bhatkhande-kerning

Conversation

@mbaljeetsingh

@mbaljeetsingh mbaljeetsingh commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

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 sssr reads spread out next to a naturally-touching nnns, 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.)

Kerned vs unkerned, all four faces

This PR adds a build-time kerning step:

  • bhatkhande/kerning.py measures 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-based kern GPOS lookup with pair values min(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.py calls it between glyph replacement and save, so the existing scripts/build flow picks it up with no new commands or dependencies (pure FontForge API).
  • The kerning is authored as a chained context applying single adjustments to the second glyph rather than plain GPOS pairs, because of the zero-advance marks (komal l/L, octave u/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

  • GPOS dumps of all four built TTFs (fonttools): 124–144 kerned joins per face under kern (DFLT+latn), no positive values, the pre-existing mark-centering lookup intact, marks classified in GDEF.
  • Browser test at 72px: Punjabi sssr tightens by 0.492em (exactly the predicted 1007/2048), 6- and 8-note runs kern at every join, nnns is effectively unchanged (−0.006em), mark-carrying slsls kerns across the komal marks with the marks staying glued (verified against the release rendering), and font-feature-settings: "kern" 0 restores the monospaced layout for consumers that need it.

Build notes

  • This PR is source-only: no rebuilt TTFs/UFOs are committed, so the diff stays reviewable — rerun scripts/build after merging (note that regenerating UFOs with current FontForge rewrites every .glif, format 1→2). Happy to push built TTFs on request.
  • The sungsit/fontforge image in scripts/build no longer pulls with modern Docker (v1 manifest error). We built and verified with debian:bookworm-slim + python3-fontforge (apt-get install python3-fontforge, then python3 ./build.py <lang> and scripts/convert.py as in scripts/build). Happy to send a follow-up updating scripts/build.

The bigger question: default-on kerning vs the tabular grid

kern is 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: Hindi N+N stays 1582 while r+r becomes 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 of kern, and consumers that want tight beats enable it explicitly (font-feature-settings: 'ss01' 1). The build code is identical either way; if you pick ss01, the only asks are a stable, documented tag and a docs note that consumers must opt in.

@mbaljeetsingh
mbaljeetsingh force-pushed the font/bhatkhande-kerning branch from 5733d4f to 06fbd4b Compare August 25, 2026 12:57
@mbaljeetsingh
mbaljeetsingh force-pushed the font/bhatkhande-kerning branch from 06fbd4b to 29de170 Compare August 25, 2026 12:59
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