@laminapdf/sdk 0.2.0-alpha.2 → 0.2.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/README.md +63 -8
  2. package/dist/api.d.ts +8 -3
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +4 -2
  5. package/dist/api.js.map +1 -1
  6. package/dist/builder.d.ts +43 -22
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js +122 -83
  9. package/dist/builder.js.map +1 -1
  10. package/dist/document.d.ts +89 -22
  11. package/dist/document.d.ts.map +1 -1
  12. package/dist/document.js +177 -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-job.d.ts +100 -0
  19. package/dist/extract-job.d.ts.map +1 -0
  20. package/dist/extract-job.js +177 -0
  21. package/dist/extract-job.js.map +1 -0
  22. package/dist/extract.d.ts +57 -0
  23. package/dist/extract.d.ts.map +1 -0
  24. package/dist/extract.js +5 -0
  25. package/dist/extract.js.map +1 -0
  26. package/dist/font.d.ts +13 -6
  27. package/dist/font.d.ts.map +1 -1
  28. package/dist/font.js +50 -20
  29. package/dist/font.js.map +1 -1
  30. package/dist/generated/builder-enums.d.ts +3 -3
  31. package/dist/generated/builder-enums.d.ts.map +1 -1
  32. package/dist/generated/builder-enums.js +10 -0
  33. package/dist/generated/builder-enums.js.map +1 -1
  34. package/dist/generated/codes.d.ts +18 -6
  35. package/dist/generated/codes.d.ts.map +1 -1
  36. package/dist/generated/codes.js +114 -5
  37. package/dist/generated/codes.js.map +1 -1
  38. package/dist/generated/explain.d.ts.map +1 -1
  39. package/dist/generated/explain.js +45 -11
  40. package/dist/generated/explain.js.map +1 -1
  41. package/dist/generated/extract.d.ts +282 -0
  42. package/dist/generated/extract.d.ts.map +1 -0
  43. package/dist/generated/extract.js +4 -0
  44. package/dist/generated/extract.js.map +1 -0
  45. package/dist/init.d.ts +13 -5
  46. package/dist/init.d.ts.map +1 -1
  47. package/dist/init.js +23 -11
  48. package/dist/init.js.map +1 -1
  49. package/dist/internal/handle.d.ts +58 -0
  50. package/dist/internal/handle.d.ts.map +1 -0
  51. package/dist/internal/handle.js +113 -0
  52. package/dist/internal/handle.js.map +1 -0
  53. package/dist/internal/instance.d.ts +56 -0
  54. package/dist/internal/instance.d.ts.map +1 -0
  55. package/dist/internal/instance.js +125 -0
  56. package/dist/internal/instance.js.map +1 -0
  57. package/dist/source/http.d.ts +1 -1
  58. package/dist/source/http.js +2 -2
  59. package/dist/source/http.js.map +1 -1
  60. package/dist/source/range.d.ts +6 -1
  61. package/dist/source/range.d.ts.map +1 -1
  62. package/dist/source/range.js +10 -6
  63. package/dist/source/range.js.map +1 -1
  64. package/dist/text.d.ts +3 -3
  65. package/dist/text.d.ts.map +1 -1
  66. package/dist/text.js +3 -3
  67. package/dist/text.js.map +1 -1
  68. package/dist/types.d.ts +19 -1
  69. package/dist/types.d.ts.map +1 -1
  70. package/dist/version.d.ts +1 -1
  71. package/dist/version.js +2 -2
  72. package/dist/version.js.map +1 -1
  73. package/dist/worker/client.d.ts +3 -2
  74. package/dist/worker/client.d.ts.map +1 -1
  75. package/dist/worker/client.js +6 -4
  76. package/dist/worker/client.js.map +1 -1
  77. package/dist/worker/entry.js +1 -1
  78. package/dist/worker/entry.js.map +1 -1
  79. package/dist/worker/server.d.ts.map +1 -1
  80. package/dist/worker/server.js +42 -9
  81. package/dist/worker/server.js.map +1 -1
  82. package/dist/worker/support.d.ts +13 -3
  83. package/dist/worker/support.d.ts.map +1 -1
  84. package/dist/worker/support.js +5 -0
  85. package/dist/worker/support.js.map +1 -1
  86. package/dist/worker/types.d.ts +8 -1
  87. package/dist/worker/types.d.ts.map +1 -1
  88. package/docs/AGENTS.md +14 -0
  89. package/docs/api/README.md +166 -0
  90. package/docs/api/classes/Builder.md +467 -0
  91. package/docs/api/classes/Document.md +854 -0
  92. package/docs/api/classes/ExtractJob.md +236 -0
  93. package/docs/api/classes/Font.md +74 -0
  94. package/docs/api/classes/Image.md +140 -0
  95. package/docs/api/classes/LaminaPdfError.md +189 -0
  96. package/docs/api/classes/Page.md +178 -0
  97. package/docs/api/classes/PageRef.md +253 -0
  98. package/docs/api/classes/PageText.md +129 -0
  99. package/docs/api/classes/PageTextRun.md +167 -0
  100. package/docs/api/classes/PendingPath.md +97 -0
  101. package/docs/api/classes/SaveJob.md +205 -0
  102. package/docs/api/classes/Surface.md +331 -0
  103. package/docs/api/classes/Transaction.md +111 -0
  104. package/docs/api/functions/callbackSource.md +33 -0
  105. package/docs/api/functions/extractWithModels.md +70 -0
  106. package/docs/api/functions/httpRangeSource.md +48 -0
  107. package/docs/api/functions/init.md +36 -0
  108. package/docs/api/functions/isExtractLoaded.md +21 -0
  109. package/docs/api/functions/isInitialized.md +19 -0
  110. package/docs/api/functions/isLaminaPdfError.md +41 -0
  111. package/docs/api/functions/isTextLoaded.md +19 -0
  112. package/docs/api/functions/loadExtract.md +28 -0
  113. package/docs/api/functions/loadText.md +28 -0
  114. package/docs/api/functions/onLeak.md +29 -0
  115. package/docs/api/functions/reset.md +22 -0
  116. package/docs/api/functions/wasmMemory.md +22 -0
  117. package/docs/api/interfaces/BuilderOptions.md +108 -0
  118. package/docs/api/interfaces/ByteRange.md +29 -0
  119. package/docs/api/interfaces/ChunkOptions.md +37 -0
  120. package/docs/api/interfaces/Diagnostic.md +67 -0
  121. package/docs/api/interfaces/ExtractOptions.md +92 -0
  122. package/docs/api/interfaces/ExtractResult.md +37 -0
  123. package/docs/api/interfaces/FontInfo.md +37 -0
  124. package/docs/api/interfaces/HostModels.md +57 -0
  125. package/docs/api/interfaces/HttpRangeOptions.md +78 -0
  126. package/docs/api/interfaces/InitOptions.md +40 -0
  127. package/docs/api/interfaces/LayoutRegion.md +38 -0
  128. package/docs/api/interfaces/Leak.md +21 -0
  129. package/docs/api/interfaces/Location.md +53 -0
  130. package/docs/api/interfaces/ModelInfo.md +45 -0
  131. package/docs/api/interfaces/ModelRequest.md +45 -0
  132. package/docs/api/interfaces/Models.md +45 -0
  133. package/docs/api/interfaces/OcrWord.md +37 -0
  134. package/docs/api/interfaces/OpLog.md +25 -0
  135. package/docs/api/interfaces/OpenOptions.md +37 -0
  136. package/docs/api/interfaces/PageTextOptions.md +21 -0
  137. package/docs/api/interfaces/PathBuilder.md +195 -0
  138. package/docs/api/interfaces/QuadPoint.md +25 -0
  139. package/docs/api/interfaces/RangeOptions.md +54 -0
  140. package/docs/api/interfaces/RangeSource.md +59 -0
  141. package/docs/api/interfaces/StrokeStyle.md +61 -0
  142. package/docs/api/interfaces/TagSpec.md +69 -0
  143. package/docs/api/interfaces/TextBoxOptions.md +93 -0
  144. package/docs/api/interfaces/TextGlyph.md +29 -0
  145. package/docs/api/interfaces/TextOptions.md +101 -0
  146. package/docs/api/interfaces/WasmMemory.md +37 -0
  147. package/docs/api/type-aliases/AfRelationship.md +15 -0
  148. package/docs/api/type-aliases/Align.md +15 -0
  149. package/docs/api/type-aliases/ArtifactKind.md +15 -0
  150. package/docs/api/type-aliases/AttachmentOptions.md +15 -0
  151. package/docs/api/type-aliases/BlendMode.md +15 -0
  152. package/docs/api/type-aliases/BoxRect.md +15 -0
  153. package/docs/api/type-aliases/BuildOptions.md +15 -0
  154. package/docs/api/type-aliases/Cell.md +71 -0
  155. package/docs/api/type-aliases/Chunk.md +79 -0
  156. package/docs/api/type-aliases/Color.md +16 -0
  157. package/docs/api/type-aliases/Coordinates.md +55 -0
  158. package/docs/api/type-aliases/Direction.md +15 -0
  159. package/docs/api/type-aliases/DocumentInfo.md +15 -0
  160. package/docs/api/type-aliases/DropReason.md +15 -0
  161. package/docs/api/type-aliases/Dropped.md +59 -0
  162. package/docs/api/type-aliases/Edited.md +15 -0
  163. package/docs/api/type-aliases/ErrorCode.md +15 -0
  164. package/docs/api/type-aliases/ErrorKind.md +15 -0
  165. package/docs/api/type-aliases/ExtractFormat.md +15 -0
  166. package/docs/api/type-aliases/ExtractJobOptions.md +31 -0
  167. package/docs/api/type-aliases/ExtractStep.md +61 -0
  168. package/docs/api/type-aliases/ExtractedBlock.md +94 -0
  169. package/docs/api/type-aliases/ExtractedPage.md +73 -0
  170. package/docs/api/type-aliases/ExtractedSource.md +48 -0
  171. package/docs/api/type-aliases/Extraction.md +22 -0
  172. package/docs/api/type-aliases/FillRule.md +15 -0
  173. package/docs/api/type-aliases/Fit.md +15 -0
  174. package/docs/api/type-aliases/Flags.md +15 -0
  175. package/docs/api/type-aliases/Generator.md +43 -0
  176. package/docs/api/type-aliases/GlyphQuad.md +15 -0
  177. package/docs/api/type-aliases/InfoPatch.md +15 -0
  178. package/docs/api/type-aliases/LaminaPdfWarning.md +16 -0
  179. package/docs/api/type-aliases/LimitName.md +15 -0
  180. package/docs/api/type-aliases/Limits.md +15 -0
  181. package/docs/api/type-aliases/LineBox.md +15 -0
  182. package/docs/api/type-aliases/LineCap.md +15 -0
  183. package/docs/api/type-aliases/LineJoin.md +15 -0
  184. package/docs/api/type-aliases/LinkTarget.md +15 -0
  185. package/docs/api/type-aliases/Metadata.md +15 -0
  186. package/docs/api/type-aliases/ModelResult.md +15 -0
  187. package/docs/api/type-aliases/Op.md +15 -0
  188. package/docs/api/type-aliases/OutlineEntry.md +15 -0
  189. package/docs/api/type-aliases/OutlineNode.md +15 -0
  190. package/docs/api/type-aliases/Overflow.md +15 -0
  191. package/docs/api/type-aliases/PageInfo.md +15 -0
  192. package/docs/api/type-aliases/PageLabel.md +15 -0
  193. package/docs/api/type-aliases/PageSizeName.md +15 -0
  194. package/docs/api/type-aliases/Point.md +15 -0
  195. package/docs/api/type-aliases/Profile.md +15 -0
  196. package/docs/api/type-aliases/Progress.md +15 -0
  197. package/docs/api/type-aliases/ReadingOrder.md +15 -0
  198. package/docs/api/type-aliases/Rect.md +15 -0
  199. package/docs/api/type-aliases/Region.md +31 -0
  200. package/docs/api/type-aliases/RegionLabel.md +15 -0
  201. package/docs/api/type-aliases/RenderMode.md +15 -0
  202. package/docs/api/type-aliases/Rotation.md +15 -0
  203. package/docs/api/type-aliases/SaveOptions.md +15 -0
  204. package/docs/api/type-aliases/SaveReport.md +15 -0
  205. package/docs/api/type-aliases/Span.md +74 -0
  206. package/docs/api/type-aliases/SplitOn.md +15 -0
  207. package/docs/api/type-aliases/Standard14.md +15 -0
  208. package/docs/api/type-aliases/StructRef.md +41 -0
  209. package/docs/api/type-aliases/StructSource.md +17 -0
  210. package/docs/api/type-aliases/StructureKid.md +15 -0
  211. package/docs/api/type-aliases/StructureNode.md +16 -0
  212. package/docs/api/type-aliases/TableDetector.md +15 -0
  213. package/docs/api/type-aliases/TableMode.md +15 -0
  214. package/docs/api/type-aliases/TableScope.md +15 -0
  215. package/docs/api/type-aliases/Tag.md +15 -0
  216. package/docs/api/type-aliases/TagName.md +15 -0
  217. package/docs/api/type-aliases/Target.md +15 -0
  218. package/docs/api/type-aliases/TargetView.md +18 -0
  219. package/docs/api/type-aliases/TextLayer.md +72 -0
  220. package/docs/api/type-aliases/TextLayerStatus.md +15 -0
  221. package/docs/api/type-aliases/TextLayout.md +15 -0
  222. package/docs/api/type-aliases/TextRange.md +33 -0
  223. package/docs/api/type-aliases/TextRunInfo.md +15 -0
  224. package/docs/api/type-aliases/Transform.md +15 -0
  225. package/docs/api/type-aliases/UndoOutcome.md +15 -0
  226. package/docs/api/type-aliases/ViewerPreferences.md +15 -0
  227. package/docs/api/type-aliases/WarningCode.md +15 -0
  228. package/docs/api/type-aliases/WasmSource.md +16 -0
  229. package/docs/api/variables/Matrix.md +99 -0
  230. package/docs/api/variables/PAGE_SIZES.md +15 -0
  231. package/docs/api/variables/TAG_NAMES.md +15 -0
  232. package/docs/errors/README.md +173 -0
  233. package/docs/errors/aes-padding-invalid.md +34 -0
  234. package/docs/errors/already-initialized.md +34 -0
  235. package/docs/errors/binary-output-to-terminal.md +35 -0
  236. package/docs/errors/builder-finished.md +35 -0
  237. package/docs/errors/cancelled.md +35 -0
  238. package/docs/errors/cmap-invalid.md +42 -0
  239. package/docs/errors/collection-index.md +33 -0
  240. package/docs/errors/color-components.md +33 -0
  241. package/docs/errors/content-bad-operands.md +36 -0
  242. package/docs/errors/content-path-unpainted.md +36 -0
  243. package/docs/errors/content-resource-missing.md +37 -0
  244. package/docs/errors/content-syntax.md +36 -0
  245. package/docs/errors/content-unbalanced-bt.md +36 -0
  246. package/docs/errors/content-unbalanced-marked.md +36 -0
  247. package/docs/errors/content-unbalanced-q.md +36 -0
  248. package/docs/errors/content-unknown-operator.md +37 -0
  249. package/docs/errors/covered-text.md +33 -0
  250. package/docs/errors/data-pending.md +34 -0
  251. package/docs/errors/decompress.md +34 -0
  252. package/docs/errors/device-color-without-output-intent.md +33 -0
  253. package/docs/errors/direct-page.md +34 -0
  254. package/docs/errors/disposed.md +34 -0
  255. package/docs/errors/document-changed.md +34 -0
  256. package/docs/errors/embedded-file-forbidden.md +35 -0
  257. package/docs/errors/embedded-file-without-mime-type.md +36 -0
  258. package/docs/errors/encoding-full.md +34 -0
  259. package/docs/errors/encrypted-base.md +33 -0
  260. package/docs/errors/extract-column-order-uncertain.md +38 -0
  261. package/docs/errors/extract-limit-blocks.md +35 -0
  262. package/docs/errors/extract-module-not-loaded.md +37 -0
  263. package/docs/errors/extract-needs-ocr.md +37 -0
  264. package/docs/errors/extract-reading-order-ambiguous.md +32 -0
  265. package/docs/errors/extract-struct-tree-ignored.md +32 -0
  266. package/docs/errors/extract-table-ambiguous.md +32 -0
  267. package/docs/errors/extract-table-limit.md +35 -0
  268. package/docs/errors/extract-untagged-content.md +32 -0
  269. package/docs/errors/fetch-failed.md +34 -0
  270. package/docs/errors/flate-truncated.md +34 -0
  271. package/docs/errors/flow-content-too-large.md +34 -0
  272. package/docs/errors/flow-keep-broken.md +34 -0
  273. package/docs/errors/flow-layout-unstable.md +38 -0
  274. package/docs/errors/font-invalid.md +36 -0
  275. package/docs/errors/font-mismatch.md +34 -0
  276. package/docs/errors/font-not-embeddable.md +34 -0
  277. package/docs/errors/font-not-embedded.md +35 -0
  278. package/docs/errors/font-not-shapable.md +33 -0
  279. package/docs/errors/glyph-out-of-range.md +34 -0
  280. package/docs/errors/glyph-without-unicode.md +37 -0
  281. package/docs/errors/heading-level-skipped.md +36 -0
  282. package/docs/errors/image-data-length.md +33 -0
  283. package/docs/errors/image-too-large.md +32 -0
  284. package/docs/errors/inline-image-unterminated.md +36 -0
  285. package/docs/errors/input-unreadable.md +37 -0
  286. package/docs/errors/instance-reset.md +34 -0
  287. package/docs/errors/invalid-argument.md +33 -0
  288. package/docs/errors/invalid-axis-value.md +32 -0
  289. package/docs/errors/invalid-box.md +34 -0
  290. package/docs/errors/invalid-color.md +32 -0
  291. package/docs/errors/invalid-command-line.md +44 -0
  292. package/docs/errors/invalid-date.md +34 -0
  293. package/docs/errors/invalid-entry.md +34 -0
  294. package/docs/errors/invalid-feature.md +32 -0
  295. package/docs/errors/invalid-font.md +32 -0
  296. package/docs/errors/invalid-geometry.md +32 -0
  297. package/docs/errors/invalid-icc-profile.md +32 -0
  298. package/docs/errors/invalid-image.md +33 -0
  299. package/docs/errors/invalid-op-log.md +33 -0
  300. package/docs/errors/invalid-page-label.md +34 -0
  301. package/docs/errors/invalid-page-labels.md +32 -0
  302. package/docs/errors/invalid-page-range.md +37 -0
  303. package/docs/errors/invalid-rotate.md +34 -0
  304. package/docs/errors/invalid-spans.md +39 -0
  305. package/docs/errors/invalid-structure-nesting.md +38 -0
  306. package/docs/errors/invalid-structure.md +34 -0
  307. package/docs/errors/invalid-text-range.md +33 -0
  308. package/docs/errors/job-finished.md +34 -0
  309. package/docs/errors/limit-exceeded.md +38 -0
  310. package/docs/errors/link-without-contents.md +33 -0
  311. package/docs/errors/link-without-structure.md +37 -0
  312. package/docs/errors/markdown-html-ignored.md +33 -0
  313. package/docs/errors/markdown-image-missing.md +34 -0
  314. package/docs/errors/missing-alt-text.md +33 -0
  315. package/docs/errors/missing-glyph.md +33 -0
  316. package/docs/errors/missing-header.md +37 -0
  317. package/docs/errors/missing-lang.md +32 -0
  318. package/docs/errors/missing-root.md +34 -0
  319. package/docs/errors/missing-title.md +32 -0
  320. package/docs/errors/model-failed.md +33 -0
  321. package/docs/errors/model-needs-host.md +40 -0
  322. package/docs/errors/model-result-unexpected.md +36 -0
  323. package/docs/errors/name-tree-node-skipped.md +34 -0
  324. package/docs/errors/nested-transaction.md +34 -0
  325. package/docs/errors/no-pending-save.md +32 -0
  326. package/docs/errors/no-transaction.md +33 -0
  327. package/docs/errors/not-initialized.md +35 -0
  328. package/docs/errors/not-pdf.md +33 -0
  329. package/docs/errors/not-rebased.md +35 -0
  330. package/docs/errors/object-number-out-of-range.md +38 -0
  331. package/docs/errors/objstm-index-mismatch.md +36 -0
  332. package/docs/errors/out-of-memory.md +38 -0
  333. package/docs/errors/outline-cycle.md +34 -0
  334. package/docs/errors/outline-item-skipped.md +34 -0
  335. package/docs/errors/output-unwritable.md +37 -0
  336. package/docs/errors/overwrite-refused.md +36 -0
  337. package/docs/errors/page-count-mismatch.md +34 -0
  338. package/docs/errors/page-index-out-of-range.md +34 -0
  339. package/docs/errors/page-tree-node-skipped.md +35 -0
  340. package/docs/errors/password-required.md +36 -0
  341. package/docs/errors/path-outside-roots.md +32 -0
  342. package/docs/errors/perms-mismatch.md +34 -0
  343. package/docs/errors/profile-version-conflict.md +35 -0
  344. package/docs/errors/redaction-not-applied.md +32 -0
  345. package/docs/errors/reference-cycle.md +35 -0
  346. package/docs/errors/render-backend-unavailable.md +32 -0
  347. package/docs/errors/render-failed.md +33 -0
  348. package/docs/errors/render-too-large.md +36 -0
  349. package/docs/errors/repaired-base.md +34 -0
  350. package/docs/errors/requires-rewrite.md +38 -0
  351. package/docs/errors/revision-index-out-of-range.md +34 -0
  352. package/docs/errors/saved-not-rebased.md +34 -0
  353. package/docs/errors/sink-not-empty.md +33 -0
  354. package/docs/errors/sink.md +31 -0
  355. package/docs/errors/source-budget-exceeded.md +34 -0
  356. package/docs/errors/source-changed.md +33 -0
  357. package/docs/errors/source.md +30 -0
  358. package/docs/errors/stale-generation.md +34 -0
  359. package/docs/errors/stream-length-recovered.md +37 -0
  360. package/docs/errors/strict.md +34 -0
  361. package/docs/errors/subset-failed.md +31 -0
  362. package/docs/errors/syntax.md +34 -0
  363. package/docs/errors/tag-element-not-open.md +41 -0
  364. package/docs/errors/tag-element-unclosed.md +34 -0
  365. package/docs/errors/text-in-earlier-revision.md +36 -0
  366. package/docs/errors/text-module-not-loaded.md +36 -0
  367. package/docs/errors/text-overflow.md +34 -0
  368. package/docs/errors/tounicode-conflict.md +32 -0
  369. package/docs/errors/type.md +34 -0
  370. package/docs/errors/unexpected-eof.md +34 -0
  371. package/docs/errors/unknown-axis.md +33 -0
  372. package/docs/errors/unknown-code.md +36 -0
  373. package/docs/errors/unknown-destination.md +33 -0
  374. package/docs/errors/unknown-font.md +33 -0
  375. package/docs/errors/unknown-option.md +33 -0
  376. package/docs/errors/unknown-page.md +34 -0
  377. package/docs/errors/unmapped-glyph.md +36 -0
  378. package/docs/errors/unnumbered-heading-misused.md +34 -0
  379. package/docs/errors/unset-object.md +30 -0
  380. package/docs/errors/unsupported-filter.md +34 -0
  381. package/docs/errors/unsupported-image.md +33 -0
  382. package/docs/errors/unsupported-layout-option.md +33 -0
  383. package/docs/errors/unsupported-security-handler.md +34 -0
  384. package/docs/errors/unsupported-version.md +32 -0
  385. package/docs/errors/untagged-content.md +35 -0
  386. package/docs/errors/warnings-truncated.md +35 -0
  387. package/docs/errors/wasm-trap.md +37 -0
  388. package/docs/errors/whole-font-rebuilt.md +30 -0
  389. package/docs/errors/worker-crashed.md +33 -0
  390. package/docs/errors/worker-failed.md +32 -0
  391. package/docs/errors/worker-version-mismatch.md +34 -0
  392. package/docs/errors/would-invalidate-signatures.md +38 -0
  393. package/docs/errors/wrong-document.md +33 -0
  394. package/docs/errors/wrong-password.md +34 -0
  395. package/docs/errors/xobject-cycle.md +36 -0
  396. package/docs/errors/xref-repaired.md +36 -0
  397. package/docs/errors/xrefstm-ignored.md +34 -0
  398. package/docs/llms.txt +94 -0
  399. package/docs/pages/concepts/dependencies.md +44 -0
  400. package/docs/pages/concepts/drawing-pages.md +76 -0
  401. package/docs/pages/concepts/errors-and-warnings.md +68 -0
  402. package/docs/pages/concepts/how-lamina-thinks.md +98 -0
  403. package/docs/pages/concepts/pages-and-coordinates.md +75 -0
  404. package/docs/pages/guides/accessible-pdfs.md +262 -0
  405. package/docs/pages/guides/archival-pdfs.md +433 -0
  406. package/docs/pages/guides/edit-a-pdf.md +143 -0
  407. package/docs/pages/guides/flowing-pages.md +379 -0
  408. package/docs/pages/guides/fonts-and-text.md +204 -0
  409. package/docs/pages/guides/images-and-vectors.md +221 -0
  410. package/docs/pages/guides/keep-the-page-responsive.md +85 -0
  411. package/docs/pages/guides/links-outlines-and-metadata.md +224 -0
  412. package/docs/pages/guides/open-large-files.md +69 -0
  413. package/docs/pages/guides/password-protected-files.md +70 -0
  414. package/docs/pages/guides/save-a-pdf.md +154 -0
  415. package/docs/pages/guides/test-generated-pdfs.md +324 -0
  416. package/docs/pages/guides/untrusted-files.md +82 -0
  417. package/docs/pages/index.md +33 -0
  418. package/docs/pages/install.md +79 -0
  419. package/docs/pages/quick-start/browser.md +71 -0
  420. package/docs/pages/quick-start/bun.md +64 -0
  421. package/docs/pages/quick-start/deno.md +62 -0
  422. package/docs/pages/quick-start/node.md +65 -0
  423. package/docs/pages/quick-start/rust.md +62 -0
  424. package/docs/pages/quick-start/workers.md +139 -0
  425. package/docs/pages/recipes/cloudflare-worker.md +115 -0
  426. package/docs/pages/recipes/element-flow.md +248 -0
  427. package/docs/pages/recipes/extract-markdown.md +152 -0
  428. package/docs/pages/recipes/generate-then-edit.md +142 -0
  429. package/docs/pages/recipes/hello.md +101 -0
  430. package/docs/pages/recipes/images-and-vectors.md +216 -0
  431. package/docs/pages/recipes/invoice.md +233 -0
  432. package/docs/pages/recipes/links-and-outlines.md +222 -0
  433. package/docs/pages/recipes/multilingual.md +196 -0
  434. package/docs/pages/recipes/open-untrusted.md +65 -0
  435. package/docs/pages/recipes/pdf-a-archive.md +212 -0
  436. package/docs/pages/recipes/pdf-a3-invoice-with-xml.md +229 -0
  437. package/docs/pages/recipes/pdf-ua-report.md +244 -0
  438. package/docs/pages/recipes/report.md +125 -0
  439. package/docs/pages/recipes/web-worker-client.md +64 -0
  440. package/docs/pages/recipes.md +48 -0
  441. package/docs/pages/reference/benchmarks.md +93 -0
  442. package/docs/pages/reference/changelog.md +134 -0
  443. package/docs/pages/reference/codes.md +14 -0
  444. package/docs/pages/reference/glossary.md +108 -0
  445. package/docs/pages/reference/rust.md +36 -0
  446. package/docs/pages/reference/stability.md +59 -0
  447. package/docs/pages/start/coding-agents.md +45 -0
  448. package/docs/pages/start/first-pdf.md +81 -0
  449. package/docs/pages/troubleshooting.md +74 -0
  450. package/docs/renames.json +86 -0
  451. package/docs/schemas/error.schema.json +322 -0
  452. package/docs/schemas/renames.schema.json +29 -0
  453. package/package.json +7 -3
  454. package/schemas/error.schema.json +37 -3
