# 旋律、歌词与和弦齐备的歌本

> 每首歌是一个ABC代码块：旋律、谱表上方的和弦名、音符下方的第一段歌词，由同一个乐谱样式按栏宽刻印。

- HTML版本: https://postext.dev/zh/cookbook/songbook-melody-words-chords
- 食谱 No. 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
- 在沙盒中打开: https://postext.dev/zh/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), [ja](https://postext.dev/ja/cookbook/songbook-melody-words-chords.md), [ar](https://postext.dev/ar/cookbook/songbook-melody-words-chords.md)

## 简单来说

一本围着餐桌唱歌用的小歌本。每首歌用一种叫ABC的记谱法写成纯文本，Postext画出谱表、音符、吉他和弦和音符下的歌词，其余几段歌词分两栏排在后面。

## 成品一览

《Songs for the Long Table》是一本A5开本的口袋歌本：茜红色的书名页，一篇写给唱歌的人的说明（附目录），三首歌，以及每首歌出处的注释。每首歌开头是一个茜红色的大号编号、用Fraunces排的歌名和一行小型大写字母排的署名。下面是旋律，用Leland排在5.5 mm的谱表上，吉他和弦标在小节上方，第一段歌词用本书的正文字体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/zh/docs/music.md#正文中的乐谱): 以文本写在```abc、```musicxml、```mei或```humdrum代码块中的乐谱，由Verovio按栏宽刻印，并切成一行行谱表系统，可跨栏、跨页接排。
- [乐谱样式](https://postext.dev/zh/docs/configuration-resources.md#乐谱样式): scoreStyle及scoreStyles中的命名样式：谱表大小、音乐字体、文字字体、间距、小节号、系统间距、边距、墨色，以及断开时保留在一起的系统数。

**还用到**

- [逐行排诗](https://postext.dev/zh/docs/document-format-directives.md#verse)
- [设计过的章首页](https://postext.dev/zh/docs/configuration-headings.md#通栏与高级设计)
- [标题属性](https://postext.dev/zh/docs/document-format.md#标题属性)
- [编号标题](https://postext.dev/zh/docs/configuration-headings.md#按级别覆盖)
- [标题样式](https://postext.dev/zh/docs/configuration-styles.md#标题样式)
- [不编号的章](https://postext.dev/zh/docs/configuration-styles.md#标题样式)
- [封面、扉页和版权页](https://postext.dev/zh/docs/configuration-styles.md#标题样式)
- [文档元数据](https://postext.dev/zh/docs/document-format.md#前置元数据)
- [目录](https://postext.dev/zh/docs/configuration-notes-references.md#目录)
- [书眉与页码](https://postext.dev/zh/docs/configuration-headers-footers.md#页眉与页脚)
- [按页面角色显示书眉](https://postext.dev/zh/docs/configuration-headers-footers.md#文本元素)
- [段落样式](https://postext.dev/zh/docs/configuration-styles.md#段落样式)
- [对称页边距](https://postext.dev/zh/docs/configuration-page-layout.md#对称页边距)
- [语义调色板](https://postext.dev/zh/docs/configuration-fonts-colors-viewers.md#调色板)
- [纸张颜色](https://postext.dev/zh/docs/configuration-page-layout.md#页面)
- [页面设计中的文字、线条和框](https://postext.dev/zh/docs/configuration-headers-footers.md#页眉与页脚)
- [导出PDF](https://postext.dev/zh/docs/configuration-pdf-output.md#生成pdf)
- [框内或正文中的分栏](https://postext.dev/zh/docs/document-format-directives.md#columns)
- [代码清单](https://postext.dev/zh/docs/configuration-styles.md#代码清单)
- [嵌入PDF的字体](https://postext.dev/zh/docs/configuration-pdf-output.md#为什么需要字体提供函数)
- [分节书眉](https://postext.dev/zh/docs/configuration-styles.md#标题样式)

**配置一览**

- [`bodyText`](https://postext.dev/zh/docs/configuration-text.md#正文), [`colorPalette`](https://postext.dev/zh/docs/configuration-fonts-colors-viewers.md#调色板), [`footer`](https://postext.dev/zh/docs/configuration-headers-footers.md#页眉与页脚), [`header`](https://postext.dev/zh/docs/configuration-headers-footers.md#页眉与页脚), [`headingStyles`](https://postext.dev/zh/docs/configuration-styles.md#标题样式), [`headings`](https://postext.dev/zh/docs/configuration-headings.md#标题), [`layout`](https://postext.dev/zh/docs/configuration-page-layout.md#版式), [`locale`](https://postext.dev/zh/docs/configuration-language.md#断词), [`page`](https://postext.dev/zh/docs/configuration-page-layout.md#页面), [`paragraphStyles`](https://postext.dev/zh/docs/configuration-styles.md#段落样式), [`scoreStyle`](https://postext.dev/zh/docs/configuration-resources.md#乐谱样式), [`toc`](https://postext.dev/zh/docs/configuration-notes-references.md#目录)

**API**

- [`buildDocumentWithFonts`](https://postext.dev/zh/docs/configuration-programmatic-usage.md#排版前加载字体), [`decompressWoff2`](https://postext.dev/zh/docs/configuration-pdf-output.md#浏览器字体提供函数fontsource--woff2), [`initMusicEngine`](https://postext.dev/zh/docs/music.md#正文中的乐谱), [`renderPageToCanvas`](https://postext.dev/zh/docs/configuration-programmatic-usage.md#把页面渲染为位图), [`renderToPdf`](https://postext.dev/zh/docs/configuration-pdf-output.md#生成pdf)

**字体**

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

## 做法

### 1 · 写下歌曲，乐谱样式只设一次

乐谱样式就是上面的[简短回答](https://postext.dev/zh/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`块，所以诗行在诗人断行的地方断开，第二行和第四行缩进1 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">`，或粘贴到新建CodePen的JS面板里（设为模块）。它从esm.sh导入postext，不需要安装或构建。

- 食谱文件夹: 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也不例外。在:::pagebreak之后以H1设置样式的目录页或版权页会继承parity 'odd'，结果落在一张空白页之后。给这类样式设置breakBefore: { enabled: false }。

- Verovio的ABC读取器忽略`w:`行中的`_`和`*`这两个占位记号：每个音符都取下一个音节。在圆滑线的第二个音符下，单独写一个不换行空格（U+00A0）作为一个音节，它什么也不印。用延音线连起的音符不取音节。
- `~`把两个音节合到一个音符下（`de~a-trás`），但刻谱引擎用普通空格把它们印出来，不印连接弧。
- 和弦名尽量不用♭或♯：大多数正文字体没有这些符号，页面会改用系统字体。选和弦都是本位音的调（G、C、D7、Em），或者在正文中写成“降B”。

## 致谢

- 食谱: 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

## 相关食谱

- [No. 015 · 逐行排诗](https://postext.dev/zh/cookbook/poetry-collection.md): 每首诗是一个照印刷样子书写的:::verse块：1918年版的缩进写成行首空格，转行比本行悬挂2 em，空行分隔诗节。 · 难度 2 (中级) · 诗歌
- [No. 044 · 校勘本：行号与按行号编排的注释](https://postext.dev/zh/cookbook/critical-edition-line-numbers.md): 按1645年文本排弥尔顿的《Lycidas》：lineNumbers给诗行计数，把每第五行的行号印在侧栏里，每条注释以其行号开头，行号排成行内标签。 · 难度 3 (高级) · 诗歌
- [No. 083 · 唐诗与英译对页排](https://postext.dev/zh/cookbook/tang-poems-facing-english.md): 每首唐诗占一个浅色底纹的右页，Giles的英译占对面的左页：一种标题样式分页到偶数页，另一种分页到奇数页。 · 难度 2 (中级) · 诗歌
