Kurz gesagt
Ein Essay darüber, wie britische Städte zur Londoner Zeit kamen. Jede Quelle, die der Autor zitiert, wird zu einer nummerierten Note am Fuß der Seite, und die Liste der Werke steht am Ende.
Was Sie setzen
Ein Sonderdruck aus der technikgeschichtlichen Reihe einer Zeitschrift: ein Essay von etwa 1.100 Wörtern darüber, wie britische Städte zwischen 1840 und 1880 ihren eigenen Mittag für den Londoner aufgaben. Die Seite misst 156 × 234 mm, gespiegelt, in Crimson Pro mit 11/14 pt, mit einem Band in Eisenbahngrün über der oberen Hälfte der ersten Seite. Der Autor schreibt jede Quelle als Schlüssel, [@howse1980, 87], und Postext setzt jede als nummerierte Note am Fuß der Seite im Chicago-Stil „notes and bibliography“. Die erste Note zu einem Werk nennt Autor, Titel und Verlag; jede spätere die Kurzform. Die eigenen erläuternden Noten des Autors nehmen ihre Nummern aus derselben Folge, und die Bibliografie schließt den Essay ab.
Dieses Rezept beantwortet
- Wie setze ich Zitate als Fußnoten im Chicago-Stil, mit Kurzformen für spätere Zitate?
- Wie zitiere ich Werke und erstelle das Literaturverzeichnis nach APA, IEEE oder einem anderen Zitierstil?
Die kurze Antwort
// The text cites with [@howse1980, 87]; a note style turns every citation into a
// footnote. The first note for a work gives it in full, later ones in the short form
// (author, short title, page), and the bibliography lists the works by author.
registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES }));
const citations = {
style: 'chicago-notes-bibliography', // CMOS 18th edition, notes and bibliography
notes: 'footnote', // each citation in a note at the foot of the page
link: true, // a note's citation jumps to its bibliography entry
bibliography: { fontSize: em(0.92), lineHeight: pt(13), hangingIndent: em(1.8),
entrySpacing: pt(3) },
};
// The citation notes share the numbering and the look of the author's own [^notes].
const footnotes = {
placement: 'column', numbering: 'chapter',
fontSize: pt(8.5), lineHeight: pt(10.75), spaceBetween: pt(2),
textAlign: 'left', // ragged: the gap after a note's number stays the same in every note
separator: { width: 0.3, lineWidth: pt(0.5), color: col('rule') },
};
Zutaten
- Funktionen
- Zitate in FußnotenZitate in einem ZitierstilLiteraturverzeichnis aus den QuellenFußnotenGestaltete AuftakteÜberschriftenattributeKolumnentitel und SeitenzahlenGespiegelte RänderAbsatzformateSemantische FarbpalettePDF-Export
- Schrift
- Crimson Pro, IBM Plex Sans Condensed (SIL OFL 1.1)
- Dateien
- Keine: Jedes Bild wird im Code gezeichnet
Zubereitung
#1 · Eine Note für jedes Zitat
Der Code steht in der kurzen Antwort oben. chicago-notes-bibliography ist ein Notenstil, also hinterlässt ein Zitat im Text eine hochgestellte Nummer, und sein Wortlaut geht in eine Fußnote, die mit den eigenen [^notes] des Autors durchgezählt wird; notes: 'footnote' sagt das ausdrücklich. citeproc-js merkt sich, welche Werke es bereits zitiert hat, sodass Note 3 Derek Howse, Greenwich Time and the Discovery of the Longitude (Oxford University Press, 1980), 87 lautet und Note 4, die dasselbe Buch erneut zitiert, Howse, Greenwich Time and the Discovery of the Longitude, 90–91. Der mitgelieferte Stil ist die 18. Auflage des Chicago Manual, die Ibid. abschafft und die Kurzform wiederholt, also drucken die Noten 11 und 12 zweimal die Kurzform.
#2 · Auch Noten des Autors zitieren
Ein Zitat in einer [^note] bleibt in dieser Note und wird inline gesetzt, vollständig, wenn das Werk zum ersten Mal erscheint: Note 1 ist der Satz des Autors, gefolgt von der vollständigen Angabe zu Schivelbusch. Jedes Zitat endet mit dem Schlusspunkt des Stils, also braucht eine Note, die mit einem Zitat endet, im Markdown keinen eigenen Punkt.
#3 · Präfixe, Suffixe und Fundstellen
[see @simmons1991, 346–47; and @bartky2000, chap. 9] setzt see vor das erste Werk, trennt die beiden mit einem Semikolon und behält die Fundstelle jedes Werks. Eine bloße Zahl ist eine Seite; chap. und cap. bezeichnen in beiden Sprachen ein Kapitel. Text nach der Fundstelle ist ein Suffix: [@howse1980, 82–84, on the mail-coach timepieces] druckt die Wendung nach den Seiten.
#4 · Der Auftakt sitzt in einem Band
const BAND = 122; // mm from the trim's top
const opener = {
enabled: true,
minHeight: mm(BAND - MARGIN.top + 9), // the text starts 9 mm under the band
slot: { elements: [
{ kind: 'box', id: 'band', style: { backgroundColor: col('green') },
placement: { ...at('page', 'top-left'), size: { width: 'fill', height: mm(BAND) } } },
{ kind: 'text', id: 'kicker', content: '{attr.kicker}', fontFamily: LABEL, fontWeight: 600,
fontSize: pt(7.5), letterSpacing: pt(1.4), textTransform: 'uppercase', color: col('tint'),
align: 'left', placement: at('container', 'top-left', 0, 4) },
{ kind: 'text', id: 'years', content: '{attr.years}', fontFamily: TEXT, fontWeight: 300,
fontSize: pt(54), lineHeight: 1, color: col('tint'), align: 'left',
placement: at('container', 'top-left', -1, 16) },
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: TEXT, fontWeight: 600,
fontSize: pt(25), lineHeight: 1.08, color: col('paper'), align: 'left', overflow: 'wrap',
placement: { ...at('container', 'top-left', 0, 47),
size: { width: mm(112), height: 'auto' } } },
{ kind: 'text', id: 'byline', content: '{attr.byline}', fontFamily: LABEL, fontWeight: 600,
fontSize: pt(9), letterSpacing: pt(0.6), color: col('tint'), align: 'left',
placement: at('container', 'top-left', 0, 92) },
] },
};
Das Band reicht 122 mm vom Endformat herab, und der Text beginnt 9 mm darunter. Die Jahreszahlen kommen aus einem Attribut der Überschrift, years="1840–1880", gesetzt in Crimson Pro Light mit 54 pt, sodass die Karte eine Zahl so groß wie ein Titel zeigt; das Attribut für die Autorenzeile speist auch den Kolumnentitel der linken Seiten.
const palette = {
ink: '#211d1a', green: '#24493d', tint: '#dfe8e2', rule: '#b7b0a4', muted: '#6a645b',
paper: '#fbf9f4',
};
// col() links a colour to its palette entry; the hex is the value the entry holds today.
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = Object.entries({ ...palette, 'main-color': palette.green })
.map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
Das ganze Rezept
// ═══ Postext Cookbook · Nº 089 · A history essay with Chicago notes ═══════════════ // https://postext.dev/en/cookbook/history-essay-chicago-notes // Code: MIT · Text: original (CC BY 4.0) · Pictures: none // Fonts: Crimson Pro, IBM Plex Sans Condensed (SIL OFL 1.1) · Needs postext ≥ 1.25.0 import { buildDocumentWithFonts, renderPageToCanvas, registerCitationEngine, } from 'https://esm.sh/postext'; import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf'; import { createCiteprocEngine, STYLES, LOCALES } from 'https://esm.sh/postext-citeproc'; const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es') const RECIPE = 'history-essay-chicago-notes'; // ─── 1 · Design ───────────────────────────────────────────────────────────── // #region palette: a railway green for the opener band, ink and warm paper for the rest const palette = { ink: '#211d1a', green: '#24493d', tint: '#dfe8e2', rule: '#b7b0a4', muted: '#6a645b', paper: '#fbf9f4', }; // col() links a colour to its palette entry; the hex is the value the entry holds today. const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id }); const colorPalette = Object.entries({ ...palette, 'main-color': palette.green }) .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })); // #endregion const TEXT = 'Crimson Pro', LABEL = 'IBM Plex Sans Condensed'; const TRIM = { width: 156, height: 234 }; // a journal offprint, trimmed like a trade book const MARGIN = { top: 22, bottom: 22, inner: 20, outer: 17 }; const LEAD = 14; // pt const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } }); // #region answer: Chicago notes and bibliography, one note per citation // The text cites with [@howse1980, 87]; a note style turns every citation into a // footnote. The first note for a work gives it in full, later ones in the short form // (author, short title, page), and the bibliography lists the works by author. registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES })); const citations = { style: 'chicago-notes-bibliography', // CMOS 18th edition, notes and bibliography notes: 'footnote', // each citation in a note at the foot of the page link: true, // a note's citation jumps to its bibliography entry bibliography: { fontSize: em(0.92), lineHeight: pt(13), hangingIndent: em(1.8), entrySpacing: pt(3) }, }; // The citation notes share the numbering and the look of the author's own [^notes]. const footnotes = { placement: 'column', numbering: 'chapter', fontSize: pt(8.5), lineHeight: pt(10.75), spaceBetween: pt(2), textAlign: 'left', // ragged: the gap after a note's number stays the same in every note separator: { width: 0.3, lineWidth: pt(0.5), color: col('rule') }, }; // #endregion // #region opener: the green band holds the kicker, the dates and the title const BAND = 122; // mm from the trim's top const opener = { enabled: true, minHeight: mm(BAND - MARGIN.top + 9), // the text starts 9 mm under the band slot: { elements: [ { kind: 'box', id: 'band', style: { backgroundColor: col('green') }, placement: { ...at('page', 'top-left'), size: { width: 'fill', height: mm(BAND) } } }, { kind: 'text', id: 'kicker', content: '{attr.kicker}', fontFamily: LABEL, fontWeight: 600, fontSize: pt(7.5), letterSpacing: pt(1.4), textTransform: 'uppercase', color: col('tint'), align: 'left', placement: at('container', 'top-left', 0, 4) }, { kind: 'text', id: 'years', content: '{attr.years}', fontFamily: TEXT, fontWeight: 300, fontSize: pt(54), lineHeight: 1, color: col('tint'), align: 'left', placement: at('container', 'top-left', -1, 16) }, { kind: 'text', id: 'title', content: '{titleText}', fontFamily: TEXT, fontWeight: 600, fontSize: pt(25), lineHeight: 1.08, color: col('paper'), align: 'left', overflow: 'wrap', placement: { ...at('container', 'top-left', 0, 47), size: { width: mm(112), height: 'auto' } } }, { kind: 'text', id: 'byline', content: '{attr.byline}', fontFamily: LABEL, fontWeight: 600, fontSize: pt(9), letterSpacing: pt(0.6), color: col('tint'), align: 'left', placement: at('container', 'top-left', 0, 92) }, ] }, }; // #endregion const head = (id, content, parity, edge, x, align) => ({ kind: 'text', id, content, parity, pages: 'body', fontFamily: LABEL, fontWeight: 600, fontSize: pt(7.5), letterSpacing: pt(1.1), textTransform: 'uppercase', color: col('muted'), align, placement: at('page', edge, x, 12), }); const config = () => ({ locale: t({ en: 'en-us', es: 'es' }), // the hyphenation patterns of each edition colorPalette, citations, footnotes, 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 }, }, layout: { layoutType: 'single' }, bodyText: { fontFamily: TEXT, fontSize: pt(11), lineHeight: pt(LEAD), color: col('ink'), boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'), textAlign: 'justify', firstLineIndent: mm(5), indentAfterHeading: false, hyphenation: { enabled: true }, optimalLineBreaking: true, avoidWidows: true, avoidOrphans: true, avoidRunts: true, }, headings: { fontFamily: TEXT, fontWeight: 600, color: col('ink'), levels: [ // parity 'any': the H1 opens on the next page, either side (the default waits for a recto). { level: 1, fontSize: pt(25), breakBefore: { enabled: true, parity: 'any' }, advancedDesign: opener }, { level: 2, fontFamily: LABEL, fontWeight: 600, fontSize: pt(8.5), letterSpacing: pt(1.3), textTransform: 'uppercase', color: col('green'), lineHeight: pt(LEAD), marginTop: pt(LEAD), marginBottom: pt(LEAD / 2) }, ], }, paragraphStyles: [ { id: 'colophon', fontFamily: LABEL, fontSize: pt(7), lineHeight: pt(9.5), color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD) }, ], header: { elements: [ head('verso-folio', '{pageNumber}', 'even', 'top-left', MARGIN.outer, 'left'), head('verso-author', '{attr.byline}', 'even', 'top-left', MARGIN.outer + 8, 'left'), head('recto-title', '{title}', 'odd', 'top-right', -(MARGIN.outer + 8), 'right'), head('recto-folio', '{pageNumber}', 'odd', 'top-right', -MARGIN.outer, 'right'), ] }, footer: { elements: [] }, }); // ─── 2 · Content ──────────────────────────────────────────────────────────── const markdown = String.raw`---Markdown-Beispiel · 106 Zeilen · content.en.md
title: "Railway Time" author: "Clara Whitcombe" --- # Railway Time: How British Towns Came to Keep London’s Hour {#essay kicker="Essays in the History of Technology · No. 14" years="1840–1880" byline="Clara Whitcombe"} In the autumn of 1840 a traveller who left Paddington on the Great Western Railway carried two times with him. The timetable in his pocket gave the hours of London; the church clock at Bath or Bristol, when he arrived, gave the hours of the place, a few minutes behind. Bristol stood a little over two and a half degrees west of Greenwich, and its noon, the moment the sun crossed the local meridian, came about ten minutes after London’s. For centuries the difference had not mattered. A stagecoach that took a day to cover the distance lost more than ten minutes to a lame horse. A train that covered it in four hours could not.[^frame] This essay follows the forty years in which that difference was settled, from the Great Western’s first timetables to the act of Parliament that made Greenwich time the legal time of Great Britain in 1880. The story has usually been told as a triumph of engineering over custom. It reads better as a negotiation, in which railway companies, telegraph companies, town councils and courts each settled the question for their own purposes, and not always in the same direction. [^frame]: For the railway’s effect on the sense of time and distance, see [@schivelbusch1986, chap. 3] ## The companies decide The first railways ran their trains by the clocks of their own stations. On a line of a few miles this caused no trouble, and the early timetables did not say which time they meant. The difficulty came with the long trunk lines of the late 1830s, which joined towns whose clocks disagreed by a quarter of an hour.[@simmons1991, 345] The Great Western printed London time in its timetables from November 1840 and asked its station clocks to keep it.[@howse1980, 87] In 1847 the Railway Clearing House, the body through which the companies settled their accounts for through traffic, recommended that every member adopt Greenwich time from the first of December. By the end of 1848 nearly all of them had.[@howse1980, 90–91] The decision was commercial before it was scientific. A company that kept one time along its line needed one timetable and one set of instructions for its staff, and it could publish connections with other lines without a table of corrections. Greenwich was chosen because London was where the companies had their offices and the Post Office its mail coaches, not because the observatory stood there.[^post] [^post]: The Post Office had sent its guards out with watches set to London time since the 1790s; see [@howse1980, 82–84, on the mail-coach timepieces] ## The wire carries the hour A time that every station is meant to keep has to reach every station. At first it travelled by hand: a guard on the morning down train carried a watch set at Paddington, and the stationmasters along the line set their clocks from it. The electric telegraph changed the means. From 1852 the Royal Observatory sent a signal each day along the wires of the Electric Telegraph Company, and the company passed it on to the railway stations and post offices on its network.[@standage1998, 59] A clock in Manchester or Edinburgh could now be checked against Greenwich within a second or two of the event. The signal made Greenwich time available everywhere the wires went; it did not oblige anyone to keep it.[@schivelbusch1986, 43] ## The towns hesitate Most towns on a main line gave way within a few years, since a clock that disagreed with the station made people miss trains. Some held out. In 1852 the clock on the Corn Exchange at Bristol was given a second minute hand, one for Bristol and one for London, and it still has both.[@simmons1991, 346] At Exeter, which the railway reached in 1844, the cathedral clock went on keeping the time of the city for some years after the station clock had changed.[^exeter] [^exeter]: On the British towns, and on the same quarrel in American towns thirty years later, see [@simmons1991, 346–47; and @bartky2000, chap. 9] The holdouts were not cranks. Local mean time followed the sun, and the sun still ordered the working day of farmers, fishermen and anyone whose hours depended on daylight. An hour fixed in London was an imposition on them. The historian of factory discipline has argued that the clock had already become a means of controlling labour by the eighteenth century;[@thompson1967, 80–82] the dispute over railway time extended that argument to whole towns, which were asked to accept an hour set elsewhere. ## The courts decline The law was slower than the towns. Statutes that named a time meant the time of the place, and that time was local mean time. In 1858 a trial at Dorchester began at ten by the court’s clock, which kept London time, and judgment was given against a defendant who arrived at ten by the time of the town, some minutes later. On appeal the judges held that the hour meant was the local one, and the judgment was set aside.[@howse1980, 113] For twenty more years a contract, a polling station or a public house could be bound by one time while the trains ran by another. The Statutes (Definition of Time) Act of 1880 closed the gap. It declared that any expression of time in an act of Parliament, deed or other legal instrument meant, unless stated otherwise, Greenwich mean time in Great Britain and Dublin mean time in Ireland.[@howse1980, 114] ## A settlement, not a conquest Seen from 1840, railway time was one of several possible arrangements. The companies might have kept local times and printed tables of corrections, as some American railroads did until 1883,[@bartky2000, 140–45] and the international system of zones that followed the Washington conference of 1884 took half a century to reach most of the world.[@ogle2015] In Britain the distances were short, the companies were few enough to agree through a single clearing house, and the telegraph reached the stations as the trains did. Those conditions, more than any principle, account for the speed with which a country of local noons came to keep one hour.[@ogle2015, 25; see also @schivelbusch1986, chap. 3] ## Bibliography :::bibliography{title=""} :::paragraphs{style="colophon"} A sample essay written for the Postext Cookbook (CC BY 4.0). The cited works are real; the page numbers in the notes are illustrative. Set in Crimson Pro and IBM Plex Sans Condensed (SIL OFL). ::: :::references{format=csl-yaml} - id: schivelbusch1986 type: book author: [{family: Schivelbusch, given: Wolfgang}] title: "The Railway Journey: The Industrialization of Time and Space in the 19th Century" publisher: University of California Press publisher-place: Berkeley issued: 1986 - id: thompson1967 type: article-journal author: [{family: Thompson, given: E. P.}] title: "Time, Work-Discipline, and Industrial Capitalism" container-title: Past & Present issue: 38 page: 56-97 issued: 1967 - id: howse1980 type: book author: [{family: Howse, given: Derek}] title: Greenwich Time and the Discovery of the Longitude publisher: Oxford University Press publisher-place: Oxford issued: 1980 - id: simmons1991 type: book author: [{family: Simmons, given: Jack}] title: The Victorian Railway publisher: Thames and Hudson publisher-place: London issued: 1991 - id: standage1998 type: book author: [{family: Standage, given: Tom}] title: The Victorian Internet publisher: Walker publisher-place: New York issued: 1998 - id: bartky2000 type: book author: [{family: Bartky, given: Ian R.}] title: "Selling the True Time: Nineteenth-Century Timekeeping in America" publisher: Stanford University Press publisher-place: Stanford, CA issued: 2000 - id: ogle2015 type: book author: [{family: Ogle, given: Vanessa}] title: The Global Transformation of Time, 1870–1950 publisher: Harvard University Press publisher-place: Cambridge, MA issued: 2015 :::`; // ─── 3 · Fonts ────────────────────────────────────────────────────────────── // Every face the design uses. const FONTS = { 'Crimson Pro': ['300', '400', '400i', '600', '600i'], 'IBM Plex Sans Condensed': ['400', '600'], }; // ─── 4 · Build & show ─────────────────────────────────────────────────────── const doc = await buildDocumentWithFonts({ markdown }, config(), kitFonts(FONTS)); const title = t({ en: 'A history essay with Chicago notes', es: 'Un ensayo de historia con notas de Chicago' }); showPages(doc, { title }); offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider }), `${RECIPE}.pdf`);Kit · core, fonts, viewer, pdf: in jedem Rezept gleich · 269 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 · pdf v3 ── the same in every recipe that exports a PDF /** The Fontsource files the screen used, as TrueType, with their range and * place: the nearest weight the family ships, upright with no italic; * latin, then what its letters need (kitSubsetsFor). */ async function fontsourceProvider(family, weight, style, request) { const id = fontsourceId(family); const meta = await fontsourceMeta(family); const weights = meta?.weights?.length ? meta.weights : [400, 700]; const w = weights.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a)); const s = style === 'italic' && meta && !meta.styles.includes('italic') ? 'normal' : style; const text = String.fromCodePoint(...(request?.codePoints ?? [])); const files = await Promise.all(['latin', ...kitSubsetsFor(text, meta)].map(async (subset, order) => { const res = await fetch(`${await fontsourceAt(id)}files/${id}-${subset}-${w}-${s}.woff2`); if (!res.ok) throw new Error(`Fontsource has no ${family} ${w} ${s} ${subset}`); return Object.assign(await decompressWoff2(new Uint8Array(await res.arrayBuffer())), { unicodeRange: kitRanges()[subset], order }); })); return files.length === 1 ? files[0] : files; } /** A "Build the PDF" button; then "Open the PDF" (a new tab: CodePen's frame * shows no PDFs) and a download link. */ function offerPdf(makePdf, filename) { viewer(); const button = Object.assign(document.createElement('button'), { type: 'button', textContent: 'Build the PDF' }); button.dataset.postextPdf = filename; button.addEventListener('click', async () => { button.disabled = true; button.textContent = 'Building the PDF…'; try { const bytes = await makePdf(); const url = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' })); const size = `${Math.max(1, Math.round(bytes.length / 1024))} KB`; button.replaceWith( Object.assign(document.createElement('a'), { href: url, target: '_blank', rel: 'noopener', textContent: 'Open the PDF ↗' }), Object.assign(document.createElement('a'), { href: url, download: filename, textContent: `Download ${filename} · ${size}` })); } catch (error) { button.disabled = false; button.textContent = 'Build the PDF'; kitFail(error); } }); document.getElementById('pt-actions').append(button); } // ─── /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 Noten ans Ende setzen
Manche Zeitschriften sammeln die Noten nach dem Essay; die Zitate behalten ihre Voll- und Kurzformen.
- placement: 'column', numbering: 'chapter',
+ placement: 'chapterEnd', numbering: 'chapter',#Wieder Ibid. schreiben
Die 17. Auflage des Chicago-Stils druckt Ibid. für eine Note, die das vorige Werk wiederholt. Fügen Sie die .csl-Datei dieses Stils aus dem Zotero-Stilverzeichnis in einen String ein und verwenden Sie ihn als eigenen Stil.
- style: 'chicago-notes-bibliography', // CMOS 18th edition, notes and bibliography
+ style: 'custom', customStyle: CHICAGO_17TH_CSL, // the XML of the .csl fileHäufige Fehler
- Beenden Sie eine Note nach einem Zitat nicht mit einem Punkt: Der Stil schließt das Zitat bereits mit einem ab, und Postext verschmilzt die beiden nicht, sodass die Note mit zwei Punkten endete.
- In einem Notenstil hinterlässt
@howse1980ohne Klammern nur die Notennummer im Satz, As ¹ says, ohne den Namen des Autors. Schreiben Sie den Namen selbst und zitieren Sie in Klammern:As Howse says,[@howse1980].
Nachweise
- Rezept
- Ignacio Ferro
- Text
- Eigener Text, CC BY 4.0
- Schriften
- Crimson Pro (SIL OFL 1.1) · IBM Plex Sans Condensed (SIL OFL 1.1)
- Code
- MIT, wie Postext
Diesen Text bearbeiten ↗ (öffnet in neuem Tab)Ordner des Rezepts auf GitHub ↗ (öffnet in neuem Tab)


