@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.
Files changed (401) hide show
  1. package/README.md +42 -7
  2. package/dist/api.d.ts +5 -2
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +3 -2
  5. package/dist/api.js.map +1 -1
  6. package/dist/builder.d.ts +35 -19
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js +114 -80
  9. package/dist/builder.js.map +1 -1
  10. package/dist/document.d.ts +47 -21
  11. package/dist/document.d.ts.map +1 -1
  12. package/dist/document.js +112 -67
  13. package/dist/document.js.map +1 -1
  14. package/dist/errors.d.ts +5 -5
  15. package/dist/errors.d.ts.map +1 -1
  16. package/dist/errors.js +10 -16
  17. package/dist/errors.js.map +1 -1
  18. package/dist/extract.d.ts +57 -0
  19. package/dist/extract.d.ts.map +1 -0
  20. package/dist/extract.js +5 -0
  21. package/dist/extract.js.map +1 -0
  22. package/dist/font.d.ts +13 -6
  23. package/dist/font.d.ts.map +1 -1
  24. package/dist/font.js +50 -20
  25. package/dist/font.js.map +1 -1
  26. package/dist/generated/codes.d.ts +18 -6
  27. package/dist/generated/codes.d.ts.map +1 -1
  28. package/dist/generated/codes.js +59 -4
  29. package/dist/generated/codes.js.map +1 -1
  30. package/dist/generated/explain.d.ts.map +1 -1
  31. package/dist/generated/explain.js +27 -10
  32. package/dist/generated/explain.js.map +1 -1
  33. package/dist/generated/extract.d.ts +235 -0
  34. package/dist/generated/extract.d.ts.map +1 -0
  35. package/dist/generated/extract.js +4 -0
  36. package/dist/generated/extract.js.map +1 -0
  37. package/dist/init.d.ts +13 -5
  38. package/dist/init.d.ts.map +1 -1
  39. package/dist/init.js +23 -11
  40. package/dist/init.js.map +1 -1
  41. package/dist/internal/handle.d.ts +58 -0
  42. package/dist/internal/handle.d.ts.map +1 -0
  43. package/dist/internal/handle.js +113 -0
  44. package/dist/internal/handle.js.map +1 -0
  45. package/dist/internal/instance.d.ts +56 -0
  46. package/dist/internal/instance.d.ts.map +1 -0
  47. package/dist/internal/instance.js +125 -0
  48. package/dist/internal/instance.js.map +1 -0
  49. package/dist/source/http.js +1 -1
  50. package/dist/source/http.js.map +1 -1
  51. package/dist/source/range.d.ts +6 -1
  52. package/dist/source/range.d.ts.map +1 -1
  53. package/dist/source/range.js +10 -6
  54. package/dist/source/range.js.map +1 -1
  55. package/dist/text.d.ts +3 -3
  56. package/dist/text.d.ts.map +1 -1
  57. package/dist/text.js +3 -3
  58. package/dist/text.js.map +1 -1
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.js +2 -2
  61. package/dist/version.js.map +1 -1
  62. package/dist/worker/client.d.ts +3 -2
  63. package/dist/worker/client.d.ts.map +1 -1
  64. package/dist/worker/client.js +6 -4
  65. package/dist/worker/client.js.map +1 -1
  66. package/dist/worker/entry.js +1 -1
  67. package/dist/worker/entry.js.map +1 -1
  68. package/dist/worker/server.d.ts.map +1 -1
  69. package/dist/worker/server.js +42 -9
  70. package/dist/worker/server.js.map +1 -1
  71. package/dist/worker/support.d.ts +6 -2
  72. package/dist/worker/support.d.ts.map +1 -1
  73. package/dist/worker/support.js +1 -0
  74. package/dist/worker/support.js.map +1 -1
  75. package/dist/worker/types.d.ts +8 -1
  76. package/dist/worker/types.d.ts.map +1 -1
  77. package/docs/AGENTS.md +14 -0
  78. package/docs/api/README.md +147 -0
  79. package/docs/api/classes/Builder.md +467 -0
  80. package/docs/api/classes/Document.md +742 -0
  81. package/docs/api/classes/Font.md +74 -0
  82. package/docs/api/classes/Image.md +140 -0
  83. package/docs/api/classes/LaminaPdfError.md +189 -0
  84. package/docs/api/classes/Page.md +178 -0
  85. package/docs/api/classes/PageRef.md +253 -0
  86. package/docs/api/classes/PageText.md +129 -0
  87. package/docs/api/classes/PageTextRun.md +167 -0
  88. package/docs/api/classes/PendingPath.md +97 -0
  89. package/docs/api/classes/SaveJob.md +205 -0
  90. package/docs/api/classes/Surface.md +326 -0
  91. package/docs/api/classes/Transaction.md +111 -0
  92. package/docs/api/functions/callbackSource.md +33 -0
  93. package/docs/api/functions/httpRangeSource.md +48 -0
  94. package/docs/api/functions/init.md +36 -0
  95. package/docs/api/functions/isExtractLoaded.md +21 -0
  96. package/docs/api/functions/isInitialized.md +19 -0
  97. package/docs/api/functions/isLaminaPdfError.md +41 -0
  98. package/docs/api/functions/isTextLoaded.md +19 -0
  99. package/docs/api/functions/loadExtract.md +28 -0
  100. package/docs/api/functions/loadText.md +28 -0
  101. package/docs/api/functions/onLeak.md +29 -0
  102. package/docs/api/functions/reset.md +22 -0
  103. package/docs/api/functions/wasmMemory.md +22 -0
  104. package/docs/api/interfaces/BuilderOptions.md +108 -0
  105. package/docs/api/interfaces/ByteRange.md +29 -0
  106. package/docs/api/interfaces/ChunkOptions.md +37 -0
  107. package/docs/api/interfaces/Diagnostic.md +67 -0
  108. package/docs/api/interfaces/ExtractOptions.md +92 -0
  109. package/docs/api/interfaces/ExtractResult.md +37 -0
  110. package/docs/api/interfaces/FontInfo.md +37 -0
  111. package/docs/api/interfaces/HttpRangeOptions.md +78 -0
  112. package/docs/api/interfaces/InitOptions.md +40 -0
  113. package/docs/api/interfaces/Leak.md +21 -0
  114. package/docs/api/interfaces/Location.md +53 -0
  115. package/docs/api/interfaces/OpLog.md +25 -0
  116. package/docs/api/interfaces/OpenOptions.md +37 -0
  117. package/docs/api/interfaces/PageTextOptions.md +21 -0
  118. package/docs/api/interfaces/PathBuilder.md +195 -0
  119. package/docs/api/interfaces/QuadPoint.md +25 -0
  120. package/docs/api/interfaces/RangeOptions.md +54 -0
  121. package/docs/api/interfaces/RangeSource.md +59 -0
  122. package/docs/api/interfaces/StrokeStyle.md +61 -0
  123. package/docs/api/interfaces/TagSpec.md +69 -0
  124. package/docs/api/interfaces/TextBoxOptions.md +93 -0
  125. package/docs/api/interfaces/TextGlyph.md +29 -0
  126. package/docs/api/interfaces/TextOptions.md +101 -0
  127. package/docs/api/interfaces/WasmMemory.md +37 -0
  128. package/docs/api/type-aliases/AfRelationship.md +15 -0
  129. package/docs/api/type-aliases/Align.md +15 -0
  130. package/docs/api/type-aliases/ArtifactKind.md +15 -0
  131. package/docs/api/type-aliases/AttachmentOptions.md +15 -0
  132. package/docs/api/type-aliases/BlendMode.md +15 -0
  133. package/docs/api/type-aliases/BoxRect.md +15 -0
  134. package/docs/api/type-aliases/BuildOptions.md +15 -0
  135. package/docs/api/type-aliases/Cell.md +71 -0
  136. package/docs/api/type-aliases/Chunk.md +79 -0
  137. package/docs/api/type-aliases/Color.md +16 -0
  138. package/docs/api/type-aliases/Coordinates.md +55 -0
  139. package/docs/api/type-aliases/Direction.md +15 -0
  140. package/docs/api/type-aliases/DocumentInfo.md +15 -0
  141. package/docs/api/type-aliases/DropReason.md +15 -0
  142. package/docs/api/type-aliases/Dropped.md +59 -0
  143. package/docs/api/type-aliases/Edited.md +15 -0
  144. package/docs/api/type-aliases/ErrorCode.md +15 -0
  145. package/docs/api/type-aliases/ErrorKind.md +15 -0
  146. package/docs/api/type-aliases/ExtractFormat.md +15 -0
  147. package/docs/api/type-aliases/ExtractedBlock.md +82 -0
  148. package/docs/api/type-aliases/ExtractedPage.md +65 -0
  149. package/docs/api/type-aliases/ExtractedSource.md +48 -0
  150. package/docs/api/type-aliases/Extraction.md +22 -0
  151. package/docs/api/type-aliases/FillRule.md +15 -0
  152. package/docs/api/type-aliases/Fit.md +15 -0
  153. package/docs/api/type-aliases/Flags.md +15 -0
  154. package/docs/api/type-aliases/Generator.md +41 -0
  155. package/docs/api/type-aliases/GlyphQuad.md +15 -0
  156. package/docs/api/type-aliases/InfoPatch.md +15 -0
  157. package/docs/api/type-aliases/LaminaPdfWarning.md +16 -0
  158. package/docs/api/type-aliases/LimitName.md +15 -0
  159. package/docs/api/type-aliases/Limits.md +15 -0
  160. package/docs/api/type-aliases/LineBox.md +15 -0
  161. package/docs/api/type-aliases/LineCap.md +15 -0
  162. package/docs/api/type-aliases/LineJoin.md +15 -0
  163. package/docs/api/type-aliases/LinkTarget.md +15 -0
  164. package/docs/api/type-aliases/Metadata.md +15 -0
  165. package/docs/api/type-aliases/Op.md +15 -0
  166. package/docs/api/type-aliases/OutlineEntry.md +15 -0
  167. package/docs/api/type-aliases/Overflow.md +15 -0
  168. package/docs/api/type-aliases/PageInfo.md +15 -0
  169. package/docs/api/type-aliases/PageLabel.md +15 -0
  170. package/docs/api/type-aliases/PageSizeName.md +15 -0
  171. package/docs/api/type-aliases/Point.md +15 -0
  172. package/docs/api/type-aliases/Profile.md +15 -0
  173. package/docs/api/type-aliases/Progress.md +15 -0
  174. package/docs/api/type-aliases/ReadingOrder.md +15 -0
  175. package/docs/api/type-aliases/Rect.md +15 -0
  176. package/docs/api/type-aliases/Region.md +31 -0
  177. package/docs/api/type-aliases/RenderMode.md +15 -0
  178. package/docs/api/type-aliases/Rotation.md +15 -0
  179. package/docs/api/type-aliases/SaveOptions.md +15 -0
  180. package/docs/api/type-aliases/SaveReport.md +15 -0
  181. package/docs/api/type-aliases/Span.md +63 -0
  182. package/docs/api/type-aliases/SplitOn.md +15 -0
  183. package/docs/api/type-aliases/Standard14.md +15 -0
  184. package/docs/api/type-aliases/StructRef.md +41 -0
  185. package/docs/api/type-aliases/StructSource.md +17 -0
  186. package/docs/api/type-aliases/TableDetector.md +15 -0
  187. package/docs/api/type-aliases/TableMode.md +15 -0
  188. package/docs/api/type-aliases/TableScope.md +15 -0
  189. package/docs/api/type-aliases/Tag.md +15 -0
  190. package/docs/api/type-aliases/TagName.md +15 -0
  191. package/docs/api/type-aliases/TextLayout.md +15 -0
  192. package/docs/api/type-aliases/TextRange.md +33 -0
  193. package/docs/api/type-aliases/TextRunInfo.md +15 -0
  194. package/docs/api/type-aliases/Transform.md +15 -0
  195. package/docs/api/type-aliases/UndoOutcome.md +15 -0
  196. package/docs/api/type-aliases/ViewerPreferences.md +15 -0
  197. package/docs/api/type-aliases/WarningCode.md +15 -0
  198. package/docs/api/type-aliases/WasmSource.md +16 -0
  199. package/docs/api/variables/Matrix.md +99 -0
  200. package/docs/api/variables/PAGE_SIZES.md +15 -0
  201. package/docs/api/variables/TAG_NAMES.md +15 -0
  202. package/docs/errors/README.md +156 -0
  203. package/docs/errors/aes-padding-invalid.md +34 -0
  204. package/docs/errors/already-initialized.md +34 -0
  205. package/docs/errors/binary-output-to-terminal.md +35 -0
  206. package/docs/errors/builder-finished.md +35 -0
  207. package/docs/errors/cancelled.md +35 -0
  208. package/docs/errors/cmap-invalid.md +42 -0
  209. package/docs/errors/collection-index.md +33 -0
  210. package/docs/errors/color-components.md +33 -0
  211. package/docs/errors/content-bad-operands.md +36 -0
  212. package/docs/errors/content-path-unpainted.md +36 -0
  213. package/docs/errors/content-resource-missing.md +37 -0
  214. package/docs/errors/content-syntax.md +36 -0
  215. package/docs/errors/content-unbalanced-bt.md +36 -0
  216. package/docs/errors/content-unbalanced-marked.md +36 -0
  217. package/docs/errors/content-unbalanced-q.md +36 -0
  218. package/docs/errors/content-unknown-operator.md +37 -0
  219. package/docs/errors/data-pending.md +34 -0
  220. package/docs/errors/decompress.md +34 -0
  221. package/docs/errors/device-color-without-output-intent.md +33 -0
  222. package/docs/errors/direct-page.md +34 -0
  223. package/docs/errors/disposed.md +34 -0
  224. package/docs/errors/document-changed.md +34 -0
  225. package/docs/errors/embedded-file-forbidden.md +35 -0
  226. package/docs/errors/embedded-file-without-mime-type.md +36 -0
  227. package/docs/errors/encoding-full.md +34 -0
  228. package/docs/errors/encrypted-base.md +33 -0
  229. package/docs/errors/extract-limit-blocks.md +35 -0
  230. package/docs/errors/extract-module-not-loaded.md +37 -0
  231. package/docs/errors/extract-reading-order-ambiguous.md +32 -0
  232. package/docs/errors/extract-struct-tree-ignored.md +32 -0
  233. package/docs/errors/extract-table-ambiguous.md +32 -0
  234. package/docs/errors/extract-table-limit.md +35 -0
  235. package/docs/errors/extract-untagged-content.md +32 -0
  236. package/docs/errors/fetch-failed.md +34 -0
  237. package/docs/errors/flate-truncated.md +34 -0
  238. package/docs/errors/font-invalid.md +36 -0
  239. package/docs/errors/font-mismatch.md +34 -0
  240. package/docs/errors/font-not-embeddable.md +34 -0
  241. package/docs/errors/font-not-embedded.md +35 -0
  242. package/docs/errors/font-not-shapable.md +33 -0
  243. package/docs/errors/glyph-out-of-range.md +34 -0
  244. package/docs/errors/glyph-without-unicode.md +35 -0
  245. package/docs/errors/heading-level-skipped.md +36 -0
  246. package/docs/errors/image-data-length.md +33 -0
  247. package/docs/errors/image-too-large.md +32 -0
  248. package/docs/errors/inline-image-unterminated.md +36 -0
  249. package/docs/errors/input-unreadable.md +37 -0
  250. package/docs/errors/instance-reset.md +34 -0
  251. package/docs/errors/invalid-argument.md +33 -0
  252. package/docs/errors/invalid-axis-value.md +32 -0
  253. package/docs/errors/invalid-box.md +34 -0
  254. package/docs/errors/invalid-color.md +32 -0
  255. package/docs/errors/invalid-command-line.md +44 -0
  256. package/docs/errors/invalid-date.md +34 -0
  257. package/docs/errors/invalid-entry.md +34 -0
  258. package/docs/errors/invalid-feature.md +32 -0
  259. package/docs/errors/invalid-font.md +32 -0
  260. package/docs/errors/invalid-geometry.md +32 -0
  261. package/docs/errors/invalid-icc-profile.md +32 -0
  262. package/docs/errors/invalid-image.md +33 -0
  263. package/docs/errors/invalid-op-log.md +33 -0
  264. package/docs/errors/invalid-page-label.md +34 -0
  265. package/docs/errors/invalid-page-labels.md +32 -0
  266. package/docs/errors/invalid-page-range.md +37 -0
  267. package/docs/errors/invalid-rotate.md +34 -0
  268. package/docs/errors/invalid-structure-nesting.md +38 -0
  269. package/docs/errors/invalid-structure.md +34 -0
  270. package/docs/errors/invalid-text-range.md +33 -0
  271. package/docs/errors/job-finished.md +34 -0
  272. package/docs/errors/limit-exceeded.md +38 -0
  273. package/docs/errors/link-without-contents.md +33 -0
  274. package/docs/errors/link-without-structure.md +37 -0
  275. package/docs/errors/missing-alt-text.md +33 -0
  276. package/docs/errors/missing-glyph.md +33 -0
  277. package/docs/errors/missing-header.md +37 -0
  278. package/docs/errors/missing-lang.md +32 -0
  279. package/docs/errors/missing-root.md +34 -0
  280. package/docs/errors/missing-title.md +32 -0
  281. package/docs/errors/name-tree-node-skipped.md +34 -0
  282. package/docs/errors/nested-transaction.md +34 -0
  283. package/docs/errors/no-pending-save.md +32 -0
  284. package/docs/errors/no-transaction.md +33 -0
  285. package/docs/errors/not-initialized.md +35 -0
  286. package/docs/errors/not-pdf.md +33 -0
  287. package/docs/errors/not-rebased.md +35 -0
  288. package/docs/errors/object-number-out-of-range.md +38 -0
  289. package/docs/errors/objstm-index-mismatch.md +36 -0
  290. package/docs/errors/out-of-memory.md +38 -0
  291. package/docs/errors/outline-cycle.md +34 -0
  292. package/docs/errors/outline-item-skipped.md +34 -0
  293. package/docs/errors/output-unwritable.md +37 -0
  294. package/docs/errors/overwrite-refused.md +36 -0
  295. package/docs/errors/page-count-mismatch.md +34 -0
  296. package/docs/errors/page-index-out-of-range.md +34 -0
  297. package/docs/errors/page-tree-node-skipped.md +35 -0
  298. package/docs/errors/password-required.md +36 -0
  299. package/docs/errors/path-outside-roots.md +32 -0
  300. package/docs/errors/perms-mismatch.md +34 -0
  301. package/docs/errors/profile-version-conflict.md +35 -0
  302. package/docs/errors/reference-cycle.md +35 -0
  303. package/docs/errors/render-backend-unavailable.md +32 -0
  304. package/docs/errors/render-failed.md +33 -0
  305. package/docs/errors/render-too-large.md +36 -0
  306. package/docs/errors/repaired-base.md +34 -0
  307. package/docs/errors/requires-rewrite.md +38 -0
  308. package/docs/errors/saved-not-rebased.md +34 -0
  309. package/docs/errors/sink-not-empty.md +33 -0
  310. package/docs/errors/sink.md +31 -0
  311. package/docs/errors/source-budget-exceeded.md +34 -0
  312. package/docs/errors/source-changed.md +33 -0
  313. package/docs/errors/source.md +30 -0
  314. package/docs/errors/stale-generation.md +34 -0
  315. package/docs/errors/stream-length-recovered.md +37 -0
  316. package/docs/errors/strict.md +34 -0
  317. package/docs/errors/subset-failed.md +31 -0
  318. package/docs/errors/syntax.md +34 -0
  319. package/docs/errors/text-module-not-loaded.md +36 -0
  320. package/docs/errors/text-overflow.md +34 -0
  321. package/docs/errors/tounicode-conflict.md +32 -0
  322. package/docs/errors/type.md +34 -0
  323. package/docs/errors/unexpected-eof.md +34 -0
  324. package/docs/errors/unknown-axis.md +33 -0
  325. package/docs/errors/unknown-code.md +36 -0
  326. package/docs/errors/unknown-destination.md +33 -0
  327. package/docs/errors/unknown-font.md +33 -0
  328. package/docs/errors/unknown-option.md +33 -0
  329. package/docs/errors/unknown-page.md +34 -0
  330. package/docs/errors/unmapped-glyph.md +36 -0
  331. package/docs/errors/unnumbered-heading-misused.md +34 -0
  332. package/docs/errors/unset-object.md +30 -0
  333. package/docs/errors/unsupported-filter.md +34 -0
  334. package/docs/errors/unsupported-image.md +33 -0
  335. package/docs/errors/unsupported-layout-option.md +33 -0
  336. package/docs/errors/unsupported-security-handler.md +34 -0
  337. package/docs/errors/unsupported-version.md +32 -0
  338. package/docs/errors/untagged-content.md +35 -0
  339. package/docs/errors/warnings-truncated.md +35 -0
  340. package/docs/errors/wasm-trap.md +37 -0
  341. package/docs/errors/whole-font-rebuilt.md +30 -0
  342. package/docs/errors/worker-crashed.md +33 -0
  343. package/docs/errors/worker-failed.md +32 -0
  344. package/docs/errors/worker-version-mismatch.md +34 -0
  345. package/docs/errors/would-invalidate-signatures.md +38 -0
  346. package/docs/errors/wrong-document.md +33 -0
  347. package/docs/errors/wrong-password.md +34 -0
  348. package/docs/errors/xobject-cycle.md +36 -0
  349. package/docs/errors/xref-repaired.md +36 -0
  350. package/docs/errors/xrefstm-ignored.md +34 -0
  351. package/docs/llms.txt +88 -0
  352. package/docs/pages/concepts/dependencies.md +44 -0
  353. package/docs/pages/concepts/errors-and-warnings.md +64 -0
  354. package/docs/pages/concepts/how-lamina-thinks.md +86 -0
  355. package/docs/pages/concepts/pages-and-coordinates.md +67 -0
  356. package/docs/pages/guides/accessible-pdfs.md +260 -0
  357. package/docs/pages/guides/archival-pdfs.md +429 -0
  358. package/docs/pages/guides/edit-a-pdf.md +137 -0
  359. package/docs/pages/guides/fonts-and-text.md +202 -0
  360. package/docs/pages/guides/images-and-vectors.md +219 -0
  361. package/docs/pages/guides/keep-the-page-responsive.md +81 -0
  362. package/docs/pages/guides/links-outlines-and-metadata.md +222 -0
  363. package/docs/pages/guides/password-protected-files.md +70 -0
  364. package/docs/pages/guides/save-a-pdf.md +144 -0
  365. package/docs/pages/guides/untrusted-files.md +80 -0
  366. package/docs/pages/index.md +33 -0
  367. package/docs/pages/install.md +75 -0
  368. package/docs/pages/quick-start/browser.md +67 -0
  369. package/docs/pages/quick-start/bun.md +60 -0
  370. package/docs/pages/quick-start/deno.md +58 -0
  371. package/docs/pages/quick-start/node.md +61 -0
  372. package/docs/pages/quick-start/rust.md +58 -0
  373. package/docs/pages/quick-start/workers.md +139 -0
  374. package/docs/pages/recipes/cloudflare-worker.md +115 -0
  375. package/docs/pages/recipes/extract-markdown.md +114 -0
  376. package/docs/pages/recipes/generate-then-edit.md +135 -0
  377. package/docs/pages/recipes/hello.md +94 -0
  378. package/docs/pages/recipes/images-and-vectors.md +209 -0
  379. package/docs/pages/recipes/invoice.md +226 -0
  380. package/docs/pages/recipes/links-and-outlines.md +215 -0
  381. package/docs/pages/recipes/multilingual.md +189 -0
  382. package/docs/pages/recipes/open-untrusted.md +65 -0
  383. package/docs/pages/recipes/pdf-a-archive.md +205 -0
  384. package/docs/pages/recipes/pdf-a3-invoice-with-xml.md +222 -0
  385. package/docs/pages/recipes/pdf-ua-report.md +237 -0
  386. package/docs/pages/recipes/report.md +125 -0
  387. package/docs/pages/recipes/web-worker-client.md +64 -0
  388. package/docs/pages/recipes.md +47 -0
  389. package/docs/pages/reference/changelog.md +86 -0
  390. package/docs/pages/reference/codes.md +14 -0
  391. package/docs/pages/reference/glossary.md +108 -0
  392. package/docs/pages/reference/rust.md +36 -0
  393. package/docs/pages/reference/stability.md +59 -0
  394. package/docs/pages/start/coding-agents.md +45 -0
  395. package/docs/pages/start/first-pdf.md +77 -0
  396. package/docs/pages/troubleshooting.md +74 -0
  397. package/docs/renames.json +86 -0
  398. package/docs/schemas/error.schema.json +305 -0
  399. package/docs/schemas/renames.schema.json +29 -0
  400. package/package.json +5 -3
  401. package/schemas/error.schema.json +19 -2
