@laminapdf/sdk 0.2.0-alpha.2 → 0.2.0-alpha.4
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 +63 -8
- package/dist/api.d.ts +8 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +4 -2
- package/dist/api.js.map +1 -1
- package/dist/builder.d.ts +43 -22
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +122 -83
- package/dist/builder.js.map +1 -1
- package/dist/document.d.ts +89 -22
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +177 -67
- package/dist/document.js.map +1 -1
- package/dist/errors.d.ts +5 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +10 -16
- package/dist/errors.js.map +1 -1
- package/dist/extract-job.d.ts +100 -0
- package/dist/extract-job.d.ts.map +1 -0
- package/dist/extract-job.js +177 -0
- package/dist/extract-job.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 +13 -6
- package/dist/font.d.ts.map +1 -1
- package/dist/font.js +50 -20
- package/dist/font.js.map +1 -1
- package/dist/generated/builder-enums.d.ts +3 -3
- package/dist/generated/builder-enums.d.ts.map +1 -1
- package/dist/generated/builder-enums.js +10 -0
- package/dist/generated/builder-enums.js.map +1 -1
- package/dist/generated/codes.d.ts +18 -6
- package/dist/generated/codes.d.ts.map +1 -1
- package/dist/generated/codes.js +114 -5
- package/dist/generated/codes.js.map +1 -1
- package/dist/generated/explain.d.ts.map +1 -1
- package/dist/generated/explain.js +45 -11
- package/dist/generated/explain.js.map +1 -1
- package/dist/generated/extract.d.ts +282 -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/init.d.ts +13 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +23 -11
- package/dist/init.js.map +1 -1
- 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/source/http.d.ts +1 -1
- package/dist/source/http.js +2 -2
- package/dist/source/http.js.map +1 -1
- package/dist/source/range.d.ts +6 -1
- package/dist/source/range.d.ts.map +1 -1
- package/dist/source/range.js +10 -6
- package/dist/source/range.js.map +1 -1
- package/dist/text.d.ts +3 -3
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js +3 -3
- package/dist/text.js.map +1 -1
- package/dist/types.d.ts +19 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/dist/version.js.map +1 -1
- package/dist/worker/client.d.ts +3 -2
- package/dist/worker/client.d.ts.map +1 -1
- package/dist/worker/client.js +6 -4
- package/dist/worker/client.js.map +1 -1
- package/dist/worker/entry.js +1 -1
- package/dist/worker/entry.js.map +1 -1
- package/dist/worker/server.d.ts.map +1 -1
- package/dist/worker/server.js +42 -9
- package/dist/worker/server.js.map +1 -1
- package/dist/worker/support.d.ts +13 -3
- package/dist/worker/support.d.ts.map +1 -1
- package/dist/worker/support.js +5 -0
- package/dist/worker/support.js.map +1 -1
- package/dist/worker/types.d.ts +8 -1
- package/dist/worker/types.d.ts.map +1 -1
- package/docs/AGENTS.md +14 -0
- package/docs/api/README.md +166 -0
- package/docs/api/classes/Builder.md +467 -0
- package/docs/api/classes/Document.md +854 -0
- package/docs/api/classes/ExtractJob.md +236 -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 +331 -0
- package/docs/api/classes/Transaction.md +111 -0
- package/docs/api/functions/callbackSource.md +33 -0
- package/docs/api/functions/extractWithModels.md +70 -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/HostModels.md +57 -0
- package/docs/api/interfaces/HttpRangeOptions.md +78 -0
- package/docs/api/interfaces/InitOptions.md +40 -0
- package/docs/api/interfaces/LayoutRegion.md +38 -0
- package/docs/api/interfaces/Leak.md +21 -0
- package/docs/api/interfaces/Location.md +53 -0
- package/docs/api/interfaces/ModelInfo.md +45 -0
- package/docs/api/interfaces/ModelRequest.md +45 -0
- package/docs/api/interfaces/Models.md +45 -0
- package/docs/api/interfaces/OcrWord.md +37 -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/ExtractJobOptions.md +31 -0
- package/docs/api/type-aliases/ExtractStep.md +61 -0
- package/docs/api/type-aliases/ExtractedBlock.md +94 -0
- package/docs/api/type-aliases/ExtractedPage.md +73 -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 +43 -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/ModelResult.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/OutlineNode.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/RegionLabel.md +15 -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 +74 -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/StructureKid.md +15 -0
- package/docs/api/type-aliases/StructureNode.md +16 -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/Target.md +15 -0
- package/docs/api/type-aliases/TargetView.md +18 -0
- package/docs/api/type-aliases/TextLayer.md +72 -0
- package/docs/api/type-aliases/TextLayerStatus.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 +173 -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/covered-text.md +33 -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-column-order-uncertain.md +38 -0
- package/docs/errors/extract-limit-blocks.md +35 -0
- package/docs/errors/extract-module-not-loaded.md +37 -0
- package/docs/errors/extract-needs-ocr.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/flow-content-too-large.md +34 -0
- package/docs/errors/flow-keep-broken.md +34 -0
- package/docs/errors/flow-layout-unstable.md +38 -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 +37 -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-spans.md +39 -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/markdown-html-ignored.md +33 -0
- package/docs/errors/markdown-image-missing.md +34 -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/model-failed.md +33 -0
- package/docs/errors/model-needs-host.md +40 -0
- package/docs/errors/model-result-unexpected.md +36 -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/redaction-not-applied.md +32 -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/revision-index-out-of-range.md +34 -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/tag-element-not-open.md +41 -0
- package/docs/errors/tag-element-unclosed.md +34 -0
- package/docs/errors/text-in-earlier-revision.md +36 -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 +94 -0
- package/docs/pages/concepts/dependencies.md +44 -0
- package/docs/pages/concepts/drawing-pages.md +76 -0
- package/docs/pages/concepts/errors-and-warnings.md +68 -0
- package/docs/pages/concepts/how-lamina-thinks.md +98 -0
- package/docs/pages/concepts/pages-and-coordinates.md +75 -0
- package/docs/pages/guides/accessible-pdfs.md +262 -0
- package/docs/pages/guides/archival-pdfs.md +433 -0
- package/docs/pages/guides/edit-a-pdf.md +143 -0
- package/docs/pages/guides/flowing-pages.md +379 -0
- package/docs/pages/guides/fonts-and-text.md +204 -0
- package/docs/pages/guides/images-and-vectors.md +221 -0
- package/docs/pages/guides/keep-the-page-responsive.md +85 -0
- package/docs/pages/guides/links-outlines-and-metadata.md +224 -0
- package/docs/pages/guides/open-large-files.md +69 -0
- package/docs/pages/guides/password-protected-files.md +70 -0
- package/docs/pages/guides/save-a-pdf.md +154 -0
- package/docs/pages/guides/test-generated-pdfs.md +324 -0
- package/docs/pages/guides/untrusted-files.md +82 -0
- package/docs/pages/index.md +33 -0
- package/docs/pages/install.md +79 -0
- package/docs/pages/quick-start/browser.md +71 -0
- package/docs/pages/quick-start/bun.md +64 -0
- package/docs/pages/quick-start/deno.md +62 -0
- package/docs/pages/quick-start/node.md +65 -0
- package/docs/pages/quick-start/rust.md +62 -0
- package/docs/pages/quick-start/workers.md +139 -0
- package/docs/pages/recipes/cloudflare-worker.md +115 -0
- package/docs/pages/recipes/element-flow.md +248 -0
- package/docs/pages/recipes/extract-markdown.md +152 -0
- package/docs/pages/recipes/generate-then-edit.md +142 -0
- package/docs/pages/recipes/hello.md +101 -0
- package/docs/pages/recipes/images-and-vectors.md +216 -0
- package/docs/pages/recipes/invoice.md +233 -0
- package/docs/pages/recipes/links-and-outlines.md +222 -0
- package/docs/pages/recipes/multilingual.md +196 -0
- package/docs/pages/recipes/open-untrusted.md +65 -0
- package/docs/pages/recipes/pdf-a-archive.md +212 -0
- package/docs/pages/recipes/pdf-a3-invoice-with-xml.md +229 -0
- package/docs/pages/recipes/pdf-ua-report.md +244 -0
- package/docs/pages/recipes/report.md +125 -0
- package/docs/pages/recipes/web-worker-client.md +64 -0
- package/docs/pages/recipes.md +48 -0
- package/docs/pages/reference/benchmarks.md +93 -0
- package/docs/pages/reference/changelog.md +134 -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 +81 -0
- package/docs/pages/troubleshooting.md +74 -0
- package/docs/renames.json +86 -0
- package/docs/schemas/error.schema.json +322 -0
- package/docs/schemas/renames.schema.json +29 -0
- package/package.json +7 -3
- package/schemas/error.schema.json +37 -3
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "would-invalidate-signatures"
|
|
3
|
+
version: "0.2.0-alpha.4"
|
|
4
|
+
url: "https://docs.laminapdf.com/reference/codes/would-invalidate-signatures/"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# would-invalidate-signatures
|
|
8
|
+
|
|
9
|
+
- Type: error (`laminapdf_save::SaveError::WouldInvalidateSignatures`)
|
|
10
|
+
- Kind: `security` (signatures block the call)
|
|
11
|
+
|
|
12
|
+
## What happened
|
|
13
|
+
|
|
14
|
+
The 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.
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
A 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.
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
Save 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.
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
```rust
|
|
27
|
+
let err = doc.save(&mut out, &SaveOptions::new().with_mode(SaveMode::FullRewrite)).unwrap_err();
|
|
28
|
+
assert_eq!(err.code(), "would-invalidate-signatures");
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Data fields
|
|
32
|
+
|
|
33
|
+
- `signatures`: every signature, as `{ field, kind, value, revision, covers }` (field ID, kind, signature dictionary as `num gen`, revision, covered length).
|
|
34
|
+
- `rewrite_reasons`: why only a full rewrite is safe, if anything says so.
|
|
35
|
+
- `violations`: pending changes a signature's restriction forbids, as `{ class, restriction, signature }`.
|
|
36
|
+
- `signatures_unknown`: part of the form couldn't be followed, so there may be signatures beyond `signatures`.
|
|
37
|
+
|
|
38
|
+
None of them hold document text.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "wrong-document"
|
|
3
|
+
version: "0.2.0-alpha.4"
|
|
4
|
+
url: "https://docs.laminapdf.com/reference/codes/wrong-document/"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# wrong-document
|
|
8
|
+
|
|
9
|
+
- Type: error
|
|
10
|
+
- Kind: `usage` (the caller misused the API)
|
|
11
|
+
|
|
12
|
+
## What happened
|
|
13
|
+
|
|
14
|
+
`FontRegistry::finish_fonts` was given a document in which a font's reserved object is not a free number reserved in that document.
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
A `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.
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
Use one `FontRegistry` per document, and pass `finish_fonts` the document its `reference` calls used.
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
```rust
|
|
27
|
+
let id = registry.reference(key, &doc_a)?;
|
|
28
|
+
registry.finish_fonts(&mut doc_b.edit())?; // wrong-document: the fonts were reserved in doc_a
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Data fields
|
|
32
|
+
|
|
33
|
+
- `object`: the reserved object number.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "wrong-password"
|
|
3
|
+
version: "0.2.0-alpha.4"
|
|
4
|
+
url: "https://docs.laminapdf.com/reference/codes/wrong-password/"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# wrong-password
|
|
8
|
+
|
|
9
|
+
- Type: error
|
|
10
|
+
- Kind: `security` (encryption, permissions or signatures block the call, or look tampered with)
|
|
11
|
+
|
|
12
|
+
## What happened
|
|
13
|
+
|
|
14
|
+
The given password is neither the user nor the owner password.
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
The 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.
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
Ask for the password again. Passwords are case-sensitive.
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
Document.open(bytes, { password: "nope" });
|
|
28
|
+
// LaminaPdfError { code: "wrong-password", kind: "security",
|
|
29
|
+
// hint: "ask for the password again and retry with the correct one", … }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Data fields
|
|
33
|
+
|
|
34
|
+
None.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "xobject-cycle"
|
|
3
|
+
version: "0.2.0-alpha.4"
|
|
4
|
+
url: "https://docs.laminapdf.com/reference/codes/xobject-cycle/"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# xobject-cycle
|
|
8
|
+
|
|
9
|
+
- Type: warning
|
|
10
|
+
- Kind: `input` (the input is malformed beyond what tolerance repairs)
|
|
11
|
+
|
|
12
|
+
## What happened
|
|
13
|
+
|
|
14
|
+
A Form XObject draws itself, directly or through other forms; the inner `Do` was skipped.
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
A malformed or hostile file: forms that draw each other without end would never finish.
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
Nothing to do.
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
8 0 obj << /Subtype /Form /Resources << /XObject << /Me 8 0 R >> >> >> stream /Me Do endstream
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The form draws itself: the inner `/Me Do` is skipped once Lamina PDF sees form 8 a second time.
|
|
31
|
+
|
|
32
|
+
## Data fields
|
|
33
|
+
|
|
34
|
+
- `object`: the form reached again.
|
|
35
|
+
|
|
36
|
+
The `location` is `object` (the form reached again).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "xref-repaired"
|
|
3
|
+
version: "0.2.0-alpha.4"
|
|
4
|
+
url: "https://docs.laminapdf.com/reference/codes/xref-repaired/"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# xref-repaired
|
|
8
|
+
|
|
9
|
+
- Type: warning
|
|
10
|
+
- Kind: `input` (the input is malformed beyond what tolerance repairs)
|
|
11
|
+
|
|
12
|
+
## What happened
|
|
13
|
+
|
|
14
|
+
The stored cross-reference data was unusable and was rebuilt by scanning the file.
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
Edited, truncated or concatenated files often have stale offsets.
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
Nothing, if the result is right. Incremental saves refuse a repaired base (`repaired-base`); save in full instead.
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
startxref
|
|
28
|
+
123456
|
|
29
|
+
%%EOF
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`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.
|
|
33
|
+
|
|
34
|
+
## Data fields
|
|
35
|
+
|
|
36
|
+
None.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "xrefstm-ignored"
|
|
3
|
+
version: "0.2.0-alpha.4"
|
|
4
|
+
url: "https://docs.laminapdf.com/reference/codes/xrefstm-ignored/"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# xrefstm-ignored
|
|
8
|
+
|
|
9
|
+
- Type: warning
|
|
10
|
+
- Kind: `input` (the input is malformed beyond what tolerance repairs)
|
|
11
|
+
|
|
12
|
+
## What happened
|
|
13
|
+
|
|
14
|
+
A hybrid file's `/XRefStm` could not be read and was ignored.
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
Hybrid 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.
|
|
19
|
+
|
|
20
|
+
## How to fix
|
|
21
|
+
|
|
22
|
+
Nothing; the classic table is used.
|
|
23
|
+
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
trailer << /Size 40 /Root 1 0 R /XRefStm 99999 >>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
A 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.
|
|
31
|
+
|
|
32
|
+
## Data fields
|
|
33
|
+
|
|
34
|
+
`offset` (where `/XRefStm` points).
|
package/docs/llms.txt
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Lamina PDF
|
|
2
|
+
|
|
3
|
+
> Lamina PDF creates, reads, edits and saves PDF files from TypeScript and Rust: one Rust core, run as WebAssembly in browsers, Node, Deno, Bun and Cloudflare Workers (`@laminapdf/sdk`), or natively from the `laminapdf` crate. Positions are top-left points of the displayed page and page indexes start at 0; every error has a stable code with its own page.
|
|
4
|
+
|
|
5
|
+
These docs describe version 0.2.0-alpha.4, the version of this package. The same docs are online at https://docs.laminapdf.com/llms.txt.
|
|
6
|
+
|
|
7
|
+
- [Using Lamina PDF with coding agents](AGENTS.md)
|
|
8
|
+
- [What old names became](renames.json)
|
|
9
|
+
|
|
10
|
+
## Start here
|
|
11
|
+
|
|
12
|
+
- [Lamina PDF](pages/index.md): Create, read, edit and save PDFs from Rust and TypeScript, with one Rust core that runs natively and as WebAssembly.
|
|
13
|
+
- [Install](pages/install.md): Install Lamina PDF from npm or crates.io, and find every published package.
|
|
14
|
+
- [Your first PDF](pages/start/first-pdf.md): Write hello.pdf from Node in five minutes.
|
|
15
|
+
- [Use Lamina PDF with coding agents](pages/start/coding-agents.md): Point a coding agent at the docs inside the installed package, so its answers match the version you use.
|
|
16
|
+
|
|
17
|
+
## Your runtime
|
|
18
|
+
|
|
19
|
+
- [Browser and bundlers](pages/quick-start/browser.md): Load @laminapdf/sdk in a web page through Vite or another bundler.
|
|
20
|
+
- [Node](pages/quick-start/node.md): Generate a PDF from Node with @laminapdf/sdk.
|
|
21
|
+
- [Deno](pages/quick-start/deno.md): Use @laminapdf/sdk in Deno.
|
|
22
|
+
- [Bun](pages/quick-start/bun.md): Use @laminapdf/sdk in Bun.
|
|
23
|
+
- [Cloudflare Workers](pages/quick-start/workers.md): Generate PDFs per request in a Cloudflare Worker.
|
|
24
|
+
- [Rust](pages/quick-start/rust.md): Generate a PDF with the Lamina PDF crate.
|
|
25
|
+
|
|
26
|
+
## Concepts
|
|
27
|
+
|
|
28
|
+
- [How Lamina PDF thinks about a PDF](pages/concepts/how-lamina-thinks.md): Bytes in and out, the same bytes every time, edits kept apart from the opened file, two ways to save, memory you free, three JavaScript modules.
|
|
29
|
+
- [Pages and coordinates](pages/concepts/pages-and-coordinates.md): Points from the top-left corner of the page as it's displayed, y going down, and pages numbered from 0.
|
|
30
|
+
- [Errors and warnings](pages/concepts/errors-and-warnings.md): One coded error shape on every surface, and warnings for what Lamina PDF repaired.
|
|
31
|
+
- [What Lamina PDF writes and what it borrows](pages/concepts/dependencies.md): Lamina PDF writes the parts that decide a PDF's bytes, borrows the rest from permissive projects, and pins and checks everything it borrows.
|
|
32
|
+
- [How a page is drawn](pages/concepts/drawing-pages.md): A renderer turns a page into pixels; Lamina PDF reads the page's text, links and selection itself, from the same bytes.
|
|
33
|
+
|
|
34
|
+
## Create PDFs
|
|
35
|
+
|
|
36
|
+
- [Text and fonts](pages/guides/fonts-and-text.md): Draw text in any script with your own fonts or the standard 14, one line at a time or as a paragraph that wraps in a box.
|
|
37
|
+
- [Shapes, colour and images](pages/guides/images-and-vectors.md): Draw lines, shapes and images on a page, in any colour, with transparency, clipping and transforms.
|
|
38
|
+
- [Links, outlines and metadata](pages/guides/links-outlines-and-metadata.md): Add links, named destinations, an outline, page labels, viewer preferences and metadata to a document.
|
|
39
|
+
- [Pages that flow](pages/guides/flowing-pages.md): Pour headings, paragraphs, tables and images, or a Markdown file, into as many pages as they need, with footnotes, a contents page and "Page X of Y" footers.
|
|
40
|
+
- [Accessible PDFs](pages/guides/accessible-pdfs.md): Build PDF/UA-1 documents that screen readers can follow, with every machine-checkable rule checked when the builder finishes.
|
|
41
|
+
- [Archival PDFs](pages/guides/archival-pdfs.md): Build PDF/A-2b, PDF/A-2u and PDF/A-3b documents that look the same in years to come, and attach files such as an e-invoice's XML.
|
|
42
|
+
|
|
43
|
+
## Read and edit PDFs
|
|
44
|
+
|
|
45
|
+
- [Edit a PDF](pages/guides/edit-a-pdf.md): Change an existing PDF in transactions you can undo, and replay edits stored as JSON.
|
|
46
|
+
- [Save a PDF](pages/guides/save-a-pdf.md): Save edits as a revision appended to the original bytes or as a whole new file, and see when a file refuses a save.
|
|
47
|
+
- [Open password-protected files](pages/guides/password-protected-files.md): Open PDFs that need a password, ask again when it is missing or wrong, and save them afterwards.
|
|
48
|
+
- [Open files you don't trust](pages/guides/untrusted-files.md): Cap how much work an uploaded PDF can make Lamina PDF do, and refuse damaged files with strict mode.
|
|
49
|
+
- [Open large files by ranges](pages/guides/open-large-files.md): Open a PDF on a web server without downloading all of it, fetching only the byte ranges each read needs.
|
|
50
|
+
|
|
51
|
+
## Production
|
|
52
|
+
|
|
53
|
+
- [Keep the page responsive](pages/guides/keep-the-page-responsive.md): Run Lamina PDF in a Web Worker with @laminapdf/sdk/worker, so a large file never freezes the page.
|
|
54
|
+
- [Test the PDFs you generate](pages/guides/test-generated-pdfs.md): Compare the PDFs your code makes with reference PDFs you approved, byte for byte, and draw both files' pages to see what changed.
|
|
55
|
+
|
|
56
|
+
## Recipes
|
|
57
|
+
|
|
58
|
+
- [Recipes](pages/recipes.md): Complete Lamina PDF programs, from one page of text to an archived invoice, each with its code and the PDF it writes.
|
|
59
|
+
- [Hello, world](pages/recipes/hello.md): One A4 page with a line of text in a standard 14 font, the smallest PDF Lamina PDF writes, in TypeScript and Rust.
|
|
60
|
+
- [An invoice](pages/recipes/invoice.md): A tagged one-page invoice with an embedded font, a table of line items with right-aligned amounts and a total, in TypeScript and Rust.
|
|
61
|
+
- [Multilingual text](pages/recipes/multilingual.md): Latin, Arabic, Devanagari, Hebrew and Chinese on one page, each in its own embedded font, as a shaped line and a wrapped paragraph.
|
|
62
|
+
- [Images and vectors](pages/recipes/images-and-vectors.md): Shapes, a dashed curve, gray, RGB, CMYK and hex colour, transparency, a blend mode, a clip, a rotation, a JPEG and an RGBA image on one page.
|
|
63
|
+
- [Element flow](pages/recipes/element-flow.md): A report whose title, paragraphs and photo each start below wherever the one before ended, with a body that continues on a new page.
|
|
64
|
+
- [Links and outlines](pages/recipes/links-and-outlines.md): Four A5 pages joined by web, page and named links, with a nested outline, page labels that read i, ii, 1, 2 and the outline shown on open.
|
|
65
|
+
- [Generate, then edit](pages/recipes/generate-then-edit.md): Build a one-page draft, open its bytes, retitle it in one transaction and save the edit as a revision after the original bytes.
|
|
66
|
+
- [Extract text for an LLM](pages/recipes/extract-markdown.md): Turn a PDF into Markdown in reading order, JSON with a box for every block, and chunks sized for retrieval, in TypeScript and Rust.
|
|
67
|
+
- [A PDF/UA report](pages/recipes/pdf-ua-report.md): A one-page sales report a screen reader can follow, with headings, a table, a described chart and a link, checked against PDF/UA-1 as it's built.
|
|
68
|
+
- [A PDF/A archive](pages/recipes/pdf-a-archive.md): An order confirmation for long-term archiving, written as PDF/A-2b and as PDF/A-2u that is also PDF/UA-1, with an embedded font and a photo.
|
|
69
|
+
- [A PDF/A-3 invoice with XML](pages/recipes/pdf-a3-invoice-with-xml.md): A PDF/A-3b invoice that carries the same invoice as an attached XML file, for people to read and for software to process.
|
|
70
|
+
- [A 100-page report](pages/recipes/report.md): A 100-page A4 report with two embedded fonts and a table on every page, the document Lamina PDF times in each JavaScript runtime.
|
|
71
|
+
- [Open someone else's file](pages/recipes/open-untrusted.md): Open an uploaded PDF with an optional password and tighter limits, and get its page count and warnings, or the error, back as a value.
|
|
72
|
+
- [Save in a Web Worker](pages/recipes/web-worker-client.md): Open and save a PDF in a Web Worker, so a large file never freezes the page, with progress as it saves and a signal that stops the work.
|
|
73
|
+
- [A Cloudflare Worker](pages/recipes/cloudflare-worker.md): A Cloudflare Worker that returns a one-page letter per request, in a standard 14 font or a bundled one, and a 400 for a name the font can't draw.
|
|
74
|
+
|
|
75
|
+
## Troubleshooting
|
|
76
|
+
|
|
77
|
+
- [Troubleshooting](pages/troubleshooting.md): The problems people hit with Lamina PDF, from a missing init() to a signed file that won't save, with the cause and the fix of each.
|
|
78
|
+
|
|
79
|
+
## Reference
|
|
80
|
+
|
|
81
|
+
- [Rust API (rustdoc)](pages/reference/rust.md): Where to read the API documentation of the Lamina PDF crates.
|
|
82
|
+
- [Error and warning codes](pages/reference/codes.md): Every code Lamina PDF raises, generated from fixtures/codes.json when the site is built.
|
|
83
|
+
- [Stability policy](pages/reference/stability.md): What a Lamina PDF version number promises, and what stays the same in every version.
|
|
84
|
+
- [Benchmarks](pages/reference/benchmarks.md): How fast Lamina PDF makes PDFs in each runtime, how large its WebAssembly files are, and how well it extracts text on a public benchmark.
|
|
85
|
+
- [Changelog](pages/reference/changelog.md): What changed in each Lamina PDF release, newest first.
|
|
86
|
+
- [Glossary](pages/reference/glossary.md): The PDF and Lamina PDF terms the docs use, each in a sentence or two.
|
|
87
|
+
|
|
88
|
+
## API reference
|
|
89
|
+
|
|
90
|
+
- [@laminapdf/sdk](api/README.md)
|
|
91
|
+
|
|
92
|
+
## Optional
|
|
93
|
+
|
|
94
|
+
- [Error and warning codes](errors/README.md)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "What Lamina PDF writes and what it borrows"
|
|
3
|
+
description: "Lamina PDF writes the parts that decide a PDF's bytes, borrows the rest from permissive projects, and pins and checks everything it borrows."
|
|
4
|
+
version: "0.2.0-alpha.4"
|
|
5
|
+
url: "https://docs.laminapdf.com/concepts/dependencies/"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# What Lamina PDF writes and what it borrows
|
|
9
|
+
|
|
10
|
+
Lamina PDF writes the parts that decide a PDF's bytes, borrows the rest from permissive projects, and pins and checks everything it borrows.
|
|
11
|
+
|
|
12
|
+
Add `@laminapdf/viewer` to your app, and it downloads about 2 MB of WebAssembly from a project called EmbedPDF. That WebAssembly is PDFium, the C++ engine behind Chrome's PDF viewer. You may wonder why a Rust library ships someone else's C++, and what else comes along with it. Lamina PDF follows one rule: it writes the parts that decide what your PDF contains. It borrows the rest only from permissively licensed projects that do the job better, and it pins and checks every borrowed piece.
|
|
13
|
+
|
|
14
|
+
## Lamina PDF writes the parts that decide your bytes
|
|
15
|
+
|
|
16
|
+
Lamina PDF's own Rust code reads, edits and saves your PDFs. Those parts take files from strangers, and they decide every byte of the file you save. A bug there corrupts documents or breaks signatures, so Lamina PDF owns them. Rust also keeps that code memory-safe while it reads damaged or hostile files. And because Lamina PDF wrote it, the same input gives the same bytes in a browser, on a server and inside a Cloudflare Worker.
|
|
17
|
+
|
|
18
|
+
## It borrows the work others already do well
|
|
19
|
+
|
|
20
|
+
Fonts and text shaping are deep problems with good open-source answers, so Lamina PDF reuses them. `skrifa` reads font files, `subsetter` cuts them down to the glyphs you use, and HarfRust shapes Arabic, Hindi and other scripts. ICU4X finds line breaks, and `pdf-writer` writes PDF syntax. Each one is permissively licensed Rust that compiles to WebAssembly. Lamina PDF measured krilla, a complete PDF generator built on the same crates. But krilla can't add content to a file you opened, so Lamina PDF builds on the crates beneath it.
|
|
21
|
+
|
|
22
|
+
## Drawing pages is the one job it hands to PDFium
|
|
23
|
+
|
|
24
|
+
Lamina PDF hands one big job to another project: turning pages into pixels. Real-world PDFs often break the specification, and PDFium draws them for everyone who opens a PDF in Chrome. Lamina PDF measured the alternative, hayro, a renderer written in Rust. It drew 91% of 2,196 test pages the same as PDFium or close to it, and most of the rest went wrong on fonts and annotations. A viewer that becomes an editor needs that fidelity from the start, so PDFium draws the pages. `@embedpdf/pdfium` is PDFium compiled to WebAssembly.
|
|
25
|
+
|
|
26
|
+
## PDFium only draws, and only in a Web Worker
|
|
27
|
+
|
|
28
|
+
PDFium is C++ reading files you didn't write, so Lamina PDF keeps it at arm's length. The viewer runs it inside a Web Worker, away from your page. PDFium never changes the document either: every edit goes through Lamina PDF, which also writes the saved file. Text, selection and links come from Lamina PDF's own model of the file, never from the renderer. Lamina PDF also checks that the two agree: its tests compare each page's size and every character's position with PDFium's. [How a page is drawn](../../pages/concepts/drawing-pages.md) shows the two side by side.
|
|
29
|
+
|
|
30
|
+
## You pay for PDFium only when you show pages
|
|
31
|
+
|
|
32
|
+
`@laminapdf/sdk`, the package that creates, reads and edits PDFs, depends only on `@laminapdf/wasm`, Lamina PDF's own WebAssembly. PDFium comes only with `@laminapdf/render-pdfium` and the viewer built on it. For pages as pixels on a server or in a Cloudflare Worker, `@laminapdf/render-hayro` is the lighter choice: pure Rust, with no C++, and a smaller download than PDFium. Every night, Lamina PDF's tests draw thousands of pages with both renderers, so hayro can take over once it matches PDFium.
|
|
33
|
+
|
|
34
|
+
## Every dependency is permissively licensed
|
|
35
|
+
|
|
36
|
+
Lamina PDF is `MIT OR Apache-2.0`, so you can use it in a closed product or a paid service. That promise only holds if every dependency allows the same. So Lamina PDF accepts no GPL, AGPL or LGPL code in anything it ships, and a licence check enforces the rule on every change. MuPDF is a fine PDF engine, but it is AGPL, so Lamina PDF never uses it, in any form. Copyleft tools such as veraPDF run as separate programs in Lamina PDF's tests and are never shipped.
|
|
37
|
+
|
|
38
|
+
## Everything borrowed is pinned and checked
|
|
39
|
+
|
|
40
|
+
A dependency can change under you, so Lamina PDF's lockfiles pin every one to an exact version. For PDFium, Lamina PDF goes further: the build checks the SHA-256 of `pdfium.wasm`, and any other file fails the build. `@embedpdf/pdfium` ships no notices for the libraries compiled into it, such as libpng, zlib and FreeType. So `@laminapdf/render-pdfium` includes a third-party notices file, and an app that bundles it ships that file too. An update to PDFium arrives as its own reviewed change, and the next release picks up its security fixes.
|
|
41
|
+
|
|
42
|
+
## What this means for your app
|
|
43
|
+
|
|
44
|
+
You can build Lamina PDF into closed software, and `@laminapdf/sdk` brings no third-party engine with it. Add the viewer when you need to show pages, and ship PDFium's notices file with it. Next, see [how Lamina PDF thinks about a PDF](../../pages/concepts/how-lamina-thinks.md), or start with [your first PDF](../../pages/start/first-pdf.md).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "How a page is drawn"
|
|
3
|
+
description: "A renderer turns a page into pixels; Lamina PDF reads the page's text, links and selection itself, from the same bytes."
|
|
4
|
+
version: "0.2.0-alpha.4"
|
|
5
|
+
url: "https://docs.laminapdf.com/concepts/drawing-pages/"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# How a page is drawn
|
|
9
|
+
|
|
10
|
+
A renderer turns a page into pixels; Lamina PDF reads the page's text, links and selection itself, from the same bytes.
|
|
11
|
+
|
|
12
|
+
Two readers go through every page you show. A renderer, PDFium or hayro, turns the page into pixels. Lamina PDF's own model of the file gives you everything you can select, search or click: the text, the links and the selection boxes. Both read the same bytes.
|
|
13
|
+
|
|
14
|
+
*A renderer draws the pixels; Lamina PDF reads the text from the same bytes.*
|
|
15
|
+
|
|
16
|
+
Two readers use your PDF's bytes. To draw, a render request names the page, the scale, the tile to draw (its clip) and a rotation. The request becomes a tile plan, which fixes the size in pixels, and a renderer, PDFium or hayro, draws the tile as RGBA pixels. Text, links and selection never come from the renderer. Lamina PDF reads them from its own model of the same bytes, the model that also edits and saves the file.
|
|
17
|
+
|
|
18
|
+
## You ask for a tile, and get RGBA pixels back
|
|
19
|
+
|
|
20
|
+
A render request names the page, the scale in pixels per point, and optionally a clip and a rotation. The clip is the part of the page you want, as a rectangle in pixels: a viewer asks for tiles of a large page rather than the whole page at once, so it only draws what's on screen. A scale of 2 draws 144 pixels per inch, sharp on most screens.
|
|
21
|
+
|
|
22
|
+
Before it draws, the renderer turns the request into a tile plan: the whole page's size in pixels after the rotation, and the tile inside it. The renderer checks the tile against its limits first: by default, a tile wider or taller than 16,384 pixels, or larger than about 16.7 million pixels, fails with `render-too-large` instead of filling your memory. The renderer then draws the tile as straight RGBA, four bytes a pixel, row by row from the top-left corner.
|
|
23
|
+
|
|
24
|
+
This example draws a page with hayro and reads the same page's text:
|
|
25
|
+
|
|
26
|
+
`packages/sdk/examples/draw-page.ts`:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// Draws one page to pixels and reads the same page's text, to show who does which job: a renderer
|
|
30
|
+
// turns the page into RGBA pixels, and Lamina PDF reads the text from its own model of the file.
|
|
31
|
+
// Both read the same bytes. In Node, load the hayro renderer with the module's bytes:
|
|
32
|
+
//
|
|
33
|
+
// const wasm = fileURLToPath(import.meta.resolve("@laminapdf/wasm/lamina_pdf_render_hayro.wasm"));
|
|
34
|
+
// const renderer = await initHayro(readFileSync(wasm));
|
|
35
|
+
//
|
|
36
|
+
// Text needs Lamina PDF's text or extract module: `await init({ text: true })`.
|
|
37
|
+
|
|
38
|
+
import { bytesSource, type RenderBackend } from "@laminapdf/render";
|
|
39
|
+
import { Document } from "@laminapdf/sdk";
|
|
40
|
+
|
|
41
|
+
/** Pixels per point: 2 draws at 144 dpi, sharp on most screens. */
|
|
42
|
+
const SCALE = 2;
|
|
43
|
+
|
|
44
|
+
/** One page as pixels, with its text beside them. */
|
|
45
|
+
export interface DrawnPage {
|
|
46
|
+
readonly width: number;
|
|
47
|
+
readonly height: number;
|
|
48
|
+
/** Straight RGBA, four bytes a pixel, row by row from the top-left corner. */
|
|
49
|
+
readonly pixels: Uint8ClampedArray;
|
|
50
|
+
readonly text: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Draws page `index` of `bytes` with `renderer` and reads its text with Lamina PDF. */
|
|
54
|
+
export function drawPage(renderer: RenderBackend, bytes: Uint8Array, index: number): DrawnPage {
|
|
55
|
+
// The renderer draws the page and nothing else: it never supplies text, links or selection.
|
|
56
|
+
using drawing = renderer.open(bytesSource(bytes));
|
|
57
|
+
const { width, height, pixels } = drawing.render({ page: index, scale: SCALE });
|
|
58
|
+
// The text comes from Lamina PDF, which also edits and saves the file, so what you select is what
|
|
59
|
+
// an edit changes.
|
|
60
|
+
using doc = Document.open(bytes);
|
|
61
|
+
const text = doc.page(index).text().text();
|
|
62
|
+
return { width, height, pixels, text };
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## The text never comes from the renderer
|
|
67
|
+
|
|
68
|
+
A renderer draws glyphs, but its idea of the text can differ from the file's: it may guess the order of letters or lose the characters behind a ligature. So Lamina PDF reads text, links and selection from its own model of the file, the same model that edits and saves it. What you select is what `page.text()` returns and what an edit changes.
|
|
69
|
+
|
|
70
|
+
The two readers can still disagree on where a glyph sits, since each reads the fonts its own way. Lamina PDF's tests compare each page's size and every character's position with PDFium's, and a difference becomes a bug to fix.
|
|
71
|
+
|
|
72
|
+
## PDFium in the browser, hayro on servers
|
|
73
|
+
|
|
74
|
+
PDFium, the engine behind Chrome's PDF viewer, draws the pages in the viewer. It runs in a Web Worker, away from your page, and only draws: it never changes the document. hayro is a renderer written in Rust. It's the lighter choice on a server or in a Cloudflare Worker, and it draws in the tests that compare pages. [What Lamina PDF writes and what it borrows](../../pages/concepts/dependencies.md) explains why each one is there.
|
|
75
|
+
|
|
76
|
+
Next, see [how to compare pages in tests](../../pages/guides/test-generated-pdfs.md), which draws two PDFs and counts the pixels that differ.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Errors and warnings"
|
|
3
|
+
description: "One coded error shape on every surface, and warnings for what Lamina PDF repaired."
|
|
4
|
+
version: "0.2.0-alpha.4"
|
|
5
|
+
url: "https://docs.laminapdf.com/concepts/errors-and-warnings/"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Errors and warnings
|
|
9
|
+
|
|
10
|
+
One coded error shape on every surface, and warnings for what Lamina PDF repaired.
|
|
11
|
+
|
|
12
|
+
Real PDFs are often damaged, so Lamina PDF separates a call that failed from one that worked around a problem. An error stops the call. A warning lets it finish and tells you what was repaired, dropped or approximated. Both carry a stable code your program can branch on.
|
|
13
|
+
|
|
14
|
+
## Errors stop the call
|
|
15
|
+
|
|
16
|
+
In TypeScript an error is a `LaminaPdfError`, and in Rust it's a `laminapdf::Error`. Branch on its `code`, never on its `message`: `isLaminaPdfError(e, "wrong-password")` checks the code and narrows the type in one step.
|
|
17
|
+
|
|
18
|
+
## Warnings say what was repaired
|
|
19
|
+
|
|
20
|
+
A warning means the call went ahead, but Lamina PDF had to repair, drop or approximate something in the file. Read them with `doc.warnings` in TypeScript or `doc.warnings()` in Rust. Show them to the user when the difference matters, such as a page that may not look the way its author intended.
|
|
21
|
+
|
|
22
|
+
## Every surface has the same fields
|
|
23
|
+
|
|
24
|
+
Errors and warnings have the same fields in TypeScript and Rust: `code`, `kind`, `message`, `hint`, `location`, `data` and `docsUrl`. A usage error can also carry `didYouMean`, the closest names to one Lamina PDF didn't recognise. `toJSON()` in TypeScript gives the same object as `serde` does in Rust, so a server and a browser can pass them to each other unchanged.
|
|
25
|
+
|
|
26
|
+
*A call stops with an error or completes with warnings, and both carry the same core fields.*
|
|
27
|
+
|
|
28
|
+
A call either stops with a `LaminaPdfError` or completes, with any warnings on `doc.warnings`. Errors and warnings carry the same fields: `code`, `kind`, `message`, `hint`, `location`, `data` and `docsUrl`; an error can also carry `didYouMean`. Text from the document goes only in `data`, never in `message` or `hint`.
|
|
29
|
+
|
|
30
|
+
- **`kind` sorts errors by who can fix them**, such as `input`, `limit`, `usage`, `state` or `io`. For example, a server returns a 4xx status for `input` and `limit` and a 5xx for the rest, as the [Cloudflare Workers quick start](../../pages/quick-start/workers.md) does.
|
|
31
|
+
- **Text from the document is untrusted**, so it never appears in `message` or `hint`. It goes only in `data`, where you can escape it before you show it.
|
|
32
|
+
|
|
33
|
+
This example opens someone else's file and returns the error's code and hint as a value, instead of letting the error escape:
|
|
34
|
+
|
|
35
|
+
`packages/sdk/examples/open-untrusted.ts`:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// Open bytes from someone else: an optional password, tighter limits than the defaults, and the
|
|
39
|
+
// outcome as a value rather than an exception. The example writes nothing: it returns what it found.
|
|
40
|
+
import { Document, isLaminaPdfError } from "@laminapdf/sdk";
|
|
41
|
+
|
|
42
|
+
const MiB = 1024 * 1024;
|
|
43
|
+
/** Limits for an upload endpoint: each decoded stream, and all of them together. */
|
|
44
|
+
const LIMITS = { maxStreamDecoded: 16 * MiB, maxDecodedTotal: 64 * MiB } as const;
|
|
45
|
+
|
|
46
|
+
/** What opening a file gave: its page count and warning codes, or why it didn't open. */
|
|
47
|
+
export type Opened =
|
|
48
|
+
| { readonly ok: true; readonly pages: number; readonly warnings: readonly string[] }
|
|
49
|
+
| { readonly ok: false; readonly code: string; readonly hint: string | undefined };
|
|
50
|
+
|
|
51
|
+
/** Opens `bytes`, with `password` if the file needs one, under {@link LIMITS}. Needs `init()`. */
|
|
52
|
+
export function openUntrusted(bytes: Uint8Array, password?: string): Opened {
|
|
53
|
+
try {
|
|
54
|
+
using doc = Document.open(bytes, { ...(password === undefined ? {} : { password }), limits: LIMITS });
|
|
55
|
+
// Repairs and dropped values are warnings: the file opened, but not exactly as written.
|
|
56
|
+
return { ok: true, pages: doc.pageCount, warnings: doc.warnings.map((w) => w.code) };
|
|
57
|
+
} catch (e) {
|
|
58
|
+
// `password-required`, `wrong-password`, `not-pdf`, `limit-exceeded`, …: a coded error whose
|
|
59
|
+
// message and hint never contain text from the file. Anything else is a bug: let it throw.
|
|
60
|
+
if (isLaminaPdfError(e)) return { ok: false, code: e.code, hint: e.hint };
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The [error and warning codes](../../errors/README.md) list every code with its kind, a summary and a hint.
|
|
67
|
+
|
|
68
|
+
Next: make a PDF with [text and fonts](../../pages/guides/fonts-and-text.md).
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "How Lamina PDF thinks about a PDF"
|
|
3
|
+
description: "Bytes in and out, the same bytes every time, edits kept apart from the opened file, two ways to save, memory you free, three JavaScript modules."
|
|
4
|
+
version: "0.2.0-alpha.4"
|
|
5
|
+
url: "https://docs.laminapdf.com/concepts/how-lamina-thinks/"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# How Lamina PDF thinks about a PDF
|
|
9
|
+
|
|
10
|
+
Bytes in and out, the same bytes every time, edits kept apart from the opened file, two ways to save, memory you free, three JavaScript modules.
|
|
11
|
+
|
|
12
|
+
The rest of the docs build on six ideas. The first two follow from one choice: Lamina PDF treats a PDF as bytes, which you give it and it gives back.
|
|
13
|
+
|
|
14
|
+
## Bytes in, bytes out
|
|
15
|
+
|
|
16
|
+
Lamina PDF's core never touches the file system or the network, and it never reads the clock. Your app reads the file, hands Lamina PDF the bytes and stores whatever comes back. The only fetches are the ones you ask for, such as opening a remote file by range requests. That's why the same code runs in a browser, on a server and inside a Cloudflare Worker.
|
|
17
|
+
|
|
18
|
+
## Same input, same bytes
|
|
19
|
+
|
|
20
|
+
Because Lamina PDF reads no clock, you pass in dates and file IDs yourself. It then writes the same bytes for the same input, on every run and every platform, so you can test, cache and compare its output byte for byte.
|
|
21
|
+
|
|
22
|
+
## The file you opened never changes
|
|
23
|
+
|
|
24
|
+
When you open a document, Lamina PDF keeps its bytes exactly as they were. Every edit goes into an [overlay](../../pages/reference/glossary.md#overlay) on top of them. Reading the document reads the overlay first, so you see your edits; undo and redo move through them.
|
|
25
|
+
|
|
26
|
+
*Edits go into an overlay; saving appends them or writes a new file.*
|
|
27
|
+
|
|
28
|
+
Opening a PDF gives you a document that holds the file's original bytes, which never change, and an overlay for your edits. `doc.transact`, `undo` and `redo` write only to the overlay, and reading the document reads the overlay first. A save then either appends the edits after the original bytes (`incremental`) or writes every byte of a new file (`fullRewrite`). An incremental save leaves the original bytes untouched, so signatures over them stay valid.
|
|
29
|
+
|
|
30
|
+
## Two ways to save
|
|
31
|
+
|
|
32
|
+
A save writes the overlay out in one of two ways:
|
|
33
|
+
|
|
34
|
+
- **[Incremental save](../../pages/reference/glossary.md#incremental-save):** the original bytes, unchanged, followed by a new revision that holds your edits. Signatures over the original stay valid.
|
|
35
|
+
- **[Full rewrite](../../pages/reference/glossary.md#full-rewrite):** the whole file written again, smaller and tidier, but no longer the original bytes.
|
|
36
|
+
|
|
37
|
+
The default, `auto`, saves incrementally when it can. A signed file saves only incrementally, and a save fails if your edits break its signatures, unless you pass `invalidateSignatures: true`. [Save a PDF](../../pages/guides/save-a-pdf.md) has the details.
|
|
38
|
+
|
|
39
|
+
This example builds a document, opens its bytes, changes the title and saves incrementally. The saved file starts with the original bytes:
|
|
40
|
+
|
|
41
|
+
**TypeScript**
|
|
42
|
+
|
|
43
|
+
`packages/sdk/examples/generate-then-edit.ts`:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// Generate a document, then open its bytes, edit them and save incrementally. The edit changes
|
|
47
|
+
// the Info dictionary in one transaction, and the incremental save keeps the generated bytes as
|
|
48
|
+
// they are, with one revision appended.
|
|
49
|
+
import { Builder, type BuilderOptions, Document, Font } from "@laminapdf/sdk";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The generated file followed by the edit's revision. Needs `init({ text: true })`.
|
|
53
|
+
*
|
|
54
|
+
* @param options - can fix the file ID, the producer and the creation date.
|
|
55
|
+
* @param editedAt - the modification date to record, as a PDF date string.
|
|
56
|
+
*/
|
|
57
|
+
export function generateThenEdit(options: BuilderOptions = {}, editedAt?: string): Uint8Array<ArrayBuffer> {
|
|
58
|
+
return edit(generate(options), editedAt);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A one-page draft, saved (a new document is always saved as a full rewrite). */
|
|
62
|
+
export function generate(options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
|
|
63
|
+
using b = new Builder(options);
|
|
64
|
+
const page = b.page("A4");
|
|
65
|
+
page.surface().text("Draft report", { font: Font.standard("Helvetica"), size: 20, at: [72, 96] });
|
|
66
|
+
page.finish();
|
|
67
|
+
b.metadata({ title: "Draft report" });
|
|
68
|
+
using doc = b.finish();
|
|
69
|
+
return doc.save();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Opens `bytes`, edits the Info dictionary in one transaction (which `undo` could revert) and saves incrementally. */
|
|
73
|
+
function edit(bytes: Uint8Array, editedAt?: string): Uint8Array<ArrayBuffer> {
|
|
74
|
+
using doc = Document.open(bytes);
|
|
75
|
+
doc.transact((tx) => tx.setInfo({ title: "Final report", author: "Ada Lovelace" }, editedAt));
|
|
76
|
+
return doc.save({ mode: "incremental" });
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Result:** [generate_then_edit.pdf](https://docs.laminapdf.com/examples/pdf/generate_then_edit.pdf)
|
|
81
|
+
|
|
82
|
+
## You free what you create
|
|
83
|
+
|
|
84
|
+
In JavaScript, documents and builders live in WebAssembly memory. The garbage collector frees them eventually, but late, so free each one when you're done: `using doc = …` frees it at the end of the block, and `doc.free()` frees it at once. A short script can skip this, but a long-running server shouldn't.
|
|
85
|
+
|
|
86
|
+
## Three modules in JavaScript
|
|
87
|
+
|
|
88
|
+
In JavaScript, Lamina PDF is WebAssembly, split into three modules so a page downloads only what it uses:
|
|
89
|
+
|
|
90
|
+
- **Base:** open, read, edit and save; vector and image pages.
|
|
91
|
+
- **Extract:** everything in base, plus text extraction.
|
|
92
|
+
- **Text:** everything in base, plus fonts, shaped text and extraction.
|
|
93
|
+
|
|
94
|
+
`init()` loads the base module, and `init({ text: true })` or `init({ extract: true })` loads one of the others. Call `loadText()` or `loadExtract()` to add a module later, when a page first needs it. Rust has one crate, with features instead.
|
|
95
|
+
|
|
96
|
+
*The three modules come in @laminapdf/wasm, under @laminapdf/sdk.*
|
|
97
|
+
|
|
98
|
+
`@laminapdf/sdk` depends on `@laminapdf/wasm`, whose WebAssembly the SDK loads as three modules: base, extract and text. `@laminapdf/viewer` depends on `@laminapdf/sdk`, on `@laminapdf/render` and on both renderers that implement it, `@laminapdf/render-pdfium` and `@laminapdf/render-hayro`. In Rust, the API is one crate, `laminapdf`, with features instead of modules.
|