# 人名索引と事項索引

> index="names"を付けたマークは人名索引に、それ以外は事項索引に入ります。:::indexがそれぞれを2段組みで刷り、ページ番号はビルドで確定します。

- HTML版: https://postext.dev/ja/cookbook/names-and-subjects-indexes
- レシピ No. 073 · 本の構成 · 難易度 2 (中級) · 出力: Canvas, PDF
- ジャンル: 教科書
- 必要なもの postext ≥ 1.7.0, postext-pdf ≥ 1.7.0 · テスト環境 1.7.0, postext-pdf 1.7.0 ／テスト日 2026-09-28
- ページ: [1](https://postext.dev/cookbook/names-and-subjects-indexes/en/p01.webp?v=e57b1277), [2](https://postext.dev/cookbook/names-and-subjects-indexes/en/p02.webp?v=e57b1277), [3](https://postext.dev/cookbook/names-and-subjects-indexes/en/p03.webp?v=e57b1277), [4](https://postext.dev/cookbook/names-and-subjects-indexes/en/p04.webp?v=e57b1277), [5](https://postext.dev/cookbook/names-and-subjects-indexes/en/p05.webp?v=e57b1277), [6](https://postext.dev/cookbook/names-and-subjects-indexes/en/p06.webp?v=e57b1277), [7](https://postext.dev/cookbook/names-and-subjects-indexes/en/p07.webp?v=e57b1277)
- PDF: https://postext.dev/cookbook/names-and-subjects-indexes/en/names-and-subjects-indexes.pdf?v=e57b1277
- Sandboxで開く: https://postext.dev/ja/sandbox#recipe=names-and-subjects-indexes&lang=en (.postext: https://postext.dev/cookbook/names-and-subjects-indexes/en/names-and-subjects-indexes.postext)
- 最終更新: 2026-09-28
- 他の言語: [en](https://postext.dev/en/cookbook/names-and-subjects-indexes.md), [es](https://postext.dev/es/cookbook/names-and-subjects-indexes.md), [ca](https://postext.dev/ca/cookbook/names-and-subjects-indexes.md), [zh](https://postext.dev/zh/cookbook/names-and-subjects-indexes.md), [ar](https://postext.dev/ar/cookbook/names-and-subjects-indexes.md)

## かんたんな説明

活字の歴史を扱う研究書の1章です。巻末に、人物の一覧と項目の一覧の2つを、それぞれが出てくるページ付きで作る方法を示します。

## できあがり

活字を論じる研究書『Letters of Metal』の第4章で、判型は155×235mmです。Crimson Proの1段組みで、題にはBodoniのイタリック、アクセントにれんが色を使います。論考はグーテンベルクのテクストゥーラから、ジェンソン、アルドゥスとグリッフォ、ガラモンとプランタン、キャスロンとバスカヴィル、フルニエのポイント制、プラデルとヒルが彫りイバラが刷ったスペインの活字を経て、ボドニのモダンまでたどります。最後はこの種の本の常として、向かい合う2ページに2つの索引を置きます。偶数ページには印刷者、父型彫刻師、パトロンを、奇数ページには活字、道具、書物を並べます。どちらも同じマーク付きの本文から作られ、そこに載るページ番号はすべて、マークが実際に組まれたページです。

**このレシピが答える問い:**

- 同じ本文から人名索引と事項索引を刷るには？
- 本文が動くと自動でノンブルが変わる索引を作るには？

## 手短な答え

```js
// script.js, 行 35–57
// Marks in the Markdown. A person goes to the names index, inverted:
//   :index[Aldus Manutius]{term="Manutius, Aldus" index="names" main}
// A subject goes to the main index, with levels split by "!" and a sort key when the
// printed form would file in the wrong place:
//   :index[textura]{term="Blackletter!textura" main}
//   :index{term="42-line Bible" sort="Forty-two-line Bible"}
//   :index{term="Cursive" see="Italic"}
// Printed at the end, each under its own heading:
//   # Index of Names {style="index"}    then    :::index{index="names"}
//   # Index of Subjects {style="index"} then    :::index
const indexHeading = {
  id: 'index',
  numbered: false, // no chapter number, and the next chapter is still chapter 5
  breakBefore: { enabled: true, parity: 'any' },
  layout: { layoutType: 'double', gutterWidth: mm(7) }, // the section after the heading
};
const index = {
  fontSize: pt(8.6), lineHeight: pt(11.2), // one leading for entries and letter heads
  indent: em(1), turnoverIndent: em(2),
  rangeFormat: 'chicago', // 212–14, as a monograph prints it
  main: { bold: true }, // the page of the principal discussion
  groups: { fontFamily: 'Bodoni Moda', fontSize: pt(11), fontWeight: 600, color: col('brick') },
};
```

## 材料

**学べること**

- [索引](https://postext.dev/ja/docs/document-format.md#索引): 本文中で:indexで印を付けた語を、:::indexがアルファベット順に並べます。副項目、範囲、相互参照のほか、本文に追随するページ番号を持ちます。
- [見出しスタイル](https://postext.dev/ja/docs/configuration.md#見出しスタイル): 見出しの階層の名前付きの変種で、{style="…"}で選びます。章ごとに章扉、文字組み、柱、ページ設定を変えられます。

**ほかに使うもの**

- [番号のない章](https://postext.dev/ja/docs/configuration.md#見出しスタイル)
- [デザインした章扉](https://postext.dev/ja/docs/configuration.md#幅と詳細デザイン)
- [見出しの属性](https://postext.dev/ja/docs/document-format.md#見出しの属性)
- [柱とノンブル](https://postext.dev/ja/docs/configuration.md#柱とノンブル)
- [見開きの余白](https://postext.dev/ja/docs/configuration.md#見開きの余白)
- [ページの役割ごとの柱](https://postext.dev/ja/docs/configuration.md#テキスト要素)
- [段落スタイル](https://postext.dev/ja/docs/configuration.md#段落スタイル)
- [セマンティックカラーパレット](https://postext.dev/ja/docs/configuration.md#カラーパレット)
- [用紙の色](https://postext.dev/ja/docs/configuration.md#ページ)
- [ウィドウ、オーファン、ラント](https://postext.dev/ja/docs/configuration.md#オーファンウィドウラント分割禁止の規則)
- [PDFの書き出し](https://postext.dev/ja/docs/configuration.md#pdfの生成)
- [PDFに埋め込むフォント](https://postext.dev/ja/docs/configuration.md#なぜフォントプロバイダーが必要か)
- [節ごとのページ構成](https://postext.dev/ja/docs/configuration.md#見出しスタイル)

**設定の一覧**

- [`bodyText`](https://postext.dev/ja/docs/configuration.md#本文), [`colorPalette`](https://postext.dev/ja/docs/configuration.md#カラーパレット), [`footer`](https://postext.dev/ja/docs/configuration.md#柱とノンブル), [`header`](https://postext.dev/ja/docs/configuration.md#柱とノンブル), [`headingStyles`](https://postext.dev/ja/docs/configuration.md#見出しスタイル), [`headings`](https://postext.dev/ja/docs/configuration.md#見出し), [`index`](https://postext.dev/ja/docs/configuration.md#索引), [`layout`](https://postext.dev/ja/docs/configuration.md#レイアウト), [`locale`](https://postext.dev/ja/docs/configuration.md#ハイフネーション), [`page`](https://postext.dev/ja/docs/configuration.md#ページ), [`paragraphStyles`](https://postext.dev/ja/docs/configuration.md#段落スタイル)

**API**

- [`buildDocument`](https://postext.dev/ja/docs/configuration.md#文書のビルド), [`clearMeasurementCache`](https://postext.dev/ja/docs/configuration.md#計測キャッシュ), [`decompressWoff2`](https://postext.dev/ja/docs/configuration.md#ブラウザー向けフォントプロバイダーfontsource--woff2), [`renderPageToCanvas`](https://postext.dev/ja/docs/configuration.md#ページをビットマップに描画する), [`renderToPdf`](https://postext.dev/ja/docs/configuration.md#pdfの生成)

**書体**

- Crimson Pro (OFL-1.1), Bodoni Moda (OFL-1.1), Libre Franklin (OFL-1.1)

## 作り方

### 1 · 人物には人名索引用の、事項には事項索引用のマークを付ける

コードは上の[手短な答え](#手短な答え)にあり、残りはMarkdownの中にあります。`index="names"`の付いたマークは`:::index{index="names"}`が刷る索引に入り、それ以外のマークはすべて、属性なしの`:::index`が刷る事項索引に入ります。たとえば`:index[Aldus Manutius]{term="Manutius, Aldus" index="names" main}`は本文に名前を刷り、人名索引の「Manutius」の下に太字のページを登録します。その2行あとの`:index[italic]{term="Italic" main}`は、同じページを事項索引の「Italic」の下に登録します。章の主題でもある人物には、両方のマークを付けられます。

### 2 · 名前を倒置し、読者が引く文字で並べる

名前は姓を先にして登録します（`term="Garamond, Claude"`、`term="Dall’Aglio, Margherita"`）。コンマも見出し語の一部なので、照合順序は索引編集者と同じく「Didot, Firmin」を「Didot, François-Ambroise」の前に並べます。刷られる形のままでは違う文字の下に入ってしまうときは、`sort`で並べ替えのキーを与えます。英語版では`term="42-line Bible" sort="Forty-two-line Bible"`とマークしているので、この聖書は0–9のグループを新たに作らず、Fの下に入ります。下位項目（`term="Foundries!Plantin-Moretus"`）、太字の主要ページ（`main`）、ボドニの節を挟む`range="start"`と`range="end"`、参照（`see="Italic"`、`seealso="Matrices"`）が残りを受け持ちます。階層を持つ`see`の参照先はコロンで区切って刷られ、「Humanist type. See Roman type: Venetian」のようになります。

### 3 · ビルドは1回、ページ番号は文書が自分で確定する

```js
// script.js, 行 218–221
// A pass finds the page of every mark and prints both indexes with those numbers. When the
// printed index moves a mark (an index set before the text would), the layout runs again,
// until no page number changes (in a buildBundle book, the chapter with :::index gets them all).
const doc = await buildWithFonts(() => buildDocument({ markdown }, config()), markdown);
```

索引が刷るページ番号はレイアウトのあとにしか存在せず、索引を刷ればそれ自体がページを取ります。これを処理するのが`buildDocument()`です。章を組み、各マークのページを読み取り、2つの索引を刷り、番号が変わるたびに組み直します。ここでは索引が本文のあとにあるので、2回目のパスは1回目を確かめるだけです。索引が本文より前にあると、その後のページがすべてずれ、パスは結果が一致するまで続きます。ボドニの節に段落を1つ足せば、人名索引はウィッティンガムの新しいページを自分で反映します。

### 4 · 各索引に2段組みの扉を付ける

```js
// script.js, 行 61–75
const opener = (sink, size) => ({
  enabled: true,
  minHeight: pt(LEAD * sink), // the text starts on the same grid line in every chapter
  slot: { elements: [
    { kind: 'text', id: 'kicker', content: '{attr.kicker}', ...label, fontSize: pt(7.8),
      letterSpacing: pt(1.6), color: col('brick'), align: 'left',
      placement: at('container', 'top-left', 0, 10) },
    { kind: 'text', id: 'title', content: '{titleText}', fontFamily: 'Bodoni Moda', italic: true,
      fontSize: pt(size), lineHeight: 1.08, color: col('ink'), align: 'left',
      overflow: 'wrap', // design text ends in an ellipsis by default
      placement: { ...at('#kicker', 'below', 0, 3), size: { width: 'fill', height: 'auto' } } },
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.75), color: col('brick'),
      placement: { ...at('#title', 'below', 0, 4.5), size: { width: mm(16) } } },
  ] },
});
```

手短な答えの見出しスタイルは、`# Index of Names {style="index"}`に対して3つのことをします。ページを改め（`parity: 'any'`なので、2つの索引は白ページを挟まずに続きます）、そのあとの節を段間7mmの2段組みにし、`numbered: false`で章の番号には手を付けません。このスタイルは章扉を流用し、6行分の下げと20ptの題を持ちます。見出し文字はそのBodoniをアクセントの色で繰り返し、索引の1行分の高さに収めるので、段の行送りは崩れません。

### 5 · 奇数ページの柱に索引名を入れる

```js
// script.js, 行 79–96
const head = (id, content, parity, edge, x, extra = {}) => ({
  kind: 'text', id, content, parity, pages: 'body', // never on openers
  ...label, fontSize: pt(7.2), letterSpacing: pt(1.3), color: col('muted'),
  placement: at('page', edge, x, 12.5), ...extra,
});
const folio = { color: col('brick'), 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' }),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -MARGIN.outer,
    { ...folio, align: 'right' }),
] };
// Openers carry 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, -12) }), pages: 'opener' }] };
```

奇数ページの柱は`{chapterTitle}`を刷ります。索引の見出しも章と同じレベル1の見出しなので、そのあとのページの上部には「Index of Subjects」と入ります。`pages: 'body'`で2つの索引の扉ページから柱を外し、そこには代わりに下ノンブルを置きます。

## レシピの全体

レシピのフォルダーから合成した1つのファイルで、サンプルのテキストとレシピ集の共通キットを埋め込んであり、自分でページを組み立てます。動かすには、空のページの`<script type="module">`に入れるか、新しいCodePenのpenのJSパネルに貼り付けます（モジュールとして）。esm.shからpostextを読み込むので、インストールもビルドも要りません。

- レシピのフォルダー: https://github.com/drnachio/postext/tree/main/cookbook/names-and-subjects-indexes

### script.js

```js
// ═══ Postext Cookbook · Nº 073 · An index of names and an index of subjects ═══════
// https://postext.dev/en/cookbook/names-and-subjects-indexes
// Code: MIT · Text: original (CC BY 4.0) · Pictures: none
// Fonts: Crimson Pro, Bodoni Moda, Libre Franklin (SIL OFL 1.1) · Needs postext ≥ 1.7.0
import { buildDocument, renderPageToCanvas, clearMeasurementCache } 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 = 'names-and-subjects-indexes';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: a warm black, one brick red for kickers, letter heads and folios
const palette = {
  ink: '#211d19', // text: a warm near-black
  brick: '#8e3b24', // the one accent
  muted: '#6d645a', // running heads, colophon
  rule: '#c8bba9', // hairlines
  paper: '#fcfaf5',
};
// A design element paints the hex written beside its paletteId (gotcha: palette-skips-designs).
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.brick, model: 'hex' } },
];
// #endregion
const TRIM = { width: 155, height: 235 }; // a scholarly monograph
const MARGIN = { top: 22, bottom: 24, inner: 19, outer: 22 }; // mirrored: 114 mm measure
const LEAD = 13.6; // body leading in pt
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });
const label = { fontFamily: 'Libre Franklin', fontWeight: 600, textTransform: 'uppercase' };

// #region answer: two indexes from one text, each under a two-column heading style
// Marks in the Markdown. A person goes to the names index, inverted:
//   :index[Aldus Manutius]{term="Manutius, Aldus" index="names" main}
// A subject goes to the main index, with levels split by "!" and a sort key when the
// printed form would file in the wrong place:
//   :index[textura]{term="Blackletter!textura" main}
//   :index{term="42-line Bible" sort="Forty-two-line Bible"}
//   :index{term="Cursive" see="Italic"}
// Printed at the end, each under its own heading:
//   # Index of Names {style="index"}    then    :::index{index="names"}
//   # Index of Subjects {style="index"} then    :::index
const indexHeading = {
  id: 'index',
  numbered: false, // no chapter number, and the next chapter is still chapter 5
  breakBefore: { enabled: true, parity: 'any' },
  layout: { layoutType: 'double', gutterWidth: mm(7) }, // the section after the heading
};
const index = {
  fontSize: pt(8.6), lineHeight: pt(11.2), // one leading for entries and letter heads
  indent: em(1), turnoverIndent: em(2),
  rangeFormat: 'chicago', // 212–14, as a monograph prints it
  main: { bold: true }, // the page of the principal discussion
  groups: { fontFamily: 'Bodoni Moda', fontSize: pt(11), fontWeight: 600, color: col('brick') },
};
// #endregion

// #region opener: kicker, a Bodoni italic title and a short rule, sunk to the same line
const opener = (sink, size) => ({
  enabled: true,
  minHeight: pt(LEAD * sink), // the text starts on the same grid line in every chapter
  slot: { elements: [
    { kind: 'text', id: 'kicker', content: '{attr.kicker}', ...label, fontSize: pt(7.8),
      letterSpacing: pt(1.6), color: col('brick'), align: 'left',
      placement: at('container', 'top-left', 0, 10) },
    { kind: 'text', id: 'title', content: '{titleText}', fontFamily: 'Bodoni Moda', italic: true,
      fontSize: pt(size), lineHeight: 1.08, color: col('ink'), align: 'left',
      overflow: 'wrap', // design text ends in an ellipsis by default
      placement: { ...at('#kicker', 'below', 0, 3), size: { width: 'fill', height: 'auto' } } },
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.75), color: col('brick'),
      placement: { ...at('#title', 'below', 0, 4.5), size: { width: mm(16) } } },
  ] },
});
// #endregion

// #region running-heads: book title on the verso, chapter or index on the recto
const head = (id, content, parity, edge, x, extra = {}) => ({
  kind: 'text', id, content, parity, pages: 'body', // never on openers
  ...label, fontSize: pt(7.2), letterSpacing: pt(1.3), color: col('muted'),
  placement: at('page', edge, x, 12.5), ...extra,
});
const folio = { color: col('brick'), 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' }),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -MARGIN.outer,
    { ...folio, align: 'right' }),
] };
// Openers carry 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, -12) }), pages: 'opener' }] };
// #endregion

const config = () => ({ // a factory: the engine caches resolved configs per object
  locale: t({ en: 'en-us', es: 'es' }), // exact codes (gotcha: hyphenation-locales)
  colorPalette,
  page: { sizePreset: 'custom', width: mm(TRIM.width), height: mm(TRIM.height), 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(10.6), 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: 'Bodoni Moda', fontWeight: 400, color: col('ink'), levels: [
    // Restated on purpose: any headings object drops the H1 break
    // (gotcha: headings-drop-h1-break).
    { level: 1, advancedDesign: opener(9, 26), marginBottom: pt(0),
      breakBefore: { enabled: true, parity: 'odd' } },
    { level: 2, fontSize: pt(12), lineHeight: pt(LEAD), italic: true, fontWeight: 400,
      color: col('brick'), marginTop: pt(LEAD), marginBottom: pt(LEAD * 0.5) },
  ] },
  // The index heading: a shallower opener over the two columns of entries.
  headingStyles: [{ ...indexHeading, span: 'page', advancedDesign: opener(6, 20),
    marginBottom: pt(0) }],
  index,
  paragraphStyles: [
    { id: 'colophon', ...label, textTransform: 'none', fontWeight: 400, fontSize: pt(7),
      lineHeight: pt(10), color: col('muted'), textAlign: 'left', firstLineIndent: pt(0),
      marginTop: pt(LEAD * 2) },
  ],
  header,
  footer,
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = String.raw`---
title: "Letters of Metal"
subtitle: "A Short History of Printing Types"
---

# From Textura to Bodoni {kicker="Chapter four"}

The first European types imitated the hand that readers trusted most. When :index[Johannes Gutenberg]{term="Gutenberg, Johannes" index="names" main} printed his Bible in Mainz, finished by 1455,:index{term="42-line Bible" sort="Forty-two-line Bible" main} he set it in :index[textura]{term="Blackletter!textura" main}, the tall, narrow letter of German choir books, with its broken strokes and diamond feet. The page was meant to pass for a manuscript, and it did: rubricators added the red initials by hand, and some copies were printed on vellum. What was new lay out of sight. Each letter had been cut in steel as a :index[punch]{term="Punchcutting" main}:index{term="Punchcutting" seealso="Matrices"}, struck into a bar of softer copper to make a :index[matrix]{term="Matrices"}, and cast in the :index[hand mould]{term="Hand mould"} from an alloy of lead, tin and antimony.:index{term="Type metal"} A single punch could yield thousands of identical sorts, and the whole chain, from punch to page, would stay in use with few changes for nearly four hundred years.

Gutenberg’s partners saw the business before he did. :index[Johann Fust]{term="Fust, Johann" index="names"} took over the shop after a lawsuit in 1455, and with :index[Peter Schoeffer]{term="Schoeffer, Peter" index="names"} he printed the Mainz Psalter of 1457, the first book to carry a printer’s colophon and a date. When Mainz was sacked in 1462 its journeymen scattered, and within twenty years there were presses in more than a hundred towns.

## The Venetian roman

Italian readers did not want German letters. The humanists of Florence had revived the Carolingian minuscule for their copies of the classics, a round, open hand they believed ancient, and the printers who came south followed them. The best of the early romans was cut in Venice by :index[Nicolas Jenson]{term="Jenson, Nicolas" index="names" main}, a Frenchman trained at the mint of Tours. His Eusebius of 1470 is set in a :index[roman]{term="Roman type!Venetian" main} of even colour and wide, well-fitted capitals, and it has been the model for revivals ever since.

Twenty-five years later :index[Aldus Manutius]{term="Manutius, Aldus" index="names" main} opened his press in the same city, with a scholar’s programme: Greek texts first, then the Latin authors in editions a student could carry. His punchcutter was :index[Francesco Griffo]{term="Griffo, Francesco" index="names" main} of Bologna. For the short dialogue *De Aetna* by the young :index[Pietro Bembo]{term="Bembo, Pietro" index="names"}, printed in 1496,:index{term="*De Aetna*"} Griffo cut a roman lighter and more regular than Jenson’s, with capitals shorter than the ascenders, the proportion book faces have kept to this day. :index{term="Roman type!Aldine"}

In 1501 Aldus issued a :index[Virgil]{term="Virgil" index="names"} in octavo, set entirely in a new letter: Griffo’s :index[italic]{term="Italic" main}, based on the sloped script of the papal chancery. It saved space, which suited a pocket book, and it looked learned. The Venetian Senate granted Aldus a privilege over it,:index{term="Privileges"} counterfeiters in Lyon copied it within a year, and Aldus and Griffo quarrelled over the credit until Griffo left Venice. For a century the italic was a text face in its own right. Only later did printers pair it with a roman and use it for emphasis, titles and foreign words, the role it still has.
:index{term="Cursive" see="Italic"}

## Paris and Antwerp

The roman became French in the 1530s. The Paris printer :index[Robert Estienne]{term="Estienne, Robert" index="names"} and his circle wanted Aldine letters, and the man who cut them best was :index[Claude Garamond]{term="Garamond, Claude" index="names" main}, a punchcutter who worked mostly for other men’s presses. His romans refine Griffo’s model: the thin strokes are finer, the serifs sharper, the capitals lighter against the lower case. Between 1540 and 1550 he also cut the *grecs du roi*,:index{term="Greek type"} three sizes of Greek for François I, closely modelled on the hand of the Cretan scribe Angelos Vergikios.

When Garamond died in 1561 his punches were sold, and many went to Antwerp. :index[Christophe Plantin]{term="Plantin, Christophe" index="names" main}, a bookbinder from Touraine, had built there the largest printing house in Europe, and he bought type as a banker buys bonds. His shop kept the punches and matrices of Garamond, of :index[Robert Granjon]{term="Granjon, Robert" index="names" main} and of Guillaume Le Bé, and for the great Polyglot Bible of 1568–1572, in Latin, Greek, Hebrew, Aramaic and Syriac, he needed all of them. Granjon was the finest italic cutter of the century; he also cut, in Lyon in 1557, the *civilité*, a French secretary hand he hoped would become a national type. It did not, but his italics did. The house stayed with the Moretus family until the city of Antwerp bought it in 1876, and its punches, matrices and account books are still there, the most complete type foundry of its age to survive.:index{term="Foundries!Plantin-Moretus"}

## The English century

England printed with Dutch type until the eighteenth century, when an engraver of gun locks set up a foundry in London. :index[William Caslon]{term="Caslon, William" index="names" main} issued his specimen sheet in 1734,:index{term="Specimens!Caslon, 1734"} and his roman, sturdy and a little irregular, became the ordinary type of the English-speaking world. It needed no explanation and drew no attention to itself, which is why printers still reach for it.

:index[John Baskerville]{term="Baskerville, John" index="names" main}, a Birmingham writing master who had made his money in japanned ware, came to printing late and changed more than the letters. For his Virgil of 1757 he had his own type cut by John Handy, with a more vertical stress and a sharper contrast than Caslon’s, and he printed it on smooth wove paper,:index{term="Paper, wove"} with a blacker ink of his own recipe, and pressed the sheets between hot copper plates to give them a gloss. English printers found the pages too bright to read. Readers on the Continent admired them, and a :index[transitional]{term="Transitional type"} letter, between the old style and the modern, was born.

## Measuring type

Until the middle of the eighteenth century every foundry named its sizes as it pleased: *pica*, *cicero*, *long primer*, *petit-romain*. A size was a habit of the house, not a measurement, and a printer who bought type from two foundries could not set it side by side. :index[Pierre-Simon Fournier]{term="Fournier, Pierre-Simon" index="names" main}, the younger son of a Paris family of typefounders, published in 1737 a table of proportions that divided an inch into seventy-two :index[points]{term="Point system" main range="start"} and gave every body a number of points. He set it out in full in his *Manuel typographique* of 1764–1766.:index{term="*Manuel typographique*"} :index[François-Ambroise Didot]{term="Didot, François-Ambroise" index="names"} tied the point to the royal foot about 1783, which made it slightly larger; the Didot point spread across the Continent, while American and British founders settled on a pica point of their own only in 1886 and 1898. :index{term="Point system" range="end"}

## Spanish types

Spanish printers of the early eighteenth century bought their type in Holland and France, and the Crown thought this a disgrace for a kingdom that had printed the Complutensian Polyglot. :index[Charles III]{term="Charles III, king of Spain" index="names"} and his ministers set out to change it. The Royal Library in Madrid opened a foundry of its own,:index{term="Foundries!Royal Library, Madrid" main} and the Crown paid pensions to punchcutters who would work in Spain. Two of them came from very different trades.

:index[Eudald Pradell]{term="Pradell, Eudald" index="names" main}, the son of a gunsmith from Ripoll, had learned to cut steel in his father’s forge and cut his first punches in Barcelona. Called to Madrid in 1764 with a royal pension, he cut romans and italics of an open, generous design that the Madrid printers took up at once. :index[Jerónimo Antonio Gil]{term="Gil, Jerónimo Antonio" index="names" main} came from the other end of the trade: he was an engraver of the Royal Mint, a medallist, and he cut for the Royal Library a series of romans, italics and Greek that the Library showed in its specimen of 1787.:index{term="Specimens!Royal Library, 1787"} Gil did not stay to see it. In 1778 he left for Mexico as chief engraver of the mint, and there he founded the school that became the Academy of San Carlos.

The printer who made these types famous was :index[Joaquín Ibarra]{term="Ibarra, Joaquín" index="names" main}, born in Zaragoza in 1725 and printer to the King from 1779. His Sallust of 1772, translated by the Infante Gabriel,:index{term="Gabriel of Bourbon, Infante" index="names"} was praised in Paris and Parma for its presswork. For the Real Academia Española he printed in 1780 the *Quixote* in four quarto volumes,:index{term="*Quixote*, Academy edition" main}:index{term="Cervantes, Miguel de" index="names"} set in Gil’s types, illustrated by the best engravers of the court and printed on paper made for it in Catalonia. The ink, dark and even, was Ibarra’s own; the page, with generous margins and no ornament besides the plates, is the Spanish counterpart of Baskerville’s.

## Bodoni’s moderns

The last step belonged to a printer at a small court.:index{term="Modern face" range="start"} :index[Giambattista Bodoni]{term="Bodoni, Giambattista" index="names" main}, born in Saluzzo in 1740, learned to cut punches at the press of the Propaganda Fide in Rome, where he cleaned and set in order its oriental types. In 1768 Duke Ferdinand called him to Parma to run the ducal press, and he stayed there for the rest of his life. His early types follow Fournier; by the 1790s he was cutting letters of a new kind, with hairline serifs, strictly vertical stress and a contrast between thick and thin that only smooth paper and careful printing could hold. These are the :index[modern]{term="Modern face" main} faces. In Paris, :index[Firmin Didot]{term="Didot, Firmin" index="names"} was drawing the same conclusions at the same time, and the two houses watched each other closely.:index{term="Didone" see="Modern face"}

Bodoni printed few books that anyone read, and many that everyone admired: Homer in Greek, Horace, Tasso, folio after folio for collectors and kings. His life’s work was the :index[*Manuale tipografico*]{term="*Manuale tipografico*" main}, which his widow, :index[Margherita Dall’Aglio]{term="Dall’Aglio, Margherita" index="names"}, published in 1818, five years after his death: two volumes of romans and italics in more than a hundred sizes and cuts, with Greek, Hebrew and other alphabets, and a preface that says what a letter ought to be. The modern face swept the Continent and then England, where by 1820 hardly a printer still set books in old-style type.:index{term="Modern face" range="end"}

## Names for the families

The words this chapter has used, :index[old style]{term="Old style" main}, transitional and modern, come from the printers of the nineteenth century, who needed a way to order their specimen books, and they are not precise. An old style in 1860 meant any letter cut on the model of Caslon; a modern meant anything after Bodoni, however clumsy. The old style came back in the 1840s, when :index[Charles Whittingham]{term="Whittingham, Charles" index="names"} of the Chiswick Press reissued Caslon’s type for a book set in the manner of the seventeenth century, and from then on the two lived side by side in every specimen.

In 1921 the French typefounder :index[Francis Thibaudeau]{term="Thibaudeau, Francis" index="names"} proposed to sort types by their serifs alone: triangular, hairline, square or none.:index{term="Classification of types!Thibaudeau, 1921"} It was easy to apply and blind to history. :index[Maximilien Vox]{term="Vox, Maximilien" index="names"}, an illustrator and publisher, went further in 1954 with nine families whose names join the masters of each style: *humanes* for the Venetians after Jenson, *garaldes* for Garamond and Aldus, *réales* for the letters of Fournier and Baskerville, *didones* for Didot and Bodoni.:index{term="Classification of types!Vox, 1954" main} The Association Typographique Internationale adopted a version of it in 1962, and the British Standards Institution followed in 1967.
:index{term="Garalde" see="Old style"}
:index{term="Humanist type" see="Roman type!Venetian"}

These labels leave out as much as they sort. Gil’s romans for the Royal Library are *réales* by the book and Spanish by every other measure; Pradell’s italics owe as much to Granjon as to anything cut in their own century. The quarrel between the two ideas of the letter, the one written with a pen and the one drawn with compasses and rule, is older than any of these names and has not been settled.

:::paragraphs{style="colophon"}
*Letters of Metal*, chapter four. Set in Crimson Pro, with Bodoni Moda for display and Libre Franklin for labels (SIL Open Font License). Text written for the Postext Cookbook, CC BY 4.0.
:::

# Index of Names {style="index" kicker="Printers, punchcutters, patrons"}

:::index{index="names"}

# Index of Subjects {style="index" kicker="Types, tools and books"}

:::index
`; // content.<lang>.md, inlined by the Cookbook

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
// Every face the design uses (gotcha: fonts-first).
const FONTS = {
  'Crimson Pro': ['400', '400i', '600', '700'],
  'Bodoni Moda': ['400', '400i', '600'],
  'Libre Franklin': ['400', '400i', '600', '700'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
await loadFonts(FONTS, markdown);
// #region build: one buildDocument, laid out again until the index's page numbers settle
// A pass finds the page of every mark and prints both indexes with those numbers. When the
// printed index moves a mark (an index set before the text would), the layout runs again,
// until no page number changes (in a buildBundle book, the chapter with :::index gets them all).
const doc = await buildWithFonts(() => buildDocument({ markdown }, config()), markdown);
// #endregion
showPages(doc, { title: t({ en: 'Two indexes', es: 'Dos índices' }) });
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 v1 ── the same in every recipe · postext.dev/cookbook ────────
// Postext measures text with the faces the browser has loaded, and caches the
// widths, so every face must be ready before the first build. Faces come from
// Fontsource: the same static files the PDF embeds, so screen and PDF agree.

/** faces = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample:
 *  letters beyond Latin-1 (č, ł, ő…) also load the latin-ext files. With
 *  `optional`, a face Fontsource does not ship is skipped instead of failing.
 *  Resolves to the number of faces added. */
async function loadFonts(faces, text = '', { optional = false } = {}) {
  kitStatus('Loading fonts…');
  const ranges = {
    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',
  };
  const subsets = /[Ā-˿Ḁ-ỿ]/.test(text) ? ['latin', 'latin-ext'] : ['latin'];
  const jobs = [];
  let added = 0;
  for (const [family, specs] of Object.entries(faces)) {
    const id = fontsourceId(family);
    const meta = optional ? await fontsourceMeta(family) : null;
    for (const spec of new Set(specs)) {
      const weight = parseInt(spec, 10);
      const style = spec.endsWith('i') ? 'italic' : 'normal';
      if (hasFace(family, weight, style)) continue;
      if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue;
      for (const subset of subsets) {
        const url = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/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().then((ready) => { document.fonts.add(ready); added++; }, () => {
          if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`);
        }));
      }
    }
  }
  await Promise.all(jobs).catch((error) => { kitFail(error); throw error; });
  return added;
}

/** Runs `build` (a buildDocument or buildBundle call) and checks the faces
 *  the pages use. A regular face missing from FONTS is loaded with a warning;
 *  bold and italic variants are loaded when the family ships them. Then the
 *  measurement caches are cleared and the build runs again. */
async function buildWithFonts(build, text = '') {
  const tried = new Set();
  for (let round = 0; round < 3; round++) {
    kitStatus('Laying out…');
    await new Promise(requestAnimationFrame);          // let the status paint first
    const result = await Promise.resolve().then(build).catch((error) => { kitFail(error); throw error; });
    const wanted = { base: {}, variants: {} };
    for (const { font, base } of [result].flat().flatMap(fontStringsOf)) {
      const { family, weight, style } = parseFont(font);
      const key = `${family}|${weight}|${style}`;
      if (tried.has(key) || hasFace(family, weight, style)) continue;
      tried.add(key);
      (wanted[base ? 'base' : 'variants'][family] ??= []).push(`${weight}${style === 'italic' ? 'i' : ''}`);
    }
    if (Object.keys(wanted.base).length) {
      console.warn(`[cookbook] FONTS does not list ${JSON.stringify(wanted.base)}: loading them.`);
    }
    const added = await loadFonts(wanted.base, text) + await loadFonts(wanted.variants, text, { optional: true });
    if (added === 0) return result;
    clearMeasurementCache();
  }
  throw new Error('The fonts did not settle after three builds.');
}

/** Every font string of the layout. `base` marks a block's own face; its
 *  bold, italic and bold-italic variants are listed whether or not used. */
function fontStringsOf(doc) {
  const found = new Map();
  const walk = (node) => {
    if (!node || typeof node !== 'object') return;
    if (Array.isArray(node)) { node.forEach(walk); return; }
    for (const [key, value] of Object.entries(node)) {
      if (typeof value === 'string' && /fontString$/i.test(key)) {
        found.set(value, found.get(value) || key === 'fontString');
      } else if (value && typeof value === 'object') walk(value);
    }
  };
  walk(doc.pages);
  walk(doc.blocks);
  return [...found].map(([font, base]) => ({ font, base }));
}

/** '700 37.5px Open Sans' / 'italic 400 13px "Source Serif 4"' → { family, weight, style }.
 *  A string with no weight ('95.8px Young Serif', from a design text) is 400. */
function parseFont(font) {
  const m = /^(?:(italic|oblique)\s+)?(?:small-caps\s+)?(?:(\d+|bold|normal)\s+)?[\d.]+px\s+(.+)$/.exec(font.trim());
  if (!m) throw new Error(`Unexpected font string: ${font}`);
  const weight = m[2] === 'bold' ? 700 : !m[2] || m[2] === 'normal' ? 400 : Number(m[2]);
  return { family: m[3].replace(/^["']|["']$/g, ''), weight, style: m[1] ? 'italic' : 'normal' };
}

/** True when a loaded FontFace covers exactly this family, weight and style
 *  (document.fonts.check() is also true 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;
}

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

/** The weights and styles a family ships ({ weights: [400, 700], styles: ['normal', 'italic'] }), 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 v1 ── the same in every recipe · postext.dev/cookbook ───────
/** Shows the pages as facing spreads on a dark desk: the first page is a
 *  recto on its own, then verso | recto pairs, as in a bound book. Pages
 *  are painted when they scroll near the screen. */
function showPages(docs, { title, width = 460 } = {}) {
  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, { 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 v1 ── the same in every recipe that exports a PDF ──────────────
/** postext-pdf embeds TrueType bytes. Fetch the Fontsource file the screen
 *  used, snapping to a weight the family ships and falling back to upright
 *  when it has no italic: the PDF asks for every face a block could use. */
async function fontsourceProvider(family, weight, style) {
  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 res = await fetch(`https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/files/${id}-latin-${w}-${s}.woff2`);
  if (!res.ok) throw new Error(`Fontsource has no ${family} ${w} ${s} (${res.status})`);
  return decompressWoff2(new Uint8Array(await res.arrayBuffer()));
}

/** A "Build the PDF" button in the bar. Once built: "Open the PDF" (a new
 *  tab, since CodePen's preview frame cannot show 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 ───────────────────────────────────────────────────────────────────────
```

## アレンジ

### 1つにまとめた索引を刷る

人物のマークと2つ目の見出しから`index="names"`を外します。すると、すべてのマークが事項索引に入り、人名と事項が1つのアルファベット順に並びます。

```diff
-:index[Aldus Manutius]{term="Manutius, Aldus" index="names" main}
+:index[Aldus Manutius]{term="Manutius, Aldus" main}
```

### 見出し文字をなくす

この程度の短い人名索引なら、見出し文字がなくても読めます。`groups.enabled: false`は見出し文字を外し、文字の区切りに1行の空きを残します。この設定は両方の索引に効きます。

```diff
-  groups: { fontFamily: 'Bodoni Moda', fontSize: pt(11), fontWeight: 600, color: col('brick') },
+  groups: { enabled: false },
```

## よくあるつまずき

- **headingsオブジェクトを渡すとH1の改ページが消える.** 既定ではH1は奇数ページへ改ページします（always-odd）。ところがheadingsオブジェクトを渡すと中身にかかわらずこの既定がリセットされ、章は改ページせずに続けて組まれ、span: 'page'も効かなくなります。どの設定でもheadings.levels[0].breakBefore: { enabled: true, parity }を書き直してください。
- **レイアウトの前にすべてのフォントを読み込む.** レイアウトはブラウザーが読み込んだフォントで文字を計測し、その幅をキャッシュします。最初のビルドのあとに届いたフォントがあると改行位置が狂い、PDFも画面と一致しなくなります。すべてのウェイトとスタイルを先に読み込み、遅れて届いたときは再ビルドの前にclearMeasurementCache()を呼んでください。
- **パレットを差し替えても、デザイン要素と参照色は変わらない.** postext 1.4.1はcolorPaletteをテキストのスタイル（本文、見出し、リスト、キャプション、表、囲み）には反映しますが、ヘッダー、フッター、章扉、部扉の要素と、bodyText.referenceColorには反映しません。これらはpaletteIdの横に書いた16進の色のままです。画面用のダーク版や色替えのためにパレットを差し替えるときは、ビルドの前に、リンクしたすべての色をcolorPaletteから書き直してください。
- **デザインのテキストのoverflowの既定値は'ellipsis-end'.** 幅に収まらないデザインのテキスト要素は、既定では省略記号で終わります。複数行に折り返したいタイトルにはoverflow: 'wrap'を設定してください。
- **ハイフネーションできるのは8つのロケールだけで、コードは完全一致.** ハイフネーションが用意されているのはen-us、es、fr、de、it、pt、ca、nlで、コードは完全一致で照合されます。'es-ES'やほかの言語は、何の知らせもなくアメリカ英語にフォールバックします。
- **設定はオブジェクトの同一性でキャッシュされる。毎回新しいオブジェクトを作る.** エンジンは解決済みの設定をオブジェクトの同一性でキャッシュします。そのため、設定をその場で書き換えて再ビルドすると前の結果が再利用されます。ビルドのたびに新しいオブジェクトを作ってください。レシピの設定がファクトリー関数config()になっているのはこのためです。

- `index`の設定は共有です。2つの索引は同じ書体、行送り、見出し文字を使います。
- キャプションや表のセルの中のマークは、書いたとおりに刷られるだけです。人物のマークは、その図を参照する段落に付けます。

## クレジット

- レシピ: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- テキスト: “From Textura to Bodoni”, an essay on the history of printing types written for this recipe, in English and Spanish: Ignacio Ferro, CC-BY-4.0
- 書体: Crimson Pro (OFL-1.1), Bodoni Moda (OFL-1.1), Libre Franklin (OFL-1.1)
- コード: MIT · サンプルの内容: CC-BY-4.0

## 関連レシピ

- [No. 031 · 学位論文の後付け：付録、用語集、索引](https://postext.dev/ja/cookbook/thesis-back-matter.md): 白黒で組む学位論文の最後のページ。文字番号の付録、2段組みの用語集、APA式の文献一覧、本文中の印から作る索引。 · 難易度 3 (上級) · 論文・学術書
- [No. 007 · 章ごとのファイルから1冊の本を組む](https://postext.dev/ja/cookbook/book-from-chapters.md): buildBundleで5つのMarkdownファイルを1冊の本として組みます。各章は奇数ページから始まり、ページ番号、章番号、図番号はファイルをまたいで続きます。 · 難易度 3 (上級) · マニュアル・ガイド・リファレンス
- [No. 126 · 読みで並べる五十音順の日本語索引](https://postext.dev/ja/cookbook/gojuon-index.md): 日本語の組版用語を扱う章とその索引。各マークは振り仮名かyomiで読みを持ち、項目はJISの順に、あ行、か行、さ行の下へ分類されます。 · 難易度 2 (中級) · マニュアル・ガイド・リファレンス, 教科書
