Saltar al contenido principal
Receta número 176

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

Cifras elzevirianas, de caja alta y tabulares

Las cuentas de Walden: figureStyle da cifras de caja alta a los títulos en versalitas y cifras tabulares a las cuentas; la prosa conserva las elzevirianas.

En esta página
Salida
Canvas · PDF
Postext
Probada con Postext 1.65.0
Requiere ≥ 1.65.0 · postext-pdf ≥ 1.65.0
Licencia
Actualizada el 31 oct 2026
Código MIT · Texto CC BY 4.0
  • Formato 140 × 216 mm
  • 1 columna
  • Alegreya 10,8/14,5
  • Alegreya SC
  • Alegreya SC@lnum
  • Alegreya Sans
  • Alegreya@tnum
  • 6 páginas
  • Nivel
  • Postext 1.65.0
  • Compuesto en 97 ms
  • 160 líneas de código

En pocas palabras

Una edición breve de las cuentas domésticas que Thoreau publicó en Walden. Las cifras del texto son pequeñas como las minúsculas, los años de los títulos miden lo que las mayúsculas y las sumas de las cuentas se alinean en columna.

Lo que vas a componer

Una edición breve de las cuentas que Thoreau publicó en el primer capítulo de Walden: lo que le costó la casa, lo que le dio el campo de judías, lo que comió en ocho meses. Va en una página de 140 × 216 mm con la familia Alegreya, y la apertura lleva el grabado de 1854 de la cabaña. Las cifras cambian según el trabajo que hacen. En la prosa, marzo de 1845 y $13,34 van en cifras elzevirianas, que suben y bajan como las minúsculas, y las fechas no destacan en la línea. Los títulos van en versalitas, y sus años miden lo que las mayúsculas. En las cuentas todas las cifras tienen el mismo ancho, de modo que los centavos de trece líneas de tablas, listones y clavos quedan uno debajo de otro hasta el total.

Esta receta responde a

  • ¿Cómo compongo cifras elzevirianas en el texto, cifras de caja alta en los títulos y cifras tabulares en las tablas?
  • ¿Cómo compongo ilustraciones sin numerar: adornos, viñetas, logotipos?

La respuesta corta

script.js · líneas 34–55en el código completo
// Alegreya's own figures are old-style, so the prose asks for none: the cut Fontsource
// serves has no onum, and 'oldstyle' would change nothing (see the write-up).
const bodyText = {
  fontFamily: TEXT, fontSize: pt(10.8), lineHeight: pt(LEAD), color: col('ink'),
  boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
  textAlign: 'justify', firstLineIndent: mm(4.5), indentAfterHeading: false,
  hyphenation: { enabled: true }, optimalLineBreaking: true,
  avoidWidows: true, avoidOrphans: true, avoidRunts: true,
};
// Small capitals take lining figures (lnum), at the height of the capitals around them.
const section = { level: 2, fontFamily: CAPS, fontSize: pt(12.5), lineHeight: pt(LEAD),
  fontWeight: 400, letterSpacing: pt(0.6), color: col('accent'), figureStyle: 'lining',
  marginTop: pt(LEAD), marginBottom: pt(0) };
// The accounts: every figure on one width (tnum), so the cents stand in columns.
// 'tabular' keeps the font's form, old-style; 'lining-tabular' would set them at cap height.
const tableStyle = {
  figureStyle: 'tabular', bodyFontFamily: TEXT, bodyFontSize: pt(9.8), bodyColor: col('ink'),
  rules: 'booktabs', groupRules: true, borderColor: col('rule'), lightRuleWidth: pt(0.4),
  heavyRuleWidth: pt(0.7), headerBackgroundEnabled: false, headerFontFamily: TEXT,
  headerFontSize: pt(9.8), headerColor: col('ink'), headerBold: false, headerItalic: false,
  cellPadding: mm(0.7),
};

Ingredientes

Tipografía
Alegreya, Alegreya SC, Alegreya Sans (SIL OFL 1.1)
Recursos
  • cabin-860.jpg
  • La viñeta de la cabaña de Thoreau en la portada de la primera edición (1854), recortada y ajustada a negro sobre blanco (Ticknor and Fields, Boston, 1854 (engraver unknown), dominio público)

Elaboración

#1 · Un estilo de cifras por estilo de texto

