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.
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:
lineage | What it is | Count | style looks like |
|---|---|---|---|
scan | The three types traced from eight printed muṣḥafs — one of each per printing | 24 | mushaf-qalon |
font | Ayah markers only, taken from the U+06DD glyph of 26 Arabic font families in their weights — 20 designs | 47 | font-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.
style | riwaya recorded in the file | Notes |
|---|---|---|
mushaf-hafs-madinah-mumtaza | Hafs 'an 'Asim (Madinah, mumtaza) | |
mushaf-hafs-madinah-kabir | Hafs 'an 'Asim (Madinah, large) | Frame does not tile; see below |
mushaf-hafs-adi | Hafs 'an 'Asim (al-'Adi) | |
mushaf-warsh | Warsh 'an Nafi' | |
mushaf-qalon | Qalun 'an Nafi' | |
mushaf-douri | al-Duri 'an Abi 'Amr | |
mushaf-sousi | al-Susi 'an Abi 'Amr | |
mushaf-shubah | Shu'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.
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.
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.
| File | What it is | Lineage |
|---|---|---|
color.svg | The full-colour drawing, one group per ink | both |
mono.svg | A single-colour silhouette, drawn as currentColor | both |
line.svg | Linework only, constant-width strokes, drawn as currentColor | scan only — a glyph has no linework |
meta.json | Palette, slot, viewBox, symmetry, provenance; stroke widths for a scan, upem and codepoint for a font | both |
source.png / source.jpg, clean.png / clean.jpg | The raw crop from the scan and the cleaned crop that was traced | scan |
source.svg | The outline as extracted from the font, before layering | font |
slices/ | Page frames only, and only where the border tiles | scan |
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+E000–U+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 */
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.
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.
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.
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%";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}%`, };Absolutely position your own element over it
The container is
position: relative, the ornament fills it, and your text sits inbox. For an ayah marker,catalog.jsonalso givescxandcyfor the same rectangle, which is what you centre a number on, and font markers addr, 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.
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:
| Piece | File | viewBox | data-repeat |
|---|---|---|---|
| Corner | assets-mushaf-warsh-slice-corner.svg | 0 0 9.4557 6.4871 | — |
| Horizontal unit | assets-mushaf-warsh-slice-edge-h.svg | 0 0 6.1572 5.3326 | 6.1572 |
| Vertical unit | assets-mushaf-warsh-slice-edge-v.svg | 0 0 5.4426 6.2122 | 6.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).
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 }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 }Leave the border its own margin
The frame is drawn around the text, not over it.
slots[0]on a frame has the roletext-area— for the Warsh frame it isx 5.4455, y 5.3355, w 57.4257, h 89.3289inside a0 0 68.3168 100viewBox — 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.
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.
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
- Nothing is published yet. The repository builds a typed module,
@quran.ws/assets, exposingassets,byId,get,ofType(type, lineage?),url,slotandframe, plus the PUA font. At the time of writing it is not on npm and the repository has no releases, so today the files come from the repository itself. - The licence is per asset. Every asset carries a
licenseblock. At591e8f5the 24 scan assets readCC-BY-NC-SA-4.0,"status": "provisional"— tracings of ornaments whose designs belong to their publishers, while permission is settled with each. 40 font markers readOFL-1.1,"status": "confirmed", verified against each family'sMETADATA.pbingoogle/fonts; the OFL text and every family's copyright line must travel with them, anddist/LICENSES.mdcarries both. The other 7 (designs 014–020, fromfonts.quran.ws) are"status": "pending"with no licence asserted. Readlicense.status, not the lineage. - Font marker ids are numeric. The OFL forbids a Reserved Font Name — Alkalami, SIL,
Scheherazade, Plex, Source — naming a modified version. Nothing here carries a family name; the
family is in
sources[]for attribution. - One source per asset. Each scan ornament comes from one occurrence on one page of one scan; a publisher who varies the design between surahs is not represented. Each font marker is one glyph of one weight.
mushaf-hafs-madinah-kabiris the weakest of the eight throughout — its source scan is 150 dpi.
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.