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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/README.md +63 -8
  2. package/dist/api.d.ts +8 -3
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +4 -2
  5. package/dist/api.js.map +1 -1
  6. package/dist/builder.d.ts +43 -22
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js +122 -83
  9. package/dist/builder.js.map +1 -1
  10. package/dist/document.d.ts +89 -22
  11. package/dist/document.d.ts.map +1 -1
  12. package/dist/document.js +177 -67
  13. package/dist/document.js.map +1 -1
  14. package/dist/errors.d.ts +5 -5
  15. package/dist/errors.d.ts.map +1 -1
  16. package/dist/errors.js +10 -16
  17. package/dist/errors.js.map +1 -1
  18. package/dist/extract-job.d.ts +100 -0
  19. package/dist/extract-job.d.ts.map +1 -0
  20. package/dist/extract-job.js +177 -0
  21. package/dist/extract-job.js.map +1 -0
  22. package/dist/extract.d.ts +57 -0
  23. package/dist/extract.d.ts.map +1 -0
  24. package/dist/extract.js +5 -0
  25. package/dist/extract.js.map +1 -0
  26. package/dist/font.d.ts +13 -6
  27. package/dist/font.d.ts.map +1 -1
  28. package/dist/font.js +50 -20
  29. package/dist/font.js.map +1 -1
  30. package/dist/generated/builder-enums.d.ts +3 -3
  31. package/dist/generated/builder-enums.d.ts.map +1 -1
  32. package/dist/generated/builder-enums.js +10 -0
  33. package/dist/generated/builder-enums.js.map +1 -1
  34. package/dist/generated/codes.d.ts +18 -6
  35. package/dist/generated/codes.d.ts.map +1 -1
  36. package/dist/generated/codes.js +114 -5
  37. package/dist/generated/codes.js.map +1 -1
  38. package/dist/generated/explain.d.ts.map +1 -1
  39. package/dist/generated/explain.js +45 -11
  40. package/dist/generated/explain.js.map +1 -1
  41. package/dist/generated/extract.d.ts +282 -0
  42. package/dist/generated/extract.d.ts.map +1 -0
  43. package/dist/generated/extract.js +4 -0
  44. package/dist/generated/extract.js.map +1 -0
  45. package/dist/init.d.ts +13 -5
  46. package/dist/init.d.ts.map +1 -1
  47. package/dist/init.js +23 -11
  48. package/dist/init.js.map +1 -1
  49. package/dist/internal/handle.d.ts +58 -0
  50. package/dist/internal/handle.d.ts.map +1 -0
  51. package/dist/internal/handle.js +113 -0
  52. package/dist/internal/handle.js.map +1 -0
  53. package/dist/internal/instance.d.ts +56 -0
  54. package/dist/internal/instance.d.ts.map +1 -0
  55. package/dist/internal/instance.js +125 -0
  56. package/dist/internal/instance.js.map +1 -0
  57. package/dist/source/http.d.ts +1 -1
  58. package/dist/source/http.js +2 -2
  59. package/dist/source/http.js.map +1 -1
  60. package/dist/source/range.d.ts +6 -1
  61. package/dist/source/range.d.ts.map +1 -1
  62. package/dist/source/range.js +10 -6
  63. package/dist/source/range.js.map +1 -1
  64. package/dist/text.d.ts +3 -3
  65. package/dist/text.d.ts.map +1 -1
  66. package/dist/text.js +3 -3
  67. package/dist/text.js.map +1 -1
  68. package/dist/types.d.ts +19 -1
  69. package/dist/types.d.ts.map +1 -1
  70. package/dist/version.d.ts +1 -1
  71. package/dist/version.js +2 -2
  72. package/dist/version.js.map +1 -1
  73. package/dist/worker/client.d.ts +3 -2
  74. package/dist/worker/client.d.ts.map +1 -1
  75. package/dist/worker/client.js +6 -4
  76. package/dist/worker/client.js.map +1 -1
  77. package/dist/worker/entry.js +1 -1
  78. package/dist/worker/entry.js.map +1 -1
  79. package/dist/worker/server.d.ts.map +1 -1
  80. package/dist/worker/server.js +42 -9
  81. package/dist/worker/server.js.map +1 -1
  82. package/dist/worker/support.d.ts +13 -3
  83. package/dist/worker/support.d.ts.map +1 -1
  84. package/dist/worker/support.js +5 -0
  85. package/dist/worker/support.js.map +1 -1
  86. package/dist/worker/types.d.ts +8 -1
  87. package/dist/worker/types.d.ts.map +1 -1
  88. package/docs/AGENTS.md +14 -0
  89. package/docs/api/README.md +166 -0
  90. package/docs/api/classes/Builder.md +467 -0
  91. package/docs/api/classes/Document.md +854 -0
  92. package/docs/api/classes/ExtractJob.md +236 -0
  93. package/docs/api/classes/Font.md +74 -0
  94. package/docs/api/classes/Image.md +140 -0
  95. package/docs/api/classes/LaminaPdfError.md +189 -0
  96. package/docs/api/classes/Page.md +178 -0
  97. package/docs/api/classes/PageRef.md +253 -0
  98. package/docs/api/classes/PageText.md +129 -0
  99. package/docs/api/classes/PageTextRun.md +167 -0
  100. package/docs/api/classes/PendingPath.md +97 -0
  101. package/docs/api/classes/SaveJob.md +205 -0
  102. package/docs/api/classes/Surface.md +331 -0
  103. package/docs/api/classes/Transaction.md +111 -0
  104. package/docs/api/functions/callbackSource.md +33 -0
  105. package/docs/api/functions/extractWithModels.md +70 -0
  106. package/docs/api/functions/httpRangeSource.md +48 -0
  107. package/docs/api/functions/init.md +36 -0
  108. package/docs/api/functions/isExtractLoaded.md +21 -0
  109. package/docs/api/functions/isInitialized.md +19 -0
  110. package/docs/api/functions/isLaminaPdfError.md +41 -0
  111. package/docs/api/functions/isTextLoaded.md +19 -0
  112. package/docs/api/functions/loadExtract.md +28 -0
  113. package/docs/api/functions/loadText.md +28 -0
  114. package/docs/api/functions/onLeak.md +29 -0
  115. package/docs/api/functions/reset.md +22 -0
  116. package/docs/api/functions/wasmMemory.md +22 -0
  117. package/docs/api/interfaces/BuilderOptions.md +108 -0
  118. package/docs/api/interfaces/ByteRange.md +29 -0
  119. package/docs/api/interfaces/ChunkOptions.md +37 -0
  120. package/docs/api/interfaces/Diagnostic.md +67 -0
  121. package/docs/api/interfaces/ExtractOptions.md +92 -0
  122. package/docs/api/interfaces/ExtractResult.md +37 -0
  123. package/docs/api/interfaces/FontInfo.md +37 -0
  124. package/docs/api/interfaces/HostModels.md +57 -0
  125. package/docs/api/interfaces/HttpRangeOptions.md +78 -0
  126. package/docs/api/interfaces/InitOptions.md +40 -0
  127. package/docs/api/interfaces/LayoutRegion.md +38 -0
  128. package/docs/api/interfaces/Leak.md +21 -0
  129. package/docs/api/interfaces/Location.md +53 -0
  130. package/docs/api/interfaces/ModelInfo.md +45 -0
  131. package/docs/api/interfaces/ModelRequest.md +45 -0
  132. package/docs/api/interfaces/Models.md +45 -0
  133. package/docs/api/interfaces/OcrWord.md +37 -0
  134. package/docs/api/interfaces/OpLog.md +25 -0
  135. package/docs/api/interfaces/OpenOptions.md +37 -0
  136. package/docs/api/interfaces/PageTextOptions.md +21 -0
  137. package/docs/api/interfaces/PathBuilder.md +195 -0
  138. package/docs/api/interfaces/QuadPoint.md +25 -0
  139. package/docs/api/interfaces/RangeOptions.md +54 -0
  140. package/docs/api/interfaces/RangeSource.md +59 -0
  141. package/docs/api/interfaces/StrokeStyle.md +61 -0
  142. package/docs/api/interfaces/TagSpec.md +69 -0
  143. package/docs/api/interfaces/TextBoxOptions.md +93 -0
  144. package/docs/api/interfaces/TextGlyph.md +29 -0
  145. package/docs/api/interfaces/TextOptions.md +101 -0
  146. package/docs/api/interfaces/WasmMemory.md +37 -0
  147. package/docs/api/type-aliases/AfRelationship.md +15 -0
  148. package/docs/api/type-aliases/Align.md +15 -0
  149. package/docs/api/type-aliases/ArtifactKind.md +15 -0
  150. package/docs/api/type-aliases/AttachmentOptions.md +15 -0
  151. package/docs/api/type-aliases/BlendMode.md +15 -0
  152. package/docs/api/type-aliases/BoxRect.md +15 -0
  153. package/docs/api/type-aliases/BuildOptions.md +15 -0
  154. package/docs/api/type-aliases/Cell.md +71 -0
  155. package/docs/api/type-aliases/Chunk.md +79 -0
  156. package/docs/api/type-aliases/Color.md +16 -0
  157. package/docs/api/type-aliases/Coordinates.md +55 -0
  158. package/docs/api/type-aliases/Direction.md +15 -0
  159. package/docs/api/type-aliases/DocumentInfo.md +15 -0
  160. package/docs/api/type-aliases/DropReason.md +15 -0
  161. package/docs/api/type-aliases/Dropped.md +59 -0
  162. package/docs/api/type-aliases/Edited.md +15 -0
  163. package/docs/api/type-aliases/ErrorCode.md +15 -0
  164. package/docs/api/type-aliases/ErrorKind.md +15 -0
  165. package/docs/api/type-aliases/ExtractFormat.md +15 -0
  166. package/docs/api/type-aliases/ExtractJobOptions.md +31 -0
  167. package/docs/api/type-aliases/ExtractStep.md +61 -0
  168. package/docs/api/type-aliases/ExtractedBlock.md +94 -0
  169. package/docs/api/type-aliases/ExtractedPage.md +73 -0
  170. package/docs/api/type-aliases/ExtractedSource.md +48 -0
  171. package/docs/api/type-aliases/Extraction.md +22 -0
  172. package/docs/api/type-aliases/FillRule.md +15 -0
  173. package/docs/api/type-aliases/Fit.md +15 -0
  174. package/docs/api/type-aliases/Flags.md +15 -0
  175. package/docs/api/type-aliases/Generator.md +43 -0
  176. package/docs/api/type-aliases/GlyphQuad.md +15 -0
  177. package/docs/api/type-aliases/InfoPatch.md +15 -0
  178. package/docs/api/type-aliases/LaminaPdfWarning.md +16 -0
  179. package/docs/api/type-aliases/LimitName.md +15 -0
  180. package/docs/api/type-aliases/Limits.md +15 -0
  181. package/docs/api/type-aliases/LineBox.md +15 -0
  182. package/docs/api/type-aliases/LineCap.md +15 -0
  183. package/docs/api/type-aliases/LineJoin.md +15 -0
  184. package/docs/api/type-aliases/LinkTarget.md +15 -0
  185. package/docs/api/type-aliases/Metadata.md +15 -0
  186. package/docs/api/type-aliases/ModelResult.md +15 -0
  187. package/docs/api/type-aliases/Op.md +15 -0
  188. package/docs/api/type-aliases/OutlineEntry.md +15 -0
  189. package/docs/api/type-aliases/OutlineNode.md +15 -0
  190. package/docs/api/type-aliases/Overflow.md +15 -0
  191. package/docs/api/type-aliases/PageInfo.md +15 -0
  192. package/docs/api/type-aliases/PageLabel.md +15 -0
  193. package/docs/api/type-aliases/PageSizeName.md +15 -0
  194. package/docs/api/type-aliases/Point.md +15 -0
  195. package/docs/api/type-aliases/Profile.md +15 -0
  196. package/docs/api/type-aliases/Progress.md +15 -0
  197. package/docs/api/type-aliases/ReadingOrder.md +15 -0
  198. package/docs/api/type-aliases/Rect.md +15 -0
  199. package/docs/api/type-aliases/Region.md +31 -0
  200. package/docs/api/type-aliases/RegionLabel.md +15 -0
  201. package/docs/api/type-aliases/RenderMode.md +15 -0
  202. package/docs/api/type-aliases/Rotation.md +15 -0
  203. package/docs/api/type-aliases/SaveOptions.md +15 -0
  204. package/docs/api/type-aliases/SaveReport.md +15 -0
  205. package/docs/api/type-aliases/Span.md +74 -0
  206. package/docs/api/type-aliases/SplitOn.md +15 -0
  207. package/docs/api/type-aliases/Standard14.md +15 -0
  208. package/docs/api/type-aliases/StructRef.md +41 -0
  209. package/docs/api/type-aliases/StructSource.md +17 -0
  210. package/docs/api/type-aliases/StructureKid.md +15 -0
  211. package/docs/api/type-aliases/StructureNode.md +16 -0
  212. package/docs/api/type-aliases/TableDetector.md +15 -0
  213. package/docs/api/type-aliases/TableMode.md +15 -0
  214. package/docs/api/type-aliases/TableScope.md +15 -0
  215. package/docs/api/type-aliases/Tag.md +15 -0
  216. package/docs/api/type-aliases/TagName.md +15 -0
  217. package/docs/api/type-aliases/Target.md +15 -0
  218. package/docs/api/type-aliases/TargetView.md +18 -0
  219. package/docs/api/type-aliases/TextLayer.md +72 -0
  220. package/docs/api/type-aliases/TextLayerStatus.md +15 -0
  221. package/docs/api/type-aliases/TextLayout.md +15 -0
  222. package/docs/api/type-aliases/TextRange.md +33 -0
  223. package/docs/api/type-aliases/TextRunInfo.md +15 -0
  224. package/docs/api/type-aliases/Transform.md +15 -0
  225. package/docs/api/type-aliases/UndoOutcome.md +15 -0
  226. package/docs/api/type-aliases/ViewerPreferences.md +15 -0
  227. package/docs/api/type-aliases/WarningCode.md +15 -0
  228. package/docs/api/type-aliases/WasmSource.md +16 -0
  229. package/docs/api/variables/Matrix.md +99 -0
  230. package/docs/api/variables/PAGE_SIZES.md +15 -0
  231. package/docs/api/variables/TAG_NAMES.md +15 -0
  232. package/docs/errors/README.md +173 -0
  233. package/docs/errors/aes-padding-invalid.md +34 -0
  234. package/docs/errors/already-initialized.md +34 -0
  235. package/docs/errors/binary-output-to-terminal.md +35 -0
  236. package/docs/errors/builder-finished.md +35 -0
  237. package/docs/errors/cancelled.md +35 -0
  238. package/docs/errors/cmap-invalid.md +42 -0
  239. package/docs/errors/collection-index.md +33 -0
  240. package/docs/errors/color-components.md +33 -0
  241. package/docs/errors/content-bad-operands.md +36 -0
  242. package/docs/errors/content-path-unpainted.md +36 -0
  243. package/docs/errors/content-resource-missing.md +37 -0
  244. package/docs/errors/content-syntax.md +36 -0
  245. package/docs/errors/content-unbalanced-bt.md +36 -0
  246. package/docs/errors/content-unbalanced-marked.md +36 -0
  247. package/docs/errors/content-unbalanced-q.md +36 -0
  248. package/docs/errors/content-unknown-operator.md +37 -0
  249. package/docs/errors/covered-text.md +33 -0
  250. package/docs/errors/data-pending.md +34 -0
  251. package/docs/errors/decompress.md +34 -0
  252. package/docs/errors/device-color-without-output-intent.md +33 -0
  253. package/docs/errors/direct-page.md +34 -0
  254. package/docs/errors/disposed.md +34 -0
  255. package/docs/errors/document-changed.md +34 -0
  256. package/docs/errors/embedded-file-forbidden.md +35 -0
  257. package/docs/errors/embedded-file-without-mime-type.md +36 -0
  258. package/docs/errors/encoding-full.md +34 -0
  259. package/docs/errors/encrypted-base.md +33 -0
  260. package/docs/errors/extract-column-order-uncertain.md +38 -0
  261. package/docs/errors/extract-limit-blocks.md +35 -0
  262. package/docs/errors/extract-module-not-loaded.md +37 -0
  263. package/docs/errors/extract-needs-ocr.md +37 -0
  264. package/docs/errors/extract-reading-order-ambiguous.md +32 -0
  265. package/docs/errors/extract-struct-tree-ignored.md +32 -0
  266. package/docs/errors/extract-table-ambiguous.md +32 -0
  267. package/docs/errors/extract-table-limit.md +35 -0
  268. package/docs/errors/extract-untagged-content.md +32 -0
  269. package/docs/errors/fetch-failed.md +34 -0
  270. package/docs/errors/flate-truncated.md +34 -0
  271. package/docs/errors/flow-content-too-large.md +34 -0
  272. package/docs/errors/flow-keep-broken.md +34 -0
  273. package/docs/errors/flow-layout-unstable.md +38 -0
  274. package/docs/errors/font-invalid.md +36 -0
  275. package/docs/errors/font-mismatch.md +34 -0
  276. package/docs/errors/font-not-embeddable.md +34 -0
  277. package/docs/errors/font-not-embedded.md +35 -0
  278. package/docs/errors/font-not-shapable.md +33 -0
  279. package/docs/errors/glyph-out-of-range.md +34 -0
  280. package/docs/errors/glyph-without-unicode.md +37 -0
  281. package/docs/errors/heading-level-skipped.md +36 -0
  282. package/docs/errors/image-data-length.md +33 -0
  283. package/docs/errors/image-too-large.md +32 -0
  284. package/docs/errors/inline-image-unterminated.md +36 -0
  285. package/docs/errors/input-unreadable.md +37 -0
  286. package/docs/errors/instance-reset.md +34 -0
  287. package/docs/errors/invalid-argument.md +33 -0
  288. package/docs/errors/invalid-axis-value.md +32 -0
  289. package/docs/errors/invalid-box.md +34 -0
  290. package/docs/errors/invalid-color.md +32 -0
  291. package/docs/errors/invalid-command-line.md +44 -0
  292. package/docs/errors/invalid-date.md +34 -0
  293. package/docs/errors/invalid-entry.md +34 -0
  294. package/docs/errors/invalid-feature.md +32 -0
  295. package/docs/errors/invalid-font.md +32 -0
  296. package/docs/errors/invalid-geometry.md +32 -0
  297. package/docs/errors/invalid-icc-profile.md +32 -0
  298. package/docs/errors/invalid-image.md +33 -0
  299. package/docs/errors/invalid-op-log.md +33 -0
  300. package/docs/errors/invalid-page-label.md +34 -0
  301. package/docs/errors/invalid-page-labels.md +32 -0
  302. package/docs/errors/invalid-page-range.md +37 -0
  303. package/docs/errors/invalid-rotate.md +34 -0
  304. package/docs/errors/invalid-spans.md +39 -0
  305. package/docs/errors/invalid-structure-nesting.md +38 -0
  306. package/docs/errors/invalid-structure.md +34 -0
  307. package/docs/errors/invalid-text-range.md +33 -0
  308. package/docs/errors/job-finished.md +34 -0
  309. package/docs/errors/limit-exceeded.md +38 -0
  310. package/docs/errors/link-without-contents.md +33 -0
  311. package/docs/errors/link-without-structure.md +37 -0
  312. package/docs/errors/markdown-html-ignored.md +33 -0
  313. package/docs/errors/markdown-image-missing.md +34 -0
  314. package/docs/errors/missing-alt-text.md +33 -0
  315. package/docs/errors/missing-glyph.md +33 -0
  316. package/docs/errors/missing-header.md +37 -0
  317. package/docs/errors/missing-lang.md +32 -0
  318. package/docs/errors/missing-root.md +34 -0
  319. package/docs/errors/missing-title.md +32 -0
  320. package/docs/errors/model-failed.md +33 -0
  321. package/docs/errors/model-needs-host.md +40 -0
  322. package/docs/errors/model-result-unexpected.md +36 -0
  323. package/docs/errors/name-tree-node-skipped.md +34 -0
  324. package/docs/errors/nested-transaction.md +34 -0
  325. package/docs/errors/no-pending-save.md +32 -0
  326. package/docs/errors/no-transaction.md +33 -0
  327. package/docs/errors/not-initialized.md +35 -0
  328. package/docs/errors/not-pdf.md +33 -0
  329. package/docs/errors/not-rebased.md +35 -0
  330. package/docs/errors/object-number-out-of-range.md +38 -0
  331. package/docs/errors/objstm-index-mismatch.md +36 -0
  332. package/docs/errors/out-of-memory.md +38 -0
  333. package/docs/errors/outline-cycle.md +34 -0
  334. package/docs/errors/outline-item-skipped.md +34 -0
  335. package/docs/errors/output-unwritable.md +37 -0
  336. package/docs/errors/overwrite-refused.md +36 -0
  337. package/docs/errors/page-count-mismatch.md +34 -0
  338. package/docs/errors/page-index-out-of-range.md +34 -0
  339. package/docs/errors/page-tree-node-skipped.md +35 -0
  340. package/docs/errors/password-required.md +36 -0
  341. package/docs/errors/path-outside-roots.md +32 -0
  342. package/docs/errors/perms-mismatch.md +34 -0
  343. package/docs/errors/profile-version-conflict.md +35 -0
  344. package/docs/errors/redaction-not-applied.md +32 -0
  345. package/docs/errors/reference-cycle.md +35 -0
  346. package/docs/errors/render-backend-unavailable.md +32 -0
  347. package/docs/errors/render-failed.md +33 -0
  348. package/docs/errors/render-too-large.md +36 -0
  349. package/docs/errors/repaired-base.md +34 -0
  350. package/docs/errors/requires-rewrite.md +38 -0
  351. package/docs/errors/revision-index-out-of-range.md +34 -0
  352. package/docs/errors/saved-not-rebased.md +34 -0
  353. package/docs/errors/sink-not-empty.md +33 -0
  354. package/docs/errors/sink.md +31 -0
  355. package/docs/errors/source-budget-exceeded.md +34 -0
  356. package/docs/errors/source-changed.md +33 -0
  357. package/docs/errors/source.md +30 -0
  358. package/docs/errors/stale-generation.md +34 -0
  359. package/docs/errors/stream-length-recovered.md +37 -0
  360. package/docs/errors/strict.md +34 -0
  361. package/docs/errors/subset-failed.md +31 -0
  362. package/docs/errors/syntax.md +34 -0
  363. package/docs/errors/tag-element-not-open.md +41 -0
  364. package/docs/errors/tag-element-unclosed.md +34 -0
  365. package/docs/errors/text-in-earlier-revision.md +36 -0
  366. package/docs/errors/text-module-not-loaded.md +36 -0
  367. package/docs/errors/text-overflow.md +34 -0
  368. package/docs/errors/tounicode-conflict.md +32 -0
  369. package/docs/errors/type.md +34 -0
  370. package/docs/errors/unexpected-eof.md +34 -0
  371. package/docs/errors/unknown-axis.md +33 -0
  372. package/docs/errors/unknown-code.md +36 -0
  373. package/docs/errors/unknown-destination.md +33 -0
  374. package/docs/errors/unknown-font.md +33 -0
  375. package/docs/errors/unknown-option.md +33 -0
  376. package/docs/errors/unknown-page.md +34 -0
  377. package/docs/errors/unmapped-glyph.md +36 -0
  378. package/docs/errors/unnumbered-heading-misused.md +34 -0
  379. package/docs/errors/unset-object.md +30 -0
  380. package/docs/errors/unsupported-filter.md +34 -0
  381. package/docs/errors/unsupported-image.md +33 -0
  382. package/docs/errors/unsupported-layout-option.md +33 -0
  383. package/docs/errors/unsupported-security-handler.md +34 -0
  384. package/docs/errors/unsupported-version.md +32 -0
  385. package/docs/errors/untagged-content.md +35 -0
  386. package/docs/errors/warnings-truncated.md +35 -0
  387. package/docs/errors/wasm-trap.md +37 -0
  388. package/docs/errors/whole-font-rebuilt.md +30 -0
  389. package/docs/errors/worker-crashed.md +33 -0
  390. package/docs/errors/worker-failed.md +32 -0
  391. package/docs/errors/worker-version-mismatch.md +34 -0
  392. package/docs/errors/would-invalidate-signatures.md +38 -0
  393. package/docs/errors/wrong-document.md +33 -0
  394. package/docs/errors/wrong-password.md +34 -0
  395. package/docs/errors/xobject-cycle.md +36 -0
  396. package/docs/errors/xref-repaired.md +36 -0
  397. package/docs/errors/xrefstm-ignored.md +34 -0
  398. package/docs/llms.txt +94 -0
  399. package/docs/pages/concepts/dependencies.md +44 -0
  400. package/docs/pages/concepts/drawing-pages.md +76 -0
  401. package/docs/pages/concepts/errors-and-warnings.md +68 -0
  402. package/docs/pages/concepts/how-lamina-thinks.md +98 -0
  403. package/docs/pages/concepts/pages-and-coordinates.md +75 -0
  404. package/docs/pages/guides/accessible-pdfs.md +262 -0
  405. package/docs/pages/guides/archival-pdfs.md +433 -0
  406. package/docs/pages/guides/edit-a-pdf.md +143 -0
  407. package/docs/pages/guides/flowing-pages.md +379 -0
  408. package/docs/pages/guides/fonts-and-text.md +204 -0
  409. package/docs/pages/guides/images-and-vectors.md +221 -0
  410. package/docs/pages/guides/keep-the-page-responsive.md +85 -0
  411. package/docs/pages/guides/links-outlines-and-metadata.md +224 -0
  412. package/docs/pages/guides/open-large-files.md +69 -0
  413. package/docs/pages/guides/password-protected-files.md +70 -0
  414. package/docs/pages/guides/save-a-pdf.md +154 -0
  415. package/docs/pages/guides/test-generated-pdfs.md +324 -0
  416. package/docs/pages/guides/untrusted-files.md +82 -0
  417. package/docs/pages/index.md +33 -0
  418. package/docs/pages/install.md +79 -0
  419. package/docs/pages/quick-start/browser.md +71 -0
  420. package/docs/pages/quick-start/bun.md +64 -0
  421. package/docs/pages/quick-start/deno.md +62 -0
  422. package/docs/pages/quick-start/node.md +65 -0
  423. package/docs/pages/quick-start/rust.md +62 -0
  424. package/docs/pages/quick-start/workers.md +139 -0
  425. package/docs/pages/recipes/cloudflare-worker.md +115 -0
  426. package/docs/pages/recipes/element-flow.md +248 -0
  427. package/docs/pages/recipes/extract-markdown.md +152 -0
  428. package/docs/pages/recipes/generate-then-edit.md +142 -0
  429. package/docs/pages/recipes/hello.md +101 -0
  430. package/docs/pages/recipes/images-and-vectors.md +216 -0
  431. package/docs/pages/recipes/invoice.md +233 -0
  432. package/docs/pages/recipes/links-and-outlines.md +222 -0
  433. package/docs/pages/recipes/multilingual.md +196 -0
  434. package/docs/pages/recipes/open-untrusted.md +65 -0
  435. package/docs/pages/recipes/pdf-a-archive.md +212 -0
  436. package/docs/pages/recipes/pdf-a3-invoice-with-xml.md +229 -0
  437. package/docs/pages/recipes/pdf-ua-report.md +244 -0
  438. package/docs/pages/recipes/report.md +125 -0
  439. package/docs/pages/recipes/web-worker-client.md +64 -0
  440. package/docs/pages/recipes.md +48 -0
  441. package/docs/pages/reference/benchmarks.md +93 -0
  442. package/docs/pages/reference/changelog.md +134 -0
  443. package/docs/pages/reference/codes.md +14 -0
  444. package/docs/pages/reference/glossary.md +108 -0
  445. package/docs/pages/reference/rust.md +36 -0
  446. package/docs/pages/reference/stability.md +59 -0
  447. package/docs/pages/start/coding-agents.md +45 -0
  448. package/docs/pages/start/first-pdf.md +81 -0
  449. package/docs/pages/troubleshooting.md +74 -0
  450. package/docs/renames.json +86 -0
  451. package/docs/schemas/error.schema.json +322 -0
  452. package/docs/schemas/renames.schema.json +29 -0
  453. package/package.json +7 -3
  454. package/schemas/error.schema.json +37 -3
