# PDF page-label teaching fixtures

Created and verified September 12, 2026. These six PDFs are three original, synthetic before/after pairs. They are not historical source scans, customer files, Acrobat screenshots, or BookTranslator output.

## Downloadable pairs

| Case | Before labels | After labels | Files |
| --- | --- | --- | --- |
| Seven-page notebook | 1 through 7 | Cover, i, ii, 1, 2, A-1, A-2 | [Before](files/seven-page-before.pdf), [after](files/seven-page-after.pdf) |
| Notebook with an unnumbered plate | 1 through 8 | Cover, i, ii, 1, Plate-A, 2, A-1, A-2 | [Before](files/inserted-plate-before.pdf), [after](files/inserted-plate-after.pdf) |
| Excerpt beginning at printed 37 | 1, 2, 3 | 37, 38, 39 | [Before](files/excerpt-37-before.pdf), [after](files/excerpt-37-after.pdf) |

`Cover` and `Plate-A` are assigned descriptive navigation labels for pages without a visible numeral. They do not assert that the source had those printed page numbers. The source text itself explicitly identifies each example as teaching material.

## Actual verification

The generator uses ReportLab to create the before PDFs, then pypdf to clone each document and set logical page labels. It does not stamp visible numbers, remove pages, or modify the page content streams.

All six files were reopened. Labels were read back on every page. Poppler rendered every page of both versions at 144 dpi: **18 corresponding page pairs, all pixel-identical**. All 18 content-stream comparisons also matched exactly. The after-pages were visually reviewed in full contact sheets; before-pages have the same rendered pixels. See [results.json](files/results.json) for per-page labels, dimensions, PDF hashes, pixel hashes and comparisons, and [page-map.csv](files/page-map.csv) for the three-layer mapping.

Versions: Python 3.12.14, ReportLab 4.4.9, pypdf 6.10.0, Pillow 12.3.0, Poppler `pdftoppm` 26.08.0. No fixture was excluded and no assertion failed in the first generation. This tests our constructed files, not arbitrary PDF repair, viewer input behavior, accessibility, signatures, links, OCR, or translation.

## Reproduce

Download `build-fixtures.py` and the `fonts` directory beside it. The script expects Python with ReportLab, pypdf and Pillow, plus `pdftoppm` on PATH. It does not install dependencies or access the network.

```sh
python3 build-fixtures.py --output-dir ./new-fixtures --render-dir ./new-renders
```

Both output directories must be new: the script refuses to overwrite existing paths. It accepts no input PDF and operates only on its authored fixture definitions. Six PDFs, the JSON report, CSV map and a labeled seven-page preview are written to the fixture directory. Rendered pages and all three contact sheets go to the render directory. Preview labels are annotations outside the page images, not a screenshot of a PDF reader.

The text uses the unmodified Noto Sans Regular font from [notofonts/noto-fonts commit ffebf8c1ee449e544955a7e813c54f9b73848eac](https://github.com/notofonts/noto-fonts/blob/ffebf8c1ee449e544955a7e813c54f9b73848eac/hinted/ttf/NotoSans/NotoSans-Regular.ttf). SHA-256: `b85c38ecea8a7cfb39c24e395a4007474fa5a4fc864f6ee33309eb4948d232d5`. Keep the included `fonts/OFL.txt` with the font.

The ordinary pypdf example in the article is intended only for our seven-page fixture. Use the official [pypdf 6.10.0 page-label API](https://pypdf.readthedocs.io/en/6.10.0/modules/PdfWriter.html#pypdf.PdfWriter.set_page_label) to understand index, style, prefix and starting value. Do not copy its ranges onto an unrelated book or a signed document.
