@laminapdf/sdk 0.2.0-alpha.2 → 0.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -7
- package/dist/api.d.ts +5 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +3 -2
- package/dist/api.js.map +1 -1
- package/dist/builder.d.ts +35 -19
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +114 -80
- package/dist/builder.js.map +1 -1
- package/dist/document.d.ts +47 -21
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +112 -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.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/codes.d.ts +18 -6
- package/dist/generated/codes.d.ts.map +1 -1
- package/dist/generated/codes.js +59 -4
- package/dist/generated/codes.js.map +1 -1
- package/dist/generated/explain.d.ts.map +1 -1
- package/dist/generated/explain.js +27 -10
- package/dist/generated/explain.js.map +1 -1
- package/dist/generated/extract.d.ts +235 -0
- package/dist/generated/extract.d.ts.map +1 -0
- package/dist/generated/extract.js +4 -0
- package/dist/generated/extract.js.map +1 -0
- package/dist/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.js +1 -1
- 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/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 +6 -2
- package/dist/worker/support.d.ts.map +1 -1
- package/dist/worker/support.js +1 -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 +147 -0
- package/docs/api/classes/Builder.md +467 -0
- package/docs/api/classes/Document.md +742 -0
- package/docs/api/classes/Font.md +74 -0
- package/docs/api/classes/Image.md +140 -0
- package/docs/api/classes/LaminaPdfError.md +189 -0
- package/docs/api/classes/Page.md +178 -0
- package/docs/api/classes/PageRef.md +253 -0
- package/docs/api/classes/PageText.md +129 -0
- package/docs/api/classes/PageTextRun.md +167 -0
- package/docs/api/classes/PendingPath.md +97 -0
- package/docs/api/classes/SaveJob.md +205 -0
- package/docs/api/classes/Surface.md +326 -0
- package/docs/api/classes/Transaction.md +111 -0
- package/docs/api/functions/callbackSource.md +33 -0
- package/docs/api/functions/httpRangeSource.md +48 -0
- package/docs/api/functions/init.md +36 -0
- package/docs/api/functions/isExtractLoaded.md +21 -0
- package/docs/api/functions/isInitialized.md +19 -0
- package/docs/api/functions/isLaminaPdfError.md +41 -0
- package/docs/api/functions/isTextLoaded.md +19 -0
- package/docs/api/functions/loadExtract.md +28 -0
- package/docs/api/functions/loadText.md +28 -0
- package/docs/api/functions/onLeak.md +29 -0
- package/docs/api/functions/reset.md +22 -0
- package/docs/api/functions/wasmMemory.md +22 -0
- package/docs/api/interfaces/BuilderOptions.md +108 -0
- package/docs/api/interfaces/ByteRange.md +29 -0
- package/docs/api/interfaces/ChunkOptions.md +37 -0
- package/docs/api/interfaces/Diagnostic.md +67 -0
- package/docs/api/interfaces/ExtractOptions.md +92 -0
- package/docs/api/interfaces/ExtractResult.md +37 -0
- package/docs/api/interfaces/FontInfo.md +37 -0
- package/docs/api/interfaces/HttpRangeOptions.md +78 -0
- package/docs/api/interfaces/InitOptions.md +40 -0
- package/docs/api/interfaces/Leak.md +21 -0
- package/docs/api/interfaces/Location.md +53 -0
- package/docs/api/interfaces/OpLog.md +25 -0
- package/docs/api/interfaces/OpenOptions.md +37 -0
- package/docs/api/interfaces/PageTextOptions.md +21 -0
- package/docs/api/interfaces/PathBuilder.md +195 -0
- package/docs/api/interfaces/QuadPoint.md +25 -0
- package/docs/api/interfaces/RangeOptions.md +54 -0
- package/docs/api/interfaces/RangeSource.md +59 -0
- package/docs/api/interfaces/StrokeStyle.md +61 -0
- package/docs/api/interfaces/TagSpec.md +69 -0
- package/docs/api/interfaces/TextBoxOptions.md +93 -0
- package/docs/api/interfaces/TextGlyph.md +29 -0
- package/docs/api/interfaces/TextOptions.md +101 -0
- package/docs/api/interfaces/WasmMemory.md +37 -0
- package/docs/api/type-aliases/AfRelationship.md +15 -0
- package/docs/api/type-aliases/Align.md +15 -0
- package/docs/api/type-aliases/ArtifactKind.md +15 -0
- package/docs/api/type-aliases/AttachmentOptions.md +15 -0
- package/docs/api/type-aliases/BlendMode.md +15 -0
- package/docs/api/type-aliases/BoxRect.md +15 -0
- package/docs/api/type-aliases/BuildOptions.md +15 -0
- package/docs/api/type-aliases/Cell.md +71 -0
- package/docs/api/type-aliases/Chunk.md +79 -0
- package/docs/api/type-aliases/Color.md +16 -0
- package/docs/api/type-aliases/Coordinates.md +55 -0
- package/docs/api/type-aliases/Direction.md +15 -0
- package/docs/api/type-aliases/DocumentInfo.md +15 -0
- package/docs/api/type-aliases/DropReason.md +15 -0
- package/docs/api/type-aliases/Dropped.md +59 -0
- package/docs/api/type-aliases/Edited.md +15 -0
- package/docs/api/type-aliases/ErrorCode.md +15 -0
- package/docs/api/type-aliases/ErrorKind.md +15 -0
- package/docs/api/type-aliases/ExtractFormat.md +15 -0
- package/docs/api/type-aliases/ExtractedBlock.md +82 -0
- package/docs/api/type-aliases/ExtractedPage.md +65 -0
- package/docs/api/type-aliases/ExtractedSource.md +48 -0
- package/docs/api/type-aliases/Extraction.md +22 -0
- package/docs/api/type-aliases/FillRule.md +15 -0
- package/docs/api/type-aliases/Fit.md +15 -0
- package/docs/api/type-aliases/Flags.md +15 -0
- package/docs/api/type-aliases/Generator.md +41 -0
- package/docs/api/type-aliases/GlyphQuad.md +15 -0
- package/docs/api/type-aliases/InfoPatch.md +15 -0
- package/docs/api/type-aliases/LaminaPdfWarning.md +16 -0
- package/docs/api/type-aliases/LimitName.md +15 -0
- package/docs/api/type-aliases/Limits.md +15 -0
- package/docs/api/type-aliases/LineBox.md +15 -0
- package/docs/api/type-aliases/LineCap.md +15 -0
- package/docs/api/type-aliases/LineJoin.md +15 -0
- package/docs/api/type-aliases/LinkTarget.md +15 -0
- package/docs/api/type-aliases/Metadata.md +15 -0
- package/docs/api/type-aliases/Op.md +15 -0
- package/docs/api/type-aliases/OutlineEntry.md +15 -0
- package/docs/api/type-aliases/Overflow.md +15 -0
- package/docs/api/type-aliases/PageInfo.md +15 -0
- package/docs/api/type-aliases/PageLabel.md +15 -0
- package/docs/api/type-aliases/PageSizeName.md +15 -0
- package/docs/api/type-aliases/Point.md +15 -0
- package/docs/api/type-aliases/Profile.md +15 -0
- package/docs/api/type-aliases/Progress.md +15 -0
- package/docs/api/type-aliases/ReadingOrder.md +15 -0
- package/docs/api/type-aliases/Rect.md +15 -0
- package/docs/api/type-aliases/Region.md +31 -0
- package/docs/api/type-aliases/RenderMode.md +15 -0
- package/docs/api/type-aliases/Rotation.md +15 -0
- package/docs/api/type-aliases/SaveOptions.md +15 -0
- package/docs/api/type-aliases/SaveReport.md +15 -0
- package/docs/api/type-aliases/Span.md +63 -0
- package/docs/api/type-aliases/SplitOn.md +15 -0
- package/docs/api/type-aliases/Standard14.md +15 -0
- package/docs/api/type-aliases/StructRef.md +41 -0
- package/docs/api/type-aliases/StructSource.md +17 -0
- package/docs/api/type-aliases/TableDetector.md +15 -0
- package/docs/api/type-aliases/TableMode.md +15 -0
- package/docs/api/type-aliases/TableScope.md +15 -0
- package/docs/api/type-aliases/Tag.md +15 -0
- package/docs/api/type-aliases/TagName.md +15 -0
- package/docs/api/type-aliases/TextLayout.md +15 -0
- package/docs/api/type-aliases/TextRange.md +33 -0
- package/docs/api/type-aliases/TextRunInfo.md +15 -0
- package/docs/api/type-aliases/Transform.md +15 -0
- package/docs/api/type-aliases/UndoOutcome.md +15 -0
- package/docs/api/type-aliases/ViewerPreferences.md +15 -0
- package/docs/api/type-aliases/WarningCode.md +15 -0
- package/docs/api/type-aliases/WasmSource.md +16 -0
- package/docs/api/variables/Matrix.md +99 -0
- package/docs/api/variables/PAGE_SIZES.md +15 -0
- package/docs/api/variables/TAG_NAMES.md +15 -0
- package/docs/errors/README.md +156 -0
- package/docs/errors/aes-padding-invalid.md +34 -0
- package/docs/errors/already-initialized.md +34 -0
- package/docs/errors/binary-output-to-terminal.md +35 -0
- package/docs/errors/builder-finished.md +35 -0
- package/docs/errors/cancelled.md +35 -0
- package/docs/errors/cmap-invalid.md +42 -0
- package/docs/errors/collection-index.md +33 -0
- package/docs/errors/color-components.md +33 -0
- package/docs/errors/content-bad-operands.md +36 -0
- package/docs/errors/content-path-unpainted.md +36 -0
- package/docs/errors/content-resource-missing.md +37 -0
- package/docs/errors/content-syntax.md +36 -0
- package/docs/errors/content-unbalanced-bt.md +36 -0
- package/docs/errors/content-unbalanced-marked.md +36 -0
- package/docs/errors/content-unbalanced-q.md +36 -0
- package/docs/errors/content-unknown-operator.md +37 -0
- package/docs/errors/data-pending.md +34 -0
- package/docs/errors/decompress.md +34 -0
- package/docs/errors/device-color-without-output-intent.md +33 -0
- package/docs/errors/direct-page.md +34 -0
- package/docs/errors/disposed.md +34 -0
- package/docs/errors/document-changed.md +34 -0
- package/docs/errors/embedded-file-forbidden.md +35 -0
- package/docs/errors/embedded-file-without-mime-type.md +36 -0
- package/docs/errors/encoding-full.md +34 -0
- package/docs/errors/encrypted-base.md +33 -0
- package/docs/errors/extract-limit-blocks.md +35 -0
- package/docs/errors/extract-module-not-loaded.md +37 -0
- package/docs/errors/extract-reading-order-ambiguous.md +32 -0
- package/docs/errors/extract-struct-tree-ignored.md +32 -0
- package/docs/errors/extract-table-ambiguous.md +32 -0
- package/docs/errors/extract-table-limit.md +35 -0
- package/docs/errors/extract-untagged-content.md +32 -0
- package/docs/errors/fetch-failed.md +34 -0
- package/docs/errors/flate-truncated.md +34 -0
- package/docs/errors/font-invalid.md +36 -0
- package/docs/errors/font-mismatch.md +34 -0
- package/docs/errors/font-not-embeddable.md +34 -0
- package/docs/errors/font-not-embedded.md +35 -0
- package/docs/errors/font-not-shapable.md +33 -0
- package/docs/errors/glyph-out-of-range.md +34 -0
- package/docs/errors/glyph-without-unicode.md +35 -0
- package/docs/errors/heading-level-skipped.md +36 -0
- package/docs/errors/image-data-length.md +33 -0
- package/docs/errors/image-too-large.md +32 -0
- package/docs/errors/inline-image-unterminated.md +36 -0
- package/docs/errors/input-unreadable.md +37 -0
- package/docs/errors/instance-reset.md +34 -0
- package/docs/errors/invalid-argument.md +33 -0
- package/docs/errors/invalid-axis-value.md +32 -0
- package/docs/errors/invalid-box.md +34 -0
- package/docs/errors/invalid-color.md +32 -0
- package/docs/errors/invalid-command-line.md +44 -0
- package/docs/errors/invalid-date.md +34 -0
- package/docs/errors/invalid-entry.md +34 -0
- package/docs/errors/invalid-feature.md +32 -0
- package/docs/errors/invalid-font.md +32 -0
- package/docs/errors/invalid-geometry.md +32 -0
- package/docs/errors/invalid-icc-profile.md +32 -0
- package/docs/errors/invalid-image.md +33 -0
- package/docs/errors/invalid-op-log.md +33 -0
- package/docs/errors/invalid-page-label.md +34 -0
- package/docs/errors/invalid-page-labels.md +32 -0
- package/docs/errors/invalid-page-range.md +37 -0
- package/docs/errors/invalid-rotate.md +34 -0
- package/docs/errors/invalid-structure-nesting.md +38 -0
- package/docs/errors/invalid-structure.md +34 -0
- package/docs/errors/invalid-text-range.md +33 -0
- package/docs/errors/job-finished.md +34 -0
- package/docs/errors/limit-exceeded.md +38 -0
- package/docs/errors/link-without-contents.md +33 -0
- package/docs/errors/link-without-structure.md +37 -0
- package/docs/errors/missing-alt-text.md +33 -0
- package/docs/errors/missing-glyph.md +33 -0
- package/docs/errors/missing-header.md +37 -0
- package/docs/errors/missing-lang.md +32 -0
- package/docs/errors/missing-root.md +34 -0
- package/docs/errors/missing-title.md +32 -0
- package/docs/errors/name-tree-node-skipped.md +34 -0
- package/docs/errors/nested-transaction.md +34 -0
- package/docs/errors/no-pending-save.md +32 -0
- package/docs/errors/no-transaction.md +33 -0
- package/docs/errors/not-initialized.md +35 -0
- package/docs/errors/not-pdf.md +33 -0
- package/docs/errors/not-rebased.md +35 -0
- package/docs/errors/object-number-out-of-range.md +38 -0
- package/docs/errors/objstm-index-mismatch.md +36 -0
- package/docs/errors/out-of-memory.md +38 -0
- package/docs/errors/outline-cycle.md +34 -0
- package/docs/errors/outline-item-skipped.md +34 -0
- package/docs/errors/output-unwritable.md +37 -0
- package/docs/errors/overwrite-refused.md +36 -0
- package/docs/errors/page-count-mismatch.md +34 -0
- package/docs/errors/page-index-out-of-range.md +34 -0
- package/docs/errors/page-tree-node-skipped.md +35 -0
- package/docs/errors/password-required.md +36 -0
- package/docs/errors/path-outside-roots.md +32 -0
- package/docs/errors/perms-mismatch.md +34 -0
- package/docs/errors/profile-version-conflict.md +35 -0
- package/docs/errors/reference-cycle.md +35 -0
- package/docs/errors/render-backend-unavailable.md +32 -0
- package/docs/errors/render-failed.md +33 -0
- package/docs/errors/render-too-large.md +36 -0
- package/docs/errors/repaired-base.md +34 -0
- package/docs/errors/requires-rewrite.md +38 -0
- package/docs/errors/saved-not-rebased.md +34 -0
- package/docs/errors/sink-not-empty.md +33 -0
- package/docs/errors/sink.md +31 -0
- package/docs/errors/source-budget-exceeded.md +34 -0
- package/docs/errors/source-changed.md +33 -0
- package/docs/errors/source.md +30 -0
- package/docs/errors/stale-generation.md +34 -0
- package/docs/errors/stream-length-recovered.md +37 -0
- package/docs/errors/strict.md +34 -0
- package/docs/errors/subset-failed.md +31 -0
- package/docs/errors/syntax.md +34 -0
- package/docs/errors/text-module-not-loaded.md +36 -0
- package/docs/errors/text-overflow.md +34 -0
- package/docs/errors/tounicode-conflict.md +32 -0
- package/docs/errors/type.md +34 -0
- package/docs/errors/unexpected-eof.md +34 -0
- package/docs/errors/unknown-axis.md +33 -0
- package/docs/errors/unknown-code.md +36 -0
- package/docs/errors/unknown-destination.md +33 -0
- package/docs/errors/unknown-font.md +33 -0
- package/docs/errors/unknown-option.md +33 -0
- package/docs/errors/unknown-page.md +34 -0
- package/docs/errors/unmapped-glyph.md +36 -0
- package/docs/errors/unnumbered-heading-misused.md +34 -0
- package/docs/errors/unset-object.md +30 -0
- package/docs/errors/unsupported-filter.md +34 -0
- package/docs/errors/unsupported-image.md +33 -0
- package/docs/errors/unsupported-layout-option.md +33 -0
- package/docs/errors/unsupported-security-handler.md +34 -0
- package/docs/errors/unsupported-version.md +32 -0
- package/docs/errors/untagged-content.md +35 -0
- package/docs/errors/warnings-truncated.md +35 -0
- package/docs/errors/wasm-trap.md +37 -0
- package/docs/errors/whole-font-rebuilt.md +30 -0
- package/docs/errors/worker-crashed.md +33 -0
- package/docs/errors/worker-failed.md +32 -0
- package/docs/errors/worker-version-mismatch.md +34 -0
- package/docs/errors/would-invalidate-signatures.md +38 -0
- package/docs/errors/wrong-document.md +33 -0
- package/docs/errors/wrong-password.md +34 -0
- package/docs/errors/xobject-cycle.md +36 -0
- package/docs/errors/xref-repaired.md +36 -0
- package/docs/errors/xrefstm-ignored.md +34 -0
- package/docs/llms.txt +88 -0
- package/docs/pages/concepts/dependencies.md +44 -0
- package/docs/pages/concepts/errors-and-warnings.md +64 -0
- package/docs/pages/concepts/how-lamina-thinks.md +86 -0
- package/docs/pages/concepts/pages-and-coordinates.md +67 -0
- package/docs/pages/guides/accessible-pdfs.md +260 -0
- package/docs/pages/guides/archival-pdfs.md +429 -0
- package/docs/pages/guides/edit-a-pdf.md +137 -0
- package/docs/pages/guides/fonts-and-text.md +202 -0
- package/docs/pages/guides/images-and-vectors.md +219 -0
- package/docs/pages/guides/keep-the-page-responsive.md +81 -0
- package/docs/pages/guides/links-outlines-and-metadata.md +222 -0
- package/docs/pages/guides/password-protected-files.md +70 -0
- package/docs/pages/guides/save-a-pdf.md +144 -0
- package/docs/pages/guides/untrusted-files.md +80 -0
- package/docs/pages/index.md +33 -0
- package/docs/pages/install.md +75 -0
- package/docs/pages/quick-start/browser.md +67 -0
- package/docs/pages/quick-start/bun.md +60 -0
- package/docs/pages/quick-start/deno.md +58 -0
- package/docs/pages/quick-start/node.md +61 -0
- package/docs/pages/quick-start/rust.md +58 -0
- package/docs/pages/quick-start/workers.md +139 -0
- package/docs/pages/recipes/cloudflare-worker.md +115 -0
- package/docs/pages/recipes/extract-markdown.md +114 -0
- package/docs/pages/recipes/generate-then-edit.md +135 -0
- package/docs/pages/recipes/hello.md +94 -0
- package/docs/pages/recipes/images-and-vectors.md +209 -0
- package/docs/pages/recipes/invoice.md +226 -0
- package/docs/pages/recipes/links-and-outlines.md +215 -0
- package/docs/pages/recipes/multilingual.md +189 -0
- package/docs/pages/recipes/open-untrusted.md +65 -0
- package/docs/pages/recipes/pdf-a-archive.md +205 -0
- package/docs/pages/recipes/pdf-a3-invoice-with-xml.md +222 -0
- package/docs/pages/recipes/pdf-ua-report.md +237 -0
- package/docs/pages/recipes/report.md +125 -0
- package/docs/pages/recipes/web-worker-client.md +64 -0
- package/docs/pages/recipes.md +47 -0
- package/docs/pages/reference/changelog.md +86 -0
- package/docs/pages/reference/codes.md +14 -0
- package/docs/pages/reference/glossary.md +108 -0
- package/docs/pages/reference/rust.md +36 -0
- package/docs/pages/reference/stability.md +59 -0
- package/docs/pages/start/coding-agents.md +45 -0
- package/docs/pages/start/first-pdf.md +77 -0
- package/docs/pages/troubleshooting.md +74 -0
- package/docs/renames.json +86 -0
- package/docs/schemas/error.schema.json +305 -0
- package/docs/schemas/renames.schema.json +29 -0
- package/package.json +5 -3
- package/schemas/error.schema.json +19 -2
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Text and fonts"
|
|
3
|
+
description: "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."
|
|
4
|
+
version: "0.2.0-alpha.3"
|
|
5
|
+
url: "https://docs.laminapdf.com/guides/fonts-and-text/"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Text and fonts
|
|
9
|
+
|
|
10
|
+
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.
|
|
11
|
+
|
|
12
|
+
You'll draw text on a page: single lines, and paragraphs that wrap to fit a box, in any script and direction. You need this whenever a PDF holds words, from a one-line label to a report in five languages.
|
|
13
|
+
|
|
14
|
+
Text needs the text module: `init({ text: true })` or `loadText()` in TypeScript, and the default `text` feature in Rust.
|
|
15
|
+
|
|
16
|
+
## Pick a font
|
|
17
|
+
|
|
18
|
+
Lamina PDF draws with two kinds of font:
|
|
19
|
+
|
|
20
|
+
- **The [standard 14 fonts](../../pages/reference/glossary.md#standard-14-fonts)** (`Font.standard("Helvetica")`, `Standard14::Helvetica` in Rust) need no font file and aren't embedded, because every PDF reader has them. They cover Latin text only, without shaping.
|
|
21
|
+
- **Your own fonts** (`Font.fromBytes(bytes)`: TrueType, OpenType or a font collection) handle any script. Lamina PDF shapes their text, so letters join and ligatures form, and embeds only the glyphs you draw.
|
|
22
|
+
|
|
23
|
+
A font is embedded once per document, however many `Font` objects name it.
|
|
24
|
+
|
|
25
|
+
## Draw a line or a paragraph
|
|
26
|
+
|
|
27
|
+
`surface.text(str, { font, size, at })` draws one line, with `at[1]` its baseline. `surface.textBox(str, { font, size, box, direction, align })` wraps a paragraph to fit its box and lays out each line's words in display order, so right-to-left text reads correctly. When the text doesn't fit, it reports `overflow`.
|
|
28
|
+
|
|
29
|
+
This example draws five scripts (Latin with ligatures, Arabic, Devanagari, Hebrew and Chinese), each in its own embedded font:
|
|
30
|
+
|
|
31
|
+
**TypeScript**
|
|
32
|
+
|
|
33
|
+
`packages/sdk/examples/multilingual.ts`:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// Five scripts on one page, each as a single line and as a wrapped paragraph. The scripts are
|
|
37
|
+
// Latin, Arabic and Hebrew (right to left), Devanagari and simplified Chinese; each is shaped and
|
|
38
|
+
// bidi-resolved, and each paragraph wraps in a text box. Each script needs a font that covers it,
|
|
39
|
+
// such as the Noto fonts (OFL).
|
|
40
|
+
import { Builder, type BuilderOptions, type Direction, Font } from "@laminapdf/sdk";
|
|
41
|
+
|
|
42
|
+
/** Each script's line, its paragraph and the paragraph's direction, in the order of the fonts. */
|
|
43
|
+
export const SCRIPTS: readonly (readonly [line: string, paragraph: string, direction: Direction])[] = [
|
|
44
|
+
[
|
|
45
|
+
"Latin: the office staff fill the affine waffle flask",
|
|
46
|
+
"A paragraph breaks at spaces and fits the width of its box, line by line.",
|
|
47
|
+
"ltr",
|
|
48
|
+
],
|
|
49
|
+
["مرحبا بالعالم السلام عليكم", "مرحبا بالعالم السلام عليكم مرحبا بالعالم السلام عليكم", "rtl"],
|
|
50
|
+
["नमस्ते दुनिया। हिन्दी पाठ का परीक्षण", "नमस्ते दुनिया। हिन्दी पाठ का परीक्षण नमस्ते दुनिया।", "ltr"],
|
|
51
|
+
["שלום עולם וברוכים הבאים", "שלום עולם וברוכים הבאים שלום עולם וברוכים הבאים", "rtl"],
|
|
52
|
+
["你好世界,这是中文字体子集测试。", "你好世界,这是中文字体子集测试。你好世界,这是中文。", "ltr"],
|
|
53
|
+
];
|
|
54
|
+
const MARGIN = 56;
|
|
55
|
+
const FIRST_BASELINE = 80;
|
|
56
|
+
/** The distance from one script's line to the next one's. */
|
|
57
|
+
const SCRIPT_HEIGHT = 140;
|
|
58
|
+
/** The paragraph's box: this far below the line, this wide and this tall. */
|
|
59
|
+
const PARAGRAPH_DROP = 14;
|
|
60
|
+
const PARAGRAPH_WIDTH = 220;
|
|
61
|
+
const PARAGRAPH_HEIGHT = 100;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The document's bytes. Needs `init({ text: true })`.
|
|
65
|
+
*
|
|
66
|
+
* @param fonts - one font file per script, in the order of {@link SCRIPTS}.
|
|
67
|
+
* @param options - can fix the file ID, the producer and the creation date.
|
|
68
|
+
*/
|
|
69
|
+
export function multilingual(fonts: readonly Uint8Array[], options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
|
|
70
|
+
if (fonts.length !== SCRIPTS.length) throw new Error(`${SCRIPTS.length} fonts, one per script, are needed`);
|
|
71
|
+
using b = new Builder(options);
|
|
72
|
+
const loaded = fonts.map((bytes) => Font.fromBytes(bytes));
|
|
73
|
+
for (const font of loaded) b.font(font);
|
|
74
|
+
const page = b.page("A4");
|
|
75
|
+
const s = page.surface();
|
|
76
|
+
SCRIPTS.forEach(([line, paragraph, direction], row) => {
|
|
77
|
+
const font = loaded[row];
|
|
78
|
+
if (font === undefined) return;
|
|
79
|
+
const y = FIRST_BASELINE + SCRIPT_HEIGHT * row;
|
|
80
|
+
s.text(line, { font, size: 16, at: [MARGIN, y] });
|
|
81
|
+
// A narrow box breaks the paragraph into lines; right-to-left paragraphs start at the right.
|
|
82
|
+
const box = [MARGIN, y + PARAGRAPH_DROP, PARAGRAPH_WIDTH, PARAGRAPH_HEIGHT] as const;
|
|
83
|
+
s.textBox(paragraph, { font, size: 12, box, direction, align: "start" });
|
|
84
|
+
});
|
|
85
|
+
page.finish();
|
|
86
|
+
using doc = b.finish();
|
|
87
|
+
return doc.save();
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Rust**
|
|
92
|
+
|
|
93
|
+
`crates/laminapdf/examples/multilingual.rs`:
|
|
94
|
+
|
|
95
|
+
````rust
|
|
96
|
+
//! Five scripts on one page, each as a single line and as a wrapped paragraph.
|
|
97
|
+
//!
|
|
98
|
+
//! The scripts are Latin, Arabic and Hebrew (right to left), Devanagari and simplified Chinese;
|
|
99
|
+
//! each is shaped and bidi-resolved, and each paragraph wraps in a text box. Each script needs a
|
|
100
|
+
//! font that covers it, such as the Noto fonts (OFL).
|
|
101
|
+
//!
|
|
102
|
+
//! ```sh
|
|
103
|
+
//! cargo run -p laminapdf --example multilingual -- latin.ttf arabic.ttf devanagari.ttf hebrew.ttf chinese.otf
|
|
104
|
+
//! ```
|
|
105
|
+
|
|
106
|
+
use std::error::Error;
|
|
107
|
+
use std::path::PathBuf;
|
|
108
|
+
|
|
109
|
+
use laminapdf::{
|
|
110
|
+
Alignment, BuildOptions, Builder, Direction, PageSize, PointPt, Pt, RectPt, SaveOptions, TextBoxOptions,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/// Each script's line, its paragraph and the paragraph's direction, in the order of the fonts.
|
|
114
|
+
const SCRIPTS: [(&str, &str, Direction); 5] = [
|
|
115
|
+
(
|
|
116
|
+
"Latin: the office staff fill the affine waffle flask",
|
|
117
|
+
"A paragraph breaks at spaces and fits the width of its box, line by line.",
|
|
118
|
+
Direction::LeftToRight,
|
|
119
|
+
),
|
|
120
|
+
(
|
|
121
|
+
"مرحبا بالعالم السلام عليكم",
|
|
122
|
+
"مرحبا بالعالم السلام عليكم مرحبا بالعالم السلام عليكم",
|
|
123
|
+
Direction::RightToLeft,
|
|
124
|
+
),
|
|
125
|
+
(
|
|
126
|
+
"नमस्ते दुनिया। हिन्दी पाठ का परीक्षण",
|
|
127
|
+
"नमस्ते दुनिया। हिन्दी पाठ का परीक्षण नमस्ते दुनिया।",
|
|
128
|
+
Direction::LeftToRight,
|
|
129
|
+
),
|
|
130
|
+
(
|
|
131
|
+
"שלום עולם וברוכים הבאים",
|
|
132
|
+
"שלום עולם וברוכים הבאים שלום עולם וברוכים הבאים",
|
|
133
|
+
Direction::RightToLeft,
|
|
134
|
+
),
|
|
135
|
+
(
|
|
136
|
+
"你好世界,这是中文字体子集测试。",
|
|
137
|
+
"你好世界,这是中文字体子集测试。你好世界,这是中文。",
|
|
138
|
+
Direction::LeftToRight,
|
|
139
|
+
),
|
|
140
|
+
];
|
|
141
|
+
const MARGIN: f64 = 56.0;
|
|
142
|
+
const FIRST_BASELINE: f64 = 80.0;
|
|
143
|
+
/// The distance from one script's line to the next one's.
|
|
144
|
+
const SCRIPT_HEIGHT: f64 = 140.0;
|
|
145
|
+
/// The paragraph's box: this far below the line, this wide and this tall.
|
|
146
|
+
const PARAGRAPH_DROP: f64 = 14.0;
|
|
147
|
+
const PARAGRAPH_WIDTH: f64 = 220.0;
|
|
148
|
+
const PARAGRAPH_HEIGHT: f64 = 100.0;
|
|
149
|
+
|
|
150
|
+
fn main() -> Result<(), Box<dyn Error>> {
|
|
151
|
+
let mut args: Vec<String> = std::env::args().skip(1).collect();
|
|
152
|
+
let out = PathBuf::from(if args.len() > SCRIPTS.len() {
|
|
153
|
+
args.remove(SCRIPTS.len())
|
|
154
|
+
} else {
|
|
155
|
+
"target/examples-out".into()
|
|
156
|
+
});
|
|
157
|
+
let fonts = args.iter().map(std::fs::read).collect::<Result<Vec<_>, _>>()?;
|
|
158
|
+
std::fs::create_dir_all(&out)?;
|
|
159
|
+
std::fs::write(out.join("multilingual.pdf"), build(BuildOptions::new(), fonts)?)?;
|
|
160
|
+
Ok(())
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// The document's bytes, with one font per script in `fonts` (in the order of [`SCRIPTS`]).
|
|
164
|
+
/// `options` can fix the file ID, the producer and the creation date.
|
|
165
|
+
pub(crate) fn build(options: BuildOptions, fonts: Vec<Vec<u8>>) -> Result<Vec<u8>, Box<dyn Error>> {
|
|
166
|
+
if fonts.len() != SCRIPTS.len() {
|
|
167
|
+
return Err(format!("{} fonts, one per script, are needed", SCRIPTS.len()).into());
|
|
168
|
+
}
|
|
169
|
+
let mut b = Builder::new(options);
|
|
170
|
+
let fonts = fonts
|
|
171
|
+
.into_iter()
|
|
172
|
+
.map(|bytes| b.font_bytes(bytes))
|
|
173
|
+
.collect::<Result<Vec<_>, _>>()?;
|
|
174
|
+
let mut page = b.page(PageSize::A4);
|
|
175
|
+
let s = page.surface();
|
|
176
|
+
for (row, ((line, paragraph, direction), font)) in (0_u32..).zip(SCRIPTS.iter().zip(&fonts)) {
|
|
177
|
+
let y = FIRST_BASELINE + SCRIPT_HEIGHT * f64::from(row);
|
|
178
|
+
s.text(line, font, Pt::new(16.0), PointPt::from_points(MARGIN, y))?;
|
|
179
|
+
// A narrow box breaks the paragraph into lines; right-to-left paragraphs start at the right.
|
|
180
|
+
let options = TextBoxOptions::new()
|
|
181
|
+
.with_direction(*direction)
|
|
182
|
+
.with_align(Alignment::Start);
|
|
183
|
+
let area = RectPt::from_points(MARGIN, y + PARAGRAPH_DROP, PARAGRAPH_WIDTH, PARAGRAPH_HEIGHT);
|
|
184
|
+
s.text_box(paragraph, font, Pt::new(12.0), area, &options)?;
|
|
185
|
+
}
|
|
186
|
+
page.finish()?;
|
|
187
|
+
let mut doc = b.finish()?;
|
|
188
|
+
Ok(doc.save(&SaveOptions::new())?.bytes)
|
|
189
|
+
}
|
|
190
|
+
````
|
|
191
|
+
|
|
192
|
+
[See the output](../../pages/recipes/multilingual.md). Text extracted from it, with pdf.js, pypdf or pdftotext, equals the input strings, ligatures included.
|
|
193
|
+
|
|
194
|
+
## When a font can't draw the text
|
|
195
|
+
|
|
196
|
+
- **A missing character:** text a font can't encode fails with `missing-glyph`, never with a blank box.
|
|
197
|
+
- **Too many letters for a standard 14 font:** a standard 14 font holds at most 37 distinct letters beyond its Western European set (WinAnsi); one more fails with `encoding-full`. Use your own font instead.
|
|
198
|
+
- **A font that may not be embedded:** a font whose licence bits forbid embedding (restricted, or bitmap only) fails with `font-not-embeddable`. In Rust, `EmbedPolicy::Override` embeds it anyway, if you hold the rights. A font marked "no subsetting" is embedded whole.
|
|
199
|
+
|
|
200
|
+
> **Details:** Lamina PDF shapes your own fonts' text with HarfRust, a Rust port of the HarfBuzz shaper, and subsets each font to the glyphs you draw. It embeds them as [Type 0](../../pages/reference/glossary.md#type-0) fonts with `/W` widths and a [ToUnicode](../../pages/reference/glossary.md#tounicode) [CMap](../../pages/reference/glossary.md#cmap), which is what lets the text be extracted again. Line breaking follows [UAX #14](https://www.unicode.org/reports/tr14/), and the order of mixed-direction text follows [UAX #9](https://www.unicode.org/reports/tr9/) (bidi). The licence bits are the font's `fsType`.
|
|
201
|
+
|
|
202
|
+
Next: [shapes, colour and images](../../pages/guides/images-and-vectors.md).
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Shapes, colour and images"
|
|
3
|
+
description: "Draw lines, shapes and images on a page, in any colour, with transparency, clipping and transforms."
|
|
4
|
+
version: "0.2.0-alpha.3"
|
|
5
|
+
url: "https://docs.laminapdf.com/guides/images-and-vectors/"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Shapes, colour and images
|
|
9
|
+
|
|
10
|
+
Draw lines, shapes and images on a page, in any colour, with transparency, clipping and transforms.
|
|
11
|
+
|
|
12
|
+
You'll draw lines, shapes and images on a page, in colour and with transparency. You need this for anything visual in a PDF: a logo, a chart, the rules of a table or a photo.
|
|
13
|
+
|
|
14
|
+
Shapes and images need only the base module (`init()`).
|
|
15
|
+
|
|
16
|
+
## Draw a shape
|
|
17
|
+
|
|
18
|
+
`surface.path(p => …)` returns the shape unpainted. Paint it with `fill`, `stroke` or `fillStroke`, or call `clip` to limit what comes after to its inside. A value that makes no shape, such as NaN or a negative line width, fails `page.finish()` with `invalid-geometry`.
|
|
19
|
+
|
|
20
|
+
## Colour it
|
|
21
|
+
|
|
22
|
+
A colour is gray `[g]`, RGB `[r, g, b]`, CMYK `[c, m, y, k]` or hex. Opacity and blend modes set how a drawing mixes with what's already on the page: in the example, `multiply` darkens where a shape overlaps what's below it.
|
|
23
|
+
|
|
24
|
+
## Place an image
|
|
25
|
+
|
|
26
|
+
- `Image.jpeg(bytes)` embeds a JPEG as it is, without decoding it.
|
|
27
|
+
- `Image.png(bytes)` keeps the PNG's transparency.
|
|
28
|
+
- `Image.rgba(width, height, pixels)` takes raw pixels.
|
|
29
|
+
|
|
30
|
+
## Move, turn and scale
|
|
31
|
+
|
|
32
|
+
A `Matrix` rotates, scales or moves what you draw. It works in [page space](../../pages/reference/glossary.md#page-space), like every other position.
|
|
33
|
+
|
|
34
|
+
This example draws rectangles, circles, curves and dashed strokes in several colours, with transparency, a clip and a rotation, and places a JPEG and a transparent image:
|
|
35
|
+
|
|
36
|
+
**TypeScript**
|
|
37
|
+
|
|
38
|
+
`packages/sdk/examples/images-and-vectors.ts`:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// Images and vector graphics on one A4 page. It draws shapes and a dashed Bézier curve in gray,
|
|
42
|
+
// RGB, CMYK and hex colour, overlaps two translucent discs with a blend mode, clips a JPEG to an
|
|
43
|
+
// ellipse and rotates an RGBA image with a soft mask. Needs only the base module.
|
|
44
|
+
import { Builder, type BuilderOptions, Image, Matrix, type Surface } from "@laminapdf/sdk";
|
|
45
|
+
|
|
46
|
+
/** The RGBA gradient's side, in pixels. */
|
|
47
|
+
const GLOW_SIDE = 16;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The document's bytes, with `photo` (a JPEG) on the page.
|
|
51
|
+
*
|
|
52
|
+
* @param options - can fix the file ID, the producer and the creation date.
|
|
53
|
+
*/
|
|
54
|
+
export function imagesAndVectors(photo: Uint8Array, options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
|
|
55
|
+
using b = new Builder(options);
|
|
56
|
+
const jpeg = Image.jpeg(photo);
|
|
57
|
+
const glow = Image.rgba(GLOW_SIDE, GLOW_SIDE, gradient());
|
|
58
|
+
const page = b.page("A4");
|
|
59
|
+
const s = page.surface();
|
|
60
|
+
shapes(s);
|
|
61
|
+
overlappingDiscs(s);
|
|
62
|
+
images(s, jpeg, glow);
|
|
63
|
+
page.finish();
|
|
64
|
+
using doc = b.finish();
|
|
65
|
+
return doc.save();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A square gradient whose alpha falls off towards the right. */
|
|
69
|
+
function gradient(): Uint8Array {
|
|
70
|
+
const out = new Uint8Array(GLOW_SIDE * GLOW_SIDE * 4);
|
|
71
|
+
for (let y = 0; y < GLOW_SIDE; y++) {
|
|
72
|
+
for (let x = 0; x < GLOW_SIDE; x++) out.set([x * 16, y * 16, 200, 255 - x * 15], (y * GLOW_SIDE + x) * 4);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A rectangle, a circle and a dashed, round-capped Bézier curve. */
|
|
78
|
+
function shapes(s: Surface): void {
|
|
79
|
+
s.fillColor([0.9, 0.3, 0.2])
|
|
80
|
+
.path((p) => p.rect([56, 56, 160, 100]))
|
|
81
|
+
.fill();
|
|
82
|
+
s.fillColor([0.8, 0.1, 0, 0])
|
|
83
|
+
.path((p) => p.circle([330, 106], 50))
|
|
84
|
+
.fill();
|
|
85
|
+
s.strokeColor("#2a9d8f")
|
|
86
|
+
.path((p) => p.moveTo(56, 220).cubicTo(180, 140, 300, 300, 539, 200))
|
|
87
|
+
.stroke({ width: 3, cap: "round", dash: [12, 8] });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Two translucent discs, the second multiplied onto the first. */
|
|
91
|
+
function overlappingDiscs(s: Surface): void {
|
|
92
|
+
s.save().opacity(0.6, 1).fillColor([0.3]);
|
|
93
|
+
s.path((p) => p.circle([120, 330], 60)).fill();
|
|
94
|
+
s.blendMode("multiply").fillColor([1, 0.8, 0]);
|
|
95
|
+
s.path((p) => p.circle([190, 330], 60))
|
|
96
|
+
.fill()
|
|
97
|
+
.restore();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The photo clipped to an ellipse, and the gradient rotated by 30 degrees. */
|
|
101
|
+
function images(s: Surface, photo: Image, glow: Image): void {
|
|
102
|
+
s.save()
|
|
103
|
+
.path((p) => p.ellipse([300, 270, 239, 120]))
|
|
104
|
+
.clip();
|
|
105
|
+
s.image(photo, [300, 250, 239, 160]).restore();
|
|
106
|
+
s.save().transform(Matrix.after(Matrix.translate(150, 520), Matrix.rotate(30)));
|
|
107
|
+
s.image(glow, [-60, -60, 120, 120]).restore();
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Rust**
|
|
112
|
+
|
|
113
|
+
`crates/laminapdf/examples/images_and_vectors.rs`:
|
|
114
|
+
|
|
115
|
+
````rust
|
|
116
|
+
//! Images and vector graphics on one A4 page.
|
|
117
|
+
//!
|
|
118
|
+
//! It draws shapes and a dashed Bézier curve in gray, RGB, CMYK and hex colour, overlaps two
|
|
119
|
+
//! translucent discs with a blend mode, clips a JPEG to an ellipse and rotates an RGBA image
|
|
120
|
+
//! with a soft mask.
|
|
121
|
+
//!
|
|
122
|
+
//! ```sh
|
|
123
|
+
//! cargo run -p laminapdf --example images_and_vectors -- photo.jpg [out-dir] # writes images_and_vectors.pdf
|
|
124
|
+
//! ```
|
|
125
|
+
|
|
126
|
+
use std::error::Error;
|
|
127
|
+
use std::path::PathBuf;
|
|
128
|
+
|
|
129
|
+
use laminapdf::{
|
|
130
|
+
BlendMode, BuildOptions, Builder, Color, Dash, FillRule, Image, LineCap, Matrix, PageSize, PathBuilder, PointPt,
|
|
131
|
+
Pt, RectPt, SaveOptions, StrokeStyle, Surface,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/// The RGBA gradient's side, in pixels.
|
|
135
|
+
const GLOW_SIDE: u8 = 16;
|
|
136
|
+
|
|
137
|
+
fn main() -> Result<(), Box<dyn Error>> {
|
|
138
|
+
let mut args = std::env::args().skip(1);
|
|
139
|
+
let photo = std::fs::read(args.next().ok_or("usage: images_and_vectors <photo.jpg> [out-dir]")?)?;
|
|
140
|
+
let out = PathBuf::from(args.next().unwrap_or_else(|| "target/examples-out".into()));
|
|
141
|
+
std::fs::create_dir_all(&out)?;
|
|
142
|
+
std::fs::write(out.join("images_and_vectors.pdf"), build(BuildOptions::new(), photo)?)?;
|
|
143
|
+
Ok(())
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// The document's bytes, with `photo` (a JPEG) on the page. `options` can fix the file ID, the
|
|
147
|
+
/// producer and the creation date.
|
|
148
|
+
pub(crate) fn build(options: BuildOptions, photo: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
|
|
149
|
+
let mut b = Builder::new(options);
|
|
150
|
+
let photo = Image::jpeg(photo)?;
|
|
151
|
+
let glow = Image::rgba(u32::from(GLOW_SIDE), u32::from(GLOW_SIDE), &gradient())?;
|
|
152
|
+
let mut page = b.page(PageSize::A4);
|
|
153
|
+
let s = page.surface();
|
|
154
|
+
shapes(s)?;
|
|
155
|
+
overlapping_discs(s);
|
|
156
|
+
images(s, &photo, &glow);
|
|
157
|
+
page.finish()?;
|
|
158
|
+
let mut doc = b.finish()?;
|
|
159
|
+
Ok(doc.save(&SaveOptions::new())?.bytes)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// A square gradient whose alpha falls off towards the right.
|
|
163
|
+
fn gradient() -> Vec<u8> {
|
|
164
|
+
(0..GLOW_SIDE)
|
|
165
|
+
.flat_map(|y| (0..GLOW_SIDE).flat_map(move |x| [x * 16, y * 16, 200, 255 - x * 15]))
|
|
166
|
+
.collect()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// A rectangle, a circle and a dashed, round-capped Bézier curve.
|
|
170
|
+
fn shapes(s: &mut Surface) -> Result<(), Box<dyn Error>> {
|
|
171
|
+
let at = PointPt::from_points;
|
|
172
|
+
s.fill_color(Color::rgb(0.9, 0.3, 0.2))
|
|
173
|
+
.path(|p| _ = p.rect(RectPt::from_points(56.0, 56.0, 160.0, 100.0)))
|
|
174
|
+
.fill(FillRule::NonZero);
|
|
175
|
+
s.fill_color(Color::cmyk(0.8, 0.1, 0.0, 0.0))
|
|
176
|
+
.path(|p| _ = p.circle(at(330.0, 106.0), Pt::new(50.0)))
|
|
177
|
+
.fill(FillRule::NonZero);
|
|
178
|
+
let dashed = StrokeStyle {
|
|
179
|
+
width: Pt::new(3.0),
|
|
180
|
+
cap: LineCap::Round,
|
|
181
|
+
dash: Some(Dash::new(vec![12.0, 8.0], 0.0)),
|
|
182
|
+
..StrokeStyle::default()
|
|
183
|
+
};
|
|
184
|
+
s.stroke_color(Color::hex("#2a9d8f")?)
|
|
185
|
+
.path(|p| {
|
|
186
|
+
p.move_to(at(56.0, 220.0))
|
|
187
|
+
.cubic_to(at(180.0, 140.0), at(300.0, 300.0), at(539.0, 200.0));
|
|
188
|
+
})
|
|
189
|
+
.stroke(&dashed);
|
|
190
|
+
Ok(())
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Two translucent discs, the second multiplied onto the first.
|
|
194
|
+
fn overlapping_discs(s: &mut Surface) {
|
|
195
|
+
let disc = |x: f64| move |p: &mut PathBuilder| _ = p.circle(PointPt::from_points(x, 330.0), Pt::new(60.0));
|
|
196
|
+
s.save().opacity(0.6, 1.0).fill_color(Color::gray(0.3));
|
|
197
|
+
s.path(disc(120.0)).fill(FillRule::NonZero);
|
|
198
|
+
s.blend_mode(BlendMode::Multiply).fill_color(Color::rgb(1.0, 0.8, 0.0));
|
|
199
|
+
s.path(disc(190.0)).fill(FillRule::NonZero).restore();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// The photo clipped to an ellipse, and the gradient rotated by 30 degrees.
|
|
203
|
+
fn images(s: &mut Surface, photo: &Image, glow: &Image) {
|
|
204
|
+
s.save()
|
|
205
|
+
.path(|p| _ = p.ellipse(RectPt::from_points(300.0, 270.0, 239.0, 120.0)))
|
|
206
|
+
.clip(FillRule::NonZero);
|
|
207
|
+
s.image(photo, RectPt::from_points(300.0, 250.0, 239.0, 160.0))
|
|
208
|
+
.restore();
|
|
209
|
+
let placement = Matrix::translate(150.0, 520.0).after(&Matrix::rotate(30.0));
|
|
210
|
+
s.save().transform(placement);
|
|
211
|
+
s.image(glow, RectPt::from_points(-60.0, -60.0, 120.0, 120.0)).restore();
|
|
212
|
+
}
|
|
213
|
+
````
|
|
214
|
+
|
|
215
|
+
[See the output](../../pages/recipes/images-and-vectors.md).
|
|
216
|
+
|
|
217
|
+
> **Details:** A PNG's alpha channel, and an RGBA image's, becomes a soft mask: a second image that holds the transparency.
|
|
218
|
+
|
|
219
|
+
Next: [links, outlines and metadata](../../pages/guides/links-outlines-and-metadata.md).
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Keep the page responsive"
|
|
3
|
+
description: "Run Lamina PDF in a Web Worker with @laminapdf/sdk/worker, so a large file never freezes the page."
|
|
4
|
+
version: "0.2.0-alpha.3"
|
|
5
|
+
url: "https://docs.laminapdf.com/guides/keep-the-page-responsive/"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Keep the page responsive
|
|
9
|
+
|
|
10
|
+
Run Lamina PDF in a Web Worker with @laminapdf/sdk/worker, so a large file never freezes the page.
|
|
11
|
+
|
|
12
|
+
You'll run Lamina PDF in a Web Worker, so opening or saving a large file never freezes your page. You need this in a browser app once files get big: on the main thread, a [full rewrite](../../pages/reference/glossary.md#full-rewrite) of a 50 MB file blocks the page for about 200 ms.
|
|
13
|
+
|
|
14
|
+
`@laminapdf/sdk` is synchronous after `init()`. `@laminapdf/sdk/worker` runs the same API in a Web Worker, where you call the same methods and each returns a `Promise`. This example opens a file in a Web Worker and saves it there. The save reports its progress, and aborting the signal stops the work, for example when the user presses Cancel:
|
|
15
|
+
|
|
16
|
+
`packages/sdk/examples/worker-client.ts`:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// Saving a PDF in a Web Worker, so a large file never blocks the page: `@laminapdf/sdk/worker`
|
|
20
|
+
// runs the same API there, and each method returns a Promise. The save reports its progress step
|
|
21
|
+
// by step, and aborting the signal (a Cancel button, say) stops the work.
|
|
22
|
+
import { type CallOptions, connect } from "@laminapdf/sdk/worker";
|
|
23
|
+
|
|
24
|
+
/** What the save reports to and listens to. */
|
|
25
|
+
export type SaveInWorkerOptions = Pick<CallOptions, "onProgress" | "signal">;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Opens `bytes` in a Web Worker and saves them there as a full rewrite.
|
|
29
|
+
*
|
|
30
|
+
* @param bytes - any PDF; it is copied to the worker (`transfer: true` would move it).
|
|
31
|
+
* @param options - `onProgress` is called after each step of the save. Aborting `signal` stops
|
|
32
|
+
* the work wherever it is (starting the Web Worker, opening, or at the save's next step), and
|
|
33
|
+
* the promise rejects with a `cancelled` error.
|
|
34
|
+
* @returns the saved file.
|
|
35
|
+
*/
|
|
36
|
+
export async function saveInWorker(bytes: Uint8Array, options: SaveInWorkerOptions = {}): Promise<Uint8Array> {
|
|
37
|
+
const client = await connect(options);
|
|
38
|
+
try {
|
|
39
|
+
await using doc = await client.open(bytes, options);
|
|
40
|
+
return await doc.save({ mode: "fullRewrite" }, options);
|
|
41
|
+
} finally {
|
|
42
|
+
client.terminate();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Call the same methods, awaited
|
|
48
|
+
|
|
49
|
+
Getters become methods: `await doc.pageCount()`. `doc.page(i)` returns a remote page, and `page.text()` returns a `PageText`, once you've called `client.loadExtract()`.
|
|
50
|
+
|
|
51
|
+
The Web Worker leaves out a few members, each for a reason:
|
|
52
|
+
|
|
53
|
+
- **`transact`:** its callback can't cross to the Web Worker, so pass the edits to `apply(ops)` instead.
|
|
54
|
+
- **`saveJob`:** the Web Worker's `save` already works in steps.
|
|
55
|
+
- **`ensure`:** the Web Worker fetches the byte ranges a call needs by itself.
|
|
56
|
+
- **`free`:** call `doc.close()`, or use `await using`, instead.
|
|
57
|
+
- **`Builder`:** build on the main thread, or in a Web Worker of your own.
|
|
58
|
+
|
|
59
|
+
## Cancel a call
|
|
60
|
+
|
|
61
|
+
Pass an `AbortSignal` as `signal` to any call. An aborted call rejects with `cancelled`, and `error.cause` holds the signal's reason. What happens to the work depends on where it was:
|
|
62
|
+
|
|
63
|
+
- A call that hasn't started never runs.
|
|
64
|
+
- `save` stops at its next step, and the document stays usable.
|
|
65
|
+
- Any other call runs to the end in the Web Worker, because it is one synchronous call. `client.terminate()` stops it at once.
|
|
66
|
+
|
|
67
|
+
## Move bytes instead of copying them
|
|
68
|
+
|
|
69
|
+
Saved bytes and page text come back transferred, not copied. Your input is copied unless you pass `transfer: true`, which moves it and leaves your buffer empty.
|
|
70
|
+
|
|
71
|
+
## Recover from a crash
|
|
72
|
+
|
|
73
|
+
If the Web Worker dies, pending calls reject with `worker-crashed`, and `client.on("crashed", …)` fires. `client.restart()` starts a fresh Web Worker, and you open your documents again.
|
|
74
|
+
|
|
75
|
+
`connect()` also checks that the Web Worker comes from the same `@laminapdf/sdk` build as your page. It rejects with `worker-version-mismatch` otherwise.
|
|
76
|
+
|
|
77
|
+
## Bundlers and a content security policy
|
|
78
|
+
|
|
79
|
+
Vite and webpack bundle the Web Worker with no setup. In Vite, exclude `@laminapdf/sdk` from `optimizeDeps`, as for the main entry. To host the Web Worker's file yourself, for example under a strict content security policy, pass `connect({ worker: () => new Worker(url, { type: "module" }) })`.
|
|
80
|
+
|
|
81
|
+
Next: browse the [recipes](../../pages/recipes.md), with the PDF each one makes, or the [troubleshooting](../../pages/troubleshooting.md) page when something goes wrong.
|