El código de este paso es la respuesta corta, más arriba. figureStyle pide a la fuente de un estilo sus cifras OpenType (estilos de cifras). El cuerpo no pide nada, porque las cifras propias de Alegreya ya son elzevirianas y proporcionales: el 1 mide 342 unidades y el 0, 533. Los títulos de segundo nivel piden a Alegreya SC 'lining', sus cifras lnum, que miden lo que las mayúsculas que las rodean. El estilo de tabla pide 'tabular', el tnum de la fuente: las cifras conservan su forma elzeviriana y toman todas el mismo ancho, 450 unidades, así que una columna de precios se lee de arriba abajo. Con 'lining-tabular' irían a la altura de las mayúsculas (mira «Variantes»).

#2 · Las cuentas son tablas de cinco columnas

script.js · líneas 59–122en el código completo
// A row starting '=' is a total (header cells: a light rule above it), '+' heads a group.
const C = (content, align = 'left', extra = {}) => ({ content, align, ...extra });
function account(tsv) {
  const rows = tsv.replace(/^\n+|\n+$/g, '').split('\n').map((line) => {
    const total = line.startsWith('='), group = line.startsWith('+');
    const [item, dollar, amount, fraction, remark] = line.replace(/^[=+]/, '').split('\t');
    if (group) return [C(item, 'left', { colSpan: 5, isHeader: true }),
      ...[1, 2, 3, 4].map(() => C('', 'left', { hiddenBy: { row: -1, col: 0 } }))];
    const h = { isHeader: total };
    return [C(item, 'left', h), C(dollar ? '\\$' : '', 'right', h), // `$` alone opens maths
      C(amount ?? '', 'right', h), C(fraction ?? '', 'left', h), C(remark ?? '', 'left', h)];
  });
  rows.forEach((row, r) => row.forEach((cell) => cell.hiddenBy && (cell.hiddenBy.row = r)));
  return { rows, headerRowCount: 0, columnWidths: [34, 2.4, 8, 3.6, 35] };
}
const accounts = String.raw`=== house
Tablas,	$	8,03	½	casi todas de chabolas.
Tablillas de desecho para tejado y paredes,		4,00
Listones,		1,25
Dos ventanas de segunda mano con cristal,		2,43
Mil ladrillos viejos,		4,00
Dos barriles de cal,		2,40		Eso fue caro.
Pelo,		0,31		Más del que necesitaba.
Hierro para la repisa,		0,15
Clavos,		3,90
Bisagras y tornillos,		0,14
Pestillo,		0,10
Tiza,		0,01
Transporte,		1,40		Buena parte la llevé a cuestas.
=En total,	$	28,12	½
=== farm
	$	23,44
Descontando los gastos,		14,72	½
=Quedan,	$	8,71	½
=== food
Arroz,	$	1,73	½
Melaza,		1,73		La forma más barata del azúcar.
Harina de centeno,		1,04	¾
Harina de maíz,		0,99	¾	Más barata que la de centeno.
Cerdo,		0,22
+Todos los experimentos que fallaron:
Harina de trigo,		0,88		Cuesta más que la de maíz, en dinero y en trabajo.
Azúcar,		0,80
Manteca,		0,65
Manzanas,		0,25
Manzana seca,		0,22
Boniatos,		0,10
Una calabaza,		0,06
Una sandía,		0,02
Sal,		0,03
=== outgoes
Casa,	$	28,12	½
Granja, un año,		14,72	½
Comida, ocho meses,		8,74
Ropa, etc., ocho meses,		8,40	¾
Aceite, etc., ocho meses,		2,00
=En total,	$	61,99	¾
=== income
	$	23,44
Ganado como jornalero,		13,34
=En total,	$	36,78
`; // content.accounts.<lang>.md: one block per table
const tables = Object.fromEntries(accounts.split(/^=== /m).filter(Boolean)
  .map((block) => [block.slice(0, block.indexOf('\n')).trim(), block.slice(block.indexOf('\n'))]));

