Index
# mmo.pdf — overview for AI code generation `mmo.pdf` (PHP package `mmo.pdf`, PHP namespace `MMO\pdf`) is Marc Mouret's PDF library. It is **not** a single coherent API — it is currently two separate, real subsystems plus one unfinished bridge between them: 1. **`PDFTK`** — a working PHP wrapper around the `pdftk` command-line tool. Reads an existing PDF's page/field metadata and fills its AcroForm fields with values, using MMOFramework's own `{path:type}` template-string syntax to bind form field names to arbitrary data. This is the one fully working, exercised feature. 2. **FPDF 1.86**, vendored at the package root as `fpdf186/fpdf.php` — a third-party library for generating a new PDF from scratch (pages, fonts, cells/text, images). It is used **directly**, by including `fpdf186/fpdf.php` and calling the plain `FPDF` class — it is not currently wrapped by any `MMO\pdf\*` class. 3. **`MMO\pdf\documents\PDFDocument` / `MMO\pdf\PDFPrinter` / `MMO\pdf\helpers\Page` / `MMO\pdf\helpers\Cell`** — scaffolding for a native MMOFramework `Document`/`Printer` pair that would render a `Page` of `Cell`s through FPDF (mirroring how `MMO\core\documents\xlsx\Document` wraps spreadsheet generation). **This does not work yet** — see "What's unfinished" below. Don't reach for these classes to actually produce a PDF today. Read this whole page before generating code against this package: which of the two real subsystems to use depends entirely on whether you're **filling an existing PDF** or **generating a brand new one**, and they share no code path. ## Decide: fill an existing PDF, or generate a new one? - **Filling an existing PDF** (an official form, a contract template, an existing layout you don't control) → `MMO\pdf\PDFTK`. It reads the PDF's own AcroForm field definitions and only needs values, not layout. - **Generating a new PDF from nothing** (an invoice, a report you're laying out yourself) → include `fpdf186/fpdf.php` and use the `FPDF` class directly (see the working example below). There is currently no MMOFramework-native way to do this — you use FPDF's own API as-is. ## The things that matter most 1. **`PDFTK` is a singleton module — never `new` it.** It extends `MMO\core\classes\aModule` (which uses `tSingleton`), so access it via `PDFTK::Shared()`, exactly like any other MMOFramework module. 2. **`PDFTK::fill()`'s template strings are MMOFramework template syntax, not raw values.** Its signature is `fill(string $filename, array|\ArrayAccess $data, array $templates, ?callable $coercer = null, array $options = [], bool $flatten = false): string`. `$templates` maps a PDF field name (as `pdftk` reports it) to a template string in the `{path:type}` syntax documented on `\MMO\core\strings\loopTemplate()` (`?` prefix for optional, `:type` suffix passed to `$coercer`). Paths are resolved against `$data` with `\MMO\core\arrays\get()`. A field with no matching template falls back to the PDF's own default value, then to `''`/an exception depending on whether it's required (`$this->throwOnRequired`, `throwOnMaxLength`, `throwOnMinLength`, `throwOnEnum` — all `true` by default except `throwOnMaxLength`; call `PDFTK::Shared()->force()` to relax all four at once). 3. **Call `getInfo()` first to know what fields actually exist.** `PDFTK::getInfo(string $filename): array` shells out to `pdftk ... dump_data_utf8` + `dump_data_fields_utf8` and returns `['pages' => [...], 'fields' => [name => ['type' => 'Text'|'Button'|'Choice'|..., 'required' => bool, 'maxLength' => int, 'default' => ..., 'enum' => [...]]]]` plus top-level PDF metadata (author, page count, etc.). Write your `$templates` array against the real field names this returns — they're whatever the PDF's original author named them, not something `mmo.pdf` controls. 4. **`fill()` returns the filled PDF as a raw string, not a filename.** It shells out to `pdftk fill_form - output -` and returns `$exec->getOutput()` directly — write it to disk yourself (`file_put_contents()`) or stream it in a response (see below). 5. **FPDF is used raw — nothing in `MMO\pdf` wraps it (yet).** `fpdf186/fpdf.php` defines the plain `FPDF` class from the well-known [FPDF library](http://www.fpdf.org) (v1.86); `MMO\pdf\documents\PDFDocument` and `MMO\pdf\PDFPrinter` are meant to eventually front it as a framework `Document`/`Printer` pair, but as of this writing `PDFPrinter` contains no FPDF calls at all and neither class is `include()`d by `src/bootstrap.php`. Include `fpdf186/fpdf.php` yourself and call `FPDF`'s own methods directly (`AddPage()`, `SetFont()`, `Cell()`, `Output()`, ...) — see FPDF's own tutorial under `fpdf186/tutorial/` for its full API (`MultiCell()`, `Image()`, `Line()`, `Rect()`, etc.), which this package does not re-document. ## Loading the package ```php require __DIR__ . '/../core/src/bootstrap.php'; // mmo.core must be bootstrapped first \MMO\inc('mmo.pdf'); // loads mmo.pdf's own bootstrap.php (exceptions, TTFFile, PDFTK) ``` ## Working example — filling an existing PDF form ```php use MMO\pdf\PDFTK; $pdf = PDFTK::Shared(); // 1. Inspect the template to see its real field names/types/constraints. $info = $pdf->getInfo(__DIR__ . '/templates/contract.pdf'); // $info['fields'] => ['CustomerName' => ['type' => 'Text', 'required' => true, 'maxLength' => 60], ...] // 2. Map each field name to a template string over your own data shape. $data = ['customer' => ['name' => 'Marc Mouret'], 'total' => 25792]; $templates = [ 'CustomerName' => '{customer/name}', 'Total' => '{total} EUR', ]; // 3. Fill it -- returns the filled PDF's bytes. $bytes = $pdf->fill(__DIR__ . '/templates/contract.pdf', $data, $templates); file_put_contents(__DIR__ . '/out/contract-filled.pdf', $bytes); ``` ## Working example — generating a new PDF with FPDF Taken from this package's own `test.php`: ```php include(__DIR__ . '/fpdf186/fpdf.php'); $pdf = new FPDF(); $pdf->AddPage(); $pdf->SetFont('Arial', 'B', 16); $pdf->Cell(0, 10, 'Invoice', 0, 1, 'C'); // ... more $pdf->Cell()/$pdf->MultiCell()/$pdf->Ln() calls to lay out the rest of the page ... $pdf->Output(__DIR__ . '/invoice.pdf', 'F'); // 'F' saves to disk; 'S' returns the raw PDF bytes as a string ``` **Uncertain / worth checking before relying on this:** this package's `.gitignore` excludes `fpdf186/*` except `fpdf186/fpdf.php` itself, which is the only `fpdf186/` file actually tracked in git. FPDF's own core fonts (`fpdf186/font/*.php` — Helvetica, Times, Courier, Symbol, ZapfDingbats) are **not** tracked, so a fresh clone of this package may be missing them; confirm they're present (or vendor FPDF's `font/` folder some other way) before calling `SetFont()` with a core font in a new checkout. ## Serving a generated/filled PDF over HTTP `MMO\core\server\Response::FromMime()` does **not** special-case PDF — `application/pdf` resolves to the `'pdf'` extension, which isn't one of `FromMime()`'s matched cases, so it silently falls through to `Response::Text()`, which is wrong for binary PDF content. Don't use it here. The accurate, working pattern is `MMO\core\server\StreamResponse::File()`, which streams a file already on disk and infers `Content-Type: application/pdf` from the `.pdf` extension automatically: ```php use MMO\core\server\StreamResponse; // PDFTK output is bytes, not a file -- write it out first. $bytes = PDFTK::Shared()->fill($templatePath, $data, $templates); $tmp = \MMO\core\files\temporaryFilename('pdf') . '.pdf'; file_put_contents($tmp, $bytes); return StreamResponse::File($tmp, [], 'contract.pdf'); // sets Content-Disposition: attachment; filename="contract.pdf" // FPDF output written with Output(..., 'F') is already a file -- stream it directly. $pdf->Output($tmp, 'F'); return StreamResponse::File($tmp, [], 'invoice.pdf'); ``` `StreamResponse::File()` defaults to `Content-Disposition: attachment` (forces a download); override the `Content-Disposition` header yourself if you want the browser to render the PDF inline instead. ## What's unfinished (don't build on these without checking first) - **`MMO\pdf\documents\PDFDocument`** extends `MMO\core\documents\Document` and sets itself up with `MMO\pdf\PDFPrinter` as its printer, plus PDF metadata setters (`setTitle()`, `setAuthor()`, `setSubject()`, `setKeywords()`, `setCreator()`). But **`PDFPrinter` (extends `MMO\core\classes\aExporter`) has no FPDF rendering logic at all** — it only defines value-export helpers (`DateTimeInterface` → JSON string, `Exception` → `['type', 'message']`) and a no-op `hash()`. Nothing turns a `PDFDocument`'s body into PDF bytes yet. - **`MMO\pdf\helpers\Page`** (orientation/unit/page size/margins/zoom/layout, an `iArray` of content) and **`MMO\pdf\helpers\Cell`** (`x`, `y`, `text`, `border`) are plain data holders with no `render()`/output method — nothing consumes them either. - **None of the four classes above are `include()`d by `src/bootstrap.php`** (only `exceptions/*.php`, `helpers/TTFFile.php` and `PDFTK.php` are). They exist in `src/` and are tracked in git, but the framework's autoload fallback cannot reach them either: `src/bootstrap.php` registers no `paths` entry for `MMO\pdf`, so `class_exists()` on any of them is `false` until you `include` its file yourself. - **`MMO\pdf\helpers\TTFFile`** is a working, from-scratch TrueType font file parser (reads `head`/`hhea`/ `maxp`/`hmtx`/`loca`/`glyf`/`cmap`/`name`/`OS/2`/`post` tables, and can `extractSubset()` a glyph subset for a given character list or encoding via `src/fonts/*.map`). It parses correctly on its own, but nothing else in `src/` currently calls it — it reads as groundwork for future custom-font embedding, not a finished feature. - **Stamping and merging PDFs** (mentioned as a goal in old comments in `PDFTK.php`, using `Imagick` to draw checkmarks/images onto form pages and `pdftk multistamp`) is entirely commented out — there is no working `stamp()`/`merge()` method today. The README's Prerequisites section still asks to install `php-imagick`, which is leftover from this: no currently-active code in `src/` calls `Imagick` anywhere. ## Prerequisites - The `pdftk` binary must be installed and on `PATH` (`PDFTK::$bin`, default `'pdftk'`) for anything that uses `PDFTK`. - `mb_convert_encoding` (the `mbstring` extension) is required — `src/bootstrap.php` throws immediately if it's missing. - FPDF generation additionally needs `fpdf186/fpdf.php` (tracked) and, in practice, FPDF's core font files (see the caveat above).