# Ein juristisches Gutachten mit Zitaten nach OSCOLA

> Eine vierseitige Urteilsanmerkung, deren Zitate [@robinson2018, para 21] zu OSCOLA-Fußnoten werden: Fälle kursiv, neutrale Zitate, ibid und Rückverweise (n 1).

- HTML-Fassung: https://postext.dev/de/cookbook/legal-opinion-oscola
- Rezept Nº 099 · Buchaufbau · Stufe 2 (Mittel) · Ausgaben: Canvas, PDF
- Gattungen: Fachartikel und Wissenschaft
- Benötigt postext ≥ 1.25.0, postext-pdf ≥ 1.25.0 · getestet mit 1.31.0, postext-pdf 1.31.0 am 2026-10-13
- Seiten: [1](https://postext.dev/cookbook/legal-opinion-oscola/en/p01.webp?v=8cf741b4), [2](https://postext.dev/cookbook/legal-opinion-oscola/en/p02.webp?v=8cf741b4), [3](https://postext.dev/cookbook/legal-opinion-oscola/en/p03.webp?v=8cf741b4), [4](https://postext.dev/cookbook/legal-opinion-oscola/en/p04.webp?v=8cf741b4)
- PDF: https://postext.dev/cookbook/legal-opinion-oscola/en/legal-opinion-oscola.pdf?v=8cf741b4
- In der Sandbox öffnen: https://postext.dev/de/sandbox#recipe=legal-opinion-oscola&lang=en (.postext: https://postext.dev/cookbook/legal-opinion-oscola/en/legal-opinion-oscola.postext)
- Zuletzt aktualisiert: 2026-10-11
- Andere Sprachen: [en](https://postext.dev/en/cookbook/legal-opinion-oscola.md), [es](https://postext.dev/es/cookbook/legal-opinion-oscola.md), [ca](https://postext.dev/ca/cookbook/legal-opinion-oscola.md), [fr](https://postext.dev/fr/cookbook/legal-opinion-oscola.md), [it](https://postext.dev/it/cookbook/legal-opinion-oscola.md), [pt](https://postext.dev/pt/cookbook/legal-opinion-oscola.md), [zh](https://postext.dev/zh/cookbook/legal-opinion-oscola.md), [ja](https://postext.dev/ja/cookbook/legal-opinion-oscola.md), [ar](https://postext.dev/ar/cookbook/legal-opinion-oscola.md)

## Kurz gesagt

Ein kurzer Kommentar zu einer englischen Gerichtsentscheidung über die Polizei. Jeder Fall, jedes Gesetz und jeder Aufsatz, auf die er sich stützt, wird in einer nummerierten Fußnote zitiert, im Stil britischer Law Schools.

## Was Sie setzen

Ein Sonderdruck aus der Reihe der Urteilsanmerkungen einer Rechtszeitschrift: vier Seiten zu *Robinson v Chief Constable of West Yorkshire Police*, der Entscheidung des Supreme Court von 2018 darüber, wann die Polizei eine Sorgfaltspflicht trifft. Die Seite misst 162 × 240 mm, gespiegelt, in Libre Baskerville mit 9,4/13,6 pt, und die erste Seite beginnt mit einem bordeauxroten Band, auf dem der Fallname in 60 pt kursiv steht. Der Autor schreibt jede Fundstelle als Schlüssel mit ihrem Pinpoint, `[@caparo1990, 617–18]`, und Postext setzt 23 Fußnoten nach OSCOLA, dem Zitierstandard britischer Law Schools: Fallnamen kursiv, Gesetze gerade, keine Punkte nach AC, HL oder edn, *ibid* für die Fundstelle der vorigen Fußnote und danach *Robinson (n 1) [55]*. Die Fundstellen sind echt; Autor und Zeitschrift sind es nicht.

**Dieses Rezept beantwortet:**

- Wie zitiere ich Fälle und Gesetze in Fußnoten nach OSCOLA?
- Wie setze ich Zitate als Fußnoten im Chicago-Stil, mit Kurzformen für spätere Zitate?

## Die kurze Antwort

```js
// script.js, Zeilen 50–68
// The text cites with [@robinson2018, para 21]; OSCOLA is a note style, so each citation
// becomes a footnote: the case in full the first time, "ibid" when the note before cites
// the same authority, "Robinson (n 1) [55]" after that. Case names come out italic,
// statutes roman, and no abbreviation takes a full stop (AC, HL, UKSC, edn, n, s).
registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES }));
const citations = {
  style: 'custom', customStyle: OSCOLA, // the bundled 'oscola', patched above
  locale: 'en-GB', // OSCOLA's words stay English in the Spanish edition too
  notes: 'footnote',
  link: false, // a case comment has no bibliography to link to
  bibliography: { auto: false }, // OSCOLA articles cite in the notes only
};
// The citation notes share the numbering and the look of the author's own [^notes].
const footnotes = {
  placement: 'column', numbering: 'chapter',
  fontSize: pt(7.8), lineHeight: pt(10.2), spaceBetween: pt(2.2),
  textAlign: 'left', // ragged: long report citations would open wide gaps if justified
  separator: { width: 0.25, lineWidth: pt(0.5), color: col('rule') },
};
```

## Zutaten

**Zeigt**

- [Zitate in Fußnoten](https://postext.dev/de/docs/document-format.md#zitate-und-literaturverzeichnis): Ein Fußnotenstil (Chicago notes, OSCOLA, GB/T 7714 notes) setzt jedes Zitat als Fußnote, mit Kurzformen für spätere Zitate, oder als zweizeilige Anmerkung in einer chinesischen Zeile.
- [Zitate in einem Zitierstil](https://postext.dev/de/docs/document-format.md#zitate-und-literaturverzeichnis): Werke, zitiert als [@key, p. 33] und in einem CSL-Stil formatiert (APA, Chicago, MLA, IEEE, Vancouver, ISO 690, GB/T 7714…), der in den Einstellungen gewählt wird, mit Links zu ihren Einträgen.

**Verwendet außerdem**

- [Fußnoten](https://postext.dev/de/docs/document-format.md#fußnoten)
- [Kapitelband über die ganze Breite](https://postext.dev/de/docs/configuration-text.md#seitenbreite-und-erweiterte-gestaltung)
- [Gestaltete Auftakte](https://postext.dev/de/docs/configuration-text.md#seitenbreite-und-erweiterte-gestaltung)
- [Überschriftenattribute](https://postext.dev/de/docs/document-format.md#attribute-von-überschriften)
- [Kolumnentitel und Seitenzahlen](https://postext.dev/de/docs/configuration-page-layout.md#kopf--und-fußzeilen)
- [Gespiegelte Ränder](https://postext.dev/de/docs/configuration-page-layout.md#gespiegelte-ränder)
- [Absatzformate](https://postext.dev/de/docs/configuration-styles.md#absatzformate)
- [Semantische Farbpalette](https://postext.dev/de/docs/configuration-fonts-colors-viewers.md#farbpalette)
- [PDF-Export](https://postext.dev/de/docs/configuration-programmatic-usage.md#pdfs-erzeugen)
- [Spaltenausgleich](https://postext.dev/de/docs/configuration-text.md#spaltenausgleich)
- [Querverweise](https://postext.dev/de/docs/document-format.md#querverweise-und-anker)
- [Literaturverzeichnis aus den Quellen](https://postext.dev/de/docs/document-format.md#zitate-und-literaturverzeichnis)
- [Papierfarbe](https://postext.dev/de/docs/configuration-page-layout.md#seite)
- [Kopfzeilen nach Seitenrolle](https://postext.dev/de/docs/configuration-page-layout.md#textelemente)
- [Im PDF eingebettete Schriften](https://postext.dev/de/docs/configuration-programmatic-usage.md#warum-ein-font-provider)

**Die Konfiguration auf einen Blick**

- [`bodyText`](https://postext.dev/de/docs/configuration-text.md#fließtext), [`citations`](https://postext.dev/de/docs/configuration-notes-references.md#zitationen), [`colorPalette`](https://postext.dev/de/docs/configuration-fonts-colors-viewers.md#farbpalette), [`footer`](https://postext.dev/de/docs/configuration-page-layout.md#kopf--und-fußzeilen), `footnotes`, [`header`](https://postext.dev/de/docs/configuration-page-layout.md#kopf--und-fußzeilen), [`headings`](https://postext.dev/de/docs/configuration-text.md#überschriften), [`layout`](https://postext.dev/de/docs/configuration-page-layout.md#layout), [`locale`](https://postext.dev/de/docs/configuration-text.md#silbentrennung), [`page`](https://postext.dev/de/docs/configuration-page-layout.md#seite), [`paragraphStyles`](https://postext.dev/de/docs/configuration-styles.md#absatzformate)

**APIs**

- [`LOCALES`](https://postext.dev/de/docs/document-format.md#zitate-und-literaturverzeichnis), [`STYLES`](https://postext.dev/de/docs/document-format.md#zitate-und-literaturverzeichnis), [`buildDocumentWithFonts`](https://postext.dev/de/docs/configuration-programmatic-usage.md#schriften-vor-dem-layout-laden), [`createCiteprocEngine`](https://postext.dev/de/docs/document-format.md#zitate-und-literaturverzeichnis), [`decompressWoff2`](https://postext.dev/de/docs/configuration-programmatic-usage.md#font-provider-im-browser-fontsource--woff2), [`registerCitationEngine`](https://postext.dev/de/docs/document-format.md#zitate-und-literaturverzeichnis), [`renderPageToCanvas`](https://postext.dev/de/docs/configuration-programmatic-usage.md#eine-seite-als-bitmap-rendern), [`renderToPdf`](https://postext.dev/de/docs/configuration-programmatic-usage.md#pdfs-erzeugen)

**Schriften**

- Libre Baskerville (OFL-1.1), Libre Caslon Display (OFL-1.1), Libre Franklin (OFL-1.1)

## Zubereitung

### 1 · Eine OSCOLA-Fußnote für jede Fundstelle

Der Code steht oben in [der kurzen Antwort](https://postext.dev/de/cookbook/legal-opinion-oscola.md#die-kurze-antwort). OSCOLA ist ein Fußnotenstil, `[@robinson2018, para 21]` hinterlässt also eine hochgestellte Ziffer nach dem Punkt, und seine Wörter gehen an den Fuß der Seite. Fußnote 1 nennt den Fall vollständig, *Robinson v Chief Constable of West Yorkshire Police [2018] UKSC 4, [2018] AC 736*; Fußnote 2, die ihn sofort erneut zitiert, lautet *ibid [21]*; Fußnote 8, nachdem andere Fälle dazwischengekommen sind, lautet *Robinson (n 1) [26–27]*. Ein Aufsatz in einer Rechtszeitschrift zitiert nur in seinen Fußnoten, `bibliography.auto: false` hält das Literaturverzeichnis daher von der letzten Seite fern.

### 2 · Ein Fall sind Daten, geschrieben, wie die Reports ihn drucken

Ein Fall ist ein CSL-Eintrag vom `type: legal_case`: sein Name in `title`, das Gericht in `authority`, die Report-Reihe in `container-title` und die erste Seite in `page`. OSCOLA setzt das Jahr eines Reports in eckige Klammern, wenn das Jahr den Band bezeichnet, wie bei den Appeal Cases; das Rezept schreibt dieses Jahr daher in `volume`, mit der Bandnummer danach: `volume: "[1990] 2"` ergibt *[1990] 2 AC 605 (HL)*. Ein nach 2001 entschiedener Fall hat außerdem ein neutrales Zitat: `authority: UKSC`, `number: "4"` und `issued: 2018` ergeben *[2018] UKSC 4*, gefolgt vom Report. Ein Gesetz ist `type: legislation` mit dem Jahr im Titel und ohne `issued`, damit das Jahr nicht zweimal gedruckt wird, und `[@hra1998, § 6]` zitiert eine Section als *s 6*.

### 3 · Zwei Korrekturen am mitgelieferten Stil

```js
// script.js, Zeilen 32–46
// The bundled oscola.csl writes a paragraph pinpoint in brackets only in a case's first
// note: a later note printed "Robinson (n 1) para 55" and "ibid 27", and a repeated
// statute "ibid 7". These two replacements give "Robinson (n 1) [55]", "ibid [27]" and
// "ibid s 7", as OSCOLA prints them.
const LOCATOR = `<macro name="label-locator"><choose>
  <if type="legal_case" locator="paragraph">
    <text variable="locator" prefix="[" suffix="]"/></if>
  <else-if locator="page"><text variable="locator"/></else-if>
  <else><group delimiter=" "><label form="short" strip-periods="true" variable="locator"/>
    <text variable="locator"/></group></else>
</choose></macro>`;
const OSCOLA = STYLES.oscola
  .replace(/<macro name="label-locator">[\s\S]*?<\/macro>/, LOCATOR)
  .replace(/(<if position="ibid-with-locator">[\s\S]*?)<text variable="locator"\/>/,
    '$1<text macro="label-locator"/>');
```

Das mitgelieferte `oscola.csl` setzt einen Pinpoint auf einen Absatz nur in der ersten Fußnote eines Falls in Klammern. Spätere Fußnoten druckten *para 55* und *ibid 27*, und eine zweite Fußnote zum selben Gesetz verlor ihr *s*. Die Korrektur ersetzt vor dem Build ein Makro und eine Zeile im XML des Stils und übergibt das Ergebnis als eigenen Stil, sodass sich Text und Referenzen nicht ändern.

### 4 · Der Fallname auf dem Band

```js
// script.js, Zeilen 72–96
const BAND = 124; // mm from the trim's top
const big = (id, content, family, size, color, y, extra = {}) => ({
  kind: 'text', id, content, fontFamily: family, fontSize: pt(size), lineHeight: 1.08, color,
  align: 'left', overflow: 'wrap', ...extra,
  placement: { ...at('container', 'top-left', 0, y), size: { width: mm(118), height: 'auto' } },
});
const opener = {
  enabled: true,
  minHeight: mm(BAND - MARGIN.top + 8), // the text starts 8 mm under the band
  slot: { elements: [
    { kind: 'box', id: 'band', style: { backgroundColor: col('claret') },
      placement: { ...at('page', 'top-left'), size: { width: 'fill', height: mm(BAND) } } },
    big('kicker', '{attr.kicker}', LABEL, 7.4, col('tint'), 2,
      { fontWeight: 600, letterSpacing: pt(1.4), textTransform: 'uppercase' }),
    big('case', '{attr.case}', TEXT, 60, col('paper'), 9, { italic: true }),
    big('parties', '{attr.parties}', TEXT, 12.5, col('tint'), 33, { italic: true }),
    big('citation', '{attr.citation}', TEXT, 10.5, col('tint'), 40),
    { kind: 'box', id: 'hairline', style: { backgroundColor: col('tint') },
      placement: { ...at('container', 'top-left', 0, 51),
        size: { width: mm(30), height: pt(0.6) } } },
    big('title', '{titleText}', DISPLAY, 27, col('paper'), 57),
    big('byline', '{attr.byline}', LABEL, 8.5, col('tint'), 82,
      { fontWeight: 500, letterSpacing: pt(0.5) }),
  ] },
};
```

Das Band reicht 124 mm von der Beschnittkante abwärts. Fallname, Parteien und Zitat sind Überschriftenattribute, sodass eine andere Anmerkung der Reihe nur ihr Markdown ändert. *Robinson* steht in Libre Baskerville Italic in 60 pt, weil OSCOLA Fallnamen kursiv setzt, und das Zitat darunter bleibt gerade, wie in den Fußnoten.

```js
// script.js, Zeilen 16–23
const palette = {
  ink: '#1f1b1a', claret: '#5e1b26', tint: '#ecdcd8', rule: '#bfb4ad', muted: '#6b625d',
  paper: '#fcfaf6',
};
// Each colour carries its hex and the palette entry it follows.
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = Object.entries({ ...palette, 'main-color': palette.claret })
  .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
```

## Das ganze Rezept

Eine Datei, aus dem Ordner des Rezepts zusammengesetzt, mit dem Beispieltext und dem gemeinsamen Baukasten der Rezepte bereits eingebettet; sie baut ihre eigene Seite auf. Zum Ausführen setzen Sie sie in ein `<script type="module">` auf einer leeren Seite oder fügen sie in das JS-Feld eines neuen CodePen-Pens ein (als Modul). Sie importiert postext von esm.sh, es gibt also nichts zu installieren oder zu bauen.

- Quellordner: https://github.com/drnachio/postext/tree/main/cookbook/legal-opinion-oscola

### script.js

```js
// ═══ Postext Cookbook · Nº 099 · A legal opinion cited in OSCOLA ══════════════════
// https://postext.dev/en/cookbook/legal-opinion-oscola
// Code: MIT · Text: original (CC BY 4.0) · Pictures: none
// Fonts: Libre Baskerville, Libre Caslon Display, Libre Franklin (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 = 'legal-opinion-oscola';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: a law-report claret for the band, ink and cream paper for the rest
const palette = {
  ink: '#1f1b1a', claret: '#5e1b26', tint: '#ecdcd8', rule: '#bfb4ad', muted: '#6b625d',
  paper: '#fcfaf6',
};
// Each colour carries its hex and the palette entry it follows.
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = Object.entries({ ...palette, 'main-color': palette.claret })
  .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
// #endregion
const TEXT = 'Libre Baskerville', DISPLAY = 'Libre Caslon Display', LABEL = 'Libre Franklin';
const TRIM = { width: 162, height: 240 }; // a law-journal offprint
const MARGIN = { top: 23, bottom: 22, inner: 19, outer: 21 };
const LEAD = 13.6; // pt
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });

// #region oscola: two patches to the bundled OSCOLA style, applied before the build
// The bundled oscola.csl writes a paragraph pinpoint in brackets only in a case's first
// note: a later note printed "Robinson (n 1) para 55" and "ibid 27", and a repeated
// statute "ibid 7". These two replacements give "Robinson (n 1) [55]", "ibid [27]" and
// "ibid s 7", as OSCOLA prints them.
const LOCATOR = `<macro name="label-locator"><choose>
  <if type="legal_case" locator="paragraph">
    <text variable="locator" prefix="[" suffix="]"/></if>
  <else-if locator="page"><text variable="locator"/></else-if>
  <else><group delimiter=" "><label form="short" strip-periods="true" variable="locator"/>
    <text variable="locator"/></group></else>
</choose></macro>`;
const OSCOLA = STYLES.oscola
  .replace(/<macro name="label-locator">[\s\S]*?<\/macro>/, LOCATOR)
  .replace(/(<if position="ibid-with-locator">[\s\S]*?)<text variable="locator"\/>/,
    '$1<text macro="label-locator"/>');
// #endregion

// #region answer: OSCOLA footnotes for cases, statutes and articles
// The text cites with [@robinson2018, para 21]; OSCOLA is a note style, so each citation
// becomes a footnote: the case in full the first time, "ibid" when the note before cites
// the same authority, "Robinson (n 1) [55]" after that. Case names come out italic,
// statutes roman, and no abbreviation takes a full stop (AC, HL, UKSC, edn, n, s).
registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES }));
const citations = {
  style: 'custom', customStyle: OSCOLA, // the bundled 'oscola', patched above
  locale: 'en-GB', // OSCOLA's words stay English in the Spanish edition too
  notes: 'footnote',
  link: false, // a case comment has no bibliography to link to
  bibliography: { auto: false }, // OSCOLA articles cite in the notes only
};
// The citation notes share the numbering and the look of the author's own [^notes].
const footnotes = {
  placement: 'column', numbering: 'chapter',
  fontSize: pt(7.8), lineHeight: pt(10.2), spaceBetween: pt(2.2),
  textAlign: 'left', // ragged: long report citations would open wide gaps if justified
  separator: { width: 0.25, lineWidth: pt(0.5), color: col('rule') },
};
// #endregion

// #region opener: the claret band holds the case name, its citation and the title
const BAND = 124; // mm from the trim's top
const big = (id, content, family, size, color, y, extra = {}) => ({
  kind: 'text', id, content, fontFamily: family, fontSize: pt(size), lineHeight: 1.08, color,
  align: 'left', overflow: 'wrap', ...extra,
  placement: { ...at('container', 'top-left', 0, y), size: { width: mm(118), height: 'auto' } },
});
const opener = {
  enabled: true,
  minHeight: mm(BAND - MARGIN.top + 8), // the text starts 8 mm under the band
  slot: { elements: [
    { kind: 'box', id: 'band', style: { backgroundColor: col('claret') },
      placement: { ...at('page', 'top-left'), size: { width: 'fill', height: mm(BAND) } } },
    big('kicker', '{attr.kicker}', LABEL, 7.4, col('tint'), 2,
      { fontWeight: 600, letterSpacing: pt(1.4), textTransform: 'uppercase' }),
    big('case', '{attr.case}', TEXT, 60, col('paper'), 9, { italic: true }),
    big('parties', '{attr.parties}', TEXT, 12.5, col('tint'), 33, { italic: true }),
    big('citation', '{attr.citation}', TEXT, 10.5, col('tint'), 40),
    { kind: 'box', id: 'hairline', style: { backgroundColor: col('tint') },
      placement: { ...at('container', 'top-left', 0, 51),
        size: { width: mm(30), height: pt(0.6) } } },
    big('title', '{titleText}', DISPLAY, 27, col('paper'), 57),
    big('byline', '{attr.byline}', LABEL, 8.5, col('tint'), 82,
      { fontWeight: 500, letterSpacing: pt(0.5) }),
  ] },
};
// #endregion

const head = (id, content, parity, edge, x, align) => ({
  kind: 'text', id, content, parity, pages: 'body', fontFamily: LABEL, fontWeight: 500,
  fontSize: pt(7.2), letterSpacing: pt(1), textTransform: 'uppercase', color: col('muted'),
  align, placement: at('page', edge, x, 13),
});
const journal = t({ en: 'Notes on Obligations', es: 'Cuadernos de Obligaciones' });

const config = () => ({
  locale: t({ en: 'en-us', es: 'es' }), // picks the hyphenation patterns
  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(9.4), lineHeight: pt(LEAD), color: col('ink'),
    boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
    textAlign: 'justify', firstLineIndent: mm(4.5), indentAfterHeading: false,
    hyphenation: { enabled: true }, optimalLineBreaking: true,
    avoidWidows: true, avoidOrphans: true, avoidRunts: true,
  },
  headings: {
    fontFamily: TEXT, fontWeight: 400, color: col('ink'),
    // A page that ends short gives each heading two lines, tries the other levers, and
    // then lets a heading take up to four. With one heading on the page and nothing else
    // to stretch, that is a hole; two lines are the most here, and the rest of a short
    // page stays over the notes.
    balancing: { maxLinesPerHeading: 2 },
    levels: [
      // A new page on either side: the default would wait for a recto.
      { level: 1, fontSize: pt(27), breakBefore: { enabled: true, parity: 'any' },
        advancedDesign: opener },
      { level: 2, italic: true, fontSize: pt(12), color: col('claret'),
        lineHeight: pt(LEAD), marginTop: pt(LEAD), marginBottom: pt(LEAD / 2) },
    ],
  },
  paragraphStyles: [
    { id: 'colophon', fontFamily: LABEL, fontSize: pt(6.8), lineHeight: pt(9.2),
      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-journal', journal, '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: [ // a drop folio on the opener page only
    { kind: 'text', id: 'drop-folio', content: '{pageNumber}', pages: 'opener',
      fontFamily: LABEL, fontWeight: 500, fontSize: pt(7.2), color: col('muted'),
      align: 'center', placement: at('page', 'bottom', 0, -11) },
  ] },
});

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = String.raw`---
title: "Acts, Omissions and the Police"
author: "Harriet Okafor"
---

# Acts, Omissions and the Police {#comment kicker="Notes on Obligations · Case Comment" case="Robinson" parties="v Chief Constable of West Yorkshire Police" citation="[2018] UKSC 4, [2018] AC 736" byline="Harriet Okafor"}

On a July afternoon in 2008 two detectives tried to arrest a man suspected of dealing drugs on a shopping street in the centre of Huddersfield. He resisted, and the three men fell against Mrs Robinson, a woman of 76 who was walking past, and knocked her to the ground. The trial judge found that the officers had been careless in attempting the arrest where and when they did, but held that the police owed no duty of care to a passer-by hurt in the course of their work against crime. The Court of Appeal agreed. The Supreme Court did not.[@robinson2018]

Lord Reed, with whom Lady Hale and Lord Hodge agreed, began by correcting a reading of the law that had gone uncorrected for almost thirty years.[@robinson2018, para 21] This comment argues that the case matters less for what it decided about the police than for the method it restored. A court asked whether a duty of care exists should look first for an established category and reason from it by analogy; it should weigh questions of policy only when the case is new.

## There is no “Caparo test”

The modern law begins with Lord Atkin’s statement that a person must take reasonable care to avoid acts or omissions which he can reasonably foresee would be likely to injure his neighbour.[@donoghue1932, 580] In the 1970s the House of Lords tried to turn that statement into a general formula. Lord Wilberforce proposed two stages: a relationship of proximity in which harm was foreseeable, and then any considerations that ought to negative or limit the duty.[@anns1978, 751–52] The formula expanded liability faster than the courts could defend, and *Anns* was overruled in 1990.[@murphy1991]

In the same year Lord Bridge described the ingredients of a duty as foreseeability of damage, a relationship of proximity, and a situation in which the court considers it fair, just and reasonable to impose a duty.[@caparo1990, 617–18] Many judges went on to apply it as a three-stage test to every claim, including claims for physical injury caused by a positive act. Lord Bridge had said the opposite. He doubted that any single test could settle every case and preferred that the law develop novel categories incrementally.[^brennan]

[^brennan]: Lord Bridge adopted the words of Brennan J in the High Court of Australia; see [@caparo1990, 618; @sutherland1985, 481]

*Robinson* restores that reading. Where a case falls within an established category, the court applies the law as settled; the question of what is fair, just and reasonable arises only where the court is asked to go further.[@robinson2018, para 26–27] Physical injury caused by a careless positive act has been an established category since *Donoghue*. The officers’ decision to make the arrest in a crowded street was such an act, the risk to people nearby was foreseeable, and nothing more was needed to found a duty.[@robinson2018, para 74]

## Acts and omissions

The common law does not, in general, impose a duty to protect another from harm caused by a third party or by nature.[@smith1987, 271] Lord Hoffmann gave the reason in political terms: it is less of an invasion of a person’s freedom to require him to take care when he chooses to act than to require him to act at all, and a duty to rescue invites the question why it should fall on one bystander rather than another.[@stovin1996, 943–44] The rule has exceptions: where the defendant has assumed responsibility, controls the source of the danger, or has created it.

Read in this light, the earlier police cases were cases about omissions. In *Hill* the mother of the last victim of Peter Sutcliffe sued the force that had failed to catch him. The House of Lords held that no duty was owed to her daughter, who was one of an indeterminate public, and added reasons of policy about the effect of liability on police work.[@hill1989, 63] For thirty years *Hill* was cited as if it gave the police a general immunity. Lord Reed treated the policy reasons as a supplement to the ordinary rule: the police, like anyone else, owe no duty to prevent harm by a third party, unless one of the exceptions applies; but they owe the ordinary duty not to injure people by what they themselves do.[@robinson2018, para 55]

The distinction had already decided *Michael*, where a call to the emergency services was wrongly graded and the caller was killed before officers arrived. The majority held that the police had assumed no responsibility for her safety and so owed her no duty in negligence.[@michael2015, para 97–100] Academic opinion had divided on the point. One view holds that a public body set up to protect people should bear a narrow duty where it knows of a specific threat to a specific person;[^peripheral] the other, that the omissions rule applies to public authorities as it applies to private persons, and that any wider duty is for Parliament.[@nolan2011]

[^peripheral]: [@tofaris2016, 130–32; cf @stapleton1995, 304–06, on the deterrent value of claims against parties who are not the primary wrongdoer]

## The statutory route

Section 6(1) of the Human Rights Act 1998 makes it unlawful for a public authority to act in a way that is incompatible with a Convention right,[@hra1998, § 6] and a victim may bring proceedings against it under section 7.[@hra1998, § 7] In *Michael* the claim under article 2 of the Convention was allowed to go to trial even though the claim in negligence failed.[@michael2015, para 139] In *DSD* the Supreme Court held the police liable under article 3 for serious failures in investigating a series of sexual assaults by a taxi driver.[@dsd2018, para 24]

The concern behind *Hill*, that liability would push the police into defensive practices, has a statutory answer of its own. When a court decides what steps a defendant should have taken to meet the standard of care, it may consider whether requiring those steps might prevent a desirable activity from being undertaken.[@compensation2006, § 1] That is a question of breach, not of duty. The officers in *Robinson* were not expected to stop arresting suspects, only to choose the moment with some regard for the people on the pavement.

## Comment

A year later the Supreme Court applied the same method to a local authority that had failed to rehouse a family harassed by their neighbours, and found no duty, because the council had neither created the danger nor assumed responsibility for the children’s safety.[@poole2019, para 64–65] The two decisions point in different directions on their facts and rest on the same reasoning.

Two difficulties remain. The line between acts and omissions is easy to state and hard to draw: an officer who opens a door and lets a violent man in has acted, but one who fails to lock it has not, and the difference may be small to the person on the other side. And the exceptions do much of the work. An assumption of responsibility can be found in a promise, a practice or the nature of a service, and the cases since *Robinson* show that the courts have not agreed how readily to find one.[^generally] The case does not resolve either difficulty; it puts them where they belong, in the analysis of established categories, and not in an open question about what is fair.

[^generally]: For the exceptions and the cases on each, see generally [@winfield2020]

:::paragraphs{style="colophon"}
Written for the Postext Cookbook (CC BY 4.0). Author and series are fictional; this is not legal advice. The authorities are real; some pinpoints are illustrative. Set in Libre Baskerville, Libre Caslon Display and Libre Franklin (SIL OFL).
:::

:::references{format=csl-yaml}
- id: donoghue1932
  type: legal_case
  title: Donoghue v Stevenson
  title-short: Donoghue
  volume: "[1932]"
  container-title: AC
  page: "562"
  authority: HL
- id: anns1978
  type: legal_case
  title: Anns v Merton London Borough Council
  title-short: Anns
  volume: "[1978]"
  container-title: AC
  page: "728"
  authority: HL
- id: murphy1991
  type: legal_case
  title: Murphy v Brentwood District Council
  title-short: Murphy
  volume: "[1991] 1"
  container-title: AC
  page: "398"
  authority: HL
- id: caparo1990
  type: legal_case
  title: Caparo Industries plc v Dickman
  title-short: Caparo
  volume: "[1990] 2"
  container-title: AC
  page: "605"
  authority: HL
- id: sutherland1985
  type: legal_case
  title: Sutherland Shire Council v Heyman
  title-short: Sutherland
  volume: "157"
  container-title: CLR
  page: "424"
  authority: HCA
  issued: 1985
- id: smith1987
  type: legal_case
  title: Smith v Littlewoods Organisation Ltd
  title-short: Smith
  volume: "[1987]"
  container-title: AC
  page: "241"
  authority: HL
- id: stovin1996
  type: legal_case
  title: Stovin v Wise
  title-short: Stovin
  volume: "[1996]"
  container-title: AC
  page: "923"
  authority: HL
- id: hill1989
  type: legal_case
  title: Hill v Chief Constable of West Yorkshire
  title-short: Hill
  volume: "[1989]"
  container-title: AC
  page: "53"
  authority: HL
- id: michael2015
  type: legal_case
  title: Michael v Chief Constable of South Wales Police
  title-short: Michael
  authority: UKSC
  number: "2"
  issued: 2015
  volume: "[2015]"
  container-title: AC
  page: "1732"
- id: robinson2018
  type: legal_case
  title: Robinson v Chief Constable of West Yorkshire Police
  title-short: Robinson
  authority: UKSC
  number: "4"
  issued: 2018
  volume: "[2018]"
  container-title: AC
  page: "736"
- id: dsd2018
  type: legal_case
  title: Commissioner of Police of the Metropolis v DSD
  title-short: DSD
  authority: UKSC
  number: "11"
  issued: 2018
  volume: "[2019]"
  container-title: AC
  page: "196"
- id: poole2019
  type: legal_case
  title: N v Poole Borough Council
  title-short: Poole
  authority: UKSC
  number: "25"
  issued: 2019
  volume: "[2020]"
  container-title: AC
  page: "780"
- id: hra1998
  type: legislation
  title: Human Rights Act 1998
- id: compensation2006
  type: legislation
  title: Compensation Act 2006
- id: stapleton1995
  type: article-journal
  author: [{family: Stapleton, given: Jane}]
  title: "Duty of Care: Peripheral Parties and Alternative Opportunities for Deterrence"
  container-title: LQR
  volume: "111"
  page: "301"
  issued: 1995
- id: nolan2011
  type: article-journal
  author: [{family: Nolan, given: Donal}]
  title: The Liability of Public Authorities for Failing to Confer Benefits
  container-title: LQR
  volume: "127"
  page: "260"
  issued: 2011
- id: tofaris2016
  type: article-journal
  author: [{family: Tofaris, given: Stelios}, {family: Steel, given: Sandy}]
  title: Negligence Liability for Omissions and the Police
  container-title: CLJ
  volume: "75"
  page: "128"
  issued: 2016
- id: winfield2020
  type: book
  author: [{family: Goudkamp, given: James}, {family: Nolan, given: Donal}]
  title: Winfield and Jolowicz on Tort
  edition: 20
  publisher: Sweet & Maxwell
  issued: 2020
:::
`;

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
// Every face the design uses.
const FONTS = {
  'Libre Baskerville': ['400', '400i', '700'],
  'Libre Caslon Display': ['400'],
  'Libre Franklin': ['400', '500', '600'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
const doc = await buildDocumentWithFonts({ markdown }, config(), kitFonts(FONTS));
const title = t({ en: 'A legal opinion cited in OSCOLA',
  es: 'Un dictamen jurídico citado en OSCOLA' });
showPages(doc, { title });
offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider }), `${RECIPE}.pdf`);

// ─── 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 ───────────────────────────────────────────────────────────────────────
```

## Varianten

### Den Stil drucken, wie er ausgeliefert wird

Zum Vergleich mit der unveränderten Ausgabe verwenden Sie die mitgelieferte ID. Pinpoints auf Absätze lauten in späteren Fußnoten dann *para 55*.

```diff
-  style: 'custom', customStyle: OSCOLA, // the bundled 'oscola', patched above
+  style: 'oscola',
```

### Die Fußnoten am Ende sammeln

Manche Zeitschriften drucken ihre Anmerkungen nach dem Text. Die Zitate behalten ihre Lang- und Kurzform und ihre Nummern.

```diff
-  placement: 'column', numbering: 'chapter',
+  placement: 'chapterEnd', numbering: 'chapter',
```

## Häufige Fehler

- Ein Report, dessen Band eine einfache Zahl ist, `volume: "2"`, druckt das Jahr in runden Klammern, *(1990) 2 AC*; OSCOLA behält das Reihen vor, die über Jahre hinweg nummeriert sind, etwa den Commonwealth Law Reports (Fußnote 7). Schreiben Sie das eingeklammerte Jahr für die Appeal Cases und die Weekly Law Reports in `volume`.
- `s` vor einem Lokator bedeutet eine Seite (wie im deutschen *Seite*), keine Section: Schreiben Sie `§ 6`. Eine Untergliederung wie `6(1)` wird nicht als Lokator gelesen; nennen Sie sie im Satz und zitieren Sie die Section.
- OSCOLA kennt in einem Aufsatz kein Literaturverzeichnis, und der Stil hat keine getrennten Verzeichnisse für Fälle und Gesetze. Eine Dissertation, die diese Verzeichnisse braucht, erhält von `:::bibliography` eine sortierte Liste, zuerst die Sekundärquellen, dann Fälle und Gesetze gemischt.

## Nachweise

- Rezept: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- Schrift: Libre Baskerville (OFL-1.1), Libre Caslon Display (OFL-1.1), Libre Franklin (OFL-1.1)
- Code: MIT · Beispielinhalt: CC-BY-4.0

## Verwandte Rezepte

- [Nº 089 · Ein historischer Essay mit Chicago-Noten](https://postext.dev/de/cookbook/history-essay-chicago-notes.md): Ein vierseitiger historischer Essay, dessen Zitate [@key, 87] zu Chicago-Noten am Seitenfuß werden: beim ersten Mal vollständig, danach kurz, mit Bibliografie. · Stufe 2 (Mittel) · Fachartikel und Wissenschaft
- [Nº 096 · Ein geisteswissenschaftlicher Essay mit MLA-Verzeichnis](https://postext.dev/de/cookbook/mla-humanities-essay.md): Ein literaturwissenschaftlicher Essay, dessen Zitate [@key, 48] zu Autor-Seite-Angaben nach MLA 9 werden, mit Blockzitat und Works Cited aus BibTeX-Daten. · Stufe 2 (Mittel) · Fachartikel und Wissenschaft
- [Nº 095 · Ein medizinischer Fachartikel im Vancouver-Stil](https://postext.dev/de/cookbook/medical-article-vancouver.md): Ein Studienbericht auf A4 in zwei Spalten: hochgestellte Zitatnummern, ein nummeriertes Literaturverzeichnis nach Vancouver und DOIs als Links im PDF. · Stufe 2 (Mittel) · Fachartikel und Wissenschaft
