@laminapdf/sdk 0.0.0-stage → 0.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -2
- package/dist/api.d.ts +26 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +12 -0
- package/dist/api.js.map +1 -0
- package/dist/builder.d.ts +335 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +574 -0
- package/dist/builder.js.map +1 -0
- package/dist/defined.d.ts +5 -0
- package/dist/defined.d.ts.map +1 -0
- package/dist/defined.js +7 -0
- package/dist/defined.js.map +1 -0
- package/dist/document.d.ts +267 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +472 -0
- package/dist/document.js.map +1 -0
- package/dist/entry/node.d.ts +30 -0
- package/dist/entry/node.d.ts.map +1 -0
- package/dist/entry/node.js +37 -0
- package/dist/entry/node.js.map +1 -0
- package/dist/entry/workerd.d.ts +30 -0
- package/dist/entry/workerd.d.ts.map +1 -0
- package/dist/entry/workerd.js +37 -0
- package/dist/entry/workerd.js.map +1 -0
- package/dist/errors.d.ts +102 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +131 -0
- package/dist/errors.js.map +1 -0
- package/dist/explain.d.ts +20 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +14 -0
- package/dist/explain.js.map +1 -0
- package/dist/extract.d.ts +57 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +5 -0
- package/dist/extract.js.map +1 -0
- package/dist/font.d.ts +73 -0
- package/dist/font.d.ts.map +1 -0
- package/dist/font.js +133 -0
- package/dist/font.js.map +1 -0
- package/dist/generated/builder-enums.d.ts +29 -0
- package/dist/generated/builder-enums.d.ts.map +1 -0
- package/dist/generated/builder-enums.js +80 -0
- package/dist/generated/builder-enums.js.map +1 -0
- package/dist/generated/codes.d.ts +109 -0
- package/dist/generated/codes.d.ts.map +1 -0
- package/dist/generated/codes.js +508 -0
- package/dist/generated/codes.js.map +1 -0
- package/dist/generated/explain.d.ts +6 -0
- package/dist/generated/explain.d.ts.map +1 -0
- package/dist/generated/explain.js +155 -0
- package/dist/generated/explain.js.map +1 -0
- package/dist/generated/extract.d.ts +235 -0
- package/dist/generated/extract.d.ts.map +1 -0
- package/dist/generated/extract.js +4 -0
- package/dist/generated/extract.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +88 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +139 -0
- package/dist/init.js.map +1 -0
- package/dist/internal/handle.d.ts +58 -0
- package/dist/internal/handle.d.ts.map +1 -0
- package/dist/internal/handle.js +113 -0
- package/dist/internal/handle.js.map +1 -0
- package/dist/internal/instance.d.ts +56 -0
- package/dist/internal/instance.d.ts.map +1 -0
- package/dist/internal/instance.js +125 -0
- package/dist/internal/instance.js.map +1 -0
- package/dist/loader/fetch.d.ts +3 -0
- package/dist/loader/fetch.d.ts.map +1 -0
- package/dist/loader/fetch.js +2 -0
- package/dist/loader/fetch.js.map +1 -0
- package/dist/loader/node.d.ts +3 -0
- package/dist/loader/node.d.ts.map +1 -0
- package/dist/loader/node.js +9 -0
- package/dist/loader/node.js.map +1 -0
- package/dist/loader/workerd.d.ts +3 -0
- package/dist/loader/workerd.d.ts.map +1 -0
- package/dist/loader/workerd.js +15 -0
- package/dist/loader/workerd.js.map +1 -0
- package/dist/matrix.d.ts +16 -0
- package/dist/matrix.d.ts.map +1 -0
- package/dist/matrix.js +36 -0
- package/dist/matrix.js.map +1 -0
- package/dist/path.d.ts +34 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +49 -0
- package/dist/path.js.map +1 -0
- package/dist/source/http.d.ts +36 -0
- package/dist/source/http.d.ts.map +1 -0
- package/dist/source/http.js +211 -0
- package/dist/source/http.js.map +1 -0
- package/dist/source/range.d.ts +75 -0
- package/dist/source/range.d.ts.map +1 -0
- package/dist/source/range.js +100 -0
- package/dist/source/range.js.map +1 -0
- package/dist/text.d.ts +90 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +127 -0
- package/dist/text.js.map +1 -0
- package/dist/transaction.d.ts +36 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +47 -0
- package/dist/transaction.js.map +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/dist/worker/client.d.ts +88 -0
- package/dist/worker/client.d.ts.map +1 -0
- package/dist/worker/client.js +407 -0
- package/dist/worker/client.js.map +1 -0
- package/dist/worker/emitter.d.ts +12 -0
- package/dist/worker/emitter.d.ts.map +1 -0
- package/dist/worker/emitter.js +34 -0
- package/dist/worker/emitter.js.map +1 -0
- package/dist/worker/entry.d.ts +2 -0
- package/dist/worker/entry.d.ts.map +1 -0
- package/dist/worker/entry.js +4 -0
- package/dist/worker/entry.js.map +1 -0
- package/dist/worker/index.d.ts +7 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +4 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/protocol.d.ts +82 -0
- package/dist/worker/protocol.d.ts.map +1 -0
- package/dist/worker/protocol.js +11 -0
- package/dist/worker/protocol.js.map +1 -0
- package/dist/worker/server.d.ts +51 -0
- package/dist/worker/server.d.ts.map +1 -0
- package/dist/worker/server.js +493 -0
- package/dist/worker/server.js.map +1 -0
- package/dist/worker/support.d.ts +43 -0
- package/dist/worker/support.d.ts.map +1 -0
- package/dist/worker/support.js +26 -0
- package/dist/worker/support.js.map +1 -0
- package/dist/worker/transfer.d.ts +3 -0
- package/dist/worker/transfer.d.ts.map +1 -0
- package/dist/worker/transfer.js +13 -0
- package/dist/worker/transfer.js.map +1 -0
- package/dist/worker/types.d.ts +50 -0
- package/dist/worker/types.d.ts.map +1 -0
- package/dist/worker/types.js +2 -0
- package/dist/worker/types.js.map +1 -0
- package/docs/AGENTS.md +14 -0
- package/docs/api/README.md +147 -0
- package/docs/api/classes/Builder.md +467 -0
- package/docs/api/classes/Document.md +742 -0
- package/docs/api/classes/Font.md +74 -0
- package/docs/api/classes/Image.md +140 -0
- package/docs/api/classes/LaminaPdfError.md +189 -0
- package/docs/api/classes/Page.md +178 -0
- package/docs/api/classes/PageRef.md +253 -0
- package/docs/api/classes/PageText.md +129 -0
- package/docs/api/classes/PageTextRun.md +167 -0
- package/docs/api/classes/PendingPath.md +97 -0
- package/docs/api/classes/SaveJob.md +205 -0
- package/docs/api/classes/Surface.md +326 -0
- package/docs/api/classes/Transaction.md +111 -0
- package/docs/api/functions/callbackSource.md +33 -0
- package/docs/api/functions/httpRangeSource.md +48 -0
- package/docs/api/functions/init.md +36 -0
- package/docs/api/functions/isExtractLoaded.md +21 -0
- package/docs/api/functions/isInitialized.md +19 -0
- package/docs/api/functions/isLaminaPdfError.md +41 -0
- package/docs/api/functions/isTextLoaded.md +19 -0
- package/docs/api/functions/loadExtract.md +28 -0
- package/docs/api/functions/loadText.md +28 -0
- package/docs/api/functions/onLeak.md +29 -0
- package/docs/api/functions/reset.md +22 -0
- package/docs/api/functions/wasmMemory.md +22 -0
- package/docs/api/interfaces/BuilderOptions.md +108 -0
- package/docs/api/interfaces/ByteRange.md +29 -0
- package/docs/api/interfaces/ChunkOptions.md +37 -0
- package/docs/api/interfaces/Diagnostic.md +67 -0
- package/docs/api/interfaces/ExtractOptions.md +92 -0
- package/docs/api/interfaces/ExtractResult.md +37 -0
- package/docs/api/interfaces/FontInfo.md +37 -0
- package/docs/api/interfaces/HttpRangeOptions.md +78 -0
- package/docs/api/interfaces/InitOptions.md +40 -0
- package/docs/api/interfaces/Leak.md +21 -0
- package/docs/api/interfaces/Location.md +53 -0
- package/docs/api/interfaces/OpLog.md +25 -0
- package/docs/api/interfaces/OpenOptions.md +37 -0
- package/docs/api/interfaces/PageTextOptions.md +21 -0
- package/docs/api/interfaces/PathBuilder.md +195 -0
- package/docs/api/interfaces/QuadPoint.md +25 -0
- package/docs/api/interfaces/RangeOptions.md +54 -0
- package/docs/api/interfaces/RangeSource.md +59 -0
- package/docs/api/interfaces/StrokeStyle.md +61 -0
- package/docs/api/interfaces/TagSpec.md +69 -0
- package/docs/api/interfaces/TextBoxOptions.md +93 -0
- package/docs/api/interfaces/TextGlyph.md +29 -0
- package/docs/api/interfaces/TextOptions.md +101 -0
- package/docs/api/interfaces/WasmMemory.md +37 -0
- package/docs/api/type-aliases/AfRelationship.md +15 -0
- package/docs/api/type-aliases/Align.md +15 -0
- package/docs/api/type-aliases/ArtifactKind.md +15 -0
- package/docs/api/type-aliases/AttachmentOptions.md +15 -0
- package/docs/api/type-aliases/BlendMode.md +15 -0
- package/docs/api/type-aliases/BoxRect.md +15 -0
- package/docs/api/type-aliases/BuildOptions.md +15 -0
- package/docs/api/type-aliases/Cell.md +71 -0
- package/docs/api/type-aliases/Chunk.md +79 -0
- package/docs/api/type-aliases/Color.md +16 -0
- package/docs/api/type-aliases/Coordinates.md +55 -0
- package/docs/api/type-aliases/Direction.md +15 -0
- package/docs/api/type-aliases/DocumentInfo.md +15 -0
- package/docs/api/type-aliases/DropReason.md +15 -0
- package/docs/api/type-aliases/Dropped.md +59 -0
- package/docs/api/type-aliases/Edited.md +15 -0
- package/docs/api/type-aliases/ErrorCode.md +15 -0
- package/docs/api/type-aliases/ErrorKind.md +15 -0
- package/docs/api/type-aliases/ExtractFormat.md +15 -0
- package/docs/api/type-aliases/ExtractedBlock.md +82 -0
- package/docs/api/type-aliases/ExtractedPage.md +65 -0
- package/docs/api/type-aliases/ExtractedSource.md +48 -0
- package/docs/api/type-aliases/Extraction.md +22 -0
- package/docs/api/type-aliases/FillRule.md +15 -0
- package/docs/api/type-aliases/Fit.md +15 -0
- package/docs/api/type-aliases/Flags.md +15 -0
- package/docs/api/type-aliases/Generator.md +41 -0
- package/docs/api/type-aliases/GlyphQuad.md +15 -0
- package/docs/api/type-aliases/InfoPatch.md +15 -0
- package/docs/api/type-aliases/LaminaPdfWarning.md +16 -0
- package/docs/api/type-aliases/LimitName.md +15 -0
- package/docs/api/type-aliases/Limits.md +15 -0
- package/docs/api/type-aliases/LineBox.md +15 -0
- package/docs/api/type-aliases/LineCap.md +15 -0
- package/docs/api/type-aliases/LineJoin.md +15 -0
- package/docs/api/type-aliases/LinkTarget.md +15 -0
- package/docs/api/type-aliases/Metadata.md +15 -0
- package/docs/api/type-aliases/Op.md +15 -0
- package/docs/api/type-aliases/OutlineEntry.md +15 -0
- package/docs/api/type-aliases/Overflow.md +15 -0
- package/docs/api/type-aliases/PageInfo.md +15 -0
- package/docs/api/type-aliases/PageLabel.md +15 -0
- package/docs/api/type-aliases/PageSizeName.md +15 -0
- package/docs/api/type-aliases/Point.md +15 -0
- package/docs/api/type-aliases/Profile.md +15 -0
- package/docs/api/type-aliases/Progress.md +15 -0
- package/docs/api/type-aliases/ReadingOrder.md +15 -0
- package/docs/api/type-aliases/Rect.md +15 -0
- package/docs/api/type-aliases/Region.md +31 -0
- package/docs/api/type-aliases/RenderMode.md +15 -0
- package/docs/api/type-aliases/Rotation.md +15 -0
- package/docs/api/type-aliases/SaveOptions.md +15 -0
- package/docs/api/type-aliases/SaveReport.md +15 -0
- package/docs/api/type-aliases/Span.md +63 -0
- package/docs/api/type-aliases/SplitOn.md +15 -0
- package/docs/api/type-aliases/Standard14.md +15 -0
- package/docs/api/type-aliases/StructRef.md +41 -0
- package/docs/api/type-aliases/StructSource.md +17 -0
- package/docs/api/type-aliases/TableDetector.md +15 -0
- package/docs/api/type-aliases/TableMode.md +15 -0
- package/docs/api/type-aliases/TableScope.md +15 -0
- package/docs/api/type-aliases/Tag.md +15 -0
- package/docs/api/type-aliases/TagName.md +15 -0
- package/docs/api/type-aliases/TextLayout.md +15 -0
- package/docs/api/type-aliases/TextRange.md +33 -0
- package/docs/api/type-aliases/TextRunInfo.md +15 -0
- package/docs/api/type-aliases/Transform.md +15 -0
- package/docs/api/type-aliases/UndoOutcome.md +15 -0
- package/docs/api/type-aliases/ViewerPreferences.md +15 -0
- package/docs/api/type-aliases/WarningCode.md +15 -0
- package/docs/api/type-aliases/WasmSource.md +16 -0
- package/docs/api/variables/Matrix.md +99 -0
- package/docs/api/variables/PAGE_SIZES.md +15 -0
- package/docs/api/variables/TAG_NAMES.md +15 -0
- package/docs/errors/README.md +156 -0
- package/docs/errors/aes-padding-invalid.md +34 -0
- package/docs/errors/already-initialized.md +34 -0
- package/docs/errors/binary-output-to-terminal.md +35 -0
- package/docs/errors/builder-finished.md +35 -0
- package/docs/errors/cancelled.md +35 -0
- package/docs/errors/cmap-invalid.md +42 -0
- package/docs/errors/collection-index.md +33 -0
- package/docs/errors/color-components.md +33 -0
- package/docs/errors/content-bad-operands.md +36 -0
- package/docs/errors/content-path-unpainted.md +36 -0
- package/docs/errors/content-resource-missing.md +37 -0
- package/docs/errors/content-syntax.md +36 -0
- package/docs/errors/content-unbalanced-bt.md +36 -0
- package/docs/errors/content-unbalanced-marked.md +36 -0
- package/docs/errors/content-unbalanced-q.md +36 -0
- package/docs/errors/content-unknown-operator.md +37 -0
- package/docs/errors/data-pending.md +34 -0
- package/docs/errors/decompress.md +34 -0
- package/docs/errors/device-color-without-output-intent.md +33 -0
- package/docs/errors/direct-page.md +34 -0
- package/docs/errors/disposed.md +34 -0
- package/docs/errors/document-changed.md +34 -0
- package/docs/errors/embedded-file-forbidden.md +35 -0
- package/docs/errors/embedded-file-without-mime-type.md +36 -0
- package/docs/errors/encoding-full.md +34 -0
- package/docs/errors/encrypted-base.md +33 -0
- package/docs/errors/extract-limit-blocks.md +35 -0
- package/docs/errors/extract-module-not-loaded.md +37 -0
- package/docs/errors/extract-reading-order-ambiguous.md +32 -0
- package/docs/errors/extract-struct-tree-ignored.md +32 -0
- package/docs/errors/extract-table-ambiguous.md +32 -0
- package/docs/errors/extract-table-limit.md +35 -0
- package/docs/errors/extract-untagged-content.md +32 -0
- package/docs/errors/fetch-failed.md +34 -0
- package/docs/errors/flate-truncated.md +34 -0
- package/docs/errors/font-invalid.md +36 -0
- package/docs/errors/font-mismatch.md +34 -0
- package/docs/errors/font-not-embeddable.md +34 -0
- package/docs/errors/font-not-embedded.md +35 -0
- package/docs/errors/font-not-shapable.md +33 -0
- package/docs/errors/glyph-out-of-range.md +34 -0
- package/docs/errors/glyph-without-unicode.md +35 -0
- package/docs/errors/heading-level-skipped.md +36 -0
- package/docs/errors/image-data-length.md +33 -0
- package/docs/errors/image-too-large.md +32 -0
- package/docs/errors/inline-image-unterminated.md +36 -0
- package/docs/errors/input-unreadable.md +37 -0
- package/docs/errors/instance-reset.md +34 -0
- package/docs/errors/invalid-argument.md +33 -0
- package/docs/errors/invalid-axis-value.md +32 -0
- package/docs/errors/invalid-box.md +34 -0
- package/docs/errors/invalid-color.md +32 -0
- package/docs/errors/invalid-command-line.md +44 -0
- package/docs/errors/invalid-date.md +34 -0
- package/docs/errors/invalid-entry.md +34 -0
- package/docs/errors/invalid-feature.md +32 -0
- package/docs/errors/invalid-font.md +32 -0
- package/docs/errors/invalid-geometry.md +32 -0
- package/docs/errors/invalid-icc-profile.md +32 -0
- package/docs/errors/invalid-image.md +33 -0
- package/docs/errors/invalid-op-log.md +33 -0
- package/docs/errors/invalid-page-label.md +34 -0
- package/docs/errors/invalid-page-labels.md +32 -0
- package/docs/errors/invalid-page-range.md +37 -0
- package/docs/errors/invalid-rotate.md +34 -0
- package/docs/errors/invalid-structure-nesting.md +38 -0
- package/docs/errors/invalid-structure.md +34 -0
- package/docs/errors/invalid-text-range.md +33 -0
- package/docs/errors/job-finished.md +34 -0
- package/docs/errors/limit-exceeded.md +38 -0
- package/docs/errors/link-without-contents.md +33 -0
- package/docs/errors/link-without-structure.md +37 -0
- package/docs/errors/missing-alt-text.md +33 -0
- package/docs/errors/missing-glyph.md +33 -0
- package/docs/errors/missing-header.md +37 -0
- package/docs/errors/missing-lang.md +32 -0
- package/docs/errors/missing-root.md +34 -0
- package/docs/errors/missing-title.md +32 -0
- package/docs/errors/name-tree-node-skipped.md +34 -0
- package/docs/errors/nested-transaction.md +34 -0
- package/docs/errors/no-pending-save.md +32 -0
- package/docs/errors/no-transaction.md +33 -0
- package/docs/errors/not-initialized.md +35 -0
- package/docs/errors/not-pdf.md +33 -0
- package/docs/errors/not-rebased.md +35 -0
- package/docs/errors/object-number-out-of-range.md +38 -0
- package/docs/errors/objstm-index-mismatch.md +36 -0
- package/docs/errors/out-of-memory.md +38 -0
- package/docs/errors/outline-cycle.md +34 -0
- package/docs/errors/outline-item-skipped.md +34 -0
- package/docs/errors/output-unwritable.md +37 -0
- package/docs/errors/overwrite-refused.md +36 -0
- package/docs/errors/page-count-mismatch.md +34 -0
- package/docs/errors/page-index-out-of-range.md +34 -0
- package/docs/errors/page-tree-node-skipped.md +35 -0
- package/docs/errors/password-required.md +36 -0
- package/docs/errors/path-outside-roots.md +32 -0
- package/docs/errors/perms-mismatch.md +34 -0
- package/docs/errors/profile-version-conflict.md +35 -0
- package/docs/errors/reference-cycle.md +35 -0
- package/docs/errors/render-backend-unavailable.md +32 -0
- package/docs/errors/render-failed.md +33 -0
- package/docs/errors/render-too-large.md +36 -0
- package/docs/errors/repaired-base.md +34 -0
- package/docs/errors/requires-rewrite.md +38 -0
- package/docs/errors/saved-not-rebased.md +34 -0
- package/docs/errors/sink-not-empty.md +33 -0
- package/docs/errors/sink.md +31 -0
- package/docs/errors/source-budget-exceeded.md +34 -0
- package/docs/errors/source-changed.md +33 -0
- package/docs/errors/source.md +30 -0
- package/docs/errors/stale-generation.md +34 -0
- package/docs/errors/stream-length-recovered.md +37 -0
- package/docs/errors/strict.md +34 -0
- package/docs/errors/subset-failed.md +31 -0
- package/docs/errors/syntax.md +34 -0
- package/docs/errors/text-module-not-loaded.md +36 -0
- package/docs/errors/text-overflow.md +34 -0
- package/docs/errors/tounicode-conflict.md +32 -0
- package/docs/errors/type.md +34 -0
- package/docs/errors/unexpected-eof.md +34 -0
- package/docs/errors/unknown-axis.md +33 -0
- package/docs/errors/unknown-code.md +36 -0
- package/docs/errors/unknown-destination.md +33 -0
- package/docs/errors/unknown-font.md +33 -0
- package/docs/errors/unknown-option.md +33 -0
- package/docs/errors/unknown-page.md +34 -0
- package/docs/errors/unmapped-glyph.md +36 -0
- package/docs/errors/unnumbered-heading-misused.md +34 -0
- package/docs/errors/unset-object.md +30 -0
- package/docs/errors/unsupported-filter.md +34 -0
- package/docs/errors/unsupported-image.md +33 -0
- package/docs/errors/unsupported-layout-option.md +33 -0
- package/docs/errors/unsupported-security-handler.md +34 -0
- package/docs/errors/unsupported-version.md +32 -0
- package/docs/errors/untagged-content.md +35 -0
- package/docs/errors/warnings-truncated.md +35 -0
- package/docs/errors/wasm-trap.md +37 -0
- package/docs/errors/whole-font-rebuilt.md +30 -0
- package/docs/errors/worker-crashed.md +33 -0
- package/docs/errors/worker-failed.md +32 -0
- package/docs/errors/worker-version-mismatch.md +34 -0
- package/docs/errors/would-invalidate-signatures.md +38 -0
- package/docs/errors/wrong-document.md +33 -0
- package/docs/errors/wrong-password.md +34 -0
- package/docs/errors/xobject-cycle.md +36 -0
- package/docs/errors/xref-repaired.md +36 -0
- package/docs/errors/xrefstm-ignored.md +34 -0
- package/docs/llms.txt +88 -0
- package/docs/pages/concepts/dependencies.md +44 -0
- package/docs/pages/concepts/errors-and-warnings.md +64 -0
- package/docs/pages/concepts/how-lamina-thinks.md +86 -0
- package/docs/pages/concepts/pages-and-coordinates.md +67 -0
- package/docs/pages/guides/accessible-pdfs.md +260 -0
- package/docs/pages/guides/archival-pdfs.md +429 -0
- package/docs/pages/guides/edit-a-pdf.md +137 -0
- package/docs/pages/guides/fonts-and-text.md +202 -0
- package/docs/pages/guides/images-and-vectors.md +219 -0
- package/docs/pages/guides/keep-the-page-responsive.md +81 -0
- package/docs/pages/guides/links-outlines-and-metadata.md +222 -0
- package/docs/pages/guides/password-protected-files.md +70 -0
- package/docs/pages/guides/save-a-pdf.md +144 -0
- package/docs/pages/guides/untrusted-files.md +80 -0
- package/docs/pages/index.md +33 -0
- package/docs/pages/install.md +75 -0
- package/docs/pages/quick-start/browser.md +67 -0
- package/docs/pages/quick-start/bun.md +60 -0
- package/docs/pages/quick-start/deno.md +58 -0
- package/docs/pages/quick-start/node.md +61 -0
- package/docs/pages/quick-start/rust.md +58 -0
- package/docs/pages/quick-start/workers.md +139 -0
- package/docs/pages/recipes/cloudflare-worker.md +115 -0
- package/docs/pages/recipes/extract-markdown.md +114 -0
- package/docs/pages/recipes/generate-then-edit.md +135 -0
- package/docs/pages/recipes/hello.md +94 -0
- package/docs/pages/recipes/images-and-vectors.md +209 -0
- package/docs/pages/recipes/invoice.md +226 -0
- package/docs/pages/recipes/links-and-outlines.md +215 -0
- package/docs/pages/recipes/multilingual.md +189 -0
- package/docs/pages/recipes/open-untrusted.md +65 -0
- package/docs/pages/recipes/pdf-a-archive.md +205 -0
- package/docs/pages/recipes/pdf-a3-invoice-with-xml.md +222 -0
- package/docs/pages/recipes/pdf-ua-report.md +237 -0
- package/docs/pages/recipes/report.md +125 -0
- package/docs/pages/recipes/web-worker-client.md +64 -0
- package/docs/pages/recipes.md +47 -0
- package/docs/pages/reference/changelog.md +86 -0
- package/docs/pages/reference/codes.md +14 -0
- package/docs/pages/reference/glossary.md +108 -0
- package/docs/pages/reference/rust.md +36 -0
- package/docs/pages/reference/stability.md +59 -0
- package/docs/pages/start/coding-agents.md +45 -0
- package/docs/pages/start/first-pdf.md +77 -0
- package/docs/pages/troubleshooting.md +74 -0
- package/docs/renames.json +86 -0
- package/docs/schemas/error.schema.json +305 -0
- package/docs/schemas/renames.schema.json +29 -0
- package/package.json +93 -3
- package/schemas/error.schema.json +305 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// @generated by cargo xtask gen codes. DO NOT EDIT.
|
|
2
|
+
// From docs/errors/<code>.md; `cargo xtask check docs` fails when it is stale. Imported only
|
|
3
|
+
// by the `@laminapdf/sdk/explain` subpath, so the text stays out of the main bundle.
|
|
4
|
+
/** Every code's explain page (ADR 0026 §6), as Markdown. */
|
|
5
|
+
export const PAGES = {
|
|
6
|
+
"missing-header": "# `missing-header`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nNo `%PDF-x.y` header was found in the first 1,024 bytes; parsing went ahead.\n\n## Why\n\nFiles are often truncated at the front or prefixed with other data.\n\n## How to fix\n\nNothing, if the document reads correctly. Open in strict mode to refuse such files, or strip whatever precedes `%PDF` before opening.\n\n## Example\n\n```text\noffset 0 From: mailer@example.org\n …(1,200 bytes of mail headers and a MIME boundary)…\noffset 1,212 %PDF-1.7\n 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n```\n\nLamina PDF looks for `%PDF-` in the first 1,024 bytes only. Here it starts at byte 1,212, so `Document::open_bytes` warns `missing-header` and reads the objects anyway. With `OpenOptions::new().with_strict(true)` the same open fails with [`strict`](strict.md).\n\n## Data fields\n\nNone.\n",
|
|
7
|
+
"xref-repaired": "# `xref-repaired`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nThe stored cross-reference data was unusable and was rebuilt by scanning the file.\n\n## Why\n\nEdited, truncated or concatenated files often have stale offsets.\n\n## How to fix\n\nNothing, if the result is right. Incremental saves refuse a repaired base (`repaired-base`); save in full instead.\n\n## Example\n\n```text\nstartxref\n123456\n%%EOF\n```\n\n`startxref` points past the end of a file that was truncated and re-padded. Lamina PDF scans the file for `n g obj` headers, rebuilds the table, raises `xref-repaired` and opens normally. `save(SaveMode::Incremental)` is then refused with [`repaired-base`](repaired-base.md); `SaveMode::Auto` rewrites the file.\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0003](../decisions/0003-reader-tolerance-and-limits.md) §1\n",
|
|
8
|
+
"xrefstm-ignored": "# `xrefstm-ignored`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA hybrid file's `/XRefStm` could not be read and was ignored.\n\n## Why\n\nHybrid files carry a classic xref table for old readers and an xref stream for objects in object streams. An editor that moved bytes without updating `/XRefStm`, or a damaged stream, leaves the stream unreadable.\n\n## How to fix\n\nNothing; the classic table is used.\n\n## Example\n\n```text\ntrailer << /Size 40 /Root 1 0 R /XRefStm 99999 >>\n```\n\nA hybrid-reference file (ISO 32000-2 §7.5.8.4) points `/XRefStm` at an offset with no stream. Lamina PDF ignores it and reads the classic table; objects only the stream listed may be missing.\n\n## Data fields\n\n`offset` (where `/XRefStm` points).\n",
|
|
9
|
+
"stream-length-recovered": "# `stream-length-recovered`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA stream's `/Length` was wrong or unresolvable; its end was found by scanning for `endstream`.\n\n## Why\n\nWriters that edit streams without updating `/Length`.\n\n## How to fix\n\nNothing; re-saving writes the correct length.\n\n## Example\n\n```text\n4 0 obj << /Length 10 >>\nstream\nBT /F1 12 Tf 72 720 Td (Hello) Tj ET\nendstream\n```\n\n`/Length` says 10 bytes but the data is longer. Lamina PDF finds `endstream`, uses the real length and raises the warning with the stream's `object`.\n\n## Data fields\n\nNone.\n",
|
|
10
|
+
"stale-generation": "# `stale-generation`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA reference's generation number did not match its xref entry, so it resolved to `null` (ISO 32000-2 §7.3.10).\n\n## Why\n\nAn editor deleted and reused an object number (bumping its generation) but left a reference with the old generation behind.\n\n## How to fix\n\nNothing; the reference is treated as missing.\n\n## Example\n\n```text\n/Contents 7 1 R % but the xref entry for object 7 has generation 0\n```\n\nThe reference resolves to `null` (ISO 32000-2 §7.3.10), as if object 7 were missing, and the warning carries `generation` 1.\n\n## Data fields\n\n`generation`.\n",
|
|
11
|
+
"page-tree-node-skipped": "# `page-tree-node-skipped`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA page-tree node was missing, unparsable, of the wrong type or too deep, and was skipped.\n\n## Why\n\nDamaged page trees: a kid that is missing, unparsable or of the wrong type, a `/Kids` that is not an array, or a tree deeper than `max_page_tree_depth`.\n\n## How to fix\n\nCheck the page count; pages under the skipped node are missing.\n\n## Example\n\n```text\n2 0 obj << /Type /Pages /Kids [3 0 R 9 0 R] /Count 2 >>\n9 0 obj (not a page) endobj\n```\n\nKid 9 is a string, not a page or a pages node: it is skipped with this warning, so the document has one page. The `location` is the skipped node's `object` when it has one.\n\n## Data fields\n\nNone.\n",
|
|
12
|
+
"page-count-mismatch": "# `page-count-mismatch`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nThe page tree's `/Count` differs from the number of pages walked; the walk wins.\n\n## Why\n\nWriters that don't update `/Count`.\n\n## How to fix\n\nNothing; Lamina PDF uses the walked count.\n\n## Example\n\n```text\n2 0 obj << /Type /Pages /Kids [3 0 R 4 0 R] /Count 7 >>\n```\n\nThe root says 7 pages but two are reachable: `page_count()` is 2, and the warning's `data` is `{ \"declared\": 7, \"walked\": 2 }`.\n\n## Data fields\n\n`declared`, `walked`.\n",
|
|
13
|
+
"flate-truncated": "# `flate-truncated`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nFlate data was truncated or had a bad checksum; the partial output was kept.\n\n## Why\n\nTruncated downloads or damaged streams.\n\n## How to fix\n\nNothing, if the content looks right; part of the stream may be missing.\n\n## Example\n\n```text\n5 0 obj << /Filter /FlateDecode /Length 900 >> stream …(the zlib data stops early)… endstream\n```\n\nLamina PDF keeps what inflated before the data ended or the Adler-32 checksum failed; the content may be cut short. The warning carries the stream's `object`.\n\n## Data fields\n\nNone.\n",
|
|
14
|
+
"objstm-index-mismatch": "# `objstm-index-mismatch`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn object stream's index was stale; the object was found by searching.\n\n## Why\n\nObject streams rewritten by a tool that did not update the index at their start.\n\n## How to fix\n\nNothing.\n\n## Example\n\n```text\n12 0 obj << /Type /ObjStm /N 3 /First 18 >> stream\n20 0 21 14 22 31\n…\n```\n\nThe index says object 21 starts at offset 14 of the stream, but it starts elsewhere. Lamina PDF searches the stream for it, uses what it finds and raises the warning.\n\n## Data fields\n\nNone.\n",
|
|
15
|
+
"object-number-out-of-range": "# `object-number-out-of-range`\n\n- Type: warning\n- Kind: `limit` (a `Limits` value or another resource bound was hit)\n\n## What happened\n\nAn xref entry above `Limits::max_object_number` was dropped.\n\n## Why\n\nCorrupt or hostile xref data.\n\n## How to fix\n\nRaise `max_object_number` only for trusted input.\n\n## Example\n\n```text\nxref\n0 1\n0000000000 65535 f\n9999999 1\n0000000015 00000 n\n```\n\nObject 9,999,999 is above `Limits::max_object_number`; its entry is dropped so a hostile xref can't make Lamina PDF allocate for it. References to it resolve to `null`.\n\n## Data fields\n\nNone.\n",
|
|
16
|
+
"aes-padding-invalid": "# `aes-padding-invalid`\n\n- Type: warning\n- Kind: `security` (encryption, permissions or signatures block the call, or look tampered with)\n\n## What happened\n\nAES-encrypted data was not `IV ‖ whole blocks` or had invalid padding; it was kept as stored or unstripped.\n\n## Why\n\nDamaged data, or plaintext spliced into an encrypted file (PDFex-style).\n\n## How to fix\n\nTreat the affected strings and streams as suspect: they may have been altered or spliced in. Don't use them for anything security-relevant.\n\n## Example\n\n```text\n(an AESV2 string whose last block does not end in valid PKCS#5 padding)\n```\n\nLamina PDF keeps the data as stored (or unstripped) instead of failing, and raises the warning with the string's or stream's `object`.\n\n## Data fields\n\nNone.\n",
|
|
17
|
+
"perms-mismatch": "# `perms-mismatch`\n\n- Type: warning\n- Kind: `security` (encryption, permissions or signatures block the call, or look tampered with)\n\n## What happened\n\nAn R5/R6 document's `/Perms` does not match its `/P` and `/EncryptMetadata`.\n\n## Why\n\n`/P` may have been tampered with (ISO 32000-2 Algorithm 13).\n\n## How to fix\n\nTreat the permissions as untrusted.\n\n## Example\n\n```text\n/Encrypt << /V 5 /R 6 /P -3904 /Perms <…> … >> % /Perms decrypts to a different /P\n```\n\nThe document opens, but `Document::permissions()` comes from a `/P` that the encrypted `/Perms` contradicts.\n\n## Data fields\n\nNone.\n",
|
|
18
|
+
"warnings-truncated": "# `warnings-truncated`\n\n- Type: warning\n- Kind: `limit` (a `Limits` value or another resource bound was hit)\n\n## What happened\n\nMore than `Limits::max_warnings` warnings were raised; the rest were dropped.\n\n## Why\n\nA badly damaged or hostile file.\n\n## How to fix\n\nRaise `max_warnings` if you need the full list.\n\n## Example\n\nA damaged file raising 50,000 repairs with the default `max_warnings`: the first warnings are kept, then one `warnings-truncated`, and the rest are dropped.\n\n```rust\nlet limits = laminapdf::Limits::default().with_max_warnings(100_000);\nlet doc = laminapdf::Document::open_bytes(bytes, laminapdf::OpenOptions::new().with_limits(limits))?;\n```\n\n## Data fields\n\nNone.\n",
|
|
19
|
+
"invalid-box": "# `invalid-box`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA page boundary box is not four numbers; it reads as absent.\n\n## Why\n\nA writer bug: a box that is not an array of four numbers (too few, or names or strings in it).\n\n## How to fix\n\nNothing; the page uses the next box in the chain.\n\n## Example\n\n```text\n3 0 obj << /Type /Page /MediaBox [0 0 612 792] /CropBox [0 0 612] >>\n```\n\nThe CropBox has three numbers, so it reads as absent and the page uses its MediaBox. `data` is `{ \"key\": \"CropBox\" }`.\n\n## Data fields\n\n`key` (which box, from Lamina PDF's fixed list).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0027](../decisions/0027-coordinates-units-and-page-indexing.md) §3\n",
|
|
20
|
+
"invalid-rotate": "# `invalid-rotate`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA page's `/Rotate` is not a multiple of 90; it reads as 0.\n\n## Why\n\nA writer bug, or a viewer setting stored as an arbitrary angle.\n\n## How to fix\n\nNothing to do if the page displays upright. Otherwise set the rotation you want with the `rotatePage` op.\n\n## Example\n\n```text\n3 0 obj << /Type /Page /Rotate 45 … >>\n```\n\n`/Rotate` must be a multiple of 90 (ISO 32000-2 §7.7.3.3); the page reads as unrotated.\n\n## Data fields\n\nNone.\n",
|
|
21
|
+
"name-tree-node-skipped": "# `name-tree-node-skipped`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA name- or number-tree node was malformed, reached twice or too deep, and was skipped.\n\n## Why\n\nDamaged trees: a node that is not a dictionary, a kid reached twice (a cycle), or a tree deeper than `max_tree_depth`.\n\n## How to fix\n\nNothing to do; the entries under the skipped node are missing from the tree.\n\n## Example\n\n```text\n<< /Kids [20 0 R 21 0 R] >> % 21 0 obj is (oops), not a dictionary\n```\n\nThe walk of the `/Dests` name tree skips node 21 and keeps the entries under node 20.\n\n## Data fields\n\nNone.\n",
|
|
22
|
+
"outline-cycle": "# `outline-cycle`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn outline item was reached twice; that branch stopped.\n\n## Why\n\nDamaged or hostile outlines.\n\n## How to fix\n\nNothing; the outline is shown without the loop.\n\n## Example\n\n```text\n11 0 obj << /Title (Chapter 1) /Parent 10 0 R /Next 11 0 R >>\n```\n\nItem 11 is its own `/Next`; the outline walk stops that branch the second time it reaches it.\n\n## Data fields\n\nNone.\n",
|
|
23
|
+
"outline-item-skipped": "# `outline-item-skipped`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn outline item was missing, not a dictionary or too deep; it and its subtree were skipped.\n\n## Why\n\nDamaged outlines.\n\n## How to fix\n\nNothing to do; the skipped item and its children are missing from the outline.\n\n## Example\n\n```text\n10 0 obj << /Type /Outlines /First 11 0 R >> % object 11 is missing\n```\n\nThe first item can't be read, so it and everything under it are left out of `outline()`.\n\n## Data fields\n\nNone.\n",
|
|
24
|
+
"invalid-date": "# `invalid-date`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn Info date is not a valid PDF date (ISO 32000-2 §7.9.4); the raw string is kept.\n\n## Why\n\nWriters that write free-form dates.\n\n## How to fix\n\nThe `Info` view keeps the raw string; read it there if you need it.\n\n## Example\n\n```text\n<< /CreationDate (last Tuesday) >>\n```\n\nThe Info view's `creation_date` is `None` and its raw string is kept; `data` is `{ \"key\": \"CreationDate\" }`.\n\n## Data fields\n\n`key` (which entry).\n",
|
|
25
|
+
"invalid-page-label": "# `invalid-page-label`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA `/PageLabels` entry is malformed; that part is dropped.\n\n## Why\n\nA writer bug: a range that is not a dictionary, an unknown `/S` style, a non-string `/P` prefix or an `/St` below 1.\n\n## How to fix\n\nNothing; affected pages fall back to decimal labels.\n\n## Example\n\n```text\n/PageLabels << /Nums [0 << /S /Fancy >> 4 << /S /r >>] >>\n```\n\n`/Fancy` is not a numbering style, so pages 0–3 get decimal labels; the range starting at 4 is kept. `data` is `{ \"start\": 0 }`.\n\n## Data fields\n\n`start`.\n",
|
|
26
|
+
"invalid-entry": "# `invalid-entry`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn entry a view reads is unreadable or of the wrong type; it reads as absent or its default.\n\n## Why\n\nA writer bug: an entry of the wrong type, or a reference that does not resolve.\n\n## How to fix\n\nNothing to do; the view uses the entry's default.\n\n## Example\n\n```text\n1 0 obj << /Type /Catalog /Pages 2 0 R /Outlines (none) >>\n```\n\n`/Outlines` should be a dictionary; the outline reads as empty and `data` is `{ \"key\": \"Outlines\" }`.\n\n## Data fields\n\n`key` (from Lamina PDF's fixed list).\n",
|
|
27
|
+
"content-unknown-operator": "# `content-unknown-operator`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA content stream used an operator ISO 32000-2 Annex A does not define, outside a `BX`/`EX` compatibility section; it was skipped.\n\n## Why\n\nA producer wrote a private or misspelled operator.\n\n## How to fix\n\nNothing to do; check the page's rendering if the operator mattered.\n\n## Example\n\n```text\nBT /F1 12 Tf 72 720 Td (Hi) Tj ET\n0 0 1 setcolor\n```\n\n`setcolor` is not an Annex A operator and is not inside `BX`/`EX`, so it is skipped with its operands. `data.offset` is its byte offset in the decoded content.\n\n## Data fields\n\n- `offset`: the operator's byte offset in the decoded content.\n\nThe `location` is `object` (the page for page content, the Form XObject for a form) with `streamOffset` in its decoded content; none for content interpreted on its own or a direct page.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0023](../decisions/0023-content-stream-reading.md)\n",
|
|
28
|
+
"content-bad-operands": "# `content-bad-operands`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn operator had too few operands or operands of the wrong type (it was skipped), or extra operands (the last ones were used).\n\n## Why\n\nA producer bug, or damaged content.\n\n## How to fix\n\nNothing to do.\n\n## Example\n\n```text\n72 Td\n```\n\n`Td` takes two numbers; with one, the operator is skipped.\n\n## Data fields\n\n- `offset`: the operator's byte offset in the decoded content.\n\nThe `location` is `object` (the page for page content, the Form XObject for a form) with `streamOffset` in its decoded content; none for content interpreted on its own or a direct page.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0023](../decisions/0023-content-stream-reading.md)\n",
|
|
29
|
+
"content-unbalanced-q": "# `content-unbalanced-q`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA `Q` had no matching `q` (it was ignored), or a content stream ended with saved graphics states still open (they were restored).\n\n## Why\n\nProducers that split `q`/`Q` pairs across streams, or damaged content.\n\n## How to fix\n\nNothing to do; each stream balances on its own.\n\n## Example\n\n```text\nq 1 0 0 1 72 72 cm 0 0 10 10 re f Q Q\n```\n\nThe second `Q` has no matching `q` and is ignored.\n\n## Data fields\n\n- `offset`: the `Q`'s byte offset, or the end of the content.\n\nThe `location` is `object` (the page for page content, the Form XObject for a form) with `streamOffset` in its decoded content; none for content interpreted on its own or a direct page.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0023](../decisions/0023-content-stream-reading.md)\n",
|
|
30
|
+
"content-unbalanced-bt": "# `content-unbalanced-bt`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA `BT` inside a text object, an `ET` outside one, a text operator outside one, or a content stream that ended inside one.\n\n## Why\n\nA producer bug, or damaged content.\n\n## How to fix\n\nNothing to do; text operators were handled tolerantly.\n\n## Example\n\n```text\nBT /F1 12 Tf (one) Tj BT (two) Tj ET\n```\n\nThe second `BT` opens a text object inside one; Lamina PDF treats it as a new text object and carries on.\n\n## Data fields\n\n- `offset`: the operator's byte offset, or the end of the content.\n\nThe `location` is `object` (the page for page content, the Form XObject for a form) with `streamOffset` in its decoded content; none for content interpreted on its own or a direct page.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0023](../decisions/0023-content-stream-reading.md)\n",
|
|
31
|
+
"content-unbalanced-marked": "# `content-unbalanced-marked`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn `EMC` had no matching `BMC`/`BDC` (it was ignored), or a content stream ended with marked-content sequences open (they were closed).\n\n## Why\n\nA producer bug, or tagging split across streams.\n\n## How to fix\n\nCheck tagged-content results; the sequences were closed for you.\n\n## Example\n\n```text\n/P << /MCID 0 >> BDC (text) Tj\n```\n\nThe stream ends with the marked-content sequence open; it is closed at the end, so MCID 0 covers the text that follows it.\n\n## Data fields\n\n- `offset`: the `EMC`'s byte offset, or the end of the content.\n\nThe `location` is `object` (the page for page content, the Form XObject for a form) with `streamOffset` in its decoded content; none for content interpreted on its own or a direct page.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0023](../decisions/0023-content-stream-reading.md)\n",
|
|
32
|
+
"content-syntax": "# `content-syntax`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA content stream held bytes that are not a token (an unbalanced `)` or `>`, a bad hex digit, an unterminated string or array) or a stray `ID`/`EI`; they were skipped with the operands read so far.\n\n## Why\n\nDamaged or truncated content.\n\n## How to fix\n\nCheck the page's content around the reported offset.\n\n## Example\n\n```text\nBT (unbalanced Tj ET\n```\n\nThe string never closes, so the bytes to the end of the token are skipped with the pending operands.\n\n## Data fields\n\n- `offset`: the problem's byte offset in the decoded content.\n\nThe `location` is `object` (the page for page content, the Form XObject for a form) with `streamOffset` in its decoded content; none for content interpreted on its own or a direct page.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0023](../decisions/0023-content-stream-reading.md)\n",
|
|
33
|
+
"content-resource-missing": "# `content-resource-missing`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn operator named a resource (font, XObject, graphics state, colour space, shading, property list) that is missing, of the wrong type or unreadable, or a Form XObject's content could not be decoded; the operator was skipped or ran without it.\n\n## Why\n\nResources lost in editing or merging, or damaged objects.\n\n## How to fix\n\nCheck the page's `/Resources`.\n\n## Example\n\n```text\n/Im7 Do % /Resources << /XObject << /Im1 8 0 R >> >>\n```\n\n`/Im7` is not in the page's resources, so the `Do` draws nothing. `data.category` is `XObject`; the resource's name, which comes from the file, is never put in the message.\n\n## Data fields\n\n- `offset`: the operator's byte offset in the decoded content.\n- `category`: the resource category, e.g. `Font` (Lamina PDF's own vocabulary).\n\nThe `location` is `object` (the page for page content, the Form XObject for a form) with `streamOffset` in its decoded content; none for content interpreted on its own or a direct page.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0023](../decisions/0023-content-stream-reading.md)\n",
|
|
34
|
+
"inline-image-unterminated": "# `inline-image-unterminated`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nNo `EI` ends an inline image (neither at its declared or computed length nor by searching for it); the rest of the content was skipped.\n\n## Why\n\nDamaged or truncated content.\n\n## How to fix\n\nCheck the page: everything after the inline image is missing.\n\n## Example\n\n```text\nBI /W 10 /H 10 /CS /G /BPC 8 ID …(100 bytes, then the stream ends)…\n```\n\nNo `EI` follows the image data, so the rest of the stream is skipped.\n\n## Data fields\n\n- `offset`: the `BI`'s byte offset in the decoded content.\n\nThe `location` is `object` (the page for page content, the Form XObject for a form) with `streamOffset` in its decoded content; none for content interpreted on its own or a direct page.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0023](../decisions/0023-content-stream-reading.md): the inline-image search\n",
|
|
35
|
+
"xobject-cycle": "# `xobject-cycle`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA Form XObject draws itself, directly or through other forms; the inner `Do` was skipped.\n\n## Why\n\nA malformed or hostile file: forms that draw each other without end would never finish.\n\n## How to fix\n\nNothing to do.\n\n## Example\n\n```text\n8 0 obj << /Subtype /Form /Resources << /XObject << /Me 8 0 R >> >> >> stream /Me Do endstream\n```\n\nThe form draws itself: the inner `/Me Do` is skipped once Lamina PDF sees form 8 a second time.\n\n## Data fields\n\n- `object`: the form reached again.\n\nThe `location` is `object` (the form reached again).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0023](../decisions/0023-content-stream-reading.md)\n",
|
|
36
|
+
"content-path-unpainted": "# `content-path-unpainted`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA content stream ended with a path that no painting operator ended; it was dropped.\n\n## Why\n\nA producer bug, or truncated content.\n\n## How to fix\n\nNothing to do.\n\n## Example\n\n```text\n0 0 m 100 100 l\n```\n\nThe content stream ends after building a path without `S`, `f` or `n`: the path is dropped, as the spec intends.\n\n## Data fields\n\n- `offset`: the byte offset of the path's first segment in the decoded content.\n\nThe `location` is `object` (the page for page content, the Form XObject for a form) with `streamOffset` in its decoded content; none for content interpreted on its own or a direct page.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0023](../decisions/0023-content-stream-reading.md)\n",
|
|
37
|
+
"font-invalid": "# `font-invalid`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA font dictionary read for text extraction has a missing, unreadable or malformed entry (the descendant font, `/Encoding` or its `/Differences`, `/Widths`, `/W`, `/W2`, the font descriptor or its font program). Its text was decoded with defaults: the standard encoding, the standard 14 font's widths or `/MissingWidth`, `/DW`.\n\n## Why\n\nA producer bug, a damaged file, or a font dictionary that relies on what a viewer guesses.\n\n## How to fix\n\nNothing to do. Text or positions extracted for that font may be off; regenerating the file with a current producer fixes it.\n\n## Example\n\n```text\n<< /Type /Font /Subtype /Type0 /BaseFont /Foo /Encoding /Identity-H /DescendantFonts 99 0 R >>\n```\n\nThe descendant font is missing, so text in this font is decoded with defaults: two-byte codes, default widths. The `location` is the font dictionary's `object`.\n\n## Data fields\n\nNone.\n\nThe `location` is `object`, the font dictionary's object; none for a direct font dictionary.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [B9b](../todos/done/B9b-text-extraction-core.md): text extraction\n",
|
|
38
|
+
"cmap-invalid": "# `cmap-invalid`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA font's CMap (its Type 0 `/Encoding`) or ToUnicode CMap couldn't be fully used, for one of three reasons:\n\n- entries that can't be read, which are skipped;\n- a predefined CMap Lamina PDF has no table for, read as Identity (Lamina PDF knows only `Identity-H`/`-V` and the `Uni…-UCS2-…`/`Uni…-UTF16-…` ones);\n- a `/UseCMap` chain deeper than 8 levels, cut there.\n\n## Why\n\nA producer bug (bad ranges, a missing `end…` keyword), or a CJK file using one of Adobe's character-collection CMaps without embedding it.\n\n## How to fix\n\nNothing to do. The rest of the CMap is used; affected glyphs may extract as other text or U+FFFD.\n\n## Example\n\n```text\n1 beginbfchar\n<0003> <0020\nendbfchar\n```\n\nThe destination string is not closed; the entry is skipped, and the rest of the ToUnicode CMap is used.\n\n## Data fields\n\nNone.\n\nThe `location` is `object`, the font dictionary's object; none for a direct font dictionary.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [B9b](../todos/done/B9b-text-extraction-core.md): text extraction\n",
|
|
39
|
+
"unmapped-glyph": "# `unmapped-glyph`\n\n- Type: warning\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA font showed glyphs that its ToUnicode CMap, its encoding's glyph names and its font program's `cmap` all leave unmapped. They extract as U+FFFD; `PageText::unmapped` counts them.\n\n## Why\n\nThe file has no ToUnicode CMap for a subset or symbolic font, uses arbitrary glyph names (`g37`), or obfuscates its encoding (ISO 32000-2 §9.10.2: there is then no way to know what the glyph means).\n\n## How to fix\n\nNothing to do in Lamina PDF. A producer that writes ToUnicode CMaps (Lamina PDF does) avoids it; OCR is the only way to recover such text.\n\n## Example\n\n```text\n<< /Subtype /Type0 /BaseFont /ABCDEF+Subset /Encoding /Identity-H >> % no /ToUnicode\n```\n\nA subset font with Identity-H and no ToUnicode: unless its embedded `cmap` maps the glyphs, they extract as U+FFFD and the warning carries the font's `object`.\n\n## Data fields\n\nNone.\n\nThe `location` is `object`, the font dictionary's object; none for a direct font dictionary.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [B9b](../todos/done/B9b-text-extraction-core.md): text extraction\n",
|
|
40
|
+
"extract-limit-blocks": "# `extract-limit-blocks`\n\n- Type: warning\n- Kind: `limit` (a `Limits` value or another resource bound was hit)\n\n## What happened\n\nA page had more runs of text than `ExtractLimits::max_blocks_per_page` allows. Since each run can be a block of its own, extraction kept the first runs the page draws and left out the rest. It cuts them before grouping runs into blocks, so the work stays bounded.\n\n## Why\n\nA page of many tiny separate pieces of text, such as a dense table, a map with hundreds of labels, or a file built to slow extraction down.\n\n## How to fix\n\nRaise `max_blocks_per_page` if the page really holds that many blocks.\n\n## Example\n\n```rust\nlet mut options = laminapdf_extract::ExtractOptions::default();\noptions.limits.max_blocks_per_page = 50_000;\n```\n\n## Data fields\n\n- `limit`: the limit that was reached.\n\nThe `location` has the page's `pageIndex`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L3a](../todos/done/L3a-lamina-extract-reading-order.md): blocks and reading order\n",
|
|
41
|
+
"extract-reading-order-ambiguous": "# `extract-reading-order-ambiguous`\n\n- Type: warning\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\nA page mixes horizontal and vertical writing. Extraction put its blocks in an order, but no layout rule tells which comes first, so the order is a guess.\n\n## Why\n\nJapanese and Chinese pages often set the body vertically and captions, headings or page numbers horizontally. Without a structure tree, nothing in the file says how the two relate.\n\n## How to fix\n\nNothing to do in Lamina PDF; the file is valid. Like every warning, it fails the call in strict mode. Check the order of that page's blocks, or ask for the order the content stream draws (`ReadingOrder::ContentStream`). A tagged file avoids the guess.\n\n## Example\n\nA page whose body text is drawn with a vertical font (`Identity-V`) and whose page number is drawn with a horizontal one.\n\n## Data fields\n\nNone.\n\nThe `location` has the page's `pageIndex`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L3a](../todos/done/L3a-lamina-extract-reading-order.md): blocks and reading order\n",
|
|
42
|
+
"extract-table-ambiguous": "# `extract-table-ambiguous`\n\n- Type: warning\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\nExtraction found a table drawn with lines, but the cells its missing lines would join don't make a rectangle. Extraction kept the table and gave it one cell per square of its grid, so a merged cell's text sits in the first of the squares it covers.\n\n## Why\n\nA table's lines tell where cells are merged: a missing line between two squares joins them. Joined squares that form an L or a T shape don't describe a cell, usually because a line is drawn slightly short, drawn twice in different places, or covered by a white rectangle.\n\n## How to fix\n\nNothing to do in Lamina PDF; the file is valid. Like every warning, it fails the call in strict mode. Check that table's cells: the text of a merged cell may be split across its neighbours.\n\n## Example\n\nA table whose header spans two columns over the first row only, but whose line under that header stops halfway.\n\n## Data fields\n\nNone.\n\nThe `location` has the page's `pageIndex`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L3b](../todos/done/L3b-extract-tables-and-serialisers.md): tables and extraction output\n",
|
|
43
|
+
"extract-table-limit": "# `extract-table-limit`\n\n- Type: warning\n- Kind: `limit` (a `Limits` value or another resource bound was hit)\n\n## What happened\n\nA page's ruling lines would have made a table with more cells than `ExtractLimits::max_table_cells` allows, or the page drew so many lines that matching them all would cost too much. Extraction made no table from those lines; their text is still in the page's paragraphs.\n\n## Why\n\nA page drawn with thousands of lines, such as a large grid, a chart's gridlines or a file built to slow extraction down. To find a table, extraction compares every horizontal line with every vertical one, so the work grows with the square of the line count.\n\n## How to fix\n\nRaise `max_table_cells` if the page really holds a table that big.\n\n## Example\n\n```rust\nlet mut options = laminapdf_extract::ExtractOptions::default();\noptions.limits.max_table_cells = 50_000;\n```\n\n## Data fields\n\n- `limit`: the limit that was reached.\n\nThe `location` has the page's `pageIndex`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L3b](../todos/done/L3b-extract-tables-and-serialisers.md): tables and extraction output\n",
|
|
44
|
+
"extract-struct-tree-ignored": "# `extract-struct-tree-ignored`\n\n- Type: warning\n- Kind: `input` (a problem with the PDF file)\n\n## What happened\n\nThe page is tagged, but its structure tree didn't pass the checks that decide whether to trust it, so extraction read the page in layout order instead.\n\n## Why\n\nA structure tree gives a page's reading order, but trees are often incomplete or wrong. Extraction uses a page's tree only when its elements cover at least 80% of the page's visible text, not counting headers, page numbers and other text the file marks as page furniture. Its order must also jump back up into an earlier column at most once in ten moves from block to block. A tree written by a tool that tags only some of the text, or that tags it in drawing order, fails one of the checks.\n\n## How to fix\n\nNothing to do: the page reads in layout order, as an untagged page would. To use the tree anyway, extract with `ReadingOrder::Tagged` (`readingOrder: \"tagged\"`). Like every warning, it fails the call in strict mode.\n\n## Example\n\nA report whose tool tagged only its headings: the tree covers a tenth of the text, so the reason is `coverage`.\n\n## Data fields\n\n- `reason`: `coverage` or `order`, the check that failed.\n\nThe `location` has the page's `pageIndex`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0030](../decisions/0030-extraction-output-and-model-plugins.md): the extraction output and the reading-order sources\n",
|
|
45
|
+
"extract-untagged-content": "# `extract-untagged-content`\n\n- Type: warning\n- Kind: `input` (a problem with the PDF file)\n\n## What happened\n\nThe page was read in its structure tree's order, but some of its visible text isn't in the tree and isn't marked as an artifact. Extraction kept that text and put it after the tree's blocks, in layout order.\n\n## Why\n\nIn a tagged file, every piece of visible content should belong to a structure element or be marked as an artifact (ISO 32000-2 §14.8.2.2). Text that is neither has no place in the tree's order. A tool that adds text after tagging a document, such as a stamp or a page number, often leaves text like this.\n\n## How to fix\n\nNothing to do in Lamina PDF. To give that text its place in the reading order, tag it in the file, or mark it as an artifact if it isn't content. Like every warning, it fails the call in strict mode.\n\n## Example\n\nA tagged contract with a \"DRAFT\" stamp drawn after tagging: the stamp's text comes after the contract's blocks.\n\n## Data fields\n\n- `chars`: how many characters (spaces left out) lie outside the tree.\n\nThe `location` has the page's `pageIndex`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0030](../decisions/0030-extraction-output-and-model-plugins.md): the extraction output and the reading-order sources\n",
|
|
46
|
+
"not-pdf": "# `not-pdf`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nThe input does not look like a PDF at all.\n\n## Why\n\nThe bytes are empty, or no `n g obj` header appears anywhere in them: an HTML error page saved as `.pdf`, a different format, or the wrong buffer.\n\n## How to fix\n\nCheck that you passed the right bytes.\n\n## Example\n\n```rust\nlet err = laminapdf::Document::open_bytes(b\"<html>…</html>\".to_vec(), Default::default()).unwrap_err();\nassert_eq!(err.code(), \"not-pdf\");\n```\n\n## Data fields\n\nNone.\n",
|
|
47
|
+
"missing-root": "# `missing-root`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nNo document catalog (`/Root`) was found, even after repair.\n\n## Why\n\nThe file is damaged beyond repair.\n\n## How to fix\n\nUse another copy of the file.\n\n## Example\n\n```text\ntrailer << /Size 3 >> % no /Root, and no object with /Type /Catalog in the file\n```\n\nRepair found no catalog to use, so `open` fails.\n\n## Data fields\n\nNone.\n",
|
|
48
|
+
"reference-cycle": "# `reference-cycle`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA reference chain or the page tree loops back on itself.\n\n## Why\n\nDamaged or hostile files.\n\n## How to fix\n\nUse another copy of the file.\n\n## Example\n\n```text\n5 0 obj 6 0 R endobj\n6 0 obj 5 0 R endobj\n```\n\nResolving object 5 leads back to itself; the call fails instead of looping. A page tree whose `/Kids` lead back to an ancestor fails the same way.\n\n## Data fields\n\nNone.\n",
|
|
49
|
+
"limit-exceeded": "# `limit-exceeded`\n\n- Type: error\n- Kind: `limit` (a `Limits` value or another resource bound was hit)\n\n## What happened\n\nA hard `Limits` value was exceeded.\n\n## Why\n\nHostile or unusually large input.\n\n## How to fix\n\nRaise the named limit only if you trust the input.\n\n## Example\n\n```rust\nlet options = OpenOptions::new().with_limits(Limits::new().with_max_nesting_depth(1));\nlet doc = Document::open_bytes(bytes, options)?;\nlet err = doc.page_count().unwrap_err();\n// { \"code\": \"limit-exceeded\", \"kind\": \"limit\", \"data\": { \"limit\": \"max_nesting_depth\" },\n// \"location\": { \"object\": 9, \"streamOffset\": 89 }, … }\n```\n\n## Data fields\n\n`limit` (the limit's name).\n\nThe `location` tells where the limit was hit. The nesting and string-length limits and the read window give `offset`, a file offset; the same limits inside an object stream give `object` with `streamOffset`. A reference chain gives `object` alone, and decoding budgets give no location.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0003](../decisions/0003-reader-tolerance-and-limits.md) §3\n",
|
|
50
|
+
"strict": "# `strict`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nStrict mode refused something the tolerant reader would have repaired.\n\n## Why\n\nThe file needs repair; see the warning code in the data.\n\n## How to fix\n\nOpen without strict mode, or fix the file.\n\n## Example\n\n```rust\nlet options = OpenOptions::new().with_strict(true);\nlet err = Document::open_bytes(bytes_with_a_broken_xref, options).unwrap_err();\n// { \"code\": \"strict\", \"data\": { \"warning\": \"xref-repaired\" }, … }\n```\n\n## Data fields\n\n`warning` (the warning code).\n",
|
|
51
|
+
"data-pending": "# `data-pending`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\nThe `ByteSource` hasn't fetched the listed byte ranges yet.\n\n## Why\n\nRange loading: the call needs more of the file.\n\n## How to fix\n\nFetch the ranges and retry the call; the document stays usable.\n\n## Example\n\n```ts\nconst doc = await Document.openRange(httpRangeSource(url));\n// pages() needs bytes that haven't arrived: ensure() fetches e.ranges and retries\nconst pages = await doc.ensure(() => doc.pages);\n```\n\n## Data fields\n\n`ranges` (offset, length pairs).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0005](../decisions/0005-document-model.md) §2\n",
|
|
52
|
+
"source": "# `source`\n\n- Type: error\n- Kind: `io` (the host failed to supply bytes; often transient)\n\n## What happened\n\nThe `ByteSource` failed to read.\n\n## Why\n\nA file-system or network error in the host.\n\n## How to fix\n\nRetry, or check the source.\n\n## Example\n\nA `ByteSource` reading from a network answered with an error for bytes 4096..+65536; the call fails with `source` and the same call can be retried once the connection is back.\n\n## Data fields\n\nNone.\n",
|
|
53
|
+
"type": "# `type`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn object had a different type than the structure requires.\n\n## Why\n\nMalformed input: an object of the wrong type where the structure requires a specific one.\n\n## How to fix\n\nUse another copy of the file.\n\n## Example\n\n```text\n2 0 obj << /Type /Pages /Kids 5 >>\n```\n\n`/Kids` must be an array; reading the page tree fails with `type`, and `data` names what was expected and where (`expected`, `context`, from Lamina PDF's own vocabulary).\n\n## Data fields\n\n`expected`, `context` (from Lamina PDF's fixed list).\n",
|
|
54
|
+
"invalid-structure": "# `invalid-structure`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn object stream, xref section or filter parameter was malformed or out of range.\n\n## Why\n\nMalformed input: an object stream, xref section or filter parameter out of range.\n\n## How to fix\n\nUse another copy of the file.\n\n## Example\n\n```text\n12 0 obj << /Type /ObjStm /N 3 /First -4 >>\n```\n\nA negative `/First` makes the object stream unusable; `data.structure` says which structure was malformed.\n\n## Data fields\n\n`structure`.\n",
|
|
55
|
+
"unset-object": "# `unset-object`\n\n- Type: error\n- Kind: `internal` (a bug in Lamina PDF or a broken invariant)\n\n## What happened\n\nA writer object number was reserved but never given a value.\n\n## Why\n\nA bug in Lamina PDF.\n\n## How to fix\n\nReport it at https://github.com/laudebugs/laminapdf/issues with the steps (and, if you can share it, the file) that led to it.\n\n## Example\n\nRaised by a writer when an object number reserved during a save never received its object. No input should cause it.\n\n## Data fields\n\nNone.\n",
|
|
56
|
+
"repaired-base": "# `repaired-base`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\nAn incremental update was requested for a document whose xref needed repair.\n\n## Why\n\nAppending to a repaired file would build on offsets that are wrong.\n\n## How to fix\n\nSave with a full rewrite instead.\n\n## Example\n\n```rust\nlet doc = Document::open_bytes(bytes_needing_repair, OpenOptions::new())?; // warns xref-repaired\nlet err = doc.save(&SaveOptions::default().with_mode(SaveMode::Incremental));\n// repaired-base; SaveMode::Auto (the default) writes a full rewrite instead\n```\n\n## Data fields\n\nNone.\n",
|
|
57
|
+
"encrypted-base": "# `encrypted-base`\n\n- Type: error\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\nA save was requested for an encrypted document without `SaveOptions::with_remove_encryption(true)` (every mode: `Auto`, `Incremental` and `FullRewrite`). Nothing was written. `laminapdf_save::SaveError::EncryptedBase` and the writers' `laminapdf_cos::Error::EncryptedBase` share this code.\n\n## Why\n\nLamina PDF can't encrypt on save yet, and it never drops or corrupts encryption silently.\n\n## How to fix\n\nSave with `SaveOptions::with_remove_encryption(true)` to write a plaintext full rewrite (every string and stream decrypted, no `/Encrypt`), or wait until Lamina PDF can encrypt on save. A signed and encrypted document also needs `with_invalidate_signatures(true)`.\n\n## Example\n\n```rust\nlet err = doc.save(&SaveOptions::default()).unwrap_err(); // encrypted-base\nlet saved = doc.save(&SaveOptions::default().with_remove_encryption(true))?;\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0007](../decisions/0007-save-planner.md) §9\n- [E11b](../todos/ready/E11b-encrypt-on-write.md): encryption on save\n",
|
|
58
|
+
"password-required": "# `password-required`\n\n- Type: error\n- Kind: `security` (encryption, permissions or signatures block the call, or look tampered with)\n\n## What happened\n\nThe document is encrypted with a non-empty user password and none was given.\n\n## Why\n\nEncrypted input.\n\n## How to fix\n\nAsk the user for the password and pass it in `OpenOptions` (`{ password }` in TS).\n\n## Example\n\n```ts\ntry {\n Document.open(bytes);\n} catch (e) {\n if (isLaminaPdfError(e, \"password-required\")) Document.open(bytes, { password: await ask() });\n}\n```\n\n## Data fields\n\nNone.\n",
|
|
59
|
+
"wrong-password": "# `wrong-password`\n\n- Type: error\n- Kind: `security` (encryption, permissions or signatures block the call, or look tampered with)\n\n## What happened\n\nThe given password is neither the user nor the owner password.\n\n## Why\n\nThe password is neither the user nor the owner password. Lamina PDF tries it as both (owner first for R5/R6), and in TS also its NFKC form.\n\n## How to fix\n\nAsk for the password again. Passwords are case-sensitive.\n\n## Example\n\n```ts\nDocument.open(bytes, { password: \"nope\" });\n// LaminaPdfError { code: \"wrong-password\", kind: \"security\",\n// hint: \"ask for the password again and retry with the correct one\", … }\n```\n\n## Data fields\n\nNone.\n",
|
|
60
|
+
"unsupported-security-handler": "# `unsupported-security-handler`\n\n- Type: error\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\nThe document uses a security handler Lamina PDF can't decrypt.\n\n## Why\n\nNot `/Standard`, an unknown `/V`/`/R`, or an unknown crypt filter method.\n\n## How to fix\n\nUse a tool that supports the handler.\n\n## Example\n\n```text\ntrailer << /Encrypt << /Filter /Adobe.PubSec /SubFilter /adbe.pkcs7.s5 … >> >>\n```\n\nA public-key (certificate) handler: Lamina PDF implements only `/Standard`, so `open` fails. `data.filter` holds the handler's name as read from the file.\n\n## Data fields\n\n`filter` (**untrusted**: read from the file; the message never includes it), `version`, `revision`.\n",
|
|
61
|
+
"unexpected-eof": "# `unexpected-eof`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nThe input ended before a complete token or object was read.\n\n## Why\n\nTruncated files.\n\n## How to fix\n\nUse a complete copy of the file.\n\n## Example\n\n```text\n1 0 obj << /Type /Catalog /Pages 2 0 R\n```\n\nThe file ends inside a dictionary; the `location` is the file offset where reading stopped.\n\n## Data fields\n\nNone. The `location` is a file `offset`, or `object` with `streamOffset` inside an object stream.\n",
|
|
62
|
+
"syntax": "# `syntax`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nA token or structure did not match the PDF grammar.\n\n## Why\n\nMalformed input.\n\n## How to fix\n\nUse another copy of the file.\n\n## Example\n\n```text\n1 0 obj << /Type /Catalog /Pages 2 0 R >> >> endobj\n```\n\nA stray `>>` where an object should end: parsing that object fails with `syntax` at its offset.\n\n## Data fields\n\nNone. The `location` is a file `offset`, or `object` with `streamOffset` inside an object stream.\n",
|
|
63
|
+
"unsupported-filter": "# `unsupported-filter`\n\n- Type: error\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\nA stream uses a filter Lamina PDF doesn't implement; only decoding that stream fails.\n\n## Why\n\nRare or proprietary filters.\n\n## How to fix\n\nUse a tool that supports the filter.\n\n## Example\n\n```text\n5 0 obj << /Filter /JBIG2Decode /Length 2048 >> stream …\n```\n\nDecoding this stream fails; the rest of the document reads normally. `data.filter` is the filter's name as read from the file.\n\n## Data fields\n\n`filter` (**untrusted**: read from the file). The message never includes it.\n",
|
|
64
|
+
"decompress": "# `decompress`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nCompressed data was corrupt and produced no output.\n\n## Why\n\nDamaged streams.\n\n## How to fix\n\nUse another copy of the file.\n\n## Example\n\n```text\n5 0 obj << /Filter /FlateDecode /Length 40 >> stream (40 bytes that are not zlib data) endstream\n```\n\nInflating fails before producing any output, so the stream has no data.\n\n## Data fields\n\nNone.\n",
|
|
65
|
+
"nested-transaction": "# `nested-transaction`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\nA transaction was started, or undo/redo called, while one is open.\n\n## Why\n\nCalling code opened a second transaction.\n\n## How to fix\n\nCommit or roll back first, or use a savepoint.\n\n## Example\n\n```ts\ndoc.transact((tx) => {\n doc.undo(); // nested-transaction: finish the transaction first\n});\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0006](../decisions/0006-op-log-and-undo.md) §2\n",
|
|
66
|
+
"no-transaction": "# `no-transaction`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\n`apply`, `commit` or `rollback` was called without an open transaction.\n\n## Why\n\nCalling code skipped `begin`.\n\n## How to fix\n\nCall `begin` first, or use `transact`.\n\n## Example\n\n```rust\n// laminapdf_ops::Document, without begin():\nops.commit()?; // no-transaction\n```\n\n## Data fields\n\nNone.\n",
|
|
67
|
+
"unknown-page": "# `unknown-page`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nAn op names a page the document doesn't have.\n\n## Why\n\nThe page ID is stale or from another document.\n\n## How to fix\n\nUse a `PageId` from this document.\n\n## Example\n\n```rust\nlet other = Document::open_bytes(other_bytes, OpenOptions::new())?;\nlet id = other.page_info(0)?.id;\ndoc.apply(vec![Op::from(RotatePage::new(id, Rotation::Clockwise90))]); // unknown-page\n```\n\n## Data fields\n\nNone.\n",
|
|
68
|
+
"direct-page": "# `direct-page`\n\n- Type: error\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\nThe page is a direct dictionary, which ops can't edit yet.\n\n## Why\n\nNon-standard files.\n\n## How to fix\n\nRewrite the file so every page is an indirect object (any full rewrite by another tool does), or wait for Lamina PDF's page-structure ops.\n\n## Example\n\n```text\n2 0 obj << /Type /Pages /Kids [<< /Type /Page /MediaBox [0 0 612 792] >>] /Count 1 >>\n```\n\nThe page is written inline in `/Kids`. Reading works (its ID is a path ID such as `p2.0/Kids/0`), but a `rotatePage` op on it fails with `direct-page`.\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0005](../decisions/0005-document-model.md) §5\n",
|
|
69
|
+
"unsupported-version": "# `unsupported-version`\n\n- Type: error\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\nAn op log has a schema version this reader doesn't know.\n\n## Why\n\nA newer Lamina PDF wrote it.\n\n## How to fix\n\nUpgrade Lamina PDF.\n\n## Example\n\n```rust\ndoc.apply_log(r#\"{ \"v\": 9, \"ops\": [] }\"#)?; // unsupported-version, data.version = 9\n```\n\n## Data fields\n\n`version`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0006](../decisions/0006-op-log-and-undo.md) §6\n",
|
|
70
|
+
"invalid-op-log": "# `invalid-op-log`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nAn op log does not parse: an unknown op or field, a bad value, or not JSON.\n\n## Why\n\nHand-written or corrupted op logs.\n\n## How to fix\n\nValidate the log against the op schema; the samples in `fixtures/ops/v1/` show the format.\n\n## Example\n\n```rust\ndoc.apply_log(r#\"{ \"v\": 1, \"ops\": [{ \"op\": \"rotate\" }] }\"#)?;\n// invalid-op-log: \"rotate\" is not an op (it is \"rotatePage\"); data.detail has serde's message\n```\n\n## Data fields\n\n`detail` (**untrusted**: may quote the log; the message never includes it).\n",
|
|
71
|
+
"saved-not-rebased": "# `saved-not-rebased`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the current state; it may succeed after a state change)\n\n## What happened\n\nThe document was saved to a sink Lamina PDF can't read back, and it waits for `rebase`: ops, undo, redo and saves are refused until then.\n\n## Why\n\n`save_full` to a write-only sink leaves the document `SavedNotRebased`: the bytes it would continue from are not available to it.\n\n## How to fix\n\nCall `rebase` with the saved bytes, then retry.\n\n## Example\n\n```rust\nops.save(&mut write_only_sink, &options)?; // full rewrite; the document waits for rebase\nops.apply(op); // saved-not-rebased\nops.rebase(saved_bytes)?; // then edits work again\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0005](../decisions/0005-document-model.md) §3\n",
|
|
72
|
+
"no-pending-save": "# `no-pending-save`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the current state; it may succeed after a state change)\n\n## What happened\n\n`rebase` was called, but no save is waiting for it.\n\n## Why\n\nRebasing onto other bytes would not match the undo history, and unsaved changes would be lost.\n\n## How to fix\n\nCall `rebase` only after a save that left the document `SavedNotRebased`.\n\n## Example\n\n```rust\ndoc.rebase(bytes)?; // no-pending-save: no save is waiting\n```\n\n## Data fields\n\nNone.\n",
|
|
73
|
+
"sink": "# `sink`\n\n- Type: error\n- Kind: `io` (the host failed to supply or take bytes)\n\n## What happened\n\nThe `WriteSink` failed to take the bytes, or could not give them back. (A sink that already held bytes is [`sink-not-empty`](sink-not-empty.md).)\n\n## Why\n\nThe destination failed: a full disk, a closed upload, a refused read-back.\n\n## How to fix\n\nCheck the destination and retry the save. The document is unchanged; the sink may hold a prefix of the output.\n\n## Example\n\nAn upload sink whose connection closed halfway through: the save fails with `sink`, the document is unchanged, and `data.kind` is the I/O error kind (`BrokenPipe`).\n\n## Data fields\n\n- `kind`: the I/O error kind, for a writer that failed.\n- `message`: the sink's or writer's own text (**untrusted**: it comes from the host and may name a path; never in the message).\n",
|
|
74
|
+
"not-rebased": "# `not-rebased`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the current state; it may succeed after a state change)\n\n## What happened\n\nThe save wrote its complete output, but rebasing the document onto it failed. The document is `SavedNotRebased`.\n\n## Why\n\nReading the output back failed (for example a limit, or a source that can't deliver the bytes yet).\n\n## How to fix\n\nRetry `rebase` with the output the error carries (`saved.source()`; in the `laminapdf` façade, `Error::saved_output()` and `Document::rebase`).\n\n## Example\n\n```rust\nmatch doc.save_with(&options, &cancel) {\n Err(e) if e.code() == \"not-rebased\" => doc.rebase(e.saved_output().unwrap())?,\n other => { other?; }\n}\n```\n\n## Data fields\n\n- `error`: the rebase's own error (its code, kind and location).\n",
|
|
75
|
+
"sink-not-empty": "# `sink-not-empty`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nThe `WriteSink` already held bytes when the save started, so the writer refused before writing anything.\n\n## Why\n\nOutput offsets (the xref table, `startxref`) count from the sink's first byte, and a sink only appends. An `AppendedOnly` incremental save also accepts a sink that holds exactly the base.\n\n## How to fix\n\nPass an empty sink. The document is unchanged.\n\n## Example\n\n```rust\nlet mut sink = b\"%PDF-\".to_vec();\nops.save(&mut sink, &options)?; // sink-not-empty, data.held = 5\n```\n\n## Data fields\n\n- `held`: the bytes the sink held.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0007](../decisions/0007-save-planner.md) §6\n",
|
|
76
|
+
"would-invalidate-signatures": "# `would-invalidate-signatures`\n\n- Type: error (`laminapdf_save::SaveError::WouldInvalidateSignatures`)\n- Kind: `security` (signatures block the call)\n\n## What happened\n\nThe save planner refused a save because it would break one or more signatures. Either it was a full rewrite of a signed document, or its pending changes or rewrite reasons conflict with the document's signatures: a DocMDP, FieldMDP or usage-rights restriction, or a repaired or encrypted base. Nothing was written.\n\n## Why\n\nA signature covers the bytes of the file as it was signed (ISO 32000-2 §12.8). A full rewrite moves those bytes, and a change a certification forbids invalidates it even when appended. Lamina PDF never breaks a signature without an explicit override.\n\n## How to fix\n\nSave with `SaveMode::Auto` or `SaveMode::Incremental` when the constraint allows it. Otherwise ask the user, naming the signatures in the error, and save again with `SaveOptions::with_invalidate_signatures(true)`: the invalidated signatures' values and `/Perms` entries are removed and `SaveReport::invalidated` lists them.\n\n## Example\n\n```rust\nlet err = doc.save(&mut out, &SaveOptions::new().with_mode(SaveMode::FullRewrite)).unwrap_err();\nassert_eq!(err.code(), \"would-invalidate-signatures\");\n```\n\n## Data fields\n\n- `signatures`: every signature, as `{ field, kind, value, revision, covers }` (field ID, kind, signature dictionary as `num gen`, revision, covered length).\n- `rewrite_reasons`: why only a full rewrite is safe, if anything says so.\n- `violations`: pending changes a signature's restriction forbids, as `{ class, restriction, signature }`.\n- `signatures_unknown`: part of the form couldn't be followed, so there may be signatures beyond `signatures`.\n\nNone of them hold document text.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0007](../decisions/0007-save-planner.md) §3\n",
|
|
77
|
+
"requires-rewrite": "# `requires-rewrite`\n\n- Type: error (`laminapdf_save::SaveError::RequiresRewrite`)\n- Kind: `state` (the call isn't valid in the document's current state)\n\n## What happened\n\nAn incremental save was requested, but only a full rewrite is safe for this document. Nothing was written.\n\n## Why\n\nThe save planner found one of four reasons the file must be rewritten:\n\n- a repaired base, whose cross-reference data an appended revision can't build on;\n- an encrypted base being saved without its encryption;\n- a document built from scratch, with no base bytes at all;\n- a pending op, such as a redaction, that removes content an earlier revision would keep.\n\n## How to fix\n\nSave with `SaveMode::Auto` (it resolves to a full rewrite) or `SaveMode::FullRewrite`. If the document is also signed, that needs the explicit override ([`would-invalidate-signatures`](would-invalidate-signatures.md)).\n\n## Example\n\n```rust\nlet err = doc.save(&mut out, &SaveOptions::new().with_mode(SaveMode::Incremental)).unwrap_err();\nassert_eq!(err.code(), \"requires-rewrite\");\n```\n\n## Data fields\n\n- `reasons`: the rewrite reasons (`RepairedBase`, `EncryptedBase`, `NoBase`, `OpRequiresRewrite`).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0007](../decisions/0007-save-planner.md) §2\n",
|
|
78
|
+
"invalid-font": "# `invalid-font`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nFont bytes passed to `Font::from_bytes` or `Font::from_collection` (`laminapdf-text`) could not be read as a font.\n\n## Why\n\nThe bytes are not an OpenType/TrueType font or collection (WOFF/WOFF2 must be decompressed first), or a table every font needs (`head`, `hhea`, `hmtx`, `maxp`) is missing or malformed.\n\n## How to fix\n\nPass the bytes of a `.ttf`, `.otf`, `.ttc` or `.otc` file; check that the file is complete.\n\n## Example\n\n```rust\nlet err = laminapdf::Font::from_bytes(std::fs::read(\"font.woff2\")?).unwrap_err(); // invalid-font\n```\n\n## Data fields\n\n- `index`: the collection member that was asked for (0 for a single font).\n",
|
|
79
|
+
"collection-index": "# `collection-index`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`Font::from_collection` was asked for a member past the end of the font collection.\n\n## Why\n\nMembers are numbered from 0; a single font counts as a collection of one.\n\n## How to fix\n\nUse an index below `Font::collection_len(bytes)`.\n\n## Example\n\n```rust\nlet font = Font::from_collection(ttc_bytes, 4)?; // the collection has 4 members (0..=3): collection-index\n```\n\n## Data fields\n\n- `index`: the member that was asked for.\n- `count`: how many members the collection has.\n",
|
|
80
|
+
"unknown-axis": "# `unknown-axis`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`Font::instance` was given a variation axis the font does not have.\n\n## Why\n\nA static font has no axes; a variable font has the axes listed in `FontInfo::axes` (`fvar`).\n\n## How to fix\n\nCheck `font.info().axes` before choosing coordinates, or use a variable font.\n\n## Example\n\n```rust\nlet bold = static_font.instance(&[(OpenTypeTag::WEIGHT, 700.0)])?; // unknown-axis: no wght axis\n```\n\n## Data fields\n\n- `font`: the font's PostScript name (**untrusted**: read from the font).\n- `tag`: the axis tag that was asked for.\n",
|
|
81
|
+
"font-not-embeddable": "# `font-not-embeddable`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`FontRegistry::add` refused a font whose `OS/2.fsType` forbids embedding (restricted licence, or bitmap embedding only).\n\n## Why\n\nFont vendors mark fonts whose licence does not allow embedding in documents (OpenType `OS/2` table; research 03 §6.3). `fsType` is advisory; the licence is what counts.\n\n## How to fix\n\nUse another font, or, if your licence allows embedding, register it with `EmbedPolicy::Override`.\n\n## Example\n\n```rust\nregistry.add(restricted_font, EmbedPolicy::Respect)?; // font-not-embeddable\nregistry.add(restricted_font, EmbedPolicy::Override)?; // only if your licence allows embedding\n```\n\n## Data fields\n\n- `font`: the font's PostScript name (**untrusted**: read from the font).\n- `fs_type`: the `fsType` bits.\n",
|
|
82
|
+
"missing-glyph": "# `missing-glyph`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nText contained a character the font has no glyph for. Lamina PDF never draws `.notdef` (an empty box) silently.\n\n## Why\n\nThe font's `cmap` does not map the character (for example a CJK character with a Latin font).\n\n## How to fix\n\nUse a font that covers the character (check with `Font::glyph_for`), or split the text across fonts.\n\n## Example\n\n```rust\nregistry.encode(latin_font, \"東京\")?; // missing-glyph: the font has no CJK glyphs\n```\n\n## Data fields\n\n- `ch`: the character (**untrusted**: caller or document text).\n- `font`: the font's PostScript name (**untrusted**: read from the font).\n",
|
|
83
|
+
"glyph-out-of-range": "# `glyph-out-of-range`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`FontRegistry::use_glyph` was given a glyph ID at or past the font's glyph count.\n\n## Why\n\nGlyph IDs must come from the same font (its `cmap` or a shaper run on it).\n\n## How to fix\n\nPass glyph IDs from the font you registered.\n\n## Example\n\n```rust\nregistry.use_glyph(key, GlyphId::new(60_000), \"x\")?; // the font has 1,200 glyphs\n```\n\n## Data fields\n\n- `gid`: the glyph ID.\n- `count`: the font's glyph count.\n- `font`: the font's PostScript name (**untrusted**: read from the font).\n",
|
|
84
|
+
"unknown-font": "# `unknown-font`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA `FontKey` was used with a `FontRegistry` that did not hand it out.\n\n## Why\n\nKeys are indices into one registry; each document has its own.\n\n## How to fix\n\nUse the key returned by this registry's `add`.\n\n## Example\n\n```rust\nlet key = registry_a.add(font, EmbedPolicy::Respect)?;\nregistry_b.encode(key, \"Hi\")?; // unknown-font: key came from registry_a\n```\n\n## Data fields\n\n- `key`: the key's index.\n",
|
|
85
|
+
"subset-failed": "# `subset-failed`\n\n- Type: error\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\nSubsetting a font at `FontRegistry::finish_fonts` failed.\n\n## Why\n\nThe `subsetter` crate could not rebuild the font program: a font feature it does not support, or a table it could not parse.\n\n## How to fix\n\nTry another version or format of the font (for example TrueType instead of CFF), and report the font if it is a common one.\n\n## Example\n\nA font whose tables `subsetter` can't rebuild makes `FontRegistry::finish_fonts` (and so `Builder::finish`) fail with `subset-failed`; `data.font` is the font's PostScript name.\n\n## Data fields\n\n- `font`: the font's PostScript name (**untrusted**: read from the font).\n- `reason`: the subsetter's failure class (fixed text).\n",
|
|
86
|
+
"invalid-axis-value": "# `invalid-axis-value`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`Font::instance` was given a NaN or infinite axis value.\n\n## Why\n\nVariation coordinates are user-space numbers such as `wght` 700; NaN and infinities have no position on an axis.\n\n## How to fix\n\nPass finite values; values outside the axis range are clamped, so there is no need for infinities.\n\n## Example\n\n```rust\nfont.instance(&[(OpenTypeTag::WEIGHT, f32::NAN)])?; // invalid-axis-value\n```\n\n## Data fields\n\n- `tag`: the axis.\n",
|
|
87
|
+
"wrong-document": "# `wrong-document`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`FontRegistry::finish_fonts` was given a document in which a font's reserved object is not a free number reserved in that document.\n\n## Why\n\nA `FontRegistry` belongs to one document: `reference` reserves numbers in the document it is given, and `finish_fonts` fills those numbers. Finishing into another document would overwrite its objects, so nothing is written.\n\n## How to fix\n\nUse one `FontRegistry` per document, and pass `finish_fonts` the document its `reference` calls used.\n\n## Example\n\n```rust\nlet id = registry.reference(key, &doc_a)?;\nregistry.finish_fonts(&mut doc_b.edit())?; // wrong-document: the fonts were reserved in doc_a\n```\n\n## Data fields\n\n- `object`: the reserved object number.\n",
|
|
88
|
+
"invalid-feature": "# `invalid-feature`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nAn OpenType feature setting given to the shaper (`\"liga=0\".parse::<Feature>()`) did not parse.\n\n## Why\n\nFeature settings use HarfBuzz's syntax without ranges: `tag` or `+tag` (on), `-tag` (off), `tag=N`, `tag=on` or `tag=off`, where the tag is one to four printable ASCII characters. A sign together with a value (`+liga=1`), an empty or longer tag, or a value that is not a non-negative integer is refused.\n\n## How to fix\n\nWrite the setting as `liga=0`, `-liga`, `smcp` or `aalt=2`, or build it with `Feature::new(OpenTypeTag::new(b\"liga\"), 0)`.\n\n## Example\n\n```rust\nlet f: Feature = \"liga[3:5]=0\".parse()?; // invalid-feature: ranges are not supported\n```\n\n## Data fields\n\n- `feature`: the setting as given (**untrusted**: caller text).\n",
|
|
89
|
+
"invalid-text-range": "# `invalid-text-range`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA byte range of a text given to `Shaper::shape_range`, `BidiParagraph::visual_runs` or `BidiParagraph::line_levels`, or the range of a `ShapedRun` given to `pdf_run`, is past the end of the text or does not start and end on character boundaries.\n\n## Why\n\nRanges are byte offsets into UTF-8 text; a range that cuts a character in two (or reaches past the end) names no text.\n\n## How to fix\n\nTake ranges from Lamina PDF's own results (`line_breaks`, `itemize`, `break_lines`, `ShapedRun::text_range`), or check them with `str::get` first, and pass the same text the range was computed on.\n\n## Example\n\n```rust\nshaper.shape_range(&font, \"héllo\", 0..2, &options)?; // byte 2 is inside 'é'\n```\n\n## Data fields\n\n- `start`, `end`: the range (bytes).\n- `len`: the text's length (bytes).\n",
|
|
90
|
+
"unsupported-layout-option": "# `unsupported-layout-option`\n\n- Type: error\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\n`break_lines` was given a layout option Lamina PDF does not implement yet: `LayoutOptions::justify` set to `true`.\n\n## Why\n\nJustification (stretching inter-word gaps, kashida) is reserved for a later plan (research 03 §7.6); silently ignoring the option would lay out ragged lines the caller did not ask for.\n\n## How to fix\n\nLeave `justify` at `false` and pick an `Alignment` (start, end, left, right or centre).\n\n## Example\n\n```rust\nlet options = LayoutOptions { justify: true, ..LayoutOptions::new(300.0, 12.0) };\nbreak_lines(&shaper, &fonts, text, &options)?; // unsupported-layout-option\n```\n\n## Data fields\n\n- `option`: the option's name (fixed text).\n",
|
|
91
|
+
"font-mismatch": "# `font-mismatch`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`pdf_run` was given a shaped run and the key of a font other than the one the run was shaped with. Nothing was recorded in the registry.\n\n## Why\n\nGlyph IDs, advances and units per em only mean something in the font that produced them; drawing them with another font would show the wrong glyphs and wrong text.\n\n## How to fix\n\nRegister every font of a layout and pass the key of the run's own font: each `LineRun::font` is an index into the font list given to `break_lines`.\n\n## Example\n\n```rust\nlet run = shaper.shape(¬o, text, &options)?;\npdf_run(&run, text, &mut registry, roboto_key)?; // font-mismatch: shaped with Noto\n```\n\n## Data fields\n\n- `font`: the registered font's PostScript name (**untrusted**: read from the font).\n- `shaped_with`: the PostScript name of the font the run was shaped with (**untrusted**: read from the font).\n",
|
|
92
|
+
"encoding-full": "# `encoding-full`\n\n- Type: error\n- Kind: `limit` (a `Limits` value or another resource bound was hit)\n\n## What happened\n\nA standard 14 font had no free one-byte code left for another glyph in this document.\n\n## Why\n\nA standard 14 font is a simple font: text is written one byte per character, so one font resource addresses at most 256 glyphs (ISO 32000-2 §9.6.5). Lamina PDF writes the Latin fonts in `WinAnsiEncoding`, and gives each glyph of the standard Latin character set outside it (`Lslash`, `Scedilla`, `minus`, …) one of the 37 codes `WinAnsiEncoding` leaves undefined, through `/Differences`.\n\nA document that draws more such glyphs with one font than there are free codes runs out. Assignment is all or nothing: when one `encode` call needs more new codes than are free, none are assigned and nothing of the text is recorded.\n\n## How to fix\n\nEmbed a font (`Font::from_bytes`), which addresses every glyph through a Type 0 font, or split the text across two registrations of the standard font (each is its own font resource with its own codes).\n\n## Example\n\nDrawing more than 37 different non-WinAnsi Latin glyphs (Ā, ă, Ą, …) with `Standard14::Helvetica` in one document: the 38th has no free code in the font's `/Differences`, so the call fails with `encoding-full`.\n\n## Data fields\n\n- `font`: the standard font's PostScript name (fixed text: one of the fourteen).\n- `limit`: how many codes `/Differences` can assign for this font.\n- `free`: how many of them were still free; the text needed more, so none were assigned.\n",
|
|
93
|
+
"font-not-shapable": "# `font-not-shapable`\n\n- Type: error\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\nA standard 14 font (`Font::standard`) was given to the shaper, `itemize`, `break_lines` or `pdf_run`.\n\n## Why\n\nThe standard 14 fonts are referred to by name and never embedded. Lamina PDF has their metrics (Adobe's AFM files) but no font program, so there is nothing for HarfRust to shape with and no `cmap` for itemizing. Nor are there CIDs for a shaped run: a standard 14 font is written as a simple font with one-byte codes.\n\n## How to fix\n\nDraw standard 14 text with `FontRegistry::encode` (one glyph per character, kerning not applied), or load a real font with `Font::from_bytes` and shape with it.\n\n## Example\n\n```rust\nlet helvetica = Font::standard(Standard14::Helvetica);\nshaper.shape(&helvetica, \"Hello\", &options)?; // font-not-shapable\n```\n\n## Data fields\n\n- `font`: the standard font's PostScript name (fixed text: one of the fourteen).\n",
|
|
94
|
+
"tounicode-conflict": "# `tounicode-conflict`\n\n- Type: warning\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nOne glyph was used for two different texts; the ToUnicode CMap maps it to the first.\n\n## Why\n\nA ToUnicode CMap maps each code to one string (ISO 32000-2 §9.10.3), for example when one space glyph stands for both U+0020 and U+00A0.\n\n## How to fix\n\nMark the second occurrence with `/ActualText` (§14.9.4) so extraction gets its text; Lamina PDF's builder does this for you.\n\n## Example\n\nA CFF font in which one space glyph is used for both U+0020 and U+00A0: the ToUnicode CMap maps it to U+0020 (the first use), and the warning names the glyph (`gid`) and the second text.\n\n## Data fields\n\n- `font`: the font's PostScript name (**untrusted**: read from the font).\n- `gid`: the glyph, in the original font's numbering.\n- `kept`, `dropped`: the two texts (**untrusted**: caller text).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [B5b](../todos/done/B5b-builder-text-and-document-features.md): the builder marks repeated glyphs\n",
|
|
95
|
+
"whole-font-rebuilt": "# `whole-font-rebuilt`\n\n- Type: warning\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA font whose `OS/2.fsType` forbids subsetting was embedded with every glyph, but rebuilt rather than byte for byte.\n\n## Why\n\nIt is a variable-font instance or has CFF2 outlines; PDF can embed neither as it is, so `subsetter` instanced it (or converted CFF2 to TrueType outlines) while keeping every glyph. Its `cmap` and layout tables are dropped.\n\n## How to fix\n\nCheck the font's licence allows this, or use a static TrueType/CFF version of the font.\n\n## Example\n\nRegistering a variable font instance (`font.instance(&[(OpenTypeTag::WEIGHT, 700.0)])`) whose `fsType` forbids subsetting: the font is embedded with every glyph, but instanced, so not byte for byte.\n\n## Data fields\n\n- `font`: the font's PostScript name (**untrusted**: read from the font).\n",
|
|
96
|
+
"invalid-image": "# `invalid-image`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\n`Image::jpeg`, `Image::png` or `Image::png_with` (`laminapdf-build`) could not read the bytes as an image of that format.\n\n## Why\n\nA JPEG must start with a start-of-image marker and have a frame header (SOF) before its first scan, with every marker segment inside the data; a PNG must decode without errors (signature, chunk CRCs, zlib data, sizes).\n\n## How to fix\n\nPass the complete file. Check that the bytes are the format you called (a PNG passed to `Image::jpeg` fails here).\n\n## Example\n\n```rust\nlet img = Image::jpeg(std::fs::read(\"photo.png\")?)?; // invalid-image: PNG bytes given as JPEG\n```\n\n## Data fields\n\n- `format`: `Jpeg`, `Png` or `Rgba`.\n- `reason`: what was wrong (fixed text).\n",
|
|
97
|
+
"unsupported-image": "# `unsupported-image`\n\n- Type: error\n- Kind: `unsupported` (valid input that Lamina PDF doesn't implement)\n\n## What happened\n\nThe image is valid but uses a feature the builder can't embed.\n\n## Why\n\nPDF's `DCTDecode` filter (ISO 32000-2 §7.4.8) takes baseline and progressive JPEG with 8-bit samples; lossless, hierarchical and arithmetic-coded JPEG and 12-bit samples are refused, as are JPEGs with 2 or more than 4 components.\n\n## How to fix\n\nRe-encode the image as a baseline or progressive 8-bit JPEG, or as PNG.\n\n## Example\n\n```rust\nlet img = Image::jpeg(lossless_jpeg_bytes)?; // unsupported-image: data.feature says which\n```\n\n## Data fields\n\n- `format`: the image's format.\n- `feature`: the feature that is not supported (fixed text).\n",
|
|
98
|
+
"image-too-large": "# `image-too-large`\n\n- Type: error\n- Kind: `limit` (a `Limits` value or another resource bound was hit)\n\n## What happened\n\nDecoding a PNG would need more than `MAX_DECODED_IMAGE_BYTES` (256 MiB), or the dimensions given to `Image::rgba` describe more bytes than fit in memory's address space.\n\n## Why\n\nThe builder decodes PNGs to split colour and alpha; a bound on the decoded size keeps a small file with huge dimensions from exhausting memory.\n\n## How to fix\n\nScale the image down before adding it, or use a JPEG, which is embedded without decoding.\n\n## Example\n\n```rust\nlet img = Image::png(&png_of_30000_by_30000_pixels)?; // image-too-large: over 256 MiB decoded\n```\n\n## Data fields\n\n- `limit`: the limit in decoded bytes.\n",
|
|
99
|
+
"image-data-length": "# `image-data-length`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`Image::rgba(width, height, bytes)` was given a number of bytes other than `width × height × 4`.\n\n## Why\n\nRaw RGBA samples are 4 bytes per pixel, row by row from the top, with no padding.\n\n## How to fix\n\nPass exactly 4 bytes per pixel.\n\n## Example\n\n```rust\nlet img = Image::rgba(2, 2, &[0; 12])?; // image-data-length: 16 bytes expected\n```\n\n## Data fields\n\n- `expected`: the length the dimensions call for.\n- `actual`: the length given.\n",
|
|
100
|
+
"invalid-icc-profile": "# `invalid-icc-profile`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\n`IccProfile::new` (`laminapdf-build`) rejected the bytes.\n\n## Why\n\nAn ICC profile (ICC.1:2010) starts with a 128-byte header holding its size, the `acsp` signature, its class and its data colour space. PDF's `ICCBased` colour spaces (ISO 32000-2 §8.6.5.5) take Gray, RGB, CMYK and Lab profiles. The builder takes Gray, RGB and CMYK: Lab components fall outside `[0, 1]` and need a `/Range`, which it doesn't support yet. Device-link, abstract and named-colour profiles can't be used.\n\n## How to fix\n\nPass the complete bytes of an input, display, output or colour-space profile with a Gray, RGB or CMYK data colour space.\n\n## Example\n\n```rust\nlet profile = IccProfile::new(b\"not a profile\".to_vec())?; // invalid-icc-profile\n```\n\n## Data fields\n\n- `reason`: what was wrong (fixed text).\n",
|
|
101
|
+
"color-components": "# `color-components`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`Color::icc` was given a number of components that differs from the profile's channel count.\n\n## Why\n\nAn `ICCBased` colour has one component per channel of its profile (`/N`: Gray 1, RGB and Lab 3, CMYK 4).\n\n## How to fix\n\nGive `IccProfile::components()` values.\n\n## Example\n\n```rust\nlet c = Color::icc(&cmyk_profile, &[0.1, 0.2, 0.3])?; // color-components: 4 expected\n```\n\n## Data fields\n\n- `expected`: the colour space's number of components.\n- `actual`: how many were given.\n",
|
|
102
|
+
"invalid-color": "# `invalid-color`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`Color::hex` was given a string that is not `#rgb` or `#rrggbb`.\n\n## Why\n\nHex colours are a `#` followed by three or six hexadecimal digits, in either case.\n\n## How to fix\n\nWrite the colour as `#3366cc` or `#36c`, or use `Color::rgb`.\n\n## Example\n\n```rust\nlet c = Color::hex(\"#12345\")?; // invalid-color: 5 digits\n```\n\n## Data fields\n\n- `text`: the string as given (untrusted: caller text, never in the message).\n",
|
|
103
|
+
"invalid-geometry": "# `invalid-geometry`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA page size, page box, `/UserUnit`, drawing value or image size given to the builder is not usable. Drawing values are checked as the `Surface` receives them and the first bad one is reported by `PageBuilder::finish`.\n\n## Why\n\nPage sizes and `/UserUnit` must be finite and positive, and page boxes, path coordinates, matrices and image rectangles finite. Line widths and dash lengths must be finite and not negative, and a dash pattern not all zeros (ISO 32000-2 §8.4.3.6). Miter limits must be at least 1, images at least one pixel wide and tall, font sizes and line heights positive, text boxes wider than 0, and destination coordinates finite.\n\n## How to fix\n\nCheck the values passed to `Builder::page`, `PageBuilder::user_unit`, the box setters, the `Surface` (paths, `transform`, `StrokeStyle`, `image`, `text` and `text_box` sizes, positions, boxes and line heights, `link` rectangles), destinations (`Fit` coordinates) and `Image::rgba`.\n\n## Example\n\n```rust\nlet page = builder.page(PageSize::new(Pt::new(0.0), Pt::new(842.0))); // invalid-geometry at finish\n```\n\n## Data fields\n\n- `what`: which value was invalid (fixed text).\n",
|
|
104
|
+
"text-overflow": "# `text-overflow`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`Surface::text_box` laid its text out and not every line fit the box (its height, or a single grapheme cluster wider than the box), and its `TextBoxOptions` asked for `Overflow::Error`. Nothing was drawn.\n\n## Why\n\nA text box draws only lines that fit inside it. With `Overflow::Error` the caller asked to be told instead of having the text cut off.\n\n## How to fix\n\nMake the box taller or wider, the font smaller or the text shorter; or use `Overflow::Clip` (the default), which draws the lines that fit and reports where the rest starts in `TextLayout::overflow`.\n\n## Example\n\n```rust\nlet options = TextBoxOptions::default().with_overflow(Overflow::Error);\nsurface.text_box(long_text, &font, Pt::new(12.0), small_rect, &options)?; // text-overflow\n```\n\n## Data fields\n\n- `fits`: the number of lines that fit.\n- `needed`: the number of lines the text needs.\n",
|
|
105
|
+
"page-index-out-of-range": "# `page-index-out-of-range`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA link (`LinkTarget::Page`), an outline item or a named destination (`Builder::destination`) names a page index the document doesn't have when the build finishes.\n\n## Why\n\nPage targets are resolved when `Builder::finish` (or `Appender::finish`) runs, so a link can name a page added after its own; by then every index must be one of the document's pages. Indexes are 0-based.\n\n## How to fix\n\nUse an index below the document's page count, or add the page before finishing.\n\n## Example\n\n```rust\nsurface.link(rect, LinkTarget::Page { index: 5, fit: Fit::Page }, None);\nbuilder.finish()?; // page-index-out-of-range: the document has 2 pages\n```\n\n## Data fields\n\n- `index`: the 0-based index given.\n- `count`: the number of pages.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0027](../decisions/0027-coordinates-units-and-page-indexing.md) §6\n",
|
|
106
|
+
"unknown-destination": "# `unknown-destination`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA link or outline item of a new document targets `LinkTarget::Named(name)`, but `Builder::destination` never defined that name.\n\n## Why\n\nA named destination is looked up in the document's `/Names /Dests` tree (ISO 32000-2 §12.3.2.4); a name that isn't there goes nowhere in every viewer.\n\n## How to fix\n\nDefine the name with `Builder::destination(name, page_index, fit)` before `Builder::finish`, or link to the page directly with `LinkTarget::Page`.\n\n## Example\n\n```rust\nsurface.link(rect, LinkTarget::Named(\"appendix\".into()), None);\nbuilder.finish()?; // unknown-destination: call builder.destination(\"appendix\", …) first\n```\n\n## Data fields\n\n- `name`: the name as given (untrusted: caller text, never in the message).\n",
|
|
107
|
+
"invalid-page-labels": "# `invalid-page-labels`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nThe ranges given to `Builder::page_labels` can't be written as a `/PageLabels` number tree.\n\n## Why\n\nISO 32000-2 §7.7.2 and §12.4.2: the tree must have an entry for page index 0, its keys are page indexes in increasing order, and a label range's first number (`/St`) is at least 1. A range starting past the document's last page would label nothing.\n\n## How to fix\n\nGive the ranges sorted by first page, the first starting at page 0, each start a page of the document, and numbers from 1 (`PageLabel::with_first`).\n\n## Example\n\n```rust\nbuilder.page_labels([PageLabel::new(1, Some(LabelStyle::Decimal))]); // invalid-page-labels at finish: no range starts at page 0\n```\n\n## Data fields\n\n- `what`: what is wrong (fixed text).\n",
|
|
108
|
+
"profile-version-conflict": "# `profile-version-conflict`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`BuildOptions::with_version(PdfVersion::V2_0)` was combined with a conformance profile (`BuildOptions::with_profile`) that is built on PDF 1.7. `Builder::finish` wrote nothing.\n\n## Why\n\nThe PDF/UA-1 standard for accessible PDF (ISO 14289-1) and the PDF/A-2 and PDF/A-3 archival standards (ISO 19005-2 and -3) build their rules on ISO 32000-1, which is PDF 1.7. A file that says it is PDF 2.0 can't conform to them. Validators such as veraPDF, the open-source PDF/A and PDF/UA validator, check the header first.\n\n## How to fix\n\nLeave the version at its default, 1.7 (or choose a lower one: the builder raises it to what the content needs). PDF 2.0 has its own profiles, PDF/A-4 and PDF/UA-2, which Lamina PDF doesn't write yet.\n\n## Example\n\n```rust\nlet options = BuildOptions::default()\n .with_version(PdfVersion::V2_0)\n .with_profile(Profile::PdfA2b);\nBuilder::new(options).finish()?; // profile-version-conflict\n```\n\n## Data fields\n\n- `profile`: the profile, such as `PDF/A-2b`.\n",
|
|
109
|
+
"missing-title": "# `missing-title`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA PDF/UA-1 document (`Profile::PdfUa1`) reached `Builder::finish` without a title: `Builder::metadata` was never called, or its `Metadata` has no title, or the title is blank.\n\n## Why\n\nA screen reader announces the title first, so a document without one is hard to tell apart from others. The PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.1) requires a title in the document's metadata. The title is `dc:title` in the XMP packet, the XML metadata stream that the builder writes from `Metadata`. The standard also asks viewers to show the title in the title bar instead of the file name (`/DisplayDocTitle`, which the builder writes for you). This is condition 06-003 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures.\n\n## How to fix\n\nGive the document a title that says what it is.\n\n## Example\n\n```rust\nbuilder.metadata(Metadata::default().with_title(\"Quarterly report, Q3 2026\"));\n```\n\n## Data fields\n\nNone.\n",
|
|
110
|
+
"missing-lang": "# `missing-lang`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA PDF/UA-1 document (`Profile::PdfUa1`) reached `Builder::finish` without a natural language: `Builder::lang` was never called, or was given a blank tag.\n\n## Why\n\nA screen reader picks its voice and pronunciation from the document's language (`/Lang` in the catalog, ISO 32000-2 §14.9.2). The PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.2) requires that the language of all text can be determined. These are conditions 11-001 and 11-006 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures.\n\n## How to fix\n\nSet the document's language with a BCP 47 tag, the standard language code of the web, such as `en-GB` or `fr`. Text in another language can carry its own tag with `Tag::with_lang`.\n\n## Example\n\n```rust\nbuilder.lang(\"en-GB\");\n```\n\n## Data fields\n\nNone.\n",
|
|
111
|
+
"missing-alt-text": "# `missing-alt-text`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nUnder PDF/UA-1, a page has a `Figure` or `Formula` element without alternate text: neither `/Alt` (`Tag::with_alt`) nor `/ActualText` (`Tag::with_actual_text`).\n\n## Why\n\nA screen reader can't describe a picture or a formula by itself. The PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.3) requires every `Figure` and every `Formula` to carry a text alternative. These are conditions 13-004 and 17-002 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures. Lamina PDF checks only that a description exists; whether it describes the figure well is for a person to judge.\n\n## How to fix\n\nDescribe the figure with `Surface::figure(&image, rect, alt)` or `Tag::new(TagKind::Figure).with_alt(…)`. If the picture is decoration that carries no meaning, draw it inside `Surface::artifact` instead.\n\n## Example\n\n```rust\nsurface.tag(TagKind::Figure, |s| { s.image(&chart, rect); Ok(()) })?;\nbuilder.finish()?; // missing-alt-text: use Tag::new(TagKind::Figure).with_alt(\"Sales by month\")\n```\n\n## Data fields\n\n- `page`: the page's index (0-based).\n",
|
|
112
|
+
"untagged-content": "# `untagged-content`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nUnder PDF/UA-1, something was drawn outside every structure element and artifact: a path, an image or text that is neither part of the document's structure nor marked as decoration.\n\n## Why\n\nAssistive technology reads a tagged document through its structure tree, and skips content outside it or reads it out of order. For this reason, the PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.1) requires every piece of content to be tagged or marked as an artifact. This is condition 01-005 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures.\n\n## How to fix\n\nDraw real content inside `Surface::tag` (or give a text run `TextRun::tag`) and decoration, such as rules, backgrounds and page numbers, inside `Surface::artifact`. `Builder::tagging_diagnostics` lists what is still untagged on each page.\n\n## Example\n\n```rust\nsurface.path(|p| { p.rect(rule); }).fill(FillRule::NonZero);\nbuilder.finish()?; // untagged-content: wrap it in surface.artifact(ArtifactKind::Layout, …)\n```\n\n## Data fields\n\n- `page`: the page's index (0-based).\n- `content`: what was drawn: `path`, `image` or `text`.\n- `count`: how many drawings of that kind.\n",
|
|
113
|
+
"heading-level-skipped": "# `heading-level-skipped`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nUnder PDF/UA-1, the document's headings skip a level. Numbered headings are `H1`, `H2` and so on, as opposed to the unnumbered `H`. Either the first numbered heading is not `H1`, or a heading is more than one level below the heading before it (an `H3` straight after an `H1`).\n\n## Why\n\nScreen-reader users move through a document by its headings, and a skipped level suggests a missing section. The PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.4.2) requires numbered headings to start at `H1` and to go down one level at a time. These are conditions 14-002 and 14-003 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures. Going back up any number of levels is fine.\n\n## How to fix\n\nUse the next level down for a subsection (`H2` under `H1`, `H3` under `H2`). If a heading should only look smaller, change its font size, not its level.\n\n## Example\n\n```rust\ns.text(\"Results\", &font, Pt::new(18.0), at)?.tag(TagKind::H1);\ns.text(\"Method\", &font, Pt::new(12.0), at2)?.tag(TagKind::H3);\nbuilder.finish()?; // heading-level-skipped: the second heading should be an H2\n```\n\n## Data fields\n\n- `page`: the page's index (0-based).\n- `level`: the heading's level.\n- `previous`: the level of the heading before it (0 when there is none).\n",
|
|
114
|
+
"unnumbered-heading-misused": "# `unnumbered-heading-misused`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nUnder PDF/UA-1, the document misuses the unnumbered heading `H`, a heading whose level comes from the section it sits in. Numbered headings are `H1`, `H2` and so on. Either two `H` elements share one parent, or the document uses `H` and numbered headings together.\n\n## Why\n\nA screen reader works out an `H` heading's level from the sections around it. A section therefore has one `H` at most, and a document that mixes the two kinds gives no consistent outline. The PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.4.4) requires one `H` per section and only one kind of heading per document. These are conditions 14-006 and 14-007 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures.\n\n## How to fix\n\nUse numbered headings (`TagKind::H1` to `TagKind::H6`) throughout; most documents need nothing else. If you use `H` (`Tag::custom(\"H\", …)`), give each section (`Sect`) its own and leave the numbered ones out.\n\n## Example\n\n```rust\ns.text(\"Results\", &font, Pt::new(18.0), at)?.tag(TagKind::H1);\ns.text(\"Method\", &font, Pt::new(14.0), at2)?.tag(Tag::custom(\"H\", TagKind::H2));\nbuilder.finish()?; // unnumbered-heading-misused: use TagKind::H2 for the second heading\n```\n\n## Data fields\n\n- `page`: the page's index (0-based).\n",
|
|
115
|
+
"invalid-structure-nesting": "# `invalid-structure-nesting`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nUnder PDF/UA-1, a table or list element sits in a parent its type doesn't allow. A table (`Table`) holds rows (`TR`), each holding header cells (`TH`) and data cells (`TD`); a list (`L`) holds list items (`LI`), each holding a label (`Lbl`, its bullet or number) and a body (`LBody`). Examples of a wrong parent: a `TD` or `TH` outside a `TR`, a `TR` outside a `Table`, an `LI` outside an `L`, or a paragraph directly inside a `Table`, `TR`, `L` or `LI`.\n\n## Why\n\nISO 32000-1 Tables 336 and 337 define how tables and lists are built, and assistive technology relies on that shape to announce rows, columns and list items. The PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.2) requires tables and lists to have that shape. These are conditions 09-004 and 09-005 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures.\n\n## How to fix\n\nNest tables as `Table` > `TR` > `TH` or `TD` (optionally with `THead`, `TBody` and `TFoot` around the rows, and a `Caption`), and lists as `L` > `LI` > `Lbl` and `LBody`. Put paragraphs and other content inside the cells and list bodies.\n\n## Example\n\n```rust\ns.tag(TagKind::Table, |s| {\n s.text(\"Total\", &font, Pt::new(11.0), at)?.tag(TagKind::TD); // a TD straight in the Table\n Ok(())\n})?;\nbuilder.finish()?; // invalid-structure-nesting: wrap the cell in a TR\n```\n\n## Data fields\n\n- `page`: the page's index (0-based).\n- `kind`: the element's standard type, such as `TD`.\n- `parent`: its parent's standard type (`Document` at the top level).\n",
|
|
116
|
+
"link-without-structure": "# `link-without-structure`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nUnder PDF/UA-1, a link annotation has no `Link` structure element. The builder gives every link one automatically, except a link drawn inside an artifact, which stays outside the structure.\n\n## Why\n\nA screen reader finds links through the structure tree, so a link inside decoration can't be reached. The PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.18.5) requires each link annotation to be nested in a `Link` element. This is condition 28-011 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures.\n\n## How to fix\n\nDraw the link outside `Surface::artifact`, next to the text it belongs to; the builder then wraps it in a `Link` element.\n\n## Example\n\n```rust\nsurface.artifact(ArtifactKind::Layout, |s| {\n s.link(rect, LinkTarget::Uri(\"https://example.com\".into()), Some(\"Example\".into()));\n Ok(())\n})?;\nbuilder.finish()?; // link-without-structure\n```\n\n## Data fields\n\n- `page`: the page's index (0-based).\n- `count`: how many links on that page lack a `Link` element.\n",
|
|
117
|
+
"link-without-contents": "# `link-without-contents`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nUnder PDF/UA-1, a link annotation has no description: `Surface::link` or `TextRun::link` was called with `None` (or an empty string) as its contents.\n\n## Why\n\nA screen reader announces a link by its `/Contents`, its alternate description. The PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.18.5) requires one on every link. This is condition 28-012 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures.\n\n## How to fix\n\nPass a short description of where the link goes as the link's contents.\n\n## Example\n\n```rust\nsurface.link(rect, LinkTarget::Uri(\"https://example.com/pay\".into()), None);\nbuilder.finish()?; // link-without-contents: pass Some(\"Pay the invoice online\".into())\n```\n\n## Data fields\n\n- `page`: the page's index (0-based).\n",
|
|
118
|
+
"font-not-embedded": "# `font-not-embedded`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nText was drawn with one of the standard 14 fonts (`Builder::standard_font`) under PDF/UA-1 or a PDF/A profile. The standard 14 fonts are never embedded: viewers substitute their own.\n\n## Why\n\nThe PDF/A standard for archival PDF (ISO 19005-2 §6.2.11.4.1) needs every font embedded, so the document looks the same everywhere in the future. The PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.21.4.1) needs it too, so viewers can render the text and map it to characters reliably. This is condition 31-009 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures.\n\n## How to fix\n\nLoad a font file with `Builder::font` (or `Builder::font_bytes` in the façade): Lamina PDF embeds the subset of glyphs you draw.\n\n## Example\n\n```rust\nlet bold = builder.standard_font(Standard14::HelveticaBold)?;\nsurface.text(\"Invoice\", &bold, Pt::new(24.0), at)?;\nbuilder.finish()?; // font-not-embedded under Profile::PdfA2b\n```\n\n## Data fields\n\n- `profile`: the profile, such as `PDF/A-2b`.\n- `font`: the font's PostScript name, such as `Helvetica-Bold`.\n",
|
|
119
|
+
"glyph-without-unicode": "# `glyph-without-unicode`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nUnder PDF/A-2u or PDF/UA-1, a glyph was drawn that stands for no text of its own, or whose text is U+0000, U+FEFF or U+FFFE. Shaping can draw one cluster (a group of characters that display as one unit) with several glyphs. The text then belongs to the first glyph, and the others stand for none.\n\n## Why\n\nSoftware can search, copy or read text aloud only when every glyph maps to a usable Unicode value. The PDF/A-2u archival standard (ISO 19005-2 §6.2.11.7.2) and the PDF/UA-1 standard for accessible PDF (ISO 14289-1 §7.21.7) both need that mapping. These are conditions 31-027 to 31-029 of the Matterhorn Protocol, the PDF Association's checklist of PDF/UA failures. Lamina PDF maps a glyph that stands for no text to an empty value, which these profiles reject.\n\n## How to fix\n\nCheck the text for control characters and byte-order marks. If shaping draws several glyphs for one cluster, choose a font that draws each cluster as one glyph. An archive that needs no accessibility can use PDF/A-2b instead, which doesn't need Unicode for every glyph. PDF/UA-1 has no such relaxed variant, so a PDF/UA-1 document needs the font change.\n\n## Example\n\n```rust\nsurface.text(\"\\u{FEFF}Report\", &font, Pt::new(12.0), at)?;\nbuilder.finish()?; // glyph-without-unicode under Profile::PdfA2u\n```\n\n## Data fields\n\n- `profile`: the profile, `PDF/A-2u` or `PDF/UA-1`.\n- `font`: the font's PostScript name (untrusted: read from the font file).\n- `gid`: the glyph's ID in the font.\n",
|
|
120
|
+
"device-color-without-output-intent": "# `device-color-without-output-intent`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nUnder a PDF/A profile, a page draws with `DeviceCMYK` (CMYK given as plain numbers, with no profile to say which inks they mean): a `Color::cmyk` colour or a CMYK JPEG.\n\n## Why\n\nThe PDF/A standard for archival PDF (ISO 19005) gives device colour a defined meaning through the document's output intent, which Lamina PDF writes as sRGB. That sRGB intent defines device RGB and gray, but not device CMYK, so ISO 19005-2 §6.2.4.3 forbids CMYK under it.\n\n## How to fix\n\nUse `Color::rgb` or `Color::gray`, convert CMYK images to RGB, or give CMYK colours an ICC profile with `Color::icc` (an ICC-based colour carries its own meaning).\n\n## Example\n\n```rust\nsurface.fill_color(Color::cmyk(0.0, 0.5, 1.0, 0.0));\nbuilder.finish()?; // device-color-without-output-intent under Profile::PdfA2b\n```\n\n## Data fields\n\n- `page`: the index (0-based) of the first page that uses `DeviceCMYK`.\n",
|
|
121
|
+
"embedded-file-forbidden": "# `embedded-file-forbidden`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`Builder::attach` added a file to a document built under PDF/A-2b or PDF/A-2u.\n\n## Why\n\nThe PDF/A-2 standard for archival PDF (ISO 19005-2 §6.8) allows only attached files that are themselves PDF/A. Lamina PDF can't check what an attachment is, so it rejects every attachment under PDF/A-2 on purpose, PDF/A ones included. The PDF/A-3 standard for archival PDF (ISO 19005-3) allows files of any kind, provided they are associated with the document, which `Builder::attach` does.\n\n## How to fix\n\nChoose `Profile::PdfA3b` for documents that carry files, such as an e-invoice's XML, or leave the attachment out.\n\n## Example\n\n```rust\nlet options = BuildOptions::default().with_profile(Profile::PdfA2b);\nlet mut builder = Builder::new(options);\nbuilder.attach(Attachment::new(\"data.csv\", csv, \"text/csv\"));\nbuilder.finish()?; // embedded-file-forbidden: use Profile::PdfA3b\n```\n\n## Data fields\n\n- `profile`: the profile, such as `PDF/A-2b`.\n",
|
|
122
|
+
"embedded-file-without-mime-type": "# `embedded-file-without-mime-type`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nUnder a PDF/A profile, `Builder::attach` added a file with an empty MIME type. A MIME type names the file's format, such as `text/xml`.\n\n## Why\n\nSoftware reading an archive years later needs to know what each attached file is. The PDF/A-3 standard for archival PDF (ISO 19005-3 §6.8) requires every embedded file to name its MIME type in the `/Subtype` of its stream, and Lamina PDF writes no `/Subtype` for an empty one.\n\n## How to fix\n\nPass the file's MIME type to `Attachment::new`. For bytes of no particular type, use `application/octet-stream`.\n\n## Example\n\n```rust\nlet options = BuildOptions::default().with_profile(Profile::PdfA3b);\nlet mut builder = Builder::new(options);\nbuilder.attach(Attachment::new(\"invoice.xml\", xml, \"\"));\nbuilder.finish()?; // embedded-file-without-mime-type: pass \"text/xml\"\n```\n\n## Data fields\n\n- `profile`: the profile, such as `PDF/A-3b`.\n- `name`: the file's name (untrusted: the caller's text).\n",
|
|
123
|
+
"render-too-large": "# `render-too-large`\n\n- Type: error\n- Kind: `limit` (a configured limit was exceeded)\n\n## What happened\n\nA render backend (`laminapdf-render`) refused a request that is too large. The tile has more than `max_render_pixels` pixels or more than `max_render_side` pixels on a side (the document's `RenderLimits`), or more than 65,535 pixels on a side for hayro. Or, for hayro, which reads the whole file, the input is longer than `max_input_bytes`.\n\n## Why\n\nPage sizes and zoom levels come from files and users, so a render could otherwise ask for a bitmap of any size. The limits are checked before anything is allocated. The defaults are 16,777,216 pixels per tile (4096 × 4096, iOS Safari's per-canvas cap), 16,384 pixels per side and 1 GiB of input.\n\n## How to fix\n\nAt high zoom, render the visible part of the page as tiles of 256 to 512 pixels (`RenderRequest::with_clip`) instead of the whole page. Raise the limits with `RenderOpenOptions::with_limits` only when the larger bitmap is intended.\n\n## Example\n\n```rust\nlet req = RenderRequest::page(0, 1000.0); // a letter page at 1000×: 612,000 × 792,000 pixels\ndoc.render(&req, &mut out, &CancelToken::new())?; // render-too-large\n```\n\n## Data fields\n\n- `width`, `height`: the tile's size (tile limits).\n- `limit`: the limit exceeded.\n- `unit`: `pixels`, `pixels per side` or `pixels per side (hayro)` (tile limits).\n- `len`: the input's length in bytes (the input limit, with `limit`).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0004](../decisions/0004-render-backend.md) §1\n",
|
|
124
|
+
"render-failed": "# `render-failed`\n\n- Type: error\n- Kind: `input` (the input is malformed beyond what tolerance repairs)\n\n## What happened\n\nThe render engine (PDFium or hayro, `laminapdf-render`) could not open the document, or could not draw the page it was asked for.\n\n## Why\n\nThe engines have their own parsers. A file that Lamina PDF repairs may still be too damaged for an engine, or use a feature it can't draw. An engine failure (for example a panic inside hayro) is reported this way too, never as a crash.\n\n## How to fix\n\nCheck the document's warnings: a damaged file is usually reported there too. Try the other backend from the debug menu. If neither can draw the page, report the file with the backend name and version.\n\n## Example\n\n```rust\nlet doc = HayroBackend::new().open(Arc::new(bytes), &RenderOpenOptions::new())?; // render-failed for a file hayro can't parse\n```\n\n## Data fields\n\n- `backend`: `pdfium` or `hayro`.\n- `stage`: `open` or `render`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [C1a](../todos/done/C1a-hayro-parity-spike.md): the hayro parity spike, which saw such a panic once\n",
|
|
125
|
+
"render-backend-unavailable": "# `render-backend-unavailable`\n\n- Type: error\n- Kind: `io` (reading or writing outside Lamina PDF failed)\n\n## What happened\n\nThe PDFium backend (`laminapdf-render-pdfium`) could not load the PDFium library, so nothing can be rendered with it.\n\n## Why\n\nPDFium is bound at runtime, never linked at build time: the library has to be present on the machine. Lamina PDF looks for it only in the folder you name.\n\n## How to fix\n\nShip the PDFium library (`libpdfium.dylib`, `libpdfium.so` or `pdfium.dll`) with your app and pass its folder to `PdfiumBackend::bind(dir)`, or set `LAMINA_PDF_PDFIUM_DIR` to that folder for `PdfiumBackend::from_env()`.\n\n## Example\n\n```rust\nlet backend = PdfiumBackend::from_env()?; // render-backend-unavailable when the library isn't there\n```\n\n## Data fields\n\n- `backend`: `pdfium`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0004](../decisions/0004-render-backend.md) §3\n\nIn a checkout of Lamina PDF, `cargo xtask fetch-pdfium` fetches the pinned build and prints its `LAMINA_PDF_PDFIUM_DIR`.\n",
|
|
126
|
+
"cancelled": "# `cancelled`\n\n- Type: error\n- Kind: `cancelled` (a `CancelToken` stopped the work)\n\n## What happened\n\nThe `CancelToken` passed to a job, or to a run-to-completion call such as `Document::save`, was cancelled. The call returned at its next check: the start of a step, a budget check, or the next object or page of a long loop.\n\n## Why\n\nThe caller (or a host on its behalf: an `AbortSignal`, Ctrl-C, a task cancellation) asked for the work to stop.\n\n## How to fix\n\nNothing: this is the outcome that was asked for. The job is finished, a cancelled save left the document as it was (`Dirty`, nothing written), and a cancelled read returned no partial result. Start a new job with a fresh token to try again.\n\n## Example\n\n```rust\nlet cancel = laminapdf::CancelToken::new();\ncancel.cancel();\nlet err = doc.save_with(&laminapdf::SaveOptions::new(), &cancel).unwrap_err();\nassert_eq!(err.code(), \"cancelled\");\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0028](../decisions/0028-facade-rules-jobs-and-cancellation.md) §5\n",
|
|
127
|
+
"job-finished": "# `job-finished`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\n`step` was called on a job that had already returned its result, failed or been cancelled.\n\n## Why\n\nA job ends with its first `Step::Done`, or with its first error: an error ends the job. A step loop that keeps going after either reaches this.\n\n## How to fix\n\nStop stepping once a step returns `Done` or an error. To run the work again, create a new job.\n\n## Example\n\n```rust\nlet mut job = doc.save_job(&laminapdf::SaveOptions::new());\nwhile let laminapdf::Step::Progress { .. } = job.step(&mut doc, laminapdf::Budget::UNLIMITED)? {}\nassert_eq!(job.step(&mut doc, laminapdf::Budget::UNLIMITED).unwrap_err().code(), \"job-finished\");\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0028](../decisions/0028-facade-rules-jobs-and-cancellation.md) §3\n",
|
|
128
|
+
"document-changed": "# `document-changed`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\nA job's `step` was given a document other than the one the job was created from, or that document changed since: a committed transaction, an undo, a redo, a save or a rebase. Every step checks the document's instance and revision before any work, and the job is now finished.\n\n## Why\n\nJobs don't borrow the document, so other calls can run between two steps. A save or a read that went on after an edit would mix two states of the document, so the job stops instead.\n\n## How to fix\n\nPass the document the job was created from, don't change it while the job runs, and create a new job after a change. A failed save left the document as it was.\n\n## Example\n\n```rust\nlet mut job = doc.save_job(&laminapdf::SaveOptions::new());\ndoc.apply(vec![laminapdf::Op::from(set_info)])?; // the revision moves on\nassert_eq!(job.step(&mut doc, laminapdf::Budget::UNLIMITED).unwrap_err().code(), \"document-changed\");\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0028](../decisions/0028-facade-rules-jobs-and-cancellation.md) §3\n",
|
|
129
|
+
"builder-finished": "# `builder-finished`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\nA `WasmBuilder` method was called after the builder's `finish`, or a `WasmPageBuilder` was drawn on or finished again after its own `finish` (or after its builder's).\n\n## Why\n\n`finish` hands the work over: a builder's becomes a `WasmDocument`, a page's is written into its builder. Neither holds anything to draw on afterwards.\n\n## How to fix\n\nDraw everything on a page before finishing it, and finish every page before the builder. To build more, create a new page or builder.\n\n## Example\n\n```js\nconst b = new WasmBuilder(undefined);\nconst page = b.page(595, 842);\npage.finish();\npage.save(); // throws { code: \"builder-finished\", … }\n```\n\n## Data fields\n\n- `argument`: always `builder`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0016](../decisions/0016-package-design.md) §7: the binding owns its pages\n",
|
|
130
|
+
"disposed": "# `disposed`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\nA `Document`, `Builder` or other handle of `@laminapdf/sdk` was used after `free()` released it, or after the `using` block that held it ended (`Symbol.dispose`). A page or save job is freed with its document, and a builder's page with its builder.\n\n## Why\n\nEach handle owns memory inside the WebAssembly module. Freeing it returns that memory at once, so the handle has nothing left to work on.\n\n## How to fix\n\nFree a handle only after its last use. To keep working with the document, open it again from its bytes, or create a new builder.\n\n## Example\n\n```ts\nconst doc = Document.open(bytes);\ndoc.free();\ndoc.save(); // throws LaminaPdfError { code: \"disposed\", … }\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L2b](../todos/done/L2b-disposal-and-memory.md): the check on every handle, and on the pages and save jobs of `@laminapdf/wasm`\n",
|
|
131
|
+
"invalid-argument": "# `invalid-argument`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA value passed from JavaScript to `@laminapdf/wasm` is not of the type its declaration describes. It might be a record with a field of the wrong type, or a string that names no known value (a blend mode, a fill rule, a standard 14 font, an op). It might also be a flat-encoded path cut short, a colour with the wrong number of components, or an ID that isn't 32 hex digits.\n\n## Why\n\nThe `.d.ts` types describe each argument, but JavaScript (and code written without the types) can pass anything. The binding checks each value as it converts it.\n\n## How to fix\n\nPass what the declaration says. When the value names an unknown string, `didYouMean` lists the closest allowed ones; `data.detail` says what the conversion found.\n\n## Example\n\n```js\npage.blendMode(\"multiplly\"); // throws { code: \"invalid-argument\", didYouMean: [\"multiply\"], … }\n```\n\n## Data fields\n\n- `argument`: the parameter that was wrong (`options`, `path`, `mode`, …).\n- `detail`: what the conversion said (untrusted: it may quote the value passed).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0016](../decisions/0016-package-design.md) §7\n- [ADR 0028](../decisions/0028-facade-rules-jobs-and-cancellation.md) F6\n",
|
|
132
|
+
"source-budget-exceeded": "# `source-budget-exceeded`\n\n- Type: error\n- Kind: `limit` (a `Limits` value or another resource bound was hit)\n\n## What happened\n\nA sparse source (`WasmSparseSource` in `@laminapdf/wasm`, or a `@laminapdf/sdk` range document) was handed bytes that would make it hold more than the byte budget it was created with. The bytes were not added.\n\n## Why\n\nA range source keeps every byte it has fetched: the document may already have read them, so nothing is evicted. The budget bounds how much of a remote or lazily read file one document may hold in memory.\n\n## How to fix\n\nCreate the source with a larger budget, or fetch less per round: smaller blocks and no readahead. A file that must be read whole (a full rewrite, or a repaired file with no usable xref) needs a budget of at least its size.\n\n## Example\n\n```js\nconst source = new WasmSparseSource(10_000_000, 1_000_000);\nsource.supply(0, new Uint8Array(2_000_000)); // throws { code: \"source-budget-exceeded\", … }\n```\n\n## Data fields\n\n- `argument`: `bytes`.\n- `detail`: how many bytes the source would hold and its budget (Lamina PDF's own numbers).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L2c](../todos/done/L2c-range-byte-source.md): the range byte source\n- [ADR 0005](../decisions/0005-document-model.md) §2\n",
|
|
133
|
+
"unknown-option": "# `unknown-option`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nAn options record passed to `@laminapdf/wasm` (`OpenOptionsInit`, `SaveOptionsInit`, `BuildOptionsInit`, …) has a key Lamina PDF doesn't know.\n\n## Why\n\nRecords refuse unknown keys instead of ignoring them, so a misspelt option fails loudly rather than silently doing nothing.\n\n## How to fix\n\nRemove the key or correct its spelling. `didYouMean` lists the known keys within two edits of it.\n\n## Example\n\n```js\nWasmDocument.openBytes(bytes, { pasword: \"secret\" }); // throws { code: \"unknown-option\", didYouMean: [\"password\"], … }\n```\n\n## Data fields\n\n- `argument`: the parameter whose record had the key (`options`, `metadata`, …).\n- `option`: the key given (untrusted: caller text, never in the message).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0028](../decisions/0028-facade-rules-jobs-and-cancellation.md) F6\n- [ADR 0026](../decisions/0026-error-and-diagnostic-contract.md) §1\n",
|
|
134
|
+
"already-initialized": "# `already-initialized`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\n`init()` or `loadText()` of `@laminapdf/sdk` named a source (a URL, `Response`, bytes or `WebAssembly.Module`) for a module that an earlier call had already loaded, or started loading, from another source (or from the default file).\n\n## Why\n\nEach module is instantiated once per JavaScript realm: every document and builder lives in that instance. Loading a second copy from another source would leave two instances whose objects can't be mixed, so the second source is refused rather than silently ignored.\n\n## How to fix\n\nPass the source on the first call only; later calls (from other parts of the app) can call `init()` without one, which waits for the same load. Calling again with the very same source object is allowed.\n\n## Example\n\n```js\nawait init(new URL(\"/wasm/lamina_pdf_base.wasm\", location.href));\nawait init(); // fine: the module is loaded\nawait init(otherBytes); // throws { code: \"already-initialized\", … }\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0016](../decisions/0016-package-design.md) §3\n",
|
|
135
|
+
"extract-module-not-loaded": "# `extract-module-not-loaded`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\n`page.text()` of `@laminapdf/sdk` was called on a page of a document opened in the base module, which has no text extraction.\n\n## Why\n\n`@laminapdf/sdk` ships three WebAssembly modules: base (open, read, edit, save), extract (base plus text extraction) and text (base plus fonts, shaping and extraction). A document lives in the module it was opened in, and opening uses the richest one loaded at that moment: text, then extract, then base. A document opened before the extract or text module was loaded stays in the base module.\n\n## How to fix\n\nLoad the extract module (about half the size of the text module) before opening the document: `await init({ extract: true })` or `await loadExtract()`. A document already open in the base module can be moved by saving it and opening the bytes again.\n\n## Example\n\n```js\nawait init();\nconst doc = Document.open(bytes);\ndoc.page(0).text(); // throws { code: \"extract-module-not-loaded\", … }\n\nawait loadExtract();\nDocument.open(bytes).page(0).text().text(); // works\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0016](../decisions/0016-package-design.md) §4\n",
|
|
136
|
+
"fetch-failed": "# `fetch-failed`\n\n- Type: error\n- Kind: `io` (the host failed to supply bytes; often transient)\n\n## What happened\n\nA range source of `@laminapdf/sdk` (`httpRangeSource`) couldn't get bytes it asked for. The request failed (a network error, a CORS refusal, DNS), or the server answered with a status other than `200` or `206`, or with a `206` whose `Content-Range` (or length) doesn't match the range asked for. A `Content-Range` that the browser hides, because CORS doesn't expose it, looks the same. It is also raised when a `callbackSource`'s read returns a different number of bytes than it was asked for.\n\n## Why\n\nOpening a remote file range by range depends on the server: it must allow the `Range` request (and, across origins, expose `Content-Range`, `Content-Length` and `ETag` through CORS), and must not compress the response (`Content-Encoding` breaks byte ranges). Lamina PDF never retries on its own; the error says the bytes are not available.\n\n## How to fix\n\nCheck that the URL is reachable from where the code runs and that the server sends `Access-Control-Allow-Origin`, `Access-Control-Allow-Headers: Range` and `Access-Control-Expose-Headers: Content-Range, Content-Length, ETag`. Serve PDFs without `Content-Encoding`. Retry the call yourself if the failure was transient.\n\n## Example\n\n```js\nconst doc = await Document.openRange(httpRangeSource(url)); // throws { code: \"fetch-failed\", data: { status: 403 } }\n```\n\n## Data fields\n\n- `reason`: `network`, `status`, `content-range` or `length` (Lamina PDF's own words). The URL is never included.\n- `status`: the HTTP status, when there was a response.\n- `expected`, `got`: for `length`, the bytes asked for and the bytes returned.\n",
|
|
137
|
+
"not-initialized": "# `not-initialized`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\nA function or class of `@laminapdf/sdk` (`Document.open`, `new Builder`, …) was called before `init()` had finished loading and instantiating the WebAssembly module.\n\n## Why\n\n`@laminapdf/sdk` is synchronous after `init()`, but fetching and compiling the `.wasm` file is asynchronous, so it happens once, up front, in `init()`. Until that promise resolves there is no module to call.\n\n## How to fix\n\nAwait `init()` once, before the first call; calling it again is cheap (it is idempotent). In a Cloudflare Worker, pass the imported module: `await init({ module: wasm })`.\n\n## Example\n\n```js\nimport { Document, init } from \"@laminapdf/sdk\";\nDocument.open(bytes); // throws { code: \"not-initialized\", … }\nawait init();\nDocument.open(bytes); // works\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0016](../decisions/0016-package-design.md) §3\n",
|
|
138
|
+
"source-changed": "# `source-changed`\n\n- Type: error\n- Kind: `io` (the host failed to supply bytes; often transient)\n\n## What happened\n\nA remote file changed while `@laminapdf/sdk` was reading it range by range: a response's `ETag` or total length differs from the first one (or the server ignored `If-Range` and sent a different file).\n\n## Why\n\nA range document is assembled from many responses. If the file changed between them, the parts no longer belong to one file, and the document would read a mix of two versions. `httpRangeSource` sends `If-Range` with the first `ETag` and compares each response's `ETag` and `Content-Range` length with the first.\n\n## How to fix\n\nOpen the file again (a new source, a new document). If the file changes often, download it whole with `Document.open(bytes)` instead.\n\n## Example\n\n```js\nconst doc = await Document.openRange(httpRangeSource(url));\nawait doc.ensure(() => doc.pages); // throws { code: \"source-changed\", … } if the file was replaced meanwhile\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L2c](../todos/done/L2c-range-byte-source.md): the range byte source\n",
|
|
139
|
+
"text-module-not-loaded": "# `text-module-not-loaded`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\nA font or text method (`Font` use, `surface.text`, `surface.textBox`, `builder.font`) was called on a `Builder` created while only the base WebAssembly module was loaded, or `new Builder({ text: true })` was called before the text module was loaded.\n\n## Why\n\n`@laminapdf/sdk` ships two modules: the base module (open, edit, save, vector and image pages) and the larger text module (all of the base plus fonts, shaping and text layout). A document that draws text is built entirely in the text module, and loading it is asynchronous, so it can't happen inside a synchronous drawing call. A builder created without the text module can't gain it later.\n\n## How to fix\n\nLoad the text module before creating the builder that draws text: `await init({ text: true })`, or `await loadText()` after `init()`. Builders created after that use the text module.\n\n## Example\n\n```js\nawait init();\nconst b = new Builder();\nb.page(\"A4\").surface().text(\"Hi\", { font: Font.standard(\"Helvetica\"), size: 12, at: [72, 72] });\n// throws { code: \"text-module-not-loaded\", … }\nawait loadText(); // then create a new Builder\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0016](../decisions/0016-package-design.md) §4\n",
|
|
140
|
+
"worker-version-mismatch": "# `worker-version-mismatch`\n\n- Type: error\n- Kind: `usage` (the caller did something the API doesn't allow)\n\n## What happened\n\n`connect()` from `@laminapdf/sdk/worker` started a Web Worker, and its handshake named a different protocol or package version than the client. The client rejected `connect()` and sent nothing else.\n\n## Why\n\nThe client and the Web Worker exchange messages whose shape belongs to one build. A Web Worker from another version (an older deploy, a CDN copy, a separately bundled copy) could misread them, so the handshake fails loudly instead. PDF.js learned the same lesson.\n\n## How to fix\n\nLet the bundler serve the Web Worker from the same `@laminapdf/sdk` package as the page: `connect()` contains the literal `new Worker(new URL(…, import.meta.url))` that Vite and webpack follow. If you pass `{ worker }` yourself, build it from the same package version.\n\n## Example\n\n```ts\nconst client = await connect({ worker: new Worker(\"/old/lamina-worker.js\", { type: \"module\" }) });\n// rejects: LaminaPdfError { code: \"worker-version-mismatch\", … }\n```\n\n## Data fields\n\n- `expected`: the client's `{ protocol, version }`.\n- `actual`: what the Web Worker answered.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L2a](../todos/done/L2a-worker-client.md): the worker client\n",
|
|
141
|
+
"worker-crashed": "# `worker-crashed`\n\n- Type: error\n- Kind: `internal` (a bug or an environment failure, not the caller's input)\n\n## What happened\n\nThe Web Worker running `@laminapdf/sdk` (from `@laminapdf/sdk/worker`) stopped: an uncaught error, a WebAssembly trap or the browser killing it (out of memory). Every call still in flight was rejected with this code, and the client emitted a `crashed` event.\n\n## Why\n\nA long synchronous call can keep the Web Worker busy without answering, so Lamina PDF has no heartbeat and never treats silence as a crash. Only the Web Worker's `error` or `messageerror` event, or a trap it reports, counts as a crash.\n\n## How to fix\n\nCall `client.restart()`, then open the document again: handles from the old Web Worker are gone. An encrypted document needs its password again. If one file keeps crashing the Web Worker, report it.\n\n## Example\n\n```ts\nclient.on(\"crashed\", () => client.restart());\nawait doc.save(); // rejects with LaminaPdfError { code: \"worker-crashed\", … } if the Web Worker died\n```\n\n## Data fields\n\n- `reason`: what ended the Web Worker: `error`, `messageerror` or `trap` (a crash, which emits `crashed`), or `terminated` (`client.terminate()`, `restart()`, or a call made when no Web Worker runs).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L2a](../todos/done/L2a-worker-client.md): the worker client\n- [ADR 0009](../decisions/0009-web-client-architecture.md) §3.6\n",
|
|
142
|
+
"worker-failed": "# `worker-failed`\n\n- Type: error\n- Kind: `internal` (a bug or an environment failure, not the caller's input)\n\n## What happened\n\nA call through `@laminapdf/sdk/worker` failed inside the Web Worker with an error that isn't a `LaminaPdfError`. The call was rejected with this code and the error's message; the Web Worker and its documents keep working.\n\n## Why\n\nThe Web Worker passes Lamina PDF's own errors through unchanged and rejects caller mistakes it can detect (an unknown method, a parameter it can't receive) with `invalid-argument`. Anything else is a failure of Lamina PDF's client code, such as a result it couldn't send back or a bug in its code.\n\n## How to fix\n\nReport it, with the call you made and the message. The same call through the sync API on the main thread is a workaround while it is fixed.\n\n## Example\n\n```ts\nawait doc.pageCount(); // rejects with LaminaPdfError { code: \"worker-failed\", kind: \"internal\", … } on a bug in Lamina PDF's client\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L2a](../todos/done/L2a-worker-client.md): the worker client\n",
|
|
143
|
+
"wasm-trap": "# `wasm-trap`\n\n- Type: error\n- Kind: `internal` (a bug in Lamina PDF or a broken invariant)\n\n## What happened\n\nThe WebAssembly module trapped during the call: Lamina PDF hit a bug and stopped. `@laminapdf/sdk` discarded the module's instance, so the next call runs in a fresh one. Every document, page and builder from the old instance now throws `instance-reset`.\n\n## Why\n\nA trap stops the module in the middle of its work, so its memory may hold half-written state. Nothing in that instance can be trusted afterwards. A fresh instance keeps one failed call from breaking every call after it.\n\n## How to fix\n\nOpen the document again from its bytes and retry. If the same call on the same file traps again, report it with the file: a trap is always a bug in Lamina PDF.\n\n## Example\n\n```ts\ntry {\n lines = doc.page(0).text().lines;\n} catch (e) {\n if (!isLaminaPdfError(e, \"wasm-trap\")) throw e;\n doc = Document.open(bytes); // the old document throws instance-reset from now on\n}\n```\n\n## Data fields\n\n- `module`: the module that trapped: `base`, `text` or `extract`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L2b](../todos/done/L2b-disposal-and-memory.md): trap recovery and the fresh instance\n- [ADR 0016](../decisions/0016-package-design.md): the modules and their build\n",
|
|
144
|
+
"out-of-memory": "# `out-of-memory`\n\n- Type: error\n- Kind: `limit` (a `Limits` value or another resource bound was hit)\n\n## What happened\n\nThe WebAssembly module needed more memory than it could get, so it stopped in the middle of the call. Each module may grow to at most 2 GiB, and a browser tab or server can run out before that. `@laminapdf/sdk` discarded the module's instance, so the next call runs in a fresh one. Every document, page and builder from the old instance now throws `instance-reset`.\n\n## Why\n\nA module's memory grows as the module needs more and never shrinks. Many open documents, a huge image, or a file built to exhaust memory can each push it to its limit. The open limits stop most hostile files before memory runs out, and the maximum catches the rest.\n\n## How to fix\n\nOpen the document again, then keep less in memory at once. Free each document and builder as soon as you're done with it, or use `using`. For files from people you don't trust, lower the open limits, such as `maxStreamDecoded` and `maxImagePixels`, so they fail with `limit-exceeded` first. An app can also open each document in its own Web Worker, with `@laminapdf/sdk/worker`, and end the Web Worker afterwards: that returns all of its memory.\n\n## Example\n\n```ts\ntry {\n doc = Document.open(bytes, { limits: { maxStreamDecoded: 64_000_000 } });\n} catch (e) {\n if (isLaminaPdfError(e, \"out-of-memory\")) showError(\"This file is too large to open here.\");\n else throw e;\n}\n```\n\n## Data fields\n\n- `module`: the module that ran out: `base`, `text` or `extract`.\n- `memoryBytes`: the size of its memory when it stopped.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L2b](../todos/done/L2b-disposal-and-memory.md): the memory maximum and the fresh instance\n- [ADR 0016](../decisions/0016-package-design.md): the maximum, set when the modules are built\n- [ADR 0003](../decisions/0003-reader-tolerance-and-limits.md) §3: the limits that come first\n",
|
|
145
|
+
"instance-reset": "# `instance-reset`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\nA document, page, builder or save job was used after its WebAssembly instance was replaced. `reset()` replaces every loaded module's instance, and a trap (`wasm-trap` or `out-of-memory`) replaces the instance that trapped. The handle belonged to the old instance, so it has nothing left to work on.\n\n## Why\n\nEach handle lives in one instance's memory, and a fresh instance starts empty. Lamina PDF can't move a document across, so every handle from the old instance stops working at once.\n\n## How to fix\n\nOpen the document again from its bytes, or create a new builder. Unsaved edits are lost with the old instance, so save often in an app that calls `reset()` or may run out of memory.\n\n## Example\n\n```ts\nconst doc = Document.open(bytes);\nreset();\ndoc.pageCount; // throws LaminaPdfError { code: \"instance-reset\", … }\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [L2b](../todos/done/L2b-disposal-and-memory.md): `reset()` and trap recovery\n",
|
|
146
|
+
"invalid-command-line": "# `invalid-command-line`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nThe command line didn't parse: a command or flag `laminapdf` doesn't have, a missing argument, a value of the wrong form, or `-` (standard input) named as more than one input.\n\n## Why\n\nThe CLI checks its whole command line before it reads any file, so a typo never runs half a command. Exit code 2 tells a script that the call itself was wrong, not the document.\n\n## How to fix\n\nRun the command with `--help` to see its arguments, and `laminapdf --help` for the commands. Read standard input at most once per command.\n\n## Example\n\n```console\n$ laminapdf info --pages 1 report.pdf\nerror[invalid-command-line]: unexpected argument '--pages' found\n\nUsage: laminapdf info [OPTIONS] <INPUT>\n\nFor more information, try '--help'.\n$ echo $?\n2\n```\n\nWith `--json`, the line on standard error has the message \"the command line doesn't parse\" and the parser's finding in `data.detail`.\n\n## Data fields\n\nWhich field is there depends on what was wrong:\n\n- `detail`: when the parser refused the line, the kind of problem it found, such as `UnknownArgument` or `MissingRequiredArgument`.\n- `argument`: when a value parsed but means nothing, the value given: the angle of `pages rotate`, a `help` topic or a `--fields` key (untrusted: caller text, never in the message).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0031](../decisions/0031-cli-and-mcp-contract.md) §4, §5 and §8\n- [L4a](../todos/done/L4a-lamina-cli.md): the `laminapdf` CLI and its first commands\n",
|
|
147
|
+
"invalid-page-range": "# `invalid-page-range`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA page range doesn't fit the grammar, or it names a page the document doesn't have, such as page 9 of a 5-page file.\n\n## Why\n\nRanges count pages from 1, as people do. A range that can't be read, or that reaches past the last page, fails before anything is written, so a command never works on the wrong pages.\n\n## How to fix\n\nWrite pages as `1-5`, `z` for the last page, `r2` for the second from the end, `x3` to leave page 3 out, and `:odd` or `:even` at the end for every other page of the result. `data.position` is the character where the range went wrong.\n\n## Example\n\n```console\n$ laminapdf pages rotate scan.pdf +90:1-9 -o out.pdf\nerror[invalid-page-range]: the document has 5 pages\n = hint: write pages as 1-5, z for the last page, …\n$ echo $?\n2\n```\n\n## Data fields\n\n- `range`: the range given (untrusted: caller text, never in the message).\n- `position`: the 1-based character of `range` where it went wrong.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0031](../decisions/0031-cli-and-mcp-contract.md) §4, §5 and §8\n- [L4a](../todos/done/L4a-lamina-cli.md): the `laminapdf` CLI and its first commands\n",
|
|
148
|
+
"binary-output-to-terminal": "# `binary-output-to-terminal`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\nA command that writes a PDF was told to write it to standard output (`-o -`), and standard output is a terminal.\n\n## Why\n\nA PDF's bytes would fill the terminal with noise and can leave it in a strange state. The CLI writes binary output only to a file or a pipe.\n\n## How to fix\n\nRedirect standard output to a file or a pipe, or write to a file with `-o`.\n\n## Example\n\n```console\n$ laminapdf pages rotate scan.pdf +90 -o -\nerror[binary-output-to-terminal]: standard output is a terminal\n = hint: redirect standard output to a file or a pipe, or write to a file with -o\n$ laminapdf pages rotate scan.pdf +90 -o - > rotated.pdf\n```\n\n## Data fields\n\nNone.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0031](../decisions/0031-cli-and-mcp-contract.md) §4, §5 and §8\n- [L4a](../todos/done/L4a-lamina-cli.md): the `laminapdf` CLI and its first commands\n",
|
|
149
|
+
"unknown-code": "# `unknown-code`\n\n- Type: error\n- Kind: `usage` (the caller misused the API)\n\n## What happened\n\n`laminapdf explain` was given a code that isn't one of Lamina PDF's error or warning codes.\n\n## Why\n\nEvery code Lamina PDF reports is listed once, with its own page. A code that isn't listed has no explanation to print.\n\n## How to fix\n\nCheck the spelling: `didYouMean` lists the known codes closest to the one given. The tables in this folder's README list every code.\n\n## Example\n\n```console\n$ laminapdf explain xref-repaird\nerror[unknown-code]: not a Lamina PDF code\n = did you mean: xref-repaired\n$ echo $?\n2\n```\n\n## Data fields\n\n- `code`: the code given (untrusted: caller text, never in the message).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0031](../decisions/0031-cli-and-mcp-contract.md) §4, §5 and §8\n- [L4a](../todos/done/L4a-lamina-cli.md): the `laminapdf` CLI and its first commands\n",
|
|
150
|
+
"overwrite-refused": "# `overwrite-refused`\n\n- Type: error\n- Kind: `state` (the call isn't valid in the object's current state)\n\n## What happened\n\nThe output file already exists, or the command was asked to change its input in place with `--in-place`, and `--force` wasn't given.\n\n## Why\n\nThe CLI never replaces a file without being told to, so a mistyped `-o` can't destroy a document. It never prompts either, because scripts and agents can't answer a prompt. Exit code 4 tells a script the command needs `--force`.\n\n## How to fix\n\nPass `--force` to replace the file, or write to a new file with `-o`. The CLI writes to a temporary file and renames it, so a replaced file is never left half written.\n\n## Example\n\n```console\n$ laminapdf pages rotate scan.pdf 90:z --in-place\nerror[overwrite-refused]: refusing to replace a file without --force\n = hint: pass --force, or write to a new file with -o\n$ echo $?\n4\n```\n\n## Data fields\n\n- `output`: the file that would have been replaced (untrusted: caller text, never in the message).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0031](../decisions/0031-cli-and-mcp-contract.md) §4, §5 and §8\n- [L4a](../todos/done/L4a-lamina-cli.md): the `laminapdf` CLI and its first commands\n",
|
|
151
|
+
"input-unreadable": "# `input-unreadable`\n\n- Type: error\n- Kind: `io` (the host failed to supply or take bytes)\n\n## What happened\n\nAn input couldn't be read: the PDF named on the command line, standard input, or the file given to `--password-file`.\n\n## Why\n\nThe file may not exist, may be a folder, or may not be readable by the user running the command.\n\n## How to fix\n\nCheck that the file exists and that you can read it. `data.input` names the file, and `data.reason` says what the system reported.\n\n## Example\n\n```console\n$ laminapdf info missing.pdf\nerror[input-unreadable]: couldn't read an input\n = hint: check that the file exists and that you can read it; data.input names it\n$ echo $?\n1\n```\n\n## Data fields\n\n- `input`: the file that couldn't be read, or `-` for standard input (untrusted: caller text, never in the message).\n- `reason`: the system's error kind, such as `NotFound` or `PermissionDenied`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0031](../decisions/0031-cli-and-mcp-contract.md) §4, §5 and §8\n- [L4a](../todos/done/L4a-lamina-cli.md): the `laminapdf` CLI and its first commands\n",
|
|
152
|
+
"output-unwritable": "# `output-unwritable`\n\n- Type: error\n- Kind: `io` (the host failed to supply or take bytes)\n\n## What happened\n\nThe output couldn't be written: its folder is missing or read-only, the disk is full, or standard output was closed before the command finished.\n\n## Why\n\nThe system refused the write, or standard output closed while the command was writing to it. The CLI writes to a temporary file beside the output and renames it into place, so a failed write leaves any old file as it was.\n\n## How to fix\n\nCheck that the output's folder exists, that you can write to it and that the disk has room. When piping, make sure the reading command doesn't exit early.\n\n## Example\n\n```console\n$ laminapdf pages rotate scan.pdf +90 -o /no/such/folder/out.pdf\nerror[output-unwritable]: couldn't write the output\n = hint: check that the output's folder exists, …\n$ echo $?\n1\n```\n\n## Data fields\n\n- `output`: the file that couldn't be written, or `-` for standard output (untrusted: caller text, never in the message).\n- `reason`: the system's error kind, such as `NotFound` or `StorageFull`.\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0031](../decisions/0031-cli-and-mcp-contract.md) §4, §5 and §8\n- [L4a](../todos/done/L4a-lamina-cli.md): the `laminapdf` CLI and its first commands\n",
|
|
153
|
+
"path-outside-roots": "# `path-outside-roots`\n\n- Type: error\n- Kind: `security` (encryption, permissions or signatures block the call)\n\n## What happened\n\nA tool of `laminapdf mcp` was given a path the server may not use: one outside every `--root` folder it was started with, one that climbs out with `..`, or a symbolic link inside a root that leads out of it. A missing file outside the roots, and a link that leads nowhere, get this answer too, so a call can't tell whether a file outside the roots exists.\n\n## Why\n\nAn MCP server acts for a model that may have read instructions planted in a document or a web page. So the server touches only the folders named on its command line by the person who started it. It resolves every path, links included, before it checks the path, so a link can't lead it elsewhere.\n\n## How to fix\n\nUse a path inside one of the server's `--root` folders; a relative path is read from the first root. To work on files elsewhere, start the server with another `--root`.\n\n## Example\n\n```json\n{\"schema\":\"laminapdf.error/1\",\"error\":{\"code\":\"path-outside-roots\",\"kind\":\"security\",\"message\":\"the path is outside the server's --root folders\",\"data\":{\"path\":\"../secret.pdf\"},\"exitCode\":1,…}}\n```\n\n## Data fields\n\n- `path`: the path the tool was given (untrusted: caller text, never in the message).\n\n## For contributors\n\nThe decisions and todos behind this code:\n\n- [ADR 0031](../decisions/0031-cli-and-mcp-contract.md) §9\n- [L4c](../todos/done/L4c-mcp-mode.md): `laminapdf mcp` and its path policy\n",
|
|
154
|
+
};
|
|
155
|
+
//# sourceMappingURL=explain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.js","sourceRoot":"","sources":["../../src/generated/explain.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,6FAA6F;AAC7F,qFAAqF;AAIrF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,KAAK,GAAwD;IACxE,gBAAgB,EAAE,o8BAAo8B;IACt9B,eAAe,EAAE,u8BAAu8B;IACx9B,iBAAiB,EAAE,8yBAA8yB;IACj0B,yBAAyB,EAAE,0pBAA0pB;IACrrB,kBAAkB,EAAE,6sBAA6sB;IACjuB,wBAAwB,EAAE,myBAAmyB;IAC7zB,qBAAqB,EAAE,ulBAAulB;IAC9mB,iBAAiB,EAAE,iqBAAiqB;IACprB,uBAAuB,EAAE,6nBAA6nB;IACtpB,4BAA4B,EAAE,mmBAAmmB;IACjoB,qBAAqB,EAAE,qxBAAqxB;IAC5yB,gBAAgB,EAAE,gnBAAgnB;IACloB,oBAAoB,EAAE,itBAAitB;IACvuB,aAAa,EAAE,6yBAA6yB;IAC5zB,gBAAgB,EAAE,0kBAA0kB;IAC5lB,wBAAwB,EAAE,oqBAAoqB;IAC9rB,eAAe,EAAE,sgBAAsgB;IACvhB,sBAAsB,EAAE,wkBAAwkB;IAChmB,cAAc,EAAE,kkBAAkkB;IACllB,oBAAoB,EAAE,8oBAA8oB;IACpqB,eAAe,EAAE,yoBAAyoB;IAC1pB,0BAA0B,EAAE,kjCAAkjC;IAC9kC,sBAAsB,EAAE,q1BAAq1B;IAC72B,sBAAsB,EAAE,o7BAAo7B;IAC58B,uBAAuB,EAAE,48BAA48B;IACr+B,2BAA2B,EAAE,6/BAA6/B;IAC1hC,gBAAgB,EAAE,8+BAA8+B;IAChgC,0BAA0B,EAAE,2uCAA2uC;IACvwC,2BAA2B,EAAE,2+BAA2+B;IACxgC,eAAe,EAAE,4zBAA4zB;IAC70B,wBAAwB,EAAE,g4BAAg4B;IAC15B,cAAc,EAAE,8uCAA8uC;IAC9vC,cAAc,EAAE,yrCAAyrC;IACzsC,gBAAgB,EAAE,+sCAA+sC;IACjuC,sBAAsB,EAAE,2hCAA2hC;IACnjC,iCAAiC,EAAE,onCAAonC;IACvpC,yBAAyB,EAAE,ouCAAouC;IAC/vC,qBAAqB,EAAE,spCAAspC;IAC7qC,6BAA6B,EAAE,47CAA47C;IAC39C,0BAA0B,EAAE,mzCAAmzC;IAC/0C,SAAS,EAAE,+kBAA+kB;IAC1lB,cAAc,EAAE,qeAAqe;IACrf,iBAAiB,EAAE,ohBAAohB;IACviB,gBAAgB,EAAE,snCAAsnC;IACxoC,QAAQ,EAAE,+mBAA+mB;IACznB,cAAc,EAAE,guBAAguB;IAChvB,QAAQ,EAAE,6dAA6d;IACve,MAAM,EAAE,4oBAA4oB;IACppB,mBAAmB,EAAE,ukBAAukB;IAC5lB,cAAc,EAAE,+gBAA+gB;IAC/hB,eAAe,EAAE,gpBAAgpB;IACjqB,gBAAgB,EAAE,gsCAAgsC;IACltC,mBAAmB,EAAE,kkBAAkkB;IACvlB,gBAAgB,EAAE,8rBAA8rB;IAChtB,8BAA8B,EAAE,wuBAAwuB;IACxwB,gBAAgB,EAAE,ijBAAijB;IACnkB,QAAQ,EAAE,+iBAA+iB;IACzjB,oBAAoB,EAAE,8nBAA8nB;IACppB,YAAY,EAAE,4eAA4e;IAC1f,oBAAoB,EAAE,ylBAAylB;IAC/mB,gBAAgB,EAAE,2bAA2b;IAC7c,cAAc,EAAE,mgBAAmgB;IACnhB,aAAa,EAAE,oyBAAoyB;IACnzB,qBAAqB,EAAE,4iBAA4iB;IACnkB,gBAAgB,EAAE,6pBAA6pB;IAC/qB,mBAAmB,EAAE,25BAA25B;IACh7B,iBAAiB,EAAE,ohBAAohB;IACviB,MAAM,EAAE,61BAA61B;IACr2B,aAAa,EAAE,ozBAAozB;IACn0B,gBAAgB,EAAE,wxBAAwxB;IAC1yB,6BAA6B,EAAE,k4DAAk4D;IACj6D,kBAAkB,EAAE,6yCAA6yC;IACj0C,cAAc,EAAE,gxBAAgxB;IAChyB,kBAAkB,EAAE,ylBAAylB;IAC7mB,cAAc,EAAE,ooBAAooB;IACppB,qBAAqB,EAAE,o2BAAo2B;IAC33B,eAAe,EAAE,2uBAA2uB;IAC5vB,oBAAoB,EAAE,ymBAAymB;IAC/nB,cAAc,EAAE,+gBAA+gB;IAC/hB,eAAe,EAAE,mzBAAmzB;IACp0B,oBAAoB,EAAE,+iBAA+iB;IACrkB,gBAAgB,EAAE,8zBAA8zB;IACh1B,iBAAiB,EAAE,w2BAAw2B;IAC33B,oBAAoB,EAAE,g5BAAg5B;IACt6B,2BAA2B,EAAE,ozBAAozB;IACj1B,eAAe,EAAE,08BAA08B;IAC39B,eAAe,EAAE,4hDAA4hD;IAC7iD,mBAAmB,EAAE,++BAA++B;IACpgC,oBAAoB,EAAE,6jCAA6jC;IACnlC,oBAAoB,EAAE,q3BAAq3B;IAC34B,eAAe,EAAE,k1BAAk1B;IACn2B,mBAAmB,EAAE,mwBAAmwB;IACxxB,iBAAiB,EAAE,ovBAAovB;IACvwB,mBAAmB,EAAE,wjBAAwjB;IAC7kB,qBAAqB,EAAE,i7BAAi7B;IACx8B,kBAAkB,EAAE,gmBAAgmB;IACpnB,eAAe,EAAE,4hBAA4hB;IAC7iB,kBAAkB,EAAE,+uCAA+uC;IACnwC,eAAe,EAAE,w9BAAw9B;IACz+B,yBAAyB,EAAE,i/BAAi/B;IAC5gC,qBAAqB,EAAE,01BAA01B;IACj3B,qBAAqB,EAAE,u1BAAu1B;IAC92B,0BAA0B,EAAE,qmCAAqmC;IACjoC,eAAe,EAAE,gjCAAgjC;IACjkC,cAAc,EAAE,w5BAAw5B;IACx6B,kBAAkB,EAAE,8qCAA8qC;IAClsC,kBAAkB,EAAE,2wCAA2wC;IAC/xC,uBAAuB,EAAE,y3CAAy3C;IACl5C,4BAA4B,EAAE,y3CAAy3C;IACv5C,2BAA2B,EAAE,2nDAA2nD;IACxpD,wBAAwB,EAAE,+nCAA+nC;IACzpC,uBAAuB,EAAE,04BAA04B;IACn6B,mBAAmB,EAAE,2qCAA2qC;IAChsC,uBAAuB,EAAE,ukDAAukD;IAChmD,oCAAoC,EAAE,8/BAA8/B;IACpiC,yBAAyB,EAAE,sjCAAsjC;IACjlC,iCAAiC,EAAE,gkCAAgkC;IACnmC,kBAAkB,EAAE,4jDAA4jD;IAChlD,eAAe,EAAE,+nCAA+nC;IAChpC,4BAA4B,EAAE,6jCAA6jC;IAC3lC,WAAW,EAAE,mlCAAmlC;IAChmC,cAAc,EAAE,27BAA27B;IAC38B,kBAAkB,EAAE,2sCAA2sC;IAC/tC,kBAAkB,EAAE,s+BAAs+B;IAC1/B,UAAU,EAAE,mhCAAmhC;IAC/hC,kBAAkB,EAAE,s0CAAs0C;IAC11C,wBAAwB,EAAE,gzCAAgzC;IAC10C,gBAAgB,EAAE,2hCAA2hC;IAC7iC,qBAAqB,EAAE,qtCAAqtC;IAC5uC,2BAA2B,EAAE,+yCAA+yC;IAC50C,cAAc,EAAE,osDAAosD;IACptD,iBAAiB,EAAE,giCAAgiC;IACnjC,gBAAgB,EAAE,0pCAA0pC;IAC5qC,wBAAwB,EAAE,44CAA44C;IACt6C,yBAAyB,EAAE,y0CAAy0C;IACp2C,gBAAgB,EAAE,67CAA67C;IAC/8C,eAAe,EAAE,uoCAAuoC;IACxpC,WAAW,EAAE,8zCAA8zC;IAC30C,eAAe,EAAE,q8DAAq8D;IACt9D,gBAAgB,EAAE,8nCAA8nC;IAChpC,sBAAsB,EAAE,2lDAA2lD;IACnnD,oBAAoB,EAAE,iwCAAiwC;IACvxC,2BAA2B,EAAE,4/BAA4/B;IACzhC,cAAc,EAAE,k8BAAk8B;IACl9B,mBAAmB,EAAE,qvCAAqvC;IAC1wC,kBAAkB,EAAE,koCAAkoC;IACtpC,mBAAmB,EAAE,2zCAA2zC;IACh1C,oBAAoB,EAAE,yhDAAyhD;CAChjD,CAAC"}
|