Skip to content

Hebrew fallback: Ivrix Mono He (uniform advance) + bundle Resources/Fonts - #2

Merged
DananzMolt merged 19 commits into
hebrew-bidifrom
ivrix-hebrew-font
Jul 28, 2026
Merged

Hebrew fallback: Ivrix Mono He (uniform advance) + bundle Resources/Fonts#2
DananzMolt merged 19 commits into
hebrew-bidifrom
ivrix-hebrew-font

Conversation

@DananzMolt

Copy link
Copy Markdown
Owner

Replaces the Hebrew fallback face and fixes the reason it could never be checked during development.

Font

Miriam Mono CLM drew Hebrew at 0.482em. Maple Mono NF's Latin x-height is 0.550em (cap 0.730em), so Hebrew rendered smaller than the surrounding lowercase Latin. That size mismatch, not the typeface's design, is why it read as thin and unclear.

Rubik draws Hebrew at 0.572em (1.04x that x-height) with a 0.606em advance against the 0.600em cell — it fills the grid without crowding it. Proportional rather than monospace, which is fine for a fallback face since the terminal snaps glyphs to the cell anyway.

Measured and rejected:

font Hebrew height advance (cell = 0.600em) note
Miriam Mono CLM 0.482em 0.600em below Latin x-height
Rubik 0.572em 0.606em chosen
Heebo 0.581em 0.644em 7% over cell
Noto Sans Hebrew 0.586em 0.615em
Miriam Libre 0.585em 0.613em
Assistant 0.552em 0.560em barely above old font
Alef 0.530em 0.594em still under x-height

Covers all 27 Hebrew letters + 17 nikud. Regular/Bold/Italic/BoldItalic instantiated from the upstream variable font with correct family naming. OFL text bundled as the license requires.

Bundling fix (separate commit)

Resources/Info.plist sets ATSApplicationFontsPath = Fonts, but nothing copied that directory during a normal Xcode build — only build-ivrix.sh did, at release packaging. Debug builds therefore shipped a dangling font path and silently fell back to system fonts for both the Latin mono and the Hebrew face, so no dev build ever rendered with the intended fonts. Added Resources/Fonts as a folder reference in the Resources build phase.

Verified: app builds, bundle now contains all 9 font files + license.

Not verified

Rendering was confirmed by direct font rasterization at terminal size, not through Ghostty's renderer — the tagged dev app hung at launch behind what appears to be a first-launch dialog for its bundle ID.

Pin: DananzMolt/ghostty @ ivrix-bidi-sync (26e85d068)
Resources/Info.plist sets ATSApplicationFontsPath = Fonts, but nothing
copied that directory during a normal Xcode build - only build-ivrix.sh
did, at release packaging time. So Debug builds shipped a dangling font
path and silently fell back to system fonts for both the Latin mono and
the Hebrew face, making dev-time font work impossible to verify.

Adds Resources/Fonts as a folder reference in the app's Resources build
phase so every build bundles it.
Miriam Mono CLM drew Hebrew letters at 0.482em. Maple Mono NF's Latin
x-height is 0.550em and its cap height 0.730em, so Hebrew rendered
smaller than the surrounding lowercase Latin - the reason it read as
thin and unclear rather than any quality of the typeface itself.

Rubik draws Hebrew at 0.572em (1.04x that x-height, 0.78x cap) with a
0.606em advance against the 0.600em cell, so it fills the grid without
crowding it. It is proportional rather than monospace, which is fine
here: it is a fallback face and the terminal snaps glyphs to the cell
regardless.

Measured alternatives: Heebo (0.581em height but 0.644em advance, 7%
over the cell), Noto Sans Hebrew (0.615em advance), Miriam Libre
(0.613em), Assistant (0.552em height, barely above the old font), Alef
(0.530em, still under the Latin x-height).

Covers all 27 Hebrew letters plus 17 nikud marks. Regular/Bold/Italic/
BoldItalic instantiated from the upstream variable font. Bundles the
OFL text alongside, as the license requires.
Rubik is proportional, and the terminal gives every Hebrew letter exactly
one cell. Yod advances 0.213em inside a 0.600em cell, so a third of the
cell was empty; across the alphabet Rubik's advances vary 24%. The result
was per-letter gaps that read as broken text.

