Quran Assets

Surah headers, ayah markers and page frames traced out of eight printed muṣḥafs, and ayah markers drawn from the end-of-ayah glyph of Arabic fonts — all SVGs you can recolour, all under one contract. This page is about consuming those files: how to address one, what is inside it, what you are expected to draw yourself, and which of the two lineages you may ship.

Blocks used
Time
About 10 minutes

It has no Qur’anic text in it: these are the decorations around the text. For the words of an ayah see Quran Text; for a complete printed page, letters and all, Quran SVG.

Three types, two lineages

The surah header, the ayah marker and the page frame, from two places:

lineageWhat it isCountstyle looks like
scanThe three types traced from eight printed muṣḥafs — one of each per printing24mushaf-qalon
fontAyah markers only, taken from the U+06DD glyph of 26 Arabic font families in their weights — 20 designs47font-003-regular

At 591e8f5 that is 71 assets, and catalog.json says so in its own count field. Every style is <lineage prefix>-<name>mushaf- for a scan, font- for a font — and the root element repeats it as data-style and data-lineage, so a file pulled out of context still says where it belongs.

The lineage also decides whether you may ship the file; see Licence below. In short: no scan asset yet, 40 of the 47 font markers today.

Pick the scan asset by muṣḥaf, not by riwayah

Scan assets are keyed by printing, and one riwayah can have several printings. Ḥafṣ has three here.

