@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: "Rust"
3
3
  description: "Generate a PDF with the Lamina PDF crate."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/quick-start/rust/"
6
6
  ---
7
7
 
@@ -9,7 +9,9 @@ url: "https://docs.laminapdf.com/quick-start/rust/"
9
9
 
10
10
  Generate a PDF with the Lamina PDF crate.
11
11
 
12
- The `laminapdf` crate is the one to depend on: it wraps Lamina PDF's core crates in one API. Add it with `cargo add laminapdf@=0.2.0-alpha.3`, then build a document. [Install](../../pages/install.md#rust-laminapdf) explains the version. This is the `hello` example, which is tested on every change:
12
+ The `laminapdf` crate is the one to depend on: it wraps Lamina PDF's core crates in one API. Add it with `cargo add laminapdf@=0.2.0-alpha.4`, then build a document. [Install](../../pages/install.md#rust-laminapdf) explains the version. This is the `hello` example, which is tested on every change:
13
+
14
+ **Rust**
13
15
 
14
16
  `crates/laminapdf/examples/hello.rs`:
15
17
 
@@ -51,6 +53,8 @@ pub(crate) fn build(options: BuildOptions) -> Result<Vec<u8>, Box<dyn Error>> {
51
53
  }
52
54
  ````
53
55
 