Las cuentas de Thoreau se guardan como TSV en content.accounts.es.md, un bloque por cuenta, de modo que la edición española traduce las partidas y cambia el punto decimal por la coma sin tocar el código. Cada fila lleva la partida, el signo del dólar, el importe, una fracción y una observación. Los medios y los cuartos tienen columna propia: ½ mide 713 unidades y ¾, 689, más que una cifra tabular, y Alegreya no tiene espacio de cifra con el que rellenar los demás importes. Un total está hecho de celdas de cabecera, así que groupRules traza un filete fino encima, igual que el impresor de Thoreau trazaba una línea antes de In all. El signo del dólar se escribe \$, porque un $ suelto abre una fórmula en una celda, igual que en el texto. El tipo de recurso account no lleva número ni prefijo de pie, y su colocación por defecto es 'here', así que cada cuenta va donde la presenta la frase de Thoreau.

#3 · La apertura declara sus propias cifras

script.js · líneas 126–144en el código completo
const at = (to, edge, y, x = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });
// 'below' starts a text at the left edge of what it hangs from: the texts take the measure
// and start half the difference to the left of the vignette, so they centre on the page.
const centred = (to, y) => ({ ...at(to, 'below', y, (VIGNETTE - MEASURE) / 2),
  size: { width: mm(MEASURE) } });
const opener = { enabled: true, minHeight: pt(19 * LEAD), slot: { elements: [
  { kind: 'image', id: 'cabin', resourceId: 'cabin',
    placement: { ...at('container', 'top', 0), size: { width: mm(VIGNETTE) } } },
  { kind: 'text', id: 'kicker', content: '{attr.kicker}', fontFamily: LABEL, fontWeight: 500,
    fontSize: pt(7.5), letterSpacing: pt(1.5), textTransform: 'uppercase', color: col('accent'),
    align: 'center', placement: centred('#cabin', 6) },
  { kind: 'text', id: 'title', content: '{titleText}', fontFamily: CAPS, fontSize: pt(30),
    letterSpacing: pt(1.2), lineHeight: 1, color: col('ink'), align: 'center',
    placement: { ...at('#kicker', 'below', 2.5), size: { width: mm(MEASURE) } } },
  // A design text names its own family, so it states its figures too.
  { kind: 'text', id: 'dates', content: '{attr.dates}', fontFamily: CAPS, fontSize: pt(11),
    letterSpacing: pt(0.8), color: col('accent'), align: 'center', figureStyle: 'lining',
    placement: { ...at('#title', 'below', 2.5), size: { width: mm(MEASURE) } } },
] } };

Un texto de diseño fija su propia familia, así que las cifras del cuerpo no le llegan. Concord, 1845–1847 va en Alegreya SC y pide 'lining' por su cuenta, como los títulos. La viñeta es el grabado de la portada de 1854, recortado y ajustado a negro sobre blanco; el antetítulo, el título y las fechas toman la medida de la caja y se centran bajo él.

#4 · Cabeceras con la sección en la página impar

script.js · líneas 148–161en el código completo
const head = (id, content, parity, edge, x, extra = {}) => ({
  kind: 'text', id, content, parity, pages: 'body', fontFamily: LABEL, fontSize: pt(7.5),
  fontWeight: 500, letterSpacing: pt(1.2), textTransform: 'uppercase', color: col('muted'),
  placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(13) } }, ...extra });
const folio = { fontFamily: TEXT, fontSize: pt(9.5), fontWeight: 400, letterSpacing: pt(0),
  color: col('ink') };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
  head('verso-title', '{title}', 'even', 'top-left', OUTER + 8),
  head('recto-title', '{firstMark.h2}', 'odd', 'top-right', -(OUTER + 8)),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
] };
const footer = { elements: [head('drop-folio', '{pageNumber}', 'all', 'top', 0, {
  ...folio, pages: 'opener', placement: at('container', 'top', 8) })] };

La página par lleva el título del libro, y la impar, la sección en curso, {firstMark.h2}, de modo que la página 3 dice El campo de judías, 1845. Los folios van en Alegreya y conservan sus cifras elzevirianas.

Página 2: las cifras elzevirianas de la cuenta de la casa tienen todas el mismo ancho, y los centavos se alinean hasta el total.

La receta completa

