@laminapdf/sdk 0.0.0-stage → 0.2.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. package/README.md +243 -2
  2. package/dist/api.d.ts +26 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +12 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/builder.d.ts +335 -0
  7. package/dist/builder.d.ts.map +1 -0
  8. package/dist/builder.js +574 -0
  9. package/dist/builder.js.map +1 -0
  10. package/dist/defined.d.ts +5 -0
  11. package/dist/defined.d.ts.map +1 -0
  12. package/dist/defined.js +7 -0
  13. package/dist/defined.js.map +1 -0
  14. package/dist/document.d.ts +267 -0
  15. package/dist/document.d.ts.map +1 -0
  16. package/dist/document.js +472 -0
  17. package/dist/document.js.map +1 -0
  18. package/dist/entry/node.d.ts +30 -0
  19. package/dist/entry/node.d.ts.map +1 -0
  20. package/dist/entry/node.js +37 -0
  21. package/dist/entry/node.js.map +1 -0
  22. package/dist/entry/workerd.d.ts +30 -0
  23. package/dist/entry/workerd.d.ts.map +1 -0
  24. package/dist/entry/workerd.js +37 -0
  25. package/dist/entry/workerd.js.map +1 -0
  26. package/dist/errors.d.ts +102 -0
  27. package/dist/errors.d.ts.map +1 -0
  28. package/dist/errors.js +131 -0
  29. package/dist/errors.js.map +1 -0
  30. package/dist/explain.d.ts +20 -0
  31. package/dist/explain.d.ts.map +1 -0
  32. package/dist/explain.js +14 -0
  33. package/dist/explain.js.map +1 -0
  34. package/dist/extract.d.ts +57 -0
  35. package/dist/extract.d.ts.map +1 -0
  36. package/dist/extract.js +5 -0
  37. package/dist/extract.js.map +1 -0
  38. package/dist/font.d.ts +73 -0
  39. package/dist/font.d.ts.map +1 -0
  40. package/dist/font.js +133 -0
  41. package/dist/font.js.map +1 -0
  42. package/dist/generated/builder-enums.d.ts +29 -0
  43. package/dist/generated/builder-enums.d.ts.map +1 -0
  44. package/dist/generated/builder-enums.js +80 -0
  45. package/dist/generated/builder-enums.js.map +1 -0
  46. package/dist/generated/codes.d.ts +109 -0
  47. package/dist/generated/codes.d.ts.map +1 -0
  48. package/dist/generated/codes.js +508 -0
  49. package/dist/generated/codes.js.map +1 -0
  50. package/dist/generated/explain.d.ts +6 -0
  51. package/dist/generated/explain.d.ts.map +1 -0
  52. package/dist/generated/explain.js +155 -0
  53. package/dist/generated/explain.js.map +1 -0
  54. package/dist/generated/extract.d.ts +235 -0
  55. package/dist/generated/extract.d.ts.map +1 -0
  56. package/dist/generated/extract.js +4 -0
  57. package/dist/generated/extract.js.map +1 -0
  58. package/dist/index.d.ts +30 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +37 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/init.d.ts +88 -0
  63. package/dist/init.d.ts.map +1 -0
  64. package/dist/init.js +139 -0
  65. package/dist/init.js.map +1 -0
  66. package/dist/internal/handle.d.ts +58 -0
  67. package/dist/internal/handle.d.ts.map +1 -0
  68. package/dist/internal/handle.js +113 -0
  69. package/dist/internal/handle.js.map +1 -0
  70. package/dist/internal/instance.d.ts +56 -0
  71. package/dist/internal/instance.d.ts.map +1 -0
  72. package/dist/internal/instance.js +125 -0
  73. package/dist/internal/instance.js.map +1 -0
  74. package/dist/loader/fetch.d.ts +3 -0
  75. package/dist/loader/fetch.d.ts.map +1 -0
  76. package/dist/loader/fetch.js +2 -0
  77. package/dist/loader/fetch.js.map +1 -0
  78. package/dist/loader/node.d.ts +3 -0
  79. package/dist/loader/node.d.ts.map +1 -0
  80. package/dist/loader/node.js +9 -0
  81. package/dist/loader/node.js.map +1 -0
  82. package/dist/loader/workerd.d.ts +3 -0
  83. package/dist/loader/workerd.d.ts.map +1 -0
  84. package/dist/loader/workerd.js +15 -0
  85. package/dist/loader/workerd.js.map +1 -0
  86. package/dist/matrix.d.ts +16 -0
  87. package/dist/matrix.d.ts.map +1 -0
  88. package/dist/matrix.js +36 -0
  89. package/dist/matrix.js.map +1 -0
  90. package/dist/path.d.ts +34 -0
  91. package/dist/path.d.ts.map +1 -0
  92. package/dist/path.js +49 -0
  93. package/dist/path.js.map +1 -0
  94. package/dist/source/http.d.ts +36 -0
  95. package/dist/source/http.d.ts.map +1 -0
  96. package/dist/source/http.js +211 -0
  97. package/dist/source/http.js.map +1 -0
  98. package/dist/source/range.d.ts +75 -0
  99. package/dist/source/range.d.ts.map +1 -0
  100. package/dist/source/range.js +100 -0
  101. package/dist/source/range.js.map +1 -0
  102. package/dist/text.d.ts +90 -0
  103. package/dist/text.d.ts.map +1 -0
  104. package/dist/text.js +127 -0
  105. package/dist/text.js.map +1 -0
  106. package/dist/transaction.d.ts +36 -0
  107. package/dist/transaction.d.ts.map +1 -0
  108. package/dist/transaction.js +47 -0
  109. package/dist/transaction.js.map +1 -0
  110. package/dist/types.d.ts +79 -0
  111. package/dist/types.d.ts.map +1 -0
  112. package/dist/types.js +2 -0
  113. package/dist/types.js.map +1 -0
  114. package/dist/version.d.ts +3 -0
  115. package/dist/version.d.ts.map +1 -0
  116. package/dist/version.js +6 -0
  117. package/dist/version.js.map +1 -0
  118. package/dist/worker/client.d.ts +88 -0
  119. package/dist/worker/client.d.ts.map +1 -0
  120. package/dist/worker/client.js +407 -0
  121. package/dist/worker/client.js.map +1 -0
  122. package/dist/worker/emitter.d.ts +12 -0
  123. package/dist/worker/emitter.d.ts.map +1 -0
  124. package/dist/worker/emitter.js +34 -0
  125. package/dist/worker/emitter.js.map +1 -0
  126. package/dist/worker/entry.d.ts +2 -0
  127. package/dist/worker/entry.d.ts.map +1 -0
  128. package/dist/worker/entry.js +4 -0
  129. package/dist/worker/entry.js.map +1 -0
  130. package/dist/worker/index.d.ts +7 -0
  131. package/dist/worker/index.d.ts.map +1 -0
  132. package/dist/worker/index.js +4 -0
  133. package/dist/worker/index.js.map +1 -0
  134. package/dist/worker/protocol.d.ts +82 -0
  135. package/dist/worker/protocol.d.ts.map +1 -0
  136. package/dist/worker/protocol.js +11 -0
  137. package/dist/worker/protocol.js.map +1 -0
  138. package/dist/worker/server.d.ts +51 -0
  139. package/dist/worker/server.d.ts.map +1 -0
  140. package/dist/worker/server.js +493 -0
  141. package/dist/worker/server.js.map +1 -0
  142. package/dist/worker/support.d.ts +43 -0
  143. package/dist/worker/support.d.ts.map +1 -0
  144. package/dist/worker/support.js +26 -0
  145. package/dist/worker/support.js.map +1 -0
  146. package/dist/worker/transfer.d.ts +3 -0
  147. package/dist/worker/transfer.d.ts.map +1 -0
  148. package/dist/worker/transfer.js +13 -0
  149. package/dist/worker/transfer.js.map +1 -0
  150. package/dist/worker/types.d.ts +50 -0
  151. package/dist/worker/types.d.ts.map +1 -0
  152. package/dist/worker/types.js +2 -0
  153. package/dist/worker/types.js.map +1 -0
  154. package/docs/AGENTS.md +14 -0
  155. package/docs/api/README.md +147 -0
  156. package/docs/api/classes/Builder.md +467 -0
  157. package/docs/api/classes/Document.md +742 -0
  158. package/docs/api/classes/Font.md +74 -0
  159. package/docs/api/classes/Image.md +140 -0
  160. package/docs/api/classes/LaminaPdfError.md +189 -0
  161. package/docs/api/classes/Page.md +178 -0
  162. package/docs/api/classes/PageRef.md +253 -0
  163. package/docs/api/classes/PageText.md +129 -0
  164. package/docs/api/classes/PageTextRun.md +167 -0
  165. package/docs/api/classes/PendingPath.md +97 -0
  166. package/docs/api/classes/SaveJob.md +205 -0
  167. package/docs/api/classes/Surface.md +326 -0
  168. package/docs/api/classes/Transaction.md +111 -0
  169. package/docs/api/functions/callbackSource.md +33 -0
  170. package/docs/api/functions/httpRangeSource.md +48 -0
  171. package/docs/api/functions/init.md +36 -0
  172. package/docs/api/functions/isExtractLoaded.md +21 -0
  173. package/docs/api/functions/isInitialized.md +19 -0
  174. package/docs/api/functions/isLaminaPdfError.md +41 -0
  175. package/docs/api/functions/isTextLoaded.md +19 -0
  176. package/docs/api/functions/loadExtract.md +28 -0
  177. package/docs/api/functions/loadText.md +28 -0
  178. package/docs/api/functions/onLeak.md +29 -0
  179. package/docs/api/functions/reset.md +22 -0
  180. package/docs/api/functions/wasmMemory.md +22 -0
  181. package/docs/api/interfaces/BuilderOptions.md +108 -0
  182. package/docs/api/interfaces/ByteRange.md +29 -0
  183. package/docs/api/interfaces/ChunkOptions.md +37 -0
  184. package/docs/api/interfaces/Diagnostic.md +67 -0
  185. package/docs/api/interfaces/ExtractOptions.md +92 -0
  186. package/docs/api/interfaces/ExtractResult.md +37 -0
  187. package/docs/api/interfaces/FontInfo.md +37 -0
  188. package/docs/api/interfaces/HttpRangeOptions.md +78 -0
  189. package/docs/api/interfaces/InitOptions.md +40 -0
  190. package/docs/api/interfaces/Leak.md +21 -0
  191. package/docs/api/interfaces/Location.md +53 -0
  192. package/docs/api/interfaces/OpLog.md +25 -0
  193. package/docs/api/interfaces/OpenOptions.md +37 -0
  194. package/docs/api/interfaces/PageTextOptions.md +21 -0
  195. package/docs/api/interfaces/PathBuilder.md +195 -0
  196. package/docs/api/interfaces/QuadPoint.md +25 -0
  197. package/docs/api/interfaces/RangeOptions.md +54 -0
  198. package/docs/api/interfaces/RangeSource.md +59 -0
  199. package/docs/api/interfaces/StrokeStyle.md +61 -0
  200. package/docs/api/interfaces/TagSpec.md +69 -0
  201. package/docs/api/interfaces/TextBoxOptions.md +93 -0
  202. package/docs/api/interfaces/TextGlyph.md +29 -0
  203. package/docs/api/interfaces/TextOptions.md +101 -0
  204. package/docs/api/interfaces/WasmMemory.md +37 -0
  205. package/docs/api/type-aliases/AfRelationship.md +15 -0
  206. package/docs/api/type-aliases/Align.md +15 -0
  207. package/docs/api/type-aliases/ArtifactKind.md +15 -0
  208. package/docs/api/type-aliases/AttachmentOptions.md +15 -0
  209. package/docs/api/type-aliases/BlendMode.md +15 -0
  210. package/docs/api/type-aliases/BoxRect.md +15 -0
  211. package/docs/api/type-aliases/BuildOptions.md +15 -0
  212. package/docs/api/type-aliases/Cell.md +71 -0
  213. package/docs/api/type-aliases/Chunk.md +79 -0
  214. package/docs/api/type-aliases/Color.md +16 -0
  215. package/docs/api/type-aliases/Coordinates.md +55 -0
  216. package/docs/api/type-aliases/Direction.md +15 -0
  217. package/docs/api/type-aliases/DocumentInfo.md +15 -0
  218. package/docs/api/type-aliases/DropReason.md +15 -0
  219. package/docs/api/type-aliases/Dropped.md +59 -0
  220. package/docs/api/type-aliases/Edited.md +15 -0
  221. package/docs/api/type-aliases/ErrorCode.md +15 -0
  222. package/docs/api/type-aliases/ErrorKind.md +15 -0
  223. package/docs/api/type-aliases/ExtractFormat.md +15 -0
  224. package/docs/api/type-aliases/ExtractedBlock.md +82 -0
  225. package/docs/api/type-aliases/ExtractedPage.md +65 -0
  226. package/docs/api/type-aliases/ExtractedSource.md +48 -0
  227. package/docs/api/type-aliases/Extraction.md +22 -0
  228. package/docs/api/type-aliases/FillRule.md +15 -0
  229. package/docs/api/type-aliases/Fit.md +15 -0
  230. package/docs/api/type-aliases/Flags.md +15 -0
  231. package/docs/api/type-aliases/Generator.md +41 -0
  232. package/docs/api/type-aliases/GlyphQuad.md +15 -0
  233. package/docs/api/type-aliases/InfoPatch.md +15 -0
  234. package/docs/api/type-aliases/LaminaPdfWarning.md +16 -0
  235. package/docs/api/type-aliases/LimitName.md +15 -0
  236. package/docs/api/type-aliases/Limits.md +15 -0
  237. package/docs/api/type-aliases/LineBox.md +15 -0
  238. package/docs/api/type-aliases/LineCap.md +15 -0
  239. package/docs/api/type-aliases/LineJoin.md +15 -0
  240. package/docs/api/type-aliases/LinkTarget.md +15 -0
  241. package/docs/api/type-aliases/Metadata.md +15 -0
  242. package/docs/api/type-aliases/Op.md +15 -0
  243. package/docs/api/type-aliases/OutlineEntry.md +15 -0
  244. package/docs/api/type-aliases/Overflow.md +15 -0
  245. package/docs/api/type-aliases/PageInfo.md +15 -0
  246. package/docs/api/type-aliases/PageLabel.md +15 -0
  247. package/docs/api/type-aliases/PageSizeName.md +15 -0
  248. package/docs/api/type-aliases/Point.md +15 -0
  249. package/docs/api/type-aliases/Profile.md +15 -0
  250. package/docs/api/type-aliases/Progress.md +15 -0
  251. package/docs/api/type-aliases/ReadingOrder.md +15 -0
  252. package/docs/api/type-aliases/Rect.md +15 -0
  253. package/docs/api/type-aliases/Region.md +31 -0
  254. package/docs/api/type-aliases/RenderMode.md +15 -0
  255. package/docs/api/type-aliases/Rotation.md +15 -0
  256. package/docs/api/type-aliases/SaveOptions.md +15 -0
  257. package/docs/api/type-aliases/SaveReport.md +15 -0
  258. package/docs/api/type-aliases/Span.md +63 -0
  259. package/docs/api/type-aliases/SplitOn.md +15 -0
  260. package/docs/api/type-aliases/Standard14.md +15 -0
  261. package/docs/api/type-aliases/StructRef.md +41 -0
  262. package/docs/api/type-aliases/StructSource.md +17 -0
  263. package/docs/api/type-aliases/TableDetector.md +15 -0
  264. package/docs/api/type-aliases/TableMode.md +15 -0
  265. package/docs/api/type-aliases/TableScope.md +15 -0
  266. package/docs/api/type-aliases/Tag.md +15 -0
  267. package/docs/api/type-aliases/TagName.md +15 -0
  268. package/docs/api/type-aliases/TextLayout.md +15 -0
  269. package/docs/api/type-aliases/TextRange.md +33 -0
  270. package/docs/api/type-aliases/TextRunInfo.md +15 -0
  271. package/docs/api/type-aliases/Transform.md +15 -0
  272. package/docs/api/type-aliases/UndoOutcome.md +15 -0
  273. package/docs/api/type-aliases/ViewerPreferences.md +15 -0
  274. package/docs/api/type-aliases/WarningCode.md +15 -0
  275. package/docs/api/type-aliases/WasmSource.md +16 -0
  276. package/docs/api/variables/Matrix.md +99 -0
  277. package/docs/api/variables/PAGE_SIZES.md +15 -0
  278. package/docs/api/variables/TAG_NAMES.md +15 -0
  279. package/docs/errors/README.md +156 -0
  280. package/docs/errors/aes-padding-invalid.md +34 -0
  281. package/docs/errors/already-initialized.md +34 -0
  282. package/docs/errors/binary-output-to-terminal.md +35 -0
  283. package/docs/errors/builder-finished.md +35 -0
  284. package/docs/errors/cancelled.md +35 -0
  285. package/docs/errors/cmap-invalid.md +42 -0
  286. package/docs/errors/collection-index.md +33 -0
  287. package/docs/errors/color-components.md +33 -0
  288. package/docs/errors/content-bad-operands.md +36 -0
  289. package/docs/errors/content-path-unpainted.md +36 -0
  290. package/docs/errors/content-resource-missing.md +37 -0
  291. package/docs/errors/content-syntax.md +36 -0
  292. package/docs/errors/content-unbalanced-bt.md +36 -0
  293. package/docs/errors/content-unbalanced-marked.md +36 -0
  294. package/docs/errors/content-unbalanced-q.md +36 -0
  295. package/docs/errors/content-unknown-operator.md +37 -0
  296. package/docs/errors/data-pending.md +34 -0
  297. package/docs/errors/decompress.md +34 -0
  298. package/docs/errors/device-color-without-output-intent.md +33 -0
  299. package/docs/errors/direct-page.md +34 -0
  300. package/docs/errors/disposed.md +34 -0
  301. package/docs/errors/document-changed.md +34 -0
  302. package/docs/errors/embedded-file-forbidden.md +35 -0
  303. package/docs/errors/embedded-file-without-mime-type.md +36 -0
  304. package/docs/errors/encoding-full.md +34 -0
  305. package/docs/errors/encrypted-base.md +33 -0
  306. package/docs/errors/extract-limit-blocks.md +35 -0
  307. package/docs/errors/extract-module-not-loaded.md +37 -0
  308. package/docs/errors/extract-reading-order-ambiguous.md +32 -0
  309. package/docs/errors/extract-struct-tree-ignored.md +32 -0
  310. package/docs/errors/extract-table-ambiguous.md +32 -0
  311. package/docs/errors/extract-table-limit.md +35 -0
  312. package/docs/errors/extract-untagged-content.md +32 -0
  313. package/docs/errors/fetch-failed.md +34 -0
  314. package/docs/errors/flate-truncated.md +34 -0
  315. package/docs/errors/font-invalid.md +36 -0
  316. package/docs/errors/font-mismatch.md +34 -0
  317. package/docs/errors/font-not-embeddable.md +34 -0
  318. package/docs/errors/font-not-embedded.md +35 -0
  319. package/docs/errors/font-not-shapable.md +33 -0
  320. package/docs/errors/glyph-out-of-range.md +34 -0
  321. package/docs/errors/glyph-without-unicode.md +35 -0
  322. package/docs/errors/heading-level-skipped.md +36 -0
  323. package/docs/errors/image-data-length.md +33 -0
  324. package/docs/errors/image-too-large.md +32 -0
  325. package/docs/errors/inline-image-unterminated.md +36 -0
  326. package/docs/errors/input-unreadable.md +37 -0
  327. package/docs/errors/instance-reset.md +34 -0
  328. package/docs/errors/invalid-argument.md +33 -0
  329. package/docs/errors/invalid-axis-value.md +32 -0
  330. package/docs/errors/invalid-box.md +34 -0
  331. package/docs/errors/invalid-color.md +32 -0
  332. package/docs/errors/invalid-command-line.md +44 -0
  333. package/docs/errors/invalid-date.md +34 -0
  334. package/docs/errors/invalid-entry.md +34 -0
  335. package/docs/errors/invalid-feature.md +32 -0
  336. package/docs/errors/invalid-font.md +32 -0
  337. package/docs/errors/invalid-geometry.md +32 -0
  338. package/docs/errors/invalid-icc-profile.md +32 -0
  339. package/docs/errors/invalid-image.md +33 -0
  340. package/docs/errors/invalid-op-log.md +33 -0
  341. package/docs/errors/invalid-page-label.md +34 -0
  342. package/docs/errors/invalid-page-labels.md +32 -0
  343. package/docs/errors/invalid-page-range.md +37 -0
  344. package/docs/errors/invalid-rotate.md +34 -0
  345. package/docs/errors/invalid-structure-nesting.md +38 -0
  346. package/docs/errors/invalid-structure.md +34 -0
  347. package/docs/errors/invalid-text-range.md +33 -0
  348. package/docs/errors/job-finished.md +34 -0
  349. package/docs/errors/limit-exceeded.md +38 -0
  350. package/docs/errors/link-without-contents.md +33 -0
  351. package/docs/errors/link-without-structure.md +37 -0
  352. package/docs/errors/missing-alt-text.md +33 -0
  353. package/docs/errors/missing-glyph.md +33 -0
  354. package/docs/errors/missing-header.md +37 -0
  355. package/docs/errors/missing-lang.md +32 -0
  356. package/docs/errors/missing-root.md +34 -0
  357. package/docs/errors/missing-title.md +32 -0
  358. package/docs/errors/name-tree-node-skipped.md +34 -0
  359. package/docs/errors/nested-transaction.md +34 -0
  360. package/docs/errors/no-pending-save.md +32 -0
  361. package/docs/errors/no-transaction.md +33 -0
  362. package/docs/errors/not-initialized.md +35 -0
  363. package/docs/errors/not-pdf.md +33 -0
  364. package/docs/errors/not-rebased.md +35 -0
  365. package/docs/errors/object-number-out-of-range.md +38 -0
  366. package/docs/errors/objstm-index-mismatch.md +36 -0
  367. package/docs/errors/out-of-memory.md +38 -0
  368. package/docs/errors/outline-cycle.md +34 -0
  369. package/docs/errors/outline-item-skipped.md +34 -0
  370. package/docs/errors/output-unwritable.md +37 -0
  371. package/docs/errors/overwrite-refused.md +36 -0
  372. package/docs/errors/page-count-mismatch.md +34 -0
  373. package/docs/errors/page-index-out-of-range.md +34 -0
  374. package/docs/errors/page-tree-node-skipped.md +35 -0
  375. package/docs/errors/password-required.md +36 -0
  376. package/docs/errors/path-outside-roots.md +32 -0
  377. package/docs/errors/perms-mismatch.md +34 -0
  378. package/docs/errors/profile-version-conflict.md +35 -0
  379. package/docs/errors/reference-cycle.md +35 -0
  380. package/docs/errors/render-backend-unavailable.md +32 -0
  381. package/docs/errors/render-failed.md +33 -0
  382. package/docs/errors/render-too-large.md +36 -0
  383. package/docs/errors/repaired-base.md +34 -0
  384. package/docs/errors/requires-rewrite.md +38 -0
  385. package/docs/errors/saved-not-rebased.md +34 -0
  386. package/docs/errors/sink-not-empty.md +33 -0
  387. package/docs/errors/sink.md +31 -0
  388. package/docs/errors/source-budget-exceeded.md +34 -0
  389. package/docs/errors/source-changed.md +33 -0
  390. package/docs/errors/source.md +30 -0
  391. package/docs/errors/stale-generation.md +34 -0
  392. package/docs/errors/stream-length-recovered.md +37 -0
  393. package/docs/errors/strict.md +34 -0
  394. package/docs/errors/subset-failed.md +31 -0
  395. package/docs/errors/syntax.md +34 -0
  396. package/docs/errors/text-module-not-loaded.md +36 -0
  397. package/docs/errors/text-overflow.md +34 -0
  398. package/docs/errors/tounicode-conflict.md +32 -0
  399. package/docs/errors/type.md +34 -0
  400. package/docs/errors/unexpected-eof.md +34 -0
  401. package/docs/errors/unknown-axis.md +33 -0
  402. package/docs/errors/unknown-code.md +36 -0
  403. package/docs/errors/unknown-destination.md +33 -0
  404. package/docs/errors/unknown-font.md +33 -0
  405. package/docs/errors/unknown-option.md +33 -0
  406. package/docs/errors/unknown-page.md +34 -0
  407. package/docs/errors/unmapped-glyph.md +36 -0
  408. package/docs/errors/unnumbered-heading-misused.md +34 -0
  409. package/docs/errors/unset-object.md +30 -0
  410. package/docs/errors/unsupported-filter.md +34 -0
  411. package/docs/errors/unsupported-image.md +33 -0
  412. package/docs/errors/unsupported-layout-option.md +33 -0
  413. package/docs/errors/unsupported-security-handler.md +34 -0
  414. package/docs/errors/unsupported-version.md +32 -0
  415. package/docs/errors/untagged-content.md +35 -0
  416. package/docs/errors/warnings-truncated.md +35 -0
  417. package/docs/errors/wasm-trap.md +37 -0
  418. package/docs/errors/whole-font-rebuilt.md +30 -0
  419. package/docs/errors/worker-crashed.md +33 -0
  420. package/docs/errors/worker-failed.md +32 -0
  421. package/docs/errors/worker-version-mismatch.md +34 -0
  422. package/docs/errors/would-invalidate-signatures.md +38 -0
  423. package/docs/errors/wrong-document.md +33 -0
  424. package/docs/errors/wrong-password.md +34 -0
  425. package/docs/errors/xobject-cycle.md +36 -0
  426. package/docs/errors/xref-repaired.md +36 -0
  427. package/docs/errors/xrefstm-ignored.md +34 -0
  428. package/docs/llms.txt +88 -0
  429. package/docs/pages/concepts/dependencies.md +44 -0
  430. package/docs/pages/concepts/errors-and-warnings.md +64 -0
  431. package/docs/pages/concepts/how-lamina-thinks.md +86 -0
  432. package/docs/pages/concepts/pages-and-coordinates.md +67 -0
  433. package/docs/pages/guides/accessible-pdfs.md +260 -0
  434. package/docs/pages/guides/archival-pdfs.md +429 -0
  435. package/docs/pages/guides/edit-a-pdf.md +137 -0
  436. package/docs/pages/guides/fonts-and-text.md +202 -0
  437. package/docs/pages/guides/images-and-vectors.md +219 -0
  438. package/docs/pages/guides/keep-the-page-responsive.md +81 -0
  439. package/docs/pages/guides/links-outlines-and-metadata.md +222 -0
  440. package/docs/pages/guides/password-protected-files.md +70 -0
  441. package/docs/pages/guides/save-a-pdf.md +144 -0
  442. package/docs/pages/guides/untrusted-files.md +80 -0
  443. package/docs/pages/index.md +33 -0
  444. package/docs/pages/install.md +75 -0
  445. package/docs/pages/quick-start/browser.md +67 -0
  446. package/docs/pages/quick-start/bun.md +60 -0
  447. package/docs/pages/quick-start/deno.md +58 -0
  448. package/docs/pages/quick-start/node.md +61 -0
  449. package/docs/pages/quick-start/rust.md +58 -0
  450. package/docs/pages/quick-start/workers.md +139 -0
  451. package/docs/pages/recipes/cloudflare-worker.md +115 -0
  452. package/docs/pages/recipes/extract-markdown.md +114 -0
  453. package/docs/pages/recipes/generate-then-edit.md +135 -0
  454. package/docs/pages/recipes/hello.md +94 -0
  455. package/docs/pages/recipes/images-and-vectors.md +209 -0
  456. package/docs/pages/recipes/invoice.md +226 -0
  457. package/docs/pages/recipes/links-and-outlines.md +215 -0
  458. package/docs/pages/recipes/multilingual.md +189 -0
  459. package/docs/pages/recipes/open-untrusted.md +65 -0
  460. package/docs/pages/recipes/pdf-a-archive.md +205 -0
  461. package/docs/pages/recipes/pdf-a3-invoice-with-xml.md +222 -0
  462. package/docs/pages/recipes/pdf-ua-report.md +237 -0
  463. package/docs/pages/recipes/report.md +125 -0
  464. package/docs/pages/recipes/web-worker-client.md +64 -0
  465. package/docs/pages/recipes.md +47 -0
  466. package/docs/pages/reference/changelog.md +86 -0
  467. package/docs/pages/reference/codes.md +14 -0
  468. package/docs/pages/reference/glossary.md +108 -0
  469. package/docs/pages/reference/rust.md +36 -0
  470. package/docs/pages/reference/stability.md +59 -0
  471. package/docs/pages/start/coding-agents.md +45 -0
  472. package/docs/pages/start/first-pdf.md +77 -0
  473. package/docs/pages/troubleshooting.md +74 -0
  474. package/docs/renames.json +86 -0
  475. package/docs/schemas/error.schema.json +305 -0
  476. package/docs/schemas/renames.schema.json +29 -0
  477. package/package.json +93 -3
  478. package/schemas/error.schema.json +305 -0