56
+ **Result:** [hello.pdf](https://docs.laminapdf.com/examples/pdf/hello.pdf)
57
+
54
58
  To run it, save it as `src/main.rs` in a project that depends on `laminapdf`, then run `cargo run`. It writes `target/examples-out/hello.pdf`, or `hello.pdf` in the folder you pass: `cargo run -- out`.
55
59
 
56
60
  - **Same input, same bytes:** `build` takes `BuildOptions`: fix the file ID, the producer and the creation date in them, and the output never changes; see [how Lamina PDF thinks about a PDF](../../pages/concepts/how-lamina-thinks.md#same-input-same-bytes).
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Cloudflare Workers"
3
3
  description: "Generate PDFs per request in a Cloudflare Worker."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/quick-start/workers/"
6
6
  ---
7
7
 
@@ -131,7 +131,7 @@ declare module "*.otf" {
131
131
  }
132
132
  ```
133
133
 
134
- To run it, install `@laminapdf/sdk` and `wrangler` in the Cloudflare Worker's folder (`npm install @laminapdf/sdk@0.2.0-alpha.3 wrangler`), put a copy of [Noto Sans](https://fonts.google.com/noto/specimen/Noto+Sans) at `src/fonts/NotoSans-Regular.ttf`, and run `npx wrangler dev`.
134
+ To run it, install `@laminapdf/sdk` and `wrangler` in the Cloudflare Worker's folder (`npm install @laminapdf/sdk@0.2.0-alpha.4 wrangler`), put a copy of [Noto Sans](https://fonts.google.com/noto/specimen/Noto+Sans) at `src/fonts/NotoSans-Regular.ttf`, and run `npx wrangler dev`.
135
135
 
136
136
  - **Errors the caller caused** become a `400` with the error's JSON: a name the font can't draw (`missing-glyph`, `encoding-full`). Anything else, such as a broken font file, stays a `500`. See [Errors and warnings](../../pages/concepts/errors-and-warnings.md).
137
137
  - **Memory:** the letter frees its builder and document as soon as the bytes are saved, because one isolate serves many requests.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "A Cloudflare Worker"
3
3
  description: "A Cloudflare Worker that returns a one-page letter per request, in a standard 14 font or a bundled one, and a 400 for a name the font can't draw."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/recipes/cloudflare-worker/"
6
6
  ---
7
7
 
@@ -0,0 +1,248 @@
1
+ ---
2
+ title: "Element flow"
3
+ description: "A report whose title, paragraphs and photo each start below wherever the one before ended, with a body that continues on a new page."
4
+ version: "0.2.0-alpha.4"
5
+ url: "https://docs.laminapdf.com/recipes/element-flow/"
6
+ ---
7
+
8
+ # Element flow
9
+
10
+ A report whose title, paragraphs and photo each start below wherever the one before ended, with a body that continues on a new page.
11
+
12
+ You'll lay out a report where each part starts below wherever the part before it ended: a title, a summary, optional notes, a photo and a body that runs onto a second page. Use it when the length of your text changes from one document to the next.
13
+
14
+ The function takes a JPEG's bytes and the report's text, and returns the PDF's bytes. In TypeScript it draws text, so it needs `init({ text: true })`. Both languages write the same bytes:
15
+
16
+ **TypeScript**
17
+
18
+ `packages/sdk/examples/element-flow.ts`:
19
+
20
+ ```ts
21
+ // A report whose title, paragraphs and photo each start below wherever the one before ended. Each
22
+ // text box reports its `bottom`, and the next element starts a gap below it, so a summary that
23
+ // wraps moves everything after it and empty notes take no space. The body continues on new pages
24
+ // from the top margin. Needs `init({ text: true })`.
25
+ import { Builder, type BuilderOptions, Font, Image, PAGE_SIZES, type Surface, type TextLayout } from "@laminapdf/sdk";
26
+
27
+ const [PAGE_WIDTH, PAGE_HEIGHT] = PAGE_SIZES.A5;
28
+ const MARGIN = 48;
29
+ const WIDTH = PAGE_WIDTH - 2 * MARGIN;
30
+ const BOTTOM = PAGE_HEIGHT - MARGIN;
31
+ const TITLE = 18;
32
+ const BODY = 11;
33
+ const GAP = 10;
34
+ /** The photo's height, for a 3:2 photo across the text column. */
35
+ const PHOTO_HEIGHT = WIDTH / 1.5;
36
+
37
+ /** What the report says. Empty `notes` take no space; blank lines separate the body's paragraphs. */
38
+ export interface Report {
39
+ readonly title: string;
40
+ readonly summary: string;
41
+ readonly notes: string;
42
+ readonly body: string;
43
+ }
44
+
45
+ /**
46
+ * The report's bytes, with `photo` (a JPEG) below its notes.
47
+ *
48
+ * @param options - can fix the file ID, the producer and the creation date.
49
+ */
50
+ export function elementFlow(photo: Uint8Array, report: Report, options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
51
+ using b = new Builder(options);
52
+ const fonts = { bold: Font.standard("Helvetica-Bold"), regular: Font.standard("Helvetica") };
53
+ const jpeg = Image.jpeg(photo);
54
+ let page = b.page("A5");
55
+ let y = header(page.surface(), fonts, jpeg, report);
56
+ let [rest, newPage] = [report.body.trimEnd(), false];
57
+ for (;;) {
58
+ const layout = page.surface().textBox(rest, { font: fonts.regular, size: BODY, box: area(y) });
59
+ // A clipped box still drew its first lines: the rest goes on a new page, from its top.
60
+ const cut = layout.overflow;
61
+ if (cut === undefined) break;
62
+ if (cut === 0 && newPage) throw new Error("a line of the body is taller than the page");
63
+ [rest, y, newPage] = [rest.slice(cut).trimStart(), MARGIN, true];
64
+ page.finish();
65
+ page = b.page("A5");
66
+ }
67
+ page.finish();
68
+ using doc = b.finish();
69
+ return doc.save();
70
+ }
71
+
72
+ /**
73
+ * The title with a rule under it, the summary, the notes and the photo, each below the one before;
74
+ * returns where the body starts. Text that doesn't fit throws `text-overflow`.
75
+ */
76
+ function header(s: Surface, fonts: { bold: Font; regular: Font }, photo: Image, report: Report): number {
77
+ const fit = (text: string, font: Font, size: number, top: number) =>
78
+ below(s.textBox(text, { font, size, box: area(top), overflow: "error" }), top);
79
+ let y = fit(report.title, fonts.bold, TITLE, MARGIN);
80
+ // A decoration half a gap above the summary. It's stroked: a fill colour would colour the text too.
81
+ s.strokeColor([0.6])
82
+ .path((p) => p.rect([MARGIN, y - GAP / 2, WIDTH, 0]))
83
+ .stroke();
84
+ for (const text of [report.summary, report.notes]) y = fit(text, fonts.regular, BODY, y);
85
+ if (y + PHOTO_HEIGHT > BOTTOM) throw new Error("the photo doesn't fit below the notes");
86
+ s.image(photo, [MARGIN, y, WIDTH, PHOTO_HEIGHT]);
87
+ return y + PHOTO_HEIGHT + GAP;
88
+ }
89
+
90
+ /** Where the next element starts: a gap below `layout`'s last drawn line, or `y` if it drew none. */
91
+ function below(layout: TextLayout, y: number): number {
92
+ return layout.bottom === undefined ? y : layout.bottom + GAP;
93
+ }
94
+
95
+ /** The rest of the page's text column, from `y` down to the bottom margin. */
96
+ function area(y: number): [number, number, number, number] {
97
+ return [MARGIN, y, WIDTH, Math.max(BOTTOM - y, 0)];
98
+ }
99
+ ```
100
+
101
+ **Rust**
102
+
103
+ `crates/laminapdf/examples/element_flow.rs`:
104
+
105
+ ```rust
106
+ //! A report whose title, paragraphs and photo each start below wherever the one before ended.
107
+ //!
108
+ //! Each text box reports its `bottom`, and the next element starts a gap below it, so a summary
109
+ //! that wraps moves everything after it and empty notes take no space. The body continues on new
110
+ //! pages from the top margin. Run it with `cargo run -p laminapdf --example element_flow -- photo.jpg`.
111
+
112
+ use std::error::Error;
113
+
114
+ use laminapdf::{
115
+ BuildOptions, Builder, Color, FontHandle, Image, Overflow, PageSize, Pt, RectPt, SaveOptions,
116
+ Standard14::{Helvetica, HelveticaBold},
117
+ StrokeStyle, Surface, TextBoxOptions, TextLayout,
118
+ };
119
+
120
+ const PAGE: PageSize = PageSize::A5;
121
+ const MARGIN: f64 = 48.0;
122
+ const WIDTH: f64 = PAGE.width.get() - 2.0 * MARGIN;
123
+ const BOTTOM: f64 = PAGE.height.get() - MARGIN;
124
+ const TITLE: Pt = Pt::new(18.0);
125
+ const BODY: Pt = Pt::new(11.0);
126
+ const GAP: f64 = 10.0;
127
+ const PHOTO_HEIGHT: f64 = WIDTH / 1.5; // a 3:2 photo across the text column
128
+
129
+ /// What the report says. Empty `notes` take no space; blank lines separate the body's paragraphs.
130
+ pub(crate) struct Report<'a> {
131
+ pub(crate) title: &'a str,
132
+ pub(crate) summary: &'a str,
133
+ pub(crate) notes: &'a str,
134
+ pub(crate) body: &'a str,
135
+ }
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: element_flow <photo.jpg> [out-dir]")?)?;
140
+ let out = std::path::PathBuf::from(args.next().unwrap_or_else(|| "target/examples-out".into()));
141
+ let report = Report {
142
+ title: "Third-quarter report",
143
+ summary: "Revenue rose twelve per cent over the quarter, led by the new reader and its offline mode.",
144
+ notes: "Figures are unaudited.",
145
+ body: "Pages now appear as soon as they are read, and support requests about slow files fell by a third.",
146
+ };
147
+ let pdf = build(BuildOptions::new(), photo, &report)?;
148
+ std::fs::create_dir_all(&out)?;
149
+ Ok(std::fs::write(out.join("element_flow.pdf"), pdf)?)
150
+ }
151
+
152
+ /// The report's bytes, with `photo` (a JPEG) below its notes. `options` can fix the file ID, the
153
+ /// producer and the creation date.
154
+ pub(crate) fn build(options: BuildOptions, photo: Vec<u8>, report: &Report) -> Result<Vec<u8>, Box<dyn Error>> {
155
+ let mut b = Builder::new(options);
156
+ let fonts = [b.standard_font(HelveticaBold)?, b.standard_font(Helvetica)?];
157
+ let photo = Image::jpeg(photo)?;
158
+ let mut page = b.page(PAGE);
159
+ let mut y = header(page.surface(), &fonts, &photo, report)?;
160
+ let (mut rest, mut new_page) = (report.body.trim_end(), false);
161
+ loop {
162
+ let s = page.surface();
163
+ let layout = s.text_box(rest, &fonts[1], BODY, area(y), &TextBoxOptions::new())?;
164
+ // A clipped box still drew its first lines: the rest goes on a new page, from its top.
165
+ let Some(cut) = layout.overflow else { break };
166
+ if cut == 0 && new_page {
167
+ return Err("a line of the body is taller than the page".into());
168
+ }
169
+ (rest, y, new_page) = (rest[cut..].trim_start(), MARGIN, true);
170
+ page.finish()?;
171
+ page = b.page(PAGE);
172
+ }
173
+ page.finish()?;
174
+ Ok(b.finish()?.save(&SaveOptions::new())?.bytes)
175
+ }
176
+
177
+ /// The title with a rule under it, the summary, the notes and the photo, each below the one
178
+ /// before; returns where the body starts. Text that doesn't fit fails `text-overflow`.
179
+ fn header(s: &mut Surface, fonts: &[FontHandle; 2], photo: &Image, report: &Report) -> Result<f64, Box<dyn Error>> {
180
+ let [bold, regular] = fonts;
181
+ let must_fit = TextBoxOptions::new().with_overflow(Overflow::Error);
182
+ let mut y = below(&s.text_box(report.title, bold, TITLE, area(MARGIN), &must_fit)?, MARGIN);
183
+ // A decoration half a gap above the summary. It's stroked: a fill colour would colour the text too.
184
+ let (rule, gray) = (RectPt::from_points(MARGIN, y - GAP / 2.0, WIDTH, 0.0), Color::gray(0.6));
185
+ let line = StrokeStyle::default();
186
+ s.stroke_color(gray).path(|p| _ = p.rect(rule)).stroke(&line);
187
+ for text in [report.summary, report.notes] {
188
+ y = below(&s.text_box(text, regular, BODY, area(y), &must_fit)?, y);
189
+ }
190
+ if y + PHOTO_HEIGHT > BOTTOM {
191
+ return Err("the photo doesn't fit below the notes".into());
192
+ }
193
+ s.image(photo, RectPt::from_points(MARGIN, y, WIDTH, PHOTO_HEIGHT));
194
+ Ok(y + PHOTO_HEIGHT + GAP)
195
+ }
196
+
197
+ /// Where the next element starts: a gap below `layout`'s last drawn line, or `y` if it drew none.
198
+ fn below(layout: &TextLayout, y: f64) -> f64 {
199
+ layout.bottom().map_or(y, |bottom| bottom.get() + GAP)
200
+ }
201
+
202
+ /// The rest of the page's text column, from `y` down to the bottom margin.
203
+ fn area(y: f64) -> RectPt {
204
+ RectPt::from_points(MARGIN, y, WIDTH, (BOTTOM - y).max(0.0))
205
+ }
206
+ ```
207
+
208
+ **Result:** [element_flow.pdf](https://docs.laminapdf.com/examples/pdf/element_flow.pdf)
209
+
210
+ The result is two A5 pages. The body fills the first page below the photo and finishes at the top of the second. The Result tab shows it, with a link to the PDF.
211
+
212
+ ## How it works
213
+
214
+ Every text box tells you where its text ended, so you never type a position for anything after the first element.
215
+
216
+ - **`bottom` is where the next element can start.** `textBox` returns it with the lines it drew: the bottom of the last line that draws something. Rust has `TextLayout::bottom()`, and `TextRun::bottom()` for a single run of text.
217
+ - **Empty text has no `bottom`.** The notes here are optional. When they're empty, `bottom` is `undefined` (`None` in Rust), `below` keeps `y` where it was, and the photo moves up with no gap left behind.
218
+ - **A longer summary moves everything after it.** When the summary wraps onto one more line, the notes, the photo and the body each move down by one line height, and none of the code changes.
219
+ - **The photo is placed by your own rectangle.** An image reports nothing back, so the code adds the photo's height and the gap to `y` itself.
220
+ - **The rule under the title is a decoration.** It's drawn half a gap above the summary and leaves `y` alone. It's stroked rather than filled: a fill colour stays set and would colour the text drawn after it.
221
+
222
+ ## Text that doesn't fit
223
+
224
+ The report treats overflow two ways, and you choose which for each element.
225
+
226
+ The title, summary and notes must fit on the first page. They're drawn with `overflow: "error"` (`Overflow::Error` in Rust), so text that doesn't fit throws `text-overflow` instead of disappearing. The photo must fit too: when it would cross the bottom margin, the code stops with an error of its own.
227
+
228
+ The body may run on. Its box reaches the bottom margin and clips what doesn't fit. Then `overflow` holds the offset of the first character it didn't draw: a UTF-16 offset in TypeScript, a byte offset in Rust. The loop starts a new page and draws the rest from the top margin. Test `overflow` with `!== undefined`, because `0` means not even one line fitted. If no line fits even on a fresh page, a line is taller than the page, so the loop stops with an error instead of adding pages for ever.
229
+
230
+ ## What `bottom` measures
231
+
232
+ `bottom` is a typographic measurement in points: the descent of the last drawn line's font, below its baseline. A glyph that reaches lower, or a thick stroke around the text, can paint past it.
233
+
234
+ Like every position, `bottom` is in [page space](../../pages/reference/glossary.md#page-space), measured from the top of the page. It's measured before the surface's transform, so inside `transform` it stays in the coordinates you drew in.
235
+
236
+ In TypeScript you pass a box as a tuple, `[x, y, width, height]`. The lines a text box returns describe their `rect` as an object with `x`, `y`, `width` and `height`.
237
+
238
+ ## What stays your job
239
+
240
+ Page breaks stay your decision: the code here makes each one from `overflow`. The builder's surface doesn't paginate, float an image beside text, or lay out tables. In Rust, [Pages that flow](../../pages/guides/flowing-pages.md) paginates for you: headings kept with their text, running headers and "Page X of Y" footers.
241
+
242
+ ## What to change
243
+
244
+ - **Paragraphs a gap apart:** draw each paragraph in its own text box and start the next one at `bottom` plus the gap, as the summary and notes do.
245
+ - **A line height for the body:** pass `lineHeight` (`with_line_height` in Rust); `bottom` follows it.
246
+ - **Two columns:** keep a `y` for each column, and when the left column fills, carry on at the top margin of the right one.
247
+
248
+ Next: [the PDF/UA report](../../pages/recipes/pdf-ua-report.md), a report that screen readers can follow.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Extract text for an LLM"
3
3
  description: "Turn a PDF into Markdown in reading order, JSON with a box for every block, and chunks sized for retrieval, in TypeScript and Rust."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/recipes/extract-markdown/"
6
6
  ---
7
7
 
@@ -13,7 +13,9 @@ You'll turn a PDF into three outputs an LLM or a search index can use: Markdown
13
13
 
14
14
  In TypeScript the function returns the three outputs and needs the extract module, `init({ extract: true })`. In Rust, the program instead extracts once and writes the three files. Both give the same JSON:
15
15
 
16
- <TabItem label="TypeScript">`packages/sdk/examples/extract-markdown.ts`:
16
+ **TypeScript**
17
+
18
+ `packages/sdk/examples/extract-markdown.ts`:
17
19
 
18
20
  ```ts