@@ -0,0 +1,154 @@
1
+ ---
2
+ title: "Save a PDF"
3
+ description: "Save edits as a revision appended to the original bytes or as a whole new file, and see when a file refuses a save."
4
+ version: "0.2.0-alpha.4"
5
+ url: "https://docs.laminapdf.com/guides/save-a-pdf/"
6
+ ---
7
+
8
+ # Save a PDF
9
+
10
+ Save edits as a revision appended to the original bytes or as a whole new file, and see when a file refuses a save.
11
+
12
+ You'll save an edited PDF, either by appending your changes to the original bytes or by writing the whole file again. The choice matters when a file is signed, was damaged, or is password-protected, because each one limits how it can be saved.
13
+
14
+ This example, from [Edit a PDF](../../pages/guides/edit-a-pdf.md), asks for an [incremental save](../../pages/reference/glossary.md#incremental-save), which keeps the original bytes at the start of the result, unchanged:
15
+
16
+ **TypeScript**
17
+
18
+ `packages/sdk/examples/generate-then-edit.ts`:
19
+
20
+ ```ts
21
+ // Generate a document, then open its bytes, edit them and save incrementally. The edit changes
22
+ // the Info dictionary in one transaction, and the incremental save keeps the generated bytes as
23
+ // they are, with one revision appended.
24
+ import { Builder, type BuilderOptions, Document, Font } from "@laminapdf/sdk";
25
+
26
+ /**
27
+ * The generated file followed by the edit's revision. Needs `init({ text: true })`.
28
+ *
29
+ * @param options - can fix the file ID, the producer and the creation date.
30
+ * @param editedAt - the modification date to record, as a PDF date string.
31
+ */
32
+ export function generateThenEdit(options: BuilderOptions = {}, editedAt?: string): Uint8Array<ArrayBuffer> {
33
+ return edit(generate(options), editedAt);
34
+ }
35
+
36
+ /** A one-page draft, saved (a new document is always saved as a full rewrite). */
37
+ export function generate(options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
38
+ using b = new Builder(options);
39
+ const page = b.page("A4");
40
+ page.surface().text("Draft report", { font: Font.standard("Helvetica"), size: 20, at: [72, 96] });
41
+ page.finish();
42
+ b.metadata({ title: "Draft report" });
43
+ using doc = b.finish();
44
+ return doc.save();
45
+ }
46
+
47
+ /** Opens `bytes`, edits the Info dictionary in one transaction (which `undo` could revert) and saves incrementally. */
48
+ function edit(bytes: Uint8Array, editedAt?: string): Uint8Array<ArrayBuffer> {
49
+ using doc = Document.open(bytes);
50
+ doc.transact((tx) => tx.setInfo({ title: "Final report", author: "Ada Lovelace" }, editedAt));
51
+ return doc.save({ mode: "incremental" });
52
+ }
53
+ ```
54
+
55
+ **Rust**
56
+
57
+ `crates/laminapdf/examples/generate_then_edit.rs`:
58
+
59
+ ````rust
60
+ //! Generate a document, then open its bytes, edit them and save incrementally.
61
+ //!
62
+ //! The edit changes the Info dictionary with an op (undoable and serialisable), and the
63
+ //! incremental save keeps the generated bytes as they are, with one revision appended.
64
+ //!
65
+ //! ```sh
66
+ //! cargo run -p laminapdf --example generate_then_edit -- [out-dir] # writes <out-dir>/generate_then_edit.pdf
67
+ //! ```
68
+
69
+ use std::error::Error;
70
+ use std::path::PathBuf;
71
+
72
+ use laminapdf::{
73
+ BuildOptions, Builder, Document, InfoPatch, Metadata, Op, OpenOptions, PageSize, PdfDate, PointPt, Pt, SaveMode,
74
+ SaveOptions, SetInfo, Standard14,
75
+ };
76
+
77
+ fn main() -> Result<(), Box<dyn Error>> {
78
+ let out = PathBuf::from(std::env::args().nth(1).unwrap_or_else(|| "target/examples-out".into()));
79
+ std::fs::create_dir_all(&out)?;
80
+ let bytes = build(BuildOptions::new(), None)?;
81
+ std::fs::write(out.join("generate_then_edit.pdf"), bytes)?;
82
+ Ok(())
83
+ }
84
+
85
+ /// The generated file followed by the edit's revision. `edited_at` is recorded as the
86
+ /// modification date; `options` can fix the file ID, the producer and the creation date.
87
+ pub(crate) fn build(options: BuildOptions, edited_at: Option<PdfDate>) -> Result<Vec<u8>, Box<dyn Error>> {
88
+ let generated = generate(options)?;
89
+ edit(generated, edited_at)
90
+ }
91
+
92
+ /// A one-page draft, saved (a new document is always saved as a full rewrite).
93
+ pub(crate) fn generate(options: BuildOptions) -> Result<Vec<u8>, Box<dyn Error>> {
94
+ let mut b = Builder::new(options);
95
+ let helvetica = b.standard_font(Standard14::Helvetica)?;
96
+ let mut page = b.page(PageSize::A4);
97
+ page.surface().text(
98
+ "Draft report",
99
+ &helvetica,
100
+ Pt::new(20.0),
101
+ PointPt::from_points(72.0, 96.0),
102
+ )?;
103
+ page.finish()?;
104
+ b.metadata(Metadata::default().with_title("Draft report"));
105
+ Ok(b.finish()?.save(&SaveOptions::new())?.bytes)
106
+ }
107
+
108
+ /// Opens `bytes`, edits the Info dictionary in one transaction (which `undo` could revert) and
109
+ /// saves incrementally: `bytes` unchanged, then the appended revision.
110
+ fn edit(bytes: Vec<u8>, edited_at: Option<PdfDate>) -> Result<Vec<u8>, Box<dyn Error>> {
111
+ let mut doc = Document::open_bytes(bytes, OpenOptions::new())?;
112
+ let patch = InfoPatch::new()
113
+ .title(Some("Final report"))
114
+ .author(Some("Ada Lovelace"));
115
+ doc.apply(vec![Op::from(SetInfo::new(patch, edited_at))])?;
116
+ let options = SaveOptions::new().with_mode(SaveMode::Incremental);
117
+ Ok(doc.save(&options)?.bytes)
118
+ }
119
+ ````
120
+
121
+ **Result:** [generate_then_edit.pdf](https://docs.laminapdf.com/examples/pdf/generate_then_edit.pdf)
122
+
123
+ ## Choose a save mode
124
+
125
+ Every save either appends a revision to the bytes you opened (an incremental save) or writes the whole file again (a [full rewrite](../../pages/reference/glossary.md#full-rewrite)). You pick one with the `mode` option:
126
+
127
+ | `mode` | What it writes |
128
+ | --- | --- |
129
+ | `auto` (the default) | Incremental when that's allowed, otherwise a full rewrite |
130
+ | `incremental` | The original bytes unchanged, then one appended revision |
131
+ | `fullRewrite` | The whole file again |
132
+
133
+ `doc.save(options)` returns the bytes, and `doc.saveWithReport(options)` also says what it wrote. `doc.saveJob()` does the same work in steps, so a page can save a large file without freezing. The same file with the same edits always saves to the same bytes.
134
+
135
+ *An incremental save appends a revision; a full rewrite writes every byte again.*
136
+
137
+ An incremental save (`incremental`) keeps the original bytes and appends one revision after them. A signature over those bytes stays valid for the edits it allows. A full rewrite (`fullRewrite`) writes every byte again as one new file. Signatures over the original bytes are no longer valid, so a signed file saves this way only when you pass `invalidateSignatures: true`, which removes them.
138
+
139
+ ## When a file refuses a save
140
+
141
+ Every save makes a plan before it writes. The plan reads the file's save constraint: what its signatures, permissions, repairs and encryption allow. Then it picks a mode or refuses:
142
+
143
+ - **A signed file** saves only incrementally, and only with edits its signatures allow. Anything else fails with `would-invalidate-signatures`. Pass `invalidateSignatures: true` to save anyway, which removes the broken signatures from the output.
144
+ - **A repaired file**, one Lamina PDF had to fix as it opened, can't safely take an appended revision. It needs a full rewrite, so asking for `incremental` fails with `requires-rewrite`.
145
+ - **An edited encrypted file** saves only as a full rewrite without its encryption, and only when you pass `removeEncryption: true`. Otherwise every mode fails with `encrypted-base`.
146
+ - **An unchanged document**, even a signed, repaired or encrypted one, saves incrementally in `auto` and `incremental` mode and returns the bytes you opened. Passing `removeEncryption: true` rewrites it instead.
147
+
148
+ *What a file allows, and the options you pass, decide its save mode or the error the save fails with.*
149
+
150
+ Every save plans before it writes, and an unchanged document comes first: in `auto` and `incremental` mode it saves incrementally and returns the bytes you opened, whatever else is true of it. A signed file saves incrementally, with the edits its signatures allow. Any other save fails with `would-invalidate-signatures` unless you pass `invalidateSignatures: true`, which saves anyway and removes the signatures that no longer hold. A repaired file needs a full rewrite: `auto` picks `fullRewrite`, and asking for `incremental` fails with `requires-rewrite`. An encrypted file you edited saves as `fullRewrite` without its encryption when you pass `removeEncryption: true`; otherwise every mode fails with `encrypted-base`. An edited file in two of these states meets both rules: a signed file that was repaired fails until you pass `invalidateSignatures: true`.
151
+
152
+ > **Details:** The save constraint covers signature fields, the [DocMDP](../../pages/reference/glossary.md#docmdp) and [UR3](../../pages/reference/glossary.md#ur3) permissions, a signature's `/Lock` entry (the form fields it locks), document timestamps, and whether the file needed repair.
153
+
154
+ Next: [open password-protected files](../../pages/guides/password-protected-files.md).
@@ -0,0 +1,324 @@
1
+ ---
2
+ title: "Test the PDFs you generate"
3
+ description: "Compare the PDFs your code makes with reference PDFs you approved, byte for byte, and draw both files' pages to see what changed."
4
+ version: "0.2.0-alpha.4"
5
+ url: "https://docs.laminapdf.com/guides/test-generated-pdfs/"
6
+ ---
7
+
8
+ # Test the PDFs you generate
9
+
10
+ Compare the PDFs your code makes with reference PDFs you approved, byte for byte, and draw both files' pages to see what changed.
11
+
12
+ You'll write a test that fails when a PDF your code makes changes. The test compares the PDF's bytes with a reference PDF you opened and approved. When the bytes differ, a second example draws both files' pages to show where they look different.
13
+
14
+ ## Why the bytes repeat
15
+
16
+ Lamina PDF never reads a clock or picks a random value while it writes. With the same code, data, options and Lamina PDF version, Rust and every JavaScript runtime write the same bytes. Three values usually come from your app rather than from the content (the file ID, the producer and the creation date), so a test fixes them.
17
+
18
+ Your own code can vary the bytes too, and a failing test should point there first:
19
+
20
+ - a date taken from the clock, such as an invoice date or `new Date()` in a footer;
21
+ - records read in a different order, such as rows from a database without `ORDER BY`;
22
+ - a different font file: an update to the font changes the embedded subset.
23
+
24
+ ## Compare with a reference PDF
25
+
26
+ The example below holds the options a test passes and a function that compares a PDF with its reference. The function answers `same`, `created` or `changed`, and your test runner checks the answer, so it works in Vitest, Jest, `node:test` or `cargo test`:
27
+
28
+ **TypeScript**
29
+
30
+ `packages/sdk/examples/snapshot-test.ts`:
31
+
32
+ ```ts
33
+ // Checks that a PDF your code makes is byte for byte the reference PDF you approved.
34
+ //
35
+ // Build the document with `TEST_OPTIONS`, then let your test runner check the answer:
36
+ //
37
+ // expect(compareWithReference(invoice(order, TEST_OPTIONS), "references/invoice.pdf")).toBe("same");
38
+ //
39
+ // The first run fails with `created` and writes the reference: open it, and commit it once it
40
+ // looks right. When the bytes change later, the new PDF is written beside the reference for
41
+ // review; pass `{ update: true }` to accept it.
42
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
43
+ import { basename, dirname, extname, join } from "node:path";
44
+ import type { BuilderOptions } from "@laminapdf/sdk";
45
+
46
+ /**
47
+ * What a test passes in place of the clock and a random file ID. With these fixed, the same code
48
+ * and data give the same bytes on every run, machine and runtime.
49
+ */
50
+ export const TEST_OPTIONS: BuilderOptions = {
51
+ // The file ID: any 32 hex digits, as long as they stay the same.
52
+ id: "00112233445566778899aabbccddeeff",
53
+ // The Info dictionary's /Producer: your app's name, without a version that changes.
54
+ producer: "my-app tests",
55
+ // The Info dictionary's /CreationDate, in PDF date form.
56
+ creationDate: "D:20260101000000Z",
57
+ };
58
+
59
+ /** `same`: the bytes match. `created`: there was no reference, so one was written. `changed`: they differ. */
60
+ export type Outcome = "same" | "created" | "changed";
61
+
62
+ export interface CompareOptions {
63
+ /** Replace the reference with the new bytes instead of writing them beside it. */
64
+ readonly update?: boolean;
65
+ }
66
+
67
+ /**
68
+ * Compares `bytes` with the reference PDF at `reference`. A changed PDF is written to
69
+ * `<name>.new.pdf` next to the reference, or replaces it when `update` is set.
70
+ */
71
+ export function compareWithReference(bytes: Uint8Array, reference: string, options: CompareOptions = {}): Outcome {
72
+ const pending = join(dirname(reference), `${basename(reference, extname(reference))}.new.pdf`);
73
+ if (!existsSync(reference)) {
74
+ mkdirSync(dirname(reference), { recursive: true });
75
+ writeFileSync(reference, bytes);
76
+ return "created";
77
+ }
78
+ if (sameBytes(readFileSync(reference), bytes)) {
79
+ rmSync(pending, { force: true });
80
+ return "same";
81
+ }
82
+ if (options.update) {
83
+ writeFileSync(reference, bytes);
84
+ rmSync(pending, { force: true });
85
+ } else {
86
+ writeFileSync(pending, bytes);
87
+ }
88
+ return "changed";
89
+ }
90
+
91
+ function sameBytes(a: Uint8Array, b: Uint8Array): boolean {
92
+ return a.length === b.length && a.every((byte, i) => byte === b[i]);
93
+ }
94
+ ```
95
+
96
+ **Rust**
97
+
98
+ `crates/laminapdf/examples/snapshot_test.rs`:
99
+
100
+ ````rust
101
+ //! Checks that a PDF your code makes is byte for byte the reference PDF you approved.
102
+ //!
103
+ //! Build the document with `test_options()`, then assert on the answer in a test:
104
+ //!
105
+ //! ```ignore
106
+ //! let bytes = invoice(&order, test_options()?)?;
107
+ //! assert_eq!(compare_with_reference(&bytes, Path::new("references/invoice.pdf"), false)?, Outcome::Same);
108
+ //! ```
109
+ //!
110
+ //! The first run fails with `Created` and writes the reference: open it, and commit it once it
111
+ //! looks right. When the bytes change later, the new PDF is written beside the reference for
112
+ //! review; pass `update: true` to accept it. Run on its own, the example compares two files:
113
+ //!
114
+ //! ```sh
115
+ //! cargo run -p laminapdf --example snapshot_test -- new.pdf reference.pdf
116
+ //! ```
117
+
118
+ use std::error::Error;
119
+ use std::path::{Path, PathBuf};
120
+
121
+ use laminapdf::BuildOptions;
122
+
123
+ fn main() -> Result<(), Box<dyn Error>> {
124
+ let mut args = std::env::args().skip(1);
125
+ let (Some(new), Some(reference)) = (args.next(), args.next()) else {
126
+ return Err("usage: snapshot_test <new.pdf> <reference.pdf>".into());
127
+ };
128
+ let outcome = compare_with_reference(&std::fs::read(new)?, Path::new(&reference), false)?;
129
+ println!("{outcome:?}");
130
+ Ok(())
131
+ }
132
+
133
+ /// What a test passes in place of the clock and a random file ID. With these fixed, the same code
134
+ /// and data give the same bytes on every run, machine and runtime.
135
+ #[cfg_attr(
136
+ not(test),
137
+ expect(dead_code, reason = "your tests call it; `main` only compares two files")
138
+ )]
139
+ pub(crate) fn test_options() -> Result<BuildOptions, Box<dyn Error>> {
140
+ /// The file ID: any 16 bytes, as long as they stay the same.
141
+ const FILE_ID: [u8; 16] = [
142
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
143
+ ];
144
+ /// The Info dictionary's `/Producer`: your app's name, without a version that changes.
145
+ const PRODUCER: &str = "my-app tests";
146
+ /// The Info dictionary's `/CreationDate`, in PDF date form.
147
+ const CREATED: &str = "D:20260101000000Z";
148
+ Ok(BuildOptions::new()
149
+ .with_id(FILE_ID)
150
+ .with_producer(PRODUCER)
151
+ .with_creation_date(CREATED.parse()?))
152
+ }
153
+
154
+ /// `Same`: the bytes match. `Created`: there was no reference, so one was written. `Changed`:
155
+ /// they differ.
156
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
157
+ pub(crate) enum Outcome {
158
+ Same,
159
+ Created,
160
+ Changed,
161
+ }
162
+
163
+ /// Compares `bytes` with the reference PDF at `reference`. A changed PDF is written to
164
+ /// `<name>.new.pdf` next to the reference, or replaces it when `update` is set.
165
+ pub(crate) fn compare_with_reference(bytes: &[u8], reference: &Path, update: bool) -> std::io::Result<Outcome> {
166
+ let pending = pending_path(reference);
167
+ if !reference.exists() {
168
+ if let Some(folder) = reference.parent() {
169
+ std::fs::create_dir_all(folder)?;
170
+ }
171
+ std::fs::write(reference, bytes)?;
172
+ return Ok(Outcome::Created);
173
+ }
174
+ if std::fs::read(reference)? == bytes {
175
+ remove_if_present(&pending)?;
176
+ return Ok(Outcome::Same);
177
+ }
178
+ if update {
179
+ std::fs::write(reference, bytes)?;
180
+ remove_if_present(&pending)?;
181
+ } else {
182
+ std::fs::write(&pending, bytes)?;
183
+ }
184
+ Ok(Outcome::Changed)
185
+ }
186
+
187
+ /// `invoice.pdf` → `invoice.new.pdf`, in the same folder.
188
+ fn pending_path(reference: &Path) -> PathBuf {
189
+ let stem = reference.file_stem().unwrap_or_default().to_string_lossy();
190
+ reference.with_file_name(format!("{stem}.new.pdf"))
191
+ }
192
+
193
+ fn remove_if_present(path: &Path) -> std::io::Result<()> {
194
+ match std::fs::remove_file(path) {
195
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
196
+ result => result,
197
+ }
198
+ }
199
+ ````
200
+
201
+ On the first run there is no reference yet, so the function writes one, answers `created`, and the test fails. Open that PDF before you commit it: the test only proves that later runs match it, so it has to be right. After that, every run answers `same` until the output changes. A reference you forget to commit fails the test on every other machine too, since a fresh checkout has none.
202
+
203
+ When the bytes change, the reference stays as it was and the new PDF goes beside it as `<name>.new.pdf`. Open both, and if the change is the one you meant, run the tests again with `update` set, for example from an environment variable such as `UPDATE_REFERENCES=1`. Commit the new reference with the change that caused it.
204
+
205
+ ## See where a changed PDF looks different
206
+
207
+ Different bytes don't always mean different pages: a reordered dictionary or a new producer changes the file but not how it looks. This example draws each page of both files and counts the pixels that differ:
208
+
209
+ `packages/sdk/examples/compare-pages.ts`:
210
+
211
+ ```ts
212
+ // Draws each page of two PDFs and counts the pixels that differ, to show where a changed PDF looks different.
213
+ //
214
+ // Both files are drawn by the same renderer, so its own quirks cancel out. In Node, load it with
215
+ // the module's bytes:
216
+ //
217
+ // const wasm = fileURLToPath(import.meta.resolve("@laminapdf/wasm/lamina_pdf_render_hayro.wasm"));
218
+ // const renderer = await loadRenderer(readFileSync(wasm));
219
+ import {
220
+ bytesSource,
221
+ DEFAULT_LIMITS,
222
+ type RenderBackend,
223
+ type RenderDocument,
224
+ type RenderedTile,
225
+ } from "@laminapdf/render";
226
+ import { initHayro } from "@laminapdf/render-hayro";
227
+
228
+ /** Pixels per point: 2 draws at 144 dpi, enough to see a glyph that moved. Pages too large for the renderer's limits at this scale are drawn smaller. */
229
+ const SCALE = 2;
230
+ /** How far a colour channel (0–255) may move before the pixel counts as changed: absorbs smoothing noise. */
231
+ const TOLERANCE = 8;
232
+
233
+ /** A rectangle in page points, from the page's top-left corner. */
234
+ export interface Box {
235
+ readonly x: number;
236
+ readonly y: number;
237
+ readonly width: number;
238
+ readonly height: number;
239
+ }
240
+
241
+ /** One page: how many pixels differ, and the box around them (`null` when none do). */
242
+ export interface PageDifference {
243
+ readonly page: number;
244
+ readonly pixels: number;
245
+ readonly box: Box | null;
246
+ }
247
+
248
+ export interface Comparison {
249
+ /** The page counts of the first and second file; only the pages both have are compared. */
250
+ readonly pageCounts: readonly [number, number];
251
+ readonly pages: readonly PageDifference[];
252
+ }
253
+
254
+ /** Loads the renderer from the module's bytes, a URL, or (in a browser) the file next to its code. */
255
+ export function loadRenderer(wasm?: Uint8Array | URL): Promise<RenderBackend> {
256
+ return initHayro(wasm);
257
+ }
258
+
259
+ /** Compares every page the two PDFs share. */
260
+ export function comparePages(renderer: RenderBackend, before: Uint8Array, after: Uint8Array): Comparison {
261
+ using a = renderer.open(bytesSource(before));
262
+ using b = renderer.open(bytesSource(after));
263
+ const shared = Math.min(a.pageCount, b.pageCount);
264
+ const pages = Array.from({ length: shared }, (_, page) => comparePage(a, b, page));
265
+ return { pageCounts: [a.pageCount, b.pageCount], pages };
266
+ }
267
+
268
+ function comparePage(a: RenderDocument, b: RenderDocument, page: number): PageDifference {
269
+ const scale = Math.min(fittingScale(a, page), fittingScale(b, page));
270
+ const first = a.render({ page, scale });
271
+ const second = b.render({ page, scale });
272
+ const width = Math.max(first.width, second.width);
273
+ const height = Math.max(first.height, second.height);
274
+ let pixels = 0;
275
+ let [left, top, right, bottom] = [width, height, -1, -1];
276
+ for (let y = 0; y < height; y++) {
277
+ for (let x = 0; x < width; x++) {
278
+ if (!differs(first, second, x, y)) continue;
279
+ pixels++;
280
+ [left, top] = [Math.min(left, x), Math.min(top, y)];
281
+ [right, bottom] = [Math.max(right, x), Math.max(bottom, y)];
282
+ }
283
+ }
284
+ const box =
285
+ pixels === 0
286
+ ? null
287
+ : { x: left / scale, y: top / scale, width: (right - left + 1) / scale, height: (bottom - top + 1) / scale };
288
+ return { page, pixels, box };
289
+ }
290
+
291
+ /** `SCALE`, or less when the page would pass the renderer's pixel or side limit at it. */
292
+ function fittingScale(doc: RenderDocument, page: number): number {
293
+ const { width, height } = doc.page(page);
294
+ const byArea = Math.sqrt(DEFAULT_LIMITS.maxRenderPixels / (width * height));
295
+ const bySide = DEFAULT_LIMITS.maxRenderSide / Math.max(width, height);
296
+ return Math.min(SCALE, byArea, bySide);
297
+ }
298
+
299
+ /** Whether the pixel at (x, y) differs; a pixel only one page has always does. */
300
+ function differs(first: RenderedTile, second: RenderedTile, x: number, y: number): boolean {
301
+ if (x >= first.width || y >= first.height || x >= second.width || y >= second.height) return true;
302
+ const i = (y * first.width + x) * 4;
303
+ const j = (y * second.width + x) * 4;
304
+ for (let channel = 0; channel < 4; channel++) {
305
+ const delta = Math.abs((first.pixels[i + channel] ?? 0) - (second.pixels[j + channel] ?? 0));
306
+ if (delta > TOLERANCE) return true;
307
+ }
308
+ return false;
309
+ }
310
+ ```
311
+
312
+ Each page gets one of two outcomes. With no differing pixels, the file's structure changed but its pages look the same. With some, the box shows where on the page, in points from the top-left corner. The same renderer draws both files, so its own small differences from other viewers cancel out.
313
+
314
+ To read the change itself, build with `compress: false` (`with_compress(false)` in Rust). The content streams then stay readable text, and a diff tool shows what moved. In Rust, the `laminapdf-render` crate's `hayro` feature draws pages the same way.
315
+
316
+ ## Upgrade Lamina PDF
317
+
318
+ Pin the exact Lamina PDF version, so an upgrade happens only when you choose it. A release that changes the bytes written for the same input lists that change first in its changelog. Upgrade in a change of its own, regenerate the references with `update` set, and look at the page comparison before you commit them.
319
+
320
+ ## Keep fonts and edits repeatable
321
+
322
+ Fonts need the same care. The embedded subset depends on the font file's exact bytes, so keep the font your test uses in your own repository rather than loading it from the system. Edited files repeat too: opening a PDF, applying the same edits and saving writes the same bytes each time.
323
+
324
+ The [stability policy](../../pages/reference/stability.md) states the promise these tests rely on: the same input gives the same bytes everywhere.
@@ -0,0 +1,82 @@
1
+ ---
2
+ title: "Open files you don't trust"
3
+ description: "Cap how much work an uploaded PDF can make Lamina PDF do, and refuse damaged files with strict mode."
4
+ version: "0.2.0-alpha.4"
5
+ url: "https://docs.laminapdf.com/guides/untrusted-files/"
6
+ ---
7
+
8
+ # Open files you don't trust
9
+
10
+ Cap how much work an uploaded PDF can make Lamina PDF do, and refuse damaged files with strict mode.
11
+
12
+ You'll open uploads with caps on how much an untrusted file can make Lamina PDF decode, read or draw. You need this on any server or page that opens PDFs from strangers, because a small file can ask for gigabytes of work.
13
+
14
+ This example opens uploads with tighter limits than the defaults: at most 16 MiB decoded from one stream and 64 MiB in all:
15
+
16
+ `packages/sdk/examples/open-untrusted.ts`:
17
+
18
+ ```ts
19
+ // Open bytes from someone else: an optional password, tighter limits than the defaults, and the
20
+ // outcome as a value rather than an exception. The example writes nothing: it returns what it found.
21
+ import { Document, isLaminaPdfError } from "@laminapdf/sdk";
22
+
23
+ const MiB = 1024 * 1024;
24
+ /** Limits for an upload endpoint: each decoded stream, and all of them together. */
25
+ const LIMITS = { maxStreamDecoded: 16 * MiB, maxDecodedTotal: 64 * MiB } as const;
26
+
27
+ /** What opening a file gave: its page count and warning codes, or why it didn't open. */
28
+ export type Opened =
29
+ | { readonly ok: true; readonly pages: number; readonly warnings: readonly string[] }
30
+ | { readonly ok: false; readonly code: string; readonly hint: string | undefined };
31
+
32
+ /** Opens `bytes`, with `password` if the file needs one, under {@link LIMITS}. Needs `init()`. */
33
+ export function openUntrusted(bytes: Uint8Array, password?: string): Opened {
34
+ try {
35
+ using doc = Document.open(bytes, { ...(password === undefined ? {} : { password }), limits: LIMITS });
36
+ // Repairs and dropped values are warnings: the file opened, but not exactly as written.
37
+ return { ok: true, pages: doc.pageCount, warnings: doc.warnings.map((w) => w.code) };
38
+ } catch (e) {
39
+ // `password-required`, `wrong-password`, `not-pdf`, `limit-exceeded`, …: a coded error whose
40
+ // message and hint never contain text from the file. Anything else is a bug: let it throw.
41
+ if (isLaminaPdfError(e)) return { ok: false, code: e.code, hint: e.hint };
42
+ throw e;
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Set only the limits you need
48
+
49
+ That work takes many forms: a stream that inflates to gigabytes, a page tree thousands of levels deep, drawings nested inside drawings. Lamina PDF keeps every file within the `limits` you pass when you open it. Every limit has a safe default, so you set only the ones you want to change.
50
+
51
+ ## Hard and soft limits
52
+
53
+ - **Hard limits** stop the call with `limit-exceeded`. Its `data.limit` names the limit, so you can tell the user which one.
54
+ - **Soft limits** drop the excess with a warning and carry on.
55
+
56
+ | Limit | Kind | Default | What it caps |
57
+ | --- | --- | --- | --- |
58
+ | `maxNestingDepth` | hard | 256 | arrays and dictionaries inside each other; saved graphics states (`q`) |
59
+ | `maxStreamDecoded` | hard | 64 MiB | bytes one stream decodes to, per filter stage |
60
+ | `maxDecodedTotal` | hard | 1 GiB | bytes one call (`open`, one page's content, one read) decodes, across all streams |
61
+ | `maxFilterChain` | hard | 8 | filters on one stream |
62
+ | `maxResolveDepth` | hard | 32 | indirect objects in flight while following a chain of references |
63
+ | `maxPageTreeDepth` | hard | 256 | `/Kids` levels below the page-tree root (a deeper subtree is skipped with a warning) |
64
+ | `maxTreeDepth` | hard | 64 | name and number tree levels, outline depth |
65
+ | `maxStringLength` | hard | 16 MiB | bytes in one string or name |
66
+ | `maxXrefSections` | soft | 4,096 | `/Prev` hops in the [cross-reference table](../../pages/reference/glossary.md#cross-reference-table) chain (past it, Lamina PDF rebuilds the table by scanning) |
67
+ | `maxObjectNumber` | soft | 8,388,607 | the highest object number kept |
68
+ | `maxWarnings` | soft | 1,000 | warnings kept per document (then one `warnings-truncated`) |
69
+ | `maxContentOperators` | hard | 10,000,000 | drawing operators run in one content call, counting a form's each time it is drawn |
70
+ | `maxXobjectDepth` | hard | 32 | forms drawn inside forms |
71
+ | `maxImagePixels` | hard (reserved) | 2²⁸ | pixels of one image |
72
+ | `maxReadWindow` | hard | 256 MiB | one read from a range source (`Document.openRange`) |
73
+
74
+ These are the TypeScript names. In Rust they're builder methods, such as `Limits::new().with_max_stream_decoded(…)`.
75
+
76
+ ## Refuse damaged files
77
+
78
+ Lamina PDF repairs damaged files as it opens them and reports each repair as a warning. Pass `strict: true` to refuse any file that needs repair instead. It then fails with `strict`.
79
+
80
+ A file on someone else's server can also decide how much you download. [Open it by ranges](../../pages/guides/open-large-files.md) and cap the bytes it may fetch with `budget`.
81
+
82
+ Next: [keep the page responsive](../../pages/guides/keep-the-page-responsive.md).
@@ -0,0 +1,33 @@
1
+ ---
2
+ title: "Lamina PDF"
3
+ description: "Create, read, edit and save PDFs from Rust and TypeScript, with one Rust core that runs natively and as WebAssembly."
4
+ version: "0.2.0-alpha.4"
5
+ url: "https://docs.laminapdf.com/"
6
+ ---
7
+
8
+ # Lamina PDF
9
+
10
+ Create, read, edit and save PDFs from Rust and TypeScript, with one Rust core that runs natively and as WebAssembly.
11
+
12
+ Lamina PDF creates, reads, edits and saves PDFs. One Rust core does the work, and it runs natively and as WebAssembly in browsers, Node, Deno, Bun and Cloudflare Workers. It's for apps that make documents, such as invoices, reports and tickets, and for apps that open documents other people send.
13
+
14
+ - **[`laminapdf`](https://crates.io/crates/laminapdf)** on crates.io is the Rust crate. Its [API reference](../pages/reference/rust.md) is on docs.rs.
15
+ - **[`@laminapdf/sdk`](https://www.npmjs.com/package/@laminapdf/sdk)** on npm is the TypeScript API over the WebAssembly build. After one `await init()`, every call is synchronous, and the API is the same in every runtime. Its [API reference](../api/README.md) is on this site.
16
+
17
+ > **Alpha releases:** Both packages are alpha releases, so name the version when you install them: `npm install @laminapdf/sdk@0.2.0-alpha.4` and `cargo add laminapdf@=0.2.0-alpha.4`. [Install](../pages/install.md) lists every published package.
18
+
19
+ ## What works today
20
+
21
+ | Area | What you get | Guide |
22
+ | --- | --- | --- |
23
+ | Generate | Pages, text with shaping, bidi and line breaking, embedded and subset fonts, the standard 14, vectors, colour, images, tagged PDF | [Fonts and text](../pages/guides/fonts-and-text.md), [Images and vectors](../pages/guides/images-and-vectors.md) |
24
+ | Navigate | Links, named destinations, outlines, page labels, Info and XMP metadata | [Links, outlines and metadata](../pages/guides/links-outlines-and-metadata.md) |
25
+ | Edit | Ops in transactions, undo and redo, incremental or full saves that respect signatures | [Edit a PDF](../pages/guides/edit-a-pdf.md), [Save a PDF](../pages/guides/save-a-pdf.md) |
26
+ | Read safely | Repair of damaged files as warnings, resource limits, strict mode, passwords | [Open password-protected files](../pages/guides/password-protected-files.md), [Open files you don't trust](../pages/guides/untrusted-files.md) |
27
+ | Errors | One coded error and warning shape on every surface | [Errors and warnings](../pages/concepts/errors-and-warnings.md) |
28
+
29
+ Every code block on this site is a tested example file, so the code you see is the code that runs. Each example writes the same bytes in Rust and in every JavaScript runtime.
30
+
31
+ ## Where to go next
32
+
33
+ Start with [your first PDF](../pages/start/first-pdf.md), then read [how Lamina PDF thinks about a PDF](../pages/concepts/how-lamina-thinks.md). When you're ready for your own setup, pick [your runtime](../pages/quick-start/browser.md).