styleriwaya recorded in the fileNotes
mushaf-hafs-madinah-mumtazaHafs 'an 'Asim (Madinah, mumtaza)
mushaf-hafs-madinah-kabirHafs 'an 'Asim (Madinah, large)Frame does not tile; see below
mushaf-hafs-adiHafs 'an 'Asim (al-'Adi)
mushaf-warshWarsh 'an Nafi'
mushaf-qalonQalun 'an Nafi'
mushaf-dourial-Duri 'an Abi 'Amr
mushaf-sousial-Susi 'an Abi 'Amr
mushaf-shubahShu'bah 'an 'Asim

Font markers have riwaya: null. They belong to no printing and suit any edition; pick one by design and licence, not by reading.

These keys are not the keys our other datasets use

This package spells the printings douri, sousi, qalon after their archive items. Elsewhere on this site, and in quran-text's muṣḥaf files, the same three readings are duri, susi, qalun. There is no single key that works across both, so a join needs an explicit mapping table — and there is no mushaf-hafs key here at all, because Ḥafṣ is three separate printings.

Ornament is not tied to a reading

These are decorative drawings. A Qālūn header above a Ḥafṣ page is a design choice, not a mistake — nothing in the data couples an ornament to a reading. The style key records the printing an asset was traced from, which is what you match on when you want the page to look like that muṣḥaf.

What one asset ships

In the repository, one asset is one directory, assets/<type>/<style>/, where <type> is surah-headers, page-frames or ayah-markers and <style> is a key of either lineage.

FileWhat it isLineage
color.svgThe full-colour drawing, one group per inkboth
mono.svgA single-colour silhouette, drawn as currentColorboth
line.svgLinework only, constant-width strokes, drawn as currentColorscan only — a glyph has no linework
meta.jsonPalette, slot, viewBox, symmetry, provenance; stroke widths for a scan, upem and codepoint for a fontboth
source.png / source.jpg, clean.png / clean.jpgThe raw crop from the scan and the cleaned crop that was tracedscan
source.svgThe outline as extracted from the font, before layeringfont
slices/Page frames only, and only where the border tilesscan

The variants are separate drawings, not filters over one another: mono.svg is not color.svg with the fills flattened, and line.svg is not an outline pass. Pick the one that suits your surface and recolour that.

The 47 font markers also ship together as one PUA font, dist/fonts/AyahMarkers.otf (and .ttf), one glyph per marker at U+E000U+E02E, with dist/fonts/font-map.json saying which codepoint is which marker. Use it for text runs where an inline SVG per ayah is too heavy.

catalog.json at the repository root is the same information for a machine — one entry per asset, keyed <type>/<style>, carrying lineage, variants, palette, slots, symmetry, sources and license; a font asset adds a font block with upem, advance and its PUA codepoint.

The contract inside the file

All 71 files behave the same way.

The root element. viewBox is normalised to a height of 100, so the width is the aspect ratio. There is no width or height attribute — you size it with CSS. Alongside it are data-style, data-lineage, data-asset, data-variant, data-symmetry and data-slot. A scan asset adds data-mushaf and the provenance attributes data-source-file, data-source-page, data-source-box, data-source-url; a font asset adds data-source-font and data-source-glyph. A <metadata> element repeats the provenance as JSON — the sha256 of the scanned PDF, or the family, variant, source URL and licence of the font.

Here is the real root of the Qālūn surah header, trimmed to the attributes you will use:

<svg viewBox="0 0 863.429 100" data-style="mushaf-qalon" data-lineage="scan" data-mushaf="qalon"
     data-asset="surah-header" data-variant="color" data-symmetry="4"
     data-slot="180.8571 8.5714 501.4286 83.1429">

And the root of a font marker — Estedad's U+06DD, regular weight:

<svg viewBox="0 0 94.0423 100" data-style="font-003-regular" data-lineage="font"
     data-asset="ayah-marker" data-variant="color" data-symmetry="1"
     data-source-font="Estedad" data-source-glyph="U+06DD"
     data-slot="16.2663 42.8749 61.5097 25.5217">

The groups. Every drawable group carries both a class and a data-part with the same value, so you can select it either way:

<g fill="none"    class="slot" data-part="slot">…</g>   <!-- transparent: your text goes here -->
<g fill="#ffffff" class="c1"   data-part="c1">…</g>     <!-- fills, light → dark -->
<g fill="#cfd0d2" class="c2"   data-part="c2">…</g>
…
<g fill="none" stroke="#343f49" class="line" data-part="line">…</g>

A scan color.svg has slot, then c1 … cN, then line. How many c groups there are depends on how many inks that press laid down — the Qālūn marker has three, the Qālūn header six, the Ḥafṣ Madinah mumtāza header ten — so read them from the file rather than assuming a fixed palette. mono.svg has slot and a single ink group; line.svg has slot and line.

A font color.svg has c1 … cN only — the Alkalami marker has five — with no slot group and no line; its mono.svg is one ink group. The slot is still there, as data-slot on the root and slots[] in the catalogue; a font marker just has nothing transparent to style.

Colours are presentation attributes, never inline style: any CSS rule beats a presentation attribute, so .c2 { fill: var(--brand) } wins, while a rasteriser with no CSS still renders the file as printed.

.c2   { fill: var(--brand); }
.line { stroke: #222; }
.slot { fill: #fff; }        /* the slot is transparent until you fill it */
Why one change recolours four corners

data-symmetry tells you how the file is built: 4 means one quadrant was traced and mirrored both ways, 2 left-to-right, c2 a 180° rotation, 1 traced whole. Mirrored files hold the artwork once in <defs> and place it with <use>, so setting a group's fill once updates every copy. Every surah header ships data-symmetry="4"; every scan ayah marker 2; every font marker 1, because a glyph is one outline.

Inlining several assets on one page used to break them

Until 5843b12 every mirrored file named its quadrant id="q", so two inlined assets shared an id and every <use href="#q"> on the page drew the first one. The id is now unique per file, q-<style>-<asset>-<variant>. If you vendored files before that commit, refresh them or namespace the ids yourself before inlining more than one.

The slot

Every asset declares a slot: data-slot gives its rectangle as x y w h in viewBox units, and scan assets also draw it as a transparent slot group. That is where the type goes — the surah name in a header, the number in a marker, the body text inside a frame. The artwork does not contain any of it.

`data-slot` is in viewBox units, not pixels or percent

For the Qālūn header data-slot="180.8571 8.5714 501.4286 83.1429" against viewBox="0 0 863.429 100". Treat those numbers as pixels and your surah name lands 180 px from the left of a 300 px-wide banner, off the artwork entirely — with no error. Divide by the viewBox width and height first.

  1. Inline the SVG, so you can style it

    An <img src> renders the ornament but seals it off: no CSS reaches inside, so you cannot recolour a group or position anything over the slot. Fetch the file and put its markup in the DOM. This is what the demo on this site does.

    const res = await fetch("/demo/assets-surah-headers-mushaf-qalon-color.svg");
    host.innerHTML = await res.text();
    
    const svg = host.querySelector("svg")!;
    svg.removeAttribute("width");
    svg.removeAttribute("height");
    svg.style.width = "100%";
    
  2. Read the slot off the root and convert it to percentages

    Never type the numbers into your code — they differ per muṣḥaf, and by a lot. The Qālūn header keeps 501.43 of its 863.43 units for the name; the Ḥafṣ Madinah mumtāza header keeps 342.19 of 854.69, which is 40% of the banner rather than 58%.

    const [, , vbW, vbH] = svg.getAttribute("viewBox")!.split(/\s+/).map(Number);
    const [x, y, w, h] = svg.getAttribute("data-slot")!.split(/\s+/).map(Number);
    
    const box = {
      left: `${(x / vbW) * 100}%`,
      top: `${(y / vbH) * 100}%`,
      width: `${(w / vbW) * 100}%`,
      height: `${(h / vbH) * 100}%`,
    };
    
  3. Absolutely position your own element over it

    The container is position: relative, the ornament fills it, and your text sits in box. For an ayah marker, catalog.json also gives cx and cy for the same rectangle, which is what you centre a number on, and font markers add r, the radius of the circle inscribed in the marker's interior.

    const label = document.createElement("div");
    label.textContent = String(ayahNumber);
    Object.assign(label.style, { position: "absolute", display: "grid", placeItems: "center", ...box });
    host.append(label);
    

The header slot is sized for a calligraphic surah name, and the marker slot for one to three digits. Nothing enforces that: text longer than the slot will simply overflow the ornament.

Size the ayah number by the slot's height, never its width

A marker's slot is the three-digit case — for Alkalami it is 34 units wide and 19 tall. Stretch a one-digit number to the width and it comes out about 2.7× too large. Set the font size from the slot's height (or from r), centre on cx, cy, and let the width take care of itself.

Recolouring

The groups are discoverable, so a theme does not need to know anything about a particular muṣḥaf. This is the loop the demo runs, adapted from src/components/blockdemo/assets.tsx:

svg.querySelectorAll<SVGGElement>("[data-part]").forEach((g) => {
  const part = g.getAttribute("data-part")!;
  if (part === "slot") return;              // leave the slot transparent
  const printed = g.getAttribute("fill");   // remember it, so "as printed" can come back
  g.setAttribute("fill", palette[part] ?? printed!);
});

Two things to keep in mind. The line group is stroked, not filled — set stroke on that one. And c1 is very often the paper — #ffffff in every scan asset, #fff8e7 in the Alkalami marker — so mapping it to a dark colour turns the ornament inside out.

Assembling a page frame

A frame's color.svg has the aspect of the page it was scanned from. If your page is a different shape, stretching that file distorts the corner motifs, which a reader notices.

Seven of the eight frames ship a slices/ directory instead: corner.svg, edge-h.svg, edge-v.svg. All three are in the frame's units — the whole frame's height is 100 — so they compose without rescaling. Each edge carries data-repeat, the length of one repeat in those units, and data-frame-viewbox, the viewBox of the frame they came from.

The three pieces this site serves are the Warsh frame:

PieceFileviewBoxdata-repeat
Cornerassets-mushaf-warsh-slice-corner.svg0 0 9.4557 6.4871
Horizontal unitassets-mushaf-warsh-slice-edge-h.svg0 0 6.1572 5.33266.1572
Vertical unitassets-mushaf-warsh-slice-edge-v.svg0 0 5.4426 6.21226.2122

Notice the band is thinner than the corner: the horizontal unit is 5.33 units tall against a 6.49-unit corner, the vertical unit 5.44 wide against 9.46. Tile every piece at the same scale and anchor the bands to the outer edge; giving the bands the corner's cell and letting them scale to fit blows the sides up, which is the first mistake the demo on this site made.

The corner is drawn once and mirrored into the other three positions. catalog.json → slices says which: corner_mode is "mirror" for every frame that tiles at this commit, and the alternative it allows is "rotate" (a 180° turn instead of a reflection).

  1. Work out how many units fit, then round

    Divide the run between the two corners by the repeat length and round to a whole number. The remainder is spread across the units by scaling each one slightly, so the run ends exactly at the corner.

    function run(total: number, unit: number) {
      const count = Math.max(1, Math.round(total / unit));
      return { count, step: total / count };   // step / unit is the stretch factor
    }
    
  2. Place four corners and two runs

    Corners first, then the horizontal units along the top and bottom, then the vertical units down the sides. Mirroring is a negative scale about the far edge.

    const across = run(width - 2 * corner.w, repeat.h);
    for (let i = 0; i < across.count; i++) {
      const x = corner.w + i * across.step;
      const k = across.step / repeat.h;
      place("edge-h", `translate(${x} 0) scale(${k} 1)`);              // top
      place("edge-h", `translate(${x} ${height}) scale(${k} -1)`);     // bottom, mirrored
    }
    
  3. Leave the border its own margin

    The frame is drawn around the text, not over it. slots[0] on a frame has the role text-area — for the Warsh frame it is x 5.4455, y 5.3355, w 57.4257, h 89.3289 inside a 0 0 68.3168 100 viewBox — so use that inset rather than inventing a padding value.

That algorithm is the one in the package's own frame() helper, and the one the demo on this site runs: one scale from the paper's width, whole units rounded, the remainder spread across the run.

One frame does not tile

mushaf-hafs-madinah-kabir has no slices entry. Its border does not repeat cleanly. The seven that ship score 0.74–0.96 reassembled against the original, a figure catalog.json publishes per frame; the repository reports 0.49 for this one. Check for slices before you reach for it, and fall back to scaling its color.svg.

Try it live

Dress page 604 of the Madinah muṣḥaf — or live text in its riwāyah's font — with any header, frame and marker of either lineage; recolour any ink group; take the SVG, the CSS or the file.

Limits

How this data is made

Scan lineage. The muṣḥaf PDFs are fetched from archive.org and are not committed; each page is rendered, the ornaments are located by their own geometry, the crop is cleaned of text and paper, symmetry is detected, and one quadrant is traced and vectorised into the grouped SVG described above. Every asset is rendered at 360, 1000 and 4000 px and diffed against the cleaned scan, the build fails on any deterioration against a committed baseline, and frame slices are cut, reassembled and compared with the frame they came from, which is the measurement that ruled mushaf-hafs-madinah-kabir out. Every file records the scan, page and crop box it came from, plus the SHA-256 of the source PDF.

Font lineage. The U+06DD glyph was extracted from 26 families, deduplicated across weights into 20 designs, and each design's contours were assigned to colour layers by hand; the box for the ayah number was derived from the glyph's geometry and then placed by hand, and the hand placement is what ships. The outlines were then normalised to the same height-100 viewBox as everything else. Because the source is a vector, the quality gate compares each marker with the outline it came from rather than with a scan; all 47 score 0.99 or better. This lineage began as a separate repository, quranpedia/ayah-markers, and was folded in at its commit e083434.

Read from quran-ws/quran-assets at commit 591e8f5, the commit pinned in public/demo/SOURCES.json.