Kurz gesagt
Ein kleines Buch mit drei Briefen zwischen dem König von Preußen und Voltaire. Es zeigt, wie man Datum, Anrede und Unterschrift jedes Briefs setzt, wobei die Briefe ohne neue Seiten weiterlaufen.
Was Sie setzen
Fünf Seiten von Mon sort est changé, einer Ausgabe im Format 14 × 21 cm mit drei Briefen auf Französisch: den beiden, die Friedrich II. im Juni 1740 sechs und zwölf Tage nach seiner Thronbesteigung als König von Preußen an Voltaire schrieb, und Voltaires Brief vom 1. April 1778, zwei Monate vor seinem Tod. Auf dem Umschlag liegt die Fotografie zweier gefalteter Briefe mit rotem Wachssiegel, mit Diffusionsmodellen erzeugt, auf grünem Maroquin, in Code gezeichnet, innerhalb einer doppelten Goldfilete. Im Innern laufen die Briefe ohne Seitenumbruch weiter. Jeder Briefkopf nennt die Nummer in roten Kapitälchen und die beiden Korrespondenten in Fell kursiv, darunter rechtsbündig Ort und Datum. Die Anrede steht in einer eigenen Zeile, und die Unterschrift in Kapitälchen beendet den Brief am rechten Rand; Friedrichs Nachschrift folgt seiner Unterschrift einen Grad kleiner. Der Kolumnentitel jeder rechten Seite nennt das Datum des Briefs.
Dieses Rezept beantwortet
- Wie setze ich ein Motto, eine Widmung, eine Unterschrift oder ein herausgestelltes Zitat mit großem Anführungszeichen?
- Wie erreiche ich guten Blocksatz und gute Silbentrennung für spanischen, französischen oder deutschen Text?
- Wie setze ich Kolumnentitel: Buchtitel auf der linken Seite, Kapiteltitel auf der rechten, Seitenzahl außen?
Die kurze Antwort
// Each letter is a level-1 heading that carries its place and date as attributes:
// # Frédéric à Voltaire {place="À Charlottembourg" date="6 juin 1740"}
// (a value holds no { or }, and one with " goes in single quotes: gotcha attr-values)
const letterHead = { enabled: true, slot: { elements: [
// {number} prints the count as numberingTemplate '{1:I}' writes it.
{ kind: 'text', id: 'number', content: 'Lettre {number}', ...sc, fontSize: pt(9),
letterSpacing: pt(1.8), color: col('seal'), placement: at('container', 'top-left') },
{ kind: 'text', id: 'title', content: '{titleText}', ...fell, fontSize: pt(15),
color: col('ink'),
placement: at('#number', 'below', 0, 1) },
// The dateline spans the measure under the title and sets its words flush right;
// inlineMarks reads the attribute's marks, so date="1^er^ avril 1778" raises its ordinal.
{ kind: 'text', id: 'dateline', content: '{attr.place}, le {attr.date}.', ...crimson,
italic: true, fontSize: pt(10.4), color: col('ink'), align: 'right', inlineMarks: true,
placement: { ...at('#title', 'below', 0, 1.5), size: { width: 'fill' } } },
] } };
// The salutation, the signature and the postscript, each a :::paragraphs{style="…"} container:
// :::paragraphs{style="signature"}
// Fédéric.
// :::
const letterParts = [
{ id: 'vedette', firstLineIndent: pt(0) }, // 'Sire,' on a line of its own, flush left
{ id: 'signature', ...sc, fontSize: pt(10.5), textAlign: 'right', marginTop: pt(LEAD / 2) },
{ id: 'postscript', fontSize: pt(9), lineHeight: pt(12.6), marginTop: pt(LEAD / 2) },
];
// Hooked up below: letterHead designs the level-1 heading, letterParts joins paragraphStyles.
Zutaten
- Funktionen
- ÜberschriftenattributeGestaltungselemente verankernAbsatzformateGestaltete AuftakteNummerierte ÜberschriftenKapitel, die rechts beginnenÜberschriftenformateUmschläge, Titelseiten und ImpressumBilder in SeitengestaltungenZeilenumbrüche in TitelnKolumnentitel und SeitenzahlenVerse Zeile für ZeileGespiegelte RänderPapierfarbeSilbentrennung und DokumentspracheHoch- und TiefstellungDokument-MetadatenSemantische FarbpaletteSeiten auf einem Canvas
- Schrift
- Crimson Pro, IM Fell French Canon, IM Fell DW Pica SC (SIL OFL 1.1)
- Dateien
letters-1160.jpg- The cover: two folded letters and a wax seal, a photograph (Generated With Diffusion Models, eigener Text)
- The cover’s green morocco and gilt fillet, drawn in code (Ignacio Ferro, CC BY 4.0)
Zubereitung
#1 · Die Datumszeile aus der Überschrift lesen
Der Code steht oben in der kurzen Antwort. Jeder Brief ist eine Überschrift der Ebene 1, deren Attribute Ort und Datum enthalten, und der Briefkopf druckt sie als {attr.place}, le {attr.date}. (Attribute von Überschriften). Der Kasten der Datumszeile reicht bis an den rechten Rand der Spalte (width: 'fill'), und align: 'right' setzt die Wörter an diesen Rand; ohne diese Einstellung wird Designtext zentriert. Anrede, Unterschrift und Nachschrift sind Absatzformate, angewandt mit :::paragraphs{style="…"}. Die Unterschrift nimmt die IM Fell DW Pica SC, eine Familie, deren Kleinbuchstaben als Kapitälchen geschnitten sind. inlineMarks: true lässt die Datumszeile die Auszeichnungen in ihrem Attribut lesen, sodass date="1^er^ avril 1778" die Ordnungszahl hochstellt.
#2 · Die Briefe fortlaufend setzen
const letters = { level: 1, numberingTemplate: '{1:I}', advancedDesign: letterHead,
// No break: left at the level's default, each letter would open on a recto.
breakBefore: { enabled: false }, marginTop: pt(2 * LEAD),
// The hidden heading line is measured in the heading face: italic keeps it the IM Fell cut
// that FONTS loads. Upright, it would need the roman, which FONTS
// leaves out; the layout would change only for a title long enough to wrap.
italic: true };
Mit ausgeschaltetem breakBefore beginnt ein Brief zwei Rasterzeilen (marginTop) unter der Rasterzeile, auf der der vorige endet. Auf Seite 4 endet die Nachschrift, gesetzt mit 12,6 pt Zeilenabstand, zwischen zwei Rasterzeilen, sodass über Brief III etwa drei Zeilen Weißraum stehen. Auf Seite 2 läuft Brief I bis an den Fuß der Seite, und Brief II eröffnet Seite 3. Bei ihrer Voreinstellung würde die Ebene 1 jeden Brief auf einer rechten Seite beginnen (Umbruch davor). numberingTemplate: '{1:I}' nummeriert die Briefe von I bis III, und der Briefkopf druckt die Ziffer über {number}.
#3 · Die rechte Seite mit dem Brief datieren
const HEAD_Y = 12; // mm from the top edge
const head = (id, content, parity, placement, look = {}) => ({ kind: 'text', id, content,
parity, pages: 'body', ...sc, fontSize: pt(8.5), letterSpacing: pt(0.9), color: col('muted'),
placement, ...look });
const folio = { ...crimson, fontSize: pt(9), letterSpacing: pt(0), color: col('ink') };
const header = { elements: [
head('verso-folio', '{pageNumber}', 'even', at('page', 'top-left', MARGIN.outer, HEAD_Y), folio),
head('verso-names', '{author}', 'even', at('page', 'top-left', MARGIN.outer + 8, HEAD_Y)),
// {attr.date} reads the last letter that starts on or before the page.
head('recto-date', '{attr.date}', 'odd', at('page', 'top-right', -(MARGIN.outer + 8), HEAD_Y),
{ ...crimson, italic: true, fontSize: pt(9.5), letterSpacing: pt(0), inlineMarks: true }),
head('recto-folio', '{pageNumber}', 'odd', at('page', 'top-right', -MARGIN.outer, HEAD_Y),
folio),
] };
In einer Kopfzeile nimmt {attr.date} das Attribut der letzten Überschrift der Ebene 1, die auf oder vor der Seite beginnt. Seite 3 druckt „12 juin 1740“, und Seite 5, auf der Voltaires Brief von Seite 4 weiterläuft, druckt „1er avril 1778“ mit hochgestellter Ordnungszahl (Textelemente). Die Kolumnentitel stehen 12 mm unter der Oberkante der Seite, die Pagina am Außensteg und die Wörter 8 mm weiter innen. pages: 'body' hält sie vom Umschlag fern, der als Auftaktseite zählt, weil seine Überschrift über die ganze Seite reicht. Das Datum steht in der Crimson Pro kursiv der Datumszeile, die es wiederholt, und die Namen auf der linken Seite in den Kapitälchen der Briefnummern.
#4 · Den Umschlag zu einer Überschrift machen
// The Markdown: # Mon sort \\ est changé {style="cover"}. The binding fills the page, so the
// design reserves all of it and the headnote starts on page 2.
const onCover = (y) => at('page', 'top', 0, y); // centred, y mm below the top edge
const LETTERS = { x: 12, y: 72, w: 116, h: 120 }; // mm: the photograph, inside the fillet
// numbered: false keeps the cover out of the count, so the first letter is I.
const cover = { id: 'cover', numbered: false,
// span: 'page' although the book has one column. Kept in the column, the design stops at
// the column's foot (a band of paper under the leather), and page 1 counts as a 'body' page
// and prints the running heads.
span: 'page', advancedDesign: { enabled: true, slot: { elements: [
{ kind: 'image', id: 'binding', resourceId: 'binding',
placement: { ...at('bleed', 'top-left'), size: { width: 'fill', height: 'fill' } } },
{ kind: 'image', id: 'letters', resourceId: 'letters', placement: { ...at('page', 'top-left',
LETTERS.x, LETTERS.y), size: { width: mm(LETTERS.w), height: mm(LETTERS.h) } } },
{ kind: 'text', id: 'names', content: '{author}', ...sc, fontSize: pt(9.5),
letterSpacing: pt(2), color: col('gilt'), placement: onCover(18) },
// \\ in the heading breaks the title here.
{ kind: 'text', id: 'title', content: '{titleText}', ...fell, fontSize: pt(50),
lineHeight: 1, color: col('paper'), align: 'center', overflow: 'wrap',
placement: onCover(24) },
{ kind: 'text', id: 'subtitle', content: '{subtitle}', ...crimson, italic: true,
fontSize: pt(12), color: col('paper'), placement: onCover(62) },
] } } };
Der Umschlag ist die erste Überschrift, im Überschriftenformat cover: Ein am Anschnitt verankertes Bildelement trägt den gezeichneten Einband, ein zweites setzt die Fotografie 72 mm tiefer, und Titel, Namen und Untertitel stammen aus der Überschrift und dem Frontmatter. numbered: false nimmt ihn aus der Zählung, sodass Friedrichs erster Brief die I ist. Das Bild des Einbands füllt die Seite, und eine Auftaktseite reserviert Raum bis zu ihrem untersten Element; so nimmt der Umschlag die ganze Seite 1 ein, und die Vorbemerkung beginnt ohne :::pagebreak auf Seite 2.
#5 · Das Französische trennen und die Wortabstände offen halten
// Justification, hyphenation, whole-paragraph line breaking and the widow, orphan and runt
// rules are defaults; locale 'fr' (in the config) picks the French patterns.
// The letters keep the transcription's unspaced ; : ? and !; a narrow no-break space
// (U+202F) typed before each would give French spacing and never end a line.
const bodyText = { fontFamily: 'Crimson Pro', fontSize: pt(10), lineHeight: pt(LEAD),
color: col('ink'), firstLineIndent: mm(5),
// No :ref here: written out with the other colours, for the default-skin check.
referenceColor: col('ink'),
// A word space never shrinks below 75 % of the font's. At the default 60 %, the tightest
// line on page 5 sets its spaces at 0.70 (each VDT line carries its justifiedSpaceRatio).
minWordSpacing: 0.75 };
locale: 'fr' trennt mit den französischen Mustern (ou-vrage auf Seite 2, représen-tation auf Seite 4). Blocksatz, Zeilenumbruch über den ganzen Absatz und die Regeln für Witwen, Waisen und zu kurze Ausgangszeilen sind standardmäßig aktiv. minWordSpacing: 0.75 verhindert, dass ein Wortzwischenraum unter drei Viertel desjenigen der Crimson Pro schrumpft; bei der Voreinstellung 0.6 würde die engste Blocksatzzeile auf Seite 5 ihre Zwischenräume auf 0,70 dieser Breite setzen. Bei 14,4 pt Zeilenabstand fasst die Seite 33 Zeilen, und der Fußsteg ist, was darunter übrig bleibt: 20,4 mm.
#6 · Jede Farbe mit der Palette verknüpfen
const palette = {
ink: '#2a2320', // the text: a warm near-black
paper: '#f6efe2', // the page, and the lettering on the cover
seal: '#9c2b24', // the letter numbers
leather: '#2a4536', // the cover: a green morocco binding
gilt: '#d0b67c', // its tooled border and the names on it
muted: '#75695d', // the running heads
};
// col(id): a colour linked to its palette entry, in text styles and design elements alike.
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' } })),
// The engine's default colours, the italic of the headnote among them, link to 'main-color';
// here it is the ink, not the default blue.
{ id: 'main-color', name: 'ink (defaults)', value: { hex: palette.ink, model: 'hex' } },
];
col() verknüpft jede Farbe mit ihrem Eintrag in der Palette, und die Verknüpfung gilt in den Textformaten ebenso wie in den Designelementen: Briefköpfe, Kolumnentitel und Umschlag. Das Kursiv der Vorbemerkung und des Impressums nimmt die voreingestellte Kursivfarbe, die mit main-color verknüpft ist; ist dieser Eintrag auf die Textfarbe gesetzt, druckt es in der Textfarbe und nicht im Blau der Engine, #295AA3.
Das ganze Rezept
// ═══ Postext Cookbook · Nº 064 · Letters edition: datelines and signatures ════════════ // https://postext.dev/en/cookbook/letters-edition // Code: MIT · Text: Frederick II and Voltaire, 1740 and 1778 (PD) · Cover photo: diffusion models // Fonts: Crimson Pro, IM Fell French Canon, IM Fell DW Pica SC (SIL OFL) · 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 = 'letters-edition'; // ─── 1 · Design ───────────────────────────────────────────────────────────── // #region palette: iron-gall ink on cream paper, wax red, green morocco and gilt const palette = { ink: '#2a2320', // the text: a warm near-black paper: '#f6efe2', // the page, and the lettering on the cover seal: '#9c2b24', // the letter numbers leather: '#2a4536', // the cover: a green morocco binding gilt: '#d0b67c', // its tooled border and the names on it muted: '#75695d', // the running heads }; // col(id): a colour linked to its palette entry, in text styles and design elements alike. 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' } })), // The engine's default colours, the italic of the headnote among them, link to 'main-color'; // here it is the ink, not the default blue. { id: 'main-color', name: 'ink (defaults)', value: { hex: palette.ink, model: 'hex' } }, ]; // #endregion const TRIM = { width: 140, height: 210 }; // the French 14 × 21 format const LEAD = 14.4; // pt: the body's leading, the grid every letter starts on const LINES = 33; // lines of text on a full page const TOP = 22; // mm: the top margin const MARGIN = { top: TOP, inner: 17.5, outer: 14.5, // mm, mirrored bottom: TRIM.height - TOP - (LINES * LEAD * 25.4) / 72 }; // ends the page on line 33 const sc = { fontFamily: 'IM Fell DW Pica SC' }; // its lower case is cut as small capitals const fell = { fontFamily: 'IM Fell French Canon', italic: true }; // the display italic const crimson = { fontFamily: 'Crimson Pro' }; // the text face const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } }); // #region answer: a letter head read from the heading, and styles for the letter's parts // Each letter is a level-1 heading that carries its place and date as attributes: // # Frédéric à Voltaire {place="À Charlottembourg" date="6 juin 1740"} // (a value holds no { or }, and one with " goes in single quotes: gotcha attr-values) const letterHead = { enabled: true, slot: { elements: [ // {number} prints the count as numberingTemplate '{1:I}' writes it. { kind: 'text', id: 'number', content: 'Lettre {number}', ...sc, fontSize: pt(9), letterSpacing: pt(1.8), color: col('seal'), placement: at('container', 'top-left') }, { kind: 'text', id: 'title', content: '{titleText}', ...fell, fontSize: pt(15), color: col('ink'), placement: at('#number', 'below', 0, 1) }, // The dateline spans the measure under the title and sets its words flush right; // inlineMarks reads the attribute's marks, so date="1^er^ avril 1778" raises its ordinal. { kind: 'text', id: 'dateline', content: '{attr.place}, le {attr.date}.', ...crimson, italic: true, fontSize: pt(10.4), color: col('ink'), align: 'right', inlineMarks: true, placement: { ...at('#title', 'below', 0, 1.5), size: { width: 'fill' } } }, ] } }; // The salutation, the signature and the postscript, each a :::paragraphs{style="…"} container: // :::paragraphs{style="signature"} // Fédéric. // ::: const letterParts = [ { id: 'vedette', firstLineIndent: pt(0) }, // 'Sire,' on a line of its own, flush left { id: 'signature', ...sc, fontSize: pt(10.5), textAlign: 'right', marginTop: pt(LEAD / 2) }, { id: 'postscript', fontSize: pt(9), lineHeight: pt(12.6), marginTop: pt(LEAD / 2) }, ]; // Hooked up below: letterHead designs the level-1 heading, letterParts joins paragraphStyles. // #endregion // #region letters: numbered I, II, III and run on, two grid lines apart const letters = { level: 1, numberingTemplate: '{1:I}', advancedDesign: letterHead, // No break: left at the level's default, each letter would open on a recto. breakBefore: { enabled: false }, marginTop: pt(2 * LEAD), // The hidden heading line is measured in the heading face: italic keeps it the IM Fell cut // that FONTS loads. Upright, it would need the roman, which FONTS // leaves out; the layout would change only for a title long enough to wrap. italic: true }; // #endregion // #region running-heads: the correspondents on the verso, the date of the letter on the recto const HEAD_Y = 12; // mm from the top edge const head = (id, content, parity, placement, look = {}) => ({ kind: 'text', id, content, parity, pages: 'body', ...sc, fontSize: pt(8.5), letterSpacing: pt(0.9), color: col('muted'), placement, ...look }); const folio = { ...crimson, fontSize: pt(9), letterSpacing: pt(0), color: col('ink') }; const header = { elements: [ head('verso-folio', '{pageNumber}', 'even', at('page', 'top-left', MARGIN.outer, HEAD_Y), folio), head('verso-names', '{author}', 'even', at('page', 'top-left', MARGIN.outer + 8, HEAD_Y)), // {attr.date} reads the last letter that starts on or before the page. head('recto-date', '{attr.date}', 'odd', at('page', 'top-right', -(MARGIN.outer + 8), HEAD_Y), { ...crimson, italic: true, fontSize: pt(9.5), letterSpacing: pt(0), inlineMarks: true }), head('recto-folio', '{pageNumber}', 'odd', at('page', 'top-right', -MARGIN.outer, HEAD_Y), folio), ] }; // #endregion // #region cover: page 1 is a heading style with the art and the title // The Markdown: # Mon sort \\ est changé {style="cover"}. The binding fills the page, so the // design reserves all of it and the headnote starts on page 2. const onCover = (y) => at('page', 'top', 0, y); // centred, y mm below the top edge const LETTERS = { x: 12, y: 72, w: 116, h: 120 }; // mm: the photograph, inside the fillet // numbered: false keeps the cover out of the count, so the first letter is I. const cover = { id: 'cover', numbered: false, // span: 'page' although the book has one column. Kept in the column, the design stops at // the column's foot (a band of paper under the leather), and page 1 counts as a 'body' page // and prints the running heads. span: 'page', advancedDesign: { enabled: true, slot: { elements: [ { kind: 'image', id: 'binding', resourceId: 'binding', placement: { ...at('bleed', 'top-left'), size: { width: 'fill', height: 'fill' } } }, { kind: 'image', id: 'letters', resourceId: 'letters', placement: { ...at('page', 'top-left', LETTERS.x, LETTERS.y), size: { width: mm(LETTERS.w), height: mm(LETTERS.h) } } }, { kind: 'text', id: 'names', content: '{author}', ...sc, fontSize: pt(9.5), letterSpacing: pt(2), color: col('gilt'), placement: onCover(18) }, // \\ in the heading breaks the title here. { kind: 'text', id: 'title', content: '{titleText}', ...fell, fontSize: pt(50), lineHeight: 1, color: col('paper'), align: 'center', overflow: 'wrap', placement: onCover(24) }, { kind: 'text', id: 'subtitle', content: '{subtitle}', ...crimson, italic: true, fontSize: pt(12), color: col('paper'), placement: onCover(62) }, ] } } }; // #endregion // #region text: Crimson Pro at 10/14.4 pt, set in French // Justification, hyphenation, whole-paragraph line breaking and the widow, orphan and runt // rules are defaults; locale 'fr' (in the config) picks the French patterns. // The letters keep the transcription's unspaced ; : ? and !; a narrow no-break space // (U+202F) typed before each would give French spacing and never end a line. const bodyText = { fontFamily: 'Crimson Pro', fontSize: pt(10), lineHeight: pt(LEAD), color: col('ink'), firstLineIndent: mm(5), // No :ref here: written out with the other colours, for the default-skin check. referenceColor: col('ink'), // A word space never shrinks below 75 % of the font's. At the default 60 %, the tightest // line on page 5 sets its spaces at 0.70 (each VDT line carries its justifiedSpaceRatio). minWordSpacing: 0.75 }; // #endregion const config = () => ({ locale: 'fr', // French hyphenation patterns colorPalette, page: { sizePreset: 'custom', width: mm(TRIM.width), height: mm(TRIM.height), dpi: 150, backgroundColor: col('paper'), margins: { top: mm(MARGIN.top), bottom: mm(MARGIN.bottom), left: mm(MARGIN.inner), right: mm(MARGIN.outer), mirror: true } }, // left = inner layout: { layoutType: 'single' }, bodyText, // A heading's own line is hidden under its design but still measured, in this face and weight. headings: { fontFamily: 'IM Fell French Canon', fontWeight: 400, levels: [letters] }, headingStyles: [cover], paragraphStyles: [...letterParts, // Frederick's verses, a :::verse block 7 mm in with a line of space above and below; the // octosyllables start two spaces (indentStep=3.5mm on the fence) further in, 14 mm, and the // two closing alexandrines at 7 mm. { id: 'verse', indent: mm(7), marginTop: pt(LEAD), marginBottom: pt(LEAD) }, // The editor's headnote at 9.6 on 13 pt, italic through *…* in the Markdown. { id: 'headnote', fontSize: pt(9.6), lineHeight: pt(13), firstLineIndent: pt(0) }, { id: 'colophon', fontSize: pt(7.8), lineHeight: pt(10.8), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(3 * LEAD) }], header, footer: { elements: [] }, // the folios ride in the header }); // #region art: the binding drawn in code, and the photograph of the letters laid on it const W = TRIM.width; const H = TRIM.height; // The binding: green leather to the edges, a gilt double fillet and a lozenge at each corner. function bindingSvg() { const tooling = [6, 7.6].map((inset, i) => `<rect x="${inset}" y="${inset}" ` + `width="${W - 2 * inset}" height="${H - 2 * inset}" fill="none" stroke="${palette.gilt}" ` + `stroke-width="${i ? 0.25 : 0.7}"/>`).join('') + [[6, 6], [W - 6, 6], [6, H - 6], [W - 6, H - 6]].map(([x, y]) => `<path d="M${x} ${y - 2.4} L${x + 2.4} ${y} L${x} ${y + 2.4} ` + `L${x - 2.4} ${y} Z" fill="${palette.gilt}"/>`).join(''); return `<svg xmlns="http://www.w3.org/2000/svg" width="${W * 10}" height="${H * 10}" ` + `viewBox="0 0 ${W} ${H}"><rect width="${W}" height="${H}" fill="${palette.leather}"/>` + `${tooling}</svg>`; } // The design's image elements name these by id; loadSvg() and loadImage() below register the // files. The photograph is a JPEG in assets/ cut to LETTERS' 116 × 120 mm, declared at its // pixels; its edges fade into the leather's green, so it sits on the drawn binding unseen. const resources = [{ id: 'binding', typeId: 'figure', kind: 'svg', createdAt: 0, updatedAt: 0, svg: { fileId: 'binding.svg', width: W * 10, height: H * 10 }, altText: 'A green leather cover with a gilt double fillet and a lozenge at each corner.' }, { id: 'letters', typeId: 'figure', kind: 'bitmap', createdAt: 0, updatedAt: 0, bitmap: { fileId: 'letters-1160.jpg', format: 'jpeg', width: 1160, height: 1200 }, altText: 'Two folded letters on the green leather: the upper one lies face down, its flap ' + 'closed by a red wax seal; the lower one shows an address in brown ink.' }]; // #endregion // ─── 2 · Content ──────────────────────────────────────────────────────────── const markdown = String.raw`---Markdown-Beispiel · 99 Zeilen · content.en.md
title: "Mon sort est changé" subtitle: "Trois lettres, 1740 et 1778" author: "Frédéric II et Voltaire" --- # Mon sort \\ est changé {style="cover"} :::paragraphs{style="headnote"} *Frédéric-Guillaume I^er^, roi de Prusse, meurt à Potsdam le 31 mai 1740. Six jours plus tard, son fils, qui règne désormais sous le nom de Frédéric II, écrit de Charlottembourg à Voltaire, son correspondant depuis août 1736. Les deux premières lettres de ce choix sont de ce mois de juin. La troisième, écrite de Paris le 1^er^ avril 1778, ferme le volume de 1889 d’où viennent ces textes. Voltaire meurt à Paris le 30 mai suivant. On garde l’orthographe de l’édition; la vedette et la signature sont détachées du texte.* ::: # Frédéric à Voltaire {place="À Charlottembourg" date="6 juin 1740"} :::paragraphs{style="vedette"} Mon cher ami, ::: Mon sort est changé, et j’ai assisté aux derniers moments d’un roi, à son agonie, à sa mort. En parvenant à la royauté, je n’avais pas besoin assurément de cette leçon pour être dégoûté de la vanité des grandeurs humaines. J’avais projeté un petit ouvrage de métaphysique; il s’est changé en un ouvrage de politique. Je croyais joûter avec l’aimable Voltaire, et il me faut escrimer avec Machiavel. Enfin, mon cher Voltaire, nous ne sommes point maîtres de notre sort. Le tourbillon des événements nous entraîne, et il faut se laisser entraîner. Ne voyez en moi, je vous prie, qu’un citoyen zélé, un philosophe un peu sceptique, mais un ami véritablement fidèle. Pour dieu, ne m’écrivez qu’en homme, et méprisez avec moi les titres, les noms, et tout l’éclat extérieur. Jusqu’à présent il me reste à peine le temps de me reconnaître; j’ai des occupations infinies: je m’en donne encore de surplus; mais malgré tout ce travail, il me reste toujours du temps assez pour admirer vos ouvrages et pour puiser chez vous des instructions et des délassements. Assurez la marquise de mon estime. Je l’admire autant que ses vastes connaissances et la rare capacité de son esprit le méritent. Adieu, mon cher Voltaire; si je vis, je vous verrai, et même dès cette année. Aimez-moi toujours, et soyez toujours sincère ami avec votre ami :::paragraphs{style="signature"} Fédéric. ::: # Frédéric à Voltaire {place="À Charlottembourg" date="12 juin 1740"} :::verse{style="verse" align=start indentStep=3.5mm} Non, ce n’est plus du mont Rémus, Douce et studieuse retraite D’où mes vers vous sont parvenus, Que je date ces vers confus: Car dans ce moment le poète Et le prince sont confondus. Désormais mon peuple que j’aime Est l’unique Dieu que je sers: Adieu les vers et les concerts. Tous les plaisirs. Voltaire même; Mon devoir est mon Dieu suprême. Qu’il entraîne de soins divers! Quel fardeau que le diadème! Quand ce dieu sera satisfait, Alors dans vos bras, cher Voltaire, Je volerai, plus prompt qu’un trait, Puiser, dans les leçons de mon ami sincère, Quel doit être d’un roi le sacré caractère. ::: Vous voyez, mon cher ami, que le changement du sort ne m’a pas tout à fait guéri de la métromanie, et que peut-être je n’en guérirai jamais. J’estime trop l’art d’Horace et de Voltaire pour y renoncer; et je suis du sentiment que chaque chose de la vie a son temps. J’avais commencé une épître sur les abus de la mode et de la coutume, lors même que la coutume de la primogéniture m’obligeait de monter sur le trône et de quitter mon épître pour quelque temps. J’aurais volontiers changé mon épître en satire contre cette même mode, si je ne savais que la satire doit être bannie de la bouche des princes. Enfin, mon cher Voltaire, je flotte entre vingt occupations, et je ne déplore que la brièveté des jours, qui me paraissent trop courts de vingt-quatre heures. Je vous avoue que la vie d’un homme qui n’existe que pour réfléchir et pour lui-même, me semble infiniment préférable à la vie d’un homme dont l’unique occupation doit être de faire le bonheur des autres. Vos vers sont charmants. Je n’en dirai rien, car ils sont trop flatteurs. Mon cher Voltaire, ne vous refusez pas plus longtemps à l’empressement que j’ai de vous voir. Faites en ma faveur tout ce que vous croyez que votre humanité comporte. J’irai à la fin d’auguste à Vesel, et peut-être plus loin. Promettez-moi de me joindre, car je ne saurais vivre heureux ni mourir tranquille sans vous avoir embrassé. Adieu. :::paragraphs{style="signature"} Fédéric. ::: :::paragraphs{style="postscript"} Mille compliments à la marquise. Je travaille des deux mains; d’un côté à l’armée, de l’autre au peuple et aux beaux-arts. ::: # Voltaire à Frédéric {place="À Paris" date="1^er^ avril 1778"} :::paragraphs{style="vedette"} Sire, ::: Le gentilhomme français qui rendra cette lettre à Votre Majesté, et qui passe pour être digne de paraître devant Elle, pourra vous dire que si je n’ai pas eu l’honneur de vous écrire depuis longtemps, c’est que j’ai été occupé à éviter deux choses qui me poursuivaient dans Paris: les sifflets et la mort. Il est plaisant qu’à quatre-vingt-quatre ans j’aie échappé à deux maladies mortelles. Voilà ce que c’est que de vous être consacré: je me suis renommé de vous, et j’ai été sauvé. J’ai vu avec surprise et avec une satisfaction bien douce, à la représentation d’une tragédie nouvelle, que le public, qui regardait il y a trente ans Constantin et Théodose comme les modèles des princes, et même des saints, a applaudi avec des transports inouïs à des vers qui disent que Constantin et Théodose n’ont été que des tyrans superstitieux. J’ai vu vingt preuves pareilles du progrès que la philosophie a fait enfin dans toutes les conditions. Je ne désespérerais pas de faire prononcer dans un mois le panégyrique de l’empereur Julien: et assurément si les Parisiens se souviennent qu’il a rendu chez eux la justice comme Caton, et qu’il a combattu pour eux comme César, ils lui doivent une éternelle reconnaissance. Il est donc vrai, Sire, qu’à la fin les hommes s’éclairent, et que ceux qui se croient payés pour les aveugler ne sont pas toujours les maîtres de leur crever les yeux! Grâces en soient rendus à Votre Majesté! Vous avez vaincu les préjugés comme vos autres ennemis: vous jouissez de vos établissements en tout genre. Vous êtes le vainqueur de la superstition, ainsi que le soutien de la liberté germanique. Vivez plus longtemps que moi, pour affermir tous les empires que vous avez fondés. Puisse Frédéric le Grand être Frédéric l’immortel! Daignez agréer le profond respect et l’inviolable attachement de :::paragraphs{style="signature"} Voltaire. ::: :::paragraphs{style="colophon"} *Correspondance de Voltaire avec le roi de Prusse* (Paris, Librairie de la Bibliothèque nationale, 1889), texte du Project Gutenberg, nº 25734. Composé en Crimson Pro et IM Fell (licence SIL OFL). :::`; // content.<lang>.md, inlined by the Cookbook // ─── 3 · Fonts ────────────────────────────────────────────────────────────── const FONTS = { // text, display and label faces 'Crimson Pro': ['400', '400i'], 'IM Fell French Canon': ['400i'], 'IM Fell DW Pica SC': ['400'] }; // ─── 4 · Build & show ─────────────────────────────────────────────────────── await loadSvg('binding.svg', bindingSvg()); await loadImage('letters-1160.jpg', asset('letters-1160.jpg')); const doc = await buildDocumentWithFonts({ markdown, resources }, config(), kitFonts(FONTS)); showPages(doc, { title: t({ en: 'Letters edition', es: 'Edición de cartas' }) });Kit · core, fonts, viewer, images: in jedem Rezept gleich · 270 Zeilen
// ─── 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 ───────────────────────────────────────────────────────────────────────
Das zusammengesetzte script.js läuft unverändert: Fügen Sie es in das Modulskript einer beliebigen Seite ein, oder öffnen Sie das Rezept auf CodePen. Ordner des Rezepts auf GitHub ↗ (öffnet in neuem Tab)
Varianten
#Den Ort im Kolumnentitel nennen
Dieselben Attribute können der rechten Seite die ganze Datumszeile geben, À Charlottembourg, le 12 juin 1740.
- head('recto-date', '{attr.date}', 'odd', at('page', 'top-right', -(MARGIN.outer + 8), HEAD_Y),
+ head('recto-date', '{attr.place}, le {attr.date}', 'odd',
+ at('page', 'top-right', -(MARGIN.outer + 8), HEAD_Y),Häufige Fehler
Häufiger Fehler
Attributwerte: kein { oder }; einen Wert mit " in einfache Anführungszeichen setzen
Ein Attributwert endet an der schließenden geschweiften Klammer, kann also kein { oder } enthalten. Ein Wert, der ein doppeltes Anführungszeichen enthält, kommt in einfache Anführungszeichen; ein Dollarzeichen ist unproblematisch. Überschriftenattribute →
- Die rechte Seite nimmt das Datum des letzten Briefs, der auf oder vor der Seite beginnt. Eine Seite, die einen Brief beendet und den nächsten eröffnet, trägt das Datum des neuen Briefs, so wie der Kolumnentitel eines Wörterbuchs den letzten Eintrag der Seite nennt.
- Eine Überschrift ohne das Attribut druckt eine leere Zeichenkette, ohne Warnung: Ein Brief ohne
place="…"bekommt eine Datumszeile, die mit einem Komma beginnt. Prüfen Sie die Überschrift jedes Briefs. - Die Briefe behalten die Zeichen ; : ? und ! der Transkription ohne Abstand davor. Die französische Praxis setzt davor einen schmalen geschützten Leerraum (U+202F); dort eingetippt, hält er jedes Zeichen in der Zeile seines Worts.
- Crimson Pro hat keine Modifikatorbuchstaben ᵉ (U+1D49) oder ʳ (U+02B3), sodass ein in Unicode geschriebenes 1ᵉʳ in einer Ersatzschrift gedruckt würde. Schreiben Sie stattdessen
1^er^: Die Vorbemerkung, die Grundtext ist, setzt es hochgestellt, ebenso die Datumszeile und der Kolumnentitel der rechten Seite, deren TextelementeinlineMarks: truetragen.
Nachweise
- Rezept
- Ignacio Ferro
- Text
- Frederick II’s letters to Voltaire of 6 and 12 June 1740 and Voltaire’s letter to him of 1 April 1778, in Correspondance de Voltaire avec le roi de Prusse (Paris, Librairie de la Bibliothèque nationale, 1889), one misprint corrected (ouvragés), the salutations and signatures set apart · Frédéric II · Voltaire · gemeinfrei
- The headnote and the colophon · Ignacio Ferro · CC BY 4.0
- Bilder
- The cover: two folded letters and a wax seal, a photograph · Generated With Diffusion Models · eigener Text
- The cover’s green morocco and gilt fillet, drawn in code · Ignacio Ferro · CC BY 4.0
- Schriften
- Crimson Pro (SIL OFL 1.1) · IM Fell French Canon (SIL OFL 1.1) · IM Fell DW Pica SC (SIL OFL 1.1)
- Code
- MIT, wie Postext
Diesen Text bearbeiten ↗ (öffnet in neuem Tab)Ordner des Rezepts auf GitHub ↗ (öffnet in neuem Tab)