19
21
  // Extract a PDF's text for an LLM: Markdown in reading order, JSON with a box for every block,
@@ -42,8 +44,11 @@ export function extractForAnLlm(bytes: Uint8Array): ForAnLlm {
42
44
  const json = doc.extract({ format: "json", pageMarkers: true, chunk: { maxTokens: 400 } });
43
45
  return { markdown, json, chunks: json.chunks ?? [] };
44
46
  }
45
- ```</TabItem>
46
- <TabItem label="Rust">`crates/laminapdf/examples/extract_markdown.rs`:
47
+ ```
48
+
49
+ **Rust**
50
+
51
+ `crates/laminapdf/examples/extract_markdown.rs`:
47
52
 
48
53
  ````rust
49
54
  //! Extract a PDF's text for an LLM: Markdown in reading order, JSON with a box for every
@@ -89,15 +94,45 @@ pub(crate) fn write_outputs(bytes: &[u8], out: &Path, stem: &str) -> Result<(),
89
94
  std::fs::write(out.join(format!("{stem}.json")), json)?;
90
95
  Ok(())
91
96
  }
92
- ````</TabItem>
97
+ ````
98
+
99
+ **Output**
100
+
101
+ `fixtures/extract/examples/invoice.md`:
102
+
103
+ ```md
104
+ <!-- page 1 -->
105
+
106
+ # Invoice 42
107
+
108
+ Acme Widgets Ltd · 1 Example Street · London
109
+
110
+ | Widgets, blue (W-100) | 3 | € 36.00 |
111
+ | --- | --- | --- |
112
+ | Gadget with fine-tuned dials | 1 | € 79.90 |
113
+ | Café-grade sprockets | 12 | € 15.00 |
114
+ | Delivery | 1 | € 4.50 |
115
+
116
+ Total € 135.40
117
+ ```
93
118
 