This is not fixable by picking a different proportional face. Measured
advance variation across the Hebrew alphabet:

  Miriam Mono CLM   0.0%   (0.600 flat - true monospace)
  Alef             18.1%
  Miriam Libre     21.5%
  Heebo            23.3%
  Rubik            24.2%
  Noto Sans Hebrew 24.5%
  Assistant        24.6%

Miriam Mono CLM was the only uniform one, but it drew Hebrew at 0.482em,
below Maple Mono's 0.550em Latin x-height, which is what made it look
small and thin in the first place.

Ivrix Mono He takes Rubik, subsets it to the Hebrew block, and centres
every spacing glyph on a uniform 0.600em advance, keeping Rubik's 0.572em
height. Combining marks (nikud) keep their zero advance so they stack on
the base letter instead of consuming a cell.

Rubik carries no Reserved Font Name, so the OFL permits the derivative;
the name table records the origin and modification, and the OFL text
ships alongside. Subsetting also drops the files from 212KB to 10KB each,
since Maple Mono owns Latin.

Verified in the running app: letters now sit on an even rhythm with no
inter-letter gaps.
@DananzMolt DananzMolt changed the title Use Rubik for Hebrew; bundle Resources/Fonts in every build Hebrew fallback: Ivrix Mono He (uniform advance) + bundle Resources/Fonts Jul 27, 2026
@DananzMolt

Copy link
Copy Markdown
Owner Author

Follow-up after testing in the app: plain Rubik was wrong.

The terminal gives every Hebrew letter exactly one cell, and Rubik is proportional — yod advances 0.213em inside a 0.600em cell, so a third of the cell sat empty. Advances vary 24% across the alphabet, producing per-letter gaps that read as broken text.

No proportional face fixes this. Measured advance variation:

font variation
Miriam Mono CLM 0.0% (true monospace)
Alef 18.1%
Miriam Libre 21.5%
Heebo 23.3%
Rubik 24.2%
Noto Sans Hebrew 24.5%
Assistant 24.6%

Miriam Mono was the only uniform option but drew Hebrew at 0.482em — under Maple Mono's 0.550em Latin x-height — which is exactly what made it look small.

Ivrix Mono He takes Rubik, subsets it to the Hebrew block, and centres every spacing glyph on a uniform 0.600em advance while keeping Rubik's 0.572em height. Nikud keep zero advance so they stack rather than consume a cell. Subsetting drops each file 212KB → 10KB.

Verified in the running app (not just rasterization this time): אבגדהוזחטיכלמנסעפצקרשת renders on an even rhythm with no inter-letter gaps.

Swaps the source face for Ivrix Mono He from Rubik to Miriam Libre, kept
at the same 0.572em letter height and uniform 0.600em advance, so this is
purely a change of letterforms.

Picked from a 13-face sampler rendered at terminal size with every
candidate normalised to the same cell and height, so the comparison was
of shape alone. Miriam Libre is a Hebrew-first design rather than a
Hebrew extension of a Latin family, and its 0.549em maximum ink width
leaves more room inside the 0.600em cell than Heebo (0.617em) or IBM Plex
Sans Hebrew (0.604em).

Miriam Libre ships no italic, so the oblique styles are synthesised with
a 12 degree shear - the same approach Miriam Mono CLM took with its
Oblique cuts. Nikud keep their zero advance.

No Reserved Font Name in the upstream OFL, so the derivative is
permitted; the name table records origin and modification and the OFL
text ships alongside.
scripts/notarize-ivrix.sh only works off a published GitHub release, so
there was no way to cut a signed DMG from a branch build. This packages
whatever scripts/build-ivrix.sh just staged.

Notarizes twice on purpose: the app is notarized and stapled so it still
launches cleanly once dragged out of the image, and the DMG is notarized
and stapled so the image itself opens without a Gatekeeper prompt.

