@laminapdf/sdk 0.2.0-alpha.3 → 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 (408) hide show
  1. package/README.md +21 -1
  2. package/dist/api.d.ts +4 -2
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +1 -0
  5. package/dist/api.js.map +1 -1
  6. package/dist/builder.d.ts +8 -3
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js +8 -3
  9. package/dist/builder.js.map +1 -1
  10. package/dist/document.d.ts +42 -1
  11. package/dist/document.d.ts.map +1 -1
  12. package/dist/document.js +65 -0
  13. package/dist/document.js.map +1 -1
  14. package/dist/extract-job.d.ts +100 -0
  15. package/dist/extract-job.d.ts.map +1 -0
  16. package/dist/extract-job.js +177 -0
  17. package/dist/extract-job.js.map +1 -0
  18. package/dist/extract.d.ts +1 -1
  19. package/dist/extract.d.ts.map +1 -1
  20. package/dist/extract.js.map +1 -1
  21. package/dist/generated/builder-enums.d.ts +3 -3
  22. package/dist/generated/builder-enums.d.ts.map +1 -1
  23. package/dist/generated/builder-enums.js +10 -0
  24. package/dist/generated/builder-enums.js.map +1 -1
  25. package/dist/generated/codes.d.ts +2 -2
  26. package/dist/generated/codes.d.ts.map +1 -1
  27. package/dist/generated/codes.js +55 -1
  28. package/dist/generated/codes.js.map +1 -1
  29. package/dist/generated/explain.d.ts.map +1 -1
  30. package/dist/generated/explain.js +19 -2
  31. package/dist/generated/explain.js.map +1 -1
  32. package/dist/generated/extract.d.ts +50 -3
  33. package/dist/generated/extract.d.ts.map +1 -1
  34. package/dist/source/http.d.ts +1 -1
  35. package/dist/source/http.js +1 -1
  36. package/dist/types.d.ts +19 -1
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/version.d.ts +1 -1
  39. package/dist/version.js +1 -1
  40. package/dist/worker/support.d.ts +7 -1
  41. package/dist/worker/support.d.ts.map +1 -1
  42. package/dist/worker/support.js +4 -0
  43. package/dist/worker/support.js.map +1 -1
  44. package/docs/AGENTS.md +1 -1
  45. package/docs/api/README.md +20 -1
  46. package/docs/api/classes/Builder.md +1 -1
  47. package/docs/api/classes/Document.md +113 -1
  48. package/docs/api/classes/ExtractJob.md +236 -0
  49. package/docs/api/classes/Font.md +1 -1
  50. package/docs/api/classes/Image.md +1 -1
  51. package/docs/api/classes/LaminaPdfError.md +1 -1
  52. package/docs/api/classes/Page.md +1 -1
  53. package/docs/api/classes/PageRef.md +1 -1
  54. package/docs/api/classes/PageText.md +1 -1
  55. package/docs/api/classes/PageTextRun.md +1 -1
  56. package/docs/api/classes/PendingPath.md +1 -1
  57. package/docs/api/classes/SaveJob.md +1 -1
  58. package/docs/api/classes/Surface.md +10 -5
  59. package/docs/api/classes/Transaction.md +1 -1
  60. package/docs/api/functions/callbackSource.md +1 -1
  61. package/docs/api/functions/extractWithModels.md +70 -0
  62. package/docs/api/functions/httpRangeSource.md +2 -2
  63. package/docs/api/functions/init.md +1 -1
  64. package/docs/api/functions/isExtractLoaded.md +1 -1
  65. package/docs/api/functions/isInitialized.md +1 -1
  66. package/docs/api/functions/isLaminaPdfError.md +1 -1
  67. package/docs/api/functions/isTextLoaded.md +1 -1
  68. package/docs/api/functions/loadExtract.md +1 -1
  69. package/docs/api/functions/loadText.md +1 -1
  70. package/docs/api/functions/onLeak.md +1 -1
  71. package/docs/api/functions/reset.md +1 -1
  72. package/docs/api/functions/wasmMemory.md +1 -1
  73. package/docs/api/interfaces/BuilderOptions.md +1 -1
  74. package/docs/api/interfaces/ByteRange.md +1 -1
  75. package/docs/api/interfaces/ChunkOptions.md +1 -1
  76. package/docs/api/interfaces/Diagnostic.md +1 -1
  77. package/docs/api/interfaces/ExtractOptions.md +1 -1
  78. package/docs/api/interfaces/ExtractResult.md +1 -1
  79. package/docs/api/interfaces/FontInfo.md +1 -1
  80. package/docs/api/interfaces/HostModels.md +57 -0
  81. package/docs/api/interfaces/HttpRangeOptions.md +1 -1
  82. package/docs/api/interfaces/InitOptions.md +1 -1
  83. package/docs/api/interfaces/LayoutRegion.md +38 -0
  84. package/docs/api/interfaces/Leak.md +1 -1
  85. package/docs/api/interfaces/Location.md +1 -1
  86. package/docs/api/interfaces/ModelInfo.md +45 -0
  87. package/docs/api/interfaces/ModelRequest.md +45 -0
  88. package/docs/api/interfaces/Models.md +45 -0
  89. package/docs/api/interfaces/OcrWord.md +37 -0
  90. package/docs/api/interfaces/OpLog.md +1 -1
  91. package/docs/api/interfaces/OpenOptions.md +1 -1
  92. package/docs/api/interfaces/PageTextOptions.md +1 -1
  93. package/docs/api/interfaces/PathBuilder.md +1 -1
  94. package/docs/api/interfaces/QuadPoint.md +1 -1
  95. package/docs/api/interfaces/RangeOptions.md +1 -1
  96. package/docs/api/interfaces/RangeSource.md +1 -1
  97. package/docs/api/interfaces/StrokeStyle.md +1 -1
  98. package/docs/api/interfaces/TagSpec.md +3 -3
  99. package/docs/api/interfaces/TextBoxOptions.md +1 -1
  100. package/docs/api/interfaces/TextGlyph.md +1 -1
  101. package/docs/api/interfaces/TextOptions.md +1 -1
  102. package/docs/api/interfaces/WasmMemory.md +1 -1
  103. package/docs/api/type-aliases/AfRelationship.md +1 -1
  104. package/docs/api/type-aliases/Align.md +1 -1
  105. package/docs/api/type-aliases/ArtifactKind.md +2 -2
  106. package/docs/api/type-aliases/AttachmentOptions.md +1 -1
  107. package/docs/api/type-aliases/BlendMode.md +1 -1
  108. package/docs/api/type-aliases/BoxRect.md +1 -1
  109. package/docs/api/type-aliases/BuildOptions.md +1 -1
  110. package/docs/api/type-aliases/Cell.md +1 -1
  111. package/docs/api/type-aliases/Chunk.md +1 -1
  112. package/docs/api/type-aliases/Color.md +1 -1
  113. package/docs/api/type-aliases/Coordinates.md +1 -1
  114. package/docs/api/type-aliases/Direction.md +1 -1
  115. package/docs/api/type-aliases/DocumentInfo.md +1 -1
  116. package/docs/api/type-aliases/DropReason.md +2 -2
  117. package/docs/api/type-aliases/Dropped.md +1 -1
  118. package/docs/api/type-aliases/Edited.md +1 -1
  119. package/docs/api/type-aliases/ErrorCode.md +2 -2
  120. package/docs/api/type-aliases/ErrorKind.md +1 -1
  121. package/docs/api/type-aliases/ExtractFormat.md +1 -1
  122. package/docs/api/type-aliases/ExtractJobOptions.md +31 -0
  123. package/docs/api/type-aliases/ExtractStep.md +61 -0
  124. package/docs/api/type-aliases/ExtractedBlock.md +13 -1
  125. package/docs/api/type-aliases/ExtractedPage.md +9 -1
  126. package/docs/api/type-aliases/ExtractedSource.md +1 -1
  127. package/docs/api/type-aliases/Extraction.md +1 -1
  128. package/docs/api/type-aliases/FillRule.md +1 -1
  129. package/docs/api/type-aliases/Fit.md +1 -1
  130. package/docs/api/type-aliases/Flags.md +1 -1
  131. package/docs/api/type-aliases/Generator.md +5 -3
  132. package/docs/api/type-aliases/GlyphQuad.md +1 -1
  133. package/docs/api/type-aliases/InfoPatch.md +1 -1
  134. package/docs/api/type-aliases/LaminaPdfWarning.md +1 -1
  135. package/docs/api/type-aliases/LimitName.md +1 -1
  136. package/docs/api/type-aliases/Limits.md +1 -1
  137. package/docs/api/type-aliases/LineBox.md +1 -1
  138. package/docs/api/type-aliases/LineCap.md +1 -1
  139. package/docs/api/type-aliases/LineJoin.md +1 -1
  140. package/docs/api/type-aliases/LinkTarget.md +1 -1
  141. package/docs/api/type-aliases/Metadata.md +1 -1
  142. package/docs/api/type-aliases/ModelResult.md +15 -0
  143. package/docs/api/type-aliases/Op.md +1 -1
  144. package/docs/api/type-aliases/OutlineEntry.md +1 -1
  145. package/docs/api/type-aliases/OutlineNode.md +15 -0
  146. package/docs/api/type-aliases/Overflow.md +1 -1
  147. package/docs/api/type-aliases/PageInfo.md +1 -1
  148. package/docs/api/type-aliases/PageLabel.md +1 -1
  149. package/docs/api/type-aliases/PageSizeName.md +1 -1
  150. package/docs/api/type-aliases/Point.md +1 -1
  151. package/docs/api/type-aliases/Profile.md +1 -1
  152. package/docs/api/type-aliases/Progress.md +1 -1
  153. package/docs/api/type-aliases/ReadingOrder.md +1 -1
  154. package/docs/api/type-aliases/Rect.md +1 -1
  155. package/docs/api/type-aliases/Region.md +1 -1
  156. package/docs/api/type-aliases/RegionLabel.md +15 -0
  157. package/docs/api/type-aliases/RenderMode.md +1 -1
  158. package/docs/api/type-aliases/Rotation.md +1 -1
  159. package/docs/api/type-aliases/SaveOptions.md +1 -1
  160. package/docs/api/type-aliases/SaveReport.md +1 -1
  161. package/docs/api/type-aliases/Span.md +12 -1
  162. package/docs/api/type-aliases/SplitOn.md +1 -1
  163. package/docs/api/type-aliases/Standard14.md +1 -1
  164. package/docs/api/type-aliases/StructRef.md +1 -1
  165. package/docs/api/type-aliases/StructSource.md +1 -1
  166. package/docs/api/type-aliases/StructureKid.md +15 -0
  167. package/docs/api/type-aliases/StructureNode.md +16 -0
  168. package/docs/api/type-aliases/TableDetector.md +1 -1
  169. package/docs/api/type-aliases/TableMode.md +1 -1
  170. package/docs/api/type-aliases/TableScope.md +1 -1
  171. package/docs/api/type-aliases/Tag.md +1 -1
  172. package/docs/api/type-aliases/TagName.md +1 -1
  173. package/docs/api/type-aliases/Target.md +15 -0
  174. package/docs/api/type-aliases/TargetView.md +18 -0
  175. package/docs/api/type-aliases/TextLayer.md +72 -0
  176. package/docs/api/type-aliases/TextLayerStatus.md +15 -0
  177. package/docs/api/type-aliases/TextLayout.md +1 -1
  178. package/docs/api/type-aliases/TextRange.md +1 -1
  179. package/docs/api/type-aliases/TextRunInfo.md +1 -1
  180. package/docs/api/type-aliases/Transform.md +1 -1
  181. package/docs/api/type-aliases/UndoOutcome.md +1 -1
  182. package/docs/api/type-aliases/ViewerPreferences.md +1 -1
  183. package/docs/api/type-aliases/WarningCode.md +2 -2
  184. package/docs/api/type-aliases/WasmSource.md +1 -1
  185. package/docs/api/variables/Matrix.md +1 -1
  186. package/docs/api/variables/PAGE_SIZES.md +1 -1
  187. package/docs/api/variables/TAG_NAMES.md +2 -2
  188. package/docs/errors/README.md +18 -1
  189. package/docs/errors/aes-padding-invalid.md +1 -1
  190. package/docs/errors/already-initialized.md +1 -1
  191. package/docs/errors/binary-output-to-terminal.md +1 -1
  192. package/docs/errors/builder-finished.md +1 -1
  193. package/docs/errors/cancelled.md +1 -1
  194. package/docs/errors/cmap-invalid.md +1 -1
  195. package/docs/errors/collection-index.md +1 -1
  196. package/docs/errors/color-components.md +1 -1
  197. package/docs/errors/content-bad-operands.md +1 -1
  198. package/docs/errors/content-path-unpainted.md +1 -1
  199. package/docs/errors/content-resource-missing.md +1 -1
  200. package/docs/errors/content-syntax.md +1 -1
  201. package/docs/errors/content-unbalanced-bt.md +1 -1
  202. package/docs/errors/content-unbalanced-marked.md +1 -1
  203. package/docs/errors/content-unbalanced-q.md +1 -1
  204. package/docs/errors/content-unknown-operator.md +1 -1
  205. package/docs/errors/covered-text.md +33 -0
  206. package/docs/errors/data-pending.md +1 -1
  207. package/docs/errors/decompress.md +1 -1
  208. package/docs/errors/device-color-without-output-intent.md +1 -1
  209. package/docs/errors/direct-page.md +1 -1
  210. package/docs/errors/disposed.md +1 -1
  211. package/docs/errors/document-changed.md +1 -1
  212. package/docs/errors/embedded-file-forbidden.md +1 -1
  213. package/docs/errors/embedded-file-without-mime-type.md +1 -1
  214. package/docs/errors/encoding-full.md +1 -1
  215. package/docs/errors/encrypted-base.md +1 -1
  216. package/docs/errors/extract-column-order-uncertain.md +38 -0
  217. package/docs/errors/extract-limit-blocks.md +1 -1
  218. package/docs/errors/extract-module-not-loaded.md +1 -1
  219. package/docs/errors/extract-needs-ocr.md +37 -0
  220. package/docs/errors/extract-reading-order-ambiguous.md +1 -1
  221. package/docs/errors/extract-struct-tree-ignored.md +1 -1
  222. package/docs/errors/extract-table-ambiguous.md +1 -1
  223. package/docs/errors/extract-table-limit.md +1 -1
  224. package/docs/errors/extract-untagged-content.md +1 -1
  225. package/docs/errors/fetch-failed.md +3 -3
  226. package/docs/errors/flate-truncated.md +1 -1
  227. package/docs/errors/flow-content-too-large.md +34 -0
  228. package/docs/errors/flow-keep-broken.md +34 -0
  229. package/docs/errors/flow-layout-unstable.md +38 -0
  230. package/docs/errors/font-invalid.md +1 -1
  231. package/docs/errors/font-mismatch.md +1 -1
  232. package/docs/errors/font-not-embeddable.md +1 -1
  233. package/docs/errors/font-not-embedded.md +1 -1
  234. package/docs/errors/font-not-shapable.md +1 -1
  235. package/docs/errors/glyph-out-of-range.md +1 -1
  236. package/docs/errors/glyph-without-unicode.md +7 -5
  237. package/docs/errors/heading-level-skipped.md +1 -1
  238. package/docs/errors/image-data-length.md +1 -1
  239. package/docs/errors/image-too-large.md +1 -1
  240. package/docs/errors/inline-image-unterminated.md +1 -1
  241. package/docs/errors/input-unreadable.md +1 -1
  242. package/docs/errors/instance-reset.md +1 -1
  243. package/docs/errors/invalid-argument.md +1 -1
  244. package/docs/errors/invalid-axis-value.md +1 -1
  245. package/docs/errors/invalid-box.md +1 -1
  246. package/docs/errors/invalid-color.md +1 -1
  247. package/docs/errors/invalid-command-line.md +1 -1
  248. package/docs/errors/invalid-date.md +1 -1
  249. package/docs/errors/invalid-entry.md +1 -1
  250. package/docs/errors/invalid-feature.md +1 -1
  251. package/docs/errors/invalid-font.md +1 -1
  252. package/docs/errors/invalid-geometry.md +1 -1
  253. package/docs/errors/invalid-icc-profile.md +1 -1
  254. package/docs/errors/invalid-image.md +1 -1
  255. package/docs/errors/invalid-op-log.md +1 -1
  256. package/docs/errors/invalid-page-label.md +1 -1
  257. package/docs/errors/invalid-page-labels.md +1 -1
  258. package/docs/errors/invalid-page-range.md +1 -1
  259. package/docs/errors/invalid-rotate.md +1 -1
  260. package/docs/errors/invalid-spans.md +39 -0
  261. package/docs/errors/invalid-structure-nesting.md +1 -1
  262. package/docs/errors/invalid-structure.md +1 -1
  263. package/docs/errors/invalid-text-range.md +1 -1
  264. package/docs/errors/job-finished.md +1 -1
  265. package/docs/errors/limit-exceeded.md +1 -1
  266. package/docs/errors/link-without-contents.md +1 -1
  267. package/docs/errors/link-without-structure.md +1 -1
  268. package/docs/errors/markdown-html-ignored.md +33 -0
  269. package/docs/errors/markdown-image-missing.md +34 -0
  270. package/docs/errors/missing-alt-text.md +1 -1
  271. package/docs/errors/missing-glyph.md +1 -1
  272. package/docs/errors/missing-header.md +1 -1
  273. package/docs/errors/missing-lang.md +1 -1
  274. package/docs/errors/missing-root.md +1 -1
  275. package/docs/errors/missing-title.md +1 -1
  276. package/docs/errors/model-failed.md +33 -0
  277. package/docs/errors/model-needs-host.md +40 -0
  278. package/docs/errors/model-result-unexpected.md +36 -0
  279. package/docs/errors/name-tree-node-skipped.md +1 -1
  280. package/docs/errors/nested-transaction.md +1 -1
  281. package/docs/errors/no-pending-save.md +1 -1
  282. package/docs/errors/no-transaction.md +1 -1
  283. package/docs/errors/not-initialized.md +1 -1
  284. package/docs/errors/not-pdf.md +1 -1
  285. package/docs/errors/not-rebased.md +1 -1
  286. package/docs/errors/object-number-out-of-range.md +1 -1
  287. package/docs/errors/objstm-index-mismatch.md +1 -1
  288. package/docs/errors/out-of-memory.md +1 -1
  289. package/docs/errors/outline-cycle.md +1 -1
  290. package/docs/errors/outline-item-skipped.md +1 -1
  291. package/docs/errors/output-unwritable.md +1 -1
  292. package/docs/errors/overwrite-refused.md +1 -1
  293. package/docs/errors/page-count-mismatch.md +1 -1
  294. package/docs/errors/page-index-out-of-range.md +1 -1
  295. package/docs/errors/page-tree-node-skipped.md +1 -1
  296. package/docs/errors/password-required.md +1 -1
  297. package/docs/errors/path-outside-roots.md +1 -1
  298. package/docs/errors/perms-mismatch.md +1 -1
  299. package/docs/errors/profile-version-conflict.md +1 -1
  300. package/docs/errors/redaction-not-applied.md +32 -0
  301. package/docs/errors/reference-cycle.md +1 -1
  302. package/docs/errors/render-backend-unavailable.md +1 -1
  303. package/docs/errors/render-failed.md +1 -1
  304. package/docs/errors/render-too-large.md +1 -1
  305. package/docs/errors/repaired-base.md +1 -1
  306. package/docs/errors/requires-rewrite.md +1 -1
  307. package/docs/errors/revision-index-out-of-range.md +34 -0
  308. package/docs/errors/saved-not-rebased.md +1 -1
  309. package/docs/errors/sink-not-empty.md +1 -1
  310. package/docs/errors/sink.md +1 -1
  311. package/docs/errors/source-budget-exceeded.md +1 -1
  312. package/docs/errors/source-changed.md +1 -1
  313. package/docs/errors/source.md +1 -1
  314. package/docs/errors/stale-generation.md +1 -1
  315. package/docs/errors/stream-length-recovered.md +1 -1
  316. package/docs/errors/strict.md +1 -1
  317. package/docs/errors/subset-failed.md +1 -1
  318. package/docs/errors/syntax.md +1 -1
  319. package/docs/errors/tag-element-not-open.md +41 -0
  320. package/docs/errors/tag-element-unclosed.md +34 -0
  321. package/docs/errors/text-in-earlier-revision.md +36 -0
  322. package/docs/errors/text-module-not-loaded.md +1 -1
  323. package/docs/errors/text-overflow.md +1 -1
  324. package/docs/errors/tounicode-conflict.md +1 -1
  325. package/docs/errors/type.md +1 -1
  326. package/docs/errors/unexpected-eof.md +1 -1
  327. package/docs/errors/unknown-axis.md +1 -1
  328. package/docs/errors/unknown-code.md +1 -1
  329. package/docs/errors/unknown-destination.md +1 -1
  330. package/docs/errors/unknown-font.md +1 -1
  331. package/docs/errors/unknown-option.md +1 -1
  332. package/docs/errors/unknown-page.md +1 -1
  333. package/docs/errors/unmapped-glyph.md +1 -1
  334. package/docs/errors/unnumbered-heading-misused.md +1 -1
  335. package/docs/errors/unset-object.md +1 -1
  336. package/docs/errors/unsupported-filter.md +1 -1
  337. package/docs/errors/unsupported-image.md +1 -1
  338. package/docs/errors/unsupported-layout-option.md +1 -1
  339. package/docs/errors/unsupported-security-handler.md +1 -1
  340. package/docs/errors/unsupported-version.md +1 -1
  341. package/docs/errors/untagged-content.md +1 -1
  342. package/docs/errors/warnings-truncated.md +1 -1
  343. package/docs/errors/wasm-trap.md +1 -1
  344. package/docs/errors/whole-font-rebuilt.md +1 -1
  345. package/docs/errors/worker-crashed.md +1 -1
  346. package/docs/errors/worker-failed.md +1 -1
  347. package/docs/errors/worker-version-mismatch.md +1 -1
  348. package/docs/errors/would-invalidate-signatures.md +1 -1
  349. package/docs/errors/wrong-document.md +1 -1
  350. package/docs/errors/wrong-password.md +1 -1
  351. package/docs/errors/xobject-cycle.md +1 -1
  352. package/docs/errors/xref-repaired.md +1 -1
  353. package/docs/errors/xrefstm-ignored.md +1 -1
  354. package/docs/llms.txt +7 -1
  355. package/docs/pages/concepts/dependencies.md +2 -2
  356. package/docs/pages/concepts/drawing-pages.md +76 -0
  357. package/docs/pages/concepts/errors-and-warnings.md +6 -2
  358. package/docs/pages/concepts/how-lamina-thinks.md +13 -1
  359. package/docs/pages/concepts/pages-and-coordinates.md +9 -1
  360. package/docs/pages/guides/accessible-pdfs.md +3 -1
  361. package/docs/pages/guides/archival-pdfs.md +5 -1
  362. package/docs/pages/guides/edit-a-pdf.md +8 -2
  363. package/docs/pages/guides/flowing-pages.md +379 -0
  364. package/docs/pages/guides/fonts-and-text.md +3 -1
  365. package/docs/pages/guides/images-and-vectors.md +3 -1
  366. package/docs/pages/guides/keep-the-page-responsive.md +8 -4
  367. package/docs/pages/guides/links-outlines-and-metadata.md +3 -1
  368. package/docs/pages/guides/open-large-files.md +69 -0
  369. package/docs/pages/guides/password-protected-files.md +1 -1
  370. package/docs/pages/guides/save-a-pdf.md +12 -2
  371. package/docs/pages/guides/test-generated-pdfs.md +324 -0
  372. package/docs/pages/guides/untrusted-files.md +3 -1
  373. package/docs/pages/index.md +2 -2
  374. package/docs/pages/install.md +9 -5
  375. package/docs/pages/quick-start/browser.md +5 -1
  376. package/docs/pages/quick-start/bun.md +5 -1
  377. package/docs/pages/quick-start/deno.md +5 -1
  378. package/docs/pages/quick-start/node.md +5 -1
  379. package/docs/pages/quick-start/rust.md +6 -2
  380. package/docs/pages/quick-start/workers.md +2 -2
  381. package/docs/pages/recipes/cloudflare-worker.md +1 -1
  382. package/docs/pages/recipes/element-flow.md +248 -0
  383. package/docs/pages/recipes/extract-markdown.md +44 -6
  384. package/docs/pages/recipes/generate-then-edit.md +13 -6
  385. package/docs/pages/recipes/hello.md +13 -6
  386. package/docs/pages/recipes/images-and-vectors.md +13 -6
  387. package/docs/pages/recipes/invoice.md +13 -6
  388. package/docs/pages/recipes/links-and-outlines.md +13 -6
  389. package/docs/pages/recipes/multilingual.md +13 -6
  390. package/docs/pages/recipes/open-untrusted.md +1 -1
  391. package/docs/pages/recipes/pdf-a-archive.md +13 -6
  392. package/docs/pages/recipes/pdf-a3-invoice-with-xml.md +13 -6
  393. package/docs/pages/recipes/pdf-ua-report.md +13 -6
  394. package/docs/pages/recipes/report.md +2 -2
  395. package/docs/pages/recipes/web-worker-client.md +1 -1
  396. package/docs/pages/recipes.md +3 -2
  397. package/docs/pages/reference/benchmarks.md +93 -0
  398. package/docs/pages/reference/changelog.md +49 -1
  399. package/docs/pages/reference/codes.md +1 -1
  400. package/docs/pages/reference/glossary.md +1 -1
  401. package/docs/pages/reference/rust.md +11 -11
  402. package/docs/pages/reference/stability.md +3 -3
  403. package/docs/pages/start/coding-agents.md +1 -1
  404. package/docs/pages/start/first-pdf.md +6 -2
  405. package/docs/pages/troubleshooting.md +1 -1
  406. package/docs/schemas/error.schema.json +18 -1
  407. package/package.json +4 -2
  408. package/schemas/error.schema.json +18 -1
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "How Lamina PDF thinks about a PDF"
3
3
  description: "Bytes in and out, the same bytes every time, edits kept apart from the opened file, two ways to save, memory you free, three JavaScript modules."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/concepts/how-lamina-thinks/"