94
119
  Run the Rust program on any PDF: `cargo run -p laminapdf --example extract_markdown -- report.pdf out`.
95
120
 
96
121
  ## How it works
97
122
 
123
+ *Extraction joins a page's text runs into lines and blocks, orders them, then writes Markdown, JSON and chunks.*
124
+
125
+ Extraction starts from each page's text runs. It sets aside runs a reader can't see, and runs the file marks as artifacts, into `dropped` with their reason: `invisible`, `off-page`, `tiny`, `clipped`, `low-contrast`, `covered` or `artifact`. The other runs are joined into lines, and the lines into blocks: paragraphs, headings and tables. Once every page is read, it also sets aside headers, footers and page numbers that repeat across pages, as `running-header`, `running-footer` or `page-number`. Reading order then puts the blocks in sequence: from the file's structure tree when it is tagged and the tree covers the page's text, else by cutting the page into columns. The ordered blocks become the three outputs: Markdown, the `laminapdf.extract/1` JSON and chunks.
126
+
127
+ *On a two-column page, a title across both columns comes first, then the left column, then the right.*
128
+
129
+ A page has a title across both columns and three blocks in each column. Extraction numbers the title 1, the left column's blocks 2 to 4 from top to bottom, and the right column's blocks 5 to 7.
130
+
98
131
  - `extract` (`doc.extract` in TypeScript) reads every page's text, joins it into lines and paragraphs, and puts the paragraphs in reading order. A two-column page reads down the left column, then the right; a title across both columns comes first.
