Kurz gesagt
Ein kleines Buch mit vier Naturgedichten von Gerard Manley Hopkins. Es zeigt, wie jede Verszeile für sich bleibt, mit den ursprünglichen Einzügen des Dichters und einer Leerzeile zwischen den Strophen.
Was Sie setzen
Ein schmaler Band mit vier Naturgedichten von Gerard Manley Hopkins, 140 × 216 mm, im Text der Erstausgabe von 1918. Nach dem Schmutztitel steht ein Frontispiz, ein Fotogramm von Farn und Vogelbeere, einem Titelblatt in der Italiana gegenüber. Das Inhaltsverzeichnis nennt jedes Gedicht mit Ziffer und kursivem Titel, mit gesperrten Punkten bis zur Seitenzahl. Jedes Gedicht eröffnet eine Seite unter einer salbeigrünen Ziffer und seinem kursiven Titel. Jedes Gedicht ist ein Block :::verse, eingezogen wie 1918: Die Zeilen von Pied Beauty rücken nach dem Reim ein, und die langen Zeilen von The Windhover brechen um und hängen 4 em ein, doppelt so tief wie seine Einzüge. Strophen stehen eine Zeile auseinander, und kein Wort wird getrennt, außer wo Hopkins es getrennt hat. Das Datum steht unter jedem Gedicht rechtsbündig, und auf das letzte folgt ein Vogelbeerzweig. Die Gedichtseiten tragen eine zentrierte Pagina am Fuß.
Dieses Rezept beantwortet
- Wie setze ich Lyrik: eine Zeile pro Vers, Abstand zwischen Strophen, hängende Einzüge für umbrochene Zeilen, keine Silbentrennung?
- Wie füge ich zusätzlichen vertikalen Abstand zwischen zwei Blöcken ein, wenn Leerzeilen nichts bewirken?
- Wie füge ich ein Inhaltsverzeichnis hinzu, das sich selbst aktualisiert (Führungspunkte, Seitenzahlen, Autoren, Teilzeilen)?
- Wie setze ich ein Motto, eine Widmung, eine Unterschrift oder ein herausgestelltes Zitat mit großem Anführungszeichen?
- Wie setze ich nicht nummerierte Grafiken: Ornamente, Vignetten, Logos?
Die kurze Antwort
// A poem is one :::verse block written as it is printed: a line of verse a line, a blank line
// between stanzas (one line of the grid), and spaces before an indented line, two to an em:
// :::verse{style="verse" align=start}
// The world is charged with the grandeur of God.
// It will flame out, like shining from shook foil;
//
// And for all this, nature is never spent;
// :::
// align=start sets the poem flush with the margin (the default centres it on its longest line).
const verse = {
id: 'verse',
hangingIndent: em(2), // a turnover hangs 2 em past its line's start: 4 em under a 2 em indent
// Verse keeps its words whole, even where bodyText.hyphenation.ragged is switched on.
hyphenation: false,
};
// Hook-up: paragraphStyles: [verse, …]; the Markdown goes to buildDocument as it is written.
Zutaten
- Funktionen
- Verse Zeile für ZeileAbsatzformateInhaltsverzeichnisExpliziter vertikaler AbstandEinzüge, Ausrichtung und AbsatzabständeMaskierung und wörtliche ZeichenNummerierte ÜberschriftenGestaltete AuftakteÜberschriftenformateKapitel ohne NummerÜberschriftenattributeBilder in SeitengestaltungenUmschläge, Titelseiten und ImpressumDokument-MetadatenKolumnentitel je AbschnittKolumnentitel und SeitenzahlenEigene RessourcentypenAbbildungen genau hierSemantische FarbpaletteKopfzeilen nach Seitenrolle
- Schrift
- Sorts Mill Goudy, Italiana, Marcellus SC (SIL OFL 1.1)
- Dateien
plate-1120.jpg- The frontispiece photogram of fern and rowan (Generated With Diffusion Models, eigener Text)
- The rowan ornament, drawn in code in the page’s palette (Ignacio Ferro, CC BY 4.0)
Zubereitung
#1 · Ein Gedicht ist ein Block :::verse
Ein Block :::verse behält jede Zeile des Markdown als Verszeile, sodass das Gedicht so geschrieben wird, wie es gedruckt ist (:::verse). Seine führenden Leerzeichen sind seine Einzüge, zwei pro Geviert, sodass die Zeilen, die miteinander reimen, gleich weit eingezogen beginnen, wie 1918. Eine Leerzeile zwischen Strophen lässt eine Rasterzeile frei und fällt am Kopf einer Seite weg. Eine Zeile, die für die Satzbreite zu lang ist, bricht an einem Wortzwischenraum um und hängt um das hangingIndent des Versformats ein, gemessen vom Anfang der eigenen Zeile: 2 em hinter einer nicht eingezogenen Zeile, 4 em unter einer um 2 em eingezogenen, sodass eine Folgezeile nie als eigene Zeile gelesen wird. align=start setzt jedes Gedicht bündig am Rand; ohne diese Angabe wird ein Gedicht auf seine längste Zeile zentriert. Verse werden nicht getrennt, und eine Zeile trennt sich an einem Seitenumbruch nie von ihrer Folgezeile.