6
6
  ---
7
7
 
@@ -23,6 +23,10 @@ Because Lamina PDF reads no clock, you pass in dates and file IDs yourself. It t
23
23
 
24
24
  When you open a document, Lamina PDF keeps its bytes exactly as they were. Every edit goes into an [overlay](../../pages/reference/glossary.md#overlay) on top of them. Reading the document reads the overlay first, so you see your edits; undo and redo move through them.
25
25
 
26
+ *Edits go into an overlay; saving appends them or writes a new file.*
27
+
28
+ Opening a PDF gives you a document that holds the file's original bytes, which never change, and an overlay for your edits. `doc.transact`, `undo` and `redo` write only to the overlay, and reading the document reads the overlay first. A save then either appends the edits after the original bytes (`incremental`) or writes every byte of a new file (`fullRewrite`). An incremental save leaves the original bytes untouched, so signatures over them stay valid.
29
+
26
30
  ## Two ways to save
27
31
 
28
32
  A save writes the overlay out in one of two ways:
@@ -34,6 +38,8 @@ The default, `auto`, saves incrementally when it can. A signed file saves only i
34
38
 
35
39
  This example builds a document, opens its bytes, changes the title and saves incrementally. The saved file starts with the original bytes:
36
40
 
41
+ **TypeScript**
42
+
37
43
  `packages/sdk/examples/generate-then-edit.ts`:
38
44
 
39
45
  ```ts
@@ -71,6 +77,8 @@ function edit(bytes: Uint8Array, editedAt?: string): Uint8Array<ArrayBuffer> {
71
77
  }
72
78
  ```
73
79
 
80
+ **Result:** [generate_then_edit.pdf](https://docs.laminapdf.com/examples/pdf/generate_then_edit.pdf)
81
+
74
82
  ## You free what you create
75
83
 
76
84
  In JavaScript, documents and builders live in WebAssembly memory. The garbage collector frees them eventually, but late, so free each one when you're done: `using doc = …` frees it at the end of the block, and `doc.free()` frees it at once. A short script can skip this, but a long-running server shouldn't.
@@ -84,3 +92,7 @@ In JavaScript, Lamina PDF is WebAssembly, split into three modules so a page dow
84
92
  - **Text:** everything in base, plus fonts, shaped text and extraction.
85
93
 
86
94
  `init()` loads the base module, and `init({ text: true })` or `init({ extract: true })` loads one of the others. Call `loadText()` or `loadExtract()` to add a module later, when a page first needs it. Rust has one crate, with features instead.
95
+
96
+ *The three modules come in @laminapdf/wasm, under @laminapdf/sdk.*
97
+
98
+ `@laminapdf/sdk` depends on `@laminapdf/wasm`, whose WebAssembly the SDK loads as three modules: base, extract and text. `@laminapdf/viewer` depends on `@laminapdf/sdk`, on `@laminapdf/render` and on both renderers that implement it, `@laminapdf/render-pdfium` and `@laminapdf/render-hayro`. In Rust, the API is one crate, `laminapdf`, with features instead of modules.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Pages and coordinates"
3
3
  description: "Points from the top-left corner of the page as it's displayed, y going down, and pages numbered from 0."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/concepts/pages-and-coordinates/"
6
6
  ---
7
7
 
@@ -23,6 +23,8 @@ For text, the second number is the baseline, the line the letters sit on. Letter
23
23
 
24
24
  This app places "Hello, world!" one inch from the top-left corner, with its baseline one text size lower:
25
25
 
26
+ **TypeScript**
27
+
26
28
  `packages/sdk/examples/quick-start/node.ts`:
27
29
 
28
30
  ```ts
@@ -58,6 +60,8 @@ export async function hello(
58
60
  if (import.meta.main) await hello();
59
61
  ```
60
62
 
63
+ **Result:** [hello.pdf](https://docs.laminapdf.com/examples/pdf/hello.pdf)
64
+
61
65
  ## Pages count from 0
62
66
 
63
67
  The first page is page 0 everywhere: `doc.page(0)`, a link's target, a page label's start.
@@ -65,3 +69,7 @@ The first page is page 0 everywhere: `doc.page(0)`, a link's target, a page labe
65
69
  ## PDF's own coordinates
66
70
 
67
71
  Inside the file, PDF measures from the bottom-left corner with y going up, before rotation. That's [user space](../../pages/reference/glossary.md#user-space). Lamina PDF converts for you, so you only meet it in Rust, in names that contain `UserSpace` or `user_space`.
72
+
73
+ *Page space starts at the top-left corner you see; PDF's user space starts at the bottom-left, before rotation.*
74
+
75
+ On an upright US Letter page, 612 by 792 points, page space starts at the top-left corner with y going down, and PDF's user space starts at the bottom-left corner with y going up. The point `[72, 96]` is one inch from the left edge and 96 points from the top, on the baseline of a heading whose box runs from `[72, 72]` to `[272, 100]`. With `/Rotate 90` a viewer shows the same page on its side, 792 by 612 points. Page space still starts at the top-left corner you see, so `[72, 96]` lands in the same place on the screen. User space starts at that corner too, with its x axis going down and its y axis going right. The heading in the file now runs down the right edge, from `[692, 72]` to `[720, 272]` in page space.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Accessible PDFs"
3
3
  description: "Build PDF/UA-1 documents that screen readers can follow, with every machine-checkable rule checked when the builder finishes."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/guides/accessible-pdfs/"
6
6
  ---
7
7
 
@@ -245,6 +245,8 @@ fn chart(s: &mut Surface) -> Result<(), BuildError> {
245
245
  }
246
246
  ```
247
247
 
248
+ **Result:** [pdf_ua_report.pdf](https://docs.laminapdf.com/examples/pdf/pdf_ua_report.pdf)
249
+
248
250
  [See the output](../../pages/recipes/pdf-ua-report.md).
249
251
 
250
252
  ## What a program can't check
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Archival PDFs"
3
3
  description: "Build PDF/A-2b, PDF/A-2u and PDF/A-3b documents that look the same in years to come, and attach files such as an e-invoice's XML."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/guides/archival-pdfs/"
6
6
  ---
7
7
 
@@ -216,6 +216,8 @@ fn draw(s: &mut Surface, font: &FontHandle, photo: &Image) -> Result<(), BuildEr
216
216
  }
217
217
  ```
218
218
 
219
+ **Result:** [pdf_a_archive-2b.pdf](https://docs.laminapdf.com/examples/pdf/pdf_a_archive-2b.pdf), [pdf_a_archive-2u.pdf](https://docs.laminapdf.com/examples/pdf/pdf_a_archive-2u.pdf)
220
+
219
221
  [See the output](../../pages/recipes/pdf-a-archive.md).
220
222
 
221
223
  ## Attaching files: a hybrid invoice
@@ -418,6 +420,8 @@ fn draw(s: &mut Surface, font: &FontHandle) -> Result<(), BuildError> {
418
420
  }
419
421
  ```
420
422
 
423
+ **Result:** [pdf_a3_invoice_with_xml.pdf](https://docs.laminapdf.com/examples/pdf/pdf_a3_invoice_with_xml.pdf)
424
+
421
425
  [See the output](../../pages/recipes/pdf-a3-invoice-with-xml.md).
422
426
 
423
427
  ## How Lamina PDF checks its own output
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Edit a PDF"
3
3
  description: "Change an existing PDF in transactions you can undo, and replay edits stored as JSON."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/guides/edit-a-pdf/"
6
6
  ---
7
7
 
@@ -118,6 +118,8 @@ fn edit(bytes: Vec<u8>, edited_at: Option<PdfDate>) -> Result<Vec<u8>, Box<dyn E
118
118
  }
119
119
  ````
120
120
 
121
+ **Result:** [generate_then_edit.pdf](https://docs.laminapdf.com/examples/pdf/generate_then_edit.pdf)
122
+
121
123
  [See the output](../../pages/recipes/generate-then-edit.md).
122
124
 
123
125
  Your edits live beside the bytes you opened and never change them. Nothing reaches a file until you save. [How Lamina PDF thinks about a PDF](../../pages/concepts/how-lamina-thinks.md) explains why it works this way.
@@ -128,7 +130,11 @@ Your edits live beside the bytes you opened and never change them. Nothing reach
128
130
 
129
131
  ## Undo and redo
130
132
 
131
- `doc.undo()` reverts the last transaction as a whole, and `doc.redo()` applies it again. A transaction is the unit of undo, so group the edits a user would expect to undo together.
133
+ `doc.undo()` reverts the last transaction as a whole, and `doc.redo()` applies it again. A transaction is the unit of undo, so group the edits a user would expect to undo together. A new transaction clears what redo could apply.
134
+
135
+ *One undo reverts a whole transaction; redo applies it again.*
136
+
137
+ Three transactions run in order: the first runs `setInfo` and `rotatePage`, the second `rotatePage`, and the third both. `doc.undo()` reverts the last transaction as a whole, and `doc.redo()` applies it again. A new transaction clears what redo could apply.
132
138
 
133
139
  ## Replay edits stored as JSON
134
140
 
@@ -0,0 +1,379 @@
1
+ ---
2
+ title: "Pages that flow"
3
+ description: "Pour headings, paragraphs, tables and images, or a Markdown file, into as many pages as they need, with footnotes, a contents page and \"Page X of Y\" footers."
4
+ version: "0.2.0-alpha.4"
5
+ url: "https://docs.laminapdf.com/guides/flowing-pages/"
6
+ ---
7
+
8
+ # Pages that flow
9
+
10
+ Pour headings, paragraphs, tables and images, or a Markdown file, into as many pages as they need, with footnotes, a contents page and "Page X of Y" footers.
11
+
12
+ This guide lays out a document whose length you don't know in advance, such as a report or a contract. You give Lamina PDF blocks and a page template, and it makes as many pages as they need, with a header and a footer on each.
13
+
14
+ It keeps headings with their text and never leaves a single line of a paragraph alone at a page's foot or head, and it tags the result so screen readers can follow it. Flowing layout is in the Rust crate today; the TypeScript package will follow.
15
+
16
+ ## The example
17
+
18
+ The example builds a 13-page report: a cover with a contents page, then five sections of headings, paragraphs, a footnote, a bulleted list and a photo. One paragraph is in Arabic and one in Hindi, and each is split by a page break. Every page after the cover names its section in the header and says "Page 3 of 13" in the footer, and every section has a bookmark. The report is built under PDF/UA-1, the accessibility standard, so the build fails if the output breaks one of its rules.
19
+
20
+ `crates/laminapdf/examples/flowing_report.rs`:
21
+
22
+ ```rust
23
+ //! A report that flows across pages: a cover with a contents page, then sections whose
24
+ //! paragraphs break between pages, footnotes at the foot of their page, the section's title in
25
+ //! a running header and "Page X of Y" in the footer.
26
+ //!
27
+ //! Lines split with at least two at the foot and the head of a page, headings stay with what
28
+ //! follows them, and the photo moves whole. The output is tagged, has bookmarks, and is checked
29
+ //! against PDF/UA-1. Run it with `cargo run -p laminapdf --example flowing_report -- regular.ttf
30
+ //! bold.ttc arabic.ttf devanagari.ttf photo.jpg`.
31
+
32
+ use std::error::Error;
33
+
34
+ use laminapdf::flow::{
35
+ Contents, Figure, Flow, FlowBlock, List, ListItem, ListKind, PageRegion, PageTemplate, Paragraph, ParagraphStyle,
36
+ TextStyle,
37
+ };
38
+ use laminapdf::{BuildOptions, Builder, Font, Image, Metadata, PageSize, Profile, SaveOptions};
39
+
40
+ const TITLE: &str = "Reading files offline: a year of the reader";
41
+ pub(crate) const ARABIC: &str =
42
+ "يعمل القارئ دون اتصال بالشبكة، فتُفتح الملفات وتُقرأ وتُبحث على الجهاز نفسه، ولا يغادر أي منها الحاسوب.";
43
+ pub(crate) const HINDI: &str = "नमस्ते दुनिया। हिन्दी पाठ का परीक्षण।";
44
+ const TOPICS: [&str; 5] = ["Opening files", "Search", "Printing", "Signing", "What comes next"];
45
+
46
+ fn main() -> Result<(), Box<dyn Error>> {
47
+ let paths: Vec<String> = std::env::args().skip(1).collect();
48
+ let [regular, bold, arabic, hindi, photo] = paths.as_slice() else {
49
+ return Err("arguments: <regular.ttf> <bold.ttc> <arabic.ttf> <devanagari.ttf> <photo.jpg>".into());
50
+ };
51
+ let read = |path: &String| std::fs::read(path);
52
+ let fonts = [read(regular)?, read(bold)?, read(arabic)?, read(hindi)?];
53
+ let pdf = build(BuildOptions::new(), fonts, std::fs::read(photo)?)?;
54
+ std::fs::create_dir_all("target/examples-out")?;
55
+ Ok(std::fs::write("target/examples-out/flowing_report.pdf", pdf)?)
56
+ }
57
+
58
+ /// The report's bytes. `options` can fix the file ID, the producer and the creation date.
59
+ pub(crate) fn build(options: BuildOptions, fonts: [Vec<u8>; 4], photo: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
60
+ let [regular, bold, arabic, hindi] = fonts;
61
+ let mut b = Builder::new(options.with_profile(Profile::PdfUa1));
62
+ b.metadata(Metadata::default().with_title(TITLE));
63
+ b.lang("en");
64
+ let regular = b.font(Font::from_bytes(regular)?)?;
65
+ let body = TextStyle::new(&regular, 11.0)
66
+ .with_fallback(&b.font(Font::from_bytes(arabic)?)?)
67
+ .with_fallback(&b.font(Font::from_bytes(hindi)?)?);
68
+ let heading = TextStyle::new(&b.font(Font::from_collection(bold, 1)?)?, 16.0);
69
+ let small = TextStyle::new(&regular, 9.0);
70
+ let header = PageRegion::new().with_end("{section}");
71
+ let footer = PageRegion::new().with_center("Page {page} of {pages}");
72
+ let pages = PageTemplate::new(PageSize::A5).with_furniture(Some(header), Some(footer), &small);
73
+ let (cover, team) = (heading.clone().with_size(24.0), "A report from the reader's team.");
74
+ let mut flow = Flow::new(pages.with_first_page(PageTemplate::new(PageSize::A5)))
75
+ .with_title(TITLE)
76
+ .block(FlowBlock::Paragraph(Paragraph::plain(TITLE, &cover)))
77
+ .block(FlowBlock::Paragraph(Paragraph::plain(team, &body)))
78
+ .block(FlowBlock::Contents(Contents::new(&body)))
79
+ .block(FlowBlock::PageBreak);
80
+ let photo = Image::jpeg(photo)?;
81
+ for (i, topic) in TOPICS.iter().enumerate() {
82
+ flow = section(flow, i, topic, &body, &heading, &photo);
83
+ }
84
+ b.flow(&flow)?;
85
+ Ok(b.finish()?.save(&SaveOptions::new())?.bytes)
86
+ }
87
+
88
+ /// A section: its heading, paragraphs (one in Arabic, one in Hindi), a list and a photo.
89
+ fn section(flow: Flow, i: usize, topic: &str, body: &TextStyle, heading: &TextStyle, photo: &Image) -> Flow {
90
+ let sentence = format!(
91
+ "The {} work of the year changed how readers use the app every day. ",
92
+ topic.to_lowercase()
93
+ );
94
+ let mut flow = flow.block(FlowBlock::Heading(1, Paragraph::plain(topic, heading)));
95
+ let note = Paragraph::plain(
96
+ format!("Measured on our test files for {topic}."),
97
+ &body.clone().with_size(9.0),
98
+ );
99
+ for n in 0..4 {
100
+ let p = Paragraph::new(ParagraphStyle::new().with_space(0.0, 8.0)).text(sentence.repeat(n + 4), body);
101
+ let p = if n == 3 { p.note(note.clone()) } else { p };
102
+ flow = flow.block(FlowBlock::Paragraph(p));
103
+ }
104
+ let foreign = match i {
105
+ 1 => Some((format!("{ARABIC} ").repeat(8).trim_end().to_owned(), "ar")),
106
+ 3 => Some((format!("{HINDI} ").repeat(12).trim_end().to_owned(), "hi")),
107
+ _ => None,
108
+ };
109
+ if let Some((text, lang)) = foreign {
110
+ let style = body.clone().with_language(lang);
111
+ flow = flow.block(FlowBlock::Paragraph(Paragraph::plain(text, &style)));
112
+ }
113
+ let list = ["faster", "smaller", "easier to share"]
114
+ .iter()
115
+ .fold(List::new(ListKind::Bullet, body), |list, word| {
116
+ list.item(ListItem::new(Paragraph::plain(format!("Files are {word}."), body)))
117
+ });
118
+ let alt = format!("A photo of the team working on {}", topic.to_lowercase());
119
+ flow.block(FlowBlock::List(list))
120
+ .block(FlowBlock::Figure(Figure::new(photo.clone(), alt).with_width(200.0)))
121
+ }
122
+ ```
123
+
124
+ The result is thirteen A5 pages: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/flowing_report.pdf).
125
+
126
+ ## Blocks, styles and the template
127
+
128
+ A `Flow` is a list of blocks and the `PageTemplate` they flow through.
129
+
130
+ - **Paragraphs** are made of pieces of text, each in its own `TextStyle`: its fonts, size, colour, language and a baseline rise for superscripts. A bold word is a piece in the bold font. A link is a piece with a target, and a line break or a footnote is a piece of its own.
131
+ - **Tables** have header rows, body rows and footer rows; a later section covers them.
132
+ - A **contents** block lists the headings with their pages, and footnotes go in paragraphs; both are covered below.
133
+ - **Headings** run from level 1 to 6. **Lists** are bulleted or numbered, and an item can hold a nested list. A **figure** is an image with its alternate text, as wide as the page's text column unless you give it a width.
134
+ - **Spacers, page breaks and keep groups** shape the layout: a spacer adds space, a page break starts a new page, and a `Keep` group keeps its blocks on one page.
135
+
136
+ The template gives the page size, the margins, a header and a footer, and optionally a different first page, such as a cover without a header. A header or footer has three slots, at the start, centre and end of the line.
137
+
138
+ ## Where pages break
139
+
140
+ The flow lays out every page before it draws any, and it follows these rules:
141
+
142
+ - **Paragraphs split only between lines,** leaving at least two lines at the foot of a page and two at the head of the next. When a page has room for only one line of a paragraph, the paragraph moves to the next page. Change the counts with `with_widows_and_orphans`.
143
+ - **A heading stays with the block after it,** so a section never starts on the last line of a page. Any paragraph can do the same with `with_keep_with_next`.
144
+ - **Figures and keep groups move whole** to the next page when they don't fit.
145
+ - **A paragraph continued on a page of another width** is broken again at that width.
146
+
147
+ When blocks you asked to keep together are taller than a page, the flow splits them anyway and adds a `flow-keep-broken` warning to the report `Builder::flow` returns. A figure or a single line taller than a page fails with `flow-content-too-large`, since no page could hold it.
148
+
149
+ ## Tables across pages
150
+
151
+ A table can run over as many pages as its rows need, with its header on each page and every row whole. The second example builds a three-page invoice of 60 rows with a total at the end:
152
+
153
+ `crates/laminapdf/examples/flowing_invoice.rs`:
154
+
155
+ ```rust
156
+ //! An invoice whose table runs over three pages: its header row repeats at the top of each
157
+ //! page, no row is split between pages, and the total stays on the page of the last row.
158
+ //!
159
+ //! The columns fit their widest cell, and numbers line up on the right. The output is tagged
160
+ //! (one `Table` element across the pages) and checked against PDF/UA-1. Run it with
161
+ //! `cargo run -p laminapdf --example flowing_invoice -- regular.ttf bold.ttc`.
162
+
163
+ use std::error::Error;
164
+
165
+ use laminapdf::flow::{
166
+ ColumnWidth, Flow, FlowBlock, Margins, PageRegion, PageTemplate, Paragraph, Table, TableCell, TableRow, TableRule,
167
+ TextStyle,
168
+ };
169
+ use laminapdf::{Alignment, BuildOptions, Builder, Color, Font, Metadata, PageSize, Profile, SaveOptions};
170
+
171
+ const TITLE: &str = "Invoice 2026-114";
172
+ const PRODUCTS: [(&str, usize); 6] = [
173
+ ("Paper, A4, 80 g, box of 2,500 sheets", 2_450),
174
+ ("Toner cartridge, black", 6_900),
175
+ ("Desk lamp with a weighted base and an adjustable arm", 4_275),
176
+ ("Stapler", 1_290),
177
+ ("Archive boxes, set of ten, with lids", 3_150),
178
+ ("Ballpoint pens, box of fifty", 1_875),
179
+ ];
180
+
181
+ fn main() -> Result<(), Box<dyn Error>> {
182
+ let paths: Vec<String> = std::env::args().skip(1).collect();
183
+ let [regular, bold] = paths.as_slice() else {
184
+ return Err("arguments: <regular.ttf> <bold.ttc>".into());
185
+ };
186
+ let pdf = build(BuildOptions::new(), std::fs::read(regular)?, std::fs::read(bold)?)?;
187
+ std::fs::create_dir_all("target/examples-out")?;
188
+ Ok(std::fs::write("target/examples-out/flowing_invoice.pdf", pdf)?)
189
+ }
190
+
191
+ /// The invoice's bytes. `options` can fix the file ID, the producer and the creation date.
192
+ pub(crate) fn build(options: BuildOptions, regular: Vec<u8>, bold: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
193
+ let mut b = Builder::new(options.with_profile(Profile::PdfUa1));
194
+ b.metadata(Metadata::default().with_title(TITLE));
195
+ b.lang("en");
196
+ let body = TextStyle::new(&b.font(Font::from_bytes(regular)?)?, 10.0);
197
+ let strong = TextStyle::new(&b.font(Font::from_collection(bold, 1)?)?, 10.0);
198
+ let footer = PageRegion::new().with_center("{title}, page {page} of {pages}");
199
+ let pages = PageTemplate::new(PageSize::A4)
200
+ .with_margins(Margins::all(56.0))
201
+ .with_furniture(None, Some(footer), &body.clone().with_size(8.0));
202
+ let flow = Flow::new(pages)
203
+ .with_title(TITLE)
204
+ .block(FlowBlock::Heading(
205
+ 1,
206
+ Paragraph::plain(TITLE, &strong.clone().with_size(18.0)),
207
+ ))
208
+ .block(FlowBlock::Paragraph(Paragraph::plain(
209
+ "Office supplies delivered in October, to be paid within 30 days.",
210
+ &body,
211
+ )))
212
+ .block(FlowBlock::Table(table(&body, &strong)));
213
+ b.flow(&flow)?;
214
+ Ok(b.finish()?.save(&SaveOptions::new())?.bytes)
215
+ }
216
+
217
+ /// Sixty lines of products, a header row and a footer row with the total.
218
+ fn table(body: &TextStyle, strong: &TextStyle) -> Table {
219
+ let number = |text: String, style: &TextStyle| TableCell::plain(text, style).with_align(Alignment::End);
220
+ let header = ["Item", "Description", "Qty", "Amount"]
221
+ .iter()
222
+ .enumerate()
223
+ .fold(TableRow::new(), |row, (i, text)| {
224
+ let cell = TableCell::plain(*text, strong);
225
+ row.cell(if i >= 2 { cell.with_align(Alignment::End) } else { cell })
226
+ });
227
+ let mut table = Table::new([ColumnWidth::Auto; 4])
228
+ .with_padding(6.0)
229
+ .with_frame(TableRule::new(0.75, Color::gray(0.4)))
230
+ .with_header_rule(TableRule::new(0.75, Color::gray(0.4)))
231
+ .with_row_rules(TableRule::new(0.25, Color::gray(0.7)))
232
+ .with_header_background(Color::gray(0.92))
233
+ .header(header);
234
+ let mut total = 0;
235
+ for (i, (name, cents)) in PRODUCTS.iter().cycle().take(60).enumerate() {
236
+ let qty = i % 5 + 1;
237
+ total += qty * cents;
238
+ table = table.row(
239
+ TableRow::new()
240
+ .cell(TableCell::plain(format!("{:03}", i + 1), body))
241
+ .cell(TableCell::plain(*name, body))
242
+ .cell(number(qty.to_string(), body))
243
+ .cell(number(money(qty * cents), body)),
244
+ );
245
+ }
246
+ let total_row = TableRow::new()
247
+ .cell(TableCell::plain("Total", strong).with_span(3))
248
+ .cell(number(money(total), strong));
249
+ table.footer(total_row)
250
+ }
251
+
252
+ fn money(cents: usize) -> String {
253
+ format!("{}.{:02}", cents / 100, cents % 100)
254
+ }
255
+ ```
256
+
257
+ The result is three A4 pages: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/flowing_invoice.pdf).
258
+
259
+ A table follows three rules when it crosses a page:
260
+
261
+ - **The header rows repeat** at the top of every page the table continues on.
262
+ - **A body row never splits.** A row that doesn't fit what is left of a page starts the next one, and a row taller than a page fails with `flow-content-too-large`.
263
+ - **The footer rows stay with the last body row,** so a total never starts a page alone. When the footer doesn't fit under the last row, that row moves to the next page with the footer.
264
+
265
+ Columns get their widths once, for the whole table, so they line up from page to page. A column is `Fixed` (a width in points), `Auto` (as wide as its widest cell) or a `Fraction` (a share of the width the others leave). When automatic columns are too wide together, the widest ones narrow first, so a column of numbers keeps its width while the descriptions wrap.
266
+
267
+ A cell holds one or more paragraphs. `with_align` lines its text up at the start, centre or end (numbers at the end), and `with_vertical_align` places it in the row. `with_span` stretches a cell over several columns, as the invoice's "Total" does. Rules come from `with_frame`, `with_header_rule` and `with_row_rules`, and `with_header_background` shades the header rows.
268
+
269
+ ## Footnotes, a contents page and bookmarks
270
+
271
+ A footnote is part of a paragraph: `Paragraph::note(body)` puts a raised number where you add it and the note's body at the foot of the same page. Notes are numbered 1, 2, 3 through the flow. A short rule separates them from the text above, and the page's footer stays below them. When a line's notes don't fit at the foot of its page, the line moves to the next page with them.
272
+
273
+ A contents block, `FlowBlock::Contents`, lists the flow's headings with the page each one starts on. Each entry is indented by its heading's level, with dots leading to its number, and the entry links to its heading. By default it lists levels 1 and 2; `Contents::with_levels` changes that. The report in the example puts its contents on the cover.
274
+
275
+ The flow can't know the page numbers before it has laid the pages out. So it lays everything out, writes the numbers it found into the contents page, and lays out again to check them. Each entry keeps room for a number of up to four digits, so the contents page keeps its length and the second layout confirms the numbers.
276
+
277
+ Every heading also gets a bookmark. PDF readers list the bookmarks in their sidebar, nested by heading level, and each one goes to its heading. Turn them off with `Flow::with_outline(false)`.
278
+
279
+ ## Write it in Markdown
280
+
281
+ You can also write the document in Markdown and let `Builder::markdown` turn it into blocks. Each Markdown construct becomes the block you would build by hand: a `#` heading becomes a heading, a list becomes a list, and a table becomes a table that runs across pages, its header row repeated on each. A link to `#a-heading` goes to the heading of that name, written the way GitHub writes it: lower case, with hyphens for spaces. Front matter at the top, plain `key: value` lines between two `---` lines, sets the document's title, language, author and subject.
282
+
283
+ The example below builds a short report from a Markdown file. It gives the fonts, the page template and a contents page in `MarkdownBuildOptions`, and it passes the one image the Markdown names. The builder reads no files and fetches nothing, so it leaves out an image you don't pass and adds a `markdown-image-missing` warning. It leaves out raw HTML too, with `markdown-html-ignored`. Both warnings give the position in the Markdown, never its text.
284
+
285
+ `crates/laminapdf/examples/markdown_to_pdf.rs`:
286
+
287
+ ```rust
288
+ //! A report written in Markdown, turned into tagged pages that flow: headings, paragraphs with
289
+ //! emphasis, code and links, a photo, lists and a task list, a block quote, a table that runs
290
+ //! across pages, a code block, footnotes, and paragraphs in Arabic and Hindi.
291
+ //!
292
+ //! The front matter gives the title and language, a contents page lists the headings, and the
293
+ //! output is checked against PDF/UA-1, so every font is embedded. Run it with `cargo run -p
294
+ //! laminapdf --example markdown_to_pdf -- report.md regular.ttf bold.ttc arabic.ttf
295
+ //! devanagari.ttf photo.jpg`.
296
+
297
+ use std::error::Error;
298
+
299
+ use laminapdf::flow::{PageRegion, PageTemplate, TextStyle};
300
+ use laminapdf::markdown::{MarkdownBuildOptions, MarkdownFonts};
301
+ use laminapdf::{BuildOptions, Builder, Font, Image, PageSize, Profile, SaveOptions};
302
+
303
+ fn main() -> Result<(), Box<dyn Error>> {
304
+ let paths: Vec<String> = std::env::args().skip(1).collect();
305
+ let [markdown, regular, bold, arabic, hindi, photo] = paths.as_slice() else {
306
+ return Err("arguments: <report.md> <regular.ttf> <bold.ttc> <arabic.ttf> <devanagari.ttf> <photo.jpg>".into());
307
+ };
308
+ let read = |path: &String| std::fs::read(path);
309
+ let fonts = [read(regular)?, read(bold)?, read(arabic)?, read(hindi)?];
310
+ let markdown = std::fs::read_to_string(markdown)?;
311
+ let pdf = build(BuildOptions::new(), &markdown, fonts, read(photo)?)?;
312
+ std::fs::create_dir_all("target/examples-out")?;
313
+ Ok(std::fs::write("target/examples-out/markdown_to_pdf.pdf", pdf)?)
314
+ }
315
+
316
+ /// The report's bytes. `options` can fix the file ID, the producer and the creation date.
317
+ pub(crate) fn build(
318
+ options: BuildOptions,
319
+ markdown: &str,
320
+ fonts: [Vec<u8>; 4],
321
+ photo: Vec<u8>,
322
+ ) -> Result<Vec<u8>, Box<dyn Error>> {
323
+ let [regular, bold, arabic, hindi] = fonts;
324
+ let mut b = Builder::new(options.with_profile(Profile::PdfUa1));
325
+ let regular = b.font(Font::from_bytes(regular)?)?;
326
+ let fonts = MarkdownFonts::new(&regular)
327
+ .with_bold(&b.font(Font::from_collection(bold, 1)?)?)
328
+ .with_fallback(&b.font(Font::from_bytes(arabic)?)?)
329
+ .with_fallback(&b.font(Font::from_bytes(hindi)?)?);
330
+ // A5 pages, so the table runs across two of them, with the page number in the footer.
331
+ let footer = PageRegion::new().with_center("Page {page} of {pages}");
332
+ let pages = PageTemplate::new(PageSize::A5).with_furniture(None, Some(footer), &TextStyle::new(&regular, 8.0));
333
+ let options = MarkdownBuildOptions::new()
334
+ .with_fonts(fonts)
335
+ .with_template(pages)
336
+ .with_contents(2)
337
+ .with_image("photo.jpg", Image::jpeg(photo)?);
338
+ let report = b.markdown(markdown, &options)?;
339
+ if let Some(warning) = report.warnings.first() {
340
+ return Err(format!("the Markdown has something the PDF leaves out: {}", warning.code()).into());
341
+ }
342
+ Ok(b.finish()?.save(&SaveOptions::new())?.bytes)
343
+ }
344
+ ```
345
+
346
+ The result is four A5 pages: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/markdown_to_pdf.pdf).
347
+
348
+ If you pass no fonts, the builder sets the Markdown in Helvetica and Courier, two fonts every PDF reader has. They set only Latin text, and since they aren't embedded, a PDF/UA-1 build refuses them with `font-not-embedded`. Pass your own fonts with `MarkdownFonts` for other scripts or an accessible document.
349
+
350
+ ## Fields in headers and footers
351
+
352
+ A slot's text can hold fields, which the flow fills in on each page:
353
+
354
+ - `{page}`: the page's number in the document, from 1.
355
+ - `{pages}`: the number of pages in the document once the flow's pages are added.
356
+ - `{section}`: the title of the last level-1 heading that started on or before the page. Choose another level with `with_section_level`.
357
+ - `{title}`: the title you gave the flow.
358
+
359
+ Write `{{` and `}}` for a literal brace. `{pages}` is right on every page because no page is drawn until all of them are laid out.
360
+
361
+ ## What the flow tags
362
+
363
+ The output is tagged unless you turn tagging off with `with_tagged(false)`. Paragraphs are `P` elements, headings `H1` to `H6`, lists `L` with their items, figures `Figure` with their alternate text, and links `Link`. A paragraph's language, taken from its first piece, is set on its element; a piece in another language is a `Span` with that language. A paragraph split across pages stays one element, so a screen reader reads it as one.
364
+
365
+ A table is one `Table` element however many pages it crosses, with `THead`, `TBody` and `TFoot` around its rows. Header cells are `TH` elements that head their columns, and a cell across several columns records how many it spans.
366
+
367
+ The header rows drawn again on later pages are marked as page furniture, like headers and footers, so a screen reader and Lamina PDF's extraction read the table's header once. The table's rules and background are marked as decoration too.
368
+
369
+ A footnote's number is a `Reference` and its body a `Note` with its own identifier, inside the paragraph that cites it. A contents page is a `TOC` element with a `TOCI` for each entry, each holding its links; its dots are marked as decoration.
370
+
371
+ Headers and footers are marked as page furniture rather than content: screen readers skip them, and Lamina PDF's own extraction leaves them out of the text.
372
+
373
+ ## What to change
374
+
375
+ - **A cover:** give the template a first page with `with_first_page`, and start the body with a page break.
376
+ - **Another typeface for a word:** add a piece of text in a `TextStyle` with that font. List fallback fonts with `with_fallback` for characters the first font lacks.
377
+ - **Page numbers only:** pass a footer with one centre slot, `"{page}"`, and no header.
378
+
379
+ Next: [Accessible PDFs](../../pages/guides/accessible-pdfs.md), for what PDF/UA-1 checks and how to meet it.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Text and fonts"
3
3
  description: "Draw text in any script with your own fonts or the standard 14, one line at a time or as a paragraph that wraps in a box."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/guides/fonts-and-text/"
6
6
  ---
7
7
 
@@ -189,6 +189,8 @@ pub(crate) fn build(options: BuildOptions, fonts: Vec<Vec<u8>>) -> Result<Vec<u8
189
189
  }
190
190
  ````
191
191
 
192
+ **Result:** [multilingual.pdf](https://docs.laminapdf.com/examples/pdf/multilingual.pdf)
193
+
192
194
  [See the output](../../pages/recipes/multilingual.md). Text extracted from it, with pdf.js, pypdf or pdftotext, equals the input strings, ligatures included.
193
195
 
194
196
  ## When a font can't draw the text
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Shapes, colour and images"
3
3
  description: "Draw lines, shapes and images on a page, in any colour, with transparency, clipping and transforms."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/guides/images-and-vectors/"
6
6
  ---
7
7
 
@@ -212,6 +212,8 @@ fn images(s: &mut Surface, photo: &Image, glow: &Image) {
212
212
  }
213
213
  ````
214
214
 
215
+ **Result:** [images_and_vectors.pdf](https://docs.laminapdf.com/examples/pdf/images_and_vectors.pdf)
216
+
215
217
  [See the output](../../pages/recipes/images-and-vectors.md).
216
218
 
217
219
  > **Details:** A PNG's alpha channel, and an RGBA image's, becomes a soft mask: a second image that holds the transparency.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Keep the page responsive"
3
3
  description: "Run Lamina PDF in a Web Worker with @laminapdf/sdk/worker, so a large file never freezes the page."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/guides/keep-the-page-responsive/"
6
6
  ---
7
7
 
@@ -58,11 +58,15 @@ The Web Worker leaves out a few members, each for a reason:
58
58
 
59
59
  ## Cancel a call
60
60
 
61
- Pass an `AbortSignal` as `signal` to any call. An aborted call rejects with `cancelled`, and `error.cause` holds the signal's reason. What happens to the work depends on where it was:
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
62
 
63
63
  - A call that hasn't started never runs.
64
- - `save` stops at its next step, and the document stays usable.
65
- - Any other call runs to the end in the Web Worker, because it is one synchronous call. `client.terminate()` stops it at once.
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.
66
70
 
67
71
  ## Move bytes instead of copying them
68
72
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Links, outlines and metadata"
3
3
  description: "Add links, named destinations, an outline, page labels, viewer preferences and metadata to a document."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/guides/links-outlines-and-metadata/"
6
6
  ---
7
7
 
@@ -215,6 +215,8 @@ fn navigation(b: &mut Builder) -> Result<(), Box<dyn Error>> {
215
215
  }
216
216
  ````
217
217
 
218
+ **Result:** [links_and_outlines.pdf](https://docs.laminapdf.com/examples/pdf/links_and_outlines.pdf)
219
+
218
220
  [See the output](../../pages/recipes/links-and-outlines.md).
219
221
 
220
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.