# REPORT-20260829-1300-pdf — mopac-pdf v0 (Redmine 499) Status: DONE. ukrrs/mopac-pdf created (Gitea API/tea), seeded AGPLv3, implemented, tested, smoked, pushed to `main` (https://git.knownelement.com/ukrrs/mopac-pdf). Clone at `~/projects/meta/MOPAC/pdf`. ## Engine decision: typst (digest-pinned), pandoc+LaTeX rejected Full rationale + measurements committed as `docs/ENGINE-DECISION.md` in the repo. Summary, measured 2026-08-29 on identical content (TOC + table + chart figure): | | typst 0.15.1 | pandoc 3.5 + xelatex | |---|---|---| | compile | 0.94 s | 7.66 s (~8x slower) | | image | ~200 MB | 758 MB | | templates | typed functions, `#show: report.with(...)` | LaTeX preamble/class surgery, multi-pass TOC | | output | modern typography out of the box | classic LaTeX look | typst also natively streams `compile doc.typ -` to stdout, which keeps our engine layer pure bytes-in/bytes-out (no root-owned files, no temp collisions). Supply chain per Charles' clarification: prebuilt Rust binary in a digest-pinned image is tooling; OUR code is Go only. Pin: `ghcr.io/typst/typst@sha256:032e292...9c4c422f` (= 0.15.1, cross-checked), run `--network none` with a private `/work` root. ## What shipped (v0) - CLI `mopac-pdf`: markdown + front-matter (title/subtitle/author/date/ classification/template) -> PDF to stdout or `-o`; `-t`/`-T` template pick/override (templates embedded in the binary AND loadable from a dir); `-pages` helper; exit codes 0 ok / 1 usage-input / 2 engine. - Templates: `report` (title page, TOC, numbered headings, running header, page X/Y + classification footer) and `brief` (dense 1-3 pager, compact title block, classification badge, small tables). - Markdown subset the whole stack actually emits: headings, paragraphs, inline bold/italic/code/links, ul/ol, blockquotes, hr, GFM tables with alignment, fenced code, block images. - Charts: fenced ```chart data blocks -> bar charts rendered PURE GO (vendored go-chart, MIT) -> PNG figures. One type proven end to end, per v0 scope. - Dev in docker only: `dev.sh`/`make` route through the family builder digest; deps vendored (hermetic); smoke drives the REAL typst container from the host (11 checks: both templates, stdin/stdout, page counts, exit codes, custom template dir — all green). - Tests: 7 packages ok — front-matter table tests, parser tests, golden .md -> .typ fixtures (drift-catching), chart PNG decode, tiny /Pages /Count parser tests, engine argv contract via stub docker, CLI flag/exit-code matrix. ## Sample outputs (local, regenerable via ./dev.sh smoke) - ~/projects/meta/MOPAC/pdf/out/sample-report.pdf — 4 pages (title, TOC, body + table + chart) - ~/projects/meta/MOPAC/pdf/out/sample-brief.pdf — 1 page (dense exec brief) - ~/projects/meta/MOPAC/pdf/out/stdin-report.pdf — stdin->stdout path - fixtures live in-repo: testdata/sample-{report,brief}.md (+ .typ.golden) ## How the briefing pipeline will call it The COS briefing/harness side already speaks markdown; the contract here is a single exec: `mopac-pdf -o .pdf ` (or pipe bytes: `mopac-pdf < brief.md > brief.pdf`). Front-matter carries the identity (title/date/classification/template=brief for the 0630 morning brief, report for long-form), so callers never touch typst. Failure semantics are harness-shaped: 0/1/2, engine stderr surfaced verbatim, no partial output files on failure. Engine image + docker binary overridable by env for canary/migration. No daemon, no state — a pure function, safe to call from any loop turn. ## Next (proposals, not started) - More chart types (line, donut) — renderer is a one-function extension point; needs a data-block convention for series. - More templates: letterhead, invoice, slide-deck-ish landscape. - Nested lists >1 level; footnotes; typst-side escape hardening fuzz. - Optional direct embedding of briefing charts without PNG round-trip (emit typst vector drawing for crisp print) once typst scripting surface stabilizes.