# كتاب أغانٍ فيه اللحن والكلمات والأكوردات

> كل أغنية كتلة ABC: اللحن، وأسماء الأكوردات فوق المدرج، والمقطع الأول تحت النوتات، يُنقش كل ذلك بعرض العمود بنمط مدونة واحد.

- نسخة HTML: https://postext.dev/ar/cookbook/songbook-melody-words-chords
- وصفة رقم 164 · منشورات كاملة · المستوى 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/songbook-melody-words-chords/en/p01.webp?v=e517901b), [2](https://postext.dev/cookbook/songbook-melody-words-chords/en/p02.webp?v=e517901b), [3](https://postext.dev/cookbook/songbook-melody-words-chords/en/p03.webp?v=e517901b), [4](https://postext.dev/cookbook/songbook-melody-words-chords/en/p04.webp?v=e517901b), [5](https://postext.dev/cookbook/songbook-melody-words-chords/en/p05.webp?v=e517901b), [6](https://postext.dev/cookbook/songbook-melody-words-chords/en/p06.webp?v=e517901b)
- PDF: https://postext.dev/cookbook/songbook-melody-words-chords/en/songbook-melody-words-chords.pdf?v=e517901b
- افتح في Sandbox: https://postext.dev/ar/sandbox#recipe=songbook-melody-words-chords&lang=en (.postext: https://postext.dev/cookbook/songbook-melody-words-chords/en/songbook-melody-words-chords.postext)
- آخر تحديث: 2026-10-22
- لغات أخرى: [en](https://postext.dev/en/cookbook/songbook-melody-words-chords.md), [es](https://postext.dev/es/cookbook/songbook-melody-words-chords.md), [ca](https://postext.dev/ca/cookbook/songbook-melody-words-chords.md), [de](https://postext.dev/de/cookbook/songbook-melody-words-chords.md), [fr](https://postext.dev/fr/cookbook/songbook-melody-words-chords.md), [it](https://postext.dev/it/cookbook/songbook-melody-words-chords.md), [pt](https://postext.dev/pt/cookbook/songbook-melody-words-chords.md), [zh](https://postext.dev/zh/cookbook/songbook-melody-words-chords.md), [ja](https://postext.dev/ja/cookbook/songbook-melody-words-chords.md)

## باختصار

كتاب أغانٍ صغير للغناء حول المائدة. تُكتب كل أغنية نصًا عاديًا بتدوين موسيقي اسمه ABC، ويرسم Postext المدرج والنوتات وأكوردات الغيتار والكلمات تحت النوتات. وتتبعها المقاطع الأخرى في عمودين.

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

*Songs for the Long Table*، كتاب أغانٍ للجيب بمقاس A5: صفحة عنوان بلون الفُوّة، وكلمة إلى المغنّين فيها المحتويات، وثلاث أغانٍ، وملاحظات عن مصدر كل واحدة منها. تبدأ كل أغنية تحت رقم كبير بلون الفوّة، ثم عنوان بخط Fraunces، وسطر نسبة بحروف كبيرة مصغّرة (small capitals). وتحته يجري اللحن على مدرج ارتفاعه 5.5 mm بخط Leland، وأكوردات الغيتار فوق المازورات، والمقطع الأول تحت النوتات بخط Spectral، وهو خط النص في الكتاب. وتقف المقاطع الباقية في عمودين، فينزل المغنّي بعينه من آخر مازورة إليها مباشرة. أما *Amazing Grace* فأسطرها الطويلة كانت ستلتف في العمود، لذلك تُنضد مقاطعها واحدًا تحت الآخر في الوسط. والطبعة الإسبانية كتاب أغانٍ لحفلات أعياد الميلاد، فيه ثلاث أغانٍ أقصر، اثنتان في كل صفحة.

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

- كيف أنضد كتاب أغانٍ فيه اللحن والكلمات تحت النوتات ورموز الأكوردات؟

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

````js
// script.js, سطرًا 33–50
// A song is an ```abc fence in the Markdown: the melody, its chords written "G" before the note
// they fall on, and the first verse on a w: line under it. The look is set here, once.
const scoreStyle = {
  staffSize: mm(5.5), // a songbook rastral; the words under it come out at about 8 pt
  musicFont: 'Leland', // lighter noteheads than Bravura, close to Spectral's colour
  textFont: TEXT, // words and chord names in the text face, not the engraver's Times
  spacing: 1, // the engraver's own: five or six bars of 3/4 to a system on this measure
  barNumbers: 'none', // a table of singers counts verses, not bars
  systemGap: mm(4.5), // room for the chord names between one system's words and the next
  marginTop: pt(4),
  marginBottom: pt(LEAD),
  color: col('ink'),
  minSystemsAtBreak: 2, // never a lone system at a page foot or head
};
// Two habits of Verovio's ABC reader shape the w: lines. Every sung note takes the next
// syllable in order: the `_` and `*` holds are read as nothing, so the second note of a
// slurred pair (B/G/ under "-ing") gets a no-break space, which prints nothing. A tied note
// takes none. `~` joins two syllables on one note: "de~a-trás".
````

## المكونات

**تعلّم**

- [التدوين الموسيقي](https://postext.dev/ar/docs/music.md#مدوّنة-داخل-النص): مدوّنات موسيقية تُكتب نصًّا داخل كتل ```abc أو ```musicxml أو ```mei أو ```humdrum، يرسمها Verovio بعرض العمود ويقسّمها إلى أنظمة تمتد عبر الأعمدة والصفحات.
- [نمط المدوّنة الموسيقية](https://postext.dev/ar/docs/configuration-resources.md#نمط-المدوّنة-الموسيقية): scoreStyle والأنماط المسمّاة في scoreStyles: حجم المدرج، والخط الموسيقي، وخط النص، والتباعد، وأرقام المازورات، والمسافة بين الأنظمة، والهوامش، والحبر، وعدد الأنظمة التي تبقى معًا عند الانقطاع.

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

- [الشعر سطرًا سطرًا](https://postext.dev/ar/docs/document-format-directives.md#verse)
- [صفحات افتتاح مصمَّمة](https://postext.dev/ar/docs/configuration-headings.md#الامتداد-والتصميم-المتقدم)
- [سمات العنوان](https://postext.dev/ar/docs/document-format.md#سمات-العناوين)
- [العناوين المرقّمة](https://postext.dev/ar/docs/configuration-headings.md#تجاوزات-كل-مستوى)
- [أنماط العناوين](https://postext.dev/ar/docs/configuration-styles.md#أنماط-العناوين)
- [فصول بلا أرقام](https://postext.dev/ar/docs/configuration-styles.md#أنماط-العناوين)
- [الأغلفة وصفحات العنوان وبيانات النشر](https://postext.dev/ar/docs/configuration-styles.md#أنماط-العناوين)
- [البيانات الوصفية للمستند](https://postext.dev/ar/docs/document-format.md#البيانات-التمهيدية-frontmatter)
- [جدول المحتويات](https://postext.dev/ar/docs/configuration-notes-references.md#جدول-المحتويات)
- [الترويسات وأرقام الصفحات](https://postext.dev/ar/docs/configuration-headers-footers.md#الترويسات-والتذييلات)
- [الترويسات بحسب دور الصفحة](https://postext.dev/ar/docs/configuration-headers-footers.md#عناصر-النص)
- [أنماط الفقرات](https://postext.dev/ar/docs/configuration-styles.md#أنماط-الفقرات)
- [هوامش متناظرة](https://postext.dev/ar/docs/configuration-page-layout.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-headers-footers.md#الترويسات-والتذييلات)
- [التصدير إلى PDF](https://postext.dev/ar/docs/configuration-pdf-output.md#توليد-ملفات-pdf)
- [أعمدة داخل الإطار أو النص](https://postext.dev/ar/docs/document-format-directives.md#columns)
- [قوائم الشيفرة](https://postext.dev/ar/docs/configuration-styles.md#قوائم-الشيفرة)
- [الخطوط المضمَّنة في PDF](https://postext.dev/ar/docs/configuration-pdf-output.md#لماذا-مزوّد-الخطوط)
- [ترويسات لكل قسم](https://postext.dev/ar/docs/configuration-styles.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#الترويسات-والتذييلات), [`header`](https://postext.dev/ar/docs/configuration-headers-footers.md#الترويسات-والتذييلات), [`headingStyles`](https://postext.dev/ar/docs/configuration-styles.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#نمط-المدوّنة-الموسيقية), [`toc`](https://postext.dev/ar/docs/configuration-notes-references.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)

**الخطوط**

- Spectral (OFL-1.1), Spectral SC (OFL-1.1), Fraunces (OFL-1.1)

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

### 1 · اكتب الأغنية، واضبط نمط المدونة مرة واحدة

نمط المدونة هو [الجواب المختصر](https://postext.dev/ar/cookbook/songbook-melody-words-chords.md#الجواب-المختصر) أعلاه. في Markdown كل أغنية كتلة ```` ```abc ````: تحدد أسطر الترويسة الميزان وطول النوتة والمقام؛ واسم الأكورد بين علامتي تنصيص مزدوجتين (`"D7"A`) يُطبع فوق النوتة التي يسبقها؛ وسطر `w:` تحت الموسيقى يضع مقطعًا لفظيًا واحدًا تحت كل نوتة، والمقاطع اللفظية مفصولة بشرطات. واللحن الذي له مقطع ولازمة يُكتب سطرين من الموسيقى، لكل منهما سطر `w:` خاص به. حجم المدرج والخط الموسيقي وخط الكلمات وأرقام المازورات والمسافة بين الأسطر الموسيقية كلها تأتي من `scoreStyle`، فتتطابق الأغاني الثلاث، ولا تحمل الكتل غير الموسيقى.

### 2 · افتتح كل أغنية تحت رقمها وسطر نسبتها

```js
// script.js, سطرًا 66–81
// # Amazing Grace {credit="Words John Newton, 1779 · Tune New Britain, American, 1835"}
// The songs run on, three lines apart: a short song shares its page with the next one.
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });
const text = (id, content, font, size, color, placement, extra = {}) => ({ kind: 'text', id,
  content, fontFamily: font, fontSize: pt(size), color: col(color), align: 'left', placement,
  ...extra });
const songHead = { enabled: true, minHeight: mm(21), slot: { elements: [
  text('number', '{number}', DISPLAY, 26, 'madder', at('container', 'top-left', 0, 1),
    { fontWeight: 600, lineHeight: 1 }),
  text('title', '{titleText}', DISPLAY, 21, 'ink', { ...at('container', 'top-left', 12, 2),
    size: { width: mm(100) } }, { fontWeight: 600, lineHeight: 1.05 }),
  text('credit', '{attr.credit}', LABEL, 8.4, 'madder', { ...at('#title', 'below', 0, 2.2),
    size: { width: mm(100) } }, { fontWeight: 600, letterSpacing: pt(0.6) }),
] } };
const songs = { level: 1, numberingTemplate: '{1}', advancedDesign: songHead,
  marginTop: pt(LEAD * 3), marginBottom: pt(0), breakBefore: { enabled: false } };
```

الأغنية عنوان من المستوى الأول له السمة `credit`، ويضع تصميمه منها الرقم والعنوان وسطر النسبة. ويعطي `minHeight` كل أغنية رأسًا بارتفاع 21 mm، فتبدأ المدرجات على العمق نفسه في كل صفحة. تتوالى الأغاني بمسافة ثلاثة أسطر فوق كل عنوان، ولا تنقسم المدونة أبدًا قبل سطرها الموسيقي الثاني (`minSystemsAtBreak: 2`): تملأ كل أغنية في الطبعة الإنجليزية صفحة، وتتقاسم اثنتان من الأغاني الإسبانية القصيرة صفحة واحدة.

### 3 · انضد المقاطع الأخرى في عمودين

```js
// script.js, سطرًا 54–62
// :::columns{count=2} holds one :::verse block per stanza; each line of the stanza is a
// line of Markdown, the turned lines indented two spaces (one em). A hymn in long lines
// (Amazing Grace) drops the columns: its stanzas stand one under another, centred.
const verse = { id: 'verse', fontSize: pt(9.4), lineHeight: pt(13), hyphenation: false,
  marginBottom: pt(13 / 2) };
const chorus = { id: 'chorus', fontSize: pt(9.4), lineHeight: pt(13), italic: true,
  hyphenation: false, marginBottom: pt(LEAD / 2) };
const colophon = { id: 'colophon', fontFamily: LABEL, fontSize: pt(7.8), lineHeight: pt(11),
  color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD * 2) };
```

كل مقطع كتلة `:::verse`، فتنكسر أسطره حيث كسرها الشاعر، ويُزاح السطران الثاني والرابع بمقدار em واحد. وتصفّها مجموعة `:::columns{count=2 gap=4mm}` جنبًا إلى جنب بحجم 9.4 pt. عُدّ الحروف أولًا: أسطر Burns يتسع لها عمود عرضه 55 mm، وأسطر Newton على الوزن الطويل (long metre) لا يتسع لها، لذلك تبقى *Amazing Grace* كتلة `:::verse` واحدة في الوسط، وتفصل الأسطر الفارغة بين مقاطعها.

### 4 · اجعل عناصر الصفحة الثابتة هادئة

```js
// script.js, سطرًا 119–131
const head = (id, content, parity, edge, x, style = {}) => ({
  kind: 'text', id, content, parity, pages: 'all', // a song's first page carries them too
  fontFamily: LABEL, fontSize: pt(8.4), fontWeight: 600, letterSpacing: pt(0.9),
  color: col('muted'), ...style,
  placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(12.5) } },
});
const folio = { fontFamily: TEXT, fontWeight: 600, letterSpacing: pt(0), color: col('madder') };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
  head('verso-title', '{title}', 'even', 'top-left', OUTER + 8),
  head('recto-song', '{chapterTitle}', 'odd', 'top-right', -(OUTER + 8)),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
] };
```

الترويسات في كل صفحة تبلغها الأغاني: عنوان الكتاب على الصفحة الزوجية والأغنية على الفردية، ورقم الصفحة بلون الفوّة نفسه الذي في أرقام الأغاني. وتخلو منها صفحة العنوان وكلمة المغنّين بفضل `header` فارغ في أنماط عناوينهما.

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

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

- مجلد الوصفة: https://github.com/drnachio/postext/tree/main/cookbook/songbook-melody-words-chords

### script.js

````js
// ═══ Postext Cookbook · Nº 164 · Songbook: melody, words under the notes, chords ═══════
// https://postext.dev/en/cookbook/songbook-melody-words-chords
// Code: MIT · Words and tunes: public domain (see the notes in each edition) · Prose: CC BY 4.0
// Fonts: Spectral, Spectral SC, Fraunces (SIL OFL 1.1); Leland (music) · 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 = 'songbook-melody-words-chords';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
const palette = { // every colour in the config links to one of these
  ink: '#211e1b', // text and notes: a warm near-black
  madder: '#8a3324', // the one accent: song numbers, the title page, credit lines
  tint: '#f1e7d8', // the subtitle on the madder ground (8.2:1)
  rule: '#cbbca6', // hairlines
  muted: '#675e55', // running heads, notes and the colophon (5.9:1 on the paper)
  paper: '#fbf8f1', // a cream stock
};
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, so nothing prints blue.
  { id: 'main-color', name: 'madder (defaults)', value: { hex: palette.madder, model: 'hex' } },
];
const [TEXT, LABEL, DISPLAY] = ['Spectral', 'Spectral SC', 'Fraunces'];
const LEAD = 14; // pt: the body's leading
const OUTER = 15; // mm: the outer margin, where the running heads and folios align

// #region answer: the score style: a 5 mm staff in Leland, the words in the book's face
// A song is an ```abc fence in the Markdown: the melody, its chords written "G" before the note
// they fall on, and the first verse on a w: line under it. The look is set here, once.
const scoreStyle = {
  staffSize: mm(5.5), // a songbook rastral; the words under it come out at about 8 pt
  musicFont: 'Leland', // lighter noteheads than Bravura, close to Spectral's colour
  textFont: TEXT, // words and chord names in the text face, not the engraver's Times
  spacing: 1, // the engraver's own: five or six bars of 3/4 to a system on this measure
  barNumbers: 'none', // a table of singers counts verses, not bars
  systemGap: mm(4.5), // room for the chord names between one system's words and the next
  marginTop: pt(4),
  marginBottom: pt(LEAD),
  color: col('ink'),
  minSystemsAtBreak: 2, // never a lone system at a page foot or head
};
// Two habits of Verovio's ABC reader shape the w: lines. Every sung note takes the next
// syllable in order: the `_` and `*` holds are read as nothing, so the second note of a
// slurred pair (B/G/ under "-ing") gets a no-break space, which prints nothing. A tied note
// takes none. `~` joins two syllables on one note: "de~a-trás".
// #endregion

// #region verse: the other verses, two stanzas side by side under the score
// :::columns{count=2} holds one :::verse block per stanza; each line of the stanza is a
// line of Markdown, the turned lines indented two spaces (one em). A hymn in long lines
// (Amazing Grace) drops the columns: its stanzas stand one under another, centred.
const verse = { id: 'verse', fontSize: pt(9.4), lineHeight: pt(13), hyphenation: false,
  marginBottom: pt(13 / 2) };
const chorus = { id: 'chorus', fontSize: pt(9.4), lineHeight: pt(13), italic: true,
  hyphenation: false, marginBottom: pt(LEAD / 2) };
const colophon = { id: 'colophon', fontFamily: LABEL, fontSize: pt(7.8), lineHeight: pt(11),
  color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD * 2) };
// #endregion

// #region song-head: a song opens under its number, its title and its credit line
// # Amazing Grace {credit="Words John Newton, 1779 · Tune New Britain, American, 1835"}
// The songs run on, three lines apart: a short song shares its page with the next one.
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });
const text = (id, content, font, size, color, placement, extra = {}) => ({ kind: 'text', id,
  content, fontFamily: font, fontSize: pt(size), color: col(color), align: 'left', placement,
  ...extra });
const songHead = { enabled: true, minHeight: mm(21), slot: { elements: [
  text('number', '{number}', DISPLAY, 26, 'madder', at('container', 'top-left', 0, 1),
    { fontWeight: 600, lineHeight: 1 }),
  text('title', '{titleText}', DISPLAY, 21, 'ink', { ...at('container', 'top-left', 12, 2),
    size: { width: mm(100) } }, { fontWeight: 600, lineHeight: 1.05 }),
  text('credit', '{attr.credit}', LABEL, 8.4, 'madder', { ...at('#title', 'below', 0, 2.2),
    size: { width: mm(100) } }, { fontWeight: 600, letterSpacing: pt(0.6) }),
] } };
const songs = { level: 1, numberingTemplate: '{1}', advancedDesign: songHead,
  marginTop: pt(LEAD * 3), marginBottom: pt(0), breakBefore: { enabled: false } };
// #endregion

// #region front: the title page on a madder field, then the note to the singers
const display = (id, content, font, size, color, y, extra = {}) => text(id, content, font, size,
  color, { ...at('page', 'top', 0, y), size: { width: mm(118) } }, { align: 'center', ...extra });
const titlePage = { id: 'title-page', numbered: false, toc: false, span: 'page',
  header: { elements: [] }, footer: { elements: [] },
  advancedDesign: { enabled: true, slot: { elements: [
    { kind: 'box', id: 'field', style: { backgroundColor: col('madder') },
      placement: { ...at('bleed', 'top-left'), size: { width: 'fill', height: mm(132) } } },
    display('kicker', '{author}', LABEL, 10, 'tint', 40, { letterSpacing: pt(2),
      fontWeight: 600 }),
    display('title', '{titleText}', DISPLAY, 46, 'paper', 54, { fontWeight: 600,
      lineHeight: 1 }),
    display('subtitle', '{subtitle}', TEXT, 13, 'tint', 104, { italic: true }),
    display('press', '{attr.press}', LABEL, 9, 'muted', 182, { letterSpacing: pt(1.6),
      fontWeight: 600 }),
  ] } } };
// The note to the singers and the notes at the back are plain headings with no number;
// the front one has no running head and stays out of the contents.
const plain = { numbered: false, advancedDesign: { enabled: false }, fontFamily: DISPLAY,
  fontWeight: 600, fontSize: pt(21), lineHeight: pt(LEAD * 2), marginBottom: pt(LEAD / 2),
  breakBefore: { enabled: true, parity: 'any' } }; // gotcha: style-inherits-break
const front = { ...plain, id: 'front', toc: false, header: { elements: [] } };
const back = { ...plain, id: 'back' };
// #endregion

// #region contents: song number, title, a dotted leader and the page
const toc = {
  levels: [{ level: 1, fontFamily: TEXT, fontSize: pt(10.5), numberFontFamily: DISPLAY,
    numberFontWeight: 600, numberColor: col('madder'), numberWidth: mm(6), numberGap: mm(2) }],
  pageNumber: { fontFamily: LABEL, fontSize: pt(9.5), color: col('muted'), width: mm(6) },
  leader: { char: '. ', gap: mm(2) },
};
// #endregion

// #region heads: the book on the verso, the song on the recto, folios outside
const head = (id, content, parity, edge, x, style = {}) => ({
  kind: 'text', id, content, parity, pages: 'all', // a song's first page carries them too
  fontFamily: LABEL, fontSize: pt(8.4), fontWeight: 600, letterSpacing: pt(0.9),
  color: col('muted'), ...style,
  placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(12.5) } },
});
const folio = { fontFamily: TEXT, fontWeight: 600, letterSpacing: pt(0), color: col('madder') };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
  head('verso-title', '{title}', 'even', 'top-left', OUTER + 8),
  head('recto-song', '{chapterTitle}', 'odd', 'top-right', -(OUTER + 8)),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
] };
// #endregion

const config = {
  locale: t({ en: 'en-us', es: 'es' }), // each edition's hyphenation patterns
  colorPalette,
  page: { // A5, the size of a parish songbook; mirror: left is the inner margin
    sizePreset: 'custom', width: mm(148), height: mm(210), dpi: 150,
    backgroundColor: col('paper'),
    margins: { top: mm(22), bottom: mm(20), left: mm(19), right: mm(OUTER), mirror: true },
  },
  layout: { layoutType: 'single' },
  bodyText: {
    fontFamily: TEXT, fontSize: pt(10.3), lineHeight: pt(LEAD), color: col('ink'),
    boldColor: col('madder'), italicColor: col('ink'), referenceColor: col('ink'),
    textAlign: 'justify', firstLineIndent: mm(4), indentAfterHeading: false,
    hyphenation: { enabled: true }, optimalLineBreaking: true,
    avoidWidows: true, avoidOrphans: true, avoidRunts: true,
  },
  headings: {
    fontFamily: DISPLAY, fontWeight: 600, color: col('ink'),
    levels: [songs, { level: 2, fontFamily: LABEL, fontSize: pt(9.5), lineHeight: pt(LEAD),
      letterSpacing: pt(1.4), color: col('madder'), marginTop: pt(LEAD), marginBottom: pt(4) }],
  },
  headingStyles: [titlePage, front, back],
  toc,
  scoreStyle,
  paragraphStyles: [verse, chorus, colophon],
  header,
  footer: { elements: [] },
};

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = `---
title: "Songs for the Long Table"
subtitle: "Three songs to sing when the plates are cleared"
author: "Edited by Margaret Hollis"
---

# Songs for the Long Table {style="title-page" press="Hollis & Rowe · Second printing"}

# To the singers {style="front"}

This little book holds three songs that a table of friends can sing without a piano: a hymn that every congregation knows, a comic ballad of the California gold rush and the Scottish song that sees the old year out. Each is printed with its melody, the guitar chords over the staff and the first verse under the notes. The other verses follow in two columns, so the eye can run on from the last bar without turning the page.

The keys suit voices that have had no training. *Amazing Grace* and *Auld Lang Syne* are in G, where the highest note is the D or E at the top of the staff, and *Clementine* is in F. A guitarist who wants a lower key can play the same shapes with a capo and leave the singers where they are.

Where one syllable is held over two notes, the second note has no word under it: carry the vowel on to the next syllable. The notes at the back of the book say where the words and the tunes come from.

## Contents

:::toc

# Amazing Grace {credit="Words John Newton, 1779 · Tune New Britain, American, 1835"}

\`\`\`abc {alt="Amazing Grace: melody in G major in three-four time, with chord symbols and the first verse"}
X:1
T:Amazing Grace
C:Tune New Britain
M:3/4
L:1/4
K:G
D | "G"G2 (B/G/) | B2 "D7"A | "Em"G2 "C"E | "G"D2 D | G2 (B/G/) | B2 "D7"A | "G"d2 B | (d3/2 B/) (d/B/) | G2 D | "C"(E3/2 G/) (G/E/) | "G"D2 D | "Em"G2 "G"(B/G/) | B2 "D7"A | "G"G3 |]
w: A-maz-ing   grace! How sweet the sound, that saved a   wretch like me! I once   was   lost, but now   am   found, was blind, but   now I see.
\`\`\`

:::verse{style="verse"}
**2** ’Twas grace that taught my heart to fear,
  And grace my fears reliev’d;
How precious did that grace appear
  The hour I first believ’d!

**3** Thro’ many dangers, toils, and snares,
  I have already come;
’Tis grace hath brought me safe thus far,
  And grace will lead me home.

**4** The Lord has promis’d good to me,
  His word my hope secures;
He will my shield and portion be
  As long as life endures.
:::

# Clementine {credit="Words and music Percy Montrose, 1884"}

\`\`\`abc {alt="Clementine: melody in F major in three-four time, with chord symbols and the first verse"}
X:2
T:Clementine
C:Percy Montrose
M:3/4
L:1/8
K:F
F>F | "F"F2 C2 A>A | A2 F2 F>A | c3 c "C7"B>A | G4 G>A | B2 B2 A>G | "F"A2 F2 F>A | "C7"G3 C E>G | "F"F4 |]
w: In a cav-ern, in a can-yon, ex-ca-vat-ing for a mine, dwelt a min-er, for-ty-nin-er, and his daugh-ter Clem-en-tine.
\`\`\`

:::verse{style="chorus" align=start}
**Chorus** Oh my darling, oh my darling,
  Oh my darling Clementine,
You are lost and gone for ever,
  Dreadful sorry, Clementine.
:::

:::columns{count=2 gap=4mm}
:::verse{style="verse" align=start}
**2** Light she was and like a fairy,
  And her shoes were number nine,
Herring boxes without topses,
  Sandals were for Clementine.
:::

:::verse{style="verse" align=start}
**3** Drove she ducklings to the water
  Every morning just at nine,
Hit her foot against a splinter,
  Fell into the foaming brine.
:::

:::verse{style="verse" align=start}
**4** Ruby lips above the water,
  Blowing bubbles soft and fine,
But alas, I was no swimmer,
  So I lost my Clementine.
:::

:::verse{style="verse" align=start}
**5** How I missed her, how I missed her,
  How I missed my Clementine,
Till I kissed her little sister
  And forgot my Clementine.
:::
:::

# Auld Lang Syne {credit="Words Robert Burns, 1788 · Tune Scots, printed 1799"}

\`\`\`abc {alt="Auld Lang Syne: melody in G major in four-four time, verse and chorus, with chord symbols and the words"}
X:3
T:Auld Lang Syne
C:Scots tune
M:4/4
L:1/4
K:G
D | "G"G>G G B | "D7"A>G A B | "G"G>G B d | "C"e3 e | "G"d>B B G | "D7"A>G A B | "Em"(G>E) "C"(E "D7"D) | "G"G3 e |
w: Should auld ac-quaint-ance be for-got, and nev-er brought to mind? Should auld ac-quaint-ance be for-got, and auld   lang   syne! For
"G"(d>B) (B G) | "D7"A>G A e | "G"(d>B) (B d) | "C"e3 e | "G"d>B B G | "D7"A>G A B | "Em"(G>E) "C"(E "D7"D) | "G"G3 |]
w: auld   lang   syne, my jo, for auld   lang   syne, we’ll tak’ a cup o’ kind-ness yet, for auld   lang   syne.
\`\`\`

:::columns{count=2 gap=4mm}
:::verse{style="verse" align=start}
**2** And surely ye’ll be your pint-stoup!
  And surely I’ll be mine!
And we’ll tak’ a cup o’ kindness yet,
  For auld lang syne.
:::

:::verse{style="verse" align=start}
**3** We twa hae run about the braes,
  And pou’d the gowans fine;
But we’ve wander’d mony a weary fit,
  Sin’ auld lang syne.
:::

:::verse{style="verse" align=start}
**4** We twa hae paidl’d in the burn,
  Frae morning sun till dine;
But seas between us braid hae roar’d
  Sin’ auld lang syne.
:::

:::verse{style="verse" align=start}
**5** And there’s a hand, my trusty fiere!
  And gie’s a hand o’ thine!
And we’ll tak’ a right gude-willie waught,
  For auld lang syne.
:::
:::

# Notes on the songs {style="back"}

## Amazing Grace

John Newton, curate of Olney in Buckinghamshire, wrote the hymn for his New Year’s Day sermon of 1773 and printed it in *Olney Hymns* in 1779. Nobody knows what the parish sang it to. William Walker first set it to *New Britain* in *Southern Harmony* in 1835, and the melody here follows the setting Edwin O. Excell printed in 1900, which most American hymnals still use. The three verses after the first keep Newton’s spelling.

## Clementine

Oliver Ditson of Boston published the song in 1884 with words by Percy Montrose, and a sheet of 1885 gives it to Barker Bradford. Both drew on an older song, *Down by the River Liv’d a Maiden*, printed in 1863. Sing the chorus after every verse, and slow down for the last one.

## Auld Lang Syne

Burns sent the song to *The Scots Musical Museum* in 1788 as an old song he had taken down from an old man’s singing. It went to another tune at first; the one sung today appeared with the words in George Thomson’s *Select Songs of Scotland* in 1799. Sing the chorus after each verse. At the last chorus the company crosses arms and joins hands round the table.

:::paragraphs{style="colophon"}
Set in Spectral, Spectral SC and Fraunces (SIL Open Font License); music in Leland. Words and tunes in the public domain. Hollis & Rowe is an imprint invented for this example.
:::
`; // content.<lang>.md, inlined by the Cookbook

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
const FONTS = {
  Spectral: ['400', '400i', '600', '700', '700i'], // 700: stanza numbers, the Chorus label
  'Spectral SC': ['400', '600'],
  Fraunces: ['600'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
await initMusicEngine(); // Verovio, from esm.sh: until it runs, a score is a grey box
const doc = await buildDocumentWithFonts({ markdown }, config, kitFonts(FONTS));
showPages(doc, { title: t({ en: 'Songs for the Long Table', es: 'Cancionero de la merienda' }) });
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 ───────────────────────────────────────────────────────────────────────
````

## تنويعات

### انقش بخط Bravura

لخط Bravura رؤوس نوتات أثقل ومظهر لوح محفور، ويليق بكتاب ترانيم منضد بخط نص أغمق.

```diff
-  musicFont: 'Leland', // lighter noteheads than Bravura, close to Spectral's colour
+  musicFont: 'Bravura',
```

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

تتدرّب الجوقة بأرقام المازورات؛ اطبع رقمًا في بداية كل سطر موسيقي.

```diff
-  barNumbers: 'none', // a table of singers counts verses, not bars
+  barNumbers: 'system',
```

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

- **يرث نمط العنوان فاصل الصفحة من مستواه.** يأخذ مُدخل headingStyles كل حقل يتركه من مستوى عنوانه، بما فيه breakBefore. فصفحة المحتويات أو صفحة بيانات النشر المنسّقة على H1 بعد :::pagebreak ترث الزوجية 'odd' وتقع بعد صفحة فارغة. أعطِ هذا النمط breakBefore: { enabled: false }.

- يتجاهل قارئ ABC في Verovio علامتي الإمساك `_` و`*` في سطر `w:`: كل نوتة تأخذ المقطع اللفظي التالي. تحت النوتة الثانية من قوس اللحن (slur) اكتب مسافة غير قابلة للكسر (U+00A0) مقطعًا لفظيًا قائمًا بذاته، فلا يُطبع شيء. والنوتات الموصولة بقوس الإطالة (tie) لا تأخذ مقطعًا لفظيًا.
- يجمع `~` مقطعين لفظيين تحت نوتة واحدة (`de~a-trás`)، لكن الناقش يطبعهما بمسافة عادية، لا بقوس وصل.
- اكتب أسماء الأكوردات من غير ♭ أو ♯ ما استطعت: أكثر خطوط النص تخلو من هاتين العلامتين، فتلجأ الصفحة إلى خط من خطوط النظام. اختر مقامات أكورداتها طبيعية (G وC وD7 وEm)، أو اكتب «سي بيمول» بالحروف في النص.

## الحقوق

- الوصفة: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- النص: Amazing Grace: words (1779, five of the six stanzas, in the Olney Hymns spelling): John Newton ([المصدر](https://en.wikipedia.org/wiki/Amazing_Grace)), ملكية عامة
- النص: Amazing Grace: the tune New Britain (Southern Harmony, 1835), melody as in E. O. Excell's setting of 1900, chord symbols added: Traditional American; William Walker; Edwin O. Excell ([المصدر](https://en.wikipedia.org/wiki/Amazing_Grace)), ملكية عامة
- النص: Oh My Darling, Clementine: words and tune (Oliver Ditson, Boston, 1884): Percy Montrose ([المصدر](https://en.wikipedia.org/wiki/Oh_My_Darling,_Clementine)), ملكية عامة
- النص: Auld Lang Syne: words (1788) and the Scots tune of George Thomson's Select Songs of Scotland (1799): Robert Burns; traditional ([المصدر](https://en.wikipedia.org/wiki/Auld_Lang_Syne)), ملكية عامة
- النص: Cumpleaños feliz: the tune of Good Morning to All (Song Stories for the Kindergarten, Chicago, 1893); Spanish words traditional: Mildred J. Hill; traditional ([المصدر](https://en.wikipedia.org/wiki/Happy_Birthday_to_You)), ملكية عامة
- النص: Martinillo: the French tune of Frère Jacques (18th century) with the traditional Spanish words: Traditional ([المصدر](https://en.wikipedia.org/wiki/Fr%C3%A8re_Jacques)), ملكية عامة
- النص: La cucaracha: tune, chorus and verse as in the Spanish Wikipedia transcription; two traditional Mexican coplas: Traditional ([المصدر](https://es.wikipedia.org/wiki/La_cucaracha)), ملكية عامة
- النص: The preface, the notes on the songs and the chord symbols: Ignacio Ferro, CC-BY-4.0
- الخطوط: Spectral (OFL-1.1), Spectral SC (OFL-1.1), Fraunces (OFL-1.1)
- الشيفرة: MIT · محتوى المثال: CC-BY-4.0

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

- [رقم 015 · قصائد منضدة سطرًا سطرًا](https://postext.dev/ar/cookbook/poetry-collection.md): كل قصيدة كتلة :::verse مكتوبة كما تُطبع: إزاحات 1918 مسافات في أول السطر، وتتعلق التتمّات 2 em بعد سطرها، ويفصل سطر فارغ بين المقاطع. · المستوى 2 (متوسط) · الشعر
- [رقم 044 · طبعة محقّقة: أرقام الأسطر وحواشٍ مرتبطة بها](https://postext.dev/ar/cookbook/critical-edition-line-numbers.md): قصيدة Lycidas لملتون بنص 1645: تعدّ lineNumbers أسطر الشعر وتطبع رقم كل سطر خامس في العمود الجانبي، وكل حاشية تبدأ برقم سطرها منضّدًا شارةً. · المستوى 3 (متقدم) · الشعر
- [رقم 083 · قصائد تانغ مقابل نظمها الإنجليزي](https://postext.dev/ar/cookbook/tang-poems-facing-english.md): تفتح كل قصيدة من عصر تانغ صفحة فردية ملوّنة، ويفتح نظم Giles الإنجليزي الصفحة الزوجية المقابلة: نمط عنوان يقفز إلى صفحة زوجية والآخر إلى فردية. · المستوى 2 (متوسط) · الشعر