132
+ - Each block has a `confidence` from 0 to 1. When the page's columns could be read in more than one order, the page raises the `extract-column-order-uncertain` warning and the blocks in doubt get a confidence of 0.5. A pipeline can check those pages or send them to a layout model.
99
133
  - Tables drawn with lines become table blocks with their cells, and Markdown writes them as tables. A cell that spans columns or rows makes the table HTML, since Markdown tables can't span.
100
- - Text a reader can't see never reaches the outputs: invisible text (such as the hidden layer of a scanned page), text off the page and text too small to read. The JSON lists each such run under `dropped`, with the reason and how many characters it held.
134
+ - A table that goes on over a page break comes out as one table in the Markdown, the text and the chunks, with its header once. The JSON keeps one block per page, linked by `continuedBy` and `continues`, so each block's box stays on its own page.
135
+ - Text a reader can't see never reaches the outputs: invisible text (such as the hidden layer of a scanned page), text off the page or too small to read, and text cut away by a clipping path. Nor does near-white text with nothing behind it, or text painted over by a later shape, image or comment. Headers, footers and page numbers that repeat across pages, and text the file marks as an artifact, are left out too. The JSON lists each such run under `dropped`, with the reason and how many characters it held.
101
136
  - `to_markdown` (`format: "markdown"` in TypeScript) writes headings, paragraphs and tables, with a `<!-- page N -->` comment before each page, so a passage can be traced to its page.
