138 lines
6.8 KiB
Markdown
138 lines
6.8 KiB
Markdown
# Tasks — @suaveplan/pdf migration
|
|
|
|
## Chunk A — Foundation (THIS CHANGE)
|
|
|
|
Status: **SHIPPED** in this change.
|
|
|
|
- [x] Scaffold `packages/devtools/docs/pdf/` (package.json, vite, vitest,
|
|
tsconfig.lib.json, README, biome).
|
|
- [x] `src/types/` — page-size enum, dimensions table, margins, document /
|
|
page / element types, colour types, unit type. One `.md` per `.ts`.
|
|
- [x] `src/error-codes/` — `PDF_CODES` via `defineCodes`, `PdfError` class,
|
|
`asPdfError` helper.
|
|
- [x] `src/schemas/` — Zod schemas (`UnitSchema`, `HexColorSchema`,
|
|
`RgbColorSchema`, `MarginsSchema`, `PageConfigSchema`,
|
|
`DocumentMetadataSchema`) + `parseOrThrow` helper raising `PdfError`.
|
|
- [x] `src/telemetry/` — six OTel instruments + `createPdfObservability`
|
|
factory + recording helpers.
|
|
- [x] `src/units/` — `toPoints`, `fromPoints`, `convert`, `parseUnit`,
|
|
`formatUnit` raising typed errors.
|
|
- [x] `src/colors/` — `hexToRgb`, `rgbToPdfColor`, `hexToPdfColor`,
|
|
`rgbToHex`, `lighten`, `darken`, `COLORS`, `isTransparent`.
|
|
- [x] Pre-publish gate: `bunx turbo lint typecheck test build --filter=@suaveplan/pdf`
|
|
green, `bunx turbo test:coverage --filter=@suaveplan/pdf` 100 / 100 / 100 / 100.
|
|
- [x] Refresh `MANIFEST.md`.
|
|
|
|
## Chunk B — Low-level writer
|
|
|
|
Status: **SHIPPED** in this change.
|
|
|
|
- [x] `src/pdf-writer/` — hand-rolled PDF 1.4 writer with:
|
|
indirect-object table, font registry for the 14 standard fonts,
|
|
stream-object assembly (raw + FlateDecode via `node:zlib`),
|
|
xref + trailer, deterministic ordering, injectable `Clock` for
|
|
`/CreationDate`, AbortSignal-aware `generate`, `dispose()` guard.
|
|
- [x] `src/content-stream/` — encoder for `BT/ET`, `Td`/`T*`/`TL`/`Tm`,
|
|
`Tj`/`Tf`, `q/Q`, `rg/RG`, `m/l/h/S/s/f/B`, `re`, `cm`, `w`, and
|
|
image XObject `Do` placement with text-mode invariants.
|
|
- [x] Real PDF parse verification via `pdf-parse@^2.4` round-trip
|
|
(text content + page count + metadata + ProducerInfo).
|
|
|
|
## Chunk C — Elements
|
|
|
|
Status: **SHIPPED** in this change.
|
|
|
|
- [x] `src/text-element/` — `TextElement` class + `wrapText` +
|
|
`resolveStandardFont` + `measureLineWidth` with alignment,
|
|
max-lines overflow flag, kerning, `AbortSignal`.
|
|
- [x] `src/table-element/` — `TableElement<TRow>` with column-width
|
|
auto-distribution, header repeat across pages via
|
|
`renderPage(context, rowStart)`, alternating row backgrounds,
|
|
border + column dividers, `LAYOUT_OVERFLOW` typed error.
|
|
- [x] `src/image-element/` — `ImageElement` with PNG decode via
|
|
`pngjs` (RGB + optional SMask alpha plane), JPEG pass-through via
|
|
SOF-marker walk for width/height, `detectImageFormat`,
|
|
`scaleToFit`, `validateImageProps`, declared-format mismatch
|
|
detection.
|
|
- [x] `src/divider-element/` — `DividerElement` with horizontal /
|
|
vertical orientation, dash pattern, spacing above / below,
|
|
`computeDividerSpacing` flow helper.
|
|
- [x] Zod input schema per element type.
|
|
- [x] Real pdf-parse round-trip per text + table element.
|
|
- [x] Pre-publish gate: lint + typecheck + test + build green,
|
|
coverage 100 / 100 / 100 / 100.
|
|
|
|
## Chunk D — Layout, styles, document builder, renderer
|
|
|
|
Status: **SHIPPED** in this change.
|
|
|
|
- [x] `src/style-resolver/` — `StyleResolver` cascade
|
|
(`DEFAULT_STYLE` → theme → base → overrides), `mergeStyles`,
|
|
`mergeTheme` deep merge incl. `headingSizes`, `normalizeSpacing`
|
|
with `PDF.INVALID_ELEMENT` on non-finite input, `DEFAULT_THEME`
|
|
palette.
|
|
- [x] `src/layout-engine/` — `LayoutEngine` with cursor + content-area
|
|
+ header/footer slot rectangles, landscape orientation swap,
|
|
`advance(cursor, height)` returning `{ fit, remaining }`,
|
|
`nextPage`, `computeTextHeight`, `applyMargin`, AbortSignal
|
|
propagation, typed `PDF.LAYOUT_OVERFLOW` /
|
|
`PDF.INVALID_PAGE_CONFIG` errors.
|
|
- [x] `src/document-builder/` — `DocumentBuilder<TData>` fluent API
|
|
(`title` / `author` / `subject` / `keywords` / `creator` /
|
|
`creationDate` / `pageConfig` / `withData` / `addText` /
|
|
`addTable` / `addImage` / `addDivider` / `newPage` / `setHeader`
|
|
/ `setFooter` / `build`) + `createDocument<TData>()` factory.
|
|
- [x] `src/image-registry/` — `ImageRegistry` + `registerImageXObject`
|
|
that allocate per-element PDF XObjects (DCTDecode for JPEG,
|
|
FlateDecode for PNG RGB plane) and share them across logical
|
|
pages.
|
|
- [x] `src/render-pipeline/` — `placeNode` / `renderTableSlice` /
|
|
`renderSlot` / `pickFontResource` helpers operating on a
|
|
`PipelineState` bundle.
|
|
- [x] `src/renderer/` — top-level `renderDocument(doc, opts)`
|
|
returning `{ bytes, pageCount, durationMs }`. AbortSignal-aware,
|
|
emits six OTel instruments (documents-rendered counter,
|
|
pages-emitted counter, render-errors counter tagged by code,
|
|
validation-failures counter, render-duration histogram,
|
|
document-size histogram). Page-break + table-continuation loop;
|
|
raises `PDF.LAYOUT_OVERFLOW` for elements that cannot fit on a
|
|
fresh page.
|
|
- [x] Real pdf-parse round-trip tests covering text, multi-page flow,
|
|
oversize-table pagination with header repeat, header / footer
|
|
stamping across emitted pages, image XObject embedding.
|
|
- [x] Pre-publish gate: `bunx turbo lint typecheck test build
|
|
--filter=@suaveplan/pdf` green AND
|
|
`bunx turbo test:coverage --filter=@suaveplan/pdf`
|
|
100 / 100 / 100 / 100.
|
|
|
|
## Chunk E — Templates
|
|
|
|
Status: **SHIPPED** in this change.
|
|
|
|
- [x] `src/templates/invoice-template/` — `createInvoiceTemplate` +
|
|
`InvoiceDataSchema` + `computeInvoiceTotals` + `formatInvoiceAmount`.
|
|
- [x] `src/templates/receipt-template/` — `createReceiptTemplate` +
|
|
`ReceiptDataSchema` + `computeReceiptTotals` + `formatReceiptAmount`.
|
|
- [x] `src/templates/report-template/` — `createReportTemplate` +
|
|
`ReportDataSchema`.
|
|
- [x] Zod input schema per template with `PDF.INVALID_TEMPLATE_INPUT`
|
|
error path attaching the failing issue paths.
|
|
- [x] Real pdf-parse round-trip per template.
|
|
- [x] Pre-publish gate: `bunx turbo lint typecheck test build
|
|
--filter=@suaveplan/pdf` green AND
|
|
`bunx turbo test:coverage --filter=@suaveplan/pdf`
|
|
100 / 100 / 100 / 100.
|
|
|
|
React PDF preview component — deferred to a follow-up change
|
|
(`enterprise-react-pdf-preview` stub left under `openspec/changes/`).
|
|
|
|
## Cross-chunk invariants (preserved at every step)
|
|
|
|
- One `.md` per `.ts` ≥ 200 words covering Purpose, Features, Usage: Basic,
|
|
Usage: Advanced, API Reference, Implementation Notes.
|
|
- 100 / 100 / 100 / 100 Istanbul coverage on every touched module — no
|
|
`istanbul ignore` comments, no `// biome-ignore`.
|
|
- Every `it` / `test` carries `{ timeout: 5000 }` (30 000 for async).
|
|
- Tests import from `@suaveplan/testing/runner` per rule 11.
|
|
- No `export *` in non-root barrels (rule 10).
|