Saltar al contenido principal
Receta número 180

Recetario · Capítulo 2 · Texto y tipografía

Resaltados por colores en un libro de lecturas

Un libro de lecturas de historia con tres colores de resaltado (verde para términos, ámbar para fechas, azul para nombres) definidos en markStyles.

  • Formato 170 × 240 mm
  • 1 columna
  • Source Serif 4 10,5/14,5
  • Source Sans 3
  • 4 páginas
  • Nivel
  • Postext 1.65.0
  • Compuesto en 123 ms
  • 164 líneas de código

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

script.js · líneas 37–52en el código completo
// :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

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

script.js · líneas 14–29en el código completo
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

script.js · líneas 56–81en el código completo
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

script.js · líneas 85–106en el código completo
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

Sandbox
// ═══ 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 band

Errores 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 markStyles no 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

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)
Sandbox