# Una novela corta en catalán, con la división del IEC

> El transplantat de Oller, completo, en un bolsillo de 32 páginas: sílabas catalanas según el IEC, l·l cortada como il- | lusió y un grabado por capítulo.

- Versión HTML: https://postext.dev/es/cookbook/catalan-pocket-novella
- Receta N.º 104 · Texto y tipografía · Nivel 2 (Intermedio) · Salidas: Canvas, PDF
- Géneros: Narrativa, teatro y prosa literaria
- Requiere postext ≥ 1.14.0, postext-pdf ≥ 1.14.0 · probada con 1.14.0, postext-pdf 1.14.0 el 2026-10-04
- Páginas: [2](https://postext.dev/cookbook/catalan-pocket-novella/es/p02.webp?v=348f4303), [3](https://postext.dev/cookbook/catalan-pocket-novella/es/p03.webp?v=348f4303), [4](https://postext.dev/cookbook/catalan-pocket-novella/es/p04.webp?v=348f4303), [5](https://postext.dev/cookbook/catalan-pocket-novella/es/p05.webp?v=348f4303), [6](https://postext.dev/cookbook/catalan-pocket-novella/es/p06.webp?v=348f4303), [26](https://postext.dev/cookbook/catalan-pocket-novella/es/p26.webp?v=348f4303), [27](https://postext.dev/cookbook/catalan-pocket-novella/es/p27.webp?v=348f4303), [32](https://postext.dev/cookbook/catalan-pocket-novella/es/p32.webp?v=348f4303)
- PDF: https://postext.dev/cookbook/catalan-pocket-novella/es/catalan-pocket-novella.pdf?v=348f4303
- Abrir en el Sandbox: https://postext.dev/es/sandbox#recipe=catalan-pocket-novella&lang=es (.postext: https://postext.dev/cookbook/catalan-pocket-novella/es/catalan-pocket-novella.postext)
- Última actualización: 2026-10-04
- Otros idiomas: [en](https://postext.dev/en/cookbook/catalan-pocket-novella.md), [zh](https://postext.dev/zh/cookbook/catalan-pocket-novella.md)

## En pocas palabras

Una novela corta entera en catalán, compuesta como un libro de bolsillo. Las palabras se cortan a final de línea como piden las normas catalanas, también la ele doble con punto.

## Lo que vas a componer

*El transplantat* de Narcís Oller entero, con el texto de sus *Obres completes* (Barcelona, 1928), en un libro de bolsillo de 12 × 18,5 cm y 32 páginas. Antes de los cuatro capítulos van una anteportada, un frontispicio con el panadero ante su horno, la portada en Cormorant Garamond cursiva y roja, y una nota sobre la edición. Cada capítulo se abre bajo un grabado de doce líneas de alto, con su número romano en rojo. El texto, en Crimson Pro de 10,4 pt, se divide por sílabas catalanas como pide el Institut d’Estudis Catalans: al menos dos letras a cada lado, ningún corte entre vocales en hiato ni justo después de un apóstrofo, y *titil·laven* se corta *titil-* | *laven*, con el guion en el lugar del punto volado. Los diálogos llevan raya y las citas, «cometes baixes».

**Esta receta responde a:**

- ¿Cómo divido bien el catalán a final de línea, con la l·l, los apóstrofos y los pronombres unidos con guion?
- ¿Cómo doy a cada capítulo su propia foto, su color o una variante de apertura?
- ¿Cómo compongo ilustraciones sin numerar: adornos, viñetas, logotipos?
- ¿Cómo escribo rayas de diálogo, años al principio de párrafo, precios y símbolos literales sin que el Markdown los malinterprete?

## La respuesta corta

```js
// script.js, líneas 48–62
const LOCALE = 'ca'; // the exact bundled code: 'ca-ES' resolves to it too (postext ≥ 1.14)
const bodyText = { // config().bodyText
  fontFamily: TEXT, fontSize: pt(BODY), lineHeight: pt(LEAD), color: col('ink'),
  boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
  firstLineIndent: mm(4.5), indentAfterHeading: false,
  // The defaults justify, hyphenate in config().locale and break by Knuth–Plass. With 'ca'
  // the dictionary follows the IEC: two letters on each side (ter-ra, cai-xa), no split in
  // a hiatus (cièn-cia), none after an apostrophe (s’ha-via), and l·l breaks with the hyphen
  // in place of the dot. compounds: false keeps arrossegar-se whole but for its own hyphen,
  // the break the IEC prefers for a verb and its pronoun (Llibre d’estil, VI 2.5).
  hyphenation: { compounds: false },
  // Spaces past 1.6× take up to 10‰ of tracking instead; a last line under 22 characters is
  // a runt. A line that must keep d’inaugurar-se whole still opens to 1.94× on page 7.
  maxWordSpacing: 1.6, maxJustifyTracking: 10, runtMinCharacters: 22,
};
```

## Ingredientes

**Enseña**

- [Separación silábica e idioma del documento](https://postext.dev/es/docs/justification.md#idiomas-soportados): Separación silábica con patrones de TeX en ocho idiomas, según el locale del documento, que fija también los textos de continuación de las tablas y el idioma del PDF etiquetado.
- [Corte de líneas óptimo (Knuth–Plass)](https://postext.dev/es/docs/justification.md#knuth-plass-ver-el-párrafo-completo): Corta cada párrafo en su conjunto para igualar el espacio entre palabras, dentro de los límites fijados, en lugar de llenar línea a línea.
- [Cubiertas, portadas y colofones](https://postext.dev/es/docs/configuration.md#estilos-de-encabezado): Diseños a página completa construidos con estilos de título: una cubierta o portada con metadatos e ilustración, una portadilla, un colofón en el verso.

**También usa**

- [Aperturas diseñadas](https://postext.dev/es/docs/configuration.md#span-y-diseño-avanzado)
- [Imágenes en los diseños de página](https://postext.dev/es/docs/configuration.md#elementos-de-imagen)
- [Estilos de título](https://postext.dev/es/docs/configuration.md#estilos-de-encabezado)
- [Atributos de título](https://postext.dev/es/docs/document-format.md#atributos-de-encabezado)
- [Textos, filetes y cajas en los diseños de página](https://postext.dev/es/docs/configuration.md#encabezados-y-pies)
- [Capítulos que abren en página impar](https://postext.dev/es/docs/configuration.md#saltar-antes)
- [Cabeceras y folios](https://postext.dev/es/docs/configuration.md#encabezados-y-pies)
- [Cabeceras según el tipo de página](https://postext.dev/es/docs/configuration.md#elementos-de-texto)
- [Márgenes simétricos](https://postext.dev/es/docs/configuration.md#márgenes-simétricos-espejo)
- [Color del papel](https://postext.dev/es/docs/configuration.md#página)
- [Estilos de párrafo](https://postext.dev/es/docs/configuration.md#estilos-de-párrafo)
- [Tipos de recurso propios](https://postext.dev/es/docs/configuration.md#tipos-de-recurso)
- [Viudas, huérfanas y líneas cortas](https://postext.dev/es/docs/configuration.md#huérfanas-viudas-runts-y-reglas-de-cohesión)
- [Metadatos del documento](https://postext.dev/es/docs/document-format.md#frontmatter)
- [Paleta de color semántica](https://postext.dev/es/docs/configuration.md#paleta-de-colores)
- [Exportación a PDF](https://postext.dev/es/docs/configuration.md#generación-de-pdf)
- [Fuentes incrustadas en el PDF](https://postext.dev/es/docs/configuration.md#por-qué-un-proveedor-de-fuentes)
- [Figuras y tablas como recursos](https://postext.dev/es/docs/document-format.md#recursos)
- [Cabeceras por sección](https://postext.dev/es/docs/configuration.md#estilos-de-encabezado)

**La configuración de un vistazo**

- [`bodyText`](https://postext.dev/es/docs/configuration.md#texto-de-cuerpo), [`colorPalette`](https://postext.dev/es/docs/configuration.md#paleta-de-colores), [`footer`](https://postext.dev/es/docs/configuration.md#encabezados-y-pies), [`header`](https://postext.dev/es/docs/configuration.md#encabezados-y-pies), [`headingStyles`](https://postext.dev/es/docs/configuration.md#estilos-de-encabezado), [`headings`](https://postext.dev/es/docs/configuration.md#encabezados), [`layout`](https://postext.dev/es/docs/configuration.md#disposición), [`locale`](https://postext.dev/es/docs/configuration.md#separación-silábica), [`page`](https://postext.dev/es/docs/configuration.md#página), [`paragraphStyles`](https://postext.dev/es/docs/configuration.md#estilos-de-párrafo), [`resourceTypes`](https://postext.dev/es/docs/configuration.md#tipos-de-recurso)

**API**

- [`buildDocument`](https://postext.dev/es/docs/configuration.md#construir-un-documento), [`clearMeasurementCache`](https://postext.dev/es/docs/configuration.md#caché-de-medidas), [`decompressWoff2`](https://postext.dev/es/docs/configuration.md#proveedor-de-fuentes-en-el-navegador-fontsource--woff2), [`registerResourceImage`](https://postext.dev/es/docs/architecture.md#superficie-de-api), [`renderPageToCanvas`](https://postext.dev/es/docs/configuration.md#renderizar-una-página-a-un-bitmap), [`renderToPdf`](https://postext.dev/es/docs/configuration.md#generación-de-pdf)

**Tipografías**

- Crimson Pro (OFL-1.1), Cormorant Garamond (OFL-1.1), Cormorant SC (OFL-1.1)

## Elaboración

### 1 · El catalán por su código

El código es [la respuesta corta](#la-respuesta-corta) de arriba. `locale: 'ca'` elige los patrones catalanes, que desde postext 1.14 siguen el *Llibre d’estil* del IEC ([idiomas soportados](/es/docs/justification#idiomas-soportados)). Son los mismos patrones que usa TeX, con al menos dos letras a cada lado del corte en lugar de las tres del paquete de npm, así que *ter-ra*, *cai-xa* y *plu-ja* pueden cortarse. Las vocales en hiato no se separan (*cièn-cia*, *ca-mions*), pero una *i* entre vocales abre su sílaba (*fe-ia*). La *ela geminada* se corta entre sus dos *l*: la línea acaba en *titil-* y la siguiente empieza por *laven*, sin punto volado en ninguna de las dos, y Knuth–Plass mide la línea sin el punto.

![Página 27. Una página de texto del capítulo IV. A media altura, una línea acaba en titil- y la siguiente empieza por laven: el punto volado de titil·laven deja su sitio al guion.](https://postext.dev/cookbook/catalan-pocket-novella/es/p27.webp?v=348f4303)

*Página 27: titil·laven a final de línea, compuesta titil- | laven. Es la única l·l que cae a final de línea en estas 32 páginas.*

`compounds: false` deja enteros el verbo y su pronombre salvo por su propio guion (*arrossegar-* | *se*), que es el corte que prefiere el IEC ([separación silábica](/es/docs/configuration#separación-silábica)). Cuesta una línea floja: en la página 7, la línea anterior a *d’inaugurar-se* abre sus espacios hasta 1,94 veces, aunque `maxJustifyTracking: 10` absorbe el exceso en las demás.

### 2 · Una página de bolsillo de líneas enteras

```js
// script.js, líneas 37–44
const TRIM = { width: 120, height: 185 }; // mm
const [TOP, INNER, OUTER, LINES] = [18, 16, 15, 29];
const MEASURE = TRIM.width - INNER - OUTER; // 89 mm: about 64 characters of Crimson Pro
const page = {
  width: mm(TRIM.width), height: mm(TRIM.height), dpi: 150, backgroundColor: col('paper'),
  margins: { top: mm(TOP), bottom: mm(TRIM.height - TOP - LINES * LINE),
    left: mm(INNER), right: mm(OUTER), mirror: true }, // left is the inner margin on a recto
};
```

El margen inferior sale del formato, el margen superior y `LINES`, así que la caja admite 29 líneas de 14 pt y toda página llena acaba en la misma línea base. La medida de 89 mm da unos 64 caracteres de Crimson Pro. `mirror: true` mantiene el margen de 16 mm en el lomo en las dos páginas del pliego ([márgenes simétricos](/es/docs/configuration#márgenes-simétricos-espejo)).

### 3 · Una apertura para los cuatro capítulos

```js
// script.js, líneas 66–81
// # II {style="capitol" art="cap2"}: the art attribute names the picture's resource.
const PICTURE = { width: MEASURE, height: 12 * LINE }; // 89 × 59.3 mm: twelve lines, 3:2
const chapter = { id: 'capitol',
  breakBefore: { enabled: true, parity: 'any' }, // a chapter opens on the next page, either side
  marginBottom: pt(0), // the opener's own height already ends on a line
  advancedDesign: { enabled: true, minHeight: pt(16 * LEAD), slot: { elements: [
    { kind: 'image', id: 'art', resourceId: '{attr.art}',
      placement: at('container', 'top', 0, 0,
        { width: mm(PICTURE.width), height: mm(PICTURE.height) }) },
    // From the container, not the image: images reserve no height (opener-image-no-reserve).
    text('numeral', '{titleText}', DISPLAY, 21, at('container', 'top', 0, PICTURE.height + 4),
      { fontWeight: 600, color: col('vermell'), lineHeight: 1 }),
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.5), color: col('vermell'),
      placement: { ...at('container', 'top', 0, PICTURE.height + 13), size: { width: mm(8) } } },
  ] } },
};
```

`resourceId: '{attr.art}'` dibuja la imagen que nombra el título, así que `# II {style="capitol" art="cap2"}` abre el capítulo II bajo su propio grabado con el mismo estilo que los demás ([atributos de título](/es/docs/document-format#atributos-de-encabezado)). Las imágenes no reservan altura en una apertura, así que `minHeight` guarda dieciséis líneas: doce para el grabado, luego el número y su filete, y el texto empieza en la línea diecisiete de la página, igual en todos los capítulos.

### 4 · Cuatro páginas antes del capítulo I

```js
// script.js, líneas 85–114
const front = (id, elements, extra = {}) => ({ id, breakBefore: { enabled: true, parity: 'any' },
  marginBottom: pt(0), header: { elements: [] }, footer: { elements: [] }, ...extra,
  advancedDesign: { enabled: true, ...(extra.minHeight && { minHeight: extra.minHeight }),
    slot: { elements } } });
const FRONTIS = { width: MEASURE, height: MEASURE * 1.5 }; // mm: the engraving is 2:3
const headingStyles = [
  front('avantportada', [text('title', '{titleText}', DISPLAY, 19,
    at('container', 'top', 0, 9 * LINE), { italic: true, fontWeight: 500, lineHeight: 1 })]),
  front('frontis', [
    { kind: 'image', id: 'art', resourceId: '{attr.art}',
      placement: at('container', 'top', 0, 0,
        { width: mm(FRONTIS.width), height: mm(FRONTIS.height) }) },
    text('legend', '{titleText}', LABEL, 8.5, at('container', 'top', 0, FRONTIS.height + 5),
      { ...caps(8.5, 1.4, 'muted') }),
  ]),
  front('portada', [
    text('author', '{author}', LABEL, 10, at('container', 'top', 0, 5 * LINE),
      caps(10, 2.2, 'ink')),
    text('title', '{titleText}', DISPLAY, 34, at('container', 'top', 0, 8 * LINE),
      { italic: true, fontWeight: 500, color: col('vermell'), lineHeight: 1 }),
    text('subtitle', '{subtitle}', DISPLAY, 13, at('container', 'top', 0, 8 * LINE + 17),
      { italic: true, fontWeight: 500 }),
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.5), color: col('vermell'),
      placement: { ...at('container', 'top', 0, 8 * LINE + 29), size: { width: mm(14) } } },
  ]),
  // The note sits low on the verso behind the title, its label a line above it.
  front('nota', [text('label', '{titleText}', LABEL, 8.5, at('container', 'top', 0, 13 * LINE),
    caps(8.5, 1.4))], { minHeight: pt(15 * LEAD) }),
  chapter,
];
```

Cada pieza de los preliminares es un título de primer nivel con estilo propio: `# El transplantat {style="portada"}` imprime el autor y el subtítulo del frontmatter y el título como título. Cada estilo repite su salto de página, porque un estilo hereda el de su nivel, y vacía la cabecera y el pie, así que estas páginas no llevan cabeceras ni folios. El frontispicio cae en la página 2, par, y queda enfrente de la portada.

### 5 · El autor en la par, el título en la impar

```js
// script.js, líneas 118–132
const SHIFT = (INNER - OUTER) / 2; // mm: the text block's centre is off the page's centre
const head = (id, content, parity, edge, x, y = 10, pages = 'body') => ({
  kind: 'text', id, content, parity, pages, ...caps(8, 1.3, 'muted'),
  placement: at('page', edge, x, y),
});
const folio = { fontFamily: TEXT, fontWeight: 400, letterSpacing: pt(0), color: col('ink') };
const header = { elements: [
  { ...head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER), ...folio },
  head('verso-author', '{author}', 'even', 'top', -SHIFT),
  head('recto-title', '{title}', 'odd', 'top', SHIFT),
  { ...head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER), ...folio },
] };
const drop = (parity, x) => ({ ...head(`drop-${parity}`, '{pageNumber}', parity, 'bottom', x,
  -11, 'opener'), ...folio });
const footer = { elements: [drop('odd', SHIFT), drop('even', -SHIFT)] };
```

`parity` pone el autor en las páginas pares y el título en las impares; `pages: 'body'` las saca de las aperturas, donde va un folio al pie con `pages: 'opener'`. `SHIFT` centra cada cabecera sobre la caja, que está medio milímetro desplazada del centro de la página porque los dos márgenes no miden lo mismo.

### 6 · Cinco grabados, sin pie

```js
// script.js, líneas 136–152
const resourceTypes = [{ id: 'gravat', name: 'Gravat', namePlural: 'Gravats', shortLabel: 'Gravat',
  captionPrefix: '', numberingTemplate: '{n}', resetOn: 'never', counterFormat: 'decimal' }];
const PICTURES = { // id: [file, width, height, alt text in the book's language]
  frontis: ['frontis-1000.jpg', 1000, 1500, 'Un carrer de poble al capvespre. A la porta del forn, '
    + 'on cremen les flames, el forner, gras i somrient, amb davantal blanc i els braços creuats.'],
  cap1: ['cap1-1200.jpg', 1200, 800, 'De nit, el tren baixa cap a Barcelona: rengleres de fanals, '
    + 'la muntanya de Montjuïc i la lluna sobre el mar. Un pagès treu el cap per la finestreta.'],
  cap2: ['cap2-1200.jpg', 1200, 800, 'Un carrer de poble buit en una nit de tardor. Per la porta '
    + 'baixa de la ferreria, el manyà pica l’enclusa enmig d’una estrella d’espurnes.'],
  cap3: ['cap3-1200.jpg', 1200, 800, 'La Rambla a trenc d’alba des d’un balcó. Un fanaler apaga '
    + 'el gas amb la perxa; al fons, els pals dels vaixells del port.'],
  cap4: ['cap4-1200.jpg', 1200, 800, 'Capvespre als afores. Un vell de cabells blancs, assegut '
    + 'al caire del desmunt, mira la via del tren, que es perd recta fins a l’horitzó.'],
};
const resources = Object.entries(PICTURES).map(([id, [fileId, w, h, altText]]) => ({
  id, typeId: 'gravat', kind: 'bitmap', createdAt: 0, updatedAt: 0, altText,
  bitmap: { fileId, format: 'jpeg', width: w, height: h } }));
```

Las imágenes son de un tipo de recurso con `captionPrefix` vacío, así que nada las numera ni las rotula. Cada una se declara con su tamaño en píxeles y se carga con su identificador de archivo antes de componer. Antes de guardarlas se llevó su blanco al crema de la página y su negro a la tinta del texto, así que se imprimen como parte de la página y no como fotos pegadas encima.

## La receta completa

Un solo archivo, compuesto a partir de la carpeta de la receta con el texto de ejemplo y el kit común del Recetario ya incluidos; construye su propia página. Para ejecutarlo, ponlo en un `<script type="module">` de una página vacía o pégalo en el panel JS de un pen nuevo de CodePen (como módulo). Importa postext desde esm.sh, así que no hay nada que instalar ni compilar.

- Carpeta de la receta: https://github.com/drnachio/postext/tree/main/cookbook/catalan-pocket-novella

### script.js

```js
// ═══ Postext Cookbook · Nº 104 · A Catalan novella, hyphenated by the IEC rules ═══════
// https://postext.dev/en/cookbook/catalan-pocket-novella
// Code: MIT · Text: N. Oller, El transplantat, 1928 (PD, Viquitexts) · Pictures: diffusion models
// Fonts: Crimson Pro, Cormorant Garamond, Cormorant SC (SIL OFL 1.1) · Needs postext ≥ 1.14.0
import { buildDocument, renderPageToCanvas, clearMeasurementCache, registerResourceImage }
  from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';

const LANG = 'es'; // @lang: the language of the sample document (the sample is Catalan in both)
const RECIPE = 'catalan-pocket-novella';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
const palette = {
  ink: '#2a2320', // text: a warm near-black, the pictures' ink too
  vermell: '#953222', // the one accent: chapter numerals, rules, the title page's title
  muted: '#6e6359', // running heads, the edition's note, the colophon
  paper: '#f5efe1', // a cream book paper, the pictures' white
};
// Each colour names its palette entry and carries its hex (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' } })),
  { id: 'main-color', name: 'vermell (defaults)', value: { hex: palette.vermell, model: 'hex' } },
];
const [TEXT, DISPLAY, LABEL] = ['Crimson Pro', 'Cormorant Garamond', 'Cormorant SC'];
const [BODY, LEAD] = [10.4, 14]; // pt: body and leading, the pitch of every vertical measure
const LINE = (LEAD * 25.4) / 72; // mm: one line, 4.94
const at = (to, edge, x = 0, y = 0, size) => ({ anchor: { to, edge },
  offset: { x: mm(x), y: mm(y) }, ...(size && { size }) });
const text = (id, content, fontFamily, size, placement, extra = {}) => ({ kind: 'text', id,
  content, fontFamily, fontSize: pt(size), color: col('ink'), align: 'center',
  overflow: 'wrap', placement, ...extra }); // wrap, never '…' (gotcha: overflow-ellipsis-default)
const caps = (size, tracking, color = 'vermell') => ({ fontFamily: LABEL, fontSize: pt(size),
  fontWeight: 600, letterSpacing: pt(tracking), color: col(color) });

// #region page: a 12 × 18.5 cm pocket book; the text block holds 29 whole lines
const TRIM = { width: 120, height: 185 }; // mm
const [TOP, INNER, OUTER, LINES] = [18, 16, 15, 29];
const MEASURE = TRIM.width - INNER - OUTER; // 89 mm: about 64 characters of Crimson Pro
const page = {
  width: mm(TRIM.width), height: mm(TRIM.height), dpi: 150, backgroundColor: col('paper'),
  margins: { top: mm(TOP), bottom: mm(TRIM.height - TOP - LINES * LINE),
    left: mm(INNER), right: mm(OUTER), mirror: true }, // left is the inner margin on a recto
};
// #endregion

// #region answer: Catalan by its own code: l·l breaks as il- | lusió, no word splits its hiatus
const LOCALE = 'ca'; // the exact bundled code: 'ca-ES' resolves to it too (postext ≥ 1.14)
const bodyText = { // config().bodyText
  fontFamily: TEXT, fontSize: pt(BODY), lineHeight: pt(LEAD), color: col('ink'),
  boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
  firstLineIndent: mm(4.5), indentAfterHeading: false,
  // The defaults justify, hyphenate in config().locale and break by Knuth–Plass. With 'ca'
  // the dictionary follows the IEC: two letters on each side (ter-ra, cai-xa), no split in
  // a hiatus (cièn-cia), none after an apostrophe (s’ha-via), and l·l breaks with the hyphen
  // in place of the dot. compounds: false keeps arrossegar-se whole but for its own hyphen,
  // the break the IEC prefers for a verb and its pronoun (Llibre d’estil, VI 2.5).
  hyphenation: { compounds: false },
  // Spaces past 1.6× take up to 10‰ of tracking instead; a last line under 22 characters is
  // a runt. A line that must keep d’inaugurar-se whole still opens to 1.94× on page 7.
  maxWordSpacing: 1.6, maxJustifyTracking: 10, runtMinCharacters: 22,
};
// #endregion

// #region opener: the chapter's engraving, then its numeral, in one style for all four
// # II {style="capitol" art="cap2"}: the art attribute names the picture's resource.
const PICTURE = { width: MEASURE, height: 12 * LINE }; // 89 × 59.3 mm: twelve lines, 3:2
const chapter = { id: 'capitol',
  breakBefore: { enabled: true, parity: 'any' }, // a chapter opens on the next page, either side
  marginBottom: pt(0), // the opener's own height already ends on a line
  advancedDesign: { enabled: true, minHeight: pt(16 * LEAD), slot: { elements: [
    { kind: 'image', id: 'art', resourceId: '{attr.art}',
      placement: at('container', 'top', 0, 0,
        { width: mm(PICTURE.width), height: mm(PICTURE.height) }) },
    // From the container, not the image: images reserve no height (opener-image-no-reserve).
    text('numeral', '{titleText}', DISPLAY, 21, at('container', 'top', 0, PICTURE.height + 4),
      { fontWeight: 600, color: col('vermell'), lineHeight: 1 }),
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.5), color: col('vermell'),
      placement: { ...at('container', 'top', 0, PICTURE.height + 13), size: { width: mm(8) } } },
  ] } },
};
// #endregion

// #region front: half-title, frontispiece, title page and the edition's note, one page each
const front = (id, elements, extra = {}) => ({ id, breakBefore: { enabled: true, parity: 'any' },
  marginBottom: pt(0), header: { elements: [] }, footer: { elements: [] }, ...extra,
  advancedDesign: { enabled: true, ...(extra.minHeight && { minHeight: extra.minHeight }),
    slot: { elements } } });
const FRONTIS = { width: MEASURE, height: MEASURE * 1.5 }; // mm: the engraving is 2:3
const headingStyles = [
  front('avantportada', [text('title', '{titleText}', DISPLAY, 19,
    at('container', 'top', 0, 9 * LINE), { italic: true, fontWeight: 500, lineHeight: 1 })]),
  front('frontis', [
    { kind: 'image', id: 'art', resourceId: '{attr.art}',
      placement: at('container', 'top', 0, 0,
        { width: mm(FRONTIS.width), height: mm(FRONTIS.height) }) },
    text('legend', '{titleText}', LABEL, 8.5, at('container', 'top', 0, FRONTIS.height + 5),
      { ...caps(8.5, 1.4, 'muted') }),
  ]),
  front('portada', [
    text('author', '{author}', LABEL, 10, at('container', 'top', 0, 5 * LINE),
      caps(10, 2.2, 'ink')),
    text('title', '{titleText}', DISPLAY, 34, at('container', 'top', 0, 8 * LINE),
      { italic: true, fontWeight: 500, color: col('vermell'), lineHeight: 1 }),
    text('subtitle', '{subtitle}', DISPLAY, 13, at('container', 'top', 0, 8 * LINE + 17),
      { italic: true, fontWeight: 500 }),
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.5), color: col('vermell'),
      placement: { ...at('container', 'top', 0, 8 * LINE + 29), size: { width: mm(14) } } },
  ]),
  // The note sits low on the verso behind the title, its label a line above it.
  front('nota', [text('label', '{titleText}', LABEL, 8.5, at('container', 'top', 0, 13 * LINE),
    caps(8.5, 1.4))], { minHeight: pt(15 * LEAD) }),
  chapter,
];
// #endregion

// #region heads: the author on the verso, the title on the recto; a drop folio on openers
const SHIFT = (INNER - OUTER) / 2; // mm: the text block's centre is off the page's centre
const head = (id, content, parity, edge, x, y = 10, pages = 'body') => ({
  kind: 'text', id, content, parity, pages, ...caps(8, 1.3, 'muted'),
  placement: at('page', edge, x, y),
});
const folio = { fontFamily: TEXT, fontWeight: 400, letterSpacing: pt(0), color: col('ink') };
const header = { elements: [
  { ...head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER), ...folio },
  head('verso-author', '{author}', 'even', 'top', -SHIFT),
  head('recto-title', '{title}', 'odd', 'top', SHIFT),
  { ...head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER), ...folio },
] };
const drop = (parity, x) => ({ ...head(`drop-${parity}`, '{pageNumber}', parity, 'bottom', x,
  -11, 'opener'), ...folio });
const footer = { elements: [drop('odd', SHIFT), drop('even', -SHIFT)] };
// #endregion

// #region pictures: five engravings, unnumbered and uncaptioned, at their pixel size
const resourceTypes = [{ id: 'gravat', name: 'Gravat', namePlural: 'Gravats', shortLabel: 'Gravat',
  captionPrefix: '', numberingTemplate: '{n}', resetOn: 'never', counterFormat: 'decimal' }];
const PICTURES = { // id: [file, width, height, alt text in the book's language]
  frontis: ['frontis-1000.jpg', 1000, 1500, 'Un carrer de poble al capvespre. A la porta del forn, '
    + 'on cremen les flames, el forner, gras i somrient, amb davantal blanc i els braços creuats.'],
  cap1: ['cap1-1200.jpg', 1200, 800, 'De nit, el tren baixa cap a Barcelona: rengleres de fanals, '
    + 'la muntanya de Montjuïc i la lluna sobre el mar. Un pagès treu el cap per la finestreta.'],
  cap2: ['cap2-1200.jpg', 1200, 800, 'Un carrer de poble buit en una nit de tardor. Per la porta '
    + 'baixa de la ferreria, el manyà pica l’enclusa enmig d’una estrella d’espurnes.'],
  cap3: ['cap3-1200.jpg', 1200, 800, 'La Rambla a trenc d’alba des d’un balcó. Un fanaler apaga '
    + 'el gas amb la perxa; al fons, els pals dels vaixells del port.'],
  cap4: ['cap4-1200.jpg', 1200, 800, 'Capvespre als afores. Un vell de cabells blancs, assegut '
    + 'al caire del desmunt, mira la via del tren, que es perd recta fins a l’horitzó.'],
};
const resources = Object.entries(PICTURES).map(([id, [fileId, w, h, altText]]) => ({
  id, typeId: 'gravat', kind: 'bitmap', createdAt: 0, updatedAt: 0, altText,
  bitmap: { fileId, format: 'jpeg', width: w, height: h } }));
// #endregion

const paragraphStyles = [
  { id: 'nota', fontSize: pt(9), lineHeight: pt(LEAD * 0.9), color: col('muted'),
    italicColor: col('muted'), firstLineIndent: mm(4.5) },
  { id: 'fi', fontFamily: LABEL, fontSize: pt(9), letterSpacing: pt(2), color: col('vermell'),
    textAlign: 'center', firstLineIndent: pt(0), marginTop: pt(LEAD) },
  { id: 'colofo', fontSize: pt(7.5), lineHeight: pt(10), color: col('muted'),
    italicColor: col('muted'), textAlign: 'center', firstLineIndent: pt(0),
    marginTop: pt(2 * LEAD) },
];

const config = () => ({ // a factory: the engine caches resolved configs per object
  locale: LOCALE,
  resourceTypes, colorPalette, page, bodyText, paragraphStyles, headingStyles, header, footer,
  layout: { layoutType: 'single' },
  headings: { // the designs paint every title; this keeps Open Sans unloaded
    fontFamily: DISPLAY, fontWeight: 600,
    // Any headings object drops the H1 break (gotcha: headings-drop-h1-break): restated.
    levels: [{ level: 1, breakBefore: { enabled: true, parity: 'any' } }],
  },
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
// The front matter, then the four chapters; chapter I in two files (2,500 words each at most).
const markdown = [String.raw`---
title: "El transplantat"
subtitle: "De «Croquis del natural»"
author: "Narcís Oller"
---

# El transplantat {style="avantportada"}

# El Forner de les Monges {style="frontis" art="frontis"}

# El transplantat {style="portada"}

# Nota a aquesta edició {style="nota"}

:::paragraphs{style="nota"}
Aquest text reprodueix «El transplantat» tal com Narcís Oller (1846–1930) el va revisar per al volum VI de les seves *Obres completes* (Barcelona, 1928), ja en l’ortografia de Pompeu Fabra. El conte havia aparegut el 1879 a *Croquis del natural*, el seu primer recull.

S’hi han esmenat unes quantes errades de la impressió i, com fan les edicions d’avui, s’hi han suprimit els signes d’interrogació i d’exclamació inicials i els diàlegs s’hi marquen amb el guió llarg. Les cursives són de l’autor. Les il·lustracions s’han fet per a aquesta edició.
:::
`, String.raw`# I {style="capitol" art="cap1"}

Daniel (o el Forner de les Monges, si el voleu conèixer per son nom popular), lliurat amb la major fe al treball des de molt jove, veient com ses gotes de suor anaven a parar al calaix convertides en monedes d’argent i d’or que la muller guardava, no com a instruments de canvi, sinó com a medalles honorífiques, era feliç en son poble, i res del món no li pareixia tan bonic.

Degut a sa felicitat impertorbable, o tal volta perquè la farina alimenta fins amb ses flaires, havia conservat sempre una gran robustesa, i s’havia tornat tan desmesuradament panxut i gros que quasi feia pena. I dic quasi perquè el color sa de son rostre, la calma de sa mirada i el somrís permanent de sos llavis revelaven tanta satisfacció i salut que era impossible conservar el sentiment de compassió que l’aspecte de la deformitat vos hagués inspirat al primer cop d’ull.

Res de descontentament o disgust de la vida, res de malestar: la felicitat del Forner de les Monges era proverbial en el poble i en tota l’encontrada.

És clar que, en els seixanta anys que ja tenia, no havia pogut lliurar-se d’aquelles desgràcies que persegueixen a tot mortal, com eren les pèrdues de família, entre les quals hi havia la molt dolorosa de sa muller; però totes les penes, fins les més fortes, eren poc duradores per a en Daniel, gràcies a sa pròpia pasta, molt millor que la dels pans que enfornava, i gràcies també a la *filosofia* i *religió* de què en casos consemblants sabia revestir-se.

Tenia una paraula per a tothom, i no hi havia qui passés per davant seu sense donar-li el bon dia o preguntar-li si *prenia la fresca*, o *el sol*, o *si feia temps*.

La popularitat, que capgira tanta gent, era la flaca del Forner de les Monges, i a satisfer-la posava tota la voluntat, procurant-se les simpaties de pobres i rics, amanyagant a tothom i no ofenent a ningú.

Això, que no deixa d’ésser difícil en totes les esferes socials, ho és en grau superlatiu en un poblet; però, com que l’instint tot ho pot i en Daniel en tenia per a això i portava a més a més la patent d’inofensiu, assolí fàcilment el que volia. Així aconseguí *fer-se* amb el doctor Andreu, home sapientíssim del poble, que deixava bocabadats tots sos veïns amb les descripcions sobre història, geografia o astronomia que els endreçava poc després d’haver-les après ell de cor, i amb els proverbis i *sentències* que, com altre Sanxo Pança, deixava caure pel descosit.

En una de ses converses, que, sens dubte pel que eren instructives, qualificava el doctor Andreu de conferències, digué un dia que «en el poble curt l’home savi es torna ruc».

Ignoro si el doctor, dient això, pensava haver degenerat, o si considerava que sos oients havien pogut ésser savis algun dia. Tampoc no sé que en Daniel tingués motiu de creure’s mal aprofitat en el poble on havia nascut i era feliç tot fent pans i acabalant or. El que sé és que l’esperit humà presenta rareses absurdes i que, per una d’aquestes, tal volta, aquella sentència donà molt que pensar al nostre senzill forner, qui des de llavors afalagà el desig de sortir un jorn de son poble per, com diríem nosaltres, descobrir nous horitzons. Sé, per fi, que, gràcies a aquest fenomen, en Daniel veié amb gust marxar son fill gran a Barcelona per aprendre l’ofici de perruquer, malgrat la pena que la separació per una part, i per una altra el rompiment del rutinari costum de fer de l’hereu el mateix que fou son pare, podien causar-li. Qui sap si el bon home creia que a son fill podria entrar-li la ciència pels dits tot pentinant algun acadèmic o algun senyoràs d’aquells que són autoritats i llueixen creus i bandes!

Fos el que fos, el fill del Forner de les Monges va fer son aprenentatge a Barcelona, i, a l’època a què ens referim, obrí *saló* pel seu compte, invertint en la instal·lació i ornament bona part de les medalles que tant havia guardat sa mare.

El plantar *saló* tenia massa importància perquè es portés a efecte sense la presència del pare; i, com era natural, el fill fou a buscar-lo i se l’emmenà a Barcelona, on no havia estat mai.

El *saló* era a la Rambla, i l’endemà de l’arribada havia d’inaugurar-se solemnement. Era no sols bonic, sinó verament luxós. En Miquelet, que en el món industrial portava com a nom de batalla el de *Rumbós*, tenia més lletra menuda que son mestre Fígaro. Havia comprès l’època: sabia com són avui reproductives certes despeses, i no volia perdonar-ne cap. Així, no satisfet d’haver posat un *saló* com cap altre no n’hi hagués a Espanya, cregué convenient d’armar rebombori en els diaris, i, seguint l’exemple d’altres empreses, resolgué dar un dinar als representants de la premsa, com ara es diu.

Ell i son pare arribaren a Barcelona la nit abans. Ja hem dit que en Daniel no hi havia estat mai, i falta afegir que encara no havia vist cap ciutat d’igual importància. Judiqueu de ses impressions, cent voltes majors que les que experimenta en tals casos una criatura, a la qual manca sempre suficient judici per a fer-se càrrec del valor absolut de les novetats, i coneixement cabal d’allò que té vist per a comparar-ho amb el que veu per primera vegada.

Pocs quilòmetres abans d’arribar ja no sabia en Daniel dar-se compte del que li passava contemplant les llargues rengleres de fanals que unes voltes veia senceres i altres cops trencades, segons les corbes o nivells que el tren travessava. Cap barceloní no ha vist tan gran la ciutat com apareixia als ulls del foraster. El fill d’aquest coadjuvava en gran manera a engrandir aquesta impressió.

—Veieu, pare, aquella renglera de mà esquerra per on corren llumets de colors? És el passeig de Gràcia. Les altres transversals són carrers de l’*Ensanxe*… Mireu a la dreta: tot aquell rengle tan llarg de fanals és un camí: el camí de Sans… Allà dalt hi ha Montjuïc… El veieu, ara que la lluna ha sortit d’aquells nuvolots?… No distingiu, dessota, allà lluny, una gran resplendor? És el mar… Ara que hi toca la lluna, mireu que és bonic! Talment sembla un bassal de *plata* bullent!
En Daniel guaitava a la dreta, a l’esquerra, amunt i avall, a terra i al cel, i quasi es marejava. Una paret, una casa, un desnivell de la via, la mateixa rapidesa del tren, li feien pampalluguejar amb tan estranya inquietud els llums, que a voltes en confonia amb estrelles i arribava a perdre l’esma de cel i terra.

—Veniu a aquest altre cantó. Si se’n veuen, ara, de fanals!

En Daniel feia uns ulls com unes taronges.

—Si fos de dia us ensenyaria Pedralbes, Sarrià, Vallvidrera, Sant Gervasi, Gràcia; un caseriu que no s’acaba mai… Ja ens hi anem acostant… Hola! el tren de Sarrià!… Mireu aquest talús que encreua la via: ara ens passarà un altre tren per sobre.
El forner, prou marejat, quasi no entenia com podria passar per sobre sense aixafar-los, i estigué a punt de senyar-se.

Per fi la màquina xiulà, i, ja parat el tren, aparegueren els mossos de l’estació a recollir els bitllets, enlluernant amb llurs brillants i inquietes llanternes als viatgers.

Mentrestant, en Daniel, seguint la indicació de son fill, contemplava la feixuga mole de la Universitat, que, embolcallada en l’ombra, li semblava un monstruós parapet flanquejat per dues grans torres.

—Heu vist aquella punxa tan prima de més avall? És un convent de monges: les *Adoratrices*. Hem passat també les *Arrepentides*, Sant Joan de Déu i altres convents. Tot això n’és ple. Els frares i les monges ho entenen com sempre: aquest és el cantó de migjorn… Ja tornem a caminar: ara la màquina ens empeny per darrera. Alceu-vos. Veieu aquestes quatre rengleres de llums? Són la Gran Via: un carrer que tindrà quatre hores.

Els frens començaren a grunyir, les rodes a arrossegar-se peresosament.

—Ja hem arribat —digué el Rumbós tot ficant-se la gorra a la butxaca i posant-se un bolet molt elegantó—. Amaniu-vos i *tentu* amb el rellotge, que per aquí corren uns alets…

Impossible de descriure minuciosament cada una de les impressions que el forner experimentà tot travessant la Rambla. Els fanalets de color dels cotxes que es movien en direccions diverses, la gent que ocupava l’imperial dels tramvies, els dolls de claror que sortien de les botigues, els rengles de persones deturades davant certs aparadors, la multitud que passejava per sota els arbres, els fanals d’anunci; tot oferia a sos ulls estranya novetat i acabava de marejar-lo.

Barcelona li pareixia un cel per sa il·luminació, un infern per son moviment. No sabia si li agradava o no: desitjava arribar a casa, i al mateix temps hauria volgut no arribar-hi massa aviat.

Aquella vacil·lació d’esperit el deixà fatigat. Per això quan, després d’haver sopat, es va veure al llit, l’home respirà amb satisfacció i tancà els ulls per adormir-se. Però era inútil: son agitat esperit feia de les seves, més entestat que mai a no descansar. Si obria els ulls, li pareixia distingir, entre la fosca, que els cantons superiors i inferiors de la cambra s’enfonsaven o s’enlairaven com els d’una cabina d’embarcació lliurada a la inestabilitat perpètua de les ones. Si els tancava, veia un eixam de llumenetes de tots colors voleiar com una immensa constel·lació deslligada de tota llei física. En Daniel no sabia què fer en aquell llit de turment, i més d’una vegada pensà a alçar-se. Per fi l’ensopiment de la son pogué més que els nervis, la cambra restà embolcada per la negror impenetrable i quieta de la fosca, aquelles llumenetes s’aprimaren fins a formar migrades ratlles d’esblanqueïda resplendor, i l’esperit d’en Daniel davallà pausadament per la costa suau de la son, on es perd tot sentit d’existència.
`, String.raw`L’endemà no fou dia de menys emocions. Matí i tarda passejà per Barcelona, aturant-se a cada pas davant els aparadors i a respectuosa distància dels cotxes que veia córrer envers ell.

La vista del port, amb son bosc de pals i cordam, per entre els quals a penes s’albirava la interminable bellugadissa de la Riba; els cotxes de luxe, amb llurs troncs arrogants i llurs galonejats lacais; la plaça Reial, amb sa agradable simetria i sa netedat; la Rambla, amb son alegre brogit; i el passeig de Gràcia, ple també de moviment i vida; tenien embadalit en Daniel.

Però, sumades totes les impressions del dia, no arribaven a poder ésser comparades amb les que li proporcionà el convit de la vesprada.

En Daniel no cabia a la pell en contemplar son fill rodejat dels representants de la premsa, que, passejant els ulls de les parets a la taula tot esperant el sopar, omplien d’elogis l’amfitrió per la riquesa i el gust desplegats en l’establiment, i li auguraven un gran esdevenidor. Els miralls, els llums i els tocadors, i, sobretot, els olis, pomades, essències i cosmètics de procedència anglesa (una novetat que el nou perruquer presentava per millor acreditar-se), eren minuciosament examinats i lloats molt calorosament.

El Rumbós no ho podia deixar d’ésser en aquell moment: amb un gest expressiu, feia que un dels aprenents anés empaquetant les ampolletes i pots que eren més elogiats, mal que l’armari perdés tota aquella simetria i abundància que a primera hora oferia.

El Forner de les Monges quasi plorava de goig. «Son fill era un savi. Bé deia el doctor Andreu que “en el poble curt l’home savi es torna ruc”. Quan, en Miquelet, hauria tractat tanta gent de lletres ni après a viure entre ells amb tanta confiança i intimitat?» La perspicàcia del vell, baldament poc conreada, començava a veure en allò cert corrent secret d’utilitat que son fill explotava amb tota la malícia refinada d’un cortesà o, si voleu, d’un industrial d’avui. El pare tot just naixia i, com diu l’endevinalla del foc, el fill ja saltava pels terrats».

Durant el sopar (que, com podeu comprendre, era confiat a un *intel·ligent* restaurant per més que tingués lloc a la mateixa perruqueria) la conversa fou poca al principi, s’animà un xic quan les viandes i els vins començaren a fer bullir els cervells, i en aparèixer el xampany esclatà amb tota sa força.

El perruquer, sense immutar-se i com si hi estigués ja avesat, alçà la copa i pronuncià un discurs, en el qual «la premsa, la veu de l’opinió pública, la gran palanca del progrés», quedava col·locada per damunt totes les potestats de la terra.

—Perquè —deia el perruquer— ningú com vosaltres s’associa i protegeix totes les empreses, per humils que siguin. La premsa, de la mateixa manera assisteix als convits de *palacio* que als d’un modest industrial com jo: jamai desdenya ningú, i la mateixa confraternitat que guarda entre tots sos representants, sense distinció de partits, dispensa a totes les classes socials.

Els comensals aplaudiren estrepitosament. El forner plorava i reia alhora.

Llavors començaren els brindis dels periodistes. L’un donà, en nom de la premsa (no barcelonina ni espanyola, sinó del món), les gràcies al perruquer pels elogis que havia prodigat a «la més gran institució dels nostres temps». Un altre procurà demostrar que no hi ha acte humà, per insignificant que sigui, que pugui deixar d’ésser útil a l’obra del progrés; i afegí que per això la premsa, «com havia dit molt bé l’amo de l’establiment», no es desdenya d’associar-se a la més petita de les empreses, protestant, però, que pogués qualificar-se de petita la d’una perruqueria, on l’art de la decoració, que purifica el gust, i l’art de tallar els cabells, que rodoneja i perfecciona el bust de l’home civilitzat fins a impedir, ja al primer cop d’ull, que es confongui amb el salvatge, corren units.

El cervell del Forner de les Monges estava a punt d’esclatar.

Un altre, amb dades estadístiques que duia apuntades a la memòria, provà que en el món civilitzat no hi ha encara sinó un perruquer per cada cinquanta mil homes, i que, si bé a Barcelona no existia tal desproporció, podia, de totes maneres, assegurar-se que el servei no s’havia explotat prou i que, per tant, la nova perruqueria tindria aviat bona parròquia.

Aquesta última paraula inspirà un altre brindis, en el qual es sostenia que l’art de tallar i pentinar cabells no era indiferent al mateix catolicisme, com ho provava la diferència de pentinats que usaven els frares, i els edictes d’excomunió que en temps antics havia hagut d’expedir l’Església contra certes modes exagerades que, a Anglaterra i altres nacions, daven a l’home i a la dona l’aspecte d’éssers monstruosos. De tot això deduïa l’orador que la inauguració d’una perruqueria no era, en realitat, cosa desposseïda d’importància ni d’interès per a tots aquells que, estudiant l’essència de la civilització, no creuen que sigui element de progrés tota cosa nova, sinó tota cosa bona.

Ja no dic ara el que passava per l’enteniment del bon forner. «Mai, mai no ho hauria somiat! Estava rodejat de savis molt més grans que el doctor Andreu!… I qui els havia aplegat, qui els obsequiava i en rebia elogis, era en Miquelet! El seu fill, Senyor, el seu fill!»

Una vaga esperança d’ésser conegut entre aquells senyors tan il·lustrats llampeguejà per l’esperit d’en Daniel. Aquell espectacle de barreja i confraternitat entre persones que ell veia amb els ulls d’una il·lusió daurada el transportaven a l’esfera dels entusiasmes més candorosos. Remembrà la *sentència* del doctor Andreu, i mai no la veié tan justa com aleshores. Tots els veïns del seu poble li semblaren dignes de la major compassió. «Viure a Barcelona era viure.» Allò que havia vist de dia, el que veia ara, era un somni que convidava a fer-ne una realitat perpètua. «Estava resolt: a ses velleses s’havia de fer barceloní.» Li entrà l’afany de viure entre *persones*, i, sentint-se com superior a si mateix, volgué brindar.

El Rumbós, que era a l’altre cap de taula, dissimulà amb estudiat somrís el temor que son pare fes alguna atzagaiada. Totes les veus callaren; tots els ulls cercaren, per sobre les estovalles, un punt en què fixar-se. Aquest silenci desconcertà el nou orador ja abans que desclogués els llavis; però el compromís era contret, el cor volia parlar.

—Senyors —digué, per fi, en Daniel—, estic molt agraït… però molt… i… brindo per la salut de vostès i de totes les seves famílies… i… no dic res més perquè no puc.

I caigué aplomat a la cadira, plens de llàgrimes els ulls, mentre l’auditori en pes l’eixordava amb sos aplaudiments tot mossegant-se els llavis.

Dos o tres discursos més, d’altres tants periodistes, coronaren aquell monument d’oratòria; i, abans d’aparèixer el cafè, vingué la gran sorpresa que el Rumbós havia preparat amb el major secret.

Era, aquesta, una mena d’urna de metall blanc, tapada i d’elegant forma etrusca. Col·locada al mig de la taula, tothom l’havia presa per un objecte d’adorn destinat a augmentar la magnificència de l’agençament entre les flors, els canelobres, les copes, les ampolles i els coberts, on s’emmirallava la llum de gas, desfent-se en mil colors, si viu i inquiet l’un, més encara l’altre. Però no era aquesta la destinació de la tal urna.

En Miquelet s’alçà, pitjà el botó amb què acabava la figurada tapa, i per cent brolladors (de tan petits, invisibles) eixiren, impel·lits amb força, altres tants raigs d’aromàtica essència, que refrescaren aquella atmosfera i ompliren el saló de delitosa fragància.

No cal pas dir si el truc fou celebrat: tot el concurs picava de mans, rabejant-se de gust, amb la fruïció sibarítica d’un romà del baix imperi, dins la suau ruixada d’aquella boira enervadora.

La flaire de les tasses de cafè s’enlairava vaporosa fins al sostre amb els cercles de fum dels cigars; les espelmes feien una llum més blanca i prima; les bombes de gas prenien un to blavenc; i un somni de tendresa i benestar embadalia els esperits.

L’endemà, els diaris de la ciutat, sense exceptuar-ne ni un, pintaven la nova perruqueria com un palau de fades, elogiaven totes les innovacions importades pel Rumbós, i daven compte dels industrials que havien intervingut en el decorat, encara que en això alguns incorrien en errades o mala atribució de noms.
`,
  String.raw`# II {style="capitol" art="cap2"}

Les impressions abans descrites, i la sort que en Miquelet estava fent, engendraren en en Daniel el desig de llogar el forn i traslladar-se a Barcelona a passar els darrers anys de sa vida. Prou havia treballat, ja, perquè ningú pogués criticar que cap a ses velleses es lliurés al descans. Els cabals li ho permetien, i, de més a més, son fill li oferia, de bon grat, plat a taula i un bon llit.

Amb tals propòsits, se’n tornà en Daniel al poble on tants anys havia viscut feliç. Si no hi hagués arribat amb la ferma resolució de deixar-lo, potser l’hauria acabat de resoldre el trist desencant que sofrí en petjar novament aquells carrers.

Era a la tardor, i el nostre home hi arribava ja de vespre.

Els que sols han vist els pobles a l’estiu, no poden judicar de la tristor que desperten a entrada d’hivern, i de com aquesta tristor creix quan el sol s’és amagat darrera les muntanyes.

La primera oreneta que s’allunya és, per als pobles, la precursora de la soledat i la quietud. Darrera d’ella segueixen les altres, deixant buits els nius, abans calentons i animats, que el vent i la pluja esgrunaran. Els masos queden barrats; els pocs cotxes i tartanes de luxe passen a ensopir-se en els magatzems, on ben aviat els cobrirà una capa de pols i de teranyines; les diligències no poden donar l’abast a endur-se’n els forasters que durant tres mesos han animat el poble i sos entorns. S’han acabat les fontades, les balles, les festes majors, el brogit i l’alegria: tota la vida es reconcentra dins les cases, i ben aviat carrers i camins seran del domini exclusiu del vent.

Quan el Forner de les Monges entrà en son carrer, el crepuscle havia terminat. Ja no hi trobà els rotllets de gent que pocs jorns abans hi prenia la fresca, ni veié oberta la botiga del fuster amb les peces de treball endreçades, el sòl regat i el menjador tribuna que en el fons s’albirava a la tèrbola claror d’un llum de petroli a mitja fosca.

Tot era tancat, no passava ànima vivent, i sobre les desiguals teulades s’estenia, com tenderol esqueixalat, una llenca de cel blau turquí sembrat d’estrelles. En mig d’aquella soledat sentí xisclar la garlopa, grunyir la serra i copejar el martell del fuster.

Els sorolls arribaven esmorteïts al cor d’en Daniel: per una vaga associació d’idees, cregué que el fuster feia una caixa de mort.

Deu passos més amunt dringava, com campaneta llunyana, un martell sobre l’enclusa, i, per la portella d’una escaleta, en Daniel veié son veí, el manyà, entre la resplendor rogenca d’una estrella d’espumes que, al compàs dels cops, estirava o encongia ses punxes com eriçó de foc en turment.

En Daniel va empènyer la feixuga porta de sa casa, davant la qual se li mostrà l’encesa boca del forn, plena de brilladores flames que, saltant pels aires i llepant l’estreta porta de llur presó, semblaven celebrar la benvinguda de l’amo. La fleca s’omplí d’alegria. Tothom deixà la feina voltant en Daniel, que no sabia per on girar-se ni com respondre a les cent preguntes que li endreçaven alhora.

Quan arribà al pis i guaità a la finestra com portat d’un tèrbol desig de comparació, el carrer oferia la més trista soledat. El vent s’arrossegava per terra empenyent polsoses i alades fantasmes, la fosca feia invisibles les fatxades, i sols el cant quasi perdut d’una mare que bressava son infant, barrejat amb el no menys frèvol d’unes cosidores i el llarg rebombori d’un garranyic de cancell, trencava aquella quietud de cementiri.

En Daniel es sentí contristat. Tot Barcelona li bullia en el cervell, i debades cercava en aquell fosc carrer els atractius que hi havia trobat durant tota sa vida.

—*Al menos* fes lluna! —deia interiorment. I era que enyorava tots aquells rius de claror que en arribar la nit inundaven la ciutat; la resplendent claredat de la perruqueria; la il·luminació *veneciana* dels cotxes i tramvies que des del balcó de casa son fill veia passar corrent i fent capritxoses giragonses.

No cal estar-hi avesat de molt temps: la bona llum artificial s’enyora així que els ulls n’han fruït algun dia. Penseu, doncs, el que devia passar al nostre forner, que s’havia vist en un establiment on, des del negre barret de copa del parroquià fins al mateix mosaic del sòl, tot lluïa, tot reflectia la clara llum de cent becs de gas.

Cregué ja impossible de viure alegre en aquell poble tan fosc, tan brut, tan solitari, i recordà el sopar de la premsa, el proverbi del doctor Andreu, els avenços d’en Miquelet, els oferiments que aquest li feia, la promesa d’acceptar-los, els motius que per a descansar tenia… i, tancant la finestra, es ficà al llit, més resolt que mai a abandonar el seu poble.
`, String.raw`# III {style="capitol" art="cap3"}

Fàcil fou a en Daniel d’arrendar el forn: ja no tant de deixar aquelles parets que, després de sos entusiasmes de moment i en començar a esvair-se-li el record de Barcelona, li tornaven a guanyar el cor revestint-se de tota la història d’una existència feliç, menyspreada de sobte i potser sense motiu prou just. Però la resolució era ferma: el forn era arrendat; ja no li quedava, en el poble, sinó una filla casada, amb la qual no vivia. «S’ajuntaria amb son fill, tractaria *persones*, i mil divertiments l’ajudarien a passar el temps que aquí, sense treballar, li havia d’ésser insuportable. Endemés, els estius hi tornaria: que per ventura no la veuria mai més, la casa? Doncs bé podia deixar-la.»

I, tot espurnejant-li els ulls i fent el cor fort, aquell home, que pocs dies abans havia sentit tanta tristesa en arribar al poble, el deixà llavors amb una recança infinita.

Però dins la colossal figura d’en Daniel alenava un esperit d’infant que, com tots sos consemblants, es sentia més decantat als alegrois frívols que a la tristor. Per això, quan veié les fileres de fanals que li anunciaven la nova arribada, l’home oblidà ja la poesia de sa casa i del seu forn, i pensà altra volta que, entre allò que deixava i el que la ciutat li oferia, l’elecció no era dubtosa. Oblidava, el pobre Daniel, que Barcelona no podia oferir-li novetats diàries, i que la força del costum, a certa edat, és del tot aclaparadora.

En sa primera estada ja havia recorregut quasi tota la ciutat; son fill, enfeinat en la perruqueria, no podria acompanyar-lo: hauria de passejar tot sol; i, si vint-i-quatre hores diàries de renda són insuportables als mateixos ganduls, quant més no ho havien d’ésser a un home acostumat, com ell, al treball?

L’endemà de l’arribada començà a tocar-ne els resultats. Avesat a aixecar-se quan les amassadores havien deixat pastada una fornada de pa, es despertà a l’hora en què això acostumava a succeir, que per a la perruqueria era a deshora; i, com que ara no hi estava en qualitat de foraster ni volia desvesar-se de matinejar, l’home saltà del llit. L’alba tot just clarejava el que era indispensable perquè a sa llum incipient pogués el forner vestir-se, endevinant amb el palp quina era cada una de les peces del seu vestit que penjaven de les cadires com draps de color incert. Tots els altres de la casa dormirien encara tres hores, i la mateixa escalforeta i quietud que regnava en dormitoris i sales feia caminar de puntetes i contenir l’alè al que acabava de llevar-se. D’altra part no sabia on eren les claus del pis ni la de la porta forana, i, per tant, no podia eixir al carrer. Si algun jorn us veieu en tals condicions, tancats dins una cambra que, per tot esbarjo, té una finestra de celobert, sabreu què és patir i comprendreu l’angúnia que degué passar en Daniel aquella matinada.

I com, al seu poble, una nit demanava lluna, aquest dia demanava sol a ciutat. «Per tot hi ha cent llegües de mal camí!», mormolaren els seus llavis. I, no podent ja suportar el lligament de sa presó, mal s’exposés a despertar els de la casa, travessà el pis i obrí un dels balcons que daven a la Rambla, per esperar més distretament que els altres s’aixequessin.

Els fanalers, mig ensopegant de son, anaven apagant el gas, que, tot esgrogueït, semblava estar ja cansat de fer pampallugues; un que altre cotxe tancat, un que altre carro buit, i una que altra persona mig perduda per la deserta via, formaven tot el moviment d’aquella Rambla que la nit d’arribada, amb sa constant bellugadissa, havia marejat en Daniel. Però, a poc a poc, el vel cendrós que s’estenia del port a la muntanya anà replegant-se sobre aquesta, arrabassat per un fresc oreig capaç de despertar les potències més adormides. Els arbres redreçaren les poques fulles que encara conservaven verdes, i remogueren llur brancatge com per espolsar-se la son; sota un cel de blau puríssim lluïren les punxes dels parallamps, els ferros i vidres dels campanars i torratxes, simulant enlluernadors estels; anaren obrint-se portes i finestres; i un formigueig de gent i carruatges, cada instant més gros, començà a bellugar per tots cantons com eixam d’abelles a l’entorn d’un buc.

Un xic més tard, els aprenents de la perruqueria es posaren a despertar el *saló* a cops d’espolsadors, a raspallar-lo amb les escombres, a rentar-li la cara, a endreçar-lo i vestir-lo; en una paraula, a deixar-lo a punt de rebre les persones que aviat començarien a visitar-lo.

Quan el forner baixà l’escala, el Rumbós encara dormia, però el *saló* era ja net, lluent, estirat com tot un senyor que sap honrar sa dignitat pròpia i vol que jamai ningú, per de matí que es llevi, no pugui trobar-lo desprevingut.

No diré que en Daniel passés el dia enujat o avorrit: encara el contrast de l’animació de Barcelona amb la soledat i quietud de son poble el tenia entretingut i distret; però no tant, per això, com en sa primera estada. Els edificis i les botigues ja no li oferien novetat: ja quasi sabia de cor què trobaria a la dreta i què a l’esquerra en emprendre tal carrer o plaça de les principals, i per experimentar noves impressions havia d’allunyar-se fins a barris que li eren desconeguts, o bé escaure’s davant algun espectacle d’aquells que sempre atreuen badocs per més que es repeteixin, com són els arrencaqueixals, els ocellets de la bonaventura, l’orquestra de cecs de Sant Gaietà, les mones que s’enfilen pels balcons, i tants i tants altres, que ell, al cap de dos dies d’haver arribat del poble, sabia més que tots els habitants de la ciutat.

Un jorn visitant la Barceloneta, veient, un altre, el castell de Montjuïc, ara anant a Pedralbes, o a Can Tunis, o a Vallcarca, o a la Bonanova (en una paraula, recorrent els encontorns que no havia pogut veure en son primer viatge), passà encara una mesada distreta; però no per això en Daniel es sentia ben centrat. Havia perdut, sense saber com, l’alegria que per tants anys resplendí en son cor: per sol que fes i per boniques que fossin les coses, li apareixien rodejades de certa ombra misteriosa que li causava més enuig que joia. Recordava el poble, sa passada vida, i anava convencent-se que podria encara suportar-la bé. Altres voltes evocava el record del sopar de la premsa, pensava en la sort que estava fent son fill, i sentia entrar en l’esperit un nou raig d’esperança i felicitat. Però en arribar a casa després de quatre o cinc hores de passejar-se sol sense obrir la boca, i en veure’s obligat a romandre assegut en el rebedor com un porter, o bé en el menjador, sense poder conversar amb ningú perquè així ho exigia la feina, el nostre home tornava a caure en un pou de melangia.

Ell mateix no sabia donar-se compte del que li passava, ni arribava a descobrir la causa principal d’aquell malestar.

—No tens bona taula, no tens bon llit, no *disfrutes* de la major llibertat i independència? Per ventura ton fill no et tracta bé? No ha de servir-te de satisfacció veure com avança son establiment, i no et trobes tu on desitjaves de molts anys ençà?

Totes ses respostes havien d’ésser afirmatives… i, això no obstant, no quedava satisfet.

La privació que sentí la primera matinada s’havia corregit el mateix dia proveint a en Daniel de les claus corresponents perquè fos amo d’entrar i sortir quan li fes pler; i, com aquesta, son fill havia anat destruint, una per una, totes les altres privacions de què aquell s’hagués queixat. Cada dia procurava assenyalar-li nous passeigs, i les vetllades de poca feina l’acompanyava a veure algun teatre o cafè mentre l’home volgués anar-hi, que fou ben poques vegades, perquè preferia dormir en el llit a adormir-se en públic.
`, String.raw`# IV {style="capitol" art="cap4"}

Una vetlla en Daniel entrà molt content: havia topat un conterrani seu i parlat llargament amb ell del poble. Era la primera vegada, fora de les poques que eixia amb son fill, que havia passejat per Barcelona parlant. Aquell mutisme en mig de la multitud era una de les coses que més el posaven de mal humor. Recordava que en el seu poble tothom el saludava, i no podia avenir-se a travessar per entre mils i mils persones com ombra indiferent. Per això, en mig del soroll confós de la multitud, en Daniel hi trobava un silenci glacial molt més trist que el de la quietud i la soledat. Per això, a voltes, les cames se l’emportaven instintivament a indrets solitaris; i, un dia que en Miquelet se n’estranyà, son pare li respongué que el moviment l’entristia, que el centre de Barcelona li produïa l’efecte d’un cementiri on els morts es passegessin.

Son conterrani trobà en Daniel decaigut, molt més blanc de cabells i «com si s’hagués escurçat o s’hagués posat calces de creixença». Observà també que mentre li parlava de Barcelona quasi no escoltava, i que en canvi s’entretenia molt a remembrar son poble, a preguntar per tothom, a informar-se de tot el que hi havia passat d’ençà de sa marxa, deixant caure alguna llàgrima sobre les noves dels morts o naixements que aquell li comunicava. Aquestes observacions van ésser comentades en el poble, i atribuïdes a desavinença entre pare i fill per uns, a un principi de feridura per altres.

A la nit en Daniel somià son poble. Es veié, de primer, a si mateix passejant amb la Roser, la seva dona, pel cementiri cobert d’herbam barrejat amb roselles i campanetes blaves que s’entortolligaven en les poques creus que hi havia; veié després un cel molt negrós, en el qual una munió d’estrelles titil·laven sense parar; i, de la pròpia manera que havia passat de la claror del sol a plena nit sense crepuscle, es trobà, també sense saber com, dins de casa, en cos de camisa i amb la pala a la mà, davant d’un forn encès com un segment del sol. Sa llum, escapant-se per la boca, queia sobre l’enrajolat en forma pareguda a la del cercle lluminós d’una llanterna màgica; i, al mig d’ella, en Daniel veia l’ombra de sa pròpia figura escurçant-se o allargant-se a mercè de l’inquiet reflex de les flames i escarnint monstruosament les contorsions que ell amb la pala feia. Mentrestant, el pa, dins el forn, s’inflava i s’estovava com llavor dins bona terra. Poc després esdevenia de color d’or, i calia veure maniobrar en Daniel per muntar-lo a la pala i atreure-se’l en mig del riu de flames que li disputava la presa.

En arribar aquí, un xiscle mig ofegat trencà la solemne quietud del dormitori. En Daniel es tombà sobre l’altre costat, i lliscà per sos llavis com un suau somrís; l’últim adéu de l’alegria que el visità aquell jorn. Després… son esperit reposà profundament.

En despertar, sos ulls van clavar-se un instant en el balcó del celobert, i una llàgrima vergonyant s’escolà per llurs pestanyes.

Llavors en Daniel rumià quant mancava per a arribar a l’estiu. Tot just era pel gener: cinc mesos li van semblar un segle. Però, com que el mateix desig de consol li engendrava noves esperances, l’home en concebé ben aviat una, i fou que anant a l’estació del carril veuria arribar algú altre del seu poble.

En Daniel no pensà que, essent aquest a la província de Tarragona i trobant-se a cinc hores del ferrocarril, els seus veïns venien a la ciutat comtal sols per causes extraordinàries, i que, component-se de curt nombre d’habitants, pocs eren els que durant l’any visitaven Barcelona.

Heus aquí, doncs, el bon home fent, dies i dies, debades, el camí de l’estació, anant-hi amb una esperança, tornant-se’n amb un desengany.

L’enyorança, mentrestant, anava minant-lo cada dia més. En Daniel no semblava ja el gros Forner de les Monges: la roba li venia cada cop més ampla i com més llarga, se li havien tornat blancs tots els cabells, i un cercle blavenc d’amarga tristor que voltava sos ulls li dava un aspecte desencaixat, quasi cadavèric.

En Miquelet, amb tot i la poca atenció que podia dispensar-li, se n’adonà, i volgué dur-lo a casa un metge. Tot fou inútil. El malalt havia posat mal geni. No sols no tenia fe en els facultatius desconeguts, sinó que (cosa pitjor) ni volia curar-se. Avorria el seu estat, avorria la vida, i notava en si mateix un misteriós afany que no sabia definir-se sinó com un cert desig de córrer, de córrer molt i empènyer les hores, que trobava molt llargues. La gran excitació en què vivia el feia passar del matí a la nit per tota una escala d’exaltacions i abatiments. A la senzilla vista d’una fleca en què el comprador no pot albirar el forn d’on surten els pans, es posava furiós. Altres voltes l’hauríeu vist dolçament entretingut en la contemplació de les flors o dels ocells que es venen a la Rambla. Si li haguessin preguntat què hi veia, per què s’hi delitava, hauria de segur respost que jamai, fins llavors, no s’havia fixat en flors ni en ocells, i que, no obstant, ara els contemplava amb gust, sense saber per què! Potser, sense adonar-se’n, hi descobria una secreta relació amb la primavera, precursora de l’anhelat estiu! L’home esclau d’un desig no sent pertot arreu veus misterioses de l’objecte de son afany?

A les mateixes veus obeïa, sens dubte, la tendència que el forner sentia per la soledat, i més encara pels voltants de l’estació del ferrocarril. Ja no s’acontentava d’anar-hi a l’arribada del tren: cada tarda sos passeigs eren per aquest cantó de ciutat. No semblava sinó que per la via li arribessin flaires de la seva terra sense les quals no podia viure.

Una tarda que les cames el portaren més enllà del convent de les Adoratrius, arribà a un punt en què la via s’adreçava i seguia recta fins a l’extrem de la visual, on els rails convergien com els talls d’una llança que es perdia dins el vel morat de la distància. Allí clavà sos ulls, desitjós de traspassar el límit de l’horitzó; i, encisat, sens dubte, per una atracció secreta, va asseure’s al caire mateix del vall o desmunt que formava la via.

El sol anava enfonsant-se per l’aiguavés de Vallvidrera oposat a la ciutat; el caseriu de Pedralbes, Sarrià i Sant Gervasi era engolit per una boira cendrosa; i l’ombra gegantina del serrat envaïa solemnement tot el pla, ensems que la nuvolada mudava i ennegria, cada volta més, sos virolats colors. El mateix vall de la via anava enfosquint-se per moments, mentre en son fons els rails prenien la lluïssor de l’aigua a la dubtosa llum de la celístia.

L’esguard d’en Daniel lliscava per damunt d’ells com el d’un desterrat per sobre el riu que va a parar a sa llunyana pàtria. El pobre home plorava. No sabia explicar-se com, el camí que un dia li havia ofert tants plaers, podia avui entristir-lo tant. Justificava una volta més la veritat del poeta: l’espectacle el porta l’espectador dins d’ell mateix.

Tot d’una en Daniel s’alçà i prengué resolt el camí de la perruqueria amb el semblant animat com de feia molt temps no havia tingut.

—Gràcies a Déu que us veig content! —digué son fill—. Sembleu tot un altre! A què es deu aquesta sobtada alegria?

—Crec que m’he endevinat el mal, i penso posar-hi cura. M’enyoro: demà me’n tornaré a casa.

—Pare! No us torneu boig? Enyorar-se a Barcelona el veí d’un poblet? No recordeu l’efecte que us va causar l’arribar-hi, venint d’aquí?

—Aleshores això em semblava un cel, i ara em sembla un cementiri.

—Espereu *sisquera* l’estiu. Que per ventura us manca alguna cosa?

—Em manca tot i no em manca res: vés-ho a entendre. Soc arbre vell que un malentès ha transplantat. Si em quedo aquí em moriré de tristesa. Deixa’m tornar a casa meva i no ho prenguis a mal. T’agraeixo tot el que has fet per mi, t’ho estimo molt… però me n’he de tornar a casa… Què hi faré, pobre de mi, si m’enyoro… m’enyoro i…

En arribar aquí, en Daniel plorava, i en una estreta de mà a son fill va encloure cent protestes d’agraïment i totes les altres raons en què es basava sa resolució.

En Miquelet comprengué l’estat de son pare, i el matí següent l’acompanyà fins al carril.

Era ja tard. L’alegria és una saba que, si es perd a certa edat, ja no es recobra. En Daniel l’havia perduda per sempre; i, si en retornar a la seva terra pogué per un instant creure-la reconquerida, aviat en tocà el desengany.

Ell ho havia dit: era un arbre vell transplantat. I l’arbre vell, un cop arrencat, és inútil replantar-lo en el lloc primitiu: ja mai més no farà brotada.

Per què tractar de pintar-vos ara el pobre forner altra volta al seu poble, fora de la casa i del forn que tan incautament havia arrendat, i perdent de dia en dia les forces i els colors, els ulls fixos a terra, enterbolits pel plor, sense delit d’enraonar ni de veure ningú, sense cap goig, sense altra esperança que la d’acabar el sofriment en la mortalla?

Ni aquell estiu (temps per ell tan desitjat algun jorn!) no pogué ja veure: pel maig, l’herbam, barrejat amb les roselles i les campanetes blaves, embolcallava ja la creu de ferro que s’aixecava de la tomba del malaurat Daniel i sa muller sota el mantell joliu d’un cel pur de primavera.

:::paragraphs{style="fi"}
Fi
:::

:::paragraphs{style="colofo"}
Compost en Crimson Pro, Cormorant Garamond i Cormorant SC (SIL OFL). Text: Narcís Oller, *Obres completes*, vol. VI, Barcelona, 1928, domini públic (Viquitexts). Il·lustracions generades amb models de difusió.
:::
`].join('\n\n');

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
const FONTS = { // every face the pages use, loaded before the build (gotcha: fonts-first)
  'Crimson Pro': ['400', '400i'], // the text, the note, the colophon, the folios
  'Cormorant Garamond': ['500i', '600'], // half-title and title; the chapter numerals
  'Cormorant SC': ['400', '600'], // the closing Fi; author, labels, running heads
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
await loadFonts(FONTS, markdown);
await Promise.all(Object.values(PICTURES).map(([file]) => loadImage(file, asset(file))));
const doc = await buildWithFonts(() => buildDocument({ markdown, resources }, config()), markdown);
showPages(doc, { title: 'El transplantat' });
offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider, resourceBytes: imageBytes }),
  `${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 · images v1 ── recipes with pictures · postext.dev/cookbook ──────────
/** Registers a photo or PNG for the canvas and keeps its bytes for the PDF.
 *  fetch → ImageBitmap never taints the canvas (a plain cross-origin <img> would). */
async function loadImage(fileId, url) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Image not found (${res.status}): ${url}`);
  const bytes = new Uint8Array(await res.arrayBuffer());
  registerResourceImage(fileId, await createImageBitmap(new Blob([bytes])));
  (loadImage.bytes ??= new Map()).set(fileId, bytes);
}

/** Registers SVG markup (drawn in code, or fetched) as a vector image. */
async function loadSvg(fileId, svg) {
  const img = new Image();
  img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
  await img.decode();
  registerResourceImage(fileId, img);
  (loadImage.bytes ??= new Map()).set(fileId, new TextEncoder().encode(svg));
}

/** renderToPdf({ resourceBytes: imageBytes }) */
function imageBytes(fileId) { return loadImage.bytes?.get(fileId); }

/** renderToHtml({ resourceImageUrl: imageUrl }) */
function imageUrl(fileId) {
  const bytes = imageBytes(fileId);
  if (!bytes) return undefined;
  imageUrl.urls ??= new Map();
  if (!imageUrl.urls.has(fileId)) {
    const type = /\.svg$/i.test(fileId) ? 'image/svg+xml' : /\.png$/i.test(fileId) ? 'image/png' : 'image/jpeg';
    imageUrl.urls.set(fileId, URL.createObjectURL(new Blob([bytes], { type })));
  }
  return imageUrl.urls.get(fileId);
}

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

## Variantes

### Que los pronombres también se corten

Con el `compounds: true` por defecto, *d’inaugurar-se* puede cortarse *d’inau-gurar-se* y la página 7 pierde su línea floja. A cambio, en estas páginas el corte `titil-` | `laven` deja de caer a final de línea.

```diff
-  hyphenation: { compounds: false },
+  hyphenation: { compounds: true },
```

### Abrir cada capítulo en página impar

`parity: 'odd'` añade una página en blanco antes de un capítulo que empezaría en par, como en una edición más holgada. El capítulo IV, que se abre en la página 26, pasa a la 27 tras una página en blanco.

```diff
-  breakBefore: { enabled: true, parity: 'any' }, // a chapter opens on the next page, either side
+  breakBefore: { enabled: true, parity: 'odd' },
```

## Errores frecuentes

- **Solo 8 idiomas tienen separación silábica, con el código exacto.** La separación silábica existe para en-us, es, fr, de, it, pt, ca y nl, con el código exacto: 'es-ES' o cualquier otro idioma pasa sin aviso al inglés americano.
- **Las imágenes de una apertura no cuentan para la altura que reserva.** En postext 1.4.1, un título con diseño avanzado mide la altura que reserva sin contar sus imágenes: sus textos, filetes y cajas cuentan, aunque estén anclados a la página, pero una imagen, como un dibujo a sangre en la cabeza de la página, no reserva nada, así que el texto puede empezar encima de ella. Fija con minHeight dónde debe empezar el texto.
- **Un estilo de título hereda el salto de página de su nivel.** Una entrada de headingStyles toma de su nivel de título todo lo que no fija, también breakBefore. Un índice o un colofón con estilo sobre un H1 tras un :::pagebreak hereda la paridad 'odd' y cae detrás de una página en blanco. Dale a ese estilo breakBefore: { enabled: false }.
- **Cualquier objeto headings desactiva el salto de página del H1.** Por defecto un H1 salta a una página impar (always-odd), pero cualquier objeto headings anula ese valor, así que los capítulos van seguidos y span: 'page' no hace nada. Vuelve a declarar headings.levels[0].breakBefore: { enabled: true, parity } en cada configuración.
- **Una paleta cambiada no llega a los elementos de diseño ni al color de las remisiones.** postext 1.4.1 aplica colorPalette a los estilos de texto (cuerpo, títulos, listas, pies, tablas, recuadros), pero no a los elementos de cabeceras, pies de página, aperturas y portadillas, ni a bodyText.referenceColor: conservan el hex escrito junto a su paletteId. Si cambias la paleta, para una edición de pantalla oscura o para recolorear, reescribe cada color enlazado a partir de colorPalette antes de componer.
- **El desbordamiento del texto de diseño es 'ellipsis-end' por defecto.** Un elemento de texto de diseño que no cabe en su ancho termina en puntos suspensivos por defecto. Pon overflow: 'wrap' en los títulos que deban pasar a más líneas.
- **Valores de atributo: sin { ni }; comillas simples si llevan ".** Un valor de atributo termina en la llave de cierre, así que no puede contener { ni }. Un valor que lleve comillas dobles va entre comillas simples; el signo de dólar no da problemas.
- **Entrecomilla cada valor del frontmatter.** YAML lee title: 1984 como un número y una fecha como un objeto Date, y los valores que no son cadenas se imprimen vacíos en los marcadores y dejan el PDF sin título. Entrecomilla cada valor: title: "1984".
- **Carga todas las fuentes antes de componer.** La composición mide el texto con las fuentes que el navegador ha cargado y guarda los anchos, así que una fuente que llega después de la primera composición deja cortes de línea erróneos y un PDF que ya no coincide con la pantalla. Carga antes todos los pesos y estilos, y llama a clearMeasurementCache() antes de recomponer si alguna llega tarde.

- Copia el texto de Viquitexts con sus cursivas: Oller marca *tentu*, *Ensanxe* y *palacio* como coloquiales o castellanas, y una exportación en texto plano las pierde. Coteja las palabras dudosas con el facsímil del volumen de 1928: seis veces la transcripción trae *tom-* donde el verbo es *tornar* (*tomà*, *tomaré*, *tomar*), y una vez *jom* por *jorn*.
- Con 6800 palabras, los cinco archivos de contenido y el kit dejan el script compuesto en unos 69 KB. Un libro entero es la muestra más larga que cabe en un pen; una obra más larga va en un paquete `.postext`.

## Créditos

- Receta: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- Texto: El transplantat, completo, de las Obres completes, vol. VI (Barcelona, 1928), con las erratas corregidas, sin ¿ ¡ iniciales y con los diálogos marcados con raya como hoy: Narcís Oller ([fuente](https://ca.wikisource.org/wiki/El_transplantat)), dominio público
- Texto: La nota a la edición: Postext Cookbook, original
- Imágenes: Grabado, frontispicio: el panadero a la puerta del horno: Generated With Diffusion Models, original
- Imágenes: Grabado, capítulo I: el tren nocturno llega a Barcelona: Generated With Diffusion Models, original
- Imágenes: Grabado, capítulo II: la calle del pueblo en otoño: Generated With Diffusion Models, original
- Imágenes: Grabado, capítulo III: la Rambla al amanecer: Generated With Diffusion Models, original
- Imágenes: Grabado, capítulo IV: el viejo junto a la vía: Generated With Diffusion Models, original
- Tipografías: Crimson Pro (OFL-1.1), Cormorant Garamond (OFL-1.1), Cormorant SC (OFL-1.1)
- Código: MIT · Contenido de ejemplo: MIT

## Relacionadas

- [N.º 016 · Español justificado en una novela de bolsillo](https://postext.dev/es/cookbook/spanish-pocket-novel.md): El comienzo de Marianela en edición de bolsillo: separación silábica del español, espacios de menos de 1,7×, capitular alzada y el plano como Figura 1. · Nivel 2 (Intermedio) · Narrativa, teatro y prosa literaria
- [N.º 066 · Capítulos de cuento con viñetas y sumarios](https://postext.dev/es/cookbook/storybook-chapter-vignettes.md): Un solo estilo de título abre todos los capítulos; cada título nombra en atributos su viñeta y su sumario en cursiva, que pinta la apertura. · Nivel 2 (Intermedio) · Narrativa, teatro y prosa literaria
- [N.º 014 · Espaciado uniforme en columnas estrechas y justificadas](https://postext.dev/es/cookbook/justification-lab.md): Corte Knuth–Plass con los espacios entre 0,8 y 1,6 veces el normal, junto a un control voraz con sus líneas flojas, solas y cortas marcadas desde el VDT. · Nivel 3 (Avanzado) · Revistas y fanzines