102
137
  - `chunks` (`format: "chunks"`, or the `chunk` option with the JSON) groups whole paragraphs and tables up to the budget, starting a new chunk at each heading. Each chunk records the blocks, pages and boxes it came from, and where its text sits in the Markdown.
103
138
  - `extract_json` (`format: "json"`) writes the `laminapdf.extract/1` document: the pages, every block with its box in points from the top-left corner, the dropped text, the file's warnings and the chunks.
@@ -109,6 +144,9 @@ Run the Rust program on any PDF: `cargo run -p laminapdf --example extract_markd
109
144
  - **Bigger or smaller chunks:** change `maxTokens` (`with_max_tokens`). A token here is about four characters, a rough count that runs low for code, numbers and scripts other than Latin, so leave room.
110
145
  - **Overlap between chunks:** `overlap: 80` (`.with_overlap(80)`) repeats up to about 80 tokens of whole paragraphs from the end of the previous chunk.
111
146
  - **One output (TypeScript):** set `format` to `"markdown"`, `"text"`, `"json"` or `"chunks"`.
147
+ - **Pages that need OCR:** a scanned page has no text to extract, so its Markdown is empty. Each page in the JSON has a `textLayer` whose `status` says so: `pages.filter(p => p.textLayer.status === "needs-ocr")` in TypeScript, `extraction.pages.iter().filter(|p| p.text_layer.status == TextLayerStatus::NeedsOcr)` in Rust. Send those pages to OCR or a vision model. A page made searchable by OCR is `ocr-layer`, since extraction leaves its invisible text out; the file's warnings name both kinds.
112
148
  - **A long file in steps (Rust):** `doc.extract_job(&options)` and `step` do a page per unit of work, so a long file can report progress or stop between steps.
113
149
 
150
+ [Benchmarks](../../pages/reference/benchmarks.md) has extraction's scores on a public benchmark and how many pages a second it reads.
151
+
114
152
  Next: [all recipes](../../pages/recipes.md).
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Generate, then edit"
3
3
  description: "Build a one-page draft, open its bytes, retitle it in one transaction and save the edit as a revision after the original bytes."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/recipes/generate-then-edit/"
6
6
  ---
7
7
 
@@ -13,7 +13,9 @@ You'll generate a one-page draft report, then open its bytes, retitle it and sav
13
13
 
14
14
  One function builds and saves the draft. Another opens those bytes, changes the title and author, and saves again. In TypeScript the draft needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
15
 
16
- <TabItem label="TypeScript">`packages/sdk/examples/generate-then-edit.ts`:
16
+ **TypeScript**
17
+
18
+ `packages/sdk/examples/generate-then-edit.ts`:
17
19
 
18
20
  ```ts
19
21
  // Generate a document, then open its bytes, edit them and save incrementally. The edit changes
@@ -48,8 +50,11 @@ function edit(bytes: Uint8Array, editedAt?: string): Uint8Array<ArrayBuffer> {
48
50
  doc.transact((tx) => tx.setInfo({ title: "Final report", author: "Ada Lovelace" }, editedAt));
49
51
  return doc.save({ mode: "incremental" });
50
52
  }
51
- ```</TabItem>
52
- <TabItem label="Rust">`crates/laminapdf/examples/generate_then_edit.rs`:
53
+ ```
54
+
55
+ **Rust**
56
+
57
+ `crates/laminapdf/examples/generate_then_edit.rs`:
53
58
 
54
59
  ````rust
55
60
  //! Generate a document, then open its bytes, edit them and save incrementally.
@@ -111,9 +116,11 @@ fn edit(bytes: Vec<u8>, edited_at: Option<PdfDate>) -> Result<Vec<u8>, Box<dyn E
111
116
  let options = SaveOptions::new().with_mode(SaveMode::Incremental);
112
117
  Ok(doc.save(&options)?.bytes)
113
118
  }
