En pocas palabras
Una lectura escolar sobre la historia de la imprenta. Los términos clave van en verde, las fechas en ámbar y las personas y lugares en azul, y una clave en la primera página explica qué significa cada color.
Lo que vas a componer
La lectura 2 de una unidad de un libro escolar de historia, Lecturas de historia moderna: cuatro páginas sobre cómo se extendió la imprenta desde Maguncia por Europa entre 1450 y 1550, en una página de 170 × 240 mm. Los editores han marcado el texto como lo haría un buen estudiante, con un código que no cambia en todo el libro: los términos clave sobre una franja verde, las fechas sobre ámbar, las personas y los lugares sobre azul pálido. Una clave bajo el título explica el código con los tres colores, y el glosario de la última página abre cada entrada con su término en verde. Las marcas son franjas debajo de texto corriente, así que las líneas se cortan, se dividen y se justifican como si no estuvieran.
Esta receta responde a
- ¿Cómo resalto las palabras clave de un texto de estudio con colores que signifiquen algo cada uno (términos, fechas, nombres)?
La respuesta corta
// :mark[Mainz]{style="name"} in the text picks a style by id. The band is painted under
// words that stay ordinary text, so the lines break, hyphenate and justify as if the
// mark were not there, and a mark that runs over a line end gets a band on each line.
const band = (id, name) => ({
id, name,
background: col(id), // pale enough for ink at full contrast on top
color: col('ink'), // the words keep the colour of the text
// borderRadius: 0 is the default (square ends, as a highlighter pen leaves them)
paddingX: em(0.12), // the band reaches a little past the first and the last letter
paddingTop: em(0.04), paddingBottom: em(0.1), // and down past the descenders
});
const markStyles = [
band('term', t({ en: 'Key term', es: 'Término clave' })),
band('date', t({ en: 'Date', es: 'Fecha' })),
band('name', t({ en: 'Name', es: 'Nombre' })),
];
Ingredientes
- Funciones
- Marcas de resaltadoRecuadrosPaleta de color semánticaAperturas diseñadasImágenes en los diseños de páginaAtributos de títuloCabeceras y foliosCabeceras según el tipo de páginaMárgenes simétricosEstilos de párrafoViudas, huérfanas y líneas cortas
- También usa
- Bibliografías y glosariosFigura y Tabla en tu idiomaTipos de recurso propiosFiguras y tablas como recursos
- Tipografía
- Source Serif 4, Source Sans 3 (SIL OFL 1.1)
- Recursos
press-1400.jpg- El taller de imprenta de la página de apertura, una pintura (Generated With Diffusion Models, original)
Elaboración
#1 · Un estilo para cada clase de palabra
El código es la respuesta corta de arriba. markStyles declara tres estilos con nombre, y el texto elige uno en cada marca: :mark[Johann Fust]{style="name"}, :mark[1455]{style="date"}, :mark[colofón]{style="term"} (marcas de resaltado). Cada estilo dice lo que significa (name es lo que ve quien edita), de modo que el Markdown guarda por qué se marca una palabra y no de qué color va. Si cambias el color de un estilo, cambian con él todas las fechas del libro. Los extremos son rectos, como por defecto desde postext 1.52 (estilos de resaltado): un fosforito deja los extremos rectos, y una franja redondeada se lee como un chip.
#2 · Tres tintas de la paleta
const palette = {
ink: '#1f1c19', // text: a warm near-black
accent: '#8a3220', // kickers, rules, numbers: an iron-gall red
tint: '#f5f0e7', // the key's background
rule: '#d3cabc', // hairlines
muted: '#6b645b', // running heads, colophon
paper: '#ffffff',
term: '#cce8c8', // key terms: a pale green
date: '#fbe0a1', // dates: amber
name: '#cfe0f4', // people and places: a pale blue
};
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: 'accent (defaults)', value: { hex: palette.accent, model: 'hex' } },
];
Las tres franjas son entradas de la paleta, igual que la tinta y el acento, así que quien abra el libro en el Sandbox puede cambiar el código desde una sola muestra de color. Son pálidas, de una luminosidad parecida a la del fondo del recuadro, y el texto sobre ellas conserva un contraste de 12,6:1 o más; las tres se distinguen cuando se juntan en una línea, como en «En 1517, Martín Lutero» en la página 3. Una marca no ocupa sitio: paddingX entra 0,12 em en el espacio junto a la frase y paddingBottom baja la franja por debajo de los trazos descendentes, y ambos se pintan en un espacio que ya existía.
#3 · La apertura: una pintura sobre el título
const opener = {
enabled: true,
minHeight: mm(122), // the floor of the opener: the key starts on the same line
marginBottom: pt(0),
slot: {
elements: [
{ kind: 'image', id: 'press', resourceId: 'press', fit: 'cover',
placement: { anchor: { to: 'page', edge: 'top-left' },
size: { width: mm(170), height: mm(96) } } },
{ kind: 'text', id: 'kicker', content: '{attr.kicker}', fontFamily: SANS,
fontSize: pt(8.5), fontWeight: 700, letterSpacing: pt(1.6),
textTransform: 'uppercase', color: col('accent'), align: 'left',
placement: { anchor: { to: 'container', edge: 'top-left' },
offset: { x: mm(0), y: mm(82) } } },
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: SERIF,
fontSize: pt(26), lineHeight: 1.08, fontWeight: 700, color: col('ink'), align: 'left',
placement: { anchor: { to: '#kicker', edge: 'below' }, offset: { x: mm(0), y: mm(2) },
size: { width: mm(126), height: 'auto' } } },
{ kind: 'text', id: 'standfirst', content: '{attr.standfirst}', fontFamily: SERIF,
italic: true, fontSize: pt(10.5), lineHeight: LEAD / 10.5, color: col('ink'),
align: 'left',
placement: { anchor: { to: '#title', edge: 'below' }, offset: { x: mm(0), y: mm(3) },
size: { width: mm(118), height: 'auto' } } },
],
},
};
El título de primer nivel imprime un diseño. La pintura se ancla a la página, así que sangra por arriba y por los dos lados, 170 por 96 mm, con fit: 'cover'. El antetítulo y la entradilla salen de atributos de la línea del título (atributos de título), y la clave sigue como un :::callout corriente cuyos elementos de lista empiezan por las tres marcas. minHeight fija la profundidad: la clave empieza en la misma línea por mucho que ocupe el título.
#4 · Cabeceras según la paridad
const head = (id, content, parity, edge, x, extra = {}) => ({
kind: 'text', id, content, parity, pages: 'body',
fontFamily: SANS, fontSize: pt(7.5), fontWeight: 600, letterSpacing: pt(1.2),
textTransform: 'uppercase', color: col('muted'),
placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(13) } },
...extra,
});
const folio = { fontWeight: 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-title', '{chapterTitle}', 'odd', 'top-right', -(OUTER + 9)),
head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
],
};
const footer = {
elements: [head('drop-folio', '{pageNumber}', 'all', 'top', 0, {
...folio, pages: 'opener',
placement: { anchor: { to: 'container', edge: 'top' }, offset: { x: mm(0), y: mm(9) } },
})],
};
La página par lleva el título de la colección y la impar el de la lectura, ambas filtradas por parity y fuera de la apertura gracias a pages: 'body' (elementos de texto). La apertura lleva el folio al pie.
La receta completa
// ═══ Postext Cookbook · Nº 180 · Colour-coded highlights in a study reader ════════
// https://postext.dev/en/cookbook/colour-coded-study-highlights
// Code: MIT · Text: original (CC BY 4.0) · Picture: painting made for the recipe
// Fonts: Source Serif 4, Source Sans 3 (SIL OFL 1.1) · Needs postext ≥ 1.65.0
import {
buildDocumentWithFonts, renderPageToCanvas, registerResourceImage, defaultResourceTypes,
} from 'https://esm.sh/postext';
const LANG = 'es'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'colour-coded-study-highlights';
// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: semantic colours, three of them for the highlighter
const palette = {
ink: '#1f1c19', // text: a warm near-black
accent: '#8a3220', // kickers, rules, numbers: an iron-gall red
tint: '#f5f0e7', // the key's background
rule: '#d3cabc', // hairlines
muted: '#6b645b', // running heads, colophon
paper: '#ffffff',
term: '#cce8c8', // key terms: a pale green
date: '#fbe0a1', // dates: amber
name: '#cfe0f4', // people and places: a pale blue
};
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: 'accent (defaults)', value: { hex: palette.accent, model: 'hex' } },
];
// #endregion
const OUTER = 20; // outer margin in mm: the running heads align to it
const LEAD = 14.5; // body leading in pt
const SANS = 'Source Sans 3';
const SERIF = 'Source Serif 4';
// #region answer: three highlight styles, one per kind of word, linked to the palette
// :mark[Mainz]{style="name"} in the text picks a style by id. The band is painted under
// words that stay ordinary text, so the lines break, hyphenate and justify as if the
// mark were not there, and a mark that runs over a line end gets a band on each line.
const band = (id, name) => ({
id, name,
background: col(id), // pale enough for ink at full contrast on top
color: col('ink'), // the words keep the colour of the text
// borderRadius: 0 is the default (square ends, as a highlighter pen leaves them)
paddingX: em(0.12), // the band reaches a little past the first and the last letter
paddingTop: em(0.04), paddingBottom: em(0.1), // and down past the descenders
});
const markStyles = [
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
#Redondea los extremos de las franjas
Un radio de una quinta parte del cuerpo da los extremos suaves de un rotulador.
- // borderRadius: 0 is the default (square ends, as a highlighter pen leaves them)
+ borderRadius: em(0.2), // soft ends, as a felt-tip pen leaves them#Da a cada línea su propia franja
Con decorationBreak: 'clone', una marca que pasa de línea lleva el relleno en los dos extremos de cada trozo, en lugar de quedar recta donde se corta la línea.
paddingX: em(0.12), // the band reaches a little past the first and the last letter
+ decorationBreak: 'clone', // each line piece gets a whole bandErrores frecuentes
- El color solo no sobrevive a una fotocopia en blanco y negro, que es como llegan muchas lecturas al aula. El glosario pone además los términos en negrita; si tus lectores van a fotocopiar las páginas, da a una de las tres clases una segunda señal propia.
- Un id de estilo que
markStylesno declara toma el primer estilo, sin ningún error en la página (el Sandbox lo señala). Usa ids cortos y revisa cómo los escribes en el texto.
Créditos
- Receta
- Ignacio Ferro
- Texto
- La lectura, su clave, las preguntas de repaso y el colofón · Ignacio Ferro · CC BY 4.0
- Imágenes
- El taller de imprenta de la página de apertura, una pintura · Generated With Diffusion Models · original
- Fuentes
- Source Serif 4 (SIL OFL 1.1) · Source Sans 3 (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)


