Saltar al contingut principal
Recepta número 73

Receptari · Capítol 5 · Estructura del llibre

Un índex onomàstic i un índex de matèries

Les marques amb index="names" van a l'índex de persones i la resta al de matèries; :::index els imprimeix a dues columnes i la maquetació en fixa les pàgines.

En aquesta pàgina
Sortida
Canvas · PDF
Nivell
Intermedi
Postext
Provada amb Postext 1.7.0
Requereix ≥ 1.7.0 · postext-pdf ≥ 1.7.0
Llicència
Actualitzada el 28 de set. del 2026
Codi MIT · Text CC BY 4.0
  • Mostra en castellà: encara no hi ha edició en català
  • Format 155 × 235 mm
  • 1 columna
  • Crimson Pro 10,6/13,6
  • Bodoni Moda
  • Libre Franklin
  • 7 pàgines
  • Nivell
  • Postext 1.7.0
  • Compost en 68 ms
  • 143 línies de codi

En poques paraules

Un capítol d'un llibre acadèmic sobre la història dels tipus d'impremta. Ensenya a crear dues llistes al final, una de persones i una altra de temes, amb la pàgina on surt cadascun.

Què compondràs

El capítol quart de Letras de metal, una monografia sobre tipus d'impremta de 155 × 235 mm: una columna de Crimson Pro, títols en Bodoni cursiva i un sol color d'accent, vermell maó. L'assaig va de la textura de Gutenberg als moderns de Bodoni, passant per Jenson, Aldo i Griffo, Garamond i Plantin, Caslon i Baskerville, els punts de Fournier i els tipus espanyols que van tallar Pradell i Gil i que va imprimir Ibarra. Acaba com acaben aquests llibres, amb dos índexs en pàgines encarades: impressors, gravadors i mecenes a la parella; tipus, eines i llibres a la senar. Tots dos surten del mateix text marcat, i cada número de pàgina és la pàgina on va caure la marca.

Aquesta recepta respon a

  • Com imprimeixo dos índexs, un d'onomàstic i un altre de matèries, a partir del mateix text marcat?
  • Com faig un índex analític els números de pàgina del qual canviïn sols quan el text es mou?

La resposta curta

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

Ingredients

Tipografia
Crimson Pro, Bodoni Moda, Libre Franklin (SIL OFL 1.1)
Recursos
Cap: totes les imatges es dibuixen en codi

Elaboració

#1 · Marca cada persona per a l'índex onomàstic i cada matèria per al principal

La configuració és la resposta curta de més amunt; la resta és al Markdown. Una marca amb index="names" va a l'índex que imprimeix :::index{index="names"}, i totes les altres a l'índex principal, el que imprimeix :::index sense atributs. Així, :index[Aldo Manucio]{term="Manucio, Aldo" index="names" main} imprimeix el nom al text i anota una pàgina en negreta sota Manucio a l'onomàstic, mentre que :index[cursiva]{term="Cursiva" main}, dues línies més avall, anota la mateixa pàgina sota Cursiva al de matèries. Una persona que també és matèria del capítol pot portar les dues marques.

#2 · Inverteix els noms i ordena per la lletra on el lector buscarà

Els noms van amb el cognom al davant: term="Garamond, Claude", term="Dall’Aglio, Margherita". La coma forma part del terme, de manera que l'ordenació posa Didot, Firmin abans de Didot, François-Ambroise, com faria un indexador. Quan la forma impresa cauria a la lletra equivocada, sort dona la clau: l'edició anglesa marca term="42-line Bible" sort="Forty-two-line Bible" perquè la Bíblia quedi sota la F i no obri un grup 0–9. Les subentrades (term="Fundiciones!Plantin-Moretus"), les pàgines principals en negreta (main), range="start" i range="end" al voltant de l'apartat de Bodoni i les remissions (see="Cursiva", seealso="Matrices") fan la resta. Una destinació amb nivells s'imprimeix amb dos punts: Humana, lletra. Vegeu Lletra romana: veneciana.

#3 · Compon una sola vegada; el document fixa els seus propis números

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

