Saltar al contingut principal
Recepta número 175

Receptari · Capítol 5 · Estructura del llibre

Una guia de museu amb els enllaços impresos com a codis QR

Un passeig de butxaca per un museu amb els enllaços impresos com a notes numerades al peu, amb adreça curta i codi QR (links.print a notes).

En aquesta pàgina
Sortida
Canvas · PDF
Nivell
Intermedi
Postext
Provada amb Postext 1.65.0
Requereix ≥ 1.65.0 · postext-pdf ≥ 1.65.0
Llicència
Actualitzada el 31 d’oct. del 2026
Codi MIT · Text CC BY 4.0
  • Mostra en castellà: encara no hi ha edició en català
  • Format 160 × 240 mm
  • 1 columna
  • Newsreader 10,2/14
  • Archivo
  • Instrument Serif
  • 7 pàgines
  • Nivell
  • Postext 1.65.0
  • Compost en 183 ms
  • 177 línies de codi

En poques paraules

Una petita guia impresa de cinc quadres. On el text esmenta una pàgina web, un número petit porta al peu de la pàgina, on un codi quadrat obre aquesta pàgina al mòbil.

Què compondràs

Una guia de butxaca de les sales holandeses del Metropolitan Museum of Art: cinc quadres, una hora a peu, un quadre per pàgina. La coberta és la noia de Vermeer al costat de la finestra, tallada per la vora superior. Cada parada s'obre amb el número de sala, el títol i el pintor, després el quadre amb la cartel·la al costat i després tres paràgrafs breus. El text enllaça amb el catàleg del museu com ho faria qualsevol pàgina web. En paper, cada enllaç es converteix en un número petit, i al peu de la pàgina la nota dona l'adreça curta i un codi QR al qual el visitant acosta el mòbil davant del quadre.

Aquesta recepta respon a

  • Com imprimeixo les adreces web dels enllaços d'un llibre, com a notes numerades o codis QR que el lector pugui escanejar?

La resposta curta

script.js · línies 39–50al codi complet
const links = {
  print: 'notes', // a note marker after the link text, the address in a note at the foot
  show: 'both', // the short address for reading, the code for the phone
  urlDisplay: 'short', // metmuseum.org/art/collection/search/436616: no https://, no www.
  repeat: 'first', // an address noted once is cited again by its number, never noted twice
  qr: { size: mm(16), side: 'start', gap: em(1.2), color: col('ink') },
};
// Link notes are footnotes: they take the note style and the numbering below. 'document'
// numbers them through the walk, so the five stops print notes 3 to 7, not five notes 1.
const footnotes = { placement: 'column', numbering: 'document', fontSize: pt(8),
  lineHeight: pt(10.5), color: col('muted'), spaceAbove: pt(LEAD), spaceBetween: pt(5),
  separator: { enabled: true, width: 0.18, lineWidth: pt(0.5), color: col('rule') } };

Ingredients

Tipografia
Newsreader, Instrument Serif, Archivo (SIL OFL 1.1)
Recursos
  • hals-1200.jpg
  • rembrandt-1200.jpg
  • ruisdael-1400.jpg
  • steen-1200.jpg
  • vermeer-1600.jpg
  • vermeer-cover-1800.jpg
  • Johannes Vermeer, Young Woman with a Water Pitcher, about 1662 (the cover, cropped) (The Metropolitan Museum of Art, CC0 1.0)
  • Frans Hals, Young Man and Woman in an Inn, 1623 (stop 1) (The Metropolitan Museum of Art, CC0 1.0)
  • Jan Steen, Merry Company on a Terrace, about 1670 (stop 2) (The Metropolitan Museum of Art, CC0 1.0)
  • Rembrandt, Aristotle with a Bust of Homer, 1653 (stop 3) (The Metropolitan Museum of Art, CC0 1.0)
  • Johannes Vermeer, Young Woman with a Water Pitcher, about 1662 (stop 4) (The Metropolitan Museum of Art, CC0 1.0)
  • Jacob van Ruisdael, Wheat Fields, about 1670 (stop 5) (The Metropolitan Museum of Art, CC0 1.0)

Elaboració

#1 · Escriu enllaços, imprimeix notes

