En pocas palabras
Una pequeña guía impresa de cinco cuadros. Donde el texto menciona una página web, un número pequeño lleva al pie de la página, donde un código cuadrado abre esa página en el móvil.
Lo que vas a componer
Una guía de bolsillo de las salas holandesas del Metropolitan Museum of Art: cinco cuadros, una hora a pie, un cuadro por página. La portada es la joven de Vermeer junto a la ventana, cortada por el borde superior. Cada parada abre con su número de sala, el título y el pintor, luego el cuadro con su cartela al lado y después tres párrafos breves. El texto enlaza con el catálogo del museo como lo haría cualquier página web. En papel, cada enlace se convierte en un número pequeño, y al pie de la página la nota da la dirección corta y un código QR al que el visitante acerca el móvil delante del cuadro.
Esta receta responde a
- ¿Cómo imprimo las direcciones web de los enlaces de un libro, como notas numeradas o códigos QR que el lector pueda escanear?
La respuesta corta
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') } };
Ingredientes
- Funciones
- Direcciones de los enlaces en papelNotas al pieAperturas diseñadasAtributos de títuloImágenes en los diseños de páginaPalabras guía en la cabeceraCabeceras y foliosCabeceras según el tipo de páginaFiguras justo aquíPies lateralesTipos de recurso propiosLíneas de fuente y créditoPaleta de color semánticaExportación a PDF
- También usa
- Banda de capítulo a todo el anchoEstilos de párrafoFuentes incrustadas en el PDFFiguras y tablas como recursos
- Tipografía
- 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, Joven con una jarra de agua, hacia 1662 (la portada, recortada) (The Metropolitan Museum of Art, CC0 1.0)
- Frans Hals, Joven pareja en una posada, 1623 (parada 1) (The Metropolitan Museum of Art, CC0 1.0)
- Jan Steen, Alegre compañía en una terraza, hacia 1670 (parada 2) (The Metropolitan Museum of Art, CC0 1.0)
- Rembrandt, Aristóteles con un busto de Homero, 1653 (parada 3) (The Metropolitan Museum of Art, CC0 1.0)
- Johannes Vermeer, Joven con una jarra de agua, hacia 1662 (parada 4) (The Metropolitan Museum of Art, CC0 1.0)
- Jacob van Ruisdael, Campos de trigo, hacia 1670 (parada 5) (The Metropolitan Museum of Art, CC0 1.0)
Elaboración
#1 · Escribe enlaces, imprime notas
El código es la respuesta corta de arriba. El Markdown conserva enlaces normales, [La ficha del catálogo](https://www.metmuseum.org/…/436616), y links.print: 'notes' convierte cada uno en una nota numerada con la dirección y el código (enlaces en papel). Las notas son notas al pie: numbering: 'document' las cuenta a lo largo del paseo, así que el lector encuentra de la 1 a la 7, y repeat: 'first' deja que la última parada cite el Hals por su nota 3 en vez de imprimir dos veces el mismo código. En el PDF el código se dibuja en vectores y es en sí mismo un enlace.

#2 · Una parada por 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 es un título de segundo nivel cuyo breakBefore abre página, par o impar, así que el visitante pasa una hoja por cuadro. El número de sala y el pintor son atributos del título; el diseño los coloca encima y debajo del título, y minHeight deja el cuadro a la misma altura en todas las páginas.
#3 · La cartela junto al cuadro
// 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' pone el pie y su nota de crédito junto al cuadro, como cuelga un museo su cartela, y width da a cada cuadro su parte de la medida: menos al Hals, que es alto, y más al Ruisdael, que es apaisado, de modo que las cinco páginas tienen una altura de texto parecida. La cartela se arma con la técnica, las medidas en centímetros con espacios de no separación y la línea de crédito del museo, con coma decimal en la edición española.
#4 · La parada en la cabecera
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} imprime el título de la parada que empieza en la página, así que la página impar nombra el cuadro y la par, la guía. Ninguna cabecera llega a la portada, una página cuyo papel es el de apertura.
La receta 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),
El script.js compuesto funciona tal cual: pégalo como script de módulo en cualquier página o abre la receta en CodePen. Carpeta de la receta en GitHub ↗ (se abre en una nueva pestaña)
Variantes
#Imprime la dirección en la línea
Para una guía que se lee en casa, 'inline' añade la dirección entre paréntesis tras el texto del enlace y no pone nota ni código.
- 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#Códigos sin dirección
show: 'qr' imprime solo el código junto al número de nota, para un cartel o una cartela donde la dirección solo ocuparía sitio.
- show: 'both', // the short address for reading, the code for the phone
+ show: 'qr', // the code aloneErrores frecuentes
Una nota con código mide al menos lo que mide el código. Cuando la línea que lleva el enlace no tiene sitio debajo para su nota en la página, la línea pasa a la siguiente con su nota; deja los enlaces fuera de las últimas líneas de una página que tiene que acabar donde acaba, como el primer párrafo de la portada de esta guía.
Créditos
- Receta
- Ignacio Ferro
- Texto
- Texto original, CC BY 4.0
- Imágenes
- Johannes Vermeer, Joven con una jarra de agua, hacia 1662 (la portada, recortada) · The Metropolitan Museum of Art · CC0 1.0
- Frans Hals, Joven pareja en una posada, 1623 (parada 1) · The Metropolitan Museum of Art · CC0 1.0
- Jan Steen, Alegre compañía en una terraza, hacia 1670 (parada 2) · The Metropolitan Museum of Art · CC0 1.0
- Rembrandt, Aristóteles con un busto de Homero, 1653 (parada 3) · The Metropolitan Museum of Art · CC0 1.0
- Johannes Vermeer, Joven con una jarra de agua, hacia 1662 (parada 4) · The Metropolitan Museum of Art · CC0 1.0
- Jacob van Ruisdael, Campos de trigo, hacia 1670 (parada 5) · The Metropolitan Museum of Art · CC0 1.0
- Fuentes
- Newsreader (SIL OFL 1.1) · Instrument Serif (SIL OFL 1.1) · Archivo (SIL OFL 1.1)
- Código
- MIT, como Postext
Editar este texto ↗ (se abre en una nueva pestaña)Carpeta de la receta en GitHub ↗ (se abre en una nueva pestaña)