114
- ````</TabItem>
119
+ ````
120
+
121
+ **Result:** [generate_then_edit.pdf](https://docs.laminapdf.com/examples/pdf/generate_then_edit.pdf)
115
122
 
116
- The result is the draft's bytes, unchanged, followed by one revision that retitles it "Final report" and names Ada Lovelace as its author: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/generate_then_edit.pdf).
123
+ The result is the draft's bytes, unchanged, followed by one revision that retitles it "Final report" and names Ada Lovelace as its author. The Result tab shows it, with a link to the PDF.
117
124
 
118
125
  ## How it works
119
126
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Hello, world"
3
3
  description: "One A4 page with a line of text in a standard 14 font, the smallest PDF Lamina PDF writes, in TypeScript and Rust."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/recipes/hello/"
6
6
  ---
7
7
 
@@ -13,7 +13,9 @@ You'll make the smallest PDF Lamina PDF writes: one A4 page that says "Hello, wo
13
13
 
14
14
  The function builds the page and returns the PDF's bytes. In TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
15
 
16
- <TabItem label="TypeScript">`packages/sdk/examples/hello.ts`:
16
+ **TypeScript**
17
+
18
+ `packages/sdk/examples/hello.ts`:
17
19
 
18
20
  ```ts
19
21
  // The smallest document: one A4 page with a line of text in a standard 14 font.
@@ -34,8 +36,11 @@ export function hello(options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
34
36
  using doc = b.finish();
35
37
  return doc.save();
36
38
  }
37
- ```</TabItem>
38
- <TabItem label="Rust">`crates/laminapdf/examples/hello.rs`:
39
+ ```
40
+
41
+ **Rust**
42
+
43
+ `crates/laminapdf/examples/hello.rs`:
39
44
 
40
45
  ````rust
41
46
  //! The smallest document: one A4 page with a line of text in a standard 14 font.
@@ -73,9 +78,11 @@ pub(crate) fn build(options: BuildOptions) -> Result<Vec<u8>, Box<dyn Error>> {
73
78
  let mut doc = b.finish()?;
74
79
  Ok(doc.save(&SaveOptions::new())?.bytes)
75
80
  }
76
- ````</TabItem>
81
+ ````
82
+
83
+ **Result:** [hello.pdf](https://docs.laminapdf.com/examples/pdf/hello.pdf)
77
84
 
78
- The result is one page with the line near its top-left corner: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/hello.pdf).
85
+ The result is one page with the line near its top-left corner. The Result tab shows it, with a link to the PDF.
79
86
 
80
87
  ## How it works
81
88
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Images and vectors"
3
3
  description: "Shapes, a dashed curve, gray, RGB, CMYK and hex colour, transparency, a blend mode, a clip, a rotation, a JPEG and an RGBA image on one page."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/recipes/images-and-vectors/"
6
6
  ---
7
7
 
@@ -13,7 +13,9 @@ You'll draw shapes and images on one A4 page: a rectangle, a circle, a dashed cu
13
13
 
14
14
  The function takes a JPEG's bytes, draws the page and returns the PDF's bytes. It draws no text, so in TypeScript it needs only the base module, `init()`. Both languages write the same bytes:
15
15
 
16
- <TabItem label="TypeScript">`packages/sdk/examples/images-and-vectors.ts`:
16
+ **TypeScript**
17
+
18
+ `packages/sdk/examples/images-and-vectors.ts`:
17
19
 
18
20
  ```ts
19
21
  // Images and vector graphics on one A4 page. It draws shapes and a dashed Bézier curve in gray,
@@ -84,8 +86,11 @@ function images(s: Surface, photo: Image, glow: Image): void {
84
86
  s.save().transform(Matrix.after(Matrix.translate(150, 520), Matrix.rotate(30)));
85
87
  s.image(glow, [-60, -60, 120, 120]).restore();
86
88
  }
87
- ```</TabItem>
88
- <TabItem label="Rust">`crates/laminapdf/examples/images_and_vectors.rs`:
89
+ ```
90
+
91
+ **Rust**
92
+
93
+ `crates/laminapdf/examples/images_and_vectors.rs`:
89
94
 
90
95
  ````rust
91
96
  //! Images and vector graphics on one A4 page.
@@ -185,9 +190,11 @@ fn images(s: &mut Surface, photo: &Image, glow: &Image) {
185
190
  s.save().transform(placement);
186
191
  s.image(glow, RectPt::from_points(-60.0, -60.0, 120.0, 120.0)).restore();
187
192
  }
188
- ````</TabItem>
193
+ ````
194
+
195
+ **Result:** [images_and_vectors.pdf](https://docs.laminapdf.com/examples/pdf/images_and_vectors.pdf)
189
196
 
190
- The result is one A4 page with every shape and both images: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/images_and_vectors.pdf).
197
+ The result is one A4 page with every shape and both images. The Result tab shows it, with a link to the PDF.
191
198
 
192
199
  ## How it works
193
200
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "An invoice"
3
3
  description: "A tagged one-page invoice with an embedded font, a table of line items with right-aligned amounts and a total, in TypeScript and Rust."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/recipes/invoice/"
6
6
  ---
7
7
 
@@ -13,7 +13,9 @@ You'll make a one-page invoice: a heading, an address, a table of line items wit
13
13
 
14
14
  The function takes a font file, draws the invoice and returns the PDF's bytes. In TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
15
 
16
- <TabItem label="TypeScript">`packages/sdk/examples/invoice.ts`:
16
+ **TypeScript**
17
+
18
+ `packages/sdk/examples/invoice.ts`:
17
19
 
18
20
  ```ts
19
21
  // An invoice that screen readers can follow, with an embedded font and a table of line items.
@@ -101,8 +103,11 @@ function rowBaseline(row: number): number {
101
103
  function cell([width, inset]: readonly [number, number], baseline: number): Rect {
102
104
  return [RIGHT_EDGE - inset - width, baseline - CELL_RISE, width, CELL_HEIGHT];
103
105
  }
104
- ```</TabItem>
105
- <TabItem label="Rust">`crates/laminapdf/examples/invoice.rs`:
106
+ ```
107
+
108
+ **Rust**
109
+
110
+ `crates/laminapdf/examples/invoice.rs`:
106
111
 
107
112
  ```rust
108
113
  //! An invoice that screen readers can follow, with an embedded font and a table of line items.
@@ -205,9 +210,11 @@ fn right_aligned(s: &mut Surface, font: &FontHandle, text: &str, area: RectPt) -
205
210
  let right = TextBoxOptions::new().with_align(Alignment::End);
206
211
  s.text_box(text, font, BODY, area, &right).map(|_| ())
207
212
  }