El codi és la resposta curta de dalt. El Markdown conserva enllaços normals, [La fitxa del catàleg](https://www.metmuseum.org/…/436616), i links.print: 'notes' converteix cadascun en una nota numerada amb l'adreça i el codi (enllaços en paper). Les notes són notes al peu: numbering: 'document' les compta al llarg del passeig, de manera que el lector troba de l'1 al 7, i repeat: 'first' deixa que l'última parada citi el Hals per la nota 3 en lloc d'imprimir dues vegades el mateix codi. Al PDF el codi es dibuixa en vectors i és ell mateix un enllaç.

El peu de la parada 1: la nota 3, la fitxa del quadre com a adreça curta al costat d'un codi de 16 mm.

#2 · Una parada per pàgina

script.js · línies 74–82al codi complet
const stop = { enabled: true, minHeight: pt(4 * LEAD), slot: { elements: [
    { kind: 'text', id: 'stop', content: '{attr.stop}', ...caps(7.5), color: col('delft'),
      align: 'left', placement: at('container', 'top-left', 0, 0) },
    { kind: 'text', id: 'title', content: '{titleText}', fontFamily: DISPLAY, fontSize: pt(22),
      lineHeight: 1.05, color: col('ink'), align: 'left', placement: at('#stop', 'below', 0, 1.6) },
    { kind: 'text', id: 'artist', content: '{attr.artist}', fontFamily: TEXT, italic: true,
      fontSize: pt(10.5), color: col('muted'), align: 'left',
      placement: at('#title', 'below', 0, 0.8) },
  ] } };

Cada parada és un títol de segon nivell amb un breakBefore que obre pàgina, parella o senar, així que el visitant passa un full per quadre. El número de sala i el pintor són atributs del títol; el disseny els col·loca a sobre i a sota del títol, i minHeight deixa el quadre a la mateixa altura a totes les pàgines.

#3 · La cartel·la al costat del quadre

script.js · línies 219–259al codi complet
// Paintings are not numbered: their own type, with no caption prefix and no number.
const paintingType = { id: 'painting', name: t({ en: 'Painting', es: 'Cuadro' }),
  shortLabel: t({ en: 'painting', es: 'cuadro' }), captionPrefix: '', numberingTemplate: '',
  resetOn: 'never', counterFormat: 'decimal' };
// Set where the ::resource line stands, under the stop's heading; caption and note beside it.
const HERE = { position: 'here', captionSide: 'inside' };
const cm = (n) => (LANG === 'es' ? String(n).replace('.', ',') : String(n)); // decimal comma
// The wall label: medium, size in cm (no-break spaces) and the museum's credit line.
const painting = (id, w, h, [cmH, cmW], credit, alt, width = 0.5) => ({
  id, typeId: 'painting', kind: 'bitmap', createdAt: 0, updatedAt: 0, placement: { ...HERE, width },
  bitmap: { fileId: `${id}-${Math.max(w, h)}.jpg`, format: 'jpeg', width: w, height: h },
  caption: `${OIL}, ${cm(cmH)}\u00a0×\u00a0${cm(cmW)}\u00a0cm. ${credit}`, altText: alt,
  note: 'The Metropolitan Museum of Art, Open Access (CC0)' });
const OIL = t({ en: 'Oil on canvas', es: 'Óleo sobre lienzo' });
const ALTMAN = t({ en: 'Bequest of Benjamin Altman, 1913', es: 'Legado de Benjamin Altman, 1913' });
const resources = [
  painting('hals', 908, 1200, [105.4, 79.4], `${ALTMAN} (14.40.602)`, t({
    en: 'A laughing young man raises a glass; a young woman leans on his shoulder.',
    es: 'Un joven ríe y alza una copa; una joven se apoya en su hombro.' }), 0.4), // portrait
  painting('steen', 1115, 1200, [141, 131.4], 'Fletcher Fund, 1958 (58.89)', t({
    en: 'A crowded party under a vine, with a lute player, a child and a dog.',
    es: 'Una fiesta concurrida bajo una parra, con un laudista, un niño y un perro.' })),
  painting('rembrandt', 1139, 1200, [143.5, 136.5], t({
    en: 'Purchase, special contributions and funds given or bequeathed by friends of the Museum',
    es: 'Compra con aportaciones y fondos donados o legados por amigos del Museo' })
    + ', 1961 (61.198)', t({ en: 'A bearded man in a black hat rests his hand on a marble bust.',
    es: 'Un hombre barbado con sombrero negro apoya la mano en un busto de mármol.' })),
  painting('vermeer', 1422, 1600, [45.7, 40.6], t({ en: 'Marquand Collection, gift of',
    es: 'Colección Marquand, donación de' }) + ' Henry G. Marquand, 1889 (89.15.21)', t({
    en: 'A young woman at an open window holds a gilt pitcher over a basin.',
    es: 'Una joven junto a una ventana abierta sujeta una jarra dorada sobre una jofaina.' })),
  painting('ruisdael', 1400, 1084, [100, 130.2], `${ALTMAN} (14.40.623)`, t({
    en: 'A sandy road between wheat fields under a tall bank of cloud.',
    es: 'Un camino de arena entre campos de trigo bajo un alto banco de nubes.' }), 0.6),
  // The cover: the Vermeer cut to the box's 4 : 3 (160 × 120 mm), since the opener fits a
  // picture inside its box. Drawn by the cover design, never cited.
  { ...painting('vermeer-cover', 1800, 1350, [], '', t({
    en: 'Detail of Vermeer’s young woman at the window, in blue and white.',
    es: 'Detalle de la joven de Vermeer junto a la ventana, en azul y blanco.' })),
  caption: '', note: '' },
];

captionSide: 'inside' posa el peu i la nota de crèdit al costat del quadre, com penja un museu la cartel·la, i width dona a cada quadre la seva part de la mesura: menys al Hals, que és alt, i més al Ruisdael, que és apaïsat, de manera que les cinc pàgines tenen una altura de text semblant. La cartel·la es fa amb la tècnica, les mides en centímetres amb espais de no separació i la línia de crèdit del museu, amb coma decimal a l'edició castellana.

#4 · La parada a la capçalera

script.js · línies 86–96al codi complet
const head = (id, content, parity, edge, x, extra = {}) => ({
  kind: 'text', id, content, parity, pages: 'body', ...caps(7, 600), color: col('muted'),
  placement: at('page', edge, x, 13), ...extra,
});
const folio = { ...caps(8, 700), color: col('ink') };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
  head('verso-title', '{title}', 'even', 'top-left', OUTER + 9),
  head('recto-stop', '{firstMark.h2}', 'odd', 'top-right', -(OUTER + 9)),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
] };

