En bref
Quatre pages d’un vieux dictionnaire de marine. Des onglets de lettres descendent le long du bord extérieur de chaque page, et celui de la lettre en cours est foncé et descend à mesure que l’alphabet avance.
Ce que vous allez composer
Quatre pages d’une nouvelle édition du Sailor’s Word-Book (1867) de l’amiral Smyth, lettres A à C, sur une page de 150 × 200 mm. Les entrées courent sur deux colonnes justifiées en Alegreya de 8 pt séparées par un filet fin, chaque mot-vedette en gras bleu marine et les lignes suivantes en sommaire de 1 em. Une lettre s’ouvre à l’intérieur de sa colonne, sous forme de lettrine bleu marine de quatre lignes au-dessus d’un filet laiton, et les entrées continuent sans saut de page. Le long de la tranche, chaque page imprime l’index complet, 22 onglets d’une teinte pâle. L’onglet de la lettre en cours est bleu marine, 3,2 mm plus large que les autres, et descend d’un cran à chaque lettre. L’édition espagnole garde les mots-vedettes anglais de Smyth et traduit les entrées.
Cette recette répond à
- Comment ajouter un index à onglets : un onglet sur la tranche qui descend le long de la page d’une lettre ou d’un chapitre au suivant ?
- Comment composer une bibliographie ou un glossaire (retrait suspendu, corps plus petit) ?
- Comment colorer les termes clés (en gras ou en italique) dans le texte ou dans les encadrés ?
- Comment régler les titres courants : titre du livre sur la page de gauche, titre du chapitre sur celle de droite, numéro de page à l’extérieur ?
- Comment masquer les titres courants sur les ouvertures et les pages blanches, ou peindre un verso blanc dans la couleur de la partie ?
La réponse courte
// 22 tabs share the 44 lines of the text block, two lines each; letters with few words
// share a tab, as in most thumb-indexed dictionaries.
const TABS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'QR', 'S', 'T', 'UV', 'W', 'XYZ'];
const STEP = 2 * LEAD * PT; // mm: 7.34
const EDGE = { odd: 'top-right', even: 'top-left' }; // the fore-edge: right on a recto
// A cell of the index, `width` mm inside the trim and 3 mm past it, so that only its
// inner corners show their rounding; its label is centred on the part inside the trim.
const cell = (id, label, i, parity, width, fill, ink, size) => {
const y = mm(TOP + i * STEP + 0.3); // a 0.6 mm gap between neighbours
const at = (x, w) => ({ anchor: { to: 'page', edge: EDGE[parity] }, offset: { x: mm(x), y },
size: { width: mm(w), height: mm(STEP - 0.6) } });
return [
{ kind: 'box', id, parity, style: { backgroundColor: col(fill), borderRadius: mm(1.2) },
placement: at(parity === 'odd' ? 3 : -3, width + 3) },
{ kind: 'text', id: `${id}-label`, parity, content: label, fontFamily: LABEL, fontWeight: 700,
fontSize: pt(size), lineHeight: 1, color: col(ink), align: 'center',
verticalAlign: 'middle', placement: at(0, width) },
];
};
const sides = (make) => ['odd', 'even'].flatMap(make);
// Every page prints the whole index, pale; the section's own letter stands out of it.
const ladder = sides((p) => TABS.flatMap((label, i) =>
cell(`index-${i}-${p}`, label, i, p, 5.8, 'tint', 'slate', 7.5)));
const letterStyles = () => TABS.flatMap((label, i) => [...label].map((letter) => ({
id: letter,
// '# B {style="B"}' opens the section of B: its pages take this header, and a page
// where A ends and B begins takes B's (gotcha: section-last-wins).
header: { elements: [...runningHeads, ...ladder,
...sides((p) => cell(`tab-${p}`, label, i, p, 9, 'navy', 'paper', 8.5))] },
})));
Ingrédients
- Fonctionnalités
- Bandeaux à fond perdu et ongletsTitres courants par sectionGras, italique et leurs couleursStyles de titreOuvertures composéesAncrer les éléments de maquetteImages dans les maquettes de pageAttributs de titreStyles de paragrapheBibliographies et glossairesTitres courants selon le rôle de la pageTitres courants et foliosFilet de colonneÉquilibrage des colonnesVeuves, orphelines et fins de paragraphe trop courtesCésure et langue du documentPalette de couleurs sémantiqueCouleur du papierMarges en miroirFigures et tableaux comme ressources
- Typographie
- Alegreya, Alegreya SC, Alegreya Sans SC (SIL OFL 1.1)
- Ressources
- Aucune : chaque image est dessinée en code
Préparation
#1 · Donner à chaque lettre un style de titre avec son onglet
Le code est dans la réponse courte plus haut. TABS liste 22 onglets, les lettres rares en partageant un (QR, UV, XYZ). Chaque onglet a la hauteur de deux lignes de texte (7,34 mm) : ensemble, ils remplissent les 44 lignes du bloc de texte. La boucle donne à chaque lettre un style de titre dont le header ajoute aux titres courants et à l’index pâle l’onglet bleu marine de cette lettre, un cran plus bas que le précédent (styles de titre). Chaque cellule est écrite deux fois, une par parity, et ancrée au bord extérieur de la page plutôt qu’au conteneur de l’en-tête. Ce conteneur est la marge de tête, aussi large que le bloc de texte et allant du bord de coupe jusqu’au texte, alors que les onglets doivent atteindre la tranche et descendre sur les 44 lignes. Une page prend l’en-tête de la dernière section qui y commence : la page 2 commence donc en A et montre l’onglet de B.
#2 · Écarter les titres courants de la première page
const HEAD = 11; // mm from the trim's top to the heads' top
const head = (id, content, parity, edge, x, extra) => ({ kind: 'text', id, content, parity,
pages: 'body', fontFamily: LABEL, fontSize: pt(8), fontWeight: 500, letterSpacing: pt(1),
color: col('muted'),
placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(HEAD) } }, ...extra });
const folio = { fontWeight: 700, color: col('ink'), letterSpacing: pt(0) };
const runningHeads = [
head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
head('verso-title', '{title}', 'even', 'top-left', OUTER + 8),
head('recto-letter', t({ en: 'Letter {chapterTitle}', es: 'Letra {chapterTitle}' }), 'odd',
'top-right', -(OUTER + 8)),
head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
];
// The first page drops its folio to the foot, under the text block.
const footer = { elements: [head('drop-folio', '{pageNumber}', 'all', 'top', 0, {
...folio, pages: 'opener',
placement: { anchor: { to: 'container', edge: 'top' }, offset: { y: mm(6) } } })] };
Les titres courants sont des éléments de texte ancrés à la page et filtrés par parity et pages: 'body' (éléments de texte). Le titre de la page 1 s’étend sur les deux colonnes, ce qui fait de la page 1 une page d’ouverture. Les titres courants l’ignorent, et son folio vient du pied de page, un élément réglé sur pages: 'opener' qui s’imprime 6 mm sous le bloc de texte. Sur un recto, {chapterTitle} imprime la lettre du dernier titre de niveau 1 placé sur la page ou avant elle, Letter C. {firstMark.<key>} et {lastMark.<key>} imprimeraient plutôt le premier et le dernier mot-vedette de la page, les mots-repères que portent la plupart des dictionnaires ; cette recette garde la lettre, puisque c’est elle qu’indexent les onglets.
#3 · Colorer les mots-vedettes et rien d’autre
// Bold and italic default to the engine's blue: the body sets them to ink, and only the
// entry style prints its bold, the headwords, in navy.
const BODY = 8; // pt: a reference size, about 50 characters to the 60 mm column
const bodyText = { fontFamily: TEXT, fontSize: pt(BODY), lineHeight: pt(LEAD), color: col('ink'),
boldColor: col('ink'), italicColor: col('ink'), firstLineIndent: mm(0),
minWordSpacing: 0.7, maxWordSpacing: 1.7,
maxJustifyTracking: 10, // up to 1 % of tracking before a line's spaces pass 1.7
hyphenateAcrossColumns: false, // no word is divided over a column or a page
maxRuntTracking: 0 }; // a short last line stays: tracking it away crushes a 60 mm line
const paragraphStyles = [
{ id: 'entry', hangingIndent: em(1), boldColor: col('navy') }, // :::paragraphs{style="entry"}
{ id: 'colophon', fontSize: pt(7), color: col('muted'), textAlign: 'center',
marginTop: pt(LEAD) },
];
Chaque entrée est un paragraphe d’un bloc :::paragraphs{style="entry"}, ses lignes suivantes en sommaire de 1 em (styles de paragraphe). Le boldColor du texte courant garde Note. en noir sur la page 1, et le boldColor du style d’entrée imprime les mots-vedettes en bleu marine. Un style de paragraphe n’a pas d’italicColor : les sous-entrées en italique gardent donc l’encre du texte courant.
#4 · Placer une lettrine de quatre lignes dans la colonne
// Its cap line meets the first body line's and it stands on the fourth baseline. Cap
// heights measured on the glyph H: 0.652 em in Alegreya SC 900, 0.646 em in Alegreya.
const DROP = 4; // body lines the letter spans
const LETTER = ((DROP - 1) * LEAD + 0.646 * BODY) / 0.652; // pt: 55.8
// A design line sets its baseline 0.8 of its height below its top; a body line does too.
const letterHead = { enabled: true, slot: { elements: [
{ kind: 'text', id: 'letter', content: '{titleText}', fontFamily: DISPLAY, fontWeight: 900,
fontSize: pt(LETTER), lineHeight: 1, color: col('navy'), align: 'left',
placement: { anchor: { to: 'container', edge: 'top-left' },
offset: { y: pt((DROP - 0.2) * LEAD - 0.8 * LETTER) } } },
{ kind: 'rule', id: 'hairline', direction: 'horizontal', thickness: pt(0.6), color: col('brass'),
placement: { anchor: { to: 'container', edge: 'top-left' },
offset: { y: pt((DROP + 0.55) * LEAD) }, size: { width: 'fill' } } },
] } };
LETTER, 55,8 pt, est le corps auquel une capitale d’Alegreya SC 900 (haute de 0,652 em) repose sur la quatrième ligne de base et atteint la hauteur des capitales de la première ligne de texte. La lettre et le filet laiton finissent dans la cinquième ligne, et le bloc du titre se cale sur la ligne suivante de la grille : chaque tête de lettre fait donc cinq lignes, et les entrées qui suivent restent sur la grille. En page 3, le C en tête de la seconde colonne est aligné sur les quatre premières lignes de la première colonne. Le niveau 1 règle breakBefore: { enabled: false }, qui désactive le saut de page qu’un titre de niveau 1 a par défaut, et les styles de lettre en héritent : B et C s’ouvrent en milieu de colonne.
#5 · Étendre le titre sur les deux colonnes
const centred = (id, content, family, size, lineHeight, y, width, extra) => ({ kind: 'text', id,
content, fontFamily: family, fontSize: pt(size), lineHeight, color: col('navy'), align: 'center',
overflow: 'wrap', ...extra, placement: { anchor: { to: 'container', edge: 'top' },
offset: { y: mm(y) }, size: { width: width ? mm(width) : 'fill' } } });
const rule = (id, y, thickness) => ({ kind: 'rule', id, direction: 'horizontal',
thickness: pt(thickness), color: col('navy'), placement: { anchor: { to: 'container',
edge: 'top-left' }, offset: { y: mm(y) }, size: { width: 'fill' } } });
const titleBand = { enabled: true, slot: { elements: [
{ kind: 'image', id: 'anchor', resourceId: 'anchor', placement: {
anchor: { to: 'container', edge: 'top' }, size: { width: 'auto', height: mm(18) } } },
centred('name', '{titleText}', DISPLAY, 22, 1.1, 20, 0, { fontWeight: 900 }),
centred('subtitle', '{attr.subtitle}', TEXT, 9, 1.25, 29.5, 92, { italic: true,
color: col('ink') }),
centred('byline', '{attr.byline}', LABEL, 7, 1.2, 38.8, 0, { fontWeight: 500,
letterSpacing: pt(1.2), color: col('muted') }),
rule('thick', 43.6, 1.2),
rule('thin', 45, 0.4),
] } };
// '# The Sailor’s Word-Book {style="title" subtitle="…" byline="…"}' opens page 1.
const titleStyle = { id: 'title', span: 'page', advancedDesign: titleBand };
Le style du titre s’étend sur toute la page avec un bandeau qui contient l’ancre, le nom, un sous-titre et une mention d’auteur lus dans les attributs du titre, et un double filet (largeur et maquette avancée). Le filet fin se termine 45,1 mm sous le haut du bloc de texte : le bandeau se cale donc sur 13 lignes de la grille, soit 47,7 mm. Le filet entre les colonnes commence sous le bandeau, sur la ligne où commence le texte des deux colonnes.
La recette complète
// ═══ Postext Cookbook · Nº 046 · Dictionary with a moving thumb index ═══════════════ // https://postext.dev/en/cookbook/dictionary-thumb-index // Code: MIT · Text: W. H. Smyth, 1867 (PD); Spanish translation CC BY 4.0 · Pictures: code // Fonts: Alegreya, Alegreya SC, Alegreya Sans SC (SIL OFL 1.1) · Needs postext ≥ 1.25.0 import { buildDocumentWithFonts, renderPageToCanvas, registerResourceImage, } from 'https://esm.sh/postext'; const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es') const RECIPE = 'dictionary-thumb-index'; // ─── 1 · Design ───────────────────────────────────────────────────────────── // #region palette: ink and navy on a warm paper, brass for the rules and the rope const palette = { ink: '#1b1f24', // text navy: '#1f3a5f', // headwords, letters and tabs brass: '#a4834f', // hairlines and the rope of the vignette rule: '#cfccc3', // the column rule tint: '#e4e9f0', // the pale cells of the index slate: '#56657b', // their letters muted: '#62666b', // running heads paper: '#fbfaf6', // the page, and the letter reversed out of each tab }; 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' } })); // #endregion const TEXT = 'Alegreya', DISPLAY = 'Alegreya SC', LABEL = 'Alegreya Sans SC'; const PT = 25.4 / 72; // mm in a point const LEAD = 10.4; // pt: the body's leading, the grid every column is set on const LINES = 44; // lines in a full column const TOP = 18, INNER = 12, OUTER = 14; // mm; the foot margin makes the block whole lines const BOTTOM = 200 - TOP - LINES * LEAD * PT; // #region answer: one heading style per letter, each with its tab one step down the fore-edge // 22 tabs share the 44 lines of the text block, two lines each; letters with few words // share a tab, as in most thumb-indexed dictionaries. const TABS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'QR', 'S', 'T', 'UV', 'W', 'XYZ']; const STEP = 2 * LEAD * PT; // mm: 7.34 const EDGE = { odd: 'top-right', even: 'top-left' }; // the fore-edge: right on a recto // A cell of the index, `width` mm inside the trim and 3 mm past it, so that only its // inner corners show their rounding; its label is centred on the part inside the trim. const cell = (id, label, i, parity, width, fill, ink, size) => { const y = mm(TOP + i * STEP + 0.3); // a 0.6 mm gap between neighbours const at = (x, w) => ({ anchor: { to: 'page', edge: EDGE[parity] }, offset: { x: mm(x), y }, size: { width: mm(w), height: mm(STEP - 0.6) } }); return [ { kind: 'box', id, parity, style: { backgroundColor: col(fill), borderRadius: mm(1.2) }, placement: at(parity === 'odd' ? 3 : -3, width + 3) }, { kind: 'text', id: `${id}-label`, parity, content: label, fontFamily: LABEL, fontWeight: 700, fontSize: pt(size), lineHeight: 1, color: col(ink), align: 'center', verticalAlign: 'middle', placement: at(0, width) }, ]; }; const sides = (make) => ['odd', 'even'].flatMap(make); // Every page prints the whole index, pale; the section's own letter stands out of it. const ladder = sides((p) => TABS.flatMap((label, i) => cell(`index-${i}-${p}`, label, i, p, 5.8, 'tint', 'slate', 7.5))); const letterStyles = () => TABS.flatMap((label, i) => [...label].map((letter) => ({ id: letter, // '# B {style="B"}' opens the section of B: its pages take this header, and a page // where A ends and B begins takes B's (gotcha: section-last-wins). header: { elements: [...runningHeads, ...ladder, ...sides((p) => cell(`tab-${p}`, label, i, p, 9, 'navy', 'paper', 8.5))] }, }))); // #endregion // #region running-heads: the book on the verso, the letter on the recto, folios outside const HEAD = 11; // mm from the trim's top to the heads' top const head = (id, content, parity, edge, x, extra) => ({ kind: 'text', id, content, parity, pages: 'body', fontFamily: LABEL, fontSize: pt(8), fontWeight: 500, letterSpacing: pt(1), color: col('muted'), placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(HEAD) } }, ...extra }); const folio = { fontWeight: 700, color: col('ink'), letterSpacing: pt(0) }; const runningHeads = [ head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio), head('verso-title', '{title}', 'even', 'top-left', OUTER + 8), head('recto-letter', t({ en: 'Letter {chapterTitle}', es: 'Letra {chapterTitle}' }), 'odd', 'top-right', -(OUTER + 8)), head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio), ]; // The first page drops its folio to the foot, under the text block. const footer = { elements: [head('drop-folio', '{pageNumber}', 'all', 'top', 0, { ...folio, pages: 'opener', placement: { anchor: { to: 'container', edge: 'top' }, offset: { y: mm(6) } } })] }; // #endregion // #region entries: one paragraph per entry, the headword bold in navy, the turnovers hanging // Bold and italic default to the engine's blue: the body sets them to ink, and only the // entry style prints its bold, the headwords, in navy. const BODY = 8; // pt: a reference size, about 50 characters to the 60 mm column const bodyText = { fontFamily: TEXT, fontSize: pt(BODY), lineHeight: pt(LEAD), color: col('ink'), boldColor: col('ink'), italicColor: col('ink'), firstLineIndent: mm(0), minWordSpacing: 0.7, maxWordSpacing: 1.7, maxJustifyTracking: 10, // up to 1 % of tracking before a line's spaces pass 1.7 hyphenateAcrossColumns: false, // no word is divided over a column or a page maxRuntTracking: 0 }; // a short last line stays: tracking it away crushes a 60 mm line const paragraphStyles = [ { id: 'entry', hangingIndent: em(1), boldColor: col('navy') }, // :::paragraphs{style="entry"} { id: 'colophon', fontSize: pt(7), color: col('muted'), textAlign: 'center', marginTop: pt(LEAD) }, ]; // #endregion // #region letter-heads: a four-line initial over a brass hairline, in the column, running on // Its cap line meets the first body line's and it stands on the fourth baseline. Cap // heights measured on the glyph H: 0.652 em in Alegreya SC 900, 0.646 em in Alegreya. const DROP = 4; // body lines the letter spans const LETTER = ((DROP - 1) * LEAD + 0.646 * BODY) / 0.652; // pt: 55.8 // A design line sets its baseline 0.8 of its height below its top; a body line does too. const letterHead = { enabled: true, slot: { elements: [ { kind: 'text', id: 'letter', content: '{titleText}', fontFamily: DISPLAY, fontWeight: 900, fontSize: pt(LETTER), lineHeight: 1, color: col('navy'), align: 'left', placement: { anchor: { to: 'container', edge: 'top-left' }, offset: { y: pt((DROP - 0.2) * LEAD - 0.8 * LETTER) } } }, { kind: 'rule', id: 'hairline', direction: 'horizontal', thickness: pt(0.6), color: col('brass'), placement: { anchor: { to: 'container', edge: 'top-left' }, offset: { y: pt((DROP + 0.55) * LEAD) }, size: { width: 'fill' } } }, ] } }; // #endregion // #region title: the book's name across both columns, under a fouled anchor const centred = (id, content, family, size, lineHeight, y, width, extra) => ({ kind: 'text', id, content, fontFamily: family, fontSize: pt(size), lineHeight, color: col('navy'), align: 'center', overflow: 'wrap', ...extra, placement: { anchor: { to: 'container', edge: 'top' }, offset: { y: mm(y) }, size: { width: width ? mm(width) : 'fill' } } }); const rule = (id, y, thickness) => ({ kind: 'rule', id, direction: 'horizontal', thickness: pt(thickness), color: col('navy'), placement: { anchor: { to: 'container', edge: 'top-left' }, offset: { y: mm(y) }, size: { width: 'fill' } } }); const titleBand = { enabled: true, slot: { elements: [ { kind: 'image', id: 'anchor', resourceId: 'anchor', placement: { anchor: { to: 'container', edge: 'top' }, size: { width: 'auto', height: mm(18) } } }, centred('name', '{titleText}', DISPLAY, 22, 1.1, 20, 0, { fontWeight: 900 }), centred('subtitle', '{attr.subtitle}', TEXT, 9, 1.25, 29.5, 92, { italic: true, color: col('ink') }), centred('byline', '{attr.byline}', LABEL, 7, 1.2, 38.8, 0, { fontWeight: 500, letterSpacing: pt(1.2), color: col('muted') }), rule('thick', 43.6, 1.2), rule('thin', 45, 0.4), ] } }; // '# The Sailor’s Word-Book {style="title" subtitle="…" byline="…"}' opens page 1. const titleStyle = { id: 'title', span: 'page', advancedDesign: titleBand }; // #endregion const config = () => ({ locale: t({ en: 'en-us', es: 'es' }), // the hyphenation patterns of each edition colorPalette, footer, header: { elements: [] }, // every page takes the header of its letter's style page: { sizePreset: 'custom', width: mm(150), height: mm(200), dpi: 150, backgroundColor: col('paper'), margins: { top: mm(TOP), bottom: mm(BOTTOM), left: mm(INNER), right: mm(OUTER), mirror: true } }, layout: { gutterWidth: mm(4), // two columns is the default layout columnRule: { enabled: true, color: col('rule'), lineWidth: pt(0.4) } }, bodyText, paragraphStyles, // The designs paint every heading; the hidden ones are still measured, and in the face // FONTS loads, or the kit would fetch Open Sans 700 for text nobody sees. headings: { fontFamily: DISPLAY, fontWeight: 900, levels: [ // Letters run on in the column: the H1 page break is switched off here, and the letter // styles inherit that (gotcha: style-inherits-break). { level: 1, breakBefore: { enabled: false }, marginTop: pt(LEAD), marginBottom: pt(0), advancedDesign: letterHead }, ] }, headingStyles: [ titleStyle, ...letterStyles(), ], }); // #region art: a fouled anchor, the Admiralty's badge, for the title band // Drawn in the page's navy and brass; the rope's lay is a dashed navy stroke over it. const anchorSvg = () => { const { navy, brass } = palette; const rope = 'M66.5 19 C88 26 86 44 60 52 C34 60 32 76 60 82 C88 88 86 104 60 110 ' + 'C44 114 38 122 42 136'; const fluke = (s) => `<path transform="translate(60 0) scale(${s} 1)" fill="${navy}" d="M-44 92 L-50 78 L-36 70 L-34 88 Z"/>`; return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 150" width="120" height="150"> <circle cx="60" cy="13" r="8.5" fill="none" stroke="${navy}" stroke-width="4"/> <path d="M56.5 21 L63.5 21 L64.5 124 L55.5 124 Z" fill="${navy}"/> <rect x="20" y="27" width="80" height="6.5" rx="3.2" fill="${navy}"/> <circle cx="19" cy="30.2" r="5" fill="${navy}"/> <circle cx="101" cy="30.2" r="5" fill="${navy}"/> <path d="M16 84 Q20 132 60 134 Q100 132 104 84" fill="none" stroke="${navy}" stroke-width="7" stroke-linecap="round"/> ${fluke(1)}${fluke(-1)} <path d="M60 126 L66 134 L60 142 L54 134 Z" fill="${navy}"/> <path d="${rope}" fill="none" stroke="${brass}" stroke-width="4.2" stroke-linecap="round"/> <path d="${rope}" fill="none" stroke="${navy}" stroke-opacity="0.5" stroke-width="4.2" stroke-dasharray="1 1.8"/> </svg>`; }; // #endregion // ─── 2 · Content ──────────────────────────────────────────────────────────── const markdown = String.raw`---Exemple Markdown · 118 lignes · content.en.md
title: "The Sailor’s Word-Book" author: "W. H. Smyth" --- # The Sailor’s Word-Book {style="title" subtitle="An alphabetical digest of nautical terms, including some more especially military and scientific, but useful to seamen" byline="Admiral W. H. Smyth · London, 1867"} **Note.** Admiral William Henry Smyth died on 8 September 1865 and left this word-book in manuscript. Sir Edward Belcher, a vice-admiral, revised it for the press, and Blackie and Son published it in London in 1867. This selection prints entries from the letters A, B and C in Smyth’s wording, some of them shortened, among them words now in everyday English: A1, *taken aback*, *aloof*, the *bitter end*, a wide *berth*, *clear the decks*, *cut and run*. Cross-references follow *see*; the entries they name are not in this selection. # A {style="A"} :::paragraphs{style="entry"} **A.** The highest class of the excellence of merchant ships on Lloyd’s books, subdivided into A1 and A2, after which they descend by the vowels: A1 being the very best of the first class. Formerly a river-built (Thames) ship took the first rate for 12 years, a Bristol one for 11, and those of the northern ports 10. Some of the out-port built ships keep their rating 6 to 8 years, and inferior ones only 4. But improvements in ship-building, and the large introduction of iron, are now claiming longer life. **Aback.** The situation of a ship’s sails when the wind bears against their front surfaces. They are *laid aback*, when this is purposely effected to deaden her way by rounding in the weather-braces; and *taken aback*, when brought to by an unexpected change of wind, or by inattention in the helmsman. – *All aback forward*, the notice given from the forecastle, when the head-sails are pressed aback by a sudden change in the wind. (*See* Work aback.) – *Taken aback*, a colloquialism for being suddenly surprised or found out. **Abeam.** In a line at right angles to the vessel’s length; opposite the centre of a ship’s side. **About.** Circularly; the situation of a ship after she has gone round, and trimmed sails on the opposite tack. – *Ready about!* and *About-ship!* are orders to the ship’s company to prepare for tacking by being at their stations. **Adrift.** Floating at random; the state of a boat or vessel broken from her moorings, and driven to and fro without control by the winds and waves. Cast loose; cut adrift. **Afloat.** Borne up and supported by the water; buoyed clear of the ground; also used for being on board ship. **Ahead.** A term especially referable to any object farther onward, or immediately before the ship, or in the course steered, and therefore opposed to *astern*. – *Ahead of the reckoning*, is sailing beyond the estimated position of the ship. – *Ahead* is also used for progress; as, *cannot get ahead*, and is generally applied to forward, in advance. **A-hull.** A ship under bare poles and her helm a-lee, driving from wind and sea, stern foremost. Also a ship deserted, and exposed to the tempestuous winds. **A-lee.** The contrary of *a-weather*: the position of the helm when its tiller is borne over to the lee-side of the ship, in order to go about or put her head to windward. – *Hard a-lee!* or *luff a-lee!* is said to the steersman to put the helm down. – *Helm’s a-lee!* the word of command given on putting the helm down, and causing the head-sails to shake in the wind. **Aloof.** The old word for “keep your luff,” in the act of sailing to the wind. (*See* Luff.) – *Keep aloof*, at a distance. **Anchor.** A large and heavy instrument in use from the earliest times for holding and retaining ships, which it executes with admirable force. With few exceptions it consists of a long iron shank, having at one end a ring, to which the cable is attached, and the other branching out into two arms, with flukes or palms at their bill or extremity. A stock of timber or iron is fixed at right angles to the arms, and serves to guide the flukes perpendicularly to the surface of the ground. According to their various form and size, anchors obtain the epithets of the *sheet*, *best bower*, *small bower*, *spare*, *stream*, *kedge*, and *grapling* (which see under their respective heads). **Apple-pie order.** A strange but not uncommon term for a ship in excellent condition and well looked to. Neat and orderly. Absurdly said to be a corruption of *du pol au pied*. **Ashore.** Aground, on land. – To *go ashore*, to disembark from a boat. Opposed to *aboard*. **Astern.** Any distance behind a vessel; in the after-part of the ship; in the direction of the stern, and therefore the opposite of *ahead*. – *To drop astern*, is to be left behind – when abaft a right angle to the keel at the main-mast, she drops astern. **Athwart.** The transverse direction; anything extending or across the line of a ship’s course. – *Athwart hawse*, a vessel, boat, or floating lumber accidentally drifted across the stem of a ship, the transverse position of the drift being understood. – *Athwart the fore-foot*, just before the stem; ships fire a shot in this direction to arrest a stranger, and make her bring-to. – *Athwart ships*, in the direction of the beam; from side to side: in opposition to *fore-and-aft*. **Avast.** The order to stop, hold, cease, or stay, in any operation: its derivation from the Italian *basta* is more plausible than *have fast*. **Awning.** A cover or canvas canopy suspended by a crow-foot and spread over a ship, boat, or other vessel, to protect the decks and crew from the sun and weather. (*See* Euphroe.) Also that part of the poop-deck which is continued forward beyond the bulk-head of the cabin. **Azure.** The deep blue colour of the sky, when perfectly cloudless. ::: # B {style="B"} :::paragraphs{style="entry"} **Ballast.** As a verb, signifies to steady; as a substantive, a comprehensive mind. A man is said to “lose his ballast” when his judgment fails him, or he becomes top-heavy from conceit. **Beam-ends.** A ship is said to be on her beam-ends when she has heeled over so much on one side that her beams approach to a vertical position; hence also a person lying down is metaphorically said to be on his beam-ends. **Berth.** The station in which a ship rides at anchor, either alone, or in a fleet; as, she lies in a good berth, *i.e.* in good anchoring ground, well sheltered from the wind and sea, and at a proper distance from the shore and other vessels. – *Snug berth*, a place, situation, or establishment. A sleeping berth. – *To berth a vessel*, is to fix upon, and put her into the place she is to occupy. – *To berth a ship’s company*, to allot to each man the space in which his hammock is to be hung, giving the customary 14 inches in width. – *To give a berth*, to keep clear of, as to give a point of land a wide berth, is to keep at a due distance from it. **Bitter-end.** That part of the cable which is abaft the bitts, and therefore within board when the ship rides at anchor. They say, “Bend to the bitter-end” when they would have that end bent to the anchor, and when a chain or rope is paid out to the bitter-end, no more remains to be let go. The bitter-end is the clinching end – sometimes that end is bent to the anchor, because it has never been used, and is more trustworthy. The first 40 fathoms of a cable of 115 fathoms is generally worn out when the inner end is comparatively new. **Booby.** A well-known tropical sea-bird, *Sula fusca*, of the family *Pelecanidae*. It is fond of resting out of the water at night, even preferring an unstable perch on the yard of a ship. The name is derived from the way in which it allows itself to be caught immediately after settling. The direction in which they fly as evening comes on often shows where land may be found. **Brace of shakes.** A moment: taken from the flapping of a sail. I will be with you before it shakes thrice. **Bring up with a round turn.** Suddenly arresting a running rope by taking a round turn round a bollard, bitt-head, or cleat. Said of doing a thing effectually though abruptly. It is used to bring one up to his senses by a severe rating. **Broach-to, to.** To fly up into the wind. It generally happens when a ship is carrying a press of canvas with the wind on the quarter, and a good deal of after-sail set. The masts are endangered by the course being so altered, as to bring it more in opposition to, and thereby increasing the pressure of the wind. In extreme cases the sails are caught flat aback, when the masts would be likely to give way, or the ship might go down stern foremost. **Broadside.** The whole array, or the simultaneous discharge of the artillery on one side of a ship of war above and below. It also implies the whole of that side of a ship above the water which is situate between the bow and quarter, and is in a position nearly perpendicular to the horizon. Also, a name given to the old folio sheets whereon ballads and proclamations were printed of old (broad-sheet). **By the board.** Over the ship’s side. When a mast is carried away near the deck it is said to go by the board. ::: # C {style="C"} :::paragraphs{style="entry"} **Cabin.** A room or compartment partitioned off in a ship, where the officers and passengers reside. In a man-of-war, the principal cabin, in which the captain or admiral lives, is the upper after-part of the vessel. **Cabin-boy.** A boy whose duty is to attend and serve the officers and passengers in the cabin. **Cable’s length.** A measure of about 100 fathoms, by which the distances of ships in a fleet are frequently estimated. This term is frequently misunderstood. In all marine charts a cable is deemed 607.56 feet, or one-tenth of a sea mile. In rope-making the cable varies from 100 to 115 fathoms; cablet, 120 fathoms; hawser-laid, 130 fathoms, as determined by the admiralty in 1830. **Caboose.** The cook-room or kitchen of merchantmen on deck; a diminutive substitute for the galley of a man-of-war. It is generally furnished with cast-iron apparatus for cooking. **Careen, to.** A ship is said to careen when she inclines to one side, or lies over when sailing on a wind; off her keel or carina. **Cat o’ nine tails.** An instrument of punishment used on board ships in the navy; it is commonly of nine pieces of line or cord, about half a yard long, fixed upon a piece of thick rope for a handle, and having three knots on each, at small intervals, nearest one end; with this the seamen who transgress are flogged upon the bare back. **Caulk, to.** (*See* Caulking.) To lie down on deck and sleep, with clothes on. **Chart.** A hydrographical map, or a projection of some part of the earth’s superficies *in plano*, for the use of navigators, further distinguished as plane-charts, Mercator’s charts, globular charts, and the bottle or current chart, to aid in the investigation of surface currents (all which see). A selenographic chart represents the moon, especially as seen by the aid of photography and Mr. De la Rue’s arrangement. **Cheer, to.** To salute a ship *en passant*, by the people all coming on deck and huzzahing three times; it also implies to encourage or animate. (*See also* Hearty and Man ship!) **Chock-a-block, or chock and block.** Is the same with *block-a-block* and *two-blocks* (which see). When the lower block of a tackle is run close up to the upper one, so that you can hoist no higher, the blocks being together. **Clear, to.** Has several significations, particularly to escape from, to unload, to empty, to prepare, &c., as: – *To clear for action.* To prepare for action. – *To clear away* for this or that, is to get obstructions out of the way. – *To clear the decks.* To remove lumber, put things in their places, and coil down the ropes. Also, to take the things off a table after a meal. – *To clear goods.* To pay the custom-house dues and duties. – *To clear the land.* To escape from the land. – *To clear a lighter, or the hold.* To empty either. **Coil.** A certain quantity of rope laid up in ring fashion. The manner in which all ropes are disposed of on board ship for convenience of stowage. They are laid up round, one fake over another, or by concentric turns, termed *Flemish coil*, forming but one tier, and lying flat on the deck, the end being in the middle of it, as a snake or worm coils itself. **Convoy.** A fleet of merchant ships similarly bound, protected by an armed force. Also, the ship or ships appointed to conduct and defend them on their passage. Also, a guard of troops to escort a supply of stores to a detached force. **Cross-trees.** Certain timbers supported by the cheeks and trestle-trees at the upper ends of the lower and top masts, athwart which they are laid to sustain the frame of the tops on the one, and to extend the top-gallant shrouds on the other. **Cuddy.** A sort of cabin or cook-room, generally in the fore-part, but sometimes near the stern of lighters and barges of burden. In the oceanic traders it is a cabin abaft, under the round-house or poop-deck, for the commander and his passengers. Also, the little cabin of a boat. **Cut and run, to.** To cut the cable for an escape. Also, to move off quickly; to quit occupation; to be gone. ::: :::paragraphs{style="colophon"} Type: Alegreya, Alegreya SC and Alegreya Sans SC (SIL OFL) Text: W. H. Smyth, 1867 (Project Gutenberg eBook 26000) Note and anchor: Postext Cookbook (CC BY 4.0) :::`; // content.<lang>.md, inlined by the Cookbook // No paragraph cites the anchor, so it is never placed as a figure: only the title band's // image element draws it. const resources = [{ id: 'anchor', typeId: 'figure', kind: 'svg', createdAt: 0, updatedAt: 0, altText: t({ en: 'An anchor with a rope wound round its shank.', es: 'Un ancla con un cabo enrollado en la caña.' }), svg: { fileId: 'anchor.svg', width: 120, height: 150 } }]; // ─── 3 · Fonts ────────────────────────────────────────────────────────────── const FONTS = { Alegreya: ['400', '400i', '700'], 'Alegreya SC': ['900'], 'Alegreya Sans SC': ['500', '700'], }; // ─── 4 · Build & show ─────────────────────────────────────────────────────── await loadSvg('anchor.svg', anchorSvg()); const doc = await buildDocumentWithFonts({ markdown, resources }, config(), kitFonts(FONTS)); showPages(doc, { title: t({ en: 'The Sailor’s Word-Book', es: 'Vocabulario del marinero' }) });Kit · core, fonts, viewer, images : identique dans toutes les recettes · 270 lignes
// ─── Kit ── helpers shared by every Cookbook recipe · postext.dev/cookbook ───── // ─── Kit · core v1 ── the same in every recipe · postext.dev/cookbook function mm(value) { return { value, unit: 'mm' }; } function pt(value) { return { value, unit: 'pt' }; } function em(value) { return { value, unit: 'em' }; } /** The sample language's string: t({ en: 'Figure', es: 'Figura' }). */ function t(strings) { return strings[LANG] ?? Object.values(strings)[0]; } /** A file in this recipe's assets folder, served from the Postext repo by jsDelivr. */ function asset(file) { return `https://cdn.jsdelivr.net/gh/drnachio/postext@main/cookbook/${RECIPE}/assets/${file}`; } // ─── Kit · fonts v3 ── the same in every recipe · postext.dev/cookbook // Postext loads the faces a config asks for before layout (prepareFonts, // buildDocumentWithFonts) and asks the kit for the files: Fontsource's, the // files the PDF embeds too. /** The options of buildDocumentWithFonts / prepareFonts / withLoadedFonts: * the Fontsource resolver, and the faces of FONTS loaded with the ones the * config names. FONTS = { 'Family Name': ['400', '400i', '700'] }. */ function kitFonts(FONTS = {}) { const faces = Object.entries(FONTS).flatMap(([family, specs]) => specs.map((spec) => ({ family, weight: parseInt(spec, 10), style: spec.endsWith('i') ? 'italic' : 'normal' }))); return { resolve: fontsourceResolver, faces }; } /** A face's Fontsource files, in a fixed order: latin, then latin-ext and greek * when `text` needs them and the family ships them (kitSubsetsFor), each with * its unicode-range. Null for a weight or slant Fontsource does not ship. */ async function fontsourceResolver(family, weight, style, { text = '' } = {}) { const id = fontsourceId(family); const meta = await fontsourceMeta(family); if (meta && !(meta.weights.includes(weight) && meta.styles.includes(style))) return null; kitStatus('Loading fonts…'); const at = await fontsourceAt(id); return ['latin', ...kitSubsetsFor(text, meta)].map((subset) => ({ source: `${at}files/${id}-${subset}-${weight}-${style}.woff2`, unicodeRange: kitRanges()[subset], })); } /** The unicode-range of each Fontsource file the kit loads (a function: the * kit is inlined after the pen's code, which runs before a const would be set). */ function kitRanges() { return { latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,' + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD', 'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,' + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF', greek: 'U+0370-03FF', }; } /** For the cjk, arabic and comics blocks, which add their own files: the * faces of `faces` = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample: * č ł † α χ also load latin-ext and greek files (kitSubsetsFor). With * `optional`, a face Fontsource does not ship is skipped instead of failing. * The files are added once all have loaded, in the order asked (latin, then * latin-ext, then greek), whatever order they arrive in: the last one added * wins a character two files hold. Resolves to the number of faces added. */ async function loadFonts(faces, text = '', { optional = false } = {}) { kitStatus('Loading fonts…'); const ranges = kitRanges(); const jobs = []; for (const [family, specs] of Object.entries(faces)) { const id = fontsourceId(family); const todo = [...new Set(specs)].map((spec) => [parseInt(spec, 10), spec.endsWith('i') ? 'italic' : 'normal']) .filter(([weight, style]) => !hasFace(family, weight, style)); // before any await const at = fontsourceAt(id); const meta = optional || /[^\0-ÿ]/u.test(text) ? await fontsourceMeta(family) : null; const subsets = ['latin', ...kitSubsetsFor(text, meta)]; for (const [weight, style] of todo) { if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue; for (const subset of subsets) { const url = `${await at}files/${id}-${subset}-${weight}-${style}.woff2`; const face = new FontFace(family, `url(${url}) format('woff2')`, { weight: String(weight), style, unicodeRange: ranges[subset] }); jobs.push(face.load().catch(() => { if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`); })); } } } const ready = await Promise.all(jobs).catch((error) => { kitFail(error); throw error; }); return ready.filter((face) => face && document.fonts.add(face)).length; } /** A loaded FontFace covers this family, weight and style (fonts.check() would * also say yes for families nobody declared). */ function hasFace(family, weight, style) { for (const face of document.fonts) { if (face.status !== 'loaded' || face.style !== style) continue; if (face.family.replace(/^["']|["']$/g, '') !== family) continue; const [low, high = low] = face.weight.split(' ').map(Number); if (weight >= low && weight <= high) return true; } return false; } /** The files beyond latin `text` needs that `meta`'s family ships. */ function kitSubsetsFor(text, meta) { return [[/[Ā-˿ᴀ-ᶿḀ-ỿ†ℓⱠ-Ɀ꜠-ꟿ]/u, 'latin-ext'], [/[Ͱ-Ͽ]/u, 'greek']] .filter(([re, x]) => re.test(text) && meta?.subsets?.includes(x)).map(([, x]) => x); } /** Fontsource's id for a family: 'Source Serif 4' → 'source-serif-4'. */ function fontsourceId(family) { return family.toLowerCase().replace(/\s+/g, '-'); } /** A package's folder at the one version @5 names now (#766). */ function fontsourceAt(id, u = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@`) { return (fontsourceAt.c ??= {})[id] ??= fetch(u + '5/package.json').then((r) => r.json()) .then((p) => p.version || 5, () => 5).then((v) => `${u}${v}/`); } /** The family's Fontsource metadata (weights, styles, subsets), or null. */ function fontsourceMeta(family) { fontsourceMeta.cache ??= new Map(); const id = fontsourceId(family); if (!fontsourceMeta.cache.has(id)) { fontsourceMeta.cache.set(id, fetch(`https://api.fontsource.org/v1/fonts/${id}`) .then((res) => (res.ok ? res.json() : null), () => null)); } return fontsourceMeta.cache.get(id); } // ─── Kit · viewer v2 ── the same in every recipe · postext.dev/cookbook /** The pages as spreads on a dark desk, page 1 alone, then verso | recto, * each painted when it scrolls near. Other options go to renderPageToCanvas. */ function showPages(docs, { title, width = 460, ...render } = {}) { const root = viewer(title); const pages = [docs].flat().flatMap((doc) => doc.pages.map((page) => ({ doc, page, n: (doc.pageIndexOffset ?? 0) + page.index }))); const spreads = []; let verso = null; for (const p of pages) { if (p.n % 2 === 1) { if (verso) spreads.push([verso, null]); verso = p; } else { spreads.push([verso, p]); verso = null; } } if (verso) spreads.push([verso, null]); const density = Math.min(window.devicePixelRatio || 1, 2); showPages.painter?.disconnect(); const painter = new IntersectionObserver((entries) => { for (const { isIntersecting, target } of entries) { if (!isIntersecting) continue; painter.unobserve(target); const { doc, page } = target.postext; renderPageToCanvas(page, doc, target, { ...render, scale: (width * density) / page.width }); } }, { rootMargin: '800px' }); showPages.painter = painter; root.replaceChildren(...spreads.map((pair) => { const spread = document.createElement('div'); spread.className = 'pt-spread'; for (const p of pair) { const figure = document.createElement('figure'); if (p) { const label = p.page.pageLabel || String(p.n + 1); const canvas = document.createElement('canvas'); canvas.postext = p; canvas.style.aspectRatio = `${p.page.width} / ${p.page.height}`; canvas.setAttribute('role', 'img'); canvas.setAttribute('aria-label', `Page ${label}`); const folio = document.createElement('figcaption'); folio.textContent = label; figure.append(canvas, folio); painter.observe(canvas); } else figure.className = 'pt-blank'; spread.append(figure); } return spread; })); kitStatus(`${pages.length} ${pages.length === 1 ? 'page' : 'pages'}`); document.documentElement.dataset.postext = 'ready'; return pages.length; } /** The desk, the bar and the error reporting, created once. */ function viewer(title) { if (!document.getElementById('pt-kit')) { document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit"> :root { color-scheme: dark; } body { margin: 0; background: #0e1014; color: #b9bcc4; font: 13px/1.45 system-ui, sans-serif; } #pt-bar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; padding: 10px 16px; background: rgb(14 16 20 / .92); backdrop-filter: blur(6px); border-bottom: 1px solid #23262d; } #pt-bar strong { color: #f4f1ea; font-weight: 600; } #pt-actions { display: flex; gap: 12px; margin-left: auto; } #pt-actions a, #pt-actions button { color: #d8a21a; font: inherit; background: none; border: 0; padding: 0; cursor: pointer; } #pages { display: grid; justify-items: center; gap: 48px; padding: 32px 16px 72px; } .pt-spread { display: flex; } .pt-spread figure { margin: 0; width: min(460px, 44vw); } .pt-spread canvas { display: block; width: 100%; background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); } .pt-spread figure:first-child canvas { box-shadow: inset -14px 0 14px -14px rgb(0 0 0 / .18), 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); } .pt-spread figcaption { margin-top: 10px; text-align: center; font: 600 10px/1 system-ui, sans-serif; letter-spacing: .18em; text-transform: uppercase; color: #6c7079; } .pt-blank { visibility: hidden; } @media (max-width: 760px) { .pt-spread { flex-direction: column; gap: 32px; } .pt-spread figure { width: min(460px, 92vw); } .pt-blank { display: none; } } </style>`); document.body.insertAdjacentHTML('afterbegin', '<header id="pt-bar"><strong id="pt-title"></strong><span id="pt-status" role="status"></span><span id="pt-actions"></span></header>'); document.getElementById('pt-title').textContent = document.title || 'Postext'; addEventListener('error', (event) => kitFail(event.error ?? event.message)); addEventListener('unhandledrejection', (event) => kitFail(event.reason)); } if (title) document.getElementById('pt-title').textContent = title; return document.getElementById('pages') ?? document.body.appendChild(Object.assign(document.createElement('main'), { id: 'pages' })); } function kitStatus(text) { viewer(); document.getElementById('pt-status').textContent = text; } function kitFail(error) { document.documentElement.dataset.postext = 'error'; kitStatus(`Error: ${error?.message ?? error}`); } // ─── Kit · images v3 ── recipes with pictures · postext.dev/cookbook /** Registers a photo or PNG for the canvas and keeps its bytes for the PDF. * fetch → ImageBitmap never taints the canvas (a plain cross-origin <img> would). */ async function loadImage(fileId, url) { const res = await fetch(url); if (!res.ok) throw new Error(`Image not found (${res.status}): ${url}`); const bytes = new Uint8Array(await res.arrayBuffer()); registerResourceImage(fileId, await createImageBitmap(new Blob([bytes]))); (loadImage.bytes ??= new Map()).set(fileId, bytes); } /** Registers SVG markup as a vector image, its fonts inside (inlineSvgFonts, ≥ 1.25). * `options` go to registerResourceImage: { singleInk: true } tints it on the canvas. */ async function loadSvg(fileId, svg, options) { if (typeof inlineSvgFonts === 'function') svg = await inlineSvgFonts(svg, svgFaces); const img = new Image(); img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; await img.decode(); registerResourceImage(fileId, img, options); (loadImage.bytes ??= new Map()).set(fileId, new TextEncoder().encode(svg)); } /** A face's Fontsource files: nearest weight; latin + what the SVG sets. */ async function svgFaces(family, weight, style, { codePoints = [] } = {}) { const [id, m] = [fontsourceId(family), await fontsourceMeta(family)]; const w = m?.weights.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a)) ?? weight; const s = m?.styles.includes(style) === false ? 'normal' : style; return Promise.all(['latin', ...kitSubsetsFor(String.fromCodePoint(...codePoints), m)].map(async (x) => new Uint8Array( await (await fetch(`${await fontsourceAt(id)}files/${id}-${x}-${w}-${s}.woff2`)).arrayBuffer()))); } /** renderToPdf({ resourceBytes: imageBytes }) */ function imageBytes(fileId) { return loadImage.bytes?.get(fileId); } /** renderToHtml({ resourceImageUrl: imageUrl }) */ function imageUrl(fileId) { const bytes = imageBytes(fileId); if (!bytes) return undefined; imageUrl.urls ??= new Map(); if (!imageUrl.urls.has(fileId)) { const type = /\.svg$/i.test(fileId) ? 'image/svg+xml' : /\.png$/i.test(fileId) ? 'image/png' : 'image/jpeg'; imageUrl.urls.set(fileId, URL.createObjectURL(new Blob([bytes], { type }))); } return imageUrl.urls.get(fileId); } // ─── /Kit ───────────────────────────────────────────────────────────────────────
Le script.js composé s’exécute tel quel : collez-le dans le script de module de n’importe quelle page, ou ouvrez la recette sur CodePen. Dossier de la recette sur GitHub ↗ (s’ouvre dans un nouvel onglet)
Variantes
#N’imprimer que l’onglet en cours
Les index à onglets imprimés ne montrent en général que l’onglet de la lettre en cours ; retirez ladder de l’en-tête et l’onglet bleu marine descend toujours de A à C.
- header: { elements: [...runningHeads, ...ladder,
+ header: { elements: [...runningHeads,Erreurs fréquentes
Erreur fréquente
Une page prend l’en-tête de la dernière section qui y commence
Quand deux styles de titre commencent sur une même page, la page prend l’en-tête, le pied de page et la palette du dernier. Une page blanche de séparation appartient au chapitre qui la précède, une page de parité au chapitre qui la suit. Titres courants par section →
Erreur fréquente
Un style de titre hérite du saut de page de son niveau
Une entrée headingStyles reprend de son niveau de titre chaque champ qu’elle omet, breakBefore compris. Une page de table des matières ou un colophon mis en forme sur un H1 après un :::pagebreak hérite de la parité 'odd' et atterrit derrière une page blanche. Donnez à un tel style breakBefore: { enabled: false }. Styles de titre →
Erreur fréquente
Les éléments d’en-tête et de pied de page se peignent par-dessus le texte
Les éléments d’en-tête et de pied de page sont peints par-dessus la page et la zone de texte ne leur fait pas de place. Gardez-les dans les marges, qui sont ce qui leur réserve de l’espace. Titres courants et folios →
Erreur fréquente
Des décalages négatifs relatifs au conteneur n’affichent rien
Le texte de maquette à largeur automatique est limité à son conteneur : un décalage négatif depuis le conteneur le pousse donc dehors et rien ne s’affiche. Ancrez de tels éléments à la page ou au fond perdu avec des décalages explicites en mm, ou donnez-leur une largeur fixe. Ancrer les éléments de maquette →
- Une colonne de 60 mm contient environ 50 caractères, et l’anglais de Smyth ne peut pas être reformulé. Composées par les seules espaces entre les mots, 18 des 218 lignes anglaises justifiées (8 %) les étiraient au-delà de 1,7 fois la largeur normale, contre moins de 2 % en espagnol, dont la traduction a été ajustée à la colonne, et la première ligne de Cross-trees ne pouvait pas être remplie du tout.
maxJustifyTracking: 10permet à une ligne de prendre jusqu’à 1 % d’approche avant que ses espaces ne dépassent cette limite : dans la capture, 25 des 217 lignes anglaises et 9 des 223 lignes espagnoles en reçoivent, et aucune ne dépasse 1,7. L’anglais en était à 17 % avant que les cinq entrées les plus mal composées (Aground, Aloft, Belay, Cockpit, Crow’s nest) ne soient remplacées par d’autres. Choisissez des entrées qui se composent bien, ou élargissez la colonne.
Crédits
- Recette
- Ignacio Ferro
- Texte
- The Sailor’s Word-Book (London: Blackie and Son, 1867), entries from A, B and C · W. H. Smyth; Sir Edward Belcher · domaine public
- Spanish translation, editor’s note and colophon · Postext Cookbook · original
- Polices
- Alegreya (SIL OFL 1.1) · Alegreya SC (SIL OFL 1.1) · Alegreya Sans SC (SIL OFL 1.1)
Modifier ce texte ↗ (s’ouvre dans un nouvel onglet)Dossier de la recette sur GitHub ↗ (s’ouvre dans un nouvel onglet)


