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
// 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
- Funciones
- Estilos de cifrasEstilo de tablasTablas a partir de datosTipos de recurso propiosColocación de figurasAperturas diseñadasImágenes en los diseños de páginaAtributos de títuloCabeceras por secciónCabeceras y foliosMárgenes simétricosEstilos de párrafoPaleta de color semánticaExportación a PDFFuentes incrustadas en el PDF
- También usa
- Figuras justo aquíFigura y Tabla en tu idiomaCabeceras según el tipo de páginaFiguras y tablas como recursos
- 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
// 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
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
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.

La receta completa
// ═══ 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 conservanpnumytnum. 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ñadenlnumpara 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
- Receta
- Ignacio Ferro
- 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
- 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
- Fuentes
- Alegreya (SIL OFL 1.1) · Alegreya SC (SIL OFL 1.1) · Alegreya Sans (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)


