.midad document formatA .midad file is UTF-8 JSON written by Document::to_json
(crates/midad-core/src/document.rs). It stores the text and the
calligrapher’s decisions — never glyph positions.
{
"format": 1,
"style": { "id": "naskh-amiri", "version": "0.1.0" },
"text": "بسم الله الرحمن الرحيم",
"options": ["ss07"],
"glyphs": [
{ "cluster": 2, "index": 0, "alternate": 1854 },
{ "cluster": 7, "index": 0, "dx": 12, "dy": 140, "scale": 1.25, "rotate": -8, "z": 1, "halo": true }
],
"kashidas": { "0": 600 },
"blocks": [
{ "start": 9, "end": 15, "dx": 600, "dy": 340, "scale": 1.6, "z": 1, "halo": true },
{ "start": 12, "end": 15, "dx": -80, "rotate": 6 }
],
"guide": { "shape": "teardrop", "cx": -2600, "cy": 0, "width": 4200, "height": 5100, "main": [4, 8] },
"stamps": [
{ "id": 1, "kind": "ornament", "ornament": "crescent", "x": -2400, "y": 900, "scale": 1.4, "auto": true },
{ "id": 2, "kind": "char", "ch": "ع", "x": -900, "y": -300, "scale": 0.35 },
{ "id": 3, "kind": "ornament", "ornament": "dot", "x": 82, "y": -100, "attach": { "cluster": 0, "index": 0 } },
{ "id": 4, "kind": "ornament", "ornament": "shard", "x": -1500, "y": 200, "z": -1 }
],
"flourishes": [
{
"id": 1,
"points": [[-120, -40], [-600, -700], [-2400, -900], [-3600, -500], [-4300, -250], [-4700, 100], [-4800, 500]],
"angle": 62, "width": 79, "taper": true, "taper_start": false,
"attach": { "cluster": 9, "index": 0 }, "z": 1, "halo": true
}
],
"layout": { "align": "right", "line_spacing": 1.0, "mirror": true, "mirror_gap": 200 }
}
| Field | Meaning |
|---|---|
format |
Format version. The engine refuses files with a higher number. |
style.id, style.version |
Style the document was made with. Opening requires a loaded style with the same id. |
text |
The text, \n between lines. |
options |
Tags of the style options switched on. |
glyphs[] |
Overrides. cluster = char index (Unicode scalar values, not bytes, not UTF-16 units) of the first character of the glyph’s cluster; index = glyph position inside the cluster in logical order (0 = base letter). Omitted fields mean “unchanged”: alternate (glyph id), dx/dy (font units, y up), scale (1 = unchanged), rotate (degrees, counter-clockwise), z (layer, see below; the letter’s harakat and kashida share it) and halo (a gap where it crosses other ink, see below). |
kashidas |
Char index → elongation length in font units. The tatweel goes between char i and i + 1. Keys are strings because JSON object keys must be. |
blocks[] |
Words or connected pieces placed as one unit (تركيب). start/end = char range [start, end); every glyph whose source starts in it (letters, harakat, elongations) moves with it. Omitted fields mean “unchanged”: dx/dy (font units, y up), scale, rotate (degrees, counter-clockwise) around the centre of the block’s ink, z (a higher block paints over a lower one, whatever the layers of its letters) and halo (the whole block cuts a gap where it crosses other ink; its own letters never cut each other). Applied after every glyph override. Blocks may nest (a piece inside its word; its offset is then in the word’s frame) but never cross. Omitted when empty. |
guide |
The template the words are fitted into (drawn while editing, never exported): shape (teardrop, oval, circle, arch, diamond, golden, crown, cascade), its frame (cx, cy, width, height, scene units, y up) and optionally main, the [start, end) of the main word, which gets a row of its own. Fitting writes blocks; the guide only records the target. Omitted when there is none. |
stamps[] |
Ornaments (حِلى), in painting order. kind: "char" draws ch with the style’s font (a haraka, or a letter’s isolated form, small); kind: "ornament" draws a pen ornament with the style’s nib: dot, two_dots, two_dots_stacked, three_dots, shin_arc, crescent, shard, long_madda, long_fatha. x, y = centre, in scene units, or with attach (a glyph key) in that glyph’s own units, so the ornament moves, scales and turns with its letter. scale and rotate as for glyphs; z its layer (on a letter, counted from the letter’s). auto: true marks the ornaments auto-fill placed (the next auto-fill replaces them). Omitted when empty. |
flourishes[] |
Madd strokes written with the reed pen (مدّات), in painting order. points = the path: a start point, then two handles and an end point per cubic segment (font units, y up), in scene units or, with attach, in that letter’s own units (the stroke grows out of the letter and follows it). angle = pen angle in degrees from the baseline, width = nib width in font units. taper / taper_start: the pen turns onto its edge at that end, so the ink runs out into a point. z and halo as for glyphs. Files of the first form (p0, c1, c2, p1, one segment) still open; they are read as one segment tapered at both ends. Omitted when empty. |
glyphs[].alternate_shape |
Fingerprint (16 hex digits) of the chosen alternate’s outline. In a font rebuilt with another glyph order, the alternate with this shape is used instead of the stored alternate id; when no alternate has it (the shape was redrawn), the id is used. Written with every alternate. |
glyphs[].dotless |
true: the letter is drawn without its dots (its dotless twin, e.g. ٮ for ب); its dots are then usually a stamp attached to it. |
layout.align |
right, center or left. |
layout.line_spacing |
Multiplier of the font’s natural line height. |
layout.slant, layout.cascade |
The slant axis for this piece, overriding the style’s [axis]: every letter turned by slant degrees around its join, letter N of a line dropped by cascade × N font units. Omitted: the style’s. |
layout.close_joints |
false: every join as the font draws it; true: small gaps at the joins closed (by the style’s [joints], or by up to 0.9 nuqta when the style closes none). Omitted: the style’s. |
layout.marks_follow |
false: harakat stay where they are when their letter moves. Omitted: they follow. |
layout.style_rules |
false: the style’s [[stacking]] and [[sizes]] rules are not applied. Omitted: applied. |
layout.mirror, layout.mirror_gap |
Mirrored writing (مثنّى): the whole piece again, reflected, to its left, mirror_gap font units away (negative: they overlap). Omitted when off. |
Letters, madd strokes and ornaments are painted in one order, bottom to
top: by layer (a glyph’s z, plus 100 × its blocks’ z; a stroke’s or an
ornament’s z, plus its letter’s layer when it rides on one), then letters
before strokes before ornaments, then in scene order. A halo is a real gap,
not paper-coloured paint: the ink painted under haloed ink is cleared within
0.3 × nuqta of its outline (an SVG mask), so a transparent export stays
transparent. Haloed ink never cuts its own connected piece, nor its own block
when the halo is the block’s, so a word’s joins stay whole.
guide follows its letters the same way. A stamp or a
flourish attached to a letter follows the letter’s key, and goes when the
letter goes. A stamp whose character the style’s font cannot draw is
skipped.format, and add
a migration in Document::from_json that upgrades older files in memory.alternate stores a glyph id. If a style is recompiled with a
different glyph order, alternates in old documents may point to other
shapes (they are validated, so the worst case is a stale-alternate warning,
not a crash). Format 2 should store a stable glyph name once styles are
built from named sources (UFO / Glyphs).