208
- ```</TabItem>
213
+ ```
214
+
215
+ **Result:** [invoice.pdf](https://docs.laminapdf.com/examples/pdf/invoice.pdf)
209
216
 
210
- The result is one A4 page with its body set in Noto Sans: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/invoice.pdf).
217
+ The result is one A4 page with its body set in Noto Sans. The Result tab shows it, with a link to the PDF.
211
218
 
212
219
  ## How it works
213
220
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "Links and outlines"
3
3
  description: "Four A5 pages joined by web, page and named links, with a nested outline, page labels that read i, ii, 1, 2 and the outline shown on open."
4
- version: "0.2.0-alpha.3"
4
+ version: "0.2.0-alpha.4"
5
5
  url: "https://docs.laminapdf.com/recipes/links-and-outlines/"
6
6
  ---
7
7
 
@@ -13,7 +13,9 @@ You'll make four A5 pages joined by links to a website and between pages, an out
13
13
 
14
14
  The function draws the four pages, links them and returns the PDF's bytes. It draws text, so in TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
15
 
16
- <TabItem label="TypeScript">`packages/sdk/examples/links-and-outlines.ts`:
16
+ **TypeScript**
17
+
18
+ `packages/sdk/examples/links-and-outlines.ts`:
17
19
 
18
20
  ```ts
19
21
  // Links and outlines: four A5 pages joined by links, bookmarks and page labels. The title page
@@ -93,8 +95,11 @@ function navigation(b: Builder): void {
93
95
  { start: 2, style: "decimal" },
94
96
  ]).viewerPreferences({ pageMode: "outlines" });
95
97
  }
96
- ```</TabItem>
97
- <TabItem label="Rust">`crates/laminapdf/examples/links_and_outlines.rs`:
98
+ ```
99
+
100
+ **Rust**
101
+
102
+ `crates/laminapdf/examples/links_and_outlines.rs`:
98
103
 
99
104
  ````rust
100
105
  //! Links and outlines: four A5 pages joined by links, bookmarks and page labels.
@@ -192,9 +197,11 @@ fn navigation(b: &mut Builder) -> Result<(), Box<dyn Error>> {
192
197
  .viewer_preferences(ViewerPreferences::default().with_page_mode(PageMode::Outlines));
193
198
  Ok(())
194
199
  }
195
- ````</TabItem>
200
+ ````
201
+
202
+ **Result:** [links_and_outlines.pdf](https://docs.laminapdf.com/examples/pdf/links_and_outlines.pdf)
196
203
 
197
- The result is four A5 pages with an outline and page labels: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/links_and_outlines.pdf).
204
+ The result is four A5 pages with an outline and page labels. The Result tab shows it, with a link to the PDF.
198
205
 
199
206
  ## How it works
200
207