# 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).