#2 · Auch die Zeilen um ein Gedicht haben Formate
const lineStyles = [
{ id: 'dedication', fontSize: pt(9.5), marginBottom: pt(LEAD) }, // under The Windhover
{ id: 'date', fontFamily: 'Marcellus SC', fontSize: pt(8), color: col('muted'),
textAlign: 'right', marginTop: pt(LEAD) }, // a line of space above, flush right
];
Die Widmung über The Windhover und Ort und Datum unter jedem Gedicht sind keine Verszeilen, deshalb ist jede ein Container :::paragraphs mit einem Absatz, in einem eigenen Format. Keines der Formate setzt einen Zeilenabstand, und ihre Abstände betragen eine Zeile (15 pt), sodass die Verse auf dem Raster des Grundtextes bleiben. Das Datum steht eine Zeile unter dem Gedicht, rechtsbündig, in 8 pt Marcellus SC, der Schrift der Paginas.
#3 · Jedes Gedicht eröffnet eine Seite unter seiner Nummer
// '{1:I}' numbers the poems I to IV: {number} prints the numeral here, :::toc in the contents.
// The head is HEAD_LINES grid lines (minHeight, no bottom margin), so each poem starts on the same
// line; numeral and title fill 4, or 5 if the title wraps.
const HEAD_LINES = 6;
const poemHead = { enabled: true, minHeight: pt(LEAD * HEAD_LINES), slot: { elements: [
{ kind: 'text', id: 'numeral', content: '{number}', fontFamily: 'Italiana', fontSize: pt(22),
color: col('sage'), align: 'left',
placement: { anchor: { to: 'container', edge: 'top-left' } } },
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: 'Sorts Mill Goudy',
italic: true, fontSize: pt(17), color: col('ink'), align: 'left',
placement: { anchor: { to: '#numeral', edge: 'below' }, offset: { y: mm(2) },
size: { width: 'fill' } } },
] } };
// Parity 'any': the next page, recto or verso, with no blank page between two poems.
const poems = { level: 1, numberingTemplate: '{1:I}', advancedDesign: poemHead,
marginBottom: pt(0), breakBefore: { enabled: true, parity: 'any' } };
Statt ihres Textes druckt die Überschrift eine Gestaltung in der Spalte. In dieser Gestaltung ist {number} die Zählung, wie numberingTemplate: '{1:I}' sie formatiert, I bis IV, und {titleText} der Titel (Seitenbreite und erweiterte Gestaltung). Die Ebene hat keinen unteren Abstand, und minHeight macht jeden Kopf sechs Rasterzeilen tief (31,75 mm). Ziffer und ein einzeiliger Titel füllen vier davon, ein umbrechender Titel fünf, sodass jedes Gedicht auf derselben Zeile seiner Seite beginnt. Auf Seite 6 steht auf dieser Zeile die Widmung. Die Parität 'any' bricht auf die nächste Seite um, rechts oder links, deshalb gibt es zwischen den Gedichten keine leeren Seiten.
#4 · Die Titelei besteht aus Überschriften
// Headings too, each on a page of its own, with no number, contents entry or folio.
// A style restates the break, or inherits the poems' (gotcha: style-inherits-break).
const leaf = { numbered: false, toc: false, span: 'page', footer: { elements: [] },
breakBefore: { enabled: true, parity: 'any' } };
const onPage = (y, width) => ({ anchor: { to: 'page', edge: 'top' }, offset: { y: mm(y) },
...(width && { size: { width: mm(width) } }) }); // centred, y mm below the trim
const face = (id, content, font, size, y, extra = {}) => ({ kind: 'text', id, content,
fontFamily: font, fontSize: pt(size), color: col('ink'), align: 'center',
placement: onPage(y), ...extra });
const image = (id, placement) => ({ kind: 'image', id, resourceId: id, placement });
const design = (...elements) => ({ enabled: true, slot: { elements } });
const front = [
{ id: 'half-title', ...leaf,
advancedDesign: design(face('title', '{titleText}', 'Italiana', 20, 60)) },
{ id: 'plate', ...leaf, advancedDesign: design( // span 'page': a column's foot would cut it
image('plate', { anchor: { to: 'bleed', edge: 'top-left' }, size: { width: 'fill' } }),
face('caption', '{attr.caption}', 'Sorts Mill Goudy', 8.5, 203,
{ italic: true, color: col('muted') })) },
{ id: 'title-page', ...leaf, advancedDesign: design(
face('author', '{author}', 'Marcellus SC', 10, 46, // {author}, {title}: the frontmatter
{ letterSpacing: pt(2.4), textTransform: 'uppercase' }),
face('title', '{title}', 'Italiana', 54, 56, { lineHeight: 1 }),
face('subtitle', '{subtitle}', 'Sorts Mill Goudy', 13, 80,
{ italic: true, color: col('sage') }),
image('sprig', onPage(96, 30)),
face('press', 'The Herbarium Press', 'Marcellus SC', 8.5, 182,
{ letterSpacing: pt(1.8), color: col('muted') })) },
{ id: 'contents', ...leaf, span: 'column', advancedDesign: { enabled: false },
fontFamily: 'Italiana', fontSize: pt(24), lineHeight: pt(LEAD * 2), marginBottom: pt(LEAD) },
];
Ein Überschriftenformat kann auf eine neue Seite umbrechen und dort eine Gestaltung zeichnen, deshalb sind Schmutztitel, Frontispiz und Titelblatt je eine Überschrift mit eigenem Format. numbered: false hält sie aus der Zählung heraus, und das erste Gedicht ist weiterhin I. toc: false hält sie aus dem Inhaltsverzeichnis heraus, und eine leere Fußzeile lässt ihre Seiten ohne Pagina (Überschriftenformate). Die Formate laufen über die ganze Seitenbreite, weil der Fuß des Satzspiegels eine in der Spalte gehaltene Gestaltung beschneidet, was die randabfallende Tafel dort kappen und ihre Bildunterschrift verlieren würde. Die Bildunterschrift kommt aus dem Attribut caption der Überschrift; Autor, Titel und Untertitel auf dem Titelblatt kommen aus dem Frontmatter.
#5 · Das Inhaltsverzeichnis füllt sich selbst
// The numerals stand on the titles' baseline, in Italiana and sage, at the titles' size.
const contents = {
levels: [{ level: 1, italic: true, marginBottom: pt(LEAD), numberFontFamily: 'Italiana',
numberFontWeight: 400, numberColor: col('sage'), numberWidth: mm(6), numberGap: mm(3) }],
pageNumber: { fontFamily: 'Marcellus SC', fontSize: pt(9), color: col('muted'), width: mm(6) },
leader: { char: '. ', gap: mm(2) }, // spaced dots, right-aligned so they line up
};
:::toc listet jede Überschrift, die ihr Format zulässt, mit der Ziffer aus derselben Vorlage und der Beschriftung der Seite, auf der die Überschrift landet. Der Build setzt das Buch erneut, bis sich diese Seitenbeschriftungen nicht mehr ändern (Inhaltsverzeichnis). Die eigene Überschrift der Seite, „Contents“, hat ein Format mit numbered: false; ohne diese Angabe wäre „Contents“ Gedicht I. Die Füllzeichen '. ' setzen nach jedem Punkt ein Leerzeichen. Die Punkte sind rechtsbündig ausgerichtet, sodass sie von einem Eintrag zum nächsten untereinanderstehen, und sie erscheinen in Schrift und Farbe der Seitenzahlen, weil toc.leader keine Farbeinstellung hat.
#6 · Ein Ornament ist ein Ressourcentyp ohne Bezeichnung
// No caption prefix and no caption: the sprig prints bare, where ::resource{id="sprig"} puts
// it (double quotes: gotcha resource-double-quotes), a fifth of the measure wide, centred.
const ornament = { id: 'ornament', name: 'Ornament', shortLabel: '', captionPrefix: '',
numberingTemplate: '{n}', resetOn: 'never', counterFormat: 'decimal',
defaultPlacement: { position: 'here', width: 0.2, align: 'center' } };
Ein Bild im Text ist eine Ressource, und jede Ressource hat einen Typ. Der Ornamenttyp hat ein leeres Legendenpräfix, und der Zweig hat keine Legende, deshalb erscheint keine Bezeichnung wie „Figure 1“ darunter (Ressourcentypen). Das defaultPlacement des Typs setzt den Zweig dorthin, wo ::resource{id="sprig"} steht, zentriert und ein Fünftel der Satzbreite breit, sodass die Schlussvignette unter Inversnaid eine Zeile Markdown braucht. resourceTypes ersetzt die Standardliste, deshalb hat dieses Buch keinen Typ Figure oder Table. Um sie zu behalten, setzen Sie defaultResourceTypes() per Spread vor das Ornament.
Das ganze Rezept
// ═══ Postext Cookbook · Nº 015 · Poems set line by line ═════════════════════════════ // https://postext.dev/en/cookbook/poetry-collection // Code: MIT · Text: G. M. Hopkins, Poems, 1918 (PD) · Plate: diffusion models · Sprig: code // Fonts: Sorts Mill Goudy, Italiana, Marcellus 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 = 'poetry-collection'; // ─── 1 · Design ───────────────────────────────────────────────────────────── const palette = { // every colour in the config links to one of these ink: '#26221f', // the text: a warm near-black sage: '#56673f', // the one accent: numerals, the subtitle, the ornament's leaves sepia: '#8c5f3a', // used for the rowan berries only muted: '#746a60', // dates, folios, leaders and the colophon paper: '#fbf8f2', // the page }; // A colour is its hex and the palette entry it follows. 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 defaults link to 'main-color': point it at the accent, so nothing prints blue. { id: 'main-color', name: 'accent (defaults)', value: { hex: palette.sage, model: 'hex' } }, ]; const TRIM_W = 140, TRIM_H = 216; // mm: a poetry trim, tall enough for a sonnet's turnovers const LEAD = 15; // pt: the body's leading, the grid every line of verse sits on // #region answer: verse: a :::verse block keeps each line, its indent, the stanza breaks // A poem is one :::verse block written as it is printed: a line of verse a line, a blank line // between stanzas (one line of the grid), and spaces before an indented line, two to an em: // :::verse{style="verse" align=start} // The world is charged with the grandeur of God. // It will flame out, like shining from shook foil; // // And for all this, nature is never spent; // ::: // align=start sets the poem flush with the margin (the default centres it on its longest line). const verse = { id: 'verse', hangingIndent: em(2), // a turnover hangs 2 em past its line's start: 4 em under a 2 em indent // Verse keeps its words whole, even where bodyText.hyphenation.ragged is switched on. hyphenation: false, }; // Hook-up: paragraphStyles: [verse, …]; the Markdown goes to buildDocument as it is written. // #endregion // #region lines: the lines around a poem: a dedication above it, a place and a date below const lineStyles = [ { id: 'dedication', fontSize: pt(9.5), marginBottom: pt(LEAD) }, // under The Windhover { id: 'date', fontFamily: 'Marcellus SC', fontSize: pt(8), color: col('muted'), textAlign: 'right', marginTop: pt(LEAD) }, // a line of space above, flush right ]; // #endregion // #region poem-head: every poem opens a page under its numeral and its title // '{1:I}' numbers the poems I to IV: {number} prints the numeral here, :::toc in the contents. // The head is HEAD_LINES grid lines (minHeight, no bottom margin), so each poem starts on the same // line; numeral and title fill 4, or 5 if the title wraps. const HEAD_LINES = 6; const poemHead = { enabled: true, minHeight: pt(LEAD * HEAD_LINES), slot: { elements: [ { kind: 'text', id: 'numeral', content: '{number}', fontFamily: 'Italiana', fontSize: pt(22), color: col('sage'), align: 'left', placement: { anchor: { to: 'container', edge: 'top-left' } } }, { kind: 'text', id: 'title', content: '{titleText}', fontFamily: 'Sorts Mill Goudy', italic: true, fontSize: pt(17), color: col('ink'), align: 'left', placement: { anchor: { to: '#numeral', edge: 'below' }, offset: { y: mm(2) }, size: { width: 'fill' } } }, ] } }; // Parity 'any': the next page, recto or verso, with no blank page between two poems. const poems = { level: 1, numberingTemplate: '{1:I}', advancedDesign: poemHead, marginBottom: pt(0), breakBefore: { enabled: true, parity: 'any' } }; // #endregion // #region front: the half-title, the frontispiece, the title page and the contents // Headings too, each on a page of its own, with no number, contents entry or folio. // A style restates the break, or inherits the poems' (gotcha: style-inherits-break). const leaf = { numbered: false, toc: false, span: 'page', footer: { elements: [] }, breakBefore: { enabled: true, parity: 'any' } }; const onPage = (y, width) => ({ anchor: { to: 'page', edge: 'top' }, offset: { y: mm(y) }, ...(width && { size: { width: mm(width) } }) }); // centred, y mm below the trim const face = (id, content, font, size, y, extra = {}) => ({ kind: 'text', id, content, fontFamily: font, fontSize: pt(size), color: col('ink'), align: 'center', placement: onPage(y), ...extra }); const image = (id, placement) => ({ kind: 'image', id, resourceId: id, placement }); const design = (...elements) => ({ enabled: true, slot: { elements } }); const front = [ { id: 'half-title', ...leaf, advancedDesign: design(face('title', '{titleText}', 'Italiana', 20, 60)) }, { id: 'plate', ...leaf, advancedDesign: design( // span 'page': a column's foot would cut it image('plate', { anchor: { to: 'bleed', edge: 'top-left' }, size: { width: 'fill' } }), face('caption', '{attr.caption}', 'Sorts Mill Goudy', 8.5, 203, { italic: true, color: col('muted') })) }, { id: 'title-page', ...leaf, advancedDesign: design( face('author', '{author}', 'Marcellus SC', 10, 46, // {author}, {title}: the frontmatter { letterSpacing: pt(2.4), textTransform: 'uppercase' }), face('title', '{title}', 'Italiana', 54, 56, { lineHeight: 1 }), face('subtitle', '{subtitle}', 'Sorts Mill Goudy', 13, 80, { italic: true, color: col('sage') }), image('sprig', onPage(96, 30)), face('press', 'The Herbarium Press', 'Marcellus SC', 8.5, 182, { letterSpacing: pt(1.8), color: col('muted') })) }, { id: 'contents', ...leaf, span: 'column', advancedDesign: { enabled: false }, fontFamily: 'Italiana', fontSize: pt(24), lineHeight: pt(LEAD * 2), marginBottom: pt(LEAD) }, ]; // #endregion // #region contents: what :::toc prints for each poem: numeral, italic title, leader, page // The numerals stand on the titles' baseline, in Italiana and sage, at the titles' size. const contents = { levels: [{ level: 1, italic: true, marginBottom: pt(LEAD), numberFontFamily: 'Italiana', numberFontWeight: 400, numberColor: col('sage'), numberWidth: mm(6), numberGap: mm(3) }], pageNumber: { fontFamily: 'Marcellus SC', fontSize: pt(9), color: col('muted'), width: mm(6) }, leader: { char: '. ', gap: mm(2) }, // spaced dots, right-aligned so they line up }; // #endregion // #region ornament: a resource type for artwork that prints no caption and no label // No caption prefix and no caption: the sprig prints bare, where ::resource{id="sprig"} puts // it (double quotes: gotcha resource-double-quotes), a fifth of the measure wide, centred. const ornament = { id: 'ornament', name: 'Ornament', shortLabel: '', captionPrefix: '', numberingTemplate: '{n}', resetOn: 'never', counterFormat: 'decimal', defaultPlacement: { position: 'here', width: 0.2, align: 'center' } }; // #endregion const proseStyles = [ // the note on the text and the colophon, under the contents { id: 'note', fontSize: pt(9.5), lineHeight: pt(13) }, { id: 'colophon', fontSize: pt(8), lineHeight: pt(11), color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD) }, ]; const folio = (pages) => ({ kind: 'text', id: `folio-${pages}`, content: '{pageNumber}', pages, fontFamily: 'Marcellus SC', fontSize: pt(9), color: col('muted'), align: 'center', placement: { anchor: { to: 'container', edge: 'top' }, offset: { y: mm(10) } } }); const config = () => ({ locale: 'en-us', // the sample is in English colorPalette, // The list replaces Figure and Table; a book with figures spreads defaultResourceTypes() in. resourceTypes: [ornament], page: { // mirror: left is the inner margin; 150 dpi is for the screen sizePreset: 'custom', width: mm(TRIM_W), height: mm(TRIM_H), dpi: 150, backgroundColor: col('paper'), margins: { top: mm(22), bottom: mm(24), left: mm(22), right: mm(18), mirror: true }, }, layout: { layoutType: 'single' }, bodyText: { // the note's prose: justified, hyphenated (en-us), spaces 0.8–1.6 of normal fontFamily: 'Sorts Mill Goudy', fontSize: pt(11), lineHeight: pt(LEAD), color: col('ink'), boldColor: col('ink'), italicColor: col('ink'), firstLineIndent: mm(4), indentAfterHeading: false, minWordSpacing: 0.8, maxWordSpacing: 1.6, }, headings: { fontFamily: 'Sorts Mill Goudy', fontWeight: 400, color: col('ink'), levels: [poems, { level: 2, fontFamily: 'Marcellus SC', fontSize: pt(9), lineHeight: pt(LEAD), color: col('sage'), marginTop: pt(0), marginBottom: pt(0) }], }, headingStyles: front, toc: contents, paragraphStyles: [verse, ...lineStyles, ...proseStyles], header: { elements: [] }, // no running heads: each poem starts a page under its own title footer: { elements: [folio('opener'), folio('body')] }, // centred; never on a blank page }); // ─── 2 · Content ──────────────────────────────────────────────────────────── const markdown = String.raw`---Markdown-Beispiel · 130 Zeilen · content.en.md
title: "Pied Beauty" subtitle: "Four poems of the natural world" author: "Gerard Manley Hopkins" --- # Pied Beauty {style="half-title"} # Fern and Rowan {style="plate" caption="Fern and rowan: the ‘flitches of fern’ and the ‘beadbonny ash’ of Inversnaid"} # Pied Beauty {style="title-page"} # Contents {style="contents"} :::toc :::space{lines=2} ## A note on the text :::paragraphs{style="note"} This selection follows the first edition of the poems, edited by Robert Bridges in 1918, nearly thirty years after Hopkins’s death. Its spelling, accents and indents are kept, and lines that rhyme with one another start the same distance in. A line too long for the page is turned over, and the turnover is set deeper than the lines around it, so that it reads as part of the line above. No word is broken unless Hopkins broke it, as he did at the end of the first line of *The Windhover*. Under each poem is the date from Bridges’s notes, with the place when he gives one. ::: :::paragraphs{style="colophon"} Set in Sorts Mill Goudy, Italiana and Marcellus SC (SIL Open Font License). Text from Project Gutenberg eBook 22403, accents and indents from Wikisource. Plate made with diffusion models; ornament drawn for this edition. ::: # God’s Grandeur :::verse{style="verse" align=start} The world is charged with the grandeur of God. It will flame out, like shining from shook foil; It gathers to a greatness, like the ooze of oil Crushed. Why do men then now not reck his rod? Generations have trod, have trod, have trod; And all is seared with trade; bleared, smeared with toil; And wears man’s smudge and shares man’s smell: the soil Is bare now, nor can foot feel, being shod. And for all this, nature is never spent; There lives the dearest freshness deep down things; And though the last lights off the black West went Oh, morning, at the brown brink eastward, springs— Because the Holy Ghost over the bent World broods with warm breast and with ah! bright wings. ::: :::paragraphs{style="date"} 23 February 1877 ::: # The Windhover :::paragraphs{style="dedication"} *To Christ our Lord* ::: :::verse{style="verse" align=start} I caught this morning morning’s minion, king- dom of daylight’s dauphin, dapple-dawn-drawn Falcon, in his riding Of the rolling level underneath him steady air, and striding High there, how he rung upon the rein of a wimpling wing In his ecstacy! then off, off forth on swing, As a skate’s heel sweeps smooth on a bow-bend: the hurl and gliding Rebuffed the big wind. My heart in hiding Stirred for a bird,—the achieve of, the mastery of the thing! Brute beauty and valour and act, oh, air, pride, plume, here Buckle! AND the fire that breaks from thee then, a billion Times told lovelier, more dangerous, O my chevalier! No wonder of it: shéer plód makes plough down sillion Shine, and blue-bleak embers, ah my dear, Fall, gall themselves, and gash gold-vermillion. ::: :::paragraphs{style="date"} St Beuno’s, 30 May 1877 ::: # Pied Beauty :::verse{style="verse" align=start} Glory be to God for dappled things— For skies of couple-colour as a brinded cow; For rose-moles all in stipple upon trout that swim: Fresh-firecoal chestnut-falls; finches’ wings; Landscape plotted and pieced—fold, fallow, and plough; And áll trádes, their gear and tackle and trim. All things counter, original, spare, strange; Whatever is fickle, freckled (who knows how?) With swift, slow; sweet, sour; adazzle, dim; He fathers-forth whose beauty is past change: Praise him. ::: :::paragraphs{style="date"} St Beuno’s, Tremeirchion, summer 1877 ::: # Inversnaid :::verse{style="verse" align=start} This darksome burn, horseback brown, His rollrock highroad roaring down, In coop and in comb the fleece of his foam Flutes and low to the lake falls home. A windpuff-bonnet of fáwn-fróth Turns and twindles over the broth Of a pool so pitchblack, féll-frówning, It rounds and rounds Despair to drowning. Degged with dew, dappled with dew Are the groins of the braes that the brook treads through, Wiry heathpacks, flitches of fern, And the beadbonny ash that sits over the burn. What would the world be, once bereft Of wet and of wildness? Let them be left, O let them be left, wildness and wet; Long live the weeds and the wilderness yet. ::: :::paragraphs{style="date"} 28 September 1881 ::: ::resource{id="sprig"}`; // content.<lang>.md, inlined by the Cookbook // #region art: a rowan sprig for the ornament, drawn in code; the plate is a JPEG in assets/ const f = (n) => n.toFixed(1); const path = (d, fill, a = 1) => `<path d="${d}" fill="${fill}" fill-opacity="${a}"/>`; const ring = (pts) => `M${pts.map(([x, y]) => `${f(x)} ${f(y)}`).join('L')}Z`; const disk = (x, y, r, fill, a = 1) => `<circle cx="${f(x)}" cy="${f(y)}" r="${f(r)}" ` + `fill="${fill}" fill-opacity="${a}"/>`; const PX = 10; // a drawing w × h mm has a viewBox in tenths of a millimetre, w·PX × h·PX const svgOf = (w, h, body) => `<svg xmlns="http://www.w3.org/2000/svg" width="${w * PX}" ` + `height="${h * PX}" viewBox="0 0 ${w * PX} ${h * PX}">${body}</svg>`; // A cubic Bézier and its unit normal at t. const bez = ([p0, p1, p2, p3], t) => { const u = 1 - t; const coord = (i) => u * u * u * p0[i] + 3 * u * u * t * p1[i] + 3 * u * t * t * p2[i] + t * t * t * p3[i]; const dt = (i) => 3 * u * u * (p1[i] - p0[i]) + 6 * u * t * (p2[i] - p1[i]) + 3 * t * t * (p3[i] - p2[i]); const [dx, dy] = [dt(0), dt(1)]; const len = Math.hypot(dx, dy) || 1; return { x: coord(0), y: coord(1), a: Math.atan2(dy, dx), nx: -dy / len, ny: dx / len }; }; // A stalk: the curve drawn as a filled band tapering from w0 to w1. const stalk = (curve, w0, w1) => { const left = []; const right = []; for (let i = 0; i <= 40; i++) { const p = bez(curve, i / 40); const w = (w0 + (w1 - w0) * (i / 40)) / 2; left.push([p.x + p.nx * w, p.y + p.ny * w]); right.unshift([p.x - p.nx * w, p.y - p.ny * w]); } return ring([...left, ...right]); }; // A leaf blade from its base (x, y) along angle a: length l, half-width w, `teeth` serrations. const blade = (x, y, a, l, w, teeth = 0) => { const [c, s] = [Math.cos(a), Math.sin(a)]; const at = (u, v) => [x + u * c - v * s, y + u * s + v * c]; const side = (sign) => Array.from({ length: 25 }, (_, i) => { const t = i / 24; let h = w * Math.sin(Math.PI * t ** 0.85) ** 0.9; if (teeth && t > 0.25) h *= 1 - 0.18 * ((t * teeth) % 1); return at(l * t, sign * h); }); return ring([...side(1), ...side(-1).reverse()]); }; // The ornament: a rowan leaf laid flat, with a bunch of berries at its tip. function sprig() { const [w, h] = [36, 12]; const rib = [[16, 66], [90, 56], [170, 56], [236, 60]]; const d = [stalk(rib, 6, 3)]; for (let j = 0; j < 5; j++) { const q = bez(rib, 0.12 + j * 0.19); const size = 50 - j * 3; for (const sgn of [1, -1]) d.push(blade(q.x, q.y, q.a + sgn * 1.1, size, size * 0.22, 8)); } const tip = bez(rib, 1); d.push(blade(tip.x, tip.y, tip.a, 44, 10.5, 8)); const out = [path(d.join(''), palette.sage)]; for (const [dx, dy, r] of [[34, -16, 9], [44, 2, 10], [30, 14, 9], [52, -12, 8.5], [58, 10, 8], [22, -2, 8]]) { const [bx, by] = [262 + dx, 60 + dy]; out.push(path(stalk([[240, 64], [252, 66], [bx - 10, by + 2], [bx, by]], 2.6, 1.8), palette.sage), disk(bx, by, r, palette.sepia)); } return svgOf(w, h, out.join('')); } await loadSvg('sprig.svg', sprig()); // The frontispiece is a photogram: the whole 140 × 216 mm page, its torn coating and the paper // under it in one JPEG, declared at its pixels. await loadImage('plate-1120.jpg', asset('plate-1120.jpg')); // Nothing cites them: designs show the plate and the sprig, ::resource sets the tailpiece. const svgResource = (id, w, h, altText) => ({ id, typeId: 'ornament', kind: 'svg', altText, createdAt: 0, updatedAt: 0, svg: { fileId: `${id}.svg`, width: w * PX, height: h * PX } }); const resources = [ { id: 'plate', typeId: 'ornament', kind: 'bitmap', createdAt: 0, updatedAt: 0, bitmap: { fileId: 'plate-1120.jpg', format: 'jpeg', width: 1120, height: 1728 }, altText: 'A photogram of a fern frond and a sprig of rowan in berry, left white on a ' + 'brushed sage ground.' }, svgResource('sprig', 36, 12, 'Ornament: a rowan leaf with a bunch of berries.'), ]; // #endregion // ─── 3 · Fonts ────────────────────────────────────────────────────────────── // Loaded before the first build. None of the three ships a bold. const FONTS = { 'Sorts Mill Goudy': ['400', '400i'], Italiana: ['400'], 'Marcellus SC': ['400'] }; // ─── 4 · Build & show ─────────────────────────────────────────────────────── // :::toc lists the page each poem lands on: the build lays out again until those settle. const doc = await buildDocumentWithFonts({ markdown, resources }, config(), kitFonts(FONTS)); showPages(doc, { title: 'Pied Beauty · four poems by Gerard Manley Hopkins' });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
#Die Folgezeilen tiefer hängen
Ein tieferer hängender Einzug setzt den Rest einer umbrochenen Zeile weiter vom Rand ab, für Gedichte, deren Zeilen oft umbrechen.
- hangingIndent: em(2), // a turnover hangs 2 em past its line's start: 4 em under a 2 em indent
+ hangingIndent: em(4), // a turnover hangs 2 em past its line's start: 4 em under a 2 em indent#Die Folgezeilen rechtsbündig setzen
Mit turnover: 'right' in bodyText.verse steht der Rest einer für die Satzbreite zu langen Zeile rechtsbündig auf der Zeile darunter, hinter einer öffnenden eckigen Klammer, wie viele englische Lyrikausgaben es setzen.
indentAfterHeading: false, minWordSpacing: 0.8, maxWordSpacing: 1.6,
+ verse: { turnover: 'right' }, // a turnover flush right, behind [
},#Jedes Gedicht auf einer rechten Seite eröffnen
Mit der Parität 'odd' eröffnet jedes Gedicht auf der nächsten rechten Seite. Das Buch wächst von acht auf elf Seiten, und die drei leeren linken Seiten drucken keine Pagina, weil die Paginas nach Seitenrolle gefiltert sind.
const poems = { level: 1, numberingTemplate: '{1:I}', advancedDesign: poemHead,
- marginBottom: pt(0), breakBefore: { enabled: true, parity: 'any' } };
+ marginBottom: pt(0), breakBefore: { enabled: true, parity: 'odd' } };#Die Gedichte mit arabischen Ziffern nummerieren
Der Zählerstil der Vorlage ändert die Ziffer auf der Seite des Gedichts und im Inhaltsverzeichnis zugleich.
-const poems = { level: 1, numberingTemplate: '{1:I}', advancedDesign: poemHead,
+const poems = { level: 1, numberingTemplate: '{1}', advancedDesign: poemHead,Häufige Fehler
Häufiger Fehler
'1998. ' oder '- ' am Absatzanfang eröffnet eine Liste
Ein Absatz, der mit einer Zahl, einem Punkt und einem Leerzeichen oder mit einem Bindestrich und einem Leerzeichen beginnt, wird zu einem Listenpunkt. Setzen Sie einen Wortverbinder (U+2060) vor die Zahl, und schreiben Sie Dialoge mit einem Geviertstrich. Maskierung und wörtliche Zeichen →
Häufiger Fehler
Ein Überschriftenformat erbt den Seitenumbruch seiner Ebene
Ein Eintrag in headingStyles übernimmt jedes Feld, das er auslässt, von seiner Überschriftenebene, breakBefore eingeschlossen. Ein Inhaltsverzeichnis oder Impressum, das nach einem :::pagebreak über eine H1 gestaltet wird, erbt die Parität 'odd' und landet hinter einer Vakatseite. Geben Sie einem solchen Format breakBefore: { enabled: false }. Überschriftenformate →
Häufiger Fehler
::resource{id="…"} nimmt nur doppelte Anführungszeichen
Eine Block-Einbettung wird nur als ::resource{id="…"} mit doppelten Anführungszeichen erkannt; jede andere Form bleibt als sichtbare Zeile im Text. Abbildungen genau hier →
- Halten Sie die Zeilen einer Strophe zusammen: Eine Leerzeile in einem Block
:::versebeginnt eine neue Strophe, gleich wie viele Leerzeilen es sind. - Eine Zeile, die mit einem Pluszeichen und einem Leerzeichen beginnt, ist eine abgesetzte Zeile, die dort beginnt, wo die Zeile darüber endete; schreiben Sie
\+, um das Pluszeichen zu drucken. - Ohne
align=startwird jedes Gedicht auf seine längste Zeile zentriert, und jedes Gedicht steht dann in einem anderen Abstand vom Rand. - Um zwei Wörter gemeinsam umzubrechen, verbinden Sie sie mit einem geschützten Leerzeichen (U+00A0), wie „the soil“ und „bright wings.“ auf Seite 5.
- Ein Element der Fußzeile, dessen
pagesauf'all'steht, druckt auch auf leeren Seiten. Die Pagina besteht hier aus zwei Elementen, einem für Auftakte und einem für Textseiten, sodass eine leere linke Seite leer bleibt.
Nachweise
- Rezept
- Ignacio Ferro
- Text
- “God’s Grandeur”, “The Windhover”, “Pied Beauty” and “Inversnaid”, with the places and dates from the editor’s notes, in the first edition of Poems of Gerard Manley Hopkins (1918); accents and indents checked against the proofread Wikisource transcription of that edition · Gerard Manley Hopkins; Robert Bridges (editor) · gemeinfrei
- The note on the text, the plate’s caption and the colophon · Ignacio Ferro · CC BY 4.0
- Bilder
- The frontispiece photogram of fern and rowan · Generated With Diffusion Models · eigener Text
- The rowan ornament, drawn in code in the page’s palette · Ignacio Ferro · CC BY 4.0
- Schriften
- Sorts Mill Goudy (SIL OFL 1.1) · Italiana (SIL OFL 1.1) · Marcellus 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)


