# أمثلة موسيقية بصيغة MEI مع جهاز نقدي في الحواشي

> كتل MEI 5 تُنقش داخل النص على مدرج ارتفاعه 5 مم، والقراءات المختلفة مرمّزة بـ app وlem وrdg، والجهاز النقدي مرتّب حسب المازورة والنغمة في الحواشي.

- نسخة HTML: https://postext.dev/ar/cookbook/musicology-mei-apparatus
- وصفة رقم 165 · الإطارات والملاحظات · المستوى 2 (متوسط) · المخرجات: Canvas, PDF
- الأنواع: الأوراق البحثية والأكاديمية
- تتطلب postext ≥ 1.54.0, postext-pdf ≥ 1.54.0 · اختُبرت مع 1.54.0, postext-pdf 1.54.0 بتاريخ 2026-10-22
- الصفحات: [1](https://postext.dev/cookbook/musicology-mei-apparatus/en/p01.webp?v=cd2d6301), [2](https://postext.dev/cookbook/musicology-mei-apparatus/en/p02.webp?v=cd2d6301), [3](https://postext.dev/cookbook/musicology-mei-apparatus/en/p03.webp?v=cd2d6301), [4](https://postext.dev/cookbook/musicology-mei-apparatus/en/p04.webp?v=cd2d6301), [5](https://postext.dev/cookbook/musicology-mei-apparatus/en/p05.webp?v=cd2d6301)
- PDF: https://postext.dev/cookbook/musicology-mei-apparatus/en/musicology-mei-apparatus.pdf?v=cd2d6301
- افتح في Sandbox: https://postext.dev/ar/sandbox#recipe=musicology-mei-apparatus&lang=en (.postext: https://postext.dev/cookbook/musicology-mei-apparatus/en/musicology-mei-apparatus.postext)
- آخر تحديث: 2026-10-22
- لغات أخرى: [en](https://postext.dev/en/cookbook/musicology-mei-apparatus.md), [es](https://postext.dev/es/cookbook/musicology-mei-apparatus.md), [ca](https://postext.dev/ca/cookbook/musicology-mei-apparatus.md), [de](https://postext.dev/de/cookbook/musicology-mei-apparatus.md), [fr](https://postext.dev/fr/cookbook/musicology-mei-apparatus.md), [it](https://postext.dev/it/cookbook/musicology-mei-apparatus.md), [pt](https://postext.dev/pt/cookbook/musicology-mei-apparatus.md), [zh](https://postext.dev/zh/cookbook/musicology-mei-apparatus.md), [ja](https://postext.dev/ja/cookbook/musicology-mei-apparatus.md)

## باختصار

فصل من دراسة عن الموسيقى في العصور الوسطى. مقاطع قصيرة من أنشودة دائرية إنجليزية من القرن الثالث عشر تُطبع نوتةً داخل النص، وتسجّل الحواشي أسفل كل صفحة، مازورةً مازورة، مواضع اختلاف الطبعات القديمة.

## ما الذي ستنضده

الفصل الثالث من دراسة علمية عن قراءة هواة الآثار الإنجليز للموسيقى الوسيطة: خمس صفحات بقطع 156 × 234 مم عن *Sumer is icumen in*، أنشودة ريدينغ الدائرية، وعن المحررين الذين وضعوها في مدوّنة كاملة بين 1776 و1889. تُنقش ثلاثة مقاطع منها داخل النص بخط Leipzig على مدرج ارتفاعه 5 مم، وتتبع أرقام الموازير مدوّنة W. S. Rockstro، ويحيل كل تعليق إلى جهازه النقدي في أسفل الصفحة: «b. 40, n. 2 A ] G Hw, Bu». الحواشي قصيرة مكثفة كما يكون الجهاز النقدي، والموسيقى تملأ عرض سطر النص، وكل قراءة مأخوذة من مقالة Rockstro في معجم Grove، وهو ما يذكره الفصل في فقرته الثانية.

**تجيب هذه الوصفة عن:**

- كيف أرفق بالأمثلة الموسيقية جهازًا نقديًا لاختلاف قراءات المصادر في الحواشي السفلية؟
- كيف أضيف الحواشي السفلية؟

## الجواب المختصر

````js
// script.js, سطرًا 36–60
// In the Markdown each example is a ```mei fence. A variant is encoded where it stands,
// <app><lem>…</lem><rdg source="#Hw #Bu">…</rdg></app>: the staff shows the lemma, and
// the file keeps the reading for anyone who opens the encoding. The apparatus entry is a
// footnote cited from the paragraph that discusses the bar: `[^b40]: **b. 40, n. 2** A ] G`.
const scoreStyle = {
  staffSize: mm(5), // running-text examples: 4.5–5.5 mm
  musicFont: 'Leipzig', // lighter than Bravura, close to a 19th-century plate
  textFont: 'Crimson Pro', // the underlay in the book's own face
  barNumbers: 'system', // the apparatus is keyed by bar: number every system
  spacing: 0.95,
  systemGap: mm(4),
  marginTop: pt(4),
  marginBottom: pt(LEAD / 2),
  color: col('ink'),
  minSystemsAtBreak: 2, // never leave one system of an example alone at a page foot
};
const footnotes = {
  placement: 'column', // the foot of the page in one column
  numbering: 'chapter',
  fontSize: pt(8.5),
  lineHeight: pt(10.75),
  hangingIndent: em(1.2), // turnover lines align past the number
  spaceBetween: pt(2),
  separator: { width: 0.2, lineWidth: pt(0.5), color: col('rubric') },
};
````

## المكونات

**تعلّم**

- [التدوين الموسيقي](https://postext.dev/ar/docs/music.md#مدوّنة-داخل-النص): مدوّنات موسيقية تُكتب نصًّا داخل كتل ```abc أو ```musicxml أو ```mei أو ```humdrum، يرسمها Verovio بعرض العمود ويقسّمها إلى أنظمة تمتد عبر الأعمدة والصفحات.
- [الحواشي السفلية](https://postext.dev/ar/docs/document-format-notes-references.md#الحواشي-السفلية): حواشٍ يُستشهد بها بالصيغة [^id] وتوضع أسفل العمود الذي يستشهد بها، أو بعد الفصل، مرقّمة لكل فصل أو على امتداد الكتاب.

**تستخدم أيضًا**

- [نمط المدوّنة الموسيقية](https://postext.dev/ar/docs/configuration-resources.md#نمط-المدوّنة-الموسيقية)
- [صفحات افتتاح مصمَّمة](https://postext.dev/ar/docs/configuration-headings.md#الامتداد-والتصميم-المتقدم)
- [سمات العنوان](https://postext.dev/ar/docs/document-format.md#سمات-العناوين)
- [الترويسات وأرقام الصفحات](https://postext.dev/ar/docs/configuration-headers-footers.md#الترويسات-والتذييلات)
- [هوامش متناظرة](https://postext.dev/ar/docs/configuration-page-layout.md#الهوامش-المتناظرة)
- [الترويسات بحسب دور الصفحة](https://postext.dev/ar/docs/configuration-headers-footers.md#عناصر-النص)
- [أنماط الفقرات](https://postext.dev/ar/docs/configuration-styles.md#أنماط-الفقرات)
- [لوحة ألوان دلالية](https://postext.dev/ar/docs/configuration-fonts-colors-viewers.md#لوحة-الألوان)
- [لون الورق](https://postext.dev/ar/docs/configuration-page-layout.md#الصفحة)
- [الأسطر الأرامل والأسطر اليتيمة والكلمات المعزولة](https://postext.dev/ar/docs/configuration-page-flow.md#الأسطر-اليتيمة-والأرامل-والكلمات-المعزولة-وقواعد-الإبقاء-معًا)
- [التصدير إلى PDF](https://postext.dev/ar/docs/configuration-pdf-output.md#توليد-ملفات-pdf)
- [قوائم الشيفرة](https://postext.dev/ar/docs/configuration-styles.md#قوائم-الشيفرة)
- [موازنة الأعمدة](https://postext.dev/ar/docs/configuration-page-flow.md#موازنة-الأعمدة)
- [الخطوط المضمَّنة في PDF](https://postext.dev/ar/docs/configuration-pdf-output.md#لماذا-مزوّد-الخطوط)

**الإعدادات في لمحة**

- [`bodyText`](https://postext.dev/ar/docs/configuration-text.md#نص-المتن), [`colorPalette`](https://postext.dev/ar/docs/configuration-fonts-colors-viewers.md#لوحة-الألوان), [`footer`](https://postext.dev/ar/docs/configuration-headers-footers.md#الترويسات-والتذييلات), `footnotes`, [`header`](https://postext.dev/ar/docs/configuration-headers-footers.md#الترويسات-والتذييلات), [`headings`](https://postext.dev/ar/docs/configuration-headings.md#العناوين), [`layout`](https://postext.dev/ar/docs/configuration-page-layout.md#التخطيط), [`locale`](https://postext.dev/ar/docs/configuration-language.md#تقسيم-الكلمات-بالواصلة), [`page`](https://postext.dev/ar/docs/configuration-page-layout.md#الصفحة), [`paragraphStyles`](https://postext.dev/ar/docs/configuration-styles.md#أنماط-الفقرات), [`scoreStyle`](https://postext.dev/ar/docs/configuration-resources.md#نمط-المدوّنة-الموسيقية)

**واجهات API**

- [`buildDocumentWithFonts`](https://postext.dev/ar/docs/configuration-programmatic-usage.md#تحميل-الخطوط-قبل-الإخراج), [`decompressWoff2`](https://postext.dev/ar/docs/configuration-pdf-output.md#مزوّد-الخطوط-في-المتصفح-fontsource--woff2), [`initMusicEngine`](https://postext.dev/ar/docs/music.md#مدوّنة-داخل-النص), [`renderPageToCanvas`](https://postext.dev/ar/docs/configuration-programmatic-usage.md#رسم-صفحة-في-صورة-نقطية), [`renderToPdf`](https://postext.dev/ar/docs/configuration-pdf-output.md#توليد-ملفات-pdf)

**الخطوط**

- Crimson Pro (OFL-1.1), Cormorant Garamond (OFL-1.1), Source Sans 3 (OFL-1.1)

## طريقة التحضير

### 1 · اكتب كل مثال بصيغة MEI مع قراءاته

الإعدادات في [الجواب المختصر](https://postext.dev/ar/cookbook/musicology-mei-apparatus.md#الجواب-المختصر) أعلاه، والموسيقى في Markdown، كتلة ```` ```mei ```` لكل مثال. حيث يختلف مصدران تحمل المازورة عنصر `<app>`، فيه القراءة المعتمدة داخل `<lem>` والأخرى داخل `<rdg source="#Hw #Bu">`. يُنقش `<lem>`، فتُظهر المازورة 40 من [المثال 3.3](https://postext.dev/cookbook/musicology-mei-apparatus/en/p05.webp?v=cd2d6301) النغمة A، وتبقى G عند Hawkins وBurney في الملف لمن يفتح الترميز. أعطِ كل كتلة `alt` بلغة الصفحة: هذا ما يقرؤه قارئ الشاشة وملف PDF الموسوم عن المدوّنة.

### 2 · اربط الجهاز النقدي بالموازير التي يراها القارئ

يرقّم `barNumbers: 'system'` أول مازورة في كل سطر موسيقي، ويبدأ `<measure n="37">` في كل كتلة العدَّ من حيث يبدأ المقطع في الطبعة المتّبعة، فيفتتح المثال 3.3 بالمازورة 37، وتشير حاشية تقول «b. 40» إلى مازورة يجدها القارئ. مدخل الجهاز حاشية سفلية عادية تُحال إليها من الفقرة التي تناقش المازورة: القراءة المعتمدة، ثم قوس معقوف، ثم القراءة الأخرى ورموز المصادر بخط مائل. لا يمكن الإحالة إلى حاشية من داخل المدوّنة، فتوضع علامة الإحالة في الجملة التي تسمّي القراءة.

### 3 · قدّر حجم الموسيقى على مقاس النص الجاري

يتسع سطر عرضه 116 مم لمدرج Leipzig بارتفاع 5 مم وخمس موازير أو ست من ميزان 3/2، فتأتي موازير المثال 3.3 الإحدى عشرة في سطرين. يجعل `textFont` كلمات الغناء بخط النص Crimson Pro، ويمنع `minSystemsAtBreak: 2` أن تنتهي صفحة بسطر موسيقي واحد من مثال ينتقل باقيه إلى الصفحة التالية. وعنوان كل مثال عنوان من المستوى الثالث، فلا يبقى وحده في أسفل الصفحة بلا مدوّنته.

### 4 · دع الصفحة ذات الحواشي تنتهي قصيرة

```js
// script.js, سطرًا 64–78
const opener = {
  enabled: true,
  minHeight: mm(50), // every chapter's text starts on the same grid line
  slot: { elements: [
    { kind: 'text', id: 'kicker', content: '{attr.kicker}', ...label, fontSize: pt(8.5),
      letterSpacing: pt(1.8), color: col('rubric'), align: 'left',
      placement: at('container', 'top-left', 0, 8) },
    { kind: 'text', id: 'title', content: '{titleText}', fontFamily: 'Cormorant Garamond',
      italic: true, fontWeight: 500, fontSize: pt(29), lineHeight: 1.05, color: col('ink'),
      align: 'left', overflow: 'wrap', // design text ends in an ellipsis by default
      placement: { ...at('#kicker', 'below', 0, 3), size: { width: mm(100), height: 'auto' } } },
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(1), color: col('rubric'),
      placement: { ...at('#title', 'below', 0, 5), size: { width: mm(14) } } },
  ] },
};
```

تُنزل صفحة الافتتاح النص 50 مم تحت عنوان بخط Cormorant المائل. وفيما بعد، تنتهي الصفحة التي كان سطرها التالي سيجرّ معه حاشية طويلة قبل موعدها ببضعة أسطر، ويمنع `balancing: { maxLinesPerHeading: 1 }` موازنة الأعمدة من نقل هذا الفراغ فوق عنوان القسم التالي، حيث يبدو انقطاعًا في الحجة.

### 5 · ضع اسم الكتاب على الصفحة الزوجية واسم الفصل على الفردية

```js
// script.js, سطرًا 82–99
const head = (id, content, parity, edge, x, extra = {}) => ({
  kind: 'text', id, content, parity, pages: 'body', // never on the opener
  ...label, fontSize: pt(7.5), letterSpacing: pt(1.3), color: col('muted'),
  placement: at('page', edge, x, 13), ...extra,
});
const folio = { color: col('rubric'), fontWeight: 700 };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', MARGIN.outer,
    { ...folio, align: 'left' }),
  head('verso-title', '{title}', 'even', 'top-left', MARGIN.outer + 8, { align: 'left' }),
  head('recto-title', '{chapterTitle}', 'odd', 'top-right', -(MARGIN.outer + 8),
    { align: 'right', overflow: 'ellipsis' }),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -MARGIN.outer,
    { ...folio, align: 'right' }),
] };
// The opener carries a drop folio instead, centred under the text block.
const footer = { elements: [{ ...head('drop-folio', '{pageNumber}', 'all', 'bottom', 0,
  { ...folio, align: 'center', placement: at('page', 'bottom', 0, -11) }), pages: 'opener' }] };
```

أرقام الصفحات بالأحمر القرمزي على الحافة الخارجية، واسم الكتاب على الصفحة الزوجية واسم الفصل على الفردية، و`pages: 'body'` يبعدها عن صفحة الافتتاح التي تأخذ رقمًا في وسط أسفل الصفحة.

## الوصفة كاملة

ملف واحد، مركّب من مجلد الوصفة ومعه نص المثال وأدوات دليل الوصفات المشتركة مضمّنة؛ وهو ينشئ صفحته بنفسه. لتشغيله، ضعه داخل `<script type="module">` في صفحة فارغة أو الصقه في لوحة JS في مثال CodePen جديد (وحدةً). يستورد postext من esm.sh، فلا حاجة إلى تثبيت أو بناء.

- مجلد الوصفة: https://github.com/drnachio/postext/tree/main/cookbook/musicology-mei-apparatus

### script.js

````js
// ═══ Postext Cookbook · Nº 165 · MEI music examples with a critical apparatus ═════
// https://postext.dev/en/cookbook/musicology-mei-apparatus
// Code: MIT · Text: original (CC BY 4.0) · Music: Sumer is icumen in, after Rockstro (PD)
// Fonts: Crimson Pro, Cormorant Garamond, Source Sans 3 (SIL OFL 1.1) · Needs postext ≥ 1.54.0
import {
  buildDocumentWithFonts, renderPageToCanvas, initMusicEngine,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';

const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'musicology-mei-apparatus';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: a rubric red for kickers, labels and the notes' rule; ink on cream
const palette = {
  ink: '#211d1a', // text and notation: a warm near-black
  rubric: '#8c2f1f', // the one accent, the red of the manuscript's rubrics
  muted: '#6c645b', // running heads, colophon
  rule: '#cbbfae', // hairlines
  paper: '#fcfaf6',
};
// col(id): a colour linked to its palette entry, with the entry's hex beside the id.
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = [
  ...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })),
  // The engine's defaults link to main-color: point it at the accent, never the default blue.
  { id: 'main-color', name: 'accent (defaults)', value: { hex: palette.rubric, model: 'hex' } },
];
// #endregion
const MARGIN = { top: 22, bottom: 22, inner: 19, outer: 21 }; // 156 × 234 mm, mirrored
const LEAD = 14.5; // body leading in pt: the baseline grid
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });
const label = { fontFamily: 'Source Sans 3', fontWeight: 600, textTransform: 'uppercase' };

// #region answer: MEI examples engraved in the text, their apparatus in footnotes
// In the Markdown each example is a ```mei fence. A variant is encoded where it stands,
// <app><lem>…</lem><rdg source="#Hw #Bu">…</rdg></app>: the staff shows the lemma, and
// the file keeps the reading for anyone who opens the encoding. The apparatus entry is a
// footnote cited from the paragraph that discusses the bar: `[^b40]: **b. 40, n. 2** A ] G`.
const scoreStyle = {
  staffSize: mm(5), // running-text examples: 4.5–5.5 mm
  musicFont: 'Leipzig', // lighter than Bravura, close to a 19th-century plate
  textFont: 'Crimson Pro', // the underlay in the book's own face
  barNumbers: 'system', // the apparatus is keyed by bar: number every system
  spacing: 0.95,
  systemGap: mm(4),
  marginTop: pt(4),
  marginBottom: pt(LEAD / 2),
  color: col('ink'),
  minSystemsAtBreak: 2, // never leave one system of an example alone at a page foot
};
const footnotes = {
  placement: 'column', // the foot of the page in one column
  numbering: 'chapter',
  fontSize: pt(8.5),
  lineHeight: pt(10.75),
  hangingIndent: em(1.2), // turnover lines align past the number
  spaceBetween: pt(2),
  separator: { width: 0.2, lineWidth: pt(0.5), color: col('rubric') },
};
// #endregion

// #region opener: kicker, a Cormorant italic title and a short red rule, sunk 50 mm
const opener = {
  enabled: true,
  minHeight: mm(50), // every chapter's text starts on the same grid line
  slot: { elements: [
    { kind: 'text', id: 'kicker', content: '{attr.kicker}', ...label, fontSize: pt(8.5),
      letterSpacing: pt(1.8), color: col('rubric'), align: 'left',
      placement: at('container', 'top-left', 0, 8) },
    { kind: 'text', id: 'title', content: '{titleText}', fontFamily: 'Cormorant Garamond',
      italic: true, fontWeight: 500, fontSize: pt(29), lineHeight: 1.05, color: col('ink'),
      align: 'left', overflow: 'wrap', // design text ends in an ellipsis by default
      placement: { ...at('#kicker', 'below', 0, 3), size: { width: mm(100), height: 'auto' } } },
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(1), color: col('rubric'),
      placement: { ...at('#title', 'below', 0, 5), size: { width: mm(14) } } },
  ] },
};
// #endregion

// #region running-heads: book title on the verso, chapter on the recto, folios outside
const head = (id, content, parity, edge, x, extra = {}) => ({
  kind: 'text', id, content, parity, pages: 'body', // never on the opener
  ...label, fontSize: pt(7.5), letterSpacing: pt(1.3), color: col('muted'),
  placement: at('page', edge, x, 13), ...extra,
});
const folio = { color: col('rubric'), fontWeight: 700 };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', MARGIN.outer,
    { ...folio, align: 'left' }),
  head('verso-title', '{title}', 'even', 'top-left', MARGIN.outer + 8, { align: 'left' }),
  head('recto-title', '{chapterTitle}', 'odd', 'top-right', -(MARGIN.outer + 8),
    { align: 'right', overflow: 'ellipsis' }),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -MARGIN.outer,
    { ...folio, align: 'right' }),
] };
// The opener carries a drop folio instead, centred under the text block.
const footer = { elements: [{ ...head('drop-folio', '{pageNumber}', 'all', 'bottom', 0,
  { ...folio, align: 'center', placement: at('page', 'bottom', 0, -11) }), pages: 'opener' }] };
// #endregion

const config = {
  locale: t({ en: 'en-gb', es: 'es' }), // the language of the hyphenation patterns
  colorPalette,
  page: { sizePreset: 'custom', width: mm(156), height: mm(234), dpi: 150,
    backgroundColor: col('paper'),
    margins: { top: mm(MARGIN.top), bottom: mm(MARGIN.bottom), left: mm(MARGIN.inner),
      right: mm(MARGIN.outer), mirror: true } }, // left = inner
  layout: { layoutType: 'single' },
  bodyText: { fontFamily: 'Crimson Pro', fontSize: pt(11), lineHeight: pt(LEAD),
    color: col('ink'), boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
    textAlign: 'justify', firstLineIndent: mm(4.5), indentAfterHeading: false,
    hyphenation: { enabled: true }, optimalLineBreaking: true,
    avoidWidows: true, avoidOrphans: true, avoidRunts: true },
  headings: { fontFamily: 'Crimson Pro', color: col('ink'),
    // A page whose next line cannot take its footnote ends short: at most one extra grid
    // line goes above a heading, the rest stays at the foot, as in a book with notes.
    balancing: { maxLinesPerHeading: 1 },
    levels: [
    { level: 1, advancedDesign: opener, marginBottom: pt(0),
      breakBefore: { enabled: true, parity: 'odd' } },
    { level: 2, fontFamily: 'Cormorant Garamond', fontSize: pt(14), lineHeight: pt(LEAD),
      fontWeight: 600, italic: true, color: col('ink'), marginTop: pt(LEAD), marginBottom: pt(3) },
    // The example labels: a heading keeps itself with the score under it.
    { level: 3, ...label, fontSize: pt(7.5), lineHeight: pt(LEAD), letterSpacing: pt(1.1),
      color: col('rubric'), marginTop: pt(LEAD / 2), marginBottom: pt(0) },
  ] },
  paragraphStyles: [
    { id: 'colophon', ...label, textTransform: 'none', fontWeight: 400, fontSize: pt(7.5),
      lineHeight: pt(10), color: col('muted'), textAlign: 'left', firstLineIndent: pt(0),
      marginTop: pt(LEAD) },
  ],
  scoreStyle,
  footnotes,
  header,
  footer,
};

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = `---
title: "The Antiquaries’ Middle Ages"
---

# The Reading Rota and Its Editors {kicker="Chapter 3"}

No piece of medieval music was printed, scored and argued over by English antiquaries as often as the six-voice rota *Sumer is icumen in*. It survives on a single leaf, fol. 11v of the miscellany that Humfrey Wanley described for the Earl of Oxford around 1709 and that is now British Library, Harley MS 978. Wanley called it the oldest piece of its kind he had seen. Between Sir John Hawkins’s *General History* of 1776 and the article that William Smyth Rockstro wrote for George Grove’s *Dictionary of Music and Musicians*, the rota went through at least five scores, each claiming to correct the last.

This chapter follows that sequence of editions and tests it at three places where the editors disagreed. The music examples are new transcriptions, and their critical apparatus stands in the notes: each entry gives the bar and the note, the reading adopted, a closing bracket, and the readings of the other witnesses.

## The witnesses and this edition

Four witnesses are cited. **H** is the manuscript. **Hw** is the score in the first volume of Hawkins’s *General History of the Science and Practice of Music* (1776). **Bu** is the score in the second volume of Charles Burney’s *General History of Music* (1782). **Ro** is Rockstro’s solution of the canon, printed with the first part of his article in the third volume of Grove’s *Dictionary*.[^ro]

The examples follow Ro in pitch, clefs and note values, since Rockstro is the only editor of the period who explains every substitution he made. The manuscript, he writes, uses only a C clef, the *B rotundum*, square black notes with tails, one square note without a tail, and black lozenges. He renders a tailed note as a dotted semibreve when it is perfect and as a semibreve when it is imperfect, the untailed square note as a semibreve, and the lozenges as minims, under a signature of perfect time with the lesser prolation, “closely corresponding with the Signature 3-2 in modern Music”.[^values] The readings of H, Hw and Bu below are those that Rockstro reports. This chapter has not collated the two eighteenth-century prints afresh, and where Rockstro is silent, the apparatus is silent too.

## The first phrase and a corrected note

### Example 3.1 · Cantus I, bars 1–7, with the English and Latin texts

\`\`\`mei {alt="Sumer is icumen in, the first voice, bars 1 to 7, with the English words and the Latin text Perspice Christicola"}
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.1">
<meiHead><fileDesc><titleStmt><title>Sumer is icumen in, Cantus I, bars 1–7</title></titleStmt><pubStmt/><sourceDesc><source xml:id="H"><bibl>BL Harley 978, f. 11v</bibl></source><source xml:id="Ro"><bibl>Rockstro, Grove iii</bibl></source></sourceDesc></fileDesc></meiHead>
<music><body><mdiv><score>
<scoreDef meter.count="3" meter.unit="2" keysig="1f"><staffGrp><staffDef n="1" lines="5" clef.shape="G" clef.line="2"/></staffGrp></scoreDef>
<section>
<measure n="1"><staff n="1"><layer n="1"><note pname="f" oct="5" dur="1"><verse n="1"><syl wordpos="i" con="d">Sum</syl></verse><verse n="2"><syl wordpos="i" con="d">Per</syl></verse></note><app><lem><note pname="e" oct="5" dur="2"><verse n="1"><syl wordpos="t">er</syl></verse><verse n="2"><syl wordpos="m" con="d">spi</syl></verse></note></lem><rdg source="#H"><note pname="f" oct="5" dur="2"><verse n="1"><syl wordpos="t">er</syl></verse><verse n="2"><syl wordpos="m" con="d">spi</syl></verse></note></rdg></app></layer></staff></measure>
<measure n="2"><staff n="1"><layer n="1"><note pname="d" oct="5" dur="1"><verse n="1"><syl>is</syl></verse><verse n="2"><syl wordpos="t">ce</syl></verse></note><note pname="e" oct="5" dur="2"><verse n="1"><syl wordpos="i" con="d">i</syl></verse><verse n="2"><syl wordpos="i" con="d">Chri</syl></verse></note></layer></staff></measure>
<measure n="3"><staff n="1"><layer n="1"><note pname="f" oct="5" dur="1"><verse n="1"><syl wordpos="m" con="d">cum</syl></verse><verse n="2"><syl wordpos="m" con="d">sti</syl></verse></note><note pname="f" oct="5" dur="2"><verse n="1"><syl wordpos="t">en</syl></verse><verse n="2"><syl wordpos="m" con="d">co</syl></verse></note></layer></staff></measure>
<measure n="4"><staff n="1"><layer n="1"><note pname="e" oct="5" dur="2" dots="1"><verse n="1"><syl>in,</syl></verse><verse n="2"><syl wordpos="t">la,</syl></verse></note><note pname="d" oct="5" dur="4"/><note pname="c" oct="5" dur="2"/></layer></staff></measure>
<measure n="5"><staff n="1"><layer n="1"><note pname="a" oct="4" dur="1"><verse n="1"><syl wordpos="i" con="d">Lhud</syl></verse><verse n="2"><syl>que</syl></verse></note><note pname="a" oct="4" dur="2"><verse n="1"><syl wordpos="t">e</syl></verse><verse n="2"><syl wordpos="i" con="d">dig</syl></verse></note></layer></staff></measure>
<measure n="6"><staff n="1"><layer n="1"><note pname="b" oct="4" dur="1"><verse n="1"><syl>sing</syl></verse><verse n="2"><syl wordpos="m" con="d">na</syl></verse></note><note pname="g" oct="4" dur="2"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse><verse n="2"><syl wordpos="m" con="d">ci</syl></verse></note></layer></staff></measure>
<measure n="7"><staff n="1"><layer n="1"><note pname="a" oct="4" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu.</syl></verse><verse n="2"><syl wordpos="t">o.</syl></verse></note></layer></staff></measure>
</section></score></mdiv></body></music></mei>
\`\`\`

The opening is the best-documented phrase of the rota, because Rockstro returned to the leaf itself in the second part of his article and listed the erasures he could see. The scribe corrected his own work in the same ink, and the first correction falls on the second note.[^b1] Rockstro took the erasures as improvements made while the piece was being written down rather than as repairs of a faulty copy, and used them to support his view that the canon grew out of a popular tune that a trained musician adjusted until its phrases fitted against one another.

Two further decisions are visible in bars 2 and 4. The Latin text is printed in full where the manuscript abbreviates the name of Christ,[^b2] and the single tailed lozenge under the word *in* becomes a dotted minim and a crotchet.[^b4] The flat in the signature stands for the *B rotundum* of the manuscript, which Rockstro reads as a B flat throughout.[^bflat]

## The pes

### Example 3.2 · Pes, bars 1–8, as scored by Rockstro

\`\`\`mei {alt="Sumer is icumen in, the two lower voices of the pes, bars 1 to 8"}
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.1">
<meiHead><fileDesc><titleStmt><title>Sumer is icumen in, the pes, bars 1–8</title></titleStmt><pubStmt/><sourceDesc><source xml:id="Ro"><bibl>Rockstro, Grove iii</bibl></source></sourceDesc></fileDesc></meiHead>
<music><body><mdiv><score>
<scoreDef meter.count="3" meter.unit="2" keysig="1f"><staffGrp symbol="bracket" bar.thru="false"><staffDef n="1" lines="5" clef.shape="F" clef.line="4"/><staffDef n="2" lines="5" clef.shape="F" clef.line="4"/></staffGrp></scoreDef>
<section>
<measure n="1"><staff n="1"><layer n="1"><note pname="f" oct="3" dur="1" dots="1"><verse n="1"><syl>Sing</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="c" oct="4" dur="1" dots="1"><verse n="1"><syl>Sing</syl></verse></note></layer></staff></measure>
<measure n="2"><staff n="1"><layer n="1"><note pname="g" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="b" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff></measure>
<measure n="3"><staff n="1"><layer n="1"><note pname="f" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="c" oct="4" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu.</syl></verse></note></layer></staff></measure>
<measure n="4"><staff n="1"><layer n="1"><note pname="g" oct="3" dur="1"><verse n="1"><syl>nu.</syl></verse></note><note pname="a" oct="3" dur="2"/></layer></staff><staff n="2"><layer n="1"><mRest/></layer></staff></measure>
<measure n="5"><staff n="1"><layer n="1"><note pname="c" oct="4" dur="1" dots="1"><verse n="1"><syl>Sing</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="f" oct="3" dur="1" dots="1"><verse n="1"><syl>Sing</syl></verse></note></layer></staff></measure>
<measure n="6"><staff n="1"><layer n="1"><note pname="b" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="g" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff></measure>
<measure n="7"><staff n="1"><layer n="1"><note pname="c" oct="4" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu.</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="f" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu</syl></verse></note></layer></staff></measure>
<measure n="8"><staff n="1"><layer n="1"><mRest/></layer></staff><staff n="2"><layer n="1"><note pname="g" oct="3" dur="1"><verse n="1"><syl>nu.</syl></verse></note><note pname="a" oct="3" dur="2"/></layer></staff></measure>
</section></score></mdiv></body></music></mei>
\`\`\`

Below the four voices of the canon the manuscript writes two short parts to be sung as a ground, each to the words *Sing cuccu nu*. The Latin rubric opens *Hanc rotam cantare possunt quatuor socii*: four companions can sing the round, not counting those who hold the pes. Each new voice begins “at the first note after the cross”, which Ro marks over bar 5. The first pes is repeated *faciens pausacionem in fine*, the second *pausans in medio et non in fine*.[^rubric] Ro follows the rubric exactly: the first pes rests in bar 8, the second in bar 4, so that one of the two lower voices is always sounding.

The pes is also where the eighteenth-century scores went most visibly wrong. According to Rockstro, Hawkins misread both of its ligatures and printed the passage incorrectly at every repetition; Burney corrected the mistake.[^lig] Neither ligature can be shown in a modern score, and Example 3.2 resolves both into separate notes, as Ro does.

Rockstro’s own analysis of the part-writing begins from this score. The fourth, sixth, seventh, eighth and twenty-fourth bars, he observes, are in strict two-part counterpoint “of irreproachable purity”, while the passage from bar 9 to bar 10 gives consecutive fifths between the first and third voices of the canon, and bars 37 to 39 three consecutive fifths between the first cantus and the second voice of the pes.[^fifths] For Rockstro the mixture of styles was the strongest argument for a melody older than its harmony.

## The question of date

The dates proposed for the leaf say as much about the editors as about the manuscript. Hawkins placed it in the middle of the fifteenth century, arguing that its notation was the *cantus figuratus* he credited to John Dunstable, who, Hawkins wrote, died in 1455; Rockstro called the reasoning an anachronism “absolutely worthless”.[^hawkins] Burney thought the piece not much later than the thirteenth or fourteenth century. Joseph Ritson referred the manuscript to the middle of the thirteenth, and Thomas Busby, reprinting Burney’s score note for note in 1819, returned it to the fifteenth.

The decisive arguments came from palaeography. In 1855 William Chappell published a coloured facsimile in *Popular Music of the Olden Time* and later argued that the leaf was written at Reading Abbey by a monk named John of Fornsete around 1226. In April 1862 Sir Frederick Madden noted on a flyleaf of the volume that the whole was of the thirteenth century and that its earlier part had been written at Reading about 1240, a date the Palaeographical Society adopted for its facsimile of 1878. On the Continent, Johann Nikolaus Forkel (1788), August Wilhelm Ambros (1862 and 1868) and Edmond de Coussemaker (1865) all wrote on the rota without seeing the manuscript, a point Rockstro makes against each of them in turn.[^continent] A more recent study puts the copying between 1261 and 1264.[^wulstan]

## “Cuccu” in bar 40

### Example 3.3 · Cantus I, bars 37–47

\`\`\`mei {alt="Sumer is icumen in, the first voice, bars 37 to 47, from Cuccu, cuccu to ne swik thu nauer nu"}
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.1">
<meiHead><fileDesc><titleStmt><title>Sumer is icumen in, Cantus I, bars 37–47</title></titleStmt><pubStmt/><sourceDesc><source xml:id="Hw"><bibl>Hawkins 1776</bibl></source><source xml:id="Bu"><bibl>Burney 1782</bibl></source><source xml:id="Ro"><bibl>Rockstro, Grove iii</bibl></source></sourceDesc></fileDesc></meiHead>
<music><body><mdiv><score>
<scoreDef meter.count="3" meter.unit="2" keysig="1f"><staffGrp><staffDef n="1" lines="5" clef.shape="G" clef.line="2"/></staffGrp></scoreDef>
<section>
<measure n="37"><staff n="1"><layer n="1"><note pname="c" oct="5" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">Cuc</syl></verse></note></layer></staff></measure>
<measure n="38"><staff n="1"><layer n="1"><note pname="d" oct="5" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu,</syl></verse></note></layer></staff></measure>
<measure n="39"><staff n="1"><layer n="1"><note pname="c" oct="5" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff></measure>
<measure n="40"><staff n="1"><layer n="1"><note pname="b" oct="4" dur="1"><verse n="1"><syl wordpos="t">cu.</syl></verse></note><app><lem><note pname="a" oct="4" dur="2"/></lem><rdg source="#Hw #Bu"><note pname="g" oct="4" dur="2"/></rdg></app></layer></staff></measure>
<measure n="41"><staff n="1"><layer n="1"><note pname="f" oct="4" dur="1"><verse n="1"><syl>Wel</syl></verse></note><note pname="a" oct="4" dur="2"><verse n="1"><syl wordpos="i" con="d">sing</syl></verse></note></layer></staff></measure>
<measure n="42"><staff n="1"><layer n="1"><note pname="b" oct="4" dur="1"><verse n="1"><syl wordpos="t">es</syl></verse></note><note pname="g" oct="4" dur="2"><verse n="1"><syl>thu</syl></verse></note></layer></staff></measure>
<measure n="43"><staff n="1"><layer n="1"><note pname="a" oct="4" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff></measure>
<measure n="44"><staff n="1"><layer n="1"><note pname="b" oct="4" dur="1"><verse n="1"><syl wordpos="t">cu,</syl></verse></note><note pname="c" oct="5" dur="2"><verse n="1"><syl>ne</syl></verse></note></layer></staff></measure>
<measure n="45"><staff n="1"><layer n="1"><note pname="a" oct="4" dur="1"><verse n="1"><syl>swik</syl></verse></note><note pname="c" oct="5" dur="2"><verse n="1"><syl>thu</syl></verse></note></layer></staff></measure>
<measure n="46"><staff n="1"><layer n="1"><note pname="g" oct="4" dur="1"><verse n="1"><syl wordpos="i" con="d">nau</syl></verse></note><note pname="e" oct="4" dur="2"><verse n="1"><syl wordpos="t">er</syl></verse></note></layer></staff></measure>
<measure n="47"><staff n="1"><layer n="1"><note pname="f" oct="4" dur="1" dots="1"><verse n="1"><syl>nu.</syl></verse></note></layer></staff></measure>
</section></score></mdiv></body></music></mei>
\`\`\`

The last passage of the tune shows how an error could travel. Hawkins and Burney, Rockstro reports, both print a G where the manuscript has an A in bar 40, on the second syllable of *cuccu*, and they do so each time the phrase returns in a new voice.[^b40] Against Hawkins’s faulty pes the G passes; against Burney’s corrected pes it makes what Rockstro calls “a horrible discord”. The variant is encoded in the source of Example 3.3 as a reading of Hw and Bu, while the engraved staff shows the lemma.

Both historians also fitted the words to the notes differently from the manuscript from bar 41 onwards, at *Wel singes thu cuccu, ne swik thu nauer nu*.[^b41] Rockstro gives no details of their underlay, and the example therefore records only the fact. The one modern copy Rockstro knew went further and left out the pes, reducing the rota to four parts.

:::paragraphs{style="colophon"}
Set in Crimson Pro, Cormorant Garamond and Source Sans 3 (SIL OFL); music in Leipzig, engraved by Verovio · Text: original, CC BY 4.0 · Music: *Sumer is icumen in* (13th century), after W. S. Rockstro’s score in Grove’s *Dictionary of Music and Musicians*, iii, 766–767 (public domain)
:::

[^ro]: W. S. Rockstro, “Sumer is icumen in”, in G. Grove (ed.), *A Dictionary of Music and Musicians*, iii, 765–768, continued in iv, 1–3 (London: Macmillan, 1900 printing). The score, iii, 766–767, is cited from the Wikisource transcription of that printing.

[^values]: Ro, iii, 765. The tailed lozenge on the first note of *in* is the one exception (see the note on bar 4).

[^b1]: **b. 1, n. 2** E ] F *H* before correction (“The second note on Stave 1 was originally an F”, Ro, iv, 1). The reading before correction is encoded as a variant in Example 3.1.

[^b2]: **b. 2, n. 2 – b. 4, n. 1** *Chri-sti-co-la* ] abbreviated with the Greek letters *chi* and *rho* *H*, Ro. Expanded as Rockstro glosses it, *Christicola* (iii, 765).

[^b4]: **b. 4, nn. 1–2** dotted minim, crotchet ] one lozenge with a tail *H*, the only tailed lozenge in the manuscript, which Rockstro could “scarcely conceive to be accidental” (iii, 765).

[^bflat]: **bb. 1–7, signature** B flat ] *B rotundum* *H*. The flat is printed in the signature, as in Ro.

[^rubric]: The rubric is quoted from the transcription and translation in the Wikipedia article “Sumer is icumen in”, which also gives the folio. Rockstro scores the canon “in exact accordance with the Latin directions” (iii, 765) but does not print them.

[^lig]: **Pes, bb. 1–8** both ligatures misread at every statement *Hw*; corrected *Bu* (Ro, iii, 765). Rockstro does not say how Hawkins read them, so no variant is encoded.

[^fifths]: Ro, iv, 2. The bar numbers refer to Rockstro’s score and to the examples of this chapter.

[^hawkins]: Ro, iii, 765. Rockstro dates Hawkins’s first volume 1770; the *General History* appeared in 1776.

[^continent]: Ro, iii, 768, and iv, 1, citing Forkel, *Allgemeine Geschichte der Musik*, ii, 490–500; Ambros, *Geschichte der Musik*, ii, 473–475; Coussemaker, *L’Art harmonique*, 144.

[^wulstan]: D. Wulstan, “‘Sumer is icumen in’: A Perpetual Puzzle-Canon?”, *Plainsong and Medieval Music* 9 (2000), 1–17, at 8. Wulstan proposes W. de Wycombe as the scribe.

[^b40]: **b. 40, n. 2** A ] G *Hw*, *Bu* (and Busby 1819, after *Bu*), at every entry of the phrase (Ro, iii, 765). Encoded as a variant in Example 3.3.

[^b41]: **bb. 41–47, underlay** as *H* ] text placed otherwise under the notes *Hw*, *Bu* (Ro, iii, 765).
`; // content.<lang>.md, inlined by the Cookbook

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
// Every face the design uses, loaded before the first build with the ones the config names.
const FONTS = {
  'Crimson Pro': ['400', '400i', '600', '600i', '700'],
  'Cormorant Garamond': ['500i', '600i'],
  'Source Sans 3': ['400', '600', '700'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
await initMusicEngine(); // Verovio, loaded only by books that hold a score
const doc = await buildDocumentWithFonts({ markdown }, config, kitFonts(FONTS));
showPages(doc, { title: t({ en: 'Music examples with a critical apparatus',
  es: 'Ejemplos musicales con aparato crítico' }) });
offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider }), `${RECIPE}.pdf`);

// ─── Kit ── helpers shared by every Cookbook recipe · postext.dev/cookbook ─────

// ─── Kit · core v1 ── the same in every recipe · postext.dev/cookbook
function mm(value) { return { value, unit: 'mm' }; }
function pt(value) { return { value, unit: 'pt' }; }
function em(value) { return { value, unit: 'em' }; }
/** The sample language's string: t({ en: 'Figure', es: 'Figura' }). */
function t(strings) { return strings[LANG] ?? Object.values(strings)[0]; }
/** A file in this recipe's assets folder, served from the Postext repo by jsDelivr. */
function asset(file) { return `https://cdn.jsdelivr.net/gh/drnachio/postext@main/cookbook/${RECIPE}/assets/${file}`; }

// ─── Kit · fonts v3 ── the same in every recipe · postext.dev/cookbook
// Postext loads the faces a config asks for before layout (prepareFonts,
// buildDocumentWithFonts) and asks the kit for the files: Fontsource's, the
// files the PDF embeds too.

/** The options of buildDocumentWithFonts / prepareFonts / withLoadedFonts:
 *  the Fontsource resolver, and the faces of FONTS loaded with the ones the
 *  config names. FONTS = { 'Family Name': ['400', '400i', '700'] }. */
function kitFonts(FONTS = {}) {
  const faces = Object.entries(FONTS).flatMap(([family, specs]) => specs.map((spec) =>
    ({ family, weight: parseInt(spec, 10), style: spec.endsWith('i') ? 'italic' : 'normal' })));
  return { resolve: fontsourceResolver, faces };
}

/** A face's Fontsource files, in a fixed order: latin, then latin-ext and greek
 *  when `text` needs them and the family ships them (kitSubsetsFor), each with
 *  its unicode-range. Null for a weight or slant Fontsource does not ship. */
async function fontsourceResolver(family, weight, style, { text = '' } = {}) {
  const id = fontsourceId(family);
  const meta = await fontsourceMeta(family);
  if (meta && !(meta.weights.includes(weight) && meta.styles.includes(style))) return null;
  kitStatus('Loading fonts…');
  const at = await fontsourceAt(id);
  return ['latin', ...kitSubsetsFor(text, meta)].map((subset) => ({
    source: `${at}files/${id}-${subset}-${weight}-${style}.woff2`,
    unicodeRange: kitRanges()[subset],
  }));
}

/** The unicode-range of each Fontsource file the kit loads (a function: the
 *  kit is inlined after the pen's code, which runs before a const would be set). */
function kitRanges() {
  return {
    latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,'
      + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD',
    'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,'
      + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF',
    greek: 'U+0370-03FF',
  };
}

/** For the cjk, arabic and comics blocks, which add their own files: the
 *  faces of `faces` = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample:
 *  č ł † α χ also load latin-ext and greek files (kitSubsetsFor). With
 *  `optional`, a face Fontsource does not ship is skipped instead of failing.
 *  The files are added once all have loaded, in the order asked (latin, then
 *  latin-ext, then greek), whatever order they arrive in: the last one added
 *  wins a character two files hold. Resolves to the number of faces added. */
async function loadFonts(faces, text = '', { optional = false } = {}) {
  kitStatus('Loading fonts…');
  const ranges = kitRanges();
  const jobs = [];
  for (const [family, specs] of Object.entries(faces)) {
    const id = fontsourceId(family);
    const todo = [...new Set(specs)].map((spec) => [parseInt(spec, 10), spec.endsWith('i') ? 'italic' : 'normal'])
      .filter(([weight, style]) => !hasFace(family, weight, style)); // before any await
    const at = fontsourceAt(id);
    const meta = optional || /[^\0-ÿ]/u.test(text) ? await fontsourceMeta(family) : null;
    const subsets = ['latin', ...kitSubsetsFor(text, meta)];
    for (const [weight, style] of todo) {
      if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue;
      for (const subset of subsets) {
        const url = `${await at}files/${id}-${subset}-${weight}-${style}.woff2`;
        const face = new FontFace(family, `url(${url}) format('woff2')`,
          { weight: String(weight), style, unicodeRange: ranges[subset] });
        jobs.push(face.load().catch(() => {
          if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`);
        }));
      }
    }
  }
  const ready = await Promise.all(jobs).catch((error) => { kitFail(error); throw error; });
  return ready.filter((face) => face && document.fonts.add(face)).length;
}

/** A loaded FontFace covers this family, weight and style (fonts.check() would
 *  also say yes for families nobody declared). */
function hasFace(family, weight, style) {
  for (const face of document.fonts) {
    if (face.status !== 'loaded' || face.style !== style) continue;
    if (face.family.replace(/^["']|["']$/g, '') !== family) continue;
    const [low, high = low] = face.weight.split(' ').map(Number);
    if (weight >= low && weight <= high) return true;
  }
  return false;
}

/** The files beyond latin `text` needs that `meta`'s family ships. */
function kitSubsetsFor(text, meta) {
  return [[/[Ā-˿ᴀ-ᶿḀ-ỿ†ℓⱠ-Ɀ꜠-ꟿ]/u, 'latin-ext'], [/[Ͱ-Ͽ]/u, 'greek']]
    .filter(([re, x]) => re.test(text) && meta?.subsets?.includes(x)).map(([, x]) => x);
}

/** Fontsource's id for a family: 'Source Serif 4' → 'source-serif-4'. */
function fontsourceId(family) { return family.toLowerCase().replace(/\s+/g, '-'); }

/** A package's folder at the one version @5 names now (#766). */
function fontsourceAt(id, u = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@`) {
  return (fontsourceAt.c ??= {})[id] ??= fetch(u + '5/package.json').then((r) => r.json())
    .then((p) => p.version || 5, () => 5).then((v) => `${u}${v}/`);
}

/** The family's Fontsource metadata (weights, styles, subsets), or null. */
function fontsourceMeta(family) {
  fontsourceMeta.cache ??= new Map();
  const id = fontsourceId(family);
  if (!fontsourceMeta.cache.has(id)) {
    fontsourceMeta.cache.set(id, fetch(`https://api.fontsource.org/v1/fonts/${id}`)
      .then((res) => (res.ok ? res.json() : null), () => null));
  }
  return fontsourceMeta.cache.get(id);
}

// ─── Kit · viewer v2 ── the same in every recipe · postext.dev/cookbook
/** The pages as spreads on a dark desk, page 1 alone, then verso | recto,
 *  each painted when it scrolls near. Other options go to renderPageToCanvas. */
function showPages(docs, { title, width = 460, ...render } = {}) {
  const root = viewer(title);
  const pages = [docs].flat().flatMap((doc) =>
    doc.pages.map((page) => ({ doc, page, n: (doc.pageIndexOffset ?? 0) + page.index })));
  const spreads = [];
  let verso = null;
  for (const p of pages) {
    if (p.n % 2 === 1) { if (verso) spreads.push([verso, null]); verso = p; }
    else { spreads.push([verso, p]); verso = null; }
  }
  if (verso) spreads.push([verso, null]);
  const density = Math.min(window.devicePixelRatio || 1, 2);
  showPages.painter?.disconnect();
  const painter = new IntersectionObserver((entries) => {
    for (const { isIntersecting, target } of entries) {
      if (!isIntersecting) continue;
      painter.unobserve(target);
      const { doc, page } = target.postext;
      renderPageToCanvas(page, doc, target, { ...render, scale: (width * density) / page.width });
    }
  }, { rootMargin: '800px' });
  showPages.painter = painter;
  root.replaceChildren(...spreads.map((pair) => {
    const spread = document.createElement('div');
    spread.className = 'pt-spread';
    for (const p of pair) {
      const figure = document.createElement('figure');
      if (p) {
        const label = p.page.pageLabel || String(p.n + 1);
        const canvas = document.createElement('canvas');
        canvas.postext = p;
        canvas.style.aspectRatio = `${p.page.width} / ${p.page.height}`;
        canvas.setAttribute('role', 'img');
        canvas.setAttribute('aria-label', `Page ${label}`);
        const folio = document.createElement('figcaption');
        folio.textContent = label;
        figure.append(canvas, folio);
        painter.observe(canvas);
      } else figure.className = 'pt-blank';
      spread.append(figure);
    }
    return spread;
  }));
  kitStatus(`${pages.length} ${pages.length === 1 ? 'page' : 'pages'}`);
  document.documentElement.dataset.postext = 'ready';
  return pages.length;
}

/** The desk, the bar and the error reporting, created once. */
function viewer(title) {
  if (!document.getElementById('pt-kit')) {
    document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit">
      :root { color-scheme: dark; }
      body { margin: 0; background: #0e1014; color: #b9bcc4; font: 13px/1.45 system-ui, sans-serif; }
      #pt-bar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center;
        gap: 6px 16px; padding: 10px 16px; background: rgb(14 16 20 / .92); backdrop-filter: blur(6px);
        border-bottom: 1px solid #23262d; }
      #pt-bar strong { color: #f4f1ea; font-weight: 600; }
      #pt-actions { display: flex; gap: 12px; margin-left: auto; }
      #pt-actions a, #pt-actions button { color: #d8a21a; font: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
      #pages { display: grid; justify-items: center; gap: 48px; padding: 32px 16px 72px; }
      .pt-spread { display: flex; }
      .pt-spread figure { margin: 0; width: min(460px, 44vw); }
      .pt-spread canvas { display: block; width: 100%; background: #fff;
        box-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figure:first-child canvas { box-shadow: inset -14px 0 14px -14px rgb(0 0 0 / .18), 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figcaption { margin-top: 10px; text-align: center; font: 600 10px/1 system-ui, sans-serif;
        letter-spacing: .18em; text-transform: uppercase; color: #6c7079; }
      .pt-blank { visibility: hidden; }
      @media (max-width: 760px) {
        .pt-spread { flex-direction: column; gap: 32px; }
        .pt-spread figure { width: min(460px, 92vw); }
        .pt-blank { display: none; }
      }
    </style>`);
    document.body.insertAdjacentHTML('afterbegin',
      '<header id="pt-bar"><strong id="pt-title"></strong><span id="pt-status" role="status"></span><span id="pt-actions"></span></header>');
    document.getElementById('pt-title').textContent = document.title || 'Postext';
    addEventListener('error', (event) => kitFail(event.error ?? event.message));
    addEventListener('unhandledrejection', (event) => kitFail(event.reason));
  }
  if (title) document.getElementById('pt-title').textContent = title;
  return document.getElementById('pages')
    ?? document.body.appendChild(Object.assign(document.createElement('main'), { id: 'pages' }));
}

function kitStatus(text) {
  viewer();
  document.getElementById('pt-status').textContent = text;
}

function kitFail(error) {
  document.documentElement.dataset.postext = 'error';
  kitStatus(`Error: ${error?.message ?? error}`);
}

// ─── Kit · pdf v3 ── the same in every recipe that exports a PDF
/** The Fontsource files the screen used, as TrueType, with their range and
 *  place: the nearest weight the family ships, upright with no italic;
 *  latin, then what its letters need (kitSubsetsFor). */
async function fontsourceProvider(family, weight, style, request) {
  const id = fontsourceId(family);
  const meta = await fontsourceMeta(family);
  const weights = meta?.weights?.length ? meta.weights : [400, 700];
  const w = weights.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a));
  const s = style === 'italic' && meta && !meta.styles.includes('italic') ? 'normal' : style;
  const text = String.fromCodePoint(...(request?.codePoints ?? []));
  const files = await Promise.all(['latin', ...kitSubsetsFor(text, meta)].map(async (subset, order) => {
    const res = await fetch(`${await fontsourceAt(id)}files/${id}-${subset}-${w}-${s}.woff2`);
    if (!res.ok) throw new Error(`Fontsource has no ${family} ${w} ${s} ${subset}`);
    return Object.assign(await decompressWoff2(new Uint8Array(await res.arrayBuffer())),
      { unicodeRange: kitRanges()[subset], order });
  }));
  return files.length === 1 ? files[0] : files;
}

/** A "Build the PDF" button; then "Open the PDF" (a new tab: CodePen's frame
 *  shows no PDFs) and a download link. */
function offerPdf(makePdf, filename) {
  viewer();
  const button = Object.assign(document.createElement('button'), { type: 'button', textContent: 'Build the PDF' });
  button.dataset.postextPdf = filename;
  button.addEventListener('click', async () => {
    button.disabled = true;
    button.textContent = 'Building the PDF…';
    try {
      const bytes = await makePdf();
      const url = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' }));
      const size = `${Math.max(1, Math.round(bytes.length / 1024))} KB`;
      button.replaceWith(
        Object.assign(document.createElement('a'), { href: url, target: '_blank', rel: 'noopener', textContent: 'Open the PDF ↗' }),
        Object.assign(document.createElement('a'), { href: url, download: filename, textContent: `Download ${filename} · ${size}` }));
    } catch (error) {
      button.disabled = false;
      button.textContent = 'Build the PDF';
      kitFail(error);
    }
  });
  document.getElementById('pt-actions').append(button);
}

// ─── /Kit ───────────────────────────────────────────────────────────────────────
````

## تنويعات

### رقّم كل المازورات

جهاز نقدي كثير المداخل في السطر الواحد أسهل تتبعًا إذا حملت كل مازورة رقمها.

```diff
-  barNumbers: 'system', // the apparatus is keyed by bar: number every system
+  barNumbers: 'every',
```

### انقش الأمثلة بخط Bravura

رؤوس النغمات الأثقل في Bravura تناسب أسلوب النقش على الألواح.

```diff
-  musicFont: 'Leipzig', // lighter than Bravura, close to a 19th-century plate
+  musicFont: 'Bravura',
```

## أخطاء شائعة

- يكتب MEI 5 دليل المقام بالصيغة `keysig="1f"` على `scoreDef` أو `staffDef`. أما السمة `key.sig` من MEI 4 فتُهمل دون أي رسالة، فتختفي علامة الخفض.
- أعطِ كل `staffDef` السمة `lines="5"`: بدونها تُنقش النغمات والمفاتيح بلا خطوط مدرج.
- يُنقش `<lem>` من `<app>` لا `<rdg>`. القراءة التي يجب أن يراها القارئ على الصفحة مكانها حاشية الجهاز أو مثال ثانٍ.

## الحقوق

- الوصفة: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- النص: The chapter text and the apparatus notes, in English and Spanish: Ignacio Ferro, CC-BY-4.0
- النص: Sumer is icumen in (13th century), the music examples transcribed from W. S. Rockstro's score and the readings his article reports, in A Dictionary of Music and Musicians, ed. George Grove, vol. 3, pp. 765–768, and vol. 4, pp. 1–3 (Macmillan, 1900 printing), as transcribed on Wikisource: Anonymous; William Smyth Rockstro ([المصدر](https://en.wikisource.org/wiki/A_Dictionary_of_Music_and_Musicians/Sumer_is_icumen_in)), ملكية عامة
- النص: The Latin rubric of Harley MS 978, f. 11v, and the folio, as quoted in the Wikipedia article (fact check): Wikipedia contributors ([المصدر](https://en.wikipedia.org/wiki/Sumer_is_icumen_in)), CC-BY-SA-4.0
- الخطوط: Crimson Pro (OFL-1.1), Cormorant Garamond (OFL-1.1), Source Sans 3 (OFL-1.1)
- الشيفرة: MIT · محتوى المثال: CC-BY-4.0

## وصفات ذات صلة

- [رقم 071 · حواشٍ سفلية في أسفل العمود الذي يحيل إليها](https://postext.dev/ar/cookbook/novel-footnotes-column-foot.md): علامات [^id] في Markdown تصير حواشي مرقّمة في أسفل العمود الذي يحيل إليها، مع مفاتيح لعمود واحد، وتعليقات في آخر الفصل، وترقيم متصل. · المستوى 2 (متوسط) · الأدب القصصي والمسرح والنثر الأدبي
- [رقم 044 · طبعة محقّقة: أرقام الأسطر وحواشٍ مرتبطة بها](https://postext.dev/ar/cookbook/critical-edition-line-numbers.md): قصيدة Lycidas لملتون بنص 1645: تعدّ lineNumbers أسطر الشعر وتطبع رقم كل سطر خامس في العمود الجانبي، وكل حاشية تبدأ برقم سطرها منضّدًا شارةً. · المستوى 3 (متقدم) · الشعر
- [رقم 163 · كتاب في الهارموني بأمثلة موسيقية مكتوبة بلغة ABC](https://postext.dev/ar/cookbook/harmony-textbook-abc-examples.md): فصل عن القفلات في كتاب للهارموني، أمثلته كتل ABC داخل Markdown تُنقش بعرض العمود، ويمتد أطولها على صفحتين. · المستوى 2 (متوسط) · الكتب المدرسية