Signing is inside-out, reusing the fix from notarize-ivrix.sh: a single
codesign --deep leaves the nested executables under Contents/Resources/bin
unsigned and notarization rejects them.
Pin: DananzMolt/ghostty @ ivrix-bidi-sync (51ac56c55)
Pin: DananzMolt/ghostty @ ivrix-bidi-sync (90b1ddc83)
Pin: DananzMolt/ghostty @ ivrix-bidi-sync (7ac8c8854)
Pin: DananzMolt/ghostty @ ivrix-bidi-sync (eb3e384b8)
Pin: DananzMolt/ghostty @ ivrix-bidi-sync (d6d379ef2)
Bundles twelve Hebrew faces and adds a picker under Settings > Terminal,
so trying a different face no longer means rebuilding the app.

Every face is the same source put through scripts/make-hebrew-font.py:
subset to the Hebrew block and normalised to a uniform 0.600em advance
matching the Latin cell. Switching therefore changes letterforms only,
never the grid, and none of them can reintroduce the per-letter gaps that
a proportional face produces in a terminal.

HebrewFontFace lives in CmuxSettings next to SessionContentAlignment so
the settings window and the terminal share one definition rather than
duplicating the list. The catalog key also makes the face selectable from
~/.config/cmux/cmux.json.

The settings UI writes UserDefaults directly, so the change has to be
bridged: KeyboardShortcutSettingsFileStore now posts the reload
notification for terminal.hebrewFont the same way it does for
terminal.copyOnSelect. Without that the face would only apply on the next
launch.

Faces are registered in the settings search index with their names as
keywords, so searching 'noto' or 'alef' finds the row.

Adds ~400KB total across twelve families - Maple Mono alone is 9MB - and
each ships its upstream OFL text with the origin recorded in the font's
name table, as the licences require.
The settings window writes UserDefaults through its own
UserDefaultsSettingsStore, not the managed-defaults path that posts
TerminalHebrewFontSettings.didChangeNotification. Picking a face
therefore persisted the choice but left every open terminal on the old
font until the next launch.

Watch UserDefaults directly as well. didChangeNotification fires for any
key, so the handler compares against the face the surface already
applied; without that comparison every unrelated settings write would
rebuild the font grid on every surface.

The notification path stays for setFace() and for edits made through
~/.config/cmux/cmux.json.
Pin: DananzMolt/ghostty @ ivrix-bidi-sync (06a5d7a55)
Pin: DananzMolt/ghostty @ ivrix-bidi-sync (f7479c629)
Binds Cmd+Z to readline's undo (Ctrl+_, 0x1f), which shells and
readline-style TUIs act on. Ghostty unescapes text bindings before
writing, so the control byte is sent rather than the literal characters.

This undoes an edit on the line being typed - it cannot un-run a command
already submitted and does not touch terminal output. Anything that
ignores 0x1f does nothing, which is what the key did before.

Note: cmux policy is that new shortcuts also appear in
KeyboardShortcutSettings so they can be remapped. This is a ghostty
keybind, so it works but is not yet remappable there.
Documents the fork's Hebrew work: right-to-left rendering and the
direction toggle, bidi-aware cursor placement, UAX manaflow-ai#9 glyph mirroring,
direction-aware arrow keys, the twelve selectable Hebrew faces, and
Cmd+Z undo. Records the constraints too - the uniform-advance
normalisation the faces need, and what Cmd+Z can and cannot undo.
The README was still upstream cmux's, so nothing on the repo said what
this fork is for or would surface for anyone searching for a Hebrew
terminal.

Leads with what Ivrix does differently - per-line RTL rendering, a
bidi-aware cursor, mirrored glyphs, direction-aware arrows, and the
bundled Hebrew faces - then hands off to cmux's own feature tour, which
Ivrix inherits unchanged. Upstream cmux and Ghostty are credited at the
top and in the footer, and the fonts' OFL attribution is noted.

Includes a Hebrew line and Hebrew search terms: someone looking for this
is at least as likely to search in Hebrew as in English.
The inherited section still offered a cmux download badge, so someone
skimming the page could install upstream cmux and wonder where the Hebrew
support went. Labels that section as upstream and points back to the
Ivrix release.
@DananzMolt
DananzMolt changed the base branch from ivrix-upstream-sync to hebrew-bidi July 28, 2026 10:01
@DananzMolt
DananzMolt marked this pull request as ready for review July 28, 2026 10:03
@DananzMolt
DananzMolt merged commit bf46e98 into hebrew-bidi Jul 28, 2026
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