Sandbox
// ═══ Postext Cookbook · Nº 176 · Old-style, lining and tabular figures ═══════════════
// https://postext.dev/en/cookbook/old-style-and-tabular-figures
// Code: MIT · Text: H. D. Thoreau, Walden (1854, public domain) · Vignette: 1854 title page (PD)
// Fonts: Alegreya, Alegreya SC, Alegreya Sans (SIL OFL 1.1) · Needs postext ≥ 1.65.0
// Thoreau's accounts from Walden. Each text style asks Alegreya for one kind of figures:
// old-style in the prose, lining in the small-capital headings, tabular in the accounts.
import {
  buildDocumentWithFonts, renderPageToCanvas, registerResourceImage, defaultResourceTypes,
} 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 = 'old-style-and-tabular-figures';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
const palette = {
  ink: '#221e1a', // text: a warm near-black
  accent: '#7d3a1f', // walnut: headings, kicker, rules of the opener
  rule: '#8a7f73', // the light rules of the accounts
  muted: '#6b6158', // running heads, the colophon
  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' } })),
  { id: 'main-color', name: 'accent (defaults)', value: { hex: palette.accent, model: 'hex' } },
];
const TEXT = 'Alegreya', CAPS = 'Alegreya SC', LABEL = 'Alegreya Sans';
const TRIM = [140, 216], INNER = 19, OUTER = 16; // mm: a trade paperback
const MEASURE = TRIM[0] - INNER - OUTER, VIGNETTE = 50; // mm
const LEAD = 14.5; // pt: the body leading, the grid the headings keep to

// #region answer: one figure style per text style
// Alegreya's own figures are old-style, so the prose asks for none: the cut Fontsource
// serves has no onum, and 'oldstyle' would change nothing (see the write-up).
const bodyText = {
  fontFamily: TEXT, fontSize: pt(10.8), lineHeight: pt(LEAD), color: col('ink'),
  boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
  textAlign: 'justify', firstLineIndent: mm(4.5), indentAfterHeading: false,
  hyphenation: { enabled: true }, optimalLineBreaking: true,
  avoidWidows: true, avoidOrphans: true, avoidRunts: true,
};
// Small capitals take lining figures (lnum), at the height of the capitals around them.
const section = { level: 2, fontFamily: CAPS, fontSize: pt(12.5), lineHeight: pt(LEAD),
  fontWeight: 400, letterSpacing: pt(0.6), color: col('accent'), figureStyle: 'lining',
  marginTop: pt(LEAD), marginBottom: pt(0) };
// The accounts: every figure on one width (tnum), so the cents stand in columns.
// 'tabular' keeps the font's form, old-style; 'lining-tabular' would set them at cap height.

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

#Compón las cuentas en cifras de caja alta

Un libro de cuentas o una lista de precios de un libro moderno suele llevar las cifras a la altura de las mayúsculas.

-  figureStyle: 'tabular', bodyFontFamily: TEXT, bodyFontSize: pt(9.8), bodyColor: col('ink'),
+  figureStyle: 'lining-tabular', bodyFontFamily: TEXT, bodyFontSize: pt(9.8), bodyColor: col('ink'),

#Compón los folios en cifras de caja alta

Los folios en cifras de caja alta casan con cabeceras en mayúsculas.

-const folio = { fontFamily: TEXT, fontSize: pt(9.5), fontWeight: 400, letterSpacing: pt(0),
+const folio = { fontFamily: TEXT, fontSize: pt(9.5), fontWeight: 400, letterSpacing: pt(0),
+  figureStyle: 'lining',

Errores frecuentes

  • Los archivos latin que sirve Fontsource suelen dejar fuera onum: Lora, EB Garamond, Source Serif 4, Crimson Pro, Newsreader y Literata solo conservan pnum y tnum. Con esos archivos, figureStyle: 'oldstyle' no cambia nada y el texto sigue con cifras de caja alta. Elige una letra cuyas cifras propias sean elzevirianas, como Alegreya, Gelasio o Cormorant Garamond, que añaden lnum para los títulos, o carga los archivos completos de la fundición.
  • Un $ en el Markdown o en una celda abre una fórmula en línea. Escribe \$14,72½ en el texto y \$ en una celda.

Créditos

Texto
  • Las cuentas y los pasajes que las rodean, de «Economy», primer capítulo de Walden; or, Life in the Woods (Boston: Ticknor and Fields, 1854) · Henry D. Thoreau · dominio público
  • La nota preliminar y la traducción al español de los pasajes y las cuentas · Ignacio Ferro · CC BY 4.0
Imágenes
Fuentes
Alegreya (SIL OFL 1.1) · Alegreya SC (SIL OFL 1.1) · Alegreya Sans (SIL OFL 1.1)
SandboxPDF