@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,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Multilingual text"
|
|
3
|
+
description: "Latin, Arabic, Devanagari, Hebrew and Chinese on one page, each in its own embedded font, as a shaped line and a wrapped paragraph."
|
|
4
|
+
version: "0.2.0-alpha.4"
|
|
5
|
+
url: "https://docs.laminapdf.com/recipes/multilingual/"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Multilingual text
|
|
9
|
+
|
|
10
|
+
Latin, Arabic, Devanagari, Hebrew and Chinese on one page, each in its own embedded font, as a shaped line and a wrapped paragraph.
|
|
11
|
+
|
|
12
|
+
You'll put five scripts on one page: Latin, Arabic, Devanagari, Hebrew and Chinese, each as a single line and as a paragraph wrapped in a box. Start from it whenever your text goes beyond Latin, or mixes left-to-right and right-to-left writing.
|
|
13
|
+
|
|
14
|
+
The function takes one font file per script, draws each script's line and paragraph, and returns the PDF's bytes. In TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
|
|
15
|
+
|
|
16
|
+
**TypeScript**
|
|
17
|
+
|
|
18
|
+
`packages/sdk/examples/multilingual.ts`:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// Five scripts on one page, each as a single line and as a wrapped paragraph. The scripts are
|
|
22
|
+
// Latin, Arabic and Hebrew (right to left), Devanagari and simplified Chinese; each is shaped and
|
|
23
|
+
// bidi-resolved, and each paragraph wraps in a text box. Each script needs a font that covers it,
|
|
24
|
+
// such as the Noto fonts (OFL).
|
|
25
|
+
import { Builder, type BuilderOptions, type Direction, Font } from "@laminapdf/sdk";
|
|
26
|
+
|
|
27
|
+
/** Each script's line, its paragraph and the paragraph's direction, in the order of the fonts. */
|
|
28
|
+
export const SCRIPTS: readonly (readonly [line: string, paragraph: string, direction: Direction])[] = [
|
|
29
|
+
[
|
|
30
|
+
"Latin: the office staff fill the affine waffle flask",
|
|
31
|
+
"A paragraph breaks at spaces and fits the width of its box, line by line.",
|
|
32
|
+
"ltr",
|
|
33
|
+
],
|
|
34
|
+
["مرحبا بالعالم السلام عليكم", "مرحبا بالعالم السلام عليكم مرحبا بالعالم السلام عليكم", "rtl"],
|
|
35
|
+
["नमस्ते दुनिया। हिन्दी पाठ का परीक्षण", "नमस्ते दुनिया। हिन्दी पाठ का परीक्षण नमस्ते दुनिया।", "ltr"],
|
|
36
|
+
["שלום עולם וברוכים הבאים", "שלום עולם וברוכים הבאים שלום עולם וברוכים הבאים", "rtl"],
|
|
37
|
+
["你好世界,这是中文字体子集测试。", "你好世界,这是中文字体子集测试。你好世界,这是中文。", "ltr"],
|
|
38
|
+
];
|
|
39
|
+
const MARGIN = 56;
|
|
40
|
+
const FIRST_BASELINE = 80;
|
|
41
|
+
/** The distance from one script's line to the next one's. */
|
|
42
|
+
const SCRIPT_HEIGHT = 140;
|
|
43
|
+
/** The paragraph's box: this far below the line, this wide and this tall. */
|
|
44
|
+
const PARAGRAPH_DROP = 14;
|
|
45
|
+
const PARAGRAPH_WIDTH = 220;
|
|
46
|
+
const PARAGRAPH_HEIGHT = 100;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The document's bytes. Needs `init({ text: true })`.
|
|
50
|
+
*
|
|
51
|
+
* @param fonts - one font file per script, in the order of {@link SCRIPTS}.
|
|
52
|
+
* @param options - can fix the file ID, the producer and the creation date.
|
|
53
|
+
*/
|
|
54
|
+
export function multilingual(fonts: readonly Uint8Array[], options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
|
|
55
|
+
if (fonts.length !== SCRIPTS.length) throw new Error(`${SCRIPTS.length} fonts, one per script, are needed`);
|
|
56
|
+
using b = new Builder(options);
|
|
57
|
+
const loaded = fonts.map((bytes) => Font.fromBytes(bytes));
|
|
58
|
+
for (const font of loaded) b.font(font);
|
|
59
|
+
const page = b.page("A4");
|
|
60
|
+
const s = page.surface();
|
|
61
|
+
SCRIPTS.forEach(([line, paragraph, direction], row) => {
|
|
62
|
+
const font = loaded[row];
|
|
63
|
+
if (font === undefined) return;
|
|
64
|
+
const y = FIRST_BASELINE + SCRIPT_HEIGHT * row;
|
|
65
|
+
s.text(line, { font, size: 16, at: [MARGIN, y] });
|
|
66
|
+
// A narrow box breaks the paragraph into lines; right-to-left paragraphs start at the right.
|
|
67
|
+
const box = [MARGIN, y + PARAGRAPH_DROP, PARAGRAPH_WIDTH, PARAGRAPH_HEIGHT] as const;
|
|
68
|
+
s.textBox(paragraph, { font, size: 12, box, direction, align: "start" });
|
|
69
|
+
});
|
|
70
|
+
page.finish();
|
|
71
|
+
using doc = b.finish();
|
|
72
|
+
return doc.save();
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Rust**
|
|
77
|
+
|
|
78
|
+
`crates/laminapdf/examples/multilingual.rs`:
|
|
79
|
+
|
|
80
|
+
````rust
|
|
81
|
+
//! Five scripts on one page, each as a single line and as a wrapped paragraph.
|
|
82
|
+
//!
|
|
83
|
+
//! The scripts are Latin, Arabic and Hebrew (right to left), Devanagari and simplified Chinese;
|
|
84
|
+
//! each is shaped and bidi-resolved, and each paragraph wraps in a text box. Each script needs a
|
|
85
|
+
//! font that covers it, such as the Noto fonts (OFL).
|
|
86
|
+
//!
|
|
87
|
+
//! ```sh
|
|
88
|
+
//! cargo run -p laminapdf --example multilingual -- latin.ttf arabic.ttf devanagari.ttf hebrew.ttf chinese.otf
|
|
89
|
+
//! ```
|
|
90
|
+
|
|
91
|
+
use std::error::Error;
|
|
92
|
+
use std::path::PathBuf;
|
|
93
|
+
|
|
94
|
+
use laminapdf::{
|
|
95
|
+
Alignment, BuildOptions, Builder, Direction, PageSize, PointPt, Pt, RectPt, SaveOptions, TextBoxOptions,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/// Each script's line, its paragraph and the paragraph's direction, in the order of the fonts.
|
|
99
|
+
const SCRIPTS: [(&str, &str, Direction); 5] = [
|
|
100
|
+
(
|
|
101
|
+
"Latin: the office staff fill the affine waffle flask",
|
|
102
|
+
"A paragraph breaks at spaces and fits the width of its box, line by line.",
|
|
103
|
+
Direction::LeftToRight,
|
|
104
|
+
),
|
|
105
|
+
(
|
|
106
|
+
"مرحبا بالعالم السلام عليكم",
|
|
107
|
+
"مرحبا بالعالم السلام عليكم مرحبا بالعالم السلام عليكم",
|
|
108
|
+
Direction::RightToLeft,
|
|
109
|
+
),
|
|
110
|
+
(
|
|
111
|
+
"नमस्ते दुनिया। हिन्दी पाठ का परीक्षण",
|
|
112
|
+
"नमस्ते दुनिया। हिन्दी पाठ का परीक्षण नमस्ते दुनिया।",
|
|
113
|
+
Direction::LeftToRight,
|
|
114
|
+
),
|
|
115
|
+
(
|
|
116
|
+
"שלום עולם וברוכים הבאים",
|
|
117
|
+
"שלום עולם וברוכים הבאים שלום עולם וברוכים הבאים",
|
|
118
|
+
Direction::RightToLeft,
|
|
119
|
+
),
|
|
120
|
+
(
|
|
121
|
+
"你好世界,这是中文字体子集测试。",
|
|
122
|
+
"你好世界,这是中文字体子集测试。你好世界,这是中文。",
|
|
123
|
+
Direction::LeftToRight,
|
|
124
|
+
),
|
|
125
|
+
];
|
|
126
|
+
const MARGIN: f64 = 56.0;
|
|
127
|
+
const FIRST_BASELINE: f64 = 80.0;
|
|
128
|
+
/// The distance from one script's line to the next one's.
|
|
129
|
+
const SCRIPT_HEIGHT: f64 = 140.0;
|
|
130
|
+
/// The paragraph's box: this far below the line, this wide and this tall.
|
|
131
|
+
const PARAGRAPH_DROP: f64 = 14.0;
|
|
132
|
+
const PARAGRAPH_WIDTH: f64 = 220.0;
|
|
133
|
+
const PARAGRAPH_HEIGHT: f64 = 100.0;
|
|
134
|
+
|
|
135
|
+
fn main() -> Result<(), Box<dyn Error>> {
|
|
136
|
+
let mut args: Vec<String> = std::env::args().skip(1).collect();
|
|
137
|
+
let out = PathBuf::from(if args.len() > SCRIPTS.len() {
|
|
138
|
+
args.remove(SCRIPTS.len())
|
|
139
|
+
} else {
|
|
140
|
+
"target/examples-out".into()
|
|
141
|
+
});
|
|
142
|
+
let fonts = args.iter().map(std::fs::read).collect::<Result<Vec<_>, _>>()?;
|
|
143
|
+
std::fs::create_dir_all(&out)?;
|
|
144
|
+
std::fs::write(out.join("multilingual.pdf"), build(BuildOptions::new(), fonts)?)?;
|
|
145
|
+
Ok(())
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// The document's bytes, with one font per script in `fonts` (in the order of [`SCRIPTS`]).
|
|
149
|
+
/// `options` can fix the file ID, the producer and the creation date.
|
|
150
|
+
pub(crate) fn build(options: BuildOptions, fonts: Vec<Vec<u8>>) -> Result<Vec<u8>, Box<dyn Error>> {
|
|
151
|
+
if fonts.len() != SCRIPTS.len() {
|
|
152
|
+
return Err(format!("{} fonts, one per script, are needed", SCRIPTS.len()).into());
|
|
153
|
+
}
|
|
154
|
+
let mut b = Builder::new(options);
|
|
155
|
+
let fonts = fonts
|
|
156
|
+
.into_iter()
|
|
157
|
+
.map(|bytes| b.font_bytes(bytes))
|
|
158
|
+
.collect::<Result<Vec<_>, _>>()?;
|
|
159
|
+
let mut page = b.page(PageSize::A4);
|
|
160
|
+
let s = page.surface();
|
|
161
|
+
for (row, ((line, paragraph, direction), font)) in (0_u32..).zip(SCRIPTS.iter().zip(&fonts)) {
|
|
162
|
+
let y = FIRST_BASELINE + SCRIPT_HEIGHT * f64::from(row);
|
|
163
|
+
s.text(line, font, Pt::new(16.0), PointPt::from_points(MARGIN, y))?;
|
|
164
|
+
// A narrow box breaks the paragraph into lines; right-to-left paragraphs start at the right.
|
|
165
|
+
let options = TextBoxOptions::new()
|
|
166
|
+
.with_direction(*direction)
|
|
167
|
+
.with_align(Alignment::Start);
|
|
168
|
+
let area = RectPt::from_points(MARGIN, y + PARAGRAPH_DROP, PARAGRAPH_WIDTH, PARAGRAPH_HEIGHT);
|
|
169
|
+
s.text_box(paragraph, font, Pt::new(12.0), area, &options)?;
|
|
170
|
+
}
|
|
171
|
+
page.finish()?;
|
|
172
|
+
let mut doc = b.finish()?;
|
|
173
|
+
Ok(doc.save(&SaveOptions::new())?.bytes)
|
|
174
|
+
}
|
|
175
|
+
````
|
|
176
|
+
|
|
177
|
+
**Result:** [multilingual.pdf](https://docs.laminapdf.com/examples/pdf/multilingual.pdf)
|
|
178
|
+
|
|
179
|
+
The result is one A4 page, with each script set in its own Noto font. The Result tab shows it, with a link to the PDF.
|
|
180
|
+
|
|
181
|
+
## How it works
|
|
182
|
+
|
|
183
|
+
- `Font.fromBytes` (`font_bytes` in Rust) loads each font, which must cover its script. The builder embeds only the glyphs the page draws.
|
|
184
|
+
- Lamina PDF shapes the text with each font, so Arabic letters join, Devanagari letters combine into clusters and the Latin line forms ligatures.
|
|
185
|
+
- `textBox` breaks each paragraph into lines that fit its 220-point-wide box. It breaks where Unicode's line-breaking rules allow: between words, and between Chinese characters.
|
|
186
|
+
- `direction: "rtl"` (`Direction::RightToLeft`) makes the Arabic and Hebrew paragraphs right to left, so `align: "start"` starts their lines at the box's right edge.
|
|
187
|
+
- Each font is embedded with its [ToUnicode](../../pages/reference/glossary.md#tounicode) mapping, so text copied or extracted from the PDF equals the strings you passed in, ligatures included.
|
|
188
|
+
|
|
189
|
+
## What to change
|
|
190
|
+
|
|
191
|
+
- **Your own scripts:** change `SCRIPTS` and pass one font per row, in the same order. A character the font lacks fails with `missing-glyph`.
|
|
192
|
+
- **The paragraph's box:** `PARAGRAPH_WIDTH` and `PARAGRAPH_HEIGHT` set its size. Lines that don't fit are left out, unless you pass `overflow: "error"` (`Overflow::Error`) to fail with `text-overflow` instead.
|
|
193
|
+
- **Alignment and spacing:** `align` also takes `"end"`, `"left"`, `"right"` or `"center"`, and `lineHeight` (`with_line_height`) sets the distance between baselines, 1.2 times the size by default.
|
|
194
|
+
- **Ligatures and other OpenType features:** `features: ["liga=0"]` on `text` or `textBox` turns the ligatures off. In Rust, call `features` on the run or `with_feature` on the text box's options. See [Text and fonts](../../pages/guides/fonts-and-text.md).
|
|
195
|
+
|
|
196
|
+
Next: [images and vectors](../../pages/recipes/images-and-vectors.md), with shapes, colour, transparency and two images.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Open someone else's file"
|
|
3
|
+
description: "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."
|
|
4
|
+
version: "0.2.0-alpha.4"
|
|
5
|
+
url: "https://docs.laminapdf.com/recipes/open-untrusted/"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Open someone else's file
|
|
9
|
+
|
|
10
|
+
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.
|
|
11
|
+
|
|
12
|
+
You'll open a PDF from someone else, with a password if it needs one and tighter limits than the defaults, and get the outcome as a value. You need this wherever you accept uploads: a stranger's file can be damaged, locked or built to use up memory.
|
|
13
|
+
|
|
14
|
+
The function opens the bytes under its limits and returns the page count and warning codes, or the error's code and hint:
|
|
15
|
+
|
|
16
|
+
`packages/sdk/examples/open-untrusted.ts`:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// Open bytes from someone else: an optional password, tighter limits than the defaults, and the
|
|
20
|
+
// outcome as a value rather than an exception. The example writes nothing: it returns what it found.
|
|
21
|
+
import { Document, isLaminaPdfError } from "@laminapdf/sdk";
|
|
22
|
+
|
|
23
|
+
const MiB = 1024 * 1024;
|
|
24
|
+
/** Limits for an upload endpoint: each decoded stream, and all of them together. */
|
|
25
|
+
const LIMITS = { maxStreamDecoded: 16 * MiB, maxDecodedTotal: 64 * MiB } as const;
|
|
26
|
+
|
|
27
|
+
/** What opening a file gave: its page count and warning codes, or why it didn't open. */
|
|
28
|
+
export type Opened =
|
|
29
|
+
| { readonly ok: true; readonly pages: number; readonly warnings: readonly string[] }
|
|
30
|
+
| { readonly ok: false; readonly code: string; readonly hint: string | undefined };
|
|
31
|
+
|
|
32
|
+
/** Opens `bytes`, with `password` if the file needs one, under {@link LIMITS}. Needs `init()`. */
|
|
33
|
+
export function openUntrusted(bytes: Uint8Array, password?: string): Opened {
|
|
34
|
+
try {
|
|
35
|
+
using doc = Document.open(bytes, { ...(password === undefined ? {} : { password }), limits: LIMITS });
|
|
36
|
+
// Repairs and dropped values are warnings: the file opened, but not exactly as written.
|
|
37
|
+
return { ok: true, pages: doc.pageCount, warnings: doc.warnings.map((w) => w.code) };
|
|
38
|
+
} catch (e) {
|
|
39
|
+
// `password-required`, `wrong-password`, `not-pdf`, `limit-exceeded`, …: a coded error whose
|
|
40
|
+
// message and hint never contain text from the file. Anything else is a bug: let it throw.
|
|
41
|
+
if (isLaminaPdfError(e)) return { ok: false, code: e.code, hint: e.hint };
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It writes no PDF. The value it returns is the result: `{ ok: true, pages, warnings }` for a file that opened, or `{ ok: false, code, hint }`, such as `password-required` for a locked file opened without its password.
|
|
48
|
+
|
|
49
|
+
## How it works
|
|
50
|
+
|
|
51
|
+
- It needs only `init()`: opening a file uses the base module, without fonts or text.
|
|
52
|
+
- `Document.open` gets the password only when you pass one, and tries it against both of the file's passwords. See [Open password-protected files](../../pages/guides/password-protected-files.md).
|
|
53
|
+
- `LIMITS` allows 16 MiB decoded from one stream and 64 MiB in all, against defaults of 64 MiB and 1 GiB. A file that needs more fails with `limit-exceeded`, and every other limit keeps its default.
|
|
54
|
+
- A file can open and still carry warnings: each one names a repair or a dropped value, and the function returns their codes.
|
|
55
|
+
- `isLaminaPdfError(e)` picks out Lamina PDF's coded errors, whose message and hint never contain text from the file. Any other error is a bug, so the function throws it again.
|
|
56
|
+
- `using` frees the document as soon as the function returns, so the WebAssembly side keeps no copy of the file.
|
|
57
|
+
|
|
58
|
+
## What to change
|
|
59
|
+
|
|
60
|
+
- **Ask for the password again:** on `password-required` or `wrong-password`, ask your user and call `openUntrusted` again with their answer.
|
|
61
|
+
- **Name the limit:** on `limit-exceeded`, return `e.data?.limit` too. It names the limit the file went over as Rust spells it, such as `max_stream_decoded` for `maxStreamDecoded`.
|
|
62
|
+
- **Other limits:** add any limit from the table in [Open files you don't trust](../../pages/guides/untrusted-files.md#hard-and-soft-limits), such as a lower `maxNestingDepth`.
|
|
63
|
+
- **Refuse damaged files:** add `strict: true` to the options, and a file that needs repair fails with `strict` instead of opening with warnings.
|
|
64
|
+
|
|
65
|
+
Next: [save in a Web Worker](../../pages/recipes/web-worker-client.md), so a large file never freezes the page.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "A PDF/A archive"
|
|
3
|
+
description: "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."
|
|
4
|
+
version: "0.2.0-alpha.4"
|
|
5
|
+
url: "https://docs.laminapdf.com/recipes/pdf-a-archive/"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A PDF/A archive
|
|
9
|
+
|
|
10
|
+
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.
|
|
11
|
+
|
|
12
|
+
You'll write a letter confirming an order as a PDF/A file, which looks the same in any viewer for decades. You need it for documents kept for years, often by law: contracts, records, letters to customers. The recipe writes the letter twice, at two PDF/A levels.
|
|
13
|
+
|
|
14
|
+
The function takes the level, `2b` or `2u`, and builds the same letter under that level's profiles. In TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
|
|
15
|
+
|
|
16
|
+
**TypeScript**
|
|
17
|
+
|
|
18
|
+
`packages/sdk/examples/pdf-a-archive.ts`:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// An archival letter, written as PDF/A-2b and as PDF/A-2u that is also PDF/UA-1.
|
|
22
|
+
import { Builder, Font, Image, PAGE_SIZES, type Profile, type Surface } from "@laminapdf/sdk";
|
|
23
|
+
|
|
24
|
+
const MARGIN = 56;
|
|
25
|
+
const BAND_HEIGHT = 70;
|
|
26
|
+
const TITLE_BASELINE = 110;
|
|
27
|
+
/** The top of the letter's text and of the photo beside it, and the height of both. */
|
|
28
|
+
const BLOCK_TOP = 130;
|
|
29
|
+
const BLOCK_HEIGHT = 120;
|
|
30
|
+
const LETTER_WIDTH = 300;
|
|
31
|
+
const PHOTO_LEFT = 380;
|
|
32
|
+
const PHOTO_WIDTH = 160;
|
|
33
|
+
const FOOTER_BASELINE = 300;
|
|
34
|
+
const TITLE = 22;
|
|
35
|
+
const BODY = 11;
|
|
36
|
+
const BAND_OPACITY = 0.35;
|
|
37
|
+
const PHOTO_ALT = "A photograph of the sprockets, packed in a box";
|
|
38
|
+
const LETTER =
|
|
39
|
+
"Thank you for your order of 12 sprockets. This letter confirms the delivery date and the price we agreed. " +
|
|
40
|
+
"Keep it: PDF/A files look the same in any viewer, now and in the future.";
|
|
41
|
+
|
|
42
|
+
/** PDF/A-2b, or PDF/A-2u (whose text maps to Unicode) with PDF/UA-1. */
|
|
43
|
+
const PROFILES: Record<"2b" | "2u", Profile[]> = {
|
|
44
|
+
"2b": ["PDF/A-2b"],
|
|
45
|
+
"2u": ["PDF/A-2u", "PDF/UA-1"],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The letter's bytes. Needs `init({ text: true })`.
|
|
50
|
+
*
|
|
51
|
+
* @param level - `2b` or `2u`.
|
|
52
|
+
* @param notoSans - the bytes of the Noto Sans font file.
|
|
53
|
+
* @param photo - the bytes of a JPEG photo.
|
|
54
|
+
*/
|
|
55
|
+
export function pdfAArchive(level: "2b" | "2u", notoSans: Uint8Array, photo: Uint8Array): Uint8Array<ArrayBuffer> {
|
|
56
|
+
using b = new Builder({ profiles: PROFILES[level], tagged: true });
|
|
57
|
+
b.metadata({ title: "Order confirmation 42", author: "Acme Widgets Ltd" });
|
|
58
|
+
b.lang("en-GB");
|
|
59
|
+
const font = Font.fromBytes(notoSans);
|
|
60
|
+
b.font(font);
|
|
61
|
+
const page = b.page("A4");
|
|
62
|
+
draw(page.surface(), font, Image.jpeg(photo));
|
|
63
|
+
page.finish();
|
|
64
|
+
using doc = b.finish();
|
|
65
|
+
return doc.save();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function draw(s: Surface, font: Font, photo: Image): void {
|
|
69
|
+
// A translucent band across the top: decoration, and transparency, which PDF/A-2 allows.
|
|
70
|
+
const [width] = PAGE_SIZES.A4;
|
|
71
|
+
s.artifact("layout", (s) => {
|
|
72
|
+
s.save().opacity(BAND_OPACITY, 1).fillColor([0.12, 0.47, 0.78]);
|
|
73
|
+
s.path((p) => p.rect([0, 0, width, BAND_HEIGHT])).fill();
|
|
74
|
+
s.restore();
|
|
75
|
+
});
|
|
76
|
+
s.tag("H1", (s) => s.text("Order confirmation 42", { font, size: TITLE, at: [MARGIN, TITLE_BASELINE] }));
|
|
77
|
+
s.tag("P", (s) => s.textBox(LETTER, { font, size: BODY, box: [MARGIN, BLOCK_TOP, LETTER_WIDTH, BLOCK_HEIGHT] }));
|
|
78
|
+
s.image(photo, [PHOTO_LEFT, BLOCK_TOP, PHOTO_WIDTH, BLOCK_HEIGHT], PHOTO_ALT);
|
|
79
|
+
s.tag("P", (s) =>
|
|
80
|
+
s.text("Acme Widgets Ltd, 1 Example Street, London", { font, size: BODY, at: [MARGIN, FOOTER_BASELINE] }),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Rust**
|
|
86
|
+
|
|
87
|
+
`crates/laminapdf/examples/pdf_a_archive.rs`:
|
|
88
|
+
|
|
89
|
+
```rust
|
|
90
|
+
//! An archival letter, written as PDF/A-2b and as PDF/A-2u that is also PDF/UA-1.
|
|
91
|
+
//!
|
|
92
|
+
//! Run it with `cargo run -p laminapdf --example pdf_a_archive -- <font.ttf> <photo.jpg> [out-dir]`: it writes
|
|
93
|
+
//! `pdf_a_archive-2b.pdf` and `pdf_a_archive-2u.pdf`.
|
|
94
|
+
|
|
95
|
+
use std::error::Error;
|
|
96
|
+
use std::path::PathBuf;
|
|
97
|
+
|
|
98
|
+
use laminapdf::{
|
|
99
|
+
ArtifactKind, BuildError, BuildOptions, Builder, Color, FillRule, FontHandle, Image, Metadata, PageSize, PointPt,
|
|
100
|
+
Profile, Pt, RectPt, SaveOptions, Surface, TagKind, TextBoxOptions,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const MARGIN: f64 = 56.0;
|
|
104
|
+
const BAND_HEIGHT: f64 = 70.0;
|
|
105
|
+
const TITLE_AT: PointPt = PointPt::from_points(MARGIN, 110.0);
|
|
106
|
+
/// The top of the letter's text and of the photo beside it, and the height of both.
|
|
107
|
+
const BLOCK_TOP: f64 = 130.0;
|
|
108
|
+
const BLOCK_HEIGHT: f64 = 120.0;
|
|
109
|
+
const LETTER_WIDTH: f64 = 300.0;
|
|
110
|
+
const PHOTO_LEFT: f64 = 380.0;
|
|
111
|
+
const PHOTO_WIDTH: f64 = 160.0;
|
|
112
|
+
const FOOTER_AT: PointPt = PointPt::from_points(MARGIN, 300.0);
|
|
113
|
+
const TITLE: Pt = Pt::new(22.0);
|
|
114
|
+
const BODY: Pt = Pt::new(11.0);
|
|
115
|
+
const BAND_OPACITY: f64 = 0.35;
|
|
116
|
+
const PHOTO_ALT: &str = "A photograph of the sprockets, packed in a box";
|
|
117
|
+
const LETTER: &str = "Thank you for your order of 12 sprockets. This letter confirms the delivery date and the \
|
|
118
|
+
price we agreed. Keep it: PDF/A files look the same in any viewer, now and in the future.";
|
|
119
|
+
|
|
120
|
+
fn main() -> Result<(), Box<dyn Error>> {
|
|
121
|
+
let mut args = std::env::args().skip(1);
|
|
122
|
+
let usage = "arguments: <font.ttf> <photo.jpg> [out-dir]";
|
|
123
|
+
let font = std::fs::read(args.next().ok_or(usage)?)?;
|
|
124
|
+
let photo = std::fs::read(args.next().ok_or(usage)?)?;
|
|
125
|
+
let out = PathBuf::from(args.next().unwrap_or_else(|| "target/examples-out".into()));
|
|
126
|
+
std::fs::create_dir_all(&out)?;
|
|
127
|
+
let a2b = build(Level::A2b, font.clone(), photo.clone())?;
|
|
128
|
+
std::fs::write(out.join("pdf_a_archive-2b.pdf"), a2b)?;
|
|
129
|
+
let a2u = build(Level::A2u, font, photo)?;
|
|
130
|
+
Ok(std::fs::write(out.join("pdf_a_archive-2u.pdf"), a2u)?)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Which archive to write.
|
|
134
|
+
#[derive(Debug, Clone, Copy)]
|
|
135
|
+
pub(crate) enum Level {
|
|
136
|
+
/// PDF/A-2b: the letter looks the same everywhere.
|
|
137
|
+
A2b,
|
|
138
|
+
/// PDF/A-2u, whose text maps to Unicode, and PDF/UA-1.
|
|
139
|
+
A2u,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// The letter's bytes, in `font` (a TrueType or OpenType file) with `photo` (a JPEG).
|
|
143
|
+
pub(crate) fn build(level: Level, font: Vec<u8>, photo: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
|
|
144
|
+
let options = match level {
|
|
145
|
+
Level::A2b => BuildOptions::new().with_profile(Profile::PdfA2b),
|
|
146
|
+
Level::A2u => BuildOptions::new()
|
|
147
|
+
.with_profile(Profile::PdfA2u)
|
|
148
|
+
.with_profile(Profile::PdfUa1),
|
|
149
|
+
};
|
|
150
|
+
let mut b = Builder::new(options.with_tagged(true));
|
|
151
|
+
b.metadata(
|
|
152
|
+
Metadata::default()
|
|
153
|
+
.with_title("Order confirmation 42")
|
|
154
|
+
.with_author("Acme Widgets Ltd"),
|
|
155
|
+
);
|
|
156
|
+
b.lang("en-GB");
|
|
157
|
+
let font = b.font_bytes(font)?;
|
|
158
|
+
let photo = Image::jpeg(photo)?;
|
|
159
|
+
let mut page = b.page(PageSize::A4);
|
|
160
|
+
draw(page.surface(), &font, &photo)?;
|
|
161
|
+
page.finish()?;
|
|
162
|
+
let mut doc = b.finish()?;
|
|
163
|
+
Ok(doc.save(&SaveOptions::new())?.bytes)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn draw(s: &mut Surface, font: &FontHandle, photo: &Image) -> Result<(), BuildError> {
|
|
167
|
+
// A translucent band across the top: decoration, and transparency, which PDF/A-2 allows.
|
|
168
|
+
let width = PageSize::A4.width.get();
|
|
169
|
+
s.artifact(ArtifactKind::Layout, |s| {
|
|
170
|
+
s.save()
|
|
171
|
+
.opacity(BAND_OPACITY, 1.0)
|
|
172
|
+
.fill_color(Color::rgb(0.12, 0.47, 0.78));
|
|
173
|
+
s.path(|p| _ = p.rect(RectPt::from_points(0.0, 0.0, width, BAND_HEIGHT)))
|
|
174
|
+
.fill(FillRule::NonZero);
|
|
175
|
+
s.restore();
|
|
176
|
+
Ok(())
|
|
177
|
+
})?;
|
|
178
|
+
s.tag(TagKind::H1, |s| {
|
|
179
|
+
s.text("Order confirmation 42", font, TITLE, TITLE_AT).map(drop)
|
|
180
|
+
})?;
|
|
181
|
+
let body = RectPt::from_points(MARGIN, BLOCK_TOP, LETTER_WIDTH, BLOCK_HEIGHT);
|
|
182
|
+
s.tag(TagKind::P, |s| {
|
|
183
|
+
s.text_box(LETTER, font, BODY, body, &TextBoxOptions::new()).map(drop)
|
|
184
|
+
})?;
|
|
185
|
+
let beside = RectPt::from_points(PHOTO_LEFT, BLOCK_TOP, PHOTO_WIDTH, BLOCK_HEIGHT);
|
|
186
|
+
s.figure(photo, beside, Some(PHOTO_ALT));
|
|
187
|
+
let footer = "Acme Widgets Ltd, 1 Example Street, London";
|
|
188
|
+
s.tag(TagKind::P, |s| s.text(footer, font, BODY, FOOTER_AT).map(drop))
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Result:** [pdf_a_archive-2b.pdf](https://docs.laminapdf.com/examples/pdf/pdf_a_archive-2b.pdf), [pdf_a_archive-2u.pdf](https://docs.laminapdf.com/examples/pdf/pdf_a_archive-2u.pdf)
|
|
193
|
+
|
|
194
|
+
The result is one A4 letter under a translucent blue band, with a photo beside its text: the Result tab shows both versions, PDF/A-2b and PDF/A-2u, with links to the PDFs.
|
|
195
|
+
|
|
196
|
+
## How it works
|
|
197
|
+
|
|
198
|
+
- PDF/A-2b makes the letter look the same everywhere. PDF/A-2u also maps every glyph to Unicode, so the text can be searched and copied. That copy is PDF/UA-1 too, so a screen reader can follow it.
|
|
199
|
+
- PDF/A needs every font embedded, so the letter uses Noto Sans rather than one of the [standard 14 fonts](../../pages/reference/glossary.md#standard-14-fonts). `finish()` checks rules like this one and fails with a coded error for the first one the letter breaks.
|
|
200
|
+
- The builder writes what PDF/A asks for: the PDF/A identification in the file's XMP metadata, and an sRGB output intent that says what its RGB colours mean. See [What PDF/A asks for](../../pages/guides/archival-pdfs.md#what-pdfa-asks-for).
|
|
201
|
+
- Both copies draw the same tagged content: an `H1` title, `P` paragraphs and the photo as a figure with alternate text. PDF/A-2b doesn't ask for tags, but PDF/UA-1 does.
|
|
202
|
+
- The blue band is drawn at 35% opacity, as a layout artifact that screen readers skip. PDF/A-2 allows transparency.
|
|
203
|
+
- `textBox` breaks the letter into lines inside its box, beside the photo. The JPEG goes into the file as it is.
|
|
204
|
+
|
|
205
|
+
## What to change
|
|
206
|
+
|
|
207
|
+
- **The letter:** change `LETTER`. `textBox` leaves out lines that don't fit, so make `BLOCK_HEIGHT` larger for a longer letter, or pass `overflow: "error"` to fail with `text-overflow` instead.
|
|
208
|
+
- **The photo:** pass any RGB or gray JPEG, or `Image.png(bytes)` for a PNG (`Image::png` in Rust). A CMYK JPEG fails `finish()` with `device-color-without-output-intent`, because the output intent is sRGB.
|
|
209
|
+
- **Other profiles:** pass the ones you need, such as `["PDF/A-2u"]` alone or `["PDF/A-2b", "PDF/UA-1"]`. A document can claim PDF/UA-1 and at most one PDF/A profile.
|
|
210
|
+
- **An attached file:** PDF/A-2 refuses attachments, so switch to PDF/A-3b, as the [PDF/A-3 invoice](../../pages/recipes/pdf-a3-invoice-with-xml.md) does.
|
|
211
|
+
|
|
212
|
+
Next: [a PDF/A-3 invoice with XML](../../pages/recipes/pdf-a3-invoice-with-xml.md), with its data attached for software to read.
|