@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,226 @@
1
+ ---
2
+ title: "An invoice"
3
+ description: "A tagged one-page invoice with an embedded font, a table of line items with right-aligned amounts and a total, in TypeScript and Rust."
4
+ version: "0.2.0-alpha.3"
5
+ url: "https://docs.laminapdf.com/recipes/invoice/"
6
+ ---
7
+
8
+ # An invoice
9
+
10
+ A tagged one-page invoice with an embedded font, a table of line items with right-aligned amounts and a total, in TypeScript and Rust.
11
+
12
+ You'll make a one-page invoice: a heading, an address, a table of line items with right-aligned amounts, and a total. It's tagged, so a screen reader reads it in order. Start from it for any document built around a table, such as a quote or a receipt.
13
+
14
+ The function takes a font file, draws the invoice and returns the PDF's bytes. In TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
+
16
+ <TabItem label="TypeScript">`packages/sdk/examples/invoice.ts`:
17
+
18
+ ```ts
19
+ // An invoice that screen readers can follow, with an embedded font and a table of line items.
20
+ // The heading, the address, the table of items and the total are tagged, so a screen reader
21
+ // reads them in order.
22
+ import { Builder, type BuilderOptions, Font, PAGE_SIZES, type Rect, type Surface } from "@laminapdf/sdk";
23
+
24
+ const ITEMS: readonly (readonly [item: string, qty: number, price: number])[] = [
25
+ ["Widgets, blue (W-100)", 3, 12.0],
26
+ ["Gadget with fine-tuned dials", 1, 79.9],
27
+ ["Café-grade sprockets", 12, 1.25],
28
+ ["Delivery", 1, 4.5],
29
+ ];
30
+ const ADDRESS = "Acme Widgets Ltd · 1 Example Street · London";
31
+ const PAGE = "A4";
32
+ const [PAGE_WIDTH] = PAGE_SIZES[PAGE];
33
+ const MARGIN = 56;
34
+ const RIGHT_EDGE = PAGE_WIDTH - MARGIN;
35
+ const TITLE_AT = [MARGIN, 80] as const;
36
+ const ADDRESS_AT = [MARGIN, 100] as const;
37
+ const TITLE = 24;
38
+ const BODY = 11;
39
+ const FIRST_ROW = 160;
40
+ const ROW_HEIGHT = 24;
41
+ /** The quantity's and the amount's columns, each a width ending this far left of the right edge. */
42
+ const COLUMNS = [
43
+ [40, 169],
44
+ [100, 0],
45
+ ] as const;
46
+ /** A cell's box starts this far above its row's baseline and is this tall. */
47
+ const CELL_RISE = 12;
48
+ const CELL_HEIGHT = 16;
49
+
50
+ /**
51
+ * The document's bytes, in `fontBytes` (a TrueType or OpenType file). Needs `init({ text: true })`.
52
+ *
53
+ * @param options - can fix the file ID, the producer and the creation date.
54
+ */
55
+ export function invoice(fontBytes: Uint8Array, options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
56
+ using b = new Builder({ ...options, tagged: true });
57
+ const font = Font.fromBytes(fontBytes);
58
+ const bold = Font.standard("Helvetica-Bold");
59
+ // Registered up front, in this order, so the embedded font is the document's first.
60
+ b.font(font);
61
+ b.font(bold);
62
+ const page = b.page(PAGE);
63
+ const s = page.surface();
64
+ s.text("Invoice 42", { font: bold, size: TITLE, at: TITLE_AT, tag: "H1" });
65
+ s.text(ADDRESS, { font, size: BODY, at: ADDRESS_AT, tag: "P" });
66
+ items(s, font);
67
+ const total = ITEMS.reduce((sum, [, qty, price]) => sum + qty * price, 0);
68
+ const box = cell(COLUMNS[1], rowBaseline(ITEMS.length));
69
+ s.textBox(`Total € ${total.toFixed(2)}`, { font, size: BODY, box, align: "end", tag: "P" });
70
+ page.finish();
71
+ b.metadata({ title: "Invoice 42", author: "Acme Widgets Ltd" }).lang("en-GB");
72
+ using doc = b.finish();
73
+ return doc.save();
74
+ }
75
+
76
+ /** The table of line items: what, how many, and the amount, right-aligned. */
77
+ function items(s: Surface, font: Font): void {
78
+ s.tag("Table", (s) => {
79
+ ITEMS.forEach(([item, qty, price], row) => {
80
+ const y = rowBaseline(row);
81
+ const cells = [
82
+ [String(qty), COLUMNS[0]],
83
+ [`€ ${(qty * price).toFixed(2)}`, COLUMNS[1]],
84
+ ] as const;
85
+ s.tag("TR", (s) => {
86
+ s.text(item, { font, size: BODY, at: [MARGIN, y], tag: "TD" });
87
+ for (const [text, column] of cells) {
88
+ s.textBox(text, { font, size: BODY, box: cell(column, y), align: "end", tag: "TD" });
89
+ }
90
+ });
91
+ });
92
+ });
93
+ }
94
+
95
+ /** The baseline of row `row` of the table. */
96
+ function rowBaseline(row: number): number {
97
+ return FIRST_ROW + ROW_HEIGHT * row;
98
+ }
99
+
100
+ /** The box of a cell in `column` (width, distance from the right edge) on the row at `baseline`. */
101
+ function cell([width, inset]: readonly [number, number], baseline: number): Rect {
102
+ return [RIGHT_EDGE - inset - width, baseline - CELL_RISE, width, CELL_HEIGHT];
103
+ }
104
+ ```</TabItem>
105
+ <TabItem label="Rust">`crates/laminapdf/examples/invoice.rs`:
106
+
107
+ ```rust
108
+ //! An invoice that screen readers can follow, with an embedded font and a table of line items.
109
+ //!
110
+ //! The heading, the address, the table of items and the total are tagged, so a screen reader
111
+ //! reads them in order. Run it with `cargo run -p laminapdf --example invoice -- <font.ttf> [out-dir]`.
112
+
113
+ use laminapdf::{
114
+ Alignment, BuildError, BuildOptions, Builder, FontHandle, Metadata, PageSize, PointPt, Pt, RectPt, SaveOptions,
115
+ Standard14, Surface, TagKind, TextBoxOptions,
116
+ };
117
+
118
+ const ITEMS: [(&str, u32, f64); 4] = [
119
+ ("Widgets, blue (W-100)", 3, 12.00),
120
+ ("Gadget with fine-tuned dials", 1, 79.90),
121
+ ("Café-grade sprockets", 12, 1.25),
122
+ ("Delivery", 1, 4.50),
123
+ ];
124
+ const ADDRESS: &str = "Acme Widgets Ltd · 1 Example Street · London";
125
+ const PAGE: PageSize = PageSize::A4;
126
+ const MARGIN: f64 = 56.0;
127
+ const RIGHT_EDGE: f64 = PAGE.width.get() - MARGIN;
128
+ const TITLE_AT: PointPt = PointPt::from_points(MARGIN, 80.0);
129
+ const ADDRESS_AT: PointPt = PointPt::from_points(MARGIN, 100.0);
130
+ const TITLE: Pt = Pt::new(24.0);
131
+ const BODY: Pt = Pt::new(11.0);
132
+ const FIRST_ROW: f64 = 160.0;
133
+ const ROW_HEIGHT: f64 = 24.0;
134
+ /// The quantity's and the amount's columns, each a width ending this far left of the right edge.
135
+ const COLUMNS: [(f64, f64); 2] = [(40.0, 169.0), (100.0, 0.0)];
136
+ /// A cell's box starts this far above its row's baseline and is this tall.
137
+ const CELL_RISE: f64 = 12.0;
138
+ const CELL_HEIGHT: f64 = 16.0;
139
+
140
+ fn main() -> Result<(), Box<dyn std::error::Error>> {
141
+ let mut args = std::env::args().skip(1);
142
+ let font = std::fs::read(args.next().ok_or("usage: invoice <font.ttf> [out-dir]")?)?;
143
+ let out = std::path::PathBuf::from(args.next().unwrap_or_else(|| "target/examples-out".into()));
144
+ std::fs::create_dir_all(&out)?;
145
+ let pdf = build(BuildOptions::new(), font)?;
146
+ Ok(std::fs::write(out.join("invoice.pdf"), pdf)?)
147
+ }
148
+
149
+ /// The bytes, in `font` (TrueType or OpenType); `options` can fix the file ID, producer and date.
150
+ pub(crate) fn build(options: BuildOptions, font: Vec<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
151
+ let mut b = Builder::new(options.with_tagged(true));
152
+ draw(&mut b, font)?;
153
+ Ok(b.finish()?.save(&SaveOptions::new())?.bytes)
154
+ }
155
+
156
+ /// Draws the invoice's one page into `b` and sets its title, author and language.
157
+ pub(crate) fn draw(b: &mut Builder, font: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {
158
+ let (font, bold) = (b.font_bytes(font)?, b.standard_font(Standard14::HelveticaBold)?);
159
+ let mut page = b.page(PAGE);
160
+ let s = page.surface();
161
+ s.text("Invoice 42", &bold, TITLE, TITLE_AT)?.tag(TagKind::H1);
162
+ s.text(ADDRESS, &font, BODY, ADDRESS_AT)?.tag(TagKind::P);
163
+ items(s, &font)?;
164
+ let total: f64 = ITEMS.iter().map(|(_, qty, price)| f64::from(*qty) * price).sum();
165
+ let (total, below_items) = (format!("Total € {total:.2}"), u32::try_from(ITEMS.len())?);
166
+ let area = cell(COLUMNS[1], row_baseline(below_items));
167
+ s.tag(TagKind::P, |s| right_aligned(s, &font, &total, area))?;
168
+ page.finish()?;
169
+ let metadata = Metadata::default().with_title("Invoice 42");
170
+ b.metadata(metadata.with_author("Acme Widgets Ltd")).lang("en-GB");
171
+ Ok(())
172
+ }
173
+
174
+ /// The table of line items: what, how many, and the amount, right-aligned.
175
+ fn items(s: &mut Surface, font: &FontHandle) -> Result<(), BuildError> {
176
+ s.tag(TagKind::Table, |s| {
177
+ for (row, (item, qty, price)) in (0_u32..).zip(ITEMS) {
178
+ let y = row_baseline(row);
179
+ s.tag(TagKind::TR, |s| {
180
+ s.text(item, font, BODY, PointPt::from_points(MARGIN, y))?
181
+ .tag(TagKind::TD);
182
+ let amount = format!("€ {:.2}", f64::from(qty) * price);
183
+ for (text, column) in [qty.to_string(), amount].iter().zip(COLUMNS) {
184
+ s.tag(TagKind::TD, |s| right_aligned(s, font, text, cell(column, y)))?;
185
+ }
186
+ Ok(())
187
+ })?;
188
+ }
189
+ Ok(())
190
+ })
191
+ }
192
+
193
+ /// The baseline of row `row` of the table.
194
+ fn row_baseline(row: u32) -> f64 {
195
+ FIRST_ROW + ROW_HEIGHT * f64::from(row)
196
+ }
197
+
198
+ /// The box of a cell in `column` (width, distance from the right edge) on the row at `baseline`.
199
+ fn cell((width, inset): (f64, f64), baseline: f64) -> RectPt {
200
+ RectPt::from_points(RIGHT_EDGE - inset - width, baseline - CELL_RISE, width, CELL_HEIGHT)
201
+ }
202
+
203
+ /// `text` in `area`, in the body size, aligned to its right edge.
204
+ fn right_aligned(s: &mut Surface, font: &FontHandle, text: &str, area: RectPt) -> Result<(), BuildError> {
205
+ let right = TextBoxOptions::new().with_align(Alignment::End);
206
+ s.text_box(text, font, BODY, area, &right).map(|_| ())
207
+ }
208
+ ```</TabItem>
209
+
210
+ The result is one A4 page with its body set in Noto Sans: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/invoice.pdf).
211
+
212
+ ## How it works
213
+
214
+ - The body uses your font file, and Lamina PDF embeds only the glyphs the invoice draws. The heading uses Helvetica Bold, one of the [standard 14 fonts](../../pages/reference/glossary.md#standard-14-fonts), so it embeds nothing.
215
+ - Each quantity and amount is a text box aligned to its `end`, so the numbers line up on the right. `cell` works out each box from its column's width and its distance from the right margin.
216
+ - `tagged: true` (`with_tagged(true)` in Rust) gives the document a structure for screen readers. The `tag` option and `s.tag(…)` make the heading an `H1`, the address and the total each a paragraph (`P`), and the items a `Table` of rows (`TR`) and cells (`TD`).
217
+ - `metadata` sets the title and author, and `lang("en-GB")` tells a screen reader which language to speak.
218
+
219
+ ## What to change
220
+
221
+ - **The line items:** edit `ITEMS`. Each row sits `ROW_HEIGHT` points below the one before, and the total follows the last row.
222
+ - **The columns:** each pair in `COLUMNS` is a column's width and its distance from the right margin, in points.
223
+ - **The currency:** change the `€` in the amount and total strings. Your font needs a glyph for the new symbol, or drawing fails with `missing-glyph`. See [Text and fonts](../../pages/guides/fonts-and-text.md).
224
+ - **A PDF/UA-1 invoice:** add `profiles: ["PDF/UA-1"]` (`with_profile(Profile::PdfUa1)` in Rust) to check the rules for accessible PDFs. PDF/UA-1 needs every font embedded, so load a bold font file for the heading. See [Accessible PDFs](../../pages/guides/accessible-pdfs.md).
225
+
226
+ Next: [multilingual text](../../pages/recipes/multilingual.md), with five scripts, each in its own embedded font.
@@ -0,0 +1,215 @@
1
+ ---
2
+ title: "Links and outlines"
3
+ description: "Four A5 pages joined by web, page and named links, with a nested outline, page labels that read i, ii, 1, 2 and the outline shown on open."
4
+ version: "0.2.0-alpha.3"
5
+ url: "https://docs.laminapdf.com/recipes/links-and-outlines/"
6
+ ---
7
+
8
+ # Links and outlines
9
+
10
+ Four A5 pages joined by web, page and named links, with a nested outline, page labels that read i, ii, 1, 2 and the outline shown on open.
11
+
12
+ You'll make four A5 pages joined by links to a website and between pages, an outline (the bookmarks panel) and page numbers that read i, ii, 1, 2. Start from it for a manual, a report or any document longer than a page or two.
13
+
14
+ The function draws the four pages, links them and returns the PDF's bytes. It draws text, so in TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
+
16
+ <TabItem label="TypeScript">`packages/sdk/examples/links-and-outlines.ts`:
17
+
18
+ ```ts
19
+ // Links and outlines: four A5 pages joined by links, bookmarks and page labels. The title page
20
+ // links to a website, to a page and through a named destination; every other page has a linked
21
+ // text run back to it. The outline is nested and shown when the file opens.
22
+ import { Builder, type BuilderOptions, Font, type LinkTarget, type Surface } from "@laminapdf/sdk";
23
+
24
+ const TITLES = ["Title page", "Contents", "Chapter 1", "Chapter 2"] as const;
25
+ const MARGIN = 36;
26
+ const TITLE_BASELINE = 60;
27
+ const TITLE_SIZE = 18;
28
+ const LINK_SIZE = 12;
29
+ /** The baselines of the title page's three links. */
30
+ const LINK_BASELINES = [100, 125, 150] as const;
31
+ /** A link's clickable box: this wide, this tall, starting this far above the baseline. */
32
+ const LINK_WIDTH = 220;
33
+ const LINK_HEIGHT = 20;
34
+ const LINK_RISE = 15;
35
+
36
+ /**
37
+ * The document's bytes. Needs `init({ text: true })`.
38
+ *
39
+ * @param options - can fix the file ID, the producer and the creation date.
40
+ */
41
+ export function linksAndOutlines(options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
42
+ using b = new Builder(options);
43
+ const font = Font.standard("Helvetica");
44
+ b.font(font);
45
+ b.destination("chapter-2", 3);
46
+ TITLES.forEach((title, index) => {
47
+ const page = b.page("A5");
48
+ const s = page.surface();
49
+ s.text(title, { font, size: TITLE_SIZE, at: [MARGIN, TITLE_BASELINE] });
50
+ if (index === 0) titlePageLinks(s, font);
51
+ else {
52
+ const link = { target: { type: "page", index: 0 }, contents: "Title page" } as const;
53
+ s.text("Back to the title page", { font, size: LINK_SIZE, at: [MARGIN, 100], fill: [0, 0.2, 0.8], link });
54
+ }
55
+ page.finish();
56
+ });
57
+ navigation(b);
58
+ using doc = b.finish();
59
+ return doc.save();
60
+ }
61
+
62
+ /** The title page's links: to a website, to a page, and to a named destination. */
63
+ function titlePageLinks(s: Surface, font: Font): void {
64
+ const links: readonly (readonly [label: string, target: LinkTarget, contents?: string])[] = [
65
+ ["Visit example.com", { type: "uri", uri: "https://example.com/" }, "Example"],
66
+ ["Go to chapter 1", { type: "page", index: 2 }],
67
+ ["Go to chapter 2 (named)", { type: "named", name: "chapter-2" }],
68
+ ];
69
+ links.forEach(([label, target, contents], i) => {
70
+ const baseline = LINK_BASELINES[i] ?? 0;
71
+ s.text(label, { font, size: LINK_SIZE, at: [MARGIN, baseline] });
72
+ s.link([MARGIN, baseline - LINK_RISE, LINK_WIDTH, LINK_HEIGHT], target, contents);
73
+ });
74
+ }
75
+
76
+ /** The outline (bookmarks), the page labels, and the viewer opening with the outline shown. */
77
+ function navigation(b: Builder): void {
78
+ b.outline([
79
+ { title: "Title page", target: { type: "page", index: 0 } },
80
+ { title: "Contents", target: { type: "page", index: 1 }, color: [0, 0, 0.6] },
81
+ {
82
+ title: "Chapters",
83
+ open: true,
84
+ bold: true,
85
+ children: [
86
+ { title: "Chapter 1", target: { type: "page", index: 2 } },
87
+ { title: "Chapter 2", target: { type: "named", name: "chapter-2" }, italic: true },
88
+ ],
89
+ },
90
+ ]);
91
+ b.pageLabels([
92
+ { start: 0, style: "lowerRoman" },
93
+ { start: 2, style: "decimal" },
94
+ ]).viewerPreferences({ pageMode: "outlines" });
95
+ }
96
+ ```</TabItem>
97
+ <TabItem label="Rust">`crates/laminapdf/examples/links_and_outlines.rs`:
98
+
99
+ ````rust
100
+ //! Links and outlines: four A5 pages joined by links, bookmarks and page labels.
101
+ //!
102
+ //! The title page has a URI link, a link to a page and one through a named destination; every
103
+ //! other page has a linked text run back to it. The outline (bookmarks) is nested and open in
104
+ //! the viewer, and the pages are labelled i, ii, 1, 2.
105
+ //!
106
+ //! ```sh
107
+ //! cargo run -p laminapdf --example links_and_outlines -- [out-dir] # writes <out-dir>/links_and_outlines.pdf
108
+ //! ```
109
+
110
+ use std::error::Error;
111
+ use std::path::PathBuf;
112
+
113
+ use laminapdf::{
114
+ BuildOptions, Builder, Color, Fit, FontHandle, LabelStyle, LinkTarget, OutlineEntry, PageLabel, PageMode, PageSize,
115
+ PointPt, Pt, RectPt, SaveOptions, Standard14, Surface, ViewerPreferences,
116
+ };
117
+
118
+ const TITLES: [&str; 4] = ["Title page", "Contents", "Chapter 1", "Chapter 2"];
119
+ const MARGIN: f64 = 36.0;
120
+ const TITLE_AT: PointPt = PointPt::from_points(MARGIN, 60.0);
121
+ const BACK_AT: PointPt = PointPt::from_points(MARGIN, 100.0);
122
+ const TITLE_SIZE: Pt = Pt::new(18.0);
123
+ const LINK_SIZE: Pt = Pt::new(12.0);
124
+ /// The baselines of the title page's three links.
125
+ const LINK_BASELINES: [f64; 3] = [100.0, 125.0, 150.0];
126
+ /// A link's clickable box: this wide, this tall, starting this far above the baseline.
127
+ const LINK_WIDTH: f64 = 220.0;
128
+ const LINK_HEIGHT: f64 = 20.0;
129
+ const LINK_RISE: f64 = 15.0;
130
+
131
+ fn main() -> Result<(), Box<dyn Error>> {
132
+ let out = PathBuf::from(std::env::args().nth(1).unwrap_or_else(|| "target/examples-out".into()));
133
+ std::fs::create_dir_all(&out)?;
134
+ std::fs::write(out.join("links_and_outlines.pdf"), build(BuildOptions::new())?)?;
135
+ Ok(())
136
+ }
137
+
138
+ /// The document's bytes. `options` can fix the file ID, the producer and the creation date.
139
+ pub(crate) fn build(options: BuildOptions) -> Result<Vec<u8>, Box<dyn Error>> {
140
+ let mut b = Builder::new(options);
141
+ let helvetica = b.standard_font(Standard14::Helvetica)?;
142
+ b.destination("chapter-2", 3, Fit::Page);
143
+ for (index, title) in (0_u32..).zip(TITLES) {
144
+ let mut page = b.page(PageSize::A5);
145
+ let s = page.surface();
146
+ s.text(title, &helvetica, TITLE_SIZE, TITLE_AT)?;
147
+ if index == 0 {
148
+ title_page_links(s, &helvetica)?;
149
+ } else {
150
+ s.text("Back to the title page", &helvetica, LINK_SIZE, BACK_AT)?
151
+ .fill(Color::rgb(0.0, 0.2, 0.8))
152
+ .link(LinkTarget::page(0), Some("Title page".into()));
153
+ }
154
+ page.finish()?;
155
+ }
156
+ navigation(&mut b)?;
157
+ let mut doc = b.finish()?;
158
+ Ok(doc.save(&SaveOptions::new())?.bytes)
159
+ }
160
+
161
+ /// The title page's links: to a website, to a page, and to a named destination.
162
+ fn title_page_links(s: &mut Surface, font: &FontHandle) -> Result<(), Box<dyn Error>> {
163
+ let site = LinkTarget::Uri("https://example.com/".into());
164
+ let links = [
165
+ ("Visit example.com", site, Some("Example")),
166
+ ("Go to chapter 1", LinkTarget::page(2), None),
167
+ ("Go to chapter 2 (named)", LinkTarget::Named("chapter-2".into()), None),
168
+ ];
169
+ for ((label, target, contents), baseline) in links.into_iter().zip(LINK_BASELINES) {
170
+ s.text(label, font, LINK_SIZE, PointPt::from_points(MARGIN, baseline))?;
171
+ let area = RectPt::from_points(MARGIN, baseline - LINK_RISE, LINK_WIDTH, LINK_HEIGHT);
172
+ s.link(area, target, contents.map(String::from));
173
+ }
174
+ Ok(())
175
+ }
176
+
177
+ /// The outline (bookmarks), the page labels, and the viewer opening with the outline shown.
178
+ fn navigation(b: &mut Builder) -> Result<(), Box<dyn Error>> {
179
+ b.outline(vec![
180
+ OutlineEntry::new("Title page", LinkTarget::page(0)),
181
+ OutlineEntry::new("Contents", LinkTarget::page(1)).with_color(0.0, 0.0, 0.6),
182
+ OutlineEntry::heading("Chapters")
183
+ .open(true)
184
+ .bold()
185
+ .with_child(OutlineEntry::new("Chapter 1", LinkTarget::page(2)))
186
+ .with_child(OutlineEntry::new("Chapter 2", LinkTarget::Named("chapter-2".into())).italic()),
187
+ ])?;
188
+ b.page_labels(vec![
189
+ PageLabel::new(0, Some(LabelStyle::LowerRoman)),
190
+ PageLabel::new(2, Some(LabelStyle::Decimal)),
191
+ ])
192
+ .viewer_preferences(ViewerPreferences::default().with_page_mode(PageMode::Outlines));
193
+ Ok(())
194
+ }
195
+ ````</TabItem>
196
+
197
+ The result is four A5 pages with an outline and page labels: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/links_and_outlines.pdf).
198
+
199
+ ## How it works
200
+
201
+ - `s.link(rect, target, contents)` puts a clickable box over each label on the title page. A target is a web address (`uri`), a page by its index from 0, or a named destination. See [Pages and coordinates](../../pages/concepts/pages-and-coordinates.md#pages-count-from-0).
202
+ - `destination("chapter-2", 3)` names the fourth page, so a link or an outline entry can go there by name. When the builder finishes, a missing page fails with `page-index-out-of-range` and a missing name with `unknown-destination`.
203
+ - On the other pages, the `link` option of `text` (`.link(…)` on the run in Rust) makes the blue run itself the link, with no box to work out.
204
+ - `contents` describes a link in words, for viewers and screen readers. Accessible PDFs need one on every link.
205
+ - `outline` builds the bookmarks. "Chapters" has no target, so it's a heading, shown open and in bold with both chapters inside it.
206
+ - `pageLabels` numbers the pages i and ii, then 1 and 2 from the third page. `pageMode: "outlines"` asks the viewer to open the file with the outline showing.
207
+
208
+ ## What to change
209
+
210
+ - **Where a page link lands:** add a `fit` to the target, such as `fit: { type: "width" }` (`Fit::Width` in Rust) to fill the window with the page's width. `destination` takes one too.
211
+ - **The page numbers:** a label also takes a `prefix` and a `first` number, so `{ start: 2, style: "decimal", prefix: "A-" }` numbers the chapters A-1 and A-2.
212
+ - **The panel on open:** `pageMode` also takes `"thumbnails"`, `"attachments"` or `"none"`.
213
+ - **A title and an author:** add `metadata({ title, author })`, and `displayDocTitle: true` in the viewer preferences so viewers show the title instead of the file name. See [Links, outlines and metadata](../../pages/guides/links-outlines-and-metadata.md).
214
+
215
+ Next: [generate, then edit](../../pages/recipes/generate-then-edit.md), which opens a PDF you made and changes its metadata.
@@ -0,0 +1,189 @@
1
+ ---
2
+ title: "Multilingual text"
3
+ description: "Latin, Arabic, Devanagari, Hebrew and Chinese on one page, each in its own embedded font, as a shaped line and a wrapped paragraph."
4
+ version: "0.2.0-alpha.3"
5
+ url: "https://docs.laminapdf.com/recipes/multilingual/"
6
+ ---
7
+
8
+ # Multilingual text
9
+
10
+ Latin, Arabic, Devanagari, Hebrew and Chinese on one page, each in its own embedded font, as a shaped line and a wrapped paragraph.
11
+
12
+ You'll put five scripts on one page: Latin, Arabic, Devanagari, Hebrew and Chinese, each as a single line and as a paragraph wrapped in a box. Start from it whenever your text goes beyond Latin, or mixes left-to-right and right-to-left writing.
13
+
14
+ The function takes one font file per script, draws each script's line and paragraph, and returns the PDF's bytes. In TypeScript it needs the text module, `init({ text: true })`. Both languages write the same bytes:
15
+
16
+ <TabItem label="TypeScript">`packages/sdk/examples/multilingual.ts`:
17
+
18
+ ```ts
19
+ // Five scripts on one page, each as a single line and as a wrapped paragraph. The scripts are
20
+ // Latin, Arabic and Hebrew (right to left), Devanagari and simplified Chinese; each is shaped and
21
+ // bidi-resolved, and each paragraph wraps in a text box. Each script needs a font that covers it,
22
+ // such as the Noto fonts (OFL).
23
+ import { Builder, type BuilderOptions, type Direction, Font } from "@laminapdf/sdk";
24
+
25
+ /** Each script's line, its paragraph and the paragraph's direction, in the order of the fonts. */
26
+ export const SCRIPTS: readonly (readonly [line: string, paragraph: string, direction: Direction])[] = [
27
+ [
28
+ "Latin: the office staff fill the affine waffle flask",
29
+ "A paragraph breaks at spaces and fits the width of its box, line by line.",
30
+ "ltr",
31
+ ],
32
+ ["مرحبا بالعالم السلام عليكم", "مرحبا بالعالم السلام عليكم مرحبا بالعالم السلام عليكم", "rtl"],
33
+ ["नमस्ते दुनिया। हिन्दी पाठ का परीक्षण", "नमस्ते दुनिया। हिन्दी पाठ का परीक्षण नमस्ते दुनिया।", "ltr"],
34
+ ["שלום עולם וברוכים הבאים", "שלום עולם וברוכים הבאים שלום עולם וברוכים הבאים", "rtl"],
35
+ ["你好世界,这是中文字体子集测试。", "你好世界,这是中文字体子集测试。你好世界,这是中文。", "ltr"],
36
+ ];
37
+ const MARGIN = 56;
38
+ const FIRST_BASELINE = 80;
39
+ /** The distance from one script's line to the next one's. */
40
+ const SCRIPT_HEIGHT = 140;
41
+ /** The paragraph's box: this far below the line, this wide and this tall. */
42
+ const PARAGRAPH_DROP = 14;
43
+ const PARAGRAPH_WIDTH = 220;
44
+ const PARAGRAPH_HEIGHT = 100;
45
+
46
+ /**
47
+ * The document's bytes. Needs `init({ text: true })`.
48
+ *
49
+ * @param fonts - one font file per script, in the order of {@link SCRIPTS}.
50
+ * @param options - can fix the file ID, the producer and the creation date.
51
+ */
52
+ export function multilingual(fonts: readonly Uint8Array[], options: BuilderOptions = {}): Uint8Array<ArrayBuffer> {
53
+ if (fonts.length !== SCRIPTS.length) throw new Error(`${SCRIPTS.length} fonts, one per script, are needed`);
54
+ using b = new Builder(options);
55
+ const loaded = fonts.map((bytes) => Font.fromBytes(bytes));
56
+ for (const font of loaded) b.font(font);
57
+ const page = b.page("A4");
58
+ const s = page.surface();
59
+ SCRIPTS.forEach(([line, paragraph, direction], row) => {
60
+ const font = loaded[row];
61
+ if (font === undefined) return;
62
+ const y = FIRST_BASELINE + SCRIPT_HEIGHT * row;
63
+ s.text(line, { font, size: 16, at: [MARGIN, y] });
64
+ // A narrow box breaks the paragraph into lines; right-to-left paragraphs start at the right.
65
+ const box = [MARGIN, y + PARAGRAPH_DROP, PARAGRAPH_WIDTH, PARAGRAPH_HEIGHT] as const;
66
+ s.textBox(paragraph, { font, size: 12, box, direction, align: "start" });
67
+ });
68
+ page.finish();
69
+ using doc = b.finish();
70
+ return doc.save();
71
+ }
72
+ ```</TabItem>
73
+ <TabItem label="Rust">`crates/laminapdf/examples/multilingual.rs`:
74
+
75
+ ````rust
76
+ //! Five scripts on one page, each as a single line and as a wrapped paragraph.
77
+ //!
78
+ //! The scripts are Latin, Arabic and Hebrew (right to left), Devanagari and simplified Chinese;
79
+ //! each is shaped and bidi-resolved, and each paragraph wraps in a text box. Each script needs a
80
+ //! font that covers it, such as the Noto fonts (OFL).
81
+ //!
82
+ //! ```sh
83
+ //! cargo run -p laminapdf --example multilingual -- latin.ttf arabic.ttf devanagari.ttf hebrew.ttf chinese.otf
84
+ //! ```
85
+
86
+ use std::error::Error;
87
+ use std::path::PathBuf;
88
+
89
+ use laminapdf::{
90
+ Alignment, BuildOptions, Builder, Direction, PageSize, PointPt, Pt, RectPt, SaveOptions, TextBoxOptions,
91
+ };
92
+
93
+ /// Each script's line, its paragraph and the paragraph's direction, in the order of the fonts.
94
+ const SCRIPTS: [(&str, &str, Direction); 5] = [
95
+ (
96
+ "Latin: the office staff fill the affine waffle flask",
97
+ "A paragraph breaks at spaces and fits the width of its box, line by line.",
98
+ Direction::LeftToRight,
99
+ ),
100
+ (
101
+ "مرحبا بالعالم السلام عليكم",
102
+ "مرحبا بالعالم السلام عليكم مرحبا بالعالم السلام عليكم",
103
+ Direction::RightToLeft,
104
+ ),
105
+ (
106
+ "नमस्ते दुनिया। हिन्दी पाठ का परीक्षण",
107
+ "नमस्ते दुनिया। हिन्दी पाठ का परीक्षण नमस्ते दुनिया।",
108
+ Direction::LeftToRight,
109
+ ),
110
+ (
111
+ "שלום עולם וברוכים הבאים",
112
+ "שלום עולם וברוכים הבאים שלום עולם וברוכים הבאים",
113
+ Direction::RightToLeft,
114
+ ),
115
+ (
116
+ "你好世界,这是中文字体子集测试。",
117
+ "你好世界,这是中文字体子集测试。你好世界,这是中文。",
118
+ Direction::LeftToRight,
119
+ ),
120
+ ];
121
+ const MARGIN: f64 = 56.0;
122
+ const FIRST_BASELINE: f64 = 80.0;
123
+ /// The distance from one script's line to the next one's.
124
+ const SCRIPT_HEIGHT: f64 = 140.0;
125
+ /// The paragraph's box: this far below the line, this wide and this tall.
126
+ const PARAGRAPH_DROP: f64 = 14.0;
127
+ const PARAGRAPH_WIDTH: f64 = 220.0;
128
+ const PARAGRAPH_HEIGHT: f64 = 100.0;
129
+
130
+ fn main() -> Result<(), Box<dyn Error>> {
131
+ let mut args: Vec<String> = std::env::args().skip(1).collect();
132
+ let out = PathBuf::from(if args.len() > SCRIPTS.len() {
133
+ args.remove(SCRIPTS.len())
134
+ } else {
135
+ "target/examples-out".into()
136
+ });
137
+ let fonts = args.iter().map(std::fs::read).collect::<Result<Vec<_>, _>>()?;
138
+ std::fs::create_dir_all(&out)?;
139
+ std::fs::write(out.join("multilingual.pdf"), build(BuildOptions::new(), fonts)?)?;
140
+ Ok(())
141
+ }
142
+
143
+ /// The document's bytes, with one font per script in `fonts` (in the order of [`SCRIPTS`]).
144
+ /// `options` can fix the file ID, the producer and the creation date.
145
+ pub(crate) fn build(options: BuildOptions, fonts: Vec<Vec<u8>>) -> Result<Vec<u8>, Box<dyn Error>> {
146
+ if fonts.len() != SCRIPTS.len() {
147
+ return Err(format!("{} fonts, one per script, are needed", SCRIPTS.len()).into());
148
+ }
149
+ let mut b = Builder::new(options);
150
+ let fonts = fonts
151
+ .into_iter()
152
+ .map(|bytes| b.font_bytes(bytes))
153
+ .collect::<Result<Vec<_>, _>>()?;
154
+ let mut page = b.page(PageSize::A4);
155
+ let s = page.surface();
156
+ for (row, ((line, paragraph, direction), font)) in (0_u32..).zip(SCRIPTS.iter().zip(&fonts)) {
157
+ let y = FIRST_BASELINE + SCRIPT_HEIGHT * f64::from(row);
158
+ s.text(line, font, Pt::new(16.0), PointPt::from_points(MARGIN, y))?;
159
+ // A narrow box breaks the paragraph into lines; right-to-left paragraphs start at the right.
160
+ let options = TextBoxOptions::new()
161
+ .with_direction(*direction)
162
+ .with_align(Alignment::Start);
163
+ let area = RectPt::from_points(MARGIN, y + PARAGRAPH_DROP, PARAGRAPH_WIDTH, PARAGRAPH_HEIGHT);
164
+ s.text_box(paragraph, font, Pt::new(12.0), area, &options)?;
165
+ }
166
+ page.finish()?;
167
+ let mut doc = b.finish()?;
168
+ Ok(doc.save(&SaveOptions::new())?.bytes)
169
+ }
170
+ ````</TabItem>
171
+
172
+ The result is one A4 page, with each script set in its own Noto font: [open the reference PDF](https://docs.laminapdf.com/examples/pdf/multilingual.pdf).
173
+
174
+ ## How it works
175
+
176
+ - `Font.fromBytes` (`font_bytes` in Rust) loads each font, which must cover its script. The builder embeds only the glyphs the page draws.
177
+ - Lamina PDF shapes the text with each font, so Arabic letters join, Devanagari letters combine into clusters and the Latin line forms ligatures.
178
+ - `textBox` breaks each paragraph into lines that fit its 220-point-wide box. It breaks where Unicode's line-breaking rules allow: between words, and between Chinese characters.
179
+ - `direction: "rtl"` (`Direction::RightToLeft`) makes the Arabic and Hebrew paragraphs right to left, so `align: "start"` starts their lines at the box's right edge.
180
+ - Each font is embedded with its [ToUnicode](../../pages/reference/glossary.md#tounicode) mapping, so text copied or extracted from the PDF equals the strings you passed in, ligatures included.
181
+
182
+ ## What to change
183
+
184
+ - **Your own scripts:** change `SCRIPTS` and pass one font per row, in the same order. A character the font lacks fails with `missing-glyph`.
185
+ - **The paragraph's box:** `PARAGRAPH_WIDTH` and `PARAGRAPH_HEIGHT` set its size. Lines that don't fit are left out, unless you pass `overflow: "error"` (`Overflow::Error`) to fail with `text-overflow` instead.
186
+ - **Alignment and spacing:** `align` also takes `"end"`, `"left"`, `"right"` or `"center"`, and `lineHeight` (`with_line_height`) sets the distance between baselines, 1.2 times the size by default.
187
+ - **Ligatures and other OpenType features:** `features: ["liga=0"]` on `text` or `textBox` turns the ligatures off. In Rust, call `features` on the run or `with_feature` on the text box's options. See [Text and fonts](../../pages/guides/fonts-and-text.md).
188
+
189
+ Next: [images and vectors](../../pages/recipes/images-and-vectors.md), with shapes, colour, transparency and two images.