@@ -0,0 +1,114 @@
1
+ ---
2
+ title: "Extract text for an LLM"
3
+ description: "Turn a PDF into Markdown in reading order, JSON with a box for every block, and chunks sized for retrieval, in TypeScript and Rust."
4
+ version: "0.2.0-alpha.3"
5
+ url: "https://docs.laminapdf.com/recipes/extract-markdown/"
6
+ ---
7
+
8
+ # Extract text for an LLM
9
+
10
+ Turn a PDF into Markdown in reading order, JSON with a box for every block, and chunks sized for retrieval, in TypeScript and Rust.
11
+
12
+ You'll turn a PDF into three outputs an LLM or a search index can use: Markdown in reading order, JSON giving every paragraph and table its page and box, and 400-token chunks.
13
+
14
+ In TypeScript the function returns the three outputs and needs the extract module, `init({ extract: true })`. In Rust, the program instead extracts once and writes the three files. Both give the same JSON:
15
+
16
+ <TabItem label="TypeScript">`packages/sdk/examples/extract-markdown.ts`:
17
+
18
+ ```ts
19
+ // Extract a PDF's text for an LLM: Markdown in reading order, JSON with a box for every block,
20
+ // and chunks sized for retrieval.
21
+ //
22
+ // Tables drawn with lines come out as Markdown tables, and text a reader can't see (white,
23
+ // invisible or off the page) is left out of all three.
24
+ import { type Chunk, Document, type Extraction } from "@laminapdf/sdk";
25
+
26
+ /** What a PDF gives an LLM or a search index. */
27
+ export interface ForAnLlm {
28
+ /** The text in reading order, with a `<!-- page N -->` comment before each page. */
29
+ readonly markdown: string;
30
+ /** Every block with its page and box, the text left out, the file's warnings and the chunks. */
31
+ readonly json: Extraction;
32
+ /** About 400 tokens each, a new one at each heading. */
33
+ readonly chunks: readonly Chunk[];
34
+ }
35
+
36
+ /** The Markdown, the JSON and the chunks of `bytes`. Needs `init({ extract: true })`. */
37
+ export function extractForAnLlm(bytes: Uint8Array): ForAnLlm {
38
+ using doc = Document.open(bytes);
39
+ // Page markers let a reader cite the page a passage came from.
40
+ const markdown = doc.extract({ format: "markdown", pageMarkers: true });
41
+ // The JSON carries the same chunks, and each block's place in the Markdown.
42
+ const json = doc.extract({ format: "json", pageMarkers: true, chunk: { maxTokens: 400 } });
43
+ return { markdown, json, chunks: json.chunks ?? [] };
44
+ }
45
+ ```</TabItem>
46
+ <TabItem label="Rust">`crates/laminapdf/examples/extract_markdown.rs`:
47
+
48
+ ````rust
49
+ //! Extract a PDF's text for an LLM: Markdown in reading order, JSON with a box for every
50
+ //! block, and chunks sized for retrieval.
51
+ //!
52
+ //! Tables drawn with lines come out as Markdown tables, and text a reader can't see (white,
53
+ //! invisible or off the page) is left out of all three.
54
+ //!
55
+ //! ```sh
56
+ //! cargo run -p laminapdf --example extract_markdown -- <file.pdf> [out-dir]
57
+ //! # writes <out-dir>/<file>.md, <file>.json and <file>.chunks.jsonl
58
+ //! ```
59
+
60
+ use std::error::Error;
61
+ use std::path::{Path, PathBuf};
62
+
63
+ use laminapdf::extract::{ChunkOptions, ExtractOptions, MarkdownOptions, chunks_jsonl};
64
+ use laminapdf::{Document, OpenOptions};
65
+
66
+ fn main() -> Result<(), Box<dyn Error>> {
67
+ let mut args = std::env::args().skip(1);
68
+ let input = PathBuf::from(args.next().ok_or("usage: extract_markdown <file.pdf> [out-dir]")?);
69
+ let out = PathBuf::from(args.next().unwrap_or_else(|| "target/examples-out".into()));
70
+ std::fs::create_dir_all(&out)?;
71
+ let stem = input.file_stem().and_then(|s| s.to_str()).unwrap_or("document");
72
+ write_outputs(&std::fs::read(&input)?, &out, stem)
73
+ }
74
+
75
+ /// Writes the Markdown, the JSON and the chunks of `bytes` into `out`.
76
+ pub(crate) fn write_outputs(bytes: &[u8], out: &Path, stem: &str) -> Result<(), Box<dyn Error>> {
77
+ let doc = Document::open_bytes(bytes.to_vec(), OpenOptions::new())?;
78
+ let extraction = doc.extract(&ExtractOptions::new())?;
79
+
80
+ // Page markers let a reader cite the page a passage came from.
81
+ let markdown = extraction.to_markdown(&MarkdownOptions::new().with_page_markers(true));
82
+ std::fs::write(out.join(format!("{stem}.md")), &markdown.text)?;
83
+
84
+ // About 400 tokens a chunk, starting a new one at each heading.
85
+ let chunks = extraction.chunks(&markdown, &ChunkOptions::new().with_max_tokens(400));
86
+ std::fs::write(out.join(format!("{stem}.chunks.jsonl")), chunks_jsonl(&chunks))?;
87
+
88
+ let json = doc.extract_json(&extraction, &markdown, Some(chunks));
89
+ std::fs::write(out.join(format!("{stem}.json")), json)?;
90
+ Ok(())
91
+ }
92
+ ````</TabItem>
93
+
94
+ Run the Rust program on any PDF: `cargo run -p laminapdf --example extract_markdown -- report.pdf out`.
95
+
96
+ ## How it works
97
+
98
+ - `extract` (`doc.extract` in TypeScript) reads every page's text, joins it into lines and paragraphs, and puts the paragraphs in reading order. A two-column page reads down the left column, then the right; a title across both columns comes first.
99
+ - Tables drawn with lines become table blocks with their cells, and Markdown writes them as tables. A cell that spans columns or rows makes the table HTML, since Markdown tables can't span.
100
+ - Text a reader can't see never reaches the outputs: invisible text (such as the hidden layer of a scanned page), text off the page and text too small to read. The JSON lists each such run under `dropped`, with the reason and how many characters it held.
101
+ - `to_markdown` (`format: "markdown"` in TypeScript) writes headings, paragraphs and tables, with a `<!-- page N -->` comment before each page, so a passage can be traced to its page.
102
+ - `chunks` (`format: "chunks"`, or the `chunk` option with the JSON) groups whole paragraphs and tables up to the budget, starting a new chunk at each heading. Each chunk records the blocks, pages and boxes it came from, and where its text sits in the Markdown.
103
+ - `extract_json` (`format: "json"`) writes the `laminapdf.extract/1` document: the pages, every block with its box in points from the top-left corner, the dropped text, the file's warnings and the chunks.
104
+
105
+ ## What to change
106
+
107
+ - **Only some pages:** `pages: [0, 1]` in TypeScript, `ExtractOptions::new().with_pages(vec![0, 1])` in Rust (page indexes start at 0).
108
+ - **Tables without lines:** `tables: "lattice-and-stream"` (`.with_tables(TableMode::LatticeAndStream)`) also looks for text lined up in columns. It finds more tables, and now and then a pair of columns that isn't one.
109
+ - **Bigger or smaller chunks:** change `maxTokens` (`with_max_tokens`). A token here is about four characters, a rough count that runs low for code, numbers and scripts other than Latin, so leave room.
110
+ - **Overlap between chunks:** `overlap: 80` (`.with_overlap(80)`) repeats up to about 80 tokens of whole paragraphs from the end of the previous chunk.
111
+ - **One output (TypeScript):** set `format` to `"markdown"`, `"text"`, `"json"` or `"chunks"`.
112
+ - **A long file in steps (Rust):** `doc.extract_job(&options)` and `step` do a page per unit of work, so a long file can report progress or stop between steps.
113
+
114
+ Next: [all recipes](../../pages/recipes.md).
@@ -0,0 +1,135 @@
1
+ ---
2
+ title: "Generate, then edit"
3
+ description: "Build a one-page draft, open its bytes, retitle it in one transaction and save the edit as a revision after the original bytes."
4
+ version: "0.2.0-alpha.3"
5
+ url: "https://docs.laminapdf.com/recipes/generate-then-edit/"
6
+ ---
7
+
8
+ # Generate, then edit
9
+
10
+ Build a one-page draft, open its bytes, retitle it in one transaction and save the edit as a revision after the original bytes.
11
+
12
+ You'll generate a one-page draft report, then open its bytes, retitle it and save the edit as a revision after them. You need this whenever your app changes a PDF it already has, such as a file a user uploads or one it wrote earlier.
13
+
14
+ One function builds and saves the draft. Another opens those bytes, changes the title and author, and saves again. In TypeScript the draft needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
+
16
+ <TabItem label="TypeScript">`packages/sdk/examples/generate-then-edit.ts`:
17
+
18
+ ```ts
19
+ // Generate a document, then open its bytes, edit them and save incrementally. The edit changes
20
+ // the Info dictionary in one transaction, and the incremental save keeps the generated bytes as
21
+ // they are, with one revision appended.
22
+ import { Builder, type BuilderOptions, Document, Font } from "@laminapdf/sdk";
23
+
24
+ /**
25
+ * The generated file followed by the edit's revision. Needs `init({ text: true })`.
26
+ *
27
+ * @param options - can fix the file ID, the producer and the creation date.
28
+ * @param editedAt - the modification date to record, as a PDF date string.
29
+ */
30
+ export function generateThenEdit(options: BuilderOptions = {}, editedAt?: string): Uint8Array<ArrayBuffer> {
31
+ return edit(generate(options), editedAt);
32
+ }
33
+
34
+ /** A one-page draft, saved (a new document is always saved as a full rewrite). */
35
+ export function generate(options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
36
+ using b = new Builder(options);
37
+ const page = b.page("A4");
38
+ page.surface().text("Draft report", { font: Font.standard("Helvetica"), size: 20, at: [72, 96] });
39
+ page.finish();
40
+ b.metadata({ title: "Draft report" });
41
+ using doc = b.finish();
42
+ return doc.save();
43
+ }
44
+
45
+ /** Opens `bytes`, edits the Info dictionary in one transaction (which `undo` could revert) and saves incrementally. */
46
+ function edit(bytes: Uint8Array, editedAt?: string): Uint8Array<ArrayBuffer> {
47
+ using doc = Document.open(bytes);
48
+ doc.transact((tx) => tx.setInfo({ title: "Final report", author: "Ada Lovelace" }, editedAt));
49
+ return doc.save({ mode: "incremental" });
50
+ }
51
+ ```</TabItem>
52
+ <TabItem label="Rust">`crates/laminapdf/examples/generate_then_edit.rs`:
53
+
54
+ ````rust
55
+ //! Generate a document, then open its bytes, edit them and save incrementally.
56
+ //!
57
+ //! The edit changes the Info dictionary with an op (undoable and serialisable), and the
58
+ //! incremental save keeps the generated bytes as they are, with one revision appended.
59
+ //!
60
+ //! ```sh
61
+ //! cargo run -p laminapdf --example generate_then_edit -- [out-dir] # writes <out-dir>/generate_then_edit.pdf
62
+ //! ```
63
+
64
+ use std::error::Error;
65
+ use std::path::PathBuf;
66
+
67
+ use laminapdf::{
68
+ BuildOptions, Builder, Document, InfoPatch, Metadata, Op, OpenOptions, PageSize, PdfDate, PointPt, Pt, SaveMode,
69
+ SaveOptions, SetInfo, Standard14,
70
+ };
71
+
72
+ fn main() -> Result<(), Box<dyn Error>> {
73
+ let out = PathBuf::from(std::env::args().nth(1).unwrap_or_else(|| "target/examples-out".into()));
74
+ std::fs::create_dir_all(&out)?;
75
+ let bytes = build(BuildOptions::new(), None)?;
76
+ std::fs::write(out.join("generate_then_edit.pdf"), bytes)?;
77
+ Ok(())
78
+ }
79
+
80
+ /// The generated file followed by the edit's revision. `edited_at` is recorded as the
81
+ /// modification date; `options` can fix the file ID, the producer and the creation date.
82
+ pub(crate) fn build(options: BuildOptions, edited_at: Option<PdfDate>) -> Result<Vec<u8>, Box<dyn Error>> {
83
+ let generated = generate(options)?;
84
+ edit(generated, edited_at)
85
+ }
86
+
87
+ /// A one-page draft, saved (a new document is always saved as a full rewrite).
88
+ pub(crate) fn generate(options: BuildOptions) -> Result<Vec<u8>, Box<dyn Error>> {
89
+ let mut b = Builder::new(options);
90
+ let helvetica = b.standard_font(Standard14::Helvetica)?;
91
+ let mut page = b.page(PageSize::A4);
92
+ page.surface().text(
93
+ "Draft report",
94
+ &helvetica,
95
+ Pt::new(20.0),
96
+ PointPt::from_points(72.0, 96.0),
97
+ )?;
98
+ page.finish()?;
99
+ b.metadata(Metadata::default().with_title("Draft report"));
100
+ Ok(b.finish()?.save(&SaveOptions::new())?.bytes)
101
+ }
102
+
103
+ /// Opens `bytes`, edits the Info dictionary in one transaction (which `undo` could revert) and
104
+ /// saves incrementally: `bytes` unchanged, then the appended revision.
105
+ fn edit(bytes: Vec<u8>, edited_at: Option<PdfDate>) -> Result<Vec<u8>, Box<dyn Error>> {
106
+ let mut doc = Document::open_bytes(bytes, OpenOptions::new())?;
107
+ let patch = InfoPatch::new()
108
+ .title(Some("Final report"))
109
+ .author(Some("Ada Lovelace"));
110
+ doc.apply(vec![Op::from(SetInfo::new(patch, edited_at))])?;
111
+ let options = SaveOptions::new().with_mode(SaveMode::Incremental);
112
+ Ok(doc.save(&options)?.bytes)
113
+ }
114
+ ````</TabItem>
115
+
116
+ The result is the draft's bytes, unchanged, followed by one revision that retitles it "Final report" and names Ada Lovelace as its author: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/generate_then_edit.pdf).
117
+
118
+ ## How it works
119
+
120
+ - A new document has no original bytes to append to, so the draft's save is a [full rewrite](../../pages/reference/glossary.md#full-rewrite).
121
+ - `Document.open` (`Document::open_bytes` in Rust) reads the draft. Your edits sit beside its bytes and never change them; see [How Lamina PDF thinks about a PDF](../../pages/concepts/how-lamina-thinks.md).
122
+ - `setInfo` changes the Info dictionary: the title, author and other facts a viewer shows in a file's properties. `transact` applies it as one transaction, which `undo` reverts as a whole. In Rust, `apply` takes the list of ops as one transaction.
123
+ - `mode: "incremental"` (`SaveMode::Incremental`) asks for an [incremental save](../../pages/reference/glossary.md#incremental-save): the draft's bytes first, then one revision with the edit.
124
+ - `editedAt` is the modification date to record, a PDF date such as `D:20261030120000Z`. Leave it out and no date is written, because Lamina PDF never reads the clock.
125
+ - The builder also wrote the draft's title into its XMP metadata, an XML copy of the same facts. `setInfo` leaves that copy alone, so it still says "Draft report".
126
+
127
+ ## What to change
128
+
129
+ - **More edits in the same revision:** add them inside the callback, such as `tx.rotatePage(doc.page(0).id, 90)` (`RotatePage` in Rust). See [Edit a PDF](../../pages/guides/edit-a-pdf.md).
130
+ - **Removing an entry:** pass `null` for it, as in `{ author: null }` (`.author(None)` in Rust).
131
+ - **Your own file:** open its bytes in place of the draft's. A signed, repaired or encrypted file limits how it saves; [Save a PDF](../../pages/guides/save-a-pdf.md#when-a-file-refuses-a-save) lists the cases.
132
+ - **A whole new file:** pass `mode: "fullRewrite"` (`SaveMode::FullRewrite`), or leave `mode` out and let the save choose. See [Choose a save mode](../../pages/guides/save-a-pdf.md#choose-a-save-mode).
133
+ - **Edits stored as JSON:** replay a saved log with `doc.applyLog({ v: 1, ops })` (`apply_log` in Rust), as [Edit a PDF](../../pages/guides/edit-a-pdf.md#replay-edits-stored-as-json) shows.
134
+
135
+ Next: [a PDF/UA report](../../pages/recipes/pdf-ua-report.md), an accessible report checked as it's built.
@@ -0,0 +1,94 @@
1
+ ---
2
+ title: "Hello, world"
3
+ description: "One A4 page with a line of text in a standard 14 font, the smallest PDF Lamina PDF writes, in TypeScript and Rust."
4
+ version: "0.2.0-alpha.3"
5
+ url: "https://docs.laminapdf.com/recipes/hello/"
6
+ ---
7
+
8
+ # Hello, world
9
+
10
+ One A4 page with a line of text in a standard 14 font, the smallest PDF Lamina PDF writes, in TypeScript and Rust.
11
+
12
+ You'll make the smallest PDF Lamina PDF writes: one A4 page that says "Hello, world!" in Helvetica. Start from it to check that a new setup works, or as the skeleton of a document of your own.
13
+
14
+ The function builds the page and returns the PDF's bytes. In TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
+
16
+ <TabItem label="TypeScript">`packages/sdk/examples/hello.ts`:
17
+
18
+ ```ts
19
+ // The smallest document: one A4 page with a line of text in a standard 14 font.
20
+ import { Builder, type BuilderOptions, Font } from "@laminapdf/sdk";
21
+
22
+ /**
23
+ * The document's bytes. Needs `init({ text: true })`.
24
+ *
25
+ * @param options - can fix the file ID, the producer and the creation date, so that the same
26
+ * input gives the same bytes on every run.
27
+ */
28
+ export function hello(options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
29
+ using b = new Builder(options);
30
+ const page = b.page("A4");
31
+ // Page space: points from the top-left corner, y down; `at[1]` is the text's baseline.
32
+ page.surface().text("Hello, world!", { font: Font.standard("Helvetica"), size: 24, at: [72, 96] });
33
+ page.finish();
34
+ using doc = b.finish();
35
+ return doc.save();
36
+ }
37
+ ```</TabItem>
38
+ <TabItem label="Rust">`crates/laminapdf/examples/hello.rs`:
39
+
40
+ ````rust
41
+ //! The smallest document: one A4 page with a line of text in a standard 14 font.
42
+ //!
43
+ //! ```sh
44
+ //! cargo run -p laminapdf --example hello -- [out-dir] # writes <out-dir>/hello.pdf (default target/examples-out)
45
+ //! ```
46
+
47
+ use std::error::Error;
48
+ use std::path::PathBuf;
49
+
50
+ use laminapdf::{BuildOptions, Builder, PageSize, PointPt, Pt, SaveOptions, Standard14};
51
+
52
+ fn main() -> Result<(), Box<dyn Error>> {
53
+ let out = PathBuf::from(std::env::args().nth(1).unwrap_or_else(|| "target/examples-out".into()));
54
+ std::fs::create_dir_all(&out)?;
55
+ std::fs::write(out.join("hello.pdf"), build(BuildOptions::new())?)?;
56
+ Ok(())
57
+ }
58
+
59
+ /// The document's bytes. `options` can fix the file ID, the producer and the creation date, so
60
+ /// that the same input gives the same bytes on every run.
61
+ pub(crate) fn build(options: BuildOptions) -> Result<Vec<u8>, Box<dyn Error>> {
62
+ let mut b = Builder::new(options);
63
+ let helvetica = b.standard_font(Standard14::Helvetica)?;
64
+ let mut page = b.page(PageSize::A4);
65
+ // Page space: points from the top-left corner, y down; `y` is the text's baseline.
66
+ page.surface().text(
67
+ "Hello, world!",
68
+ &helvetica,
69
+ Pt::new(24.0),
70
+ PointPt::from_points(72.0, 96.0),
71
+ )?;
72
+ page.finish()?;
73
+ let mut doc = b.finish()?;
74
+ Ok(doc.save(&SaveOptions::new())?.bytes)
75
+ }
76
+ ````</TabItem>
77
+
78
+ The result is one page with the line near its top-left corner: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/hello.pdf).
79
+
80
+ ## How it works
81
+
82
+ - The builder makes an A4 page, and the page's surface is what you draw on.
83
+ - `at` is in points from the top-left corner of the page, with y going down. Its second number is the text's baseline. See [Pages and coordinates](../../pages/concepts/pages-and-coordinates.md).
84
+ - Helvetica is one of the [standard 14 fonts](../../pages/reference/glossary.md#standard-14-fonts), which every PDF viewer has, so the PDF embeds no font.
85
+ - Finishing the builder gives a document, and saving the document gives its bytes. `using` in TypeScript frees both as soon as the function ends.
86
+
87
+ ## What to change
88
+
89
+ - **The text and where it goes:** change the string, the size and `at`.
90
+ - **The page size:** name another size, such as `"Letter"`, or pass `[width, height]` in points.
91
+ - **The font:** for characters outside the standard 14 fonts' Latin set, embed a font. See [Text and fonts](../../pages/guides/fonts-and-text.md).
92
+ - **A file ID or a date of your own:** pass them in the builder's options. Without them, the file ID is a hash of the content and the PDF has no creation date, so the same code writes the same bytes on every run.
93
+
94
+ Next: [an invoice](../../pages/recipes/invoice.md), with an embedded font and a table of line items.
@@ -0,0 +1,209 @@
1
+ ---
2
+ title: "Images and vectors"
3
+ description: "Shapes, a dashed curve, gray, RGB, CMYK and hex colour, transparency, a blend mode, a clip, a rotation, a JPEG and an RGBA image on one page."
4
+ version: "0.2.0-alpha.3"
5
+ url: "https://docs.laminapdf.com/recipes/images-and-vectors/"
6
+ ---
7
+
8
+ # Images and vectors
9
+
10
+ Shapes, a dashed curve, gray, RGB, CMYK and hex colour, transparency, a blend mode, a clip, a rotation, a JPEG and an RGBA image on one page.
11
+
12
+ You'll draw shapes and images on one A4 page: a rectangle, a circle, a dashed curve, two see-through discs, a photo cut to an ellipse and a rotated gradient that fades out. Start from it for a logo, a chart, the rules of a table or a page with photos.
13
+
14
+ The function takes a JPEG's bytes, draws the page and returns the PDF's bytes. It draws no text, so in TypeScript it needs only the base module, `init()`. Both languages write the same bytes:
15
+
16
+ <TabItem label="TypeScript">`packages/sdk/examples/images-and-vectors.ts`:
17
+
18
+ ```ts
19
+ // Images and vector graphics on one A4 page. It draws shapes and a dashed Bézier curve in gray,
20
+ // RGB, CMYK and hex colour, overlaps two translucent discs with a blend mode, clips a JPEG to an
21
+ // ellipse and rotates an RGBA image with a soft mask. Needs only the base module.
22
+ import { Builder, type BuilderOptions, Image, Matrix, type Surface } from "@laminapdf/sdk";
23
+
24
+ /** The RGBA gradient's side, in pixels. */
25
+ const GLOW_SIDE = 16;
26
+
27
+ /**
28
+ * The document's bytes, with `photo` (a JPEG) on the page.
29
+ *
30
+ * @param options - can fix the file ID, the producer and the creation date.
31
+ */
32
+ export function imagesAndVectors(photo: Uint8Array, options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
33
+ using b = new Builder(options);
34
+ const jpeg = Image.jpeg(photo);
35
+ const glow = Image.rgba(GLOW_SIDE, GLOW_SIDE, gradient());
36
+ const page = b.page("A4");
37
+ const s = page.surface();
38
+ shapes(s);
39
+ overlappingDiscs(s);
40
+ images(s, jpeg, glow);
41
+ page.finish();
42
+ using doc = b.finish();
43
+ return doc.save();
44
+ }
45
+
46
+ /** A square gradient whose alpha falls off towards the right. */
47
+ function gradient(): Uint8Array {
48
+ const out = new Uint8Array(GLOW_SIDE * GLOW_SIDE * 4);
49
+ for (let y = 0; y < GLOW_SIDE; y++) {
50
+ for (let x = 0; x < GLOW_SIDE; x++) out.set([x * 16, y * 16, 200, 255 - x * 15], (y * GLOW_SIDE + x) * 4);
51
+ }
52
+ return out;
53
+ }
54
+
55
+ /** A rectangle, a circle and a dashed, round-capped Bézier curve. */
56
+ function shapes(s: Surface): void {
57
+ s.fillColor([0.9, 0.3, 0.2])
58
+ .path((p) => p.rect([56, 56, 160, 100]))
59
+ .fill();
60
+ s.fillColor([0.8, 0.1, 0, 0])
61
+ .path((p) => p.circle([330, 106], 50))
62
+ .fill();
63
+ s.strokeColor("#2a9d8f")
64
+ .path((p) => p.moveTo(56, 220).cubicTo(180, 140, 300, 300, 539, 200))
65
+ .stroke({ width: 3, cap: "round", dash: [12, 8] });
66
+ }
67
+
68
+ /** Two translucent discs, the second multiplied onto the first. */
69
+ function overlappingDiscs(s: Surface): void {
70
+ s.save().opacity(0.6, 1).fillColor([0.3]);
71
+ s.path((p) => p.circle([120, 330], 60)).fill();
72
+ s.blendMode("multiply").fillColor([1, 0.8, 0]);
73
+ s.path((p) => p.circle([190, 330], 60))
74
+ .fill()
75
+ .restore();
76
+ }
77
+
78
+ /** The photo clipped to an ellipse, and the gradient rotated by 30 degrees. */
79
+ function images(s: Surface, photo: Image, glow: Image): void {
80
+ s.save()
81
+ .path((p) => p.ellipse([300, 270, 239, 120]))
82
+ .clip();
83
+ s.image(photo, [300, 250, 239, 160]).restore();
84
+ s.save().transform(Matrix.after(Matrix.translate(150, 520), Matrix.rotate(30)));
85
+ s.image(glow, [-60, -60, 120, 120]).restore();
86
+ }
87
+ ```</TabItem>
88
+ <TabItem label="Rust">`crates/laminapdf/examples/images_and_vectors.rs`:
89
+
90
+ ````rust
91
+ //! Images and vector graphics on one A4 page.
92
+ //!
93
+ //! It draws shapes and a dashed Bézier curve in gray, RGB, CMYK and hex colour, overlaps two
94
+ //! translucent discs with a blend mode, clips a JPEG to an ellipse and rotates an RGBA image
95
+ //! with a soft mask.
96
+ //!
97
+ //! ```sh
98
+ //! cargo run -p laminapdf --example images_and_vectors -- photo.jpg [out-dir] # writes images_and_vectors.pdf
99
+ //! ```
100
+
101
+ use std::error::Error;
102
+ use std::path::PathBuf;
103
+
104
+ use laminapdf::{
105
+ BlendMode, BuildOptions, Builder, Color, Dash, FillRule, Image, LineCap, Matrix, PageSize, PathBuilder, PointPt,
106
+ Pt, RectPt, SaveOptions, StrokeStyle, Surface,
107
+ };
108
+
109
+ /// The RGBA gradient's side, in pixels.
110
+ const GLOW_SIDE: u8 = 16;
111
+
112
+ fn main() -> Result<(), Box<dyn Error>> {
113
+ let mut args = std::env::args().skip(1);
114
+ let photo = std::fs::read(args.next().ok_or("usage: images_and_vectors <photo.jpg> [out-dir]")?)?;
115
+ let out = PathBuf::from(args.next().unwrap_or_else(|| "target/examples-out".into()));
116
+ std::fs::create_dir_all(&out)?;
117
+ std::fs::write(out.join("images_and_vectors.pdf"), build(BuildOptions::new(), photo)?)?;
118
+ Ok(())
119
+ }
120
+
121
+ /// The document's bytes, with `photo` (a JPEG) on the page. `options` can fix the file ID, the
122
+ /// producer and the creation date.
123
+ pub(crate) fn build(options: BuildOptions, photo: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
124
+ let mut b = Builder::new(options);
125
+ let photo = Image::jpeg(photo)?;
126
+ let glow = Image::rgba(u32::from(GLOW_SIDE), u32::from(GLOW_SIDE), &gradient())?;
127
+ let mut page = b.page(PageSize::A4);
128
+ let s = page.surface();
129
+ shapes(s)?;
130
+ overlapping_discs(s);
131
+ images(s, &photo, &glow);
132
+ page.finish()?;
133
+ let mut doc = b.finish()?;
134
+ Ok(doc.save(&SaveOptions::new())?.bytes)
135
+ }
136
+
137
+ /// A square gradient whose alpha falls off towards the right.
138
+ fn gradient() -> Vec<u8> {
139
+ (0..GLOW_SIDE)
140
+ .flat_map(|y| (0..GLOW_SIDE).flat_map(move |x| [x * 16, y * 16, 200, 255 - x * 15]))
141
+ .collect()
142
+ }
143
+
144
+ /// A rectangle, a circle and a dashed, round-capped Bézier curve.
145
+ fn shapes(s: &mut Surface) -> Result<(), Box<dyn Error>> {
146
+ let at = PointPt::from_points;
147
+ s.fill_color(Color::rgb(0.9, 0.3, 0.2))
148
+ .path(|p| _ = p.rect(RectPt::from_points(56.0, 56.0, 160.0, 100.0)))
149
+ .fill(FillRule::NonZero);
150
+ s.fill_color(Color::cmyk(0.8, 0.1, 0.0, 0.0))
151
+ .path(|p| _ = p.circle(at(330.0, 106.0), Pt::new(50.0)))
152
+ .fill(FillRule::NonZero);
153
+ let dashed = StrokeStyle {
154
+ width: Pt::new(3.0),
155
+ cap: LineCap::Round,
156
+ dash: Some(Dash::new(vec![12.0, 8.0], 0.0)),
157
+ ..StrokeStyle::default()
158
+ };
159
+ s.stroke_color(Color::hex("#2a9d8f")?)
160
+ .path(|p| {
161
+ p.move_to(at(56.0, 220.0))
162
+ .cubic_to(at(180.0, 140.0), at(300.0, 300.0), at(539.0, 200.0));
163
+ })
164
+ .stroke(&dashed);
165
+ Ok(())
166
+ }
167
+
168
+ /// Two translucent discs, the second multiplied onto the first.
169
+ fn overlapping_discs(s: &mut Surface) {
170
+ let disc = |x: f64| move |p: &mut PathBuilder| _ = p.circle(PointPt::from_points(x, 330.0), Pt::new(60.0));
171
+ s.save().opacity(0.6, 1.0).fill_color(Color::gray(0.3));
172
+ s.path(disc(120.0)).fill(FillRule::NonZero);
173
+ s.blend_mode(BlendMode::Multiply).fill_color(Color::rgb(1.0, 0.8, 0.0));
174
+ s.path(disc(190.0)).fill(FillRule::NonZero).restore();
175
+ }
176
+
177
+ /// The photo clipped to an ellipse, and the gradient rotated by 30 degrees.
178
+ fn images(s: &mut Surface, photo: &Image, glow: &Image) {
179
+ s.save()
180
+ .path(|p| _ = p.ellipse(RectPt::from_points(300.0, 270.0, 239.0, 120.0)))
181
+ .clip(FillRule::NonZero);
182
+ s.image(photo, RectPt::from_points(300.0, 250.0, 239.0, 160.0))
183
+ .restore();
184
+ let placement = Matrix::translate(150.0, 520.0).after(&Matrix::rotate(30.0));
185
+ s.save().transform(placement);
186
+ s.image(glow, RectPt::from_points(-60.0, -60.0, 120.0, 120.0)).restore();
187
+ }
188
+ ````</TabItem>
189
+
190
+ The result is one A4 page with every shape and both images: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/images_and_vectors.pdf).
191
+
192
+ ## How it works
193
+
194
+ - `path(p => …)` builds a shape and returns it unpainted. `fill` and `stroke` paint it, and `clip` limits what's drawn next to its inside. See [Shapes, colour and images](../../pages/guides/images-and-vectors.md).
195
+ - A colour of one, three or four numbers from 0 to 1 is gray, RGB or CMYK, and a string such as `"#2a9d8f"` is hex. Rust has `Color::gray`, `rgb`, `cmyk` and `hex`.
196
+ - `save()` and `restore()` bracket each group of drawings, so the opacity, blend mode, clip and rotation set inside stop at `restore()`.
197
+ - Both discs are 60% opaque, and `multiply` darkens the area where the second disc covers the first.
198
+ - `Image.jpeg` embeds the photo as it is, without decoding it. `Image.rgba` takes raw pixels, and their alpha becomes a soft mask: a second image that holds the transparency.
199
+ - The matrix turns the gradient 30 degrees clockwise, then moves it to (150, 520). The gradient is drawn centred on the origin, so it turns about its own centre. Like every position, the matrix works in [page space](../../pages/reference/glossary.md#page-space).
200
+
201
+ ## What to change
202
+
203
+ - **A PNG instead of the JPEG:** `Image.png(bytes)` (`Image::png` in Rust) keeps the PNG's transparency.
204
+ - **A shape with a hole:** draw both outlines in one path and fill it with `"evenOdd"` (`FillRule::EvenOdd`), so the inner shape stays empty.
205
+ - **A filled shape with an outline:** `fillStroke(rule, style)` (`fill_stroke` in Rust) paints both at once.
206
+ - **Another blend mode:** `"screen"` lightens where `"multiply"` darkens. `BlendMode` lists all sixteen.
207
+ - **Alternate text:** pass a description as the third argument of `image` (`figure` in Rust), and a screen reader reads it out. See [Accessible PDFs](../../pages/guides/accessible-pdfs.md).
208
+
209
+ Next: [links and outlines](../../pages/recipes/links-and-outlines.md), with four pages joined by links, an outline and page labels.