L'índex imprimeix números de pàgina que només existeixen després de maquetar, i imprimir-lo ocupa pàgines. buildDocument() ho resol: maqueta el capítol, llegeix la pàgina de cada marca, imprimeix els dos índexs i torna a maquetar cada vegada que un número canvia. Aquí els índexs van darrere del text, així que la segona passada confirma la primera; un índex col·locat abans del text desplaçaria totes les pàgines següents, i les passades continuarien fins que coincidissin. Afegeix un paràgraf a l'apartat de Bodoni i l'índex onomàstic donarà a Whittingham la seva pàgina nova sense que hi toquis res.

#4 · Dona a cada índex una obertura sobre dues columnes

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

L'estil de títol de la resposta curta fa tres coses per # Índice onomástico {style="index"}: obre pàgina (parity: 'any', perquè els dos índexs se segueixin sense una pàgina en blanc), compon a dues columnes separades 7 mm la secció que comença després i, amb numbered: false, no compta com a capítol. L'estil reutilitza l'obertura del capítol amb un enfonsament de sis línies i un títol de 20 pt, i les lletres de cada grup repeteixen la seva Bodoni en el color d'accent, amb l'alçada d'una línia de l'índex perquè les columnes no perdin el pas.

#5 · Posa el nom de l'índex a la pàgina senar

script.js · línies 79–96al codi complet
const head = (id, content, parity, edge, x, extra = {}) => ({
  kind: 'text', id, content, parity, pages: 'body', // never on openers
  ...label, fontSize: pt(7.2), letterSpacing: pt(1.3), color: col('muted'),
  placement: at('page', edge, x, 12.5), ...extra,
});
const folio = { color: col('brick'), fontWeight: 700 };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', MARGIN.outer,
    { ...folio, align: 'left' }),
  head('verso-title', '{title}', 'even', 'top-left', MARGIN.outer + 8, { align: 'left' }),
  head('recto-title', '{chapterTitle}', 'odd', 'top-right', -(MARGIN.outer + 8),
    { align: 'right' }),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -MARGIN.outer,
    { ...folio, align: 'right' }),
] };
// Openers carry a drop folio instead, centred under the text block.
const footer = { elements: [{ ...head('drop-folio', '{pageNumber}', 'all', 'bottom', 0,
  { ...folio, align: 'center', placement: at('page', 'bottom', 0, -12) }), pages: 'opener' }] };

La capçalera de la pàgina senar imprimeix {chapterTitle}, i el títol d'un índex és de primer nivell com el de qualsevol capítol, així que les pàgines que el segueixen diuen Índex de matèries a dalt. pages: 'body' treu les capçaleres de les dues obertures d'índex, que porten un foli al peu.

La recepta completa

Sandbox
// ═══ Postext Cookbook · Nº 073 · An index of names and an index of subjects ═══════
// https://postext.dev/en/cookbook/names-and-subjects-indexes
// Code: MIT · Text: original (CC BY 4.0) · Pictures: none
// Fonts: Crimson Pro, Bodoni Moda, Libre Franklin (SIL OFL 1.1) · Needs postext ≥ 1.7.0
import { buildDocument, renderPageToCanvas, clearMeasurementCache } from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';

const LANG = 'es'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'names-and-subjects-indexes';

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

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

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

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