@@ -0,0 +1,221 @@
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.4"
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
+ **Result:** [images_and_vectors.pdf](https://docs.laminapdf.com/examples/pdf/images_and_vectors.pdf)
216
+
217
+ [See the output](../../pages/recipes/images-and-vectors.md).
218
+
219
+ > **Details:** A PNG's alpha channel, and an RGBA image's, becomes a soft mask: a second image that holds the transparency.
220
+
221
+ Next: [links, outlines and metadata](../../pages/guides/links-outlines-and-metadata.md).
@@ -0,0 +1,85 @@
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.4"
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
+ The Web Worker runs one call at a time, so a call waits while another runs. 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` and `saveWithReport` stop at their 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, though the byte fetches of a file opened with `openUrl` stop. `client.terminate()` stops it at once.
66
+
67
+ *Each call crosses to the Web Worker as a message; an abort skips a call not started and stops a running save.*
68
+
69
+ Your page's client sends each call, such as `await doc.pageCount()`, to the Web Worker as a message. There `@laminapdf/sdk` runs one call at a time, and each answer comes back as a Promise; saved bytes and page text come back transferred rather than copied. When a call's `AbortSignal` aborts, the call rejects with `cancelled`. A call not started never runs, and a running `save` or `saveWithReport` stops at its next step. Any other call runs to the end, though the byte fetches of a file opened with `openUrl` stop.
70
+
71
+ ## Move bytes instead of copying them
72
+
73
+ 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.
74
+
75
+ ## Recover from a crash
76
+
77
+ 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.
78
+
79
+ `connect()` also checks that the Web Worker comes from the same `@laminapdf/sdk` build as your page. It rejects with `worker-version-mismatch` otherwise.
80
+
81
+ ## Bundlers and a content security policy
82
+
83
+ 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" }) })`.
84
+
85
+ Next: browse the [recipes](../../pages/recipes.md), with the PDF each one makes, or the [troubleshooting](../../pages/troubleshooting.md) page when something goes wrong.
@@ -0,0 +1,224 @@
1
+ ---
2
+ title: "Links, outlines and metadata"
3
+ description: "Add links, named destinations, an outline, page labels, viewer preferences and metadata to a document."
4
+ version: "0.2.0-alpha.4"
5
+ url: "https://docs.laminapdf.com/guides/links-outlines-and-metadata/"
6
+ ---
7
+
8
+ # Links, outlines and metadata
9
+
10
+ Add links, named destinations, an outline, page labels, viewer preferences and metadata to a document.
11
+
12
+ You'll make a document readers can find their way around: links between pages and to the web, an outline (the bookmarks panel), page numbers that read i, ii, 1, 2, and a title. You need this for any document longer than a page or two.
13
+
14
+ ## Link to a page or the web
15
+
16
+ `surface.link(rect, target, contents)` makes a clickable area. The target is a web address (`{ type: "uri" }`), a page (`{ type: "page", index }`) or a named destination (`{ type: "named", name }`). A text run takes a `link` option too. The builder checks every target when it finishes: a missing page fails with `page-index-out-of-range`, and a missing name with `unknown-destination`.
17
+
18
+ `builder.destination(name, index)` names a page, so links can point to it by name.
19
+
20
+ ## Add an outline and page labels
21
+
22
+ - **Outline:** `builder.outline([...])` builds the bookmarks a viewer shows beside the pages, with nested entries, colour, bold, italic and `open` (an entry shown expanded).
23
+ - **Page labels:** `builder.pageLabels([{ start, style }])` sets the page numbers a viewer shows. The example below uses roman numerals for the front matter, then decimal from page 3.
24
+ - **Viewer preferences:** for example, `pageMode: "outlines"` opens the file with the outline showing.
25
+
26
+ ## Set the metadata
27
+
28
+ `builder.metadata({ title, author, … })` sets the document's title, author and the rest (see Details).
29
+
30
+ This example builds four pages that link to each other and to the web, with an outline, page labels and the outline shown on open. It draws its labels as text, so it needs the text module. It sets no metadata ([Edit a PDF](../../pages/guides/edit-a-pdf.md) does). The example:
31
+
32
+ **TypeScript**
33
+
34
+ `packages/sdk/examples/links-and-outlines.ts`:
35
+
36
+ ```ts
37
+ // Links and outlines: four A5 pages joined by links, bookmarks and page labels. The title page
38
+ // links to a website, to a page and through a named destination; every other page has a linked
39
+ // text run back to it. The outline is nested and shown when the file opens.
40
+ import { Builder, type BuilderOptions, Font, type LinkTarget, type Surface } from "@laminapdf/sdk";
41
+
42
+ const TITLES = ["Title page", "Contents", "Chapter 1", "Chapter 2"] as const;
43
+ const MARGIN = 36;
44
+ const TITLE_BASELINE = 60;
45
+ const TITLE_SIZE = 18;
46
+ const LINK_SIZE = 12;
47
+ /** The baselines of the title page's three links. */
48
+ const LINK_BASELINES = [100, 125, 150] as const;
49
+ /** A link's clickable box: this wide, this tall, starting this far above the baseline. */
50
+ const LINK_WIDTH = 220;
51
+ const LINK_HEIGHT = 20;
52
+ const LINK_RISE = 15;
53
+
54
+ /**
55
+ * The document's bytes. Needs `init({ text: true })`.
56
+ *
57
+ * @param options - can fix the file ID, the producer and the creation date.
58
+ */
59
+ export function linksAndOutlines(options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
60
+ using b = new Builder(options);
61
+ const font = Font.standard("Helvetica");
62
+ b.font(font);
63
+ b.destination("chapter-2", 3);
64
+ TITLES.forEach((title, index) => {
65
+ const page = b.page("A5");
66
+ const s = page.surface();
67
+ s.text(title, { font, size: TITLE_SIZE, at: [MARGIN, TITLE_BASELINE] });
68
+ if (index === 0) titlePageLinks(s, font);
69
+ else {
70
+ const link = { target: { type: "page", index: 0 }, contents: "Title page" } as const;
71
+ s.text("Back to the title page", { font, size: LINK_SIZE, at: [MARGIN, 100], fill: [0, 0.2, 0.8], link });
72
+ }
73
+ page.finish();
74
+ });
75
+ navigation(b);
76
+ using doc = b.finish();
77
+ return doc.save();
78
+ }
79
+
80
+ /** The title page's links: to a website, to a page, and to a named destination. */
81
+ function titlePageLinks(s: Surface, font: Font): void {
82
+ const links: readonly (readonly [label: string, target: LinkTarget, contents?: string])[] = [
83
+ ["Visit example.com", { type: "uri", uri: "https://example.com/" }, "Example"],
84
+ ["Go to chapter 1", { type: "page", index: 2 }],
85
+ ["Go to chapter 2 (named)", { type: "named", name: "chapter-2" }],
86
+ ];
87
+ links.forEach(([label, target, contents], i) => {
88
+ const baseline = LINK_BASELINES[i] ?? 0;
89
+ s.text(label, { font, size: LINK_SIZE, at: [MARGIN, baseline] });
90
+ s.link([MARGIN, baseline - LINK_RISE, LINK_WIDTH, LINK_HEIGHT], target, contents);
91
+ });
92
+ }
93
+
94
+ /** The outline (bookmarks), the page labels, and the viewer opening with the outline shown. */
95
+ function navigation(b: Builder): void {
96
+ b.outline([
97
+ { title: "Title page", target: { type: "page", index: 0 } },
98
+ { title: "Contents", target: { type: "page", index: 1 }, color: [0, 0, 0.6] },
99
+ {
100
+ title: "Chapters",
101
+ open: true,
102
+ bold: true,
103
+ children: [
104
+ { title: "Chapter 1", target: { type: "page", index: 2 } },
105
+ { title: "Chapter 2", target: { type: "named", name: "chapter-2" }, italic: true },
106
+ ],
107
+ },
108
+ ]);
109
+ b.pageLabels([
110
+ { start: 0, style: "lowerRoman" },
111
+ { start: 2, style: "decimal" },
112
+ ]).viewerPreferences({ pageMode: "outlines" });
113
+ }
114
+ ```
115
+
116
+ **Rust**
117
+
118
+ `crates/laminapdf/examples/links_and_outlines.rs`:
119
+
120
+ ````rust
121
+ //! Links and outlines: four A5 pages joined by links, bookmarks and page labels.
122
+ //!
123
+ //! The title page has a URI link, a link to a page and one through a named destination; every
124
+ //! other page has a linked text run back to it. The outline (bookmarks) is nested and open in
125
+ //! the viewer, and the pages are labelled i, ii, 1, 2.
126
+ //!
127
+ //! ```sh
128
+ //! cargo run -p laminapdf --example links_and_outlines -- [out-dir] # writes <out-dir>/links_and_outlines.pdf
129
+ //! ```
130
+
131
+ use std::error::Error;
132
+ use std::path::PathBuf;
133
+
134
+ use laminapdf::{
135
+ BuildOptions, Builder, Color, Fit, FontHandle, LabelStyle, LinkTarget, OutlineEntry, PageLabel, PageMode, PageSize,
136
+ PointPt, Pt, RectPt, SaveOptions, Standard14, Surface, ViewerPreferences,
137
+ };
138
+
139
+ const TITLES: [&str; 4] = ["Title page", "Contents", "Chapter 1", "Chapter 2"];
140
+ const MARGIN: f64 = 36.0;
141
+ const TITLE_AT: PointPt = PointPt::from_points(MARGIN, 60.0);
142
+ const BACK_AT: PointPt = PointPt::from_points(MARGIN, 100.0);
143
+ const TITLE_SIZE: Pt = Pt::new(18.0);
144
+ const LINK_SIZE: Pt = Pt::new(12.0);
145
+ /// The baselines of the title page's three links.
146
+ const LINK_BASELINES: [f64; 3] = [100.0, 125.0, 150.0];
147
+ /// A link's clickable box: this wide, this tall, starting this far above the baseline.
148
+ const LINK_WIDTH: f64 = 220.0;
149
+ const LINK_HEIGHT: f64 = 20.0;
150
+ const LINK_RISE: f64 = 15.0;
151
+
152
+ fn main() -> Result<(), Box<dyn Error>> {
153
+ let out = PathBuf::from(std::env::args().nth(1).unwrap_or_else(|| "target/examples-out".into()));
154
+ std::fs::create_dir_all(&out)?;
155
+ std::fs::write(out.join("links_and_outlines.pdf"), build(BuildOptions::new())?)?;
156
+ Ok(())
157
+ }
158
+
159
+ /// The document's bytes. `options` can fix the file ID, the producer and the creation date.
160
+ pub(crate) fn build(options: BuildOptions) -> Result<Vec<u8>, Box<dyn Error>> {
161
+ let mut b = Builder::new(options);
162
+ let helvetica = b.standard_font(Standard14::Helvetica)?;
163
+ b.destination("chapter-2", 3, Fit::Page);
164
+ for (index, title) in (0_u32..).zip(TITLES) {
165
+ let mut page = b.page(PageSize::A5);
166
+ let s = page.surface();
167
+ s.text(title, &helvetica, TITLE_SIZE, TITLE_AT)?;
168
+ if index == 0 {
169
+ title_page_links(s, &helvetica)?;
170
+ } else {
171
+ s.text("Back to the title page", &helvetica, LINK_SIZE, BACK_AT)?
172
+ .fill(Color::rgb(0.0, 0.2, 0.8))
173
+ .link(LinkTarget::page(0), Some("Title page".into()));
174
+ }
175
+ page.finish()?;
176
+ }
177
+ navigation(&mut b)?;
178
+ let mut doc = b.finish()?;
179
+ Ok(doc.save(&SaveOptions::new())?.bytes)
180
+ }
181
+
182
+ /// The title page's links: to a website, to a page, and to a named destination.
183
+ fn title_page_links(s: &mut Surface, font: &FontHandle) -> Result<(), Box<dyn Error>> {
184
+ let site = LinkTarget::Uri("https://example.com/".into());
185
+ let links = [
186
+ ("Visit example.com", site, Some("Example")),
187
+ ("Go to chapter 1", LinkTarget::page(2), None),
188
+ ("Go to chapter 2 (named)", LinkTarget::Named("chapter-2".into()), None),
189
+ ];
190
+ for ((label, target, contents), baseline) in links.into_iter().zip(LINK_BASELINES) {
191
+ s.text(label, font, LINK_SIZE, PointPt::from_points(MARGIN, baseline))?;
192
+ let area = RectPt::from_points(MARGIN, baseline - LINK_RISE, LINK_WIDTH, LINK_HEIGHT);
193
+ s.link(area, target, contents.map(String::from));
194
+ }
195
+ Ok(())
196
+ }
197
+
198
+ /// The outline (bookmarks), the page labels, and the viewer opening with the outline shown.
199
+ fn navigation(b: &mut Builder) -> Result<(), Box<dyn Error>> {
200
+ b.outline(vec![
201
+ OutlineEntry::new("Title page", LinkTarget::page(0)),
202
+ OutlineEntry::new("Contents", LinkTarget::page(1)).with_color(0.0, 0.0, 0.6),
203
+ OutlineEntry::heading("Chapters")
204
+ .open(true)
205
+ .bold()
206
+ .with_child(OutlineEntry::new("Chapter 1", LinkTarget::page(2)))
207
+ .with_child(OutlineEntry::new("Chapter 2", LinkTarget::Named("chapter-2".into())).italic()),
208
+ ])?;
209
+ b.page_labels(vec![
210
+ PageLabel::new(0, Some(LabelStyle::LowerRoman)),
211
+ PageLabel::new(2, Some(LabelStyle::Decimal)),
212
+ ])
213
+ .viewer_preferences(ViewerPreferences::default().with_page_mode(PageMode::Outlines));
214
+ Ok(())
215
+ }
216
+ ````
217
+
218
+ **Result:** [links_and_outlines.pdf](https://docs.laminapdf.com/examples/pdf/links_and_outlines.pdf)
219
+
220
+ [See the output](../../pages/recipes/links-and-outlines.md).
221
+
222
+ > **Details:** The metadata goes into two places: an XMP packet, the XML that describes the file, and the Info dictionary, the older list of the same facts. Lamina PDF writes both from the same values, so they always agree.
223
+
224
+ Next: [accessible PDFs](../../pages/guides/accessible-pdfs.md).
@@ -0,0 +1,69 @@
1
+ ---
2
+ title: "Open large files by ranges"
3
+ description: "Open a PDF on a web server without downloading all of it, fetching only the byte ranges each read needs."
4
+ version: "0.2.0-alpha.4"
5
+ url: "https://docs.laminapdf.com/guides/open-large-files/"
6
+ ---
7
+
8
+ # Open large files by ranges
9
+
10
+ Open a PDF on a web server without downloading all of it, fetching only the byte ranges each read needs.
11
+
12
+ You'll open a PDF on a web server by fetching only the parts you read. A large report opens after a few small requests, and reading its first page fetches that page and little else. You need this when files are too large to download before the first page shows.
13
+
14
+ This example opens a remote file, then reads its page count and first page:
15
+
16
+ `packages/sdk/examples/open-large-file.ts`:
17
+
18
+ ```ts
19
+ // Opens a PDF on a web server by range requests: only the parts it reads are downloaded, so a
20
+ // large file opens without fetching all of it. The server must answer `Range` requests, as most
21
+ // file hosts and CDNs do; across origins it must also allow the `Range` and `If-Range` headers.
22
+ // Needs `init()`.
23
+ import { Document, httpRangeSource } from "@laminapdf/sdk";
24
+
25
+ /** What a first look at a remote file needs: its page count and the first page's size. */
26
+ export interface FirstLook {
27
+ readonly pages: number;
28
+ /** The first page's displayed size, in points (1/72 inch). */
29
+ readonly width: number;
30
+ readonly height: number;
31
+ }
32
+
33
+ /** Opens the PDF at `url` range by range and reads its page count and first page. */
34
+ export async function firstLook(url: string): Promise<FirstLook> {
35
+ // The first request reads the end of the file, where a PDF keeps the index of its objects.
36
+ using doc = await Document.openRange(httpRangeSource(url));
37
+ // A read can need bytes that haven't arrived. `ensure` catches the `data-pending` error, fetches
38
+ // the ranges it names, and runs the read again until it has what it needs.
39
+ const pages = await doc.ensure(() => doc.pageCount);
40
+ const first = await doc.ensure(() => doc.pageInfo(0));
41
+ return { pages, width: first.width, height: first.height };
42
+ }
43
+ ```
44
+
45
+ ## Each read fetches the bytes it needs
46
+
47
+ `Document.openRange` fetches only what opening needs: the end of the file, where a PDF keeps the index of its objects, then that index and the document's catalog. Calls on the document stay synchronous, so a read that needs bytes not fetched yet can't wait for them. It throws `data-pending` instead, with the byte ranges it needs. `doc.ensure` runs your read, fetches those ranges when it throws, and runs it again until it returns:
48
+
49
+ *A read that needs missing bytes fetches them and runs again.*
50
+
51
+ `Document.openRange` opens a remote file from the few byte ranges the open needs. A later read, such as `doc.pageInfo(0)`, may need bytes that haven't arrived: it then throws `data-pending`, naming the ranges it needs. `doc.ensure` catches the error, fetches those ranges through the source (`httpRangeSource`), supplies them to the document and runs the read again. Once the bytes are there, the read returns its result.
52
+
53
+ Wrap every read after the open in `doc.ensure`, saving included: `await doc.ensure(() => doc.save())` fetches whatever the save writes. A read fails with the last `data-pending` when its fetches bring nothing new, or when it still needs bytes after 1,024 rounds of fetches. Pass `maxRounds` to `Document.openRange` to change that number.
54
+
55
+ ## The server must answer `Range` requests
56
+
57
+ `httpRangeSource(url)` asks for byte ranges with the `Range` header, and most file hosts and CDNs answer them. Across origins, the server must also allow the `Range` and `If-Range` request headers (`Access-Control-Allow-Headers: Range, If-Range`) and expose `Content-Range`, `Content-Length` and `ETag` (`Access-Control-Expose-Headers`). It must not compress the response, or the ranges it sends wouldn't match the file's bytes. An error status from the server fails with `fetch-failed`, its `data.status` holding the status code.
58
+
59
+ A server that ignores `Range` and answers `200` sends the whole file. Lamina PDF then opens that one download, as `Document.open` would.
60
+
61
+ ## A file that changes on the server fails the read
62
+
63
+ Every request after the first sends `If-Range` with the `ETag` the first response gave, when it's a strong one, or else that response's `Last-Modified` date. When the file changes between two reads, the read fails with `source-changed` rather than mixing two versions. Open the file again to read the new one.
64
+
65
+ ## Limit how much a file can make you fetch
66
+
67
+ A remote file decides how much you fetch, so cap it when the file comes from someone else. Pass `budget` to `Document.openRange`: it's the most bytes the document may hold, and past it a read fails with `source-budget-exceeded`. Pass `signal` with the same options to stop the open's fetches. Later reads take their own: `doc.ensure(read, { signal: AbortSignal.timeout(10_000) })`.
68
+
69
+ The [limits for files you don't trust](../../pages/guides/untrusted-files.md) apply here too: pass `limits` with the other options.
@@ -0,0 +1,70 @@
1
+ ---
2
+ title: "Open password-protected files"
3
+ description: "Open PDFs that need a password, ask again when it is missing or wrong, and save them afterwards."
4
+ version: "0.2.0-alpha.4"
5
+ url: "https://docs.laminapdf.com/guides/password-protected-files/"
6
+ ---
7
+
8
+ # Open password-protected files
9
+
10
+ Open PDFs that need a password, ask again when it is missing or wrong, and save them afterwards.
11
+
12
+ You'll open a PDF that needs a password, and ask your user again when the password is missing or wrong. You need this for bank statements, contracts and other files that people protect before they share them.
13
+
14
+ This example opens an upload with an optional password and returns the outcome as a value instead of throwing. Its test opens a file encrypted with AES-256 three times: with the right password, with none and with a wrong one:
15
+
16
+ `packages/sdk/examples/open-untrusted.ts`:
17
+
18
+ ```ts
19
+ // Open bytes from someone else: an optional password, tighter limits than the defaults, and the
20
+ // outcome as a value rather than an exception. The example writes nothing: it returns what it found.
21
+ import { Document, isLaminaPdfError } from "@laminapdf/sdk";
22
+
23
+ const MiB = 1024 * 1024;
24
+ /** Limits for an upload endpoint: each decoded stream, and all of them together. */
25
+ const LIMITS = { maxStreamDecoded: 16 * MiB, maxDecodedTotal: 64 * MiB } as const;
26
+
27
+ /** What opening a file gave: its page count and warning codes, or why it didn't open. */
28
+ export type Opened =
29
+ | { readonly ok: true; readonly pages: number; readonly warnings: readonly string[] }
30
+ | { readonly ok: false; readonly code: string; readonly hint: string | undefined };
31
+
32
+ /** Opens `bytes`, with `password` if the file needs one, under {@link LIMITS}. Needs `init()`. */
33
+ export function openUntrusted(bytes: Uint8Array, password?: string): Opened {
34
+ try {
35
+ using doc = Document.open(bytes, { ...(password === undefined ? {} : { password }), limits: LIMITS });
36
+ // Repairs and dropped values are warnings: the file opened, but not exactly as written.
37
+ return { ok: true, pages: doc.pageCount, warnings: doc.warnings.map((w) => w.code) };
38
+ } catch (e) {
39
+ // `password-required`, `wrong-password`, `not-pdf`, `limit-exceeded`, …: a coded error whose
40
+ // message and hint never contain text from the file. Anything else is a bug: let it throw.
41
+ if (isLaminaPdfError(e)) return { ok: false, code: e.code, hint: e.hint };
42
+ throw e;
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Pass the password
48
+
49
+ Pass the password when you open the file: `Document.open(bytes, { password })`. The password goes only to the WebAssembly module. Lamina PDF never stores or logs it, and never puts it in an error.
50
+
51
+ A PDF can have two passwords. The user password opens the file, and the owner password also lifts its restrictions, such as on printing or copying. Lamina PDF checks your password against both:
52
+
53
+ - **Older files** (RC4 and AES-128) check the user password first.
54
+ - **AES-256 files** check the owner password first, as PDF 2.0 requires. So a password that matches both counts as the owner's.
55
+
56
+ ## When the password is missing or wrong
57
+
58
+ Opening fails with `password-required` when you pass no password, and with `wrong-password` when it matches neither. Nothing else has changed, so ask your user and open the file again.
59
+
60
+ ## Passwords with accents and other symbols
61
+
62
+ A character such as "é" can be written in more than one way in Unicode, so the same password can arrive as different bytes. In TypeScript, Lamina PDF tries the password as you give it and, after `wrong-password`, again in its NFKC form, the Unicode form that makes such spellings match. Older files also accept a password's Latin-1 bytes. AES-256 files accept the password as given or in the normalised form that PDF 2.0 asks for (SASLprep).
63
+
64
+ ## Save an encrypted file
65
+
66
+ Once you've edited an encrypted file, it saves only as an unencrypted [full rewrite](../../pages/reference/glossary.md#full-rewrite), with `removeEncryption: true`. Without that option, saving fails with `encrypted-base` (see [save a PDF](../../pages/guides/save-a-pdf.md#when-a-file-refuses-a-save)). Lamina PDF can't yet write encrypted files.
67
+
68
+ > **Details:** Lamina PDF reads files encrypted with the standard security handler: RC4 (revisions 2–4), AES-128 (revision 4) and AES-256 (revisions 5 and 6), including crypt filters and unencrypted metadata. It decrypts strings and streams as the file loads.
69
+
70
+ Next: [open files you don't trust](../../pages/guides/untrusted-files.md).