Model → PDF 2.0
Uint8Array— header, indirects, classical xref, trailer.
Module pdfWriter | Source packages/front/office/pdf/src/document/writer.js | Deps pdfErrors, pdfSerializer | Worker-safe yes
Emits a complete PDF document per ISO 32000-2 §7.5.2–7.5.5:
- Header
%PDF-x.y\nplus the binary marker comment. - For each indirect,
serializeIndirectand the recording of its offset. - Classical xref table —
xrefkeyword, one0 Nsubsection, 20-byte entries (0000000000 65535 ffor the free-list head,oooooooooo 00000 nfor live objects,0000000000 00000 ffor holes). - Trailer dictionary plus
startxrefand%%EOF.
writeDocument is model-agnostic: it happily emits a from-scratch indirect list
(see the second example). assembleIndirects(model) covers the read → write
round trip: it forces full resolution of a model read through pdf.read(),
then snapshots the _raw.indirects cache into an ordered list ready for
writeDocument. The result is byte-different but semantically equivalent.
Two sibling factories build on this one: pdfBuilder ({ builder }) offers a
chainable constructive DSL — addPage, addContent, addFont, addMetadata,
setVersion, setId, build() — and pdfIncrementalWriter
({ appendIncremental }) appends an incremental-update section (§7.5.6) to
existing bytes. pdfEncryptedWriter ({ writeEncryptedDocument }) wraps
writeDocument with the standard security handlers.
Resolve
const writer = runtime.resolve('pdfWriter');
// Returns: { writeDocument, assembleIndirects }
API
| Method | Signature | Returns |
|---|---|---|
writeDocument |
(opts: WriteOpts) => Uint8Array |
Complete document. |
assembleIndirects |
(model: ReadModel, opts?: { strict?: boolean }) => Array<{num, gen, value}> |
Ordered snapshot; carries skippedObjects (see below). |
WriteOpts
{
indirects: [ { num, gen, value }, … ], // required — num >= 1, no duplicates
root: { num, gen }, // required — trailer /Root
info?: { num, gen }, // optional — /Info
version?: '2.0' | '1.7' | … // default '2.0', matches /^\d\.\d$/
id?: [ Uint8Array, Uint8Array ] // 2 × 16 bytes for /ID
}
indirects is sorted by num before emission, so the caller need not supply it
in ascending order.
Unresolvable objects (skippedObjects, strict)
assembleIndirects forces resolution of every in-use xref entry. An entry whose
resolution throws cannot be written, so it never reaches the snapshot. The loss is
reported, not silent:
- Lenient (default) — the snapshot is returned as before (byte-identical
output for a fully resolvable model) and carries the skipped entries on its
non-enumerable
skippedObjectsproperty:Array<{ num, gen, code }>, empty when nothing was skipped.codeis the caught error'scodewhen it is a typed pdf error, else'unknown'. Being non-enumerable, it does not change iteration, spreading,JSON.stringifyor deep equality of the array. opts.strict === true— throws aRenderErrorcodedpdf/writer/unresolvable-objects(message names the count) whosecontext.objectsis the same list.
const indirects = writer.assembleIndirects(model);
if (indirects.skippedObjects.length > 0) {
console.warn('dropped', indirects.skippedObjects); // [{ num, gen, code }, …]
}
writer.assembleIndirects(model, { strict: true }); // throws instead of dropping
pdf.write(model) calls assembleIndirects(model) in lenient mode and does not
forward the list; call assembleIndirects directly to observe or forbid drops.
Examples
Read → write round trip
const pdf = runtime.resolve('pdf');
const writer = runtime.resolve('pdfWriter');
const model = pdf.read(srcBytes);
const indirects = writer.assembleIndirects(model);
const out = writer.writeDocument({
indirects,
root: { num: model.trailer.root.num, gen: model.trailer.root.gen },
info: model.trailer.info,
version: model.version
});
Minimal from-scratch document
const writer = runtime.resolve('pdfWriter');
const out = writer.writeDocument({
indirects: [
{ num: 1, gen: 0, value: { type: 'dict', entries: {
Type: { type: 'name', value: 'Catalog' },
Pages: { type: 'ref', num: 2, gen: 0 }
}}},
{ num: 2, gen: 0, value: { type: 'dict', entries: {
Type: { type: 'name', value: 'Pages' },
Count: { type: 'int', value: 0 },
Kids: { type: 'array', items: [] }
}}}
],
root: { num: 1, gen: 0 }
});
/ID for reproducibility
const id = new Uint8Array(16); /* … filled … */
writer.writeDocument({ indirects, root, id: [id, id] });
Errors
| Code | Class | When |
|---|---|---|
pdf/writer/bad-input |
RenderError |
opts.indirects is not an array. |
pdf/writer/no-root |
RenderError |
opts.root missing or num not finite. |
pdf/writer/bad-version |
RenderError |
version does not match /^\d\.\d$/. |
pdf/writer/bad-indirect |
RenderError |
An element without a finite num ≥ 1. |
pdf/writer/duplicate-num |
RenderError |
Two entries share the same num. |
pdf/writer/bad-model |
RenderError |
assembleIndirects given something other than a pdf.read() model. |
pdf/writer/unresolvable-objects |
RenderError |
assembleIndirects(model, { strict: true }) and at least one in-use object could not be resolved; context.objects is Array<{ num, gen, code }>. |
It also propagates every code from pdfSerializer.
See also
pdfSerializer— per-object emission.pdfDocument— inverse operation.pdfErrors