const config = () => ({ // a factory: the engine caches resolved configs per object
  locale: t({ en: 'en-us', es: 'es' }), // exact codes (gotcha: hyphenation-locales)
  colorPalette,
  page: { sizePreset: 'custom', width: mm(TRIM.width), height: mm(TRIM.height), dpi: 150,
    backgroundColor: col('paper'),
    margins: { top: mm(MARGIN.top), bottom: mm(MARGIN.bottom), left: mm(MARGIN.inner),
      right: mm(MARGIN.outer), mirror: true } }, // left = inner
  layout: { layoutType: 'single' },
  bodyText: { fontFamily: 'Crimson Pro', fontSize: pt(10.6), lineHeight: pt(LEAD),
    color: col('ink'), boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
    textAlign: 'justify', firstLineIndent: mm(4.5), indentAfterHeading: false,
    hyphenation: { enabled: true }, optimalLineBreaking: true,
    avoidWidows: true, avoidOrphans: true, avoidRunts: true },
  headings: { fontFamily: 'Bodoni Moda', fontWeight: 400, color: col('ink'), levels: [
    // Restated on purpose: any headings object drops the H1 break
    // (gotcha: headings-drop-h1-break).
    { level: 1, advancedDesign: opener(9, 26), marginBottom: pt(0),
      breakBefore: { enabled: true, parity: 'odd' } },
    { level: 2, fontSize: pt(12), lineHeight: pt(LEAD), italic: true, fontWeight: 400,
      color: col('brick'), marginTop: pt(LEAD), marginBottom: pt(LEAD * 0.5) },
  ] },
  // The index heading: a shallower opener over the two columns of entries.
  headingStyles: [{ ...indexHeading, span: 'page', advancedDesign: opener(6, 20),
    marginBottom: pt(0) }],
  index,
  paragraphStyles: [
    { id: 'colophon', ...label, textTransform: 'none', fontWeight: 400, fontSize: pt(7),
      lineHeight: pt(10), color: col('muted'), textAlign: 'left', firstLineIndent: pt(0),
      marginTop: pt(LEAD * 2) },
  ],
  header,
  footer,
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = String.raw`---
Mostra en Markdown · 71 línies · content.es.mdtitle: "Letras de metal" subtitle: "Breve historia de los tipos de imprenta" --- # De la textura a Bodoni {kicker="Capítulo cuarto"} Los primeros tipos europeos copiaron la letra en la que más confiaban los lectores. Cuando :index[Johannes Gutenberg]{term="Gutenberg, Johannes" index="names" main} terminó en Maguncia su Biblia, hacia 1455,:index{term="Biblia de 42 líneas" main} la compuso en :index[textura]{term="Letra gótica!textura" main}, la gótica alta y estrecha de los libros de coro alemanes, de trazos quebrados y pies en rombo. La página quería pasar por un manuscrito, y lo consiguió: los rubricadores pintaban a mano las iniciales rojas, y algunos ejemplares se tiraron en vitela. La novedad no se veía. Cada letra se había tallado en acero como :index[punzón]{term="Punzones" main}:index{term="Punzones" seealso="Matrices"}, se había hundido en una barra de cobre, más blando, para obtener la :index[matriz]{term="Matrices"}, y se había fundido en el :index[molde de mano]{term="Molde de mano"} con una aleación de plomo, estaño y antimonio.:index{term="Metal de imprenta"} Un solo punzón daba miles de letras idénticas, y toda la cadena, del punzón a la página, duró casi cuatrocientos años con pocos cambios. Los socios de Gutenberg vieron el negocio antes que él. :index[Johann Fust]{term="Fust, Johann" index="names"} se quedó con el taller tras un pleito en 1455 y, con :index[Peter Schoeffer]{term="Schoeffer, Peter" index="names"}, imprimió el Salterio de Maguncia de 1457, el primer libro con colofón de impresor y fecha. Cuando Maguncia fue saqueada en 1462 sus oficiales se dispersaron, y veinte años después había prensas en más de cien ciudades. ## La romana veneciana Los lectores italianos no querían letras alemanas. Los humanistas de Florencia habían recuperado la minúscula carolina para copiar a los clásicos, una letra redonda y abierta que creían antigua, y los impresores que bajaron al sur los siguieron. La mejor de las primeras romanas la talló en Venecia :index[Nicolas Jenson]{term="Jenson, Nicolas" index="names" main}, un francés formado en la ceca de Tours. Su Eusebio de 1470 está compuesto en una :index[redonda]{term="Letra romana!veneciana" main} de color parejo y mayúsculas anchas y bien espaciadas, y desde entonces ha servido de modelo a todas las recuperaciones. Veinticinco años después :index[Aldo Manucio]{term="Manucio, Aldo" index="names" main} abrió su imprenta en la misma ciudad con un programa de erudito: primero los textos griegos, luego los autores latinos en ediciones que un estudiante pudiera llevar consigo. Su grabador de punzones era el boloñés :index[Francesco Griffo]{term="Griffo, Francesco" index="names" main}. Para el breve diálogo *De Aetna* del joven :index[Pietro Bembo]{term="Bembo, Pietro" index="names"}, impreso en 1496,:index{term="*De Aetna*"} Griffo talló una romana más ligera y regular que la de Jenson, con las mayúsculas más bajas que los trazos ascendentes, la proporción que los tipos de libro conservan hasta hoy.:index{term="Letra romana!aldina"} En 1501 Aldo publicó un :index[Virgilio]{term="Virgilio" index="names"} en octavo compuesto entero en una letra nueva: la :index[cursiva]{term="Cursiva" main} de Griffo, dibujada sobre la escritura inclinada de la cancillería papal. Ahorraba espacio, lo que convenía a un libro de bolsillo, y parecía docta. El Senado de Venecia concedió a Aldo un privilegio sobre ella,:index{term="Privilegios"} los falsificadores de Lyon la copiaron antes de un año, y Aldo y Griffo riñeron por el mérito hasta que Griffo se marchó de Venecia. Durante un siglo la cursiva fue letra de texto por derecho propio. Solo más tarde los impresores la emparejaron con una redonda y la reservaron para el énfasis, los títulos y las palabras extranjeras, el oficio que aún tiene. :index{term="Itálica" see="Cursiva"} :index{term="Bastardilla" see="Cursiva"} ## París y Amberes La romana se hizo francesa en la década de 1530. El impresor parisino :index[Robert Estienne]{term="Estienne, Robert" index="names"} y su círculo querían letras aldinas, y quien mejor las talló fue :index[Claude Garamond]{term="Garamond, Claude" index="names" main}, grabador de punzones que trabajaba sobre todo para prensas ajenas. Sus romanas afinan el modelo de Griffo: los trazos finos son más finos, los remates más agudos, las mayúsculas más ligeras frente a la caja baja. Entre 1540 y 1550 talló además los *grecs du roi*,:index{term="Letra griega"} tres cuerpos de griego para Francisco I, calcados de la mano del copista cretense Ángel Vergecio. A la muerte de Garamond, en 1561, se vendieron sus punzones, y muchos acabaron en Amberes. :index[Christophe Plantin]{term="Plantin, Christophe" index="names" main}, encuadernador nacido en Turena, había levantado allí la mayor imprenta de Europa, y compraba letra como un banquero compra deuda. Su casa guardaba punzones y matrices de Garamond, de :index[Robert Granjon]{term="Granjon, Robert" index="names" main} y de Guillaume Le Bé, y los necesitó todos para la Biblia Políglota de 1568-1572, que dirigió Benito Arias Montano.:index{term="Arias Montano, Benito" index="names"} Granjon fue el mejor tallista de cursivas del siglo; en Lyon, en 1557, grabó además la *civilité*, una letra de secretario francesa que esperaba convertir en tipo nacional. No lo consiguió, pero sus cursivas sí triunfaron. La casa siguió en manos de los Moretus hasta que la ciudad de Amberes la compró en 1876, y allí siguen sus punzones, sus matrices y sus libros de cuentas: la fundición de su época que ha llegado más completa hasta nosotros.:index{term="Fundiciones!Plantin-Moretus"} ## El siglo inglés Inglaterra imprimió con letra holandesa hasta el siglo XVIII, cuando un grabador de llaves de arcabuz abrió una fundición en Londres. :index[William Caslon]{term="Caslon, William" index="names" main} publicó su hoja de muestras en 1734,:index{term="Muestrarios!Caslon, 1734"} y su romana, recia y algo irregular, se convirtió en la letra corriente del mundo de habla inglesa. No pedía explicaciones ni llamaba la atención, y por eso los impresores siguen echando mano de ella. :index[John Baskerville]{term="Baskerville, John" index="names" main}, maestro de caligrafía de Birmingham enriquecido con la loza lacada, llegó tarde a la imprenta y cambió algo más que las letras. Para su Virgilio de 1757 hizo tallar un tipo propio a John Handy, de eje más vertical y más contraste que el de Caslon, y lo imprimió sobre papel avitelado,:index{term="Papel avitelado"} con una tinta más negra de receta propia, y satinó los pliegos entre planchas de cobre calientes. A los impresores ingleses las páginas les parecieron demasiado brillantes para leerlas. Los lectores del continente las admiraron, y nació una letra :index[de transición]{term="Letra de transición"} entre la antigua y la moderna. ## Medir la letra Hasta mediados del siglo XVIII cada fundición llamaba a sus cuerpos como quería: *atanasia*, *lectura*, *entredós*, *breviario*. Un cuerpo era una costumbre de la casa, no una medida, y el impresor que compraba letra a dos fundiciones no podía componerlas juntas. :index[Pierre-Simon Fournier]{term="Fournier, Pierre-Simon" index="names" main}, el hijo menor de una familia parisina de fundidores, publicó en 1737 una tabla de proporciones que dividía la pulgada en setenta y dos :index[puntos]{term="Punto tipográfico" main range="start"} y daba a cada cuerpo un número de puntos. La expuso por extenso en su *Manuel typographique* de 1764-1766.:index{term="*Manuel typographique*"} :index[François-Ambroise Didot]{term="Didot, François-Ambroise" index="names"} ató el punto al pie de rey hacia 1783, lo que lo hizo algo mayor; el punto Didot se extendió por el continente, y los fundidores de Estados Unidos y Gran Bretaña no fijaron su propio punto hasta 1886 y 1898.:index{term="Punto tipográfico" range="end"} ## Los tipos españoles A comienzos del siglo XVIII los impresores españoles compraban la letra en Holanda y en Francia, y la Corona lo tenía por una vergüenza en el reino que había impreso la Políglota Complutense. :index[Carlos III]{term="Carlos III, rey de España" index="names"} y sus ministros se propusieron cambiarlo. La Real Biblioteca de Madrid abrió fundición propia,:index{term="Fundiciones!Real Biblioteca, Madrid" main} y la Corona pensionó a los grabadores de punzones que quisieran trabajar en España. Dos de ellos venían de oficios muy distintos. :index[Eudald Pradell]{term="Pradell, Eudald" index="names" main}, hijo de un armero de Ripoll, aprendió a trabajar el acero en la fragua de su padre y talló sus primeros punzones en Barcelona. Llamado a Madrid en 1764 con una pensión real, grabó romanas y cursivas de dibujo abierto y generoso que los impresores madrileños adoptaron enseguida. :index[Jerónimo Antonio Gil]{term="Gil, Jerónimo Antonio" index="names" main} venía del otro extremo del oficio: era grabador de la Real Casa de la Moneda y medallista, y talló para la Real Biblioteca una serie de romanas, cursivas y griegas que la Biblioteca mostró en su muestrario de 1787.:index{term="Muestrarios!Real Biblioteca, 1787"} Gil no llegó a verlo. En 1778 partió a México como grabador mayor de la Casa de Moneda, y allí fundó la escuela que se convertiría en la Academia de San Carlos. El impresor que dio fama a estos tipos fue :index[Joaquín Ibarra]{term="Ibarra, Joaquín" index="names" main}, nacido en Zaragoza en 1725 e impresor de cámara del Rey desde 1779. Su Salustio de 1772, traducido por el infante don Gabriel,:index{term="Gabriel de Borbón, infante" index="names"} fue elogiado en París y en Parma por la calidad de la tirada. Para la Real Academia Española imprimió en 1780 el *Quijote* en cuatro volúmenes en cuarto,:index{term="*Quijote*, edición de la Academia" main}:index{term="Cervantes, Miguel de" index="names"} con los tipos de Gil, láminas de los mejores grabadores de la corte y papel hecho para la ocasión en Cataluña. La tinta, negra y pareja, era del propio Ibarra; la página, de márgenes generosos y sin más adorno que las láminas, es la réplica española de las de Baskerville. ## Los modernos de Bodoni El último paso lo dio un impresor de una corte pequeña.:index{term="Letra moderna" range="start"} :index[Giambattista Bodoni]{term="Bodoni, Giambattista" index="names" main}, nacido en Saluzzo en 1740, aprendió a tallar punzones en la imprenta de Propaganda Fide, en Roma, donde limpió y ordenó sus tipos orientales. En 1768 el duque Fernando lo llamó a Parma para dirigir la imprenta ducal, y allí se quedó el resto de su vida. Sus primeros tipos siguen a Fournier; en la década de 1790 tallaba ya letras de otra especie, con remates finísimos, eje estrictamente vertical y un contraste entre gruesos y finos que solo el papel liso y una tirada cuidadosa podían sostener. Son las letras :index[modernas]{term="Letra moderna" main}. En París, :index[Firmin Didot]{term="Didot, Firmin" index="names"} llegaba a las mismas conclusiones al mismo tiempo, y las dos casas se vigilaban de cerca.:index{term="Didona" see="Letra moderna"} Bodoni imprimió pocos libros que alguien leyera y muchos que todos admiraban: Homero en griego, Horacio, Tasso, folio tras folio para coleccionistas y reyes. La obra de su vida fue el :index[*Manuale tipografico*]{term="*Manuale tipografico*" main}, que su viuda, :index[Margherita Dall’Aglio]{term="Dall’Aglio, Margherita" index="names"}, publicó en 1818, cinco años después de su muerte: dos volúmenes de romanas y cursivas en más de un centenar de cuerpos y cortes, con griego, hebreo y otros alfabetos, y un prefacio que dice cómo debe ser una letra. La letra moderna conquistó el continente y después Inglaterra, donde hacia 1820 apenas quedaba impresor que compusiera libros en letra antigua.:index{term="Letra moderna" range="end"} ## Nombres para las familias Las palabras que ha usado este capítulo, :index[antigua]{term="Letra antigua" main}, de transición y moderna, las pusieron en circulación los impresores del siglo XIX, que necesitaban ordenar sus muestrarios, y no son precisas. En 1860 una antigua era cualquier letra tallada sobre el modelo de Caslon, y una moderna, cualquier cosa posterior a Bodoni, por torpe que fuera. La antigua volvió en la década de 1840, cuando :index[Charles Whittingham]{term="Whittingham, Charles" index="names"}, de la Chiswick Press, recuperó los tipos de Caslon para un libro compuesto a la manera del siglo XVII, y desde entonces las dos convivieron en todos los muestrarios. En 1921 el fundidor francés :index[Francis Thibaudeau]{term="Thibaudeau, Francis" index="names"} propuso clasificar los tipos solo por sus remates: triangulares, filiformes, cuadrados o ninguno.:index{term="Clasificación de los tipos!Thibaudeau, 1921"} Era fácil de aplicar y ciega a la historia. :index[Maximilien Vox]{term="Vox, Maximilien" index="names"}, ilustrador y editor, fue más lejos en 1954 con nueve familias cuyos nombres unen a los maestros de cada estilo: *humanas* para los venecianos que siguieron a Jenson, *garaldas* para Garamond y Aldo, *reales* para las letras de Fournier y Baskerville, *didonas* para Didot y Bodoni.:index{term="Clasificación de los tipos!Vox, 1954" main} La Association Typographique Internationale adoptó una versión en 1962, y la British Standards Institution la siguió en 1967. :index{term="Garalda" see="Letra antigua"} :index{term="Humana, letra" see="Letra romana!veneciana"} Estas etiquetas dejan fuera tanto como ordenan. Las romanas de Gil para la Real Biblioteca son *reales* según el manual y españolas según cualquier otra medida; las cursivas de Pradell deben tanto a Granjon como a cualquier letra tallada en su propio siglo. La disputa entre las dos ideas de la letra, la que se escribe con pluma y la que se dibuja con compás y regla, es más antigua que todos estos nombres y sigue sin resolverse. :::paragraphs{style="colophon"} *Letras de metal*, capítulo cuarto. Compuesto en Crimson Pro, con Bodoni Moda en los títulos y Libre Franklin en los rótulos (SIL Open Font License). Texto escrito para el Recetario de Postext, CC BY 4.0. ::: # Índice onomástico {style="index" kicker="Impresores, grabadores, mecenas"} :::index{index="names"} # Índice de materias {style="index" kicker="Tipos, útiles y libros"} :::index
`; // content.<lang>.md, inlined by the Cookbook // ─── 3 · Fonts ────────────────────────────────────────────────────────────── // Every face the design uses (gotcha: fonts-first). const FONTS = { 'Crimson Pro': ['400', '400i', '600', '700'], 'Bodoni Moda': ['400', '400i', '600'], 'Libre Franklin': ['400', '400i', '600', '700'], }; // ─── 4 · Build & show ─────────────────────────────────────────────────────── await loadFonts(FONTS, markdown); // #region build: one buildDocument, laid out again until the index's page numbers settle // A pass finds the page of every mark and prints both indexes with those numbers. When the // printed index moves a mark (an index set before the text would), the layout runs again, // until no page number changes (in a buildBundle book, the chapter with :::index gets them all). const doc = await buildWithFonts(() => buildDocument({ markdown }, config()), markdown); // #endregion showPages(doc, { title: t({ en: 'Two indexes', es: 'Dos índices' }) }); offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider }), `${RECIPE}.pdf`);
Kit · core, fonts, viewer, pdf: igual a totes les receptes · 275 línies// ─── 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 ───────────────────────────────────────────────────────────────────────

L'script.js compost funciona tal com és: enganxa'l com a script de mòdul en qualsevol pàgina o obre la recepta a CodePen. Carpeta de la recepta a GitHub ↗ (s'obre en una pestanya nova)

Variants

#Imprimeix un únic índex

Treu index="names" de les marques de persones i el segon títol: llavors totes les marques van a l'índex principal, noms i matèries en un sol alfabet.

-:index[Aldo Manucio]{term="Manucio, Aldo" index="names" main}
+:index[Aldo Manucio]{term="Manucio, Aldo" main}

#Treu les lletres de grup

Un onomàstic tan curt es llegeix bé sense aquestes lletres. groups.enabled: false les treu i deixa una línia d'espai entre lletres; l'opció val per als dos índexs.

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

Errors freqüents

Error freqüent

Qualsevol objecte headings desactiva el salt de pàgina de l'H1

Per defecte un H1 salta a una pàgina senar (always-odd), però qualsevol objecte headings anul·la aquest valor, de manera que els capítols van seguits i span: 'page' no fa res. Torna a declarar headings.levels[0].breakBefore: { enabled: true, parity } a cada configuració. Capítols que obren en pàgina senar →

Error freqüent

Carrega totes les fonts abans de compondre

La composició mesura el text amb les fonts que el navegador ha carregat i en desa les amplades, així que una font que arriba després de la primera composició deixa talls de línia erronis i un PDF que ja no coincideix amb la pantalla. Carrega abans tots els pesos i estils, i crida clearMeasurementCache() abans de recompondre si alguna arriba tard. Fonts abans de compondre →

Error freqüent

Una paleta canviada no arriba als elements de disseny ni al color de les remissions

postext 1.4.1 aplica colorPalette als estils de text (cos, títols, llistes, peus, taules, requadres), però no als elements de capçaleres, peus de pàgina, obertures i portadelles, ni a bodyText.referenceColor: conserven l'hex escrit al costat del seu paletteId. Si canvies la paleta, per a una edició de pantalla fosca o per recolorejar, reescriu cada color enllaçat a partir de colorPalette abans de compondre. Paleta de color semàntica →

Error freqüent

El desbordament del text de disseny és 'ellipsis-end' per defecte

Un element de text de disseny que no cap en la seva amplada acaba en punts suspensius per defecte. Posa overflow: 'wrap' als títols que hagin de passar a més línies. Textos, filets i caixes als dissenys de pàgina →

Error freqüent

Només 8 llengües tenen partició de mots, amb el codi exacte

La partició de mots existeix per a en-us, es, fr, de, it, pt, ca i nl, amb el codi exacte: 'es-ES' o qualsevol altra llengua passa sense avís a l'anglès americà. Partició de mots i llengua del document →

Error freqüent

Una configuració es desa a la memòria cau per identitat: crea un objecte nou

El motor desa a la memòria cau les configuracions resoltes segons la identitat de l'objecte, de manera que modificar el mateix objecte i tornar a compondre reutilitza el resultat anterior. Crea un objecte nou a cada composició: per això la configuració d'una recepta és una funció, config(). Pàgines en un canvas →

  • La configuració index és comuna: els dos índexs comparteixen lletra, interlineat i lletres de grup.
  • Una marca en un peu de figura o en una cel·la de taula s'imprimeix tal qual. Marca la persona al paràgraf que cita la figura.

Crèdits

Text
  • “From Textura to Bodoni”, an essay on the history of printing types written for this recipe, in English and Spanish · Ignacio Ferro · CC BY 4.0
Fonts
Crimson Pro (SIL OFL 1.1) · Bodoni Moda (SIL OFL 1.1) · Libre Franklin (SIL OFL 1.1)
SandboxPDF