Files
mopac-pmo/archive/REPORT-20260829-1300-pdf.md

80 lines
3.9 KiB
Markdown

# 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 <path>.pdf <brief.md>` (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.