midad

Style packages

A style is one calligraphic hand (Naskh, Ruqaa, Thuluth…) packaged for Midad. Each lives in its own folder under styles/:

styles/naskh-amiri/
├── style.toml               # manifest — read by the compiler AND the engine
├── OFL.txt                  # license of the font (SIL Open Font License 1.1)
├── source/
│   └── Amiri-Regular.ttf    # input of the compiler (never loaded at runtime)
└── dist/
    ├── MidadNaskh-Regular.ttf   # compiled font, loaded by the engine
    └── build-info.json          # hashes + stats written by the compiler

dist/ is committed so that working on the editor never requires Python. CI fails if it is stale (python -m midad_style check styles/*).

Workflow

pip install -e tools
python -m midad_style inspect styles/naskh-amiri/source/Amiri-Regular.ttf   # what does the font offer?
# edit styles/naskh-amiri/style.toml
python -m midad_style compile styles/naskh-amiri
npm run dev            # sync-styles.mjs copies the package into the web app

Check a style from the command line, without the UI:

cargo run -p midad-cli -- info -s styles/naskh-amiri
cargo run -p midad-cli -- alternates -s styles/naskh-amiri -t "بسم الله" --at 2
cargo run -p midad-cli -- render -s styles/naskh-amiri -t "بسم الله" -o out.svg

style.toml reference

[style]

Key Type Meaning
id string Stable identifier, used in .midad documents. Never change it after release.
version semver string Bump when glyphs or rules change.
name { ar, en, … } Display name.
description { ar, en, … } One sentence.
license SPDX id OFL-1.1 for fonts.
credits string Designers, sources. Shown in the editor.
upstream URL (optional) Where the original font lives.

[font]

Key Meaning
source Font the compiler reads (relative path).
compiled Font the engine loads (relative path).
family_name Family name written into the compiled font. Required by the OFL spirit: a modified font must not pose as the original.

[metrics]

Key Meaning
nuqta Size of one rhombic pen dot in font units. The editor shows offsets and kashida lengths in dots and draws the dot ladder. Measure the alef height and divide by alef_dots.
alef_dots Height of the alef in dots (5 for Naskh, 7 for Thuluth…).

[shaping]

Key Default Meaning
script arab OpenType or ISO 15924 script tag.
language none BCP 47 language (ar, ur, fa) if the font has language-specific forms.
features [] Extra features for the whole text, HarfBuzz syntax: "ss05", "-liga", "cv01=2".

[alternates]

Key Used by Meaning
feature compiler + engine GSUB feature holding the alternates (type-3 lookups). Default salt. If the source font already has that feature, choose a private tag such as MDAL.
edge_features compiler Features whose substitutions mean “variant of the same letter”. Usually rlig, calt, liga plus the stylistic sets that swap letter shapes.
max_per_glyph compiler Cap on alternates per glyph (keeps the picker usable).
exclude compiler Glyph ids never offered as alternates (shapes that look wrong out of context).
remove compiler { "gid" = [gid, …] } — alternates taken away from one glyph only (a shape that breaks its joins in that position). midad review writes this table.
extra compiler { "gid" = [gid, …] } — alternates added by hand.

Amiri ships without glyph names, so corrections use glyph ids. Run midad alternates … --at <char> to see the ids of a letter’s shapes.

[[options]]

User-toggleable features (usually stylistic sets). The editor shows each one as a tile that draws sample with the option on or off; label becomes the tile’s name on hover. Without a sample, the label is shown as a switch.

[[options]]
tag = "ss07"
label = { ar = "كشيدة مستقيمة (بدون تقويس)", en = "Straight kashida (no curves)" }
default = false
sample = "بـــه"   # optional: a short word this option visibly changes

midad_style inspect prints each stylistic set with the name the font gives it — copy the meaning from there, never guess it.

[kashida]

Key Default Meaning
enabled true Allow elongation at all (also requires a tatweel glyph in the font).
max_length 1500 Longest elongation, font units.
max_per_word 2 Elongations per word (0 = unlimited).
exclude_words [] Words (compared without diacritics) never elongated, e.g. الله.
never_before [] Letters a kashida never precedes (e.g. alef).
never_after [] Letters a kashida never follows.
curve "flat" Shape of the madd stroke: flat (straight), rise, fall, wave.
curve_height 120 How far a curved madd deviates from the baseline, font units.
flat_option none Tag of the style option that asks for straight elongations (Amiri’s ss07): while the user has it on, the madd is flat whatever curve says.

[pen]

The reed pen the engine writes with itself: madd strokes, the curved kashida, and pen ornaments.

Key Default Meaning
angle 62 Angle the nib is held at, degrees from the baseline (0–180).
halo 0.3 Width of the interlacing gap, in nuqat (0–2).
fillers [] What auto-fill may put in the space: pen ornaments by name (dot, two_dots, two_dots_stacked, three_dots, shin_arc, crescent, shard, long_madda, long_fatha) and single characters (a haraka, a small ihmal letter such as "ع"). Empty: the engine’s own choice.

[joints]

Key Default Meaning
overlap 0 Extra overlap (font units) applied when a gap is closed.
max_gap 0 Close gaps up to this size; larger gaps are only reported. 0 = diagnostics only.

[axis]

Key Default Meaning
angle 0 Rotation of each letter around its join point, degrees (negative = Nastaliq slant).
step 0 Vertical drop per letter, font units: letter N sits step × N lower (the cascade).

angle and step are the Nastaliq multi-axis. 0/0 = off (a flat baseline, correct for Naskh). Marks share their base letter’s drop. A piece may override both (layout.slant, layout.cascade) from the editor.

[[stacking]]

Letter groups written above each other (تركيب), as Thuluth and Diwani write «لم» or «لمح». Applied after shaping, before blocks; manual offsets stay possible on top, and a piece can switch the style’s rules off (layout.style_rules).

Key Meaning
sequence The letters, in reading order, joined one to the next (harakat between them do not count).
steps For each letter after the first, [x, y] in nuqat (x to the right, y up): how far it moves, with the rest of its piece, from where the letter before it left it. One step per letter after the first.
[[stacking]]
sequence = "لم"
steps = [[0.5, -1.0]]   # the meem half a nuqta right and one nuqta down

Moving a letter only helps where the font draws a join that meets it there; a font without stacked forms shows the gap. No shipped style uses a rule yet.

[[sizes]]

Letters drawn larger or smaller than the font has them.

Key Default Meaning
letters — The letters the rule is for, e.g. "ني".
position "any" isolated, initial, medial, final or any, from how the letter joins its neighbours.
scale — 1 = as the font draws it (0.3–3).

A final letter grows around its join with the letter before it, an initial one around its join with the next, so the joins stay where the shaper put them. Rules apply before the letter’s own scale from the editor.

Joint gaps are always diagnosed and reported in the scene (scene.joints), regardless of these keys. Non-joining letters (alef, dal, reh…) are never treated as broken joints — their gap is natural.

The shaper already prevents impossible positions (after a non-joining letter, inside a ligature). These rules express calligraphic convention on top of that.

Adding a new style

  1. Create styles/<id>/ with source/<font>.ttf, OFL.txt and a style.toml (copy naskh-amiri and edit).
  2. python -m midad_style inspect the font; fill [[options]] from the stylistic-set names it prints; choose edge_features.
  3. Measure the alef and set [metrics].
  4. Compile, then review every letter’s shapes in the editor. Add broken shapes to exclude.
  5. Add a short integration test in crates/midad-core/tests/ if the style has special rules.
  6. Open a pull request with screenshots (see CONTRIBUTING.md).

A candidate second style is Aref Ruqaa (OFL) — it exercises a hand with very different joins and few ligatures, which is a good test of the compiler’s assumptions.

Licensing

Every font in styles/ must be under the SIL Open Font License 1.1. A compiled font is a Modified Version under the OFL: it keeps the original copyright and license, and gets its own family name (family_name). Do not use a Reserved Font Name of the original. Drawings contributed by calligraphers are accepted only under the OFL.