@@ -0,0 +1,65 @@
1
+ ---
2
+ title: "Open someone else's file"
3
+ description: "Open an uploaded PDF with an optional password and tighter limits, and get its page count and warnings, or the error, back as a value."
4
+ version: "0.2.0-alpha.3"
5
+ url: "https://docs.laminapdf.com/recipes/open-untrusted/"
6
+ ---
7
+
8
+ # Open someone else's file
9
+
10
+ Open an uploaded PDF with an optional password and tighter limits, and get its page count and warnings, or the error, back as a value.
11
+
12
+ You'll open a PDF from someone else, with a password if it needs one and tighter limits than the defaults, and get the outcome as a value. You need this wherever you accept uploads: a stranger's file can be damaged, locked or built to use up memory.
13
+
14
+ The function opens the bytes under its limits and returns the page count and warning codes, or the error's code and hint:
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
+ It writes no PDF. The value it returns is the result: `{ ok: true, pages, warnings }` for a file that opened, or `{ ok: false, code, hint }`, such as `password-required` for a locked file opened without its password.
48
+
49
+ ## How it works
50
+
51
+ - It needs only `init()`: opening a file uses the base module, without fonts or text.
52
+ - `Document.open` gets the password only when you pass one, and tries it against both of the file's passwords. See [Open password-protected files](../../pages/guides/password-protected-files.md).
53
+ - `LIMITS` allows 16 MiB decoded from one stream and 64 MiB in all, against defaults of 64 MiB and 1 GiB. A file that needs more fails with `limit-exceeded`, and every other limit keeps its default.
54
+ - A file can open and still carry warnings: each one names a repair or a dropped value, and the function returns their codes.
55
+ - `isLaminaPdfError(e)` picks out Lamina PDF's coded errors, whose message and hint never contain text from the file. Any other error is a bug, so the function throws it again.
56
+ - `using` frees the document as soon as the function returns, so the WebAssembly side keeps no copy of the file.
57
+
58
+ ## What to change
59
+
60
+ - **Ask for the password again:** on `password-required` or `wrong-password`, ask your user and call `openUntrusted` again with their answer.
61
+ - **Name the limit:** on `limit-exceeded`, return `e.data?.limit` too. It names the limit the file went over as Rust spells it, such as `max_stream_decoded` for `maxStreamDecoded`.
62
+ - **Other limits:** add any limit from the table in [Open files you don't trust](../../pages/guides/untrusted-files.md#hard-and-soft-limits), such as a lower `maxNestingDepth`.
63
+ - **Refuse damaged files:** add `strict: true` to the options, and a file that needs repair fails with `strict` instead of opening with warnings.
64
+
65
+ Next: [save in a Web Worker](../../pages/recipes/web-worker-client.md), so a large file never freezes the page.
@@ -0,0 +1,205 @@
1
+ ---
2
+ title: "A PDF/A archive"
3
+ description: "An order confirmation for long-term archiving, written as PDF/A-2b and as PDF/A-2u that is also PDF/UA-1, with an embedded font and a photo."
4
+ version: "0.2.0-alpha.3"
5
+ url: "https://docs.laminapdf.com/recipes/pdf-a-archive/"
6
+ ---
7
+
8
+ # A PDF/A archive
9
+
10
+ An order confirmation for long-term archiving, written as PDF/A-2b and as PDF/A-2u that is also PDF/UA-1, with an embedded font and a photo.
11
+
12
+ You'll write a letter confirming an order as a PDF/A file, which looks the same in any viewer for decades. You need it for documents kept for years, often by law: contracts, records, letters to customers. The recipe writes the letter twice, at two PDF/A levels.
13
+
14
+ The function takes the level, `2b` or `2u`, and builds the same letter under that level's profiles. In TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
+
16
+ <TabItem label="TypeScript">`packages/sdk/examples/pdf-a-archive.ts`:
17
+
18
+ ```ts
19
+ // An archival letter, written as PDF/A-2b and as PDF/A-2u that is also PDF/UA-1.
20
+ import { Builder, Font, Image, PAGE_SIZES, type Profile, type Surface } from "@laminapdf/sdk";
21
+
22
+ const MARGIN = 56;
23
+ const BAND_HEIGHT = 70;
24
+ const TITLE_BASELINE = 110;
25
+ /** The top of the letter's text and of the photo beside it, and the height of both. */
26
+ const BLOCK_TOP = 130;
27
+ const BLOCK_HEIGHT = 120;
28
+ const LETTER_WIDTH = 300;
29
+ const PHOTO_LEFT = 380;
30
+ const PHOTO_WIDTH = 160;
31
+ const FOOTER_BASELINE = 300;
32
+ const TITLE = 22;
33
+ const BODY = 11;
34
+ const BAND_OPACITY = 0.35;
35
+ const PHOTO_ALT = "A photograph of the sprockets, packed in a box";
36
+ const LETTER =
37
+ "Thank you for your order of 12 sprockets. This letter confirms the delivery date and the price we agreed. " +
38
+ "Keep it: PDF/A files look the same in any viewer, now and in the future.";
39
+
40
+ /** PDF/A-2b, or PDF/A-2u (whose text maps to Unicode) with PDF/UA-1. */
41
+ const PROFILES: Record<"2b" | "2u", Profile[]> = {
42
+ "2b": ["PDF/A-2b"],
43
+ "2u": ["PDF/A-2u", "PDF/UA-1"],
44
+ };
45
+
46
+ /**
47
+ * The letter's bytes. Needs `init({ text: true })`.
48
+ *
49
+ * @param level - `2b` or `2u`.
50
+ * @param notoSans - the bytes of the Noto Sans font file.
51
+ * @param photo - the bytes of a JPEG photo.
52
+ */
53
+ export function pdfAArchive(level: "2b" | "2u", notoSans: Uint8Array, photo: Uint8Array): Uint8Array<ArrayBuffer> {
54
+ using b = new Builder({ profiles: PROFILES[level], tagged: true });
55
+ b.metadata({ title: "Order confirmation 42", author: "Acme Widgets Ltd" });
56
+ b.lang("en-GB");
57
+ const font = Font.fromBytes(notoSans);
58
+ b.font(font);
59
+ const page = b.page("A4");
60
+ draw(page.surface(), font, Image.jpeg(photo));
61
+ page.finish();
62
+ using doc = b.finish();
63
+ return doc.save();
64
+ }
65
+
66
+ function draw(s: Surface, font: Font, photo: Image): void {
67
+ // A translucent band across the top: decoration, and transparency, which PDF/A-2 allows.
68
+ const [width] = PAGE_SIZES.A4;
69
+ s.artifact("layout", (s) => {
70
+ s.save().opacity(BAND_OPACITY, 1).fillColor([0.12, 0.47, 0.78]);
71
+ s.path((p) => p.rect([0, 0, width, BAND_HEIGHT])).fill();
72
+ s.restore();
73
+ });
74
+ s.tag("H1", (s) => s.text("Order confirmation 42", { font, size: TITLE, at: [MARGIN, TITLE_BASELINE] }));
75
+ s.tag("P", (s) => s.textBox(LETTER, { font, size: BODY, box: [MARGIN, BLOCK_TOP, LETTER_WIDTH, BLOCK_HEIGHT] }));
76
+ s.image(photo, [PHOTO_LEFT, BLOCK_TOP, PHOTO_WIDTH, BLOCK_HEIGHT], PHOTO_ALT);
77
+ s.tag("P", (s) =>
78
+ s.text("Acme Widgets Ltd, 1 Example Street, London", { font, size: BODY, at: [MARGIN, FOOTER_BASELINE] }),
79
+ );
80
+ }
81
+ ```</TabItem>
82
+ <TabItem label="Rust">`crates/laminapdf/examples/pdf_a_archive.rs`:
83
+
84
+ ```rust
85
+ //! An archival letter, written as PDF/A-2b and as PDF/A-2u that is also PDF/UA-1.
86
+ //!
87
+ //! Run it with `cargo run -p laminapdf --example pdf_a_archive -- <font.ttf> <photo.jpg> [out-dir]`: it writes
88
+ //! `pdf_a_archive-2b.pdf` and `pdf_a_archive-2u.pdf`.
89
+
90
+ use std::error::Error;
91
+ use std::path::PathBuf;
92
+
93
+ use laminapdf::{
94
+ ArtifactKind, BuildError, BuildOptions, Builder, Color, FillRule, FontHandle, Image, Metadata, PageSize, PointPt,
95
+ Profile, Pt, RectPt, SaveOptions, Surface, TagKind, TextBoxOptions,
96
+ };
97
+
98
+ const MARGIN: f64 = 56.0;
99
+ const BAND_HEIGHT: f64 = 70.0;
100
+ const TITLE_AT: PointPt = PointPt::from_points(MARGIN, 110.0);
101
+ /// The top of the letter's text and of the photo beside it, and the height of both.
102
+ const BLOCK_TOP: f64 = 130.0;
103
+ const BLOCK_HEIGHT: f64 = 120.0;
104
+ const LETTER_WIDTH: f64 = 300.0;
105
+ const PHOTO_LEFT: f64 = 380.0;
106
+ const PHOTO_WIDTH: f64 = 160.0;
107
+ const FOOTER_AT: PointPt = PointPt::from_points(MARGIN, 300.0);
108
+ const TITLE: Pt = Pt::new(22.0);
109
+ const BODY: Pt = Pt::new(11.0);
110
+ const BAND_OPACITY: f64 = 0.35;
111
+ const PHOTO_ALT: &str = "A photograph of the sprockets, packed in a box";
112
+ const LETTER: &str = "Thank you for your order of 12 sprockets. This letter confirms the delivery date and the \
113
+ price we agreed. Keep it: PDF/A files look the same in any viewer, now and in the future.";
114
+
115
+ fn main() -> Result<(), Box<dyn Error>> {
116
+ let mut args = std::env::args().skip(1);
117
+ let usage = "arguments: <font.ttf> <photo.jpg> [out-dir]";
118
+ let font = std::fs::read(args.next().ok_or(usage)?)?;
119
+ let photo = std::fs::read(args.next().ok_or(usage)?)?;
120
+ let out = PathBuf::from(args.next().unwrap_or_else(|| "target/examples-out".into()));
121
+ std::fs::create_dir_all(&out)?;
122
+ let a2b = build(Level::A2b, font.clone(), photo.clone())?;
123
+ std::fs::write(out.join("pdf_a_archive-2b.pdf"), a2b)?;
124
+ let a2u = build(Level::A2u, font, photo)?;
125
+ Ok(std::fs::write(out.join("pdf_a_archive-2u.pdf"), a2u)?)
126
+ }
127
+
128
+ /// Which archive to write.
129
+ #[derive(Debug, Clone, Copy)]
130
+ pub(crate) enum Level {
131
+ /// PDF/A-2b: the letter looks the same everywhere.
132
+ A2b,
133
+ /// PDF/A-2u, whose text maps to Unicode, and PDF/UA-1.
134
+ A2u,
135
+ }
136
+
137
+ /// The letter's bytes, in `font` (a TrueType or OpenType file) with `photo` (a JPEG).
138
+ pub(crate) fn build(level: Level, font: Vec<u8>, photo: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
139
+ let options = match level {
140
+ Level::A2b => BuildOptions::new().with_profile(Profile::PdfA2b),
141
+ Level::A2u => BuildOptions::new()
142
+ .with_profile(Profile::PdfA2u)
143
+ .with_profile(Profile::PdfUa1),
144
+ };
145
+ let mut b = Builder::new(options.with_tagged(true));
146
+ b.metadata(
147
+ Metadata::default()
148
+ .with_title("Order confirmation 42")
149
+ .with_author("Acme Widgets Ltd"),
150
+ );
151
+ b.lang("en-GB");
152
+ let font = b.font_bytes(font)?;
153
+ let photo = Image::jpeg(photo)?;
154
+ let mut page = b.page(PageSize::A4);
155
+ draw(page.surface(), &font, &photo)?;
156
+ page.finish()?;
157
+ let mut doc = b.finish()?;
158
+ Ok(doc.save(&SaveOptions::new())?.bytes)
159
+ }
160
+
161
+ fn draw(s: &mut Surface, font: &FontHandle, photo: &Image) -> Result<(), BuildError> {
162
+ // A translucent band across the top: decoration, and transparency, which PDF/A-2 allows.
163
+ let width = PageSize::A4.width.get();
164
+ s.artifact(ArtifactKind::Layout, |s| {
165
+ s.save()
166
+ .opacity(BAND_OPACITY, 1.0)
167
+ .fill_color(Color::rgb(0.12, 0.47, 0.78));
168
+ s.path(|p| _ = p.rect(RectPt::from_points(0.0, 0.0, width, BAND_HEIGHT)))
169
+ .fill(FillRule::NonZero);
170
+ s.restore();
171
+ Ok(())
172
+ })?;
173
+ s.tag(TagKind::H1, |s| {
174
+ s.text("Order confirmation 42", font, TITLE, TITLE_AT).map(drop)
175
+ })?;
176
+ let body = RectPt::from_points(MARGIN, BLOCK_TOP, LETTER_WIDTH, BLOCK_HEIGHT);
177
+ s.tag(TagKind::P, |s| {
178
+ s.text_box(LETTER, font, BODY, body, &TextBoxOptions::new()).map(drop)
179
+ })?;
180
+ let beside = RectPt::from_points(PHOTO_LEFT, BLOCK_TOP, PHOTO_WIDTH, BLOCK_HEIGHT);
181
+ s.figure(photo, beside, Some(PHOTO_ALT));
182
+ let footer = "Acme Widgets Ltd, 1 Example Street, London";
183
+ s.tag(TagKind::P, |s| s.text(footer, font, BODY, FOOTER_AT).map(drop))
184
+ }
185
+ ```</TabItem>
186
+
187
+ The result is one A4 letter under a translucent blue band, with a photo beside its text: open the [PDF/A-2b](https://docs.laminapdf.com/examples/pdf/pdf_a_archive-2b.pdf) or the [PDF/A-2u](https://docs.laminapdf.com/examples/pdf/pdf_a_archive-2u.pdf) reference PDF.
188
+
189
+ ## How it works
190
+
191
+ - PDF/A-2b makes the letter look the same everywhere. PDF/A-2u also maps every glyph to Unicode, so the text can be searched and copied. That copy is PDF/UA-1 too, so a screen reader can follow it.
192
+ - PDF/A needs every font embedded, so the letter uses Noto Sans rather than one of the [standard 14 fonts](../../pages/reference/glossary.md#standard-14-fonts). `finish()` checks rules like this one and fails with a coded error for the first one the letter breaks.
193
+ - The builder writes what PDF/A asks for: the PDF/A identification in the file's XMP metadata, and an sRGB output intent that says what its RGB colours mean. See [What PDF/A asks for](../../pages/guides/archival-pdfs.md#what-pdfa-asks-for).
194
+ - Both copies draw the same tagged content: an `H1` title, `P` paragraphs and the photo as a figure with alternate text. PDF/A-2b doesn't ask for tags, but PDF/UA-1 does.
195
+ - The blue band is drawn at 35% opacity, as a layout artifact that screen readers skip. PDF/A-2 allows transparency.
196
+ - `textBox` breaks the letter into lines inside its box, beside the photo. The JPEG goes into the file as it is.
197
+
198
+ ## What to change
199
+
200
+ - **The letter:** change `LETTER`. `textBox` leaves out lines that don't fit, so make `BLOCK_HEIGHT` larger for a longer letter, or pass `overflow: "error"` to fail with `text-overflow` instead.
201
+ - **The photo:** pass any RGB or gray JPEG, or `Image.png(bytes)` for a PNG (`Image::png` in Rust). A CMYK JPEG fails `finish()` with `device-color-without-output-intent`, because the output intent is sRGB.
202
+ - **Other profiles:** pass the ones you need, such as `["PDF/A-2u"]` alone or `["PDF/A-2b", "PDF/UA-1"]`. A document can claim PDF/UA-1 and at most one PDF/A profile.
203
+ - **An attached file:** PDF/A-2 refuses attachments, so switch to PDF/A-3b, as the [PDF/A-3 invoice](../../pages/recipes/pdf-a3-invoice-with-xml.md) does.
204
+
205
+ Next: [a PDF/A-3 invoice with XML](../../pages/recipes/pdf-a3-invoice-with-xml.md), with its data attached for software to read.
@@ -0,0 +1,222 @@
1
+ ---
2
+ title: "A PDF/A-3 invoice with XML"
3
+ description: "A PDF/A-3b invoice that carries the same invoice as an attached XML file, for people to read and for software to process."
4
+ version: "0.2.0-alpha.3"
5
+ url: "https://docs.laminapdf.com/recipes/pdf-a3-invoice-with-xml/"
6
+ ---
7
+
8
+ # A PDF/A-3 invoice with XML
9
+
10
+ A PDF/A-3b invoice that carries the same invoice as an attached XML file, for people to read and for software to process.
11
+
12
+ You'll write a one-page invoice as PDF/A-3b, with the same invoice attached as XML. You need this for e-invoices such as Factur-X and ZUGFeRD, where a person reads the PDF and the buyer's software reads the XML.
13
+
14
+ The function draws the invoice, writes the same items as XML and attaches that file before it finishes. In TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
+
16
+ <TabItem label="TypeScript">`packages/sdk/examples/pdf-a3-invoice-with-xml.ts`:
17
+
18
+ ```ts
19
+ // A PDF/A-3b invoice that carries the same invoice as XML, attached for software to read.
20
+ import { Builder, Font, PAGE_SIZES, type Surface } from "@laminapdf/sdk";
21
+
22
+ const MARGIN = 56;
23
+ /** The page's right edge, less the margin: where the rule under the items ends. */
24
+ const RIGHT = PAGE_SIZES.A4[0] - MARGIN;
25
+ const AMOUNTS = 460;
26
+ const ROW = 22;
27
+ const TITLE_BASELINE = 80;
28
+ const FIRST_ROW = 130;
29
+ /**
30
+ * How far above the next row's baseline the rule sits, and how far below it the total and
31
+ * the note are.
32
+ */
33
+ const RULE_RISE = 7;
34
+ const TOTAL_DROP = 18;
35
+ const NOTE_DROP = 68;
36
+ /** How far left of the amounts column the total starts, to fit its "Total" label. */
37
+ const TOTAL_INDENT = 40;
38
+ const TITLE = 24;
39
+ const TOTAL = 14;
40
+ const BODY = 11;
41
+ const ITEMS = [
42
+ ["Sprockets", 12, 1.25],
43
+ ["Gadget", 1, 79.9],
44
+ ["Delivery", 1, 4.5],
45
+ ] as const;
46
+
47
+ /** The invoice's total. */
48
+ function total(): number {
49
+ return ITEMS.reduce((sum, [, qty, price]) => sum + qty * price, 0);
50
+ }
51
+
52
+ /** The invoice as XML, in a made-up schema (a real e-invoice follows EN 16931, as Factur-X does). */
53
+ function invoiceXml(): string {
54
+ const lines = ITEMS.map(
55
+ ([item, qty, price]) => ` <line item="${item}" quantity="${qty}" price="${price.toFixed(2)}"/>\n`,
56
+ );
57
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<invoice number="42" currency="EUR">\n${lines.join("")} <total>${total().toFixed(2)}</total>\n</invoice>\n`;
58
+ }
59
+
60
+ /**
61
+ * The invoice's bytes. Needs `init({ text: true })`.
62
+ *
63
+ * @param notoSans - the bytes of the Noto Sans font file.
64
+ */
65
+ export function pdfA3InvoiceWithXml(notoSans: Uint8Array): Uint8Array<ArrayBuffer> {
66
+ using b = new Builder({ profiles: ["PDF/A-3b"] });
67
+ b.metadata({ title: "Invoice 42", author: "Acme Widgets Ltd" });
68
+ const font = Font.fromBytes(notoSans);
69
+ b.font(font);
70
+ const page = b.page("A4");
71
+ draw(page.surface(), font);
72
+ page.finish();
73
+ b.attach("invoice-42.xml", new TextEncoder().encode(invoiceXml()), "text/xml", {
74
+ relationship: "alternative",
75
+ description: "Invoice 42 as XML",
76
+ });
77
+ using doc = b.finish();
78
+ return doc.save();
79
+ }
80
+
81
+ function draw(s: Surface, font: Font): void {
82
+ s.text("Invoice 42", { font, size: TITLE, at: [MARGIN, TITLE_BASELINE] });
83
+ let y = FIRST_ROW;
84
+ for (const [item, qty, price] of ITEMS) {
85
+ s.text(`${qty} × ${item}`, { font, size: BODY, at: [MARGIN, y] });
86
+ s.text(`€ ${(qty * price).toFixed(2)}`, { font, size: BODY, at: [AMOUNTS, y] });
87
+ y += ROW;
88
+ }
89
+ const rule = y - RULE_RISE;
90
+ s.strokeColor([0.5])
91
+ .path((p) => p.moveTo(MARGIN, rule).lineTo(RIGHT, rule))
92
+ .stroke();
93
+ s.text(`Total € ${total().toFixed(2)}`, { font, size: TOTAL, at: [AMOUNTS - TOTAL_INDENT, y + TOTAL_DROP] });
94
+ s.text("The invoice is attached as invoice-42.xml.", {
95
+ font,
96
+ size: BODY,
97
+ at: [MARGIN, y + NOTE_DROP],
98
+ link: { target: { type: "uri", uri: "https://example.com/invoices/42" }, contents: "Invoice 42 online" },
99
+ });
100
+ }
101
+ ```</TabItem>
102
+ <TabItem label="Rust">`crates/laminapdf/examples/pdf_a3_invoice_with_xml.rs`:
103
+
104
+ ```rust
105
+ //! A PDF/A-3b invoice that carries the same invoice as XML, attached for software to read.
106
+ //!
107
+ //! Run it with `cargo run -p laminapdf --example pdf_a3_invoice_with_xml -- <font.ttf> [out-dir]`.
108
+
109
+ use std::error::Error;
110
+ use std::fmt::Write as _;
111
+ use std::path::PathBuf;
112
+
113
+ use laminapdf::{
114
+ AfRelationship, Attachment, BuildError, BuildOptions, Builder, Color, FontHandle, LinkTarget, Metadata, PageSize,
115
+ PointPt, Profile, Pt, SaveOptions, StrokeStyle, Surface,
116
+ };
117
+
118
+ const MARGIN: f64 = 56.0;
119
+ /// The page's right edge, less the margin: where the rule under the items ends.
120
+ const RIGHT: f64 = PageSize::A4.width.get() - MARGIN;
121
+ const AMOUNTS: f64 = 460.0;
122
+ const ROW: f64 = 22.0;
123
+ const TITLE_BASELINE: f64 = 80.0;
124
+ const FIRST_ROW: f64 = 130.0;
125
+ /// How far above the next row's baseline the rule sits, and how far below it the total and
126
+ /// the note are.
127
+ const RULE_RISE: f64 = 7.0;
128
+ const TOTAL_DROP: f64 = 18.0;
129
+ const NOTE_DROP: f64 = 68.0;
130
+ /// How far left of the amounts column the total starts, to fit its "Total" label.
131
+ const TOTAL_INDENT: f64 = 40.0;
132
+ const TITLE: Pt = Pt::new(24.0);
133
+ const TOTAL: Pt = Pt::new(14.0);
134
+ const BODY: Pt = Pt::new(11.0);
135
+ const ITEMS: [(&str, u32, f64); 3] = [("Sprockets", 12, 1.25), ("Gadget", 1, 79.90), ("Delivery", 1, 4.50)];
136
+
137
+ fn main() -> Result<(), Box<dyn Error>> {
138
+ let mut args = std::env::args().skip(1);
139
+ let font = std::fs::read(args.next().ok_or("arguments: <font.ttf> [out-dir]")?)?;
140
+ let out = PathBuf::from(args.next().unwrap_or_else(|| "target/examples-out".into()));
141
+ std::fs::create_dir_all(&out)?;
142
+ Ok(std::fs::write(out.join("pdf_a3_invoice_with_xml.pdf"), build(font)?)?)
143
+ }
144
+
145
+ /// The invoice's total.
146
+ fn total() -> f64 {
147
+ ITEMS.iter().map(|(_, qty, price)| f64::from(*qty) * price).sum()
148
+ }
149
+
150
+ /// The invoice as XML, in a made-up schema (a real e-invoice follows EN 16931, as Factur-X does).
151
+ fn invoice_xml() -> Result<String, std::fmt::Error> {
152
+ let mut xml =
153
+ String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<invoice number=\"42\" currency=\"EUR\">\n");
154
+ for (item, qty, price) in ITEMS {
155
+ writeln!(xml, " <line item=\"{item}\" quantity=\"{qty}\" price=\"{price:.2}\"/>")?;
156
+ }
157
+ writeln!(xml, " <total>{:.2}</total>\n</invoice>", total())?;
158
+ Ok(xml)
159
+ }
160
+
161
+ /// The invoice's bytes, in `font` (a TrueType or OpenType file).
162
+ pub(crate) fn build(font: Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
163
+ let mut b = Builder::new(BuildOptions::new().with_profile(Profile::PdfA3b));
164
+ b.metadata(
165
+ Metadata::default()
166
+ .with_title("Invoice 42")
167
+ .with_author("Acme Widgets Ltd"),
168
+ );
169
+ let font = b.font_bytes(font)?;
170
+ let mut page = b.page(PageSize::A4);
171
+ draw(page.surface(), &font)?;
172
+ page.finish()?;
173
+ let xml = Attachment::new("invoice-42.xml", invoice_xml()?.into_bytes(), "text/xml")
174
+ .with_relationship(AfRelationship::Alternative)
175
+ .with_description("Invoice 42 as XML");
176
+ b.attach(xml);
177
+ let mut doc = b.finish()?;
178
+ Ok(doc.save(&SaveOptions::new())?.bytes)
179
+ }
180
+
181
+ fn draw(s: &mut Surface, font: &FontHandle) -> Result<(), BuildError> {
182
+ let at = PointPt::from_points;
183
+ s.text("Invoice 42", font, TITLE, at(MARGIN, TITLE_BASELINE))?;
184
+ let mut y = FIRST_ROW;
185
+ for (item, qty, price) in ITEMS {
186
+ s.text(&format!("{qty} × {item}"), font, BODY, at(MARGIN, y))?;
187
+ let amount = f64::from(qty) * price;
188
+ s.text(&format!("€ {amount:.2}"), font, BODY, at(AMOUNTS, y))?;
189
+ y += ROW;
190
+ }
191
+ let rule = y - RULE_RISE;
192
+ s.stroke_color(Color::gray(0.5))
193
+ .path(|p| _ = p.move_to(at(MARGIN, rule)).line_to(at(RIGHT, rule)))
194
+ .stroke(&StrokeStyle::default());
195
+ let total_at = at(AMOUNTS - TOTAL_INDENT, y + TOTAL_DROP);
196
+ s.text(&format!("Total € {:.2}", total()), font, TOTAL, total_at)?;
197
+ let note = "The invoice is attached as invoice-42.xml.";
198
+ let note = s.text(note, font, BODY, at(MARGIN, y + NOTE_DROP))?;
199
+ let online = LinkTarget::Uri("https://example.com/invoices/42".into());
200
+ note.link(online, Some("Invoice 42 online".into()));
201
+ Ok(())
202
+ }
203
+ ```</TabItem>
204
+
205
+ The result is one A4 page with three line items and a total of € 99.40, carrying `invoice-42.xml`: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/pdf_a3_invoice_with_xml.pdf). Viewers that show attachments list it there.
206
+
207
+ ## How it works
208
+
209
+ - `profiles: ["PDF/A-3b"]` (`Profile::PdfA3b` in Rust) asks for PDF/A-2b's rules and allows attached files of any kind. `finish()` fails with a coded error for the first rule the invoice breaks; see [Archival PDFs](../../pages/guides/archival-pdfs.md).
210
+ - PDF/A needs every font embedded, so the invoice uses Noto Sans rather than one of the [standard 14 fonts](../../pages/reference/glossary.md#standard-14-fonts).
211
+ - `invoiceXml` writes the line items and the total from the same `ITEMS` the page draws, so the PDF and the XML always agree. Its schema is made up for the recipe.
212
+ - `attach` adds the file with its name, its bytes and its MIME type, `text/xml`. PDF/A needs a MIME type on every attachment, or `finish()` fails with `embedded-file-without-mime-type`.
213
+ - `relationship: "alternative"` (`AfRelationship::Alternative`) says the XML is another form of the same content, and the builder associates it with the document. `description` is the text a viewer shows beside the file.
214
+
215
+ ## What to change
216
+
217
+ - **The items:** change `ITEMS`. The page, the total and the XML all follow.
218
+ - **A real e-invoice:** replace `invoiceXml` with XML that follows EN 16931, the European e-invoicing standard, as Factur-X and ZUGFeRD do. Those formats also need XMP entries of their own, which the builder can't add yet.
219
+ - **Other attachments:** attach the file the invoice was made from with `relationship: "source"`, or the data behind it with `"data"` (`AfRelationship::Source`, `AfRelationship::Data`). Each needs its own name and MIME type.
220
+ - **Accessible too:** add `"PDF/UA-1"` to `profiles`, set `lang` and tag the content, as the [PDF/UA report](../../pages/recipes/pdf-ua-report.md) does.
221
+
222
+ Next: [a 100-page report](../../pages/recipes/report.md), the case Lamina PDF times in every runtime.