{firstMark.h2} imprimeix el títol de la parada que comença a la pàgina, així que la pàgina senar anomena el quadre i la parella, la guia. Cap capçalera no arriba a la coberta, una pàgina que fa d'obertura.

La recepta completa

Sandbox
// ═══ Postext Cookbook · Nº 175 · A museum walk whose links print as QR notes ═════
// https://postext.dev/en/cookbook/museum-guide-qr-link-notes
// Code: MIT · Text: original (CC BY 4.0) · Paintings: The Metropolitan Museum of Art (CC0)
// Fonts: Newsreader, Instrument Serif, Archivo (SIL OFL 1.1) · Needs postext ≥ 1.65.0
// A pocket gallery guide: five paintings, one to a page. The Markdown writes the museum's
// catalogue pages as ordinary links; the print edition turns each one into a numbered note at
// the foot of the page, with the short address and a code a phone can scan.
import {
  buildDocumentWithFonts, renderPageToCanvas, 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 ('en' | 'es')
const RECIPE = 'museum-guide-qr-link-notes';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: Delft blue on warm paper, every colour linked by id
const palette = {
  ink: '#1f1d1a', // text: a warm near-black
  delft: '#2b4a7a', // the one accent: kickers, stop numbers, rules (8.6:1 on paper)
  rule: '#c6c9cf', // hairlines
  muted: '#5f6168', // running heads, notes, credits (6.1:1 on paper)
  paper: '#ffffff',
};
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = [
  ...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })),
  // The engine's defaults link to main-color: point it at the accent, so nothing prints blue.
  { id: 'main-color', name: 'delft (defaults)', value: { hex: palette.delft, model: 'hex' } },
];
// #endregion
const TRIM = { w: 160, h: 240 }; // a large pocket guide, in mm
const TOP = 22; // top margin in mm: the cover's container starts here
const OUTER = 22; // outer margin in mm: folios and running heads align to it
const LEAD = 14; // body leading in pt: the baseline grid
const [TEXT, DISPLAY, LABEL] = ['Newsreader', 'Instrument Serif', 'Archivo']; // three voices

// #region answer: every web link of the guide prints as a numbered note with a code to scan
const links = {
  print: 'notes', // a note marker after the link text, the address in a note at the foot
  show: 'both', // the short address for reading, the code for the phone
  urlDisplay: 'short', // metmuseum.org/art/collection/search/436616: no https://, no www.
  repeat: 'first', // an address noted once is cited again by its number, never noted twice
  qr: { size: mm(16), side: 'start', gap: em(1.2), color: col('ink') },
};
// Link notes are footnotes: they take the note style and the numbering below. 'document'
// numbers them through the walk, so the five stops print notes 3 to 7, not five notes 1.
const footnotes = { placement: 'column', numbering: 'document', fontSize: pt(8),

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 l'adreça a la línia

Per a una guia que es llegeix a casa, 'inline' afegeix l'adreça entre parèntesis després del text de l'enllaç i no posa nota ni codi.

-  print: 'notes', // a note marker after the link text, the address in a note at the foot
-  show: 'both', // the short address for reading, the code for the phone
+  print: 'inline', // (metmuseum.org/…) after the link text

#Codis sense adreça

show: 'qr' imprimeix només el codi al costat del número de nota, per a un cartell o una cartel·la on l'adreça només ocuparia lloc.

-  show: 'both', // the short address for reading, the code for the phone
+  show: 'qr', // the code alone

Errors freqüents

Una nota amb codi fa com a mínim el que fa el codi. Quan la línia que porta l'enllaç no té lloc a sota per a la nota a la pàgina, la línia passa a la següent amb la nota; deixa els enllaços fora de les últimes línies d'una pàgina que ha d'acabar on acaba, com el primer paràgraf de la coberta d'aquesta guia.

Crèdits

Text
Text original, CC BY 4.0
Imatges
Fonts
Newsreader (SIL OFL 1.1) · Instrument Serif (SIL OFL 1.1) · Archivo (SIL OFL 1.1)
SandboxPDF