# Mixed-language OCR teaching experiment

Recorded 2026-09-08 for BookTranslator's `ocr-mixed-language-pdf` article.
All source text, identifiers and measurement-like values are self-authored
examples. They are not customer documents or real measurements.

## What is included

- `mixed-language-source.pdf`: three **image-only** pages; it is intentionally
  not searchable. It contains simplified Chinese/English prose, traditional
  Chinese/English prose, and a simple Chinese/English table.
- `*-source.png`: original generated artwork; `simplified.png`,
  `traditional.png` and `table.png`: actual Poppler-rendered PDF pages used for OCR.
- `ground-truth.json` and the three `*-ground-truth.txt` files: expected source
  text, protected fields and table row relationships.
- `ocr-results.json`: all ten actual recognition results, timestamps, model
  hashes, package/core versions and fixed parameters.
- Ten `.txt`/`.tsv` pairs: unchanged raw output. The two empty table TXT files
  are intentional recorded results, not missing assets.
- `comparison.csv`, `field-checks.csv`, `error-annotations.csv`: computed
  diagnostics and writer annotations.
- `review-sheet.csv`: blank reader audit sheet; no unchecked item is marked passed.
- `generate-samples.py`, `run-ocr.cjs`, `evaluate.py`: reproducible source.

No BookTranslator, NAPS2 or OCRmyPDF run is represented here. The pipeline
actually executed is **generated images → image-only PDF → rendered images →
Tesseract.js OCR → TXT/TSV diagnostics**. It does not generate or test an OCR
searchable-output PDF, a semantic table, or an accessible PDF.

## Fixed environment and inputs

- Node v25.2.1, macOS ARM64; Tesseract.js and tesseract.js-core 7.0.0.
- Pillow 12.3.0, reportlab 4.4.9, pypdf 6.10.0; Poppler `pdftoppm` 26.08.0.
- 1600×1000-pixel pages rendered at 300 dpi; 48-pixel font size. Changing a
  DPI label alone does not improve source image detail.
- OEM 1, PSM 6, `user_defined_dpi=300` for every recognition call.
- Official [tessdata_fast](https://github.com/tesseract-ocr/tessdata_fast/tree/87416418657359cb625c412a48b6e1d6d41c29bd)
  commit `87416418657359cb625c412a48b6e1d6d41c29bd`; files `eng.traineddata`,
  `chi_sim.traineddata`, `chi_tra.traineddata`. Download from that exact commit,
  not a mutable latest URL. Exact SHA-256 values are in `ocr-results.json`.
- Noto Sans CJK SC/TC Regular at commit
  `f8d157532fbfaeda587e826d4cd5b21a49186f7c`. Exact official file URLs and
  hashes are in `sample-manifest.json`. Fonts are not redistributed; see the
  [Noto CJK license](https://github.com/notofonts/noto-cjk/blob/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Sans/LICENSE).

The renderer uses ordinary horizontal font glyphs and punctuation, not drawn
punctuation replacements. No blur, skew, paper noise, handwriting, vertical
text, rare historical characters or complex tables were tested.

## Reproduce

Work in a copy of this directory to keep the published raw outputs unchanged.
Install the listed package versions in your own environment if needed. Put the
three uncompressed official model files in a `models` directory and the two
official font files in a `fonts` directory. The scripts do not download models
or send images to an external service.

With the required Python packages and `pdftoppm` available:

```sh
python3 generate-samples.py /absolute/path/to/fonts
```

This regenerates the PDF, proves that all three source pages have no extracted
text, then renders each page using Poppler. Compare the resulting hashes with
`sample-manifest.json` from the published bundle. ReportLab derives embedded
image resource names from absolute paths, so rebuilding in another directory
can change the PDF's bytes and hash even when its rendered pages are identical.
Compare the rendered PNG hashes to check OCR-input equivalence.
Rasterizer/font/library changes can change pixels; record them rather than
describing a changed environment as an exact replication.

With Tesseract.js/core 7.0.0 installed:

```sh
node run-ocr.cjs /absolute/path/to/models /absolute/path/to/node_modules /absolute/path/to/new-results
python3 evaluate.py /absolute/path/to/new-results
```

The third path points to a separate result directory. The runner performs ten
calls: four language recipes per prose page and two for the table. The table
uses the same PSM 6 as the prose deliberately to test a fixed uniform-block
recipe; it is not a tuned table-recognition comparison.

## Read the results correctly

The whole-text comparison applies Unicode NFC and removes whitespace only.
It does not change case, punctuation, decimals, Chinese script or character
order. `whole_text_edit_distance` is a count, not a claimed general accuracy
rate. Both prose ground truths have 124 non-whitespace code points, including
41 Han characters.

`han_sequence_edit_distance` compares only the sequence of CJK unified
ideographs. This isolates one diagnostic: it deliberately ignores other
characters and cannot prove whole-text correctness. `protected_fields_found`
checks whether the whitespace-normalized expected strings occur anywhere in
the output. It does not prove correct counts, location, unit association, or
table relationships. Inspect the original page for those.

All ten planned calls completed, with no retries, parameter tuning, changed
corpus or excluded successful outputs. One writer agent authored the corpus,
visually inspected all three rendered pages and annotated the outputs. There
was no blinded human review. A tiny clean synthetic corpus is not a production
benchmark, tool ranking, or promise about another PDF. In particular, the two
empty table outputs do not establish that other Tesseract settings or table
tools cannot handle that table.
