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
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
- Funcions
- Adreces dels enllaços en paperNotes a peu de pàginaObertures dissenyadesAtributs de títolImatges als dissenys de pàginaParaules guia a la capçaleraCapçaleres i folisCapçaleres segons el tipus de pàginaFigures just aquíPeus lateralsTipus de recurs propisLínies de font i crèditPaleta de color semànticaExportació a PDF
- També fa servir
- Banda de capítol d'amplada completaEstils de paràgrafFonts incrustades al PDFFigures i taules com a recursos
- Tipografia
- Newsreader, Instrument Serif, Archivo (SIL OFL 1.1)
- Recursos
hals-1200.jpgrembrandt-1200.jpgruisdael-1400.jpgsteen-1200.jpgvermeer-1600.jpgvermeer-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ç.

#2 · Una parada per pàgina
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
// 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
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
// ═══ 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 aloneErrors 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
- Recepta
- Ignacio Ferro
- Text
- Text original, CC BY 4.0
- Imatges
- 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
- Fonts
- Newsreader (SIL OFL 1.1) · Instrument Serif (SIL OFL 1.1) · Archivo (SIL OFL 1.1)
- Codi
- MIT, com Postext
Editar aquest text ↗ (s'obre en una pestanya nova)Carpeta de la recepta a GitHub ↗ (s'obre en una pestanya nova)


