@jasy/pdf 1.0.0-alpha.1 → 1.0.0-alpha.10

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 (275) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +35 -21
  3. package/dist/api/args.d.ts +1 -1
  4. package/dist/api/args.js +2 -5
  5. package/dist/api/color.d.ts +4 -4
  6. package/dist/api/color.js +11 -17
  7. package/dist/api/content.d.ts +20 -13
  8. package/dist/api/content.js +44 -29
  9. package/dist/api/descriptor.d.ts +2 -2
  10. package/dist/api/descriptor.js +121 -31
  11. package/dist/api/dimension.d.ts +76 -0
  12. package/dist/api/dimension.js +43 -0
  13. package/dist/api/font-url.d.ts +28 -0
  14. package/dist/api/font-url.js +134 -0
  15. package/dist/api/forms.d.ts +347 -0
  16. package/dist/api/forms.js +189 -0
  17. package/dist/api/gradient.d.ts +53 -0
  18. package/dist/api/gradient.js +97 -0
  19. package/dist/api/index.d.ts +13 -8
  20. package/dist/api/index.js +13 -24
  21. package/dist/api/insets.d.ts +19 -12
  22. package/dist/api/insets.js +19 -12
  23. package/dist/api/layout.d.ts +149 -25
  24. package/dist/api/layout.js +182 -51
  25. package/dist/api/page-builder.d.ts +40 -0
  26. package/dist/api/page-builder.js +44 -0
  27. package/dist/api/structure.d.ts +100 -13
  28. package/dist/api/structure.js +179 -88
  29. package/dist/api/table.d.ts +5 -5
  30. package/dist/api/table.js +38 -26
  31. package/dist/api/text.d.ts +90 -4
  32. package/dist/api/text.js +116 -31
  33. package/dist/assets/font-data.d.ts +2 -0
  34. package/dist/assets/font-data.js +21 -0
  35. package/dist/assets/font-data.ts +37 -0
  36. package/dist/common/color.js +1 -5
  37. package/dist/constants/page-sizes.js +3 -6
  38. package/dist/constants/pdf-parts.js +1 -4
  39. package/dist/crypto/legacy-handler.d.ts +43 -0
  40. package/dist/crypto/legacy-handler.js +145 -0
  41. package/dist/crypto/security-handler.d.ts +62 -0
  42. package/dist/crypto/security-handler.js +155 -0
  43. package/dist/crypto/webcrypto.d.ts +11 -0
  44. package/dist/crypto/webcrypto.js +62 -0
  45. package/dist/edit/acroform-reader.d.ts +70 -0
  46. package/dist/edit/acroform-reader.js +126 -0
  47. package/dist/edit/appearance.d.ts +47 -0
  48. package/dist/edit/appearance.js +171 -0
  49. package/dist/edit/document.d.ts +145 -0
  50. package/dist/edit/document.js +642 -0
  51. package/dist/edit/fill.d.ts +65 -0
  52. package/dist/edit/fill.js +424 -0
  53. package/dist/edit/flatten.d.ts +54 -0
  54. package/dist/edit/flatten.js +462 -0
  55. package/dist/edit/index.d.ts +20 -0
  56. package/dist/edit/index.js +18 -0
  57. package/dist/edit/lexer.d.ts +30 -0
  58. package/dist/edit/lexer.js +285 -0
  59. package/dist/edit/objects.d.ts +68 -0
  60. package/dist/edit/objects.js +83 -0
  61. package/dist/edit/writer.d.ts +60 -0
  62. package/dist/edit/writer.js +283 -0
  63. package/dist/elements/container-element.d.ts +50 -5
  64. package/dist/elements/container-element.js +100 -39
  65. package/dist/elements/forms/checkbox-element.d.ts +51 -0
  66. package/dist/elements/forms/checkbox-element.js +48 -0
  67. package/dist/elements/forms/choice-element.d.ts +53 -0
  68. package/dist/elements/forms/choice-element.js +59 -0
  69. package/dist/elements/forms/push-button-element.d.ts +56 -0
  70. package/dist/elements/forms/push-button-element.js +49 -0
  71. package/dist/elements/forms/radio-element.d.ts +52 -0
  72. package/dist/elements/forms/radio-element.js +49 -0
  73. package/dist/elements/forms/signature-element.d.ts +54 -0
  74. package/dist/elements/forms/signature-element.js +47 -0
  75. package/dist/elements/forms/text-field-element.d.ts +61 -0
  76. package/dist/elements/forms/text-field-element.js +54 -0
  77. package/dist/elements/image-element.d.ts +58 -6
  78. package/dist/elements/image-element.js +145 -111
  79. package/dist/elements/index.d.ts +26 -10
  80. package/dist/elements/index.js +26 -28
  81. package/dist/elements/layout/anchor-element.d.ts +23 -0
  82. package/dist/elements/layout/anchor-element.js +27 -0
  83. package/dist/elements/layout/bookmark-element.d.ts +27 -0
  84. package/dist/elements/layout/bookmark-element.js +30 -0
  85. package/dist/elements/layout/default-text-style-element.d.ts +30 -0
  86. package/dist/elements/layout/default-text-style-element.js +47 -0
  87. package/dist/elements/layout/deferred-element.d.ts +3 -3
  88. package/dist/elements/layout/deferred-element.js +4 -8
  89. package/dist/elements/layout/expanded-element.d.ts +6 -4
  90. package/dist/elements/layout/expanded-element.js +22 -17
  91. package/dist/elements/layout/keep-together-element.d.ts +42 -0
  92. package/dist/elements/layout/keep-together-element.js +85 -0
  93. package/dist/elements/layout/link-element.d.ts +33 -0
  94. package/dist/elements/layout/link-element.js +39 -0
  95. package/dist/elements/layout/padding-element.d.ts +10 -5
  96. package/dist/elements/layout/padding-element.js +25 -16
  97. package/dist/elements/layout/page-break-element.d.ts +17 -0
  98. package/dist/elements/layout/page-break-element.js +24 -0
  99. package/dist/elements/layout/page-builder-element.d.ts +32 -0
  100. package/dist/elements/layout/page-builder-element.js +45 -0
  101. package/dist/elements/layout/positioned-element.d.ts +47 -0
  102. package/dist/elements/layout/positioned-element.js +73 -0
  103. package/dist/elements/layout/repeating-header-element.d.ts +3 -3
  104. package/dist/elements/layout/repeating-header-element.js +8 -12
  105. package/dist/elements/layout/rotated-box-element.d.ts +35 -0
  106. package/dist/elements/layout/rotated-box-element.js +54 -0
  107. package/dist/elements/layout/rotated-element.d.ts +32 -0
  108. package/dist/elements/layout/rotated-element.js +41 -0
  109. package/dist/elements/layout/sized-container-element.d.ts +2 -2
  110. package/dist/elements/layout/sized-container-element.js +6 -11
  111. package/dist/elements/layout/struct-group.d.ts +28 -0
  112. package/dist/elements/layout/struct-group.js +42 -0
  113. package/dist/elements/line-element.d.ts +3 -3
  114. package/dist/elements/line-element.js +5 -10
  115. package/dist/elements/page-element.d.ts +26 -6
  116. package/dist/elements/page-element.js +72 -27
  117. package/dist/elements/pdf-document-element.d.ts +10 -4
  118. package/dist/elements/pdf-document-element.js +11 -10
  119. package/dist/elements/pdf-element.d.ts +150 -4
  120. package/dist/elements/pdf-element.js +138 -20
  121. package/dist/elements/rectangle-element.d.ts +52 -11
  122. package/dist/elements/rectangle-element.js +129 -36
  123. package/dist/elements/row-element.d.ts +45 -4
  124. package/dist/elements/row-element.js +66 -18
  125. package/dist/elements/text-element.d.ts +132 -11
  126. package/dist/elements/text-element.js +206 -56
  127. package/dist/forms/acroform.d.ts +63 -0
  128. package/dist/forms/acroform.js +427 -0
  129. package/dist/forms/appearance.d.ts +58 -0
  130. package/dist/forms/appearance.js +204 -0
  131. package/dist/forms/field.d.ts +164 -0
  132. package/dist/forms/field.js +1 -0
  133. package/dist/forms/pdf.d.ts +19 -0
  134. package/dist/forms/pdf.js +28 -0
  135. package/dist/index.d.ts +3 -3
  136. package/dist/index.js +3 -19
  137. package/dist/ir/display-list.d.ts +197 -8
  138. package/dist/ir/display-list.js +8 -2
  139. package/dist/layout/alignment.d.ts +10 -0
  140. package/dist/layout/alignment.js +6 -0
  141. package/dist/layout/box-constraints.d.ts +61 -0
  142. package/dist/layout/box-constraints.js +88 -6
  143. package/dist/layout/collect-images.d.ts +9 -0
  144. package/dist/layout/collect-images.js +34 -0
  145. package/dist/layout/fragmentation.d.ts +20 -1
  146. package/dist/layout/fragmentation.js +74 -18
  147. package/dist/layout/insets.d.ts +28 -0
  148. package/dist/layout/insets.js +20 -0
  149. package/dist/platform/browser-fs.d.ts +2 -0
  150. package/dist/platform/browser-fs.js +9 -0
  151. package/dist/platform/browser-image.d.ts +5 -0
  152. package/dist/platform/browser-image.js +13 -0
  153. package/dist/platform/node-fs.d.ts +2 -0
  154. package/dist/platform/node-fs.js +10 -0
  155. package/dist/platform/node-image.d.ts +5 -0
  156. package/dist/platform/node-image.js +9 -0
  157. package/dist/renderer/anchor-renderer.d.ts +6 -0
  158. package/dist/renderer/anchor-renderer.js +11 -0
  159. package/dist/renderer/bookmark-renderer.d.ts +6 -0
  160. package/dist/renderer/bookmark-renderer.js +11 -0
  161. package/dist/renderer/container-renderer.d.ts +3 -3
  162. package/dist/renderer/container-renderer.js +11 -28
  163. package/dist/renderer/default-text-style-renderer.d.ts +6 -0
  164. package/dist/renderer/default-text-style-renderer.js +10 -0
  165. package/dist/renderer/deferred-renderer.d.ts +3 -3
  166. package/dist/renderer/deferred-renderer.js +8 -23
  167. package/dist/renderer/emoji-image.d.ts +9 -0
  168. package/dist/renderer/emoji-image.js +68 -0
  169. package/dist/renderer/expanded-renderer.d.ts +3 -3
  170. package/dist/renderer/expanded-renderer.js +6 -21
  171. package/dist/renderer/form-field-renderer.d.ts +20 -0
  172. package/dist/renderer/form-field-renderer.js +12 -0
  173. package/dist/renderer/image-renderer.d.ts +4 -4
  174. package/dist/renderer/image-renderer.js +86 -75
  175. package/dist/renderer/index.d.ts +10 -10
  176. package/dist/renderer/index.js +10 -26
  177. package/dist/renderer/keep-together-renderer.d.ts +10 -0
  178. package/dist/renderer/keep-together-renderer.js +12 -0
  179. package/dist/renderer/line-renderer.d.ts +3 -3
  180. package/dist/renderer/line-renderer.js +13 -28
  181. package/dist/renderer/link-renderer.d.ts +6 -0
  182. package/dist/renderer/link-renderer.js +11 -0
  183. package/dist/renderer/padding-renderer.d.ts +3 -3
  184. package/dist/renderer/padding-renderer.js +6 -21
  185. package/dist/renderer/page-break-renderer.d.ts +14 -0
  186. package/dist/renderer/page-break-renderer.js +15 -0
  187. package/dist/renderer/page-builder-renderer.d.ts +6 -0
  188. package/dist/renderer/page-builder-renderer.js +11 -0
  189. package/dist/renderer/page-renderer.d.ts +2 -2
  190. package/dist/renderer/page-renderer.js +115 -77
  191. package/dist/renderer/pdf-backend.d.ts +31 -4
  192. package/dist/renderer/pdf-backend.js +287 -49
  193. package/dist/renderer/pdf-config.js +4 -7
  194. package/dist/renderer/pdf-document-class.d.ts +5 -5
  195. package/dist/renderer/pdf-document-class.js +27 -41
  196. package/dist/renderer/pdf-document-renderer.d.ts +19 -10
  197. package/dist/renderer/pdf-document-renderer.js +135 -91
  198. package/dist/renderer/pdf-renderer.d.ts +2 -2
  199. package/dist/renderer/pdf-renderer.js +165 -90
  200. package/dist/renderer/positioned-renderer.d.ts +6 -0
  201. package/dist/renderer/positioned-renderer.js +10 -0
  202. package/dist/renderer/rectangle-renderer.d.ts +3 -3
  203. package/dist/renderer/rectangle-renderer.js +49 -44
  204. package/dist/renderer/repeating-header-renderer.d.ts +3 -3
  205. package/dist/renderer/repeating-header-renderer.js +10 -26
  206. package/dist/renderer/rotated-renderer.d.ts +7 -0
  207. package/dist/renderer/rotated-renderer.js +29 -0
  208. package/dist/renderer/row-renderer.d.ts +3 -3
  209. package/dist/renderer/row-renderer.js +11 -28
  210. package/dist/renderer/struct-group-renderer.d.ts +10 -0
  211. package/dist/renderer/struct-group-renderer.js +24 -0
  212. package/dist/renderer/text-renderer.d.ts +9 -5
  213. package/dist/renderer/text-renderer.js +437 -89
  214. package/dist/text/advance.d.ts +34 -0
  215. package/dist/text/advance.js +45 -0
  216. package/dist/text/arabic.d.ts +20 -0
  217. package/dist/text/arabic.js +101 -0
  218. package/dist/text/bidi.d.ts +33 -0
  219. package/dist/text/bidi.js +94 -0
  220. package/dist/text/emoji-codepoints.d.ts +1 -0
  221. package/dist/text/emoji-codepoints.js +29 -0
  222. package/dist/text/font-fallback.d.ts +25 -0
  223. package/dist/text/font-fallback.js +38 -0
  224. package/dist/text/line-breaker.d.ts +50 -12
  225. package/dist/text/line-breaker.js +158 -37
  226. package/dist/text/line-metrics.d.ts +49 -0
  227. package/dist/text/line-metrics.js +57 -0
  228. package/dist/text/orphans-widows.d.ts +27 -0
  229. package/dist/text/orphans-widows.js +53 -0
  230. package/dist/text/shape.d.ts +29 -0
  231. package/dist/text/shape.js +67 -0
  232. package/dist/text/text-decoration.d.ts +50 -0
  233. package/dist/text/text-decoration.js +62 -0
  234. package/dist/text/text-style.d.ts +60 -0
  235. package/dist/text/text-style.js +79 -0
  236. package/dist/utils/afm-parser.d.ts +34 -0
  237. package/dist/utils/afm-parser.js +76 -17
  238. package/dist/utils/bytes.d.ts +24 -0
  239. package/dist/utils/bytes.js +76 -0
  240. package/dist/utils/dest-registry.d.ts +22 -0
  241. package/dist/utils/dest-registry.js +32 -0
  242. package/dist/utils/flex-layout.d.ts +36 -9
  243. package/dist/utils/flex-layout.js +207 -30
  244. package/dist/utils/font-metrics.d.ts +20 -1
  245. package/dist/utils/font-metrics.js +1 -2
  246. package/dist/utils/font-path.js +3 -6
  247. package/dist/utils/gsub.d.ts +36 -0
  248. package/dist/utils/gsub.js +178 -0
  249. package/dist/utils/image-helper.d.ts +6 -5
  250. package/dist/utils/image-helper.js +101 -111
  251. package/dist/utils/inflate.d.ts +16 -0
  252. package/dist/utils/inflate.js +52 -0
  253. package/dist/utils/md5.d.ts +4 -0
  254. package/dist/utils/md5.js +79 -0
  255. package/dist/utils/outline.d.ts +21 -0
  256. package/dist/utils/outline.js +79 -0
  257. package/dist/utils/pdf-object-manager.d.ts +110 -8
  258. package/dist/utils/pdf-object-manager.js +583 -141
  259. package/dist/utils/renderer-registry.d.ts +13 -1
  260. package/dist/utils/renderer-registry.js +23 -6
  261. package/dist/utils/struct-tree.d.ts +44 -0
  262. package/dist/utils/struct-tree.js +129 -0
  263. package/dist/utils/ttf-parser.d.ts +134 -2
  264. package/dist/utils/ttf-parser.js +873 -50
  265. package/dist/utils/ttf-subsetter.d.ts +1 -1
  266. package/dist/utils/ttf-subsetter.js +40 -42
  267. package/dist/utils/ua-xmp.d.ts +9 -0
  268. package/dist/utils/ua-xmp.js +32 -0
  269. package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
  270. package/dist/utils/utf8-to-windows1252-encoder.js +11 -4
  271. package/dist/utils/woff.d.ts +10 -0
  272. package/dist/utils/woff.js +116 -0
  273. package/dist/validators/element-validator.d.ts +2 -2
  274. package/dist/validators/element-validator.js +31 -26
  275. package/package.json +22 -2
@@ -0,0 +1,45 @@
1
+ /**
2
+ * How wide a run of text is: the horizontal counterpart to `line-metrics.ts` (how tall a line is)
3
+ * and `line-breaker.ts` (where a line breaks). The ONE canonical answer to "how far does the pen
4
+ * move", so measuring and drawing can never disagree about it.
5
+ *
6
+ * An advance has three parts, each with its own PDF mechanism:
7
+ *
8
+ * advance = sum(glyph widths) + sum(kerning) + n * letterSpacing
9
+ * /Widths TJ array Tc
10
+ *
11
+ * `letterSpacing` is added after EVERY code point, the last one included - exactly what the `Tc`
12
+ * operator does, and exactly what CSS `letter-spacing` does (which is why right-aligned spaced text
13
+ * looks a hair offset: a spacing hangs off the final glyph). Measuring `(n - 1)` spacings while `Tc`
14
+ * applies `n` would make every line draw one spacing wider than it was wrapped - text out of its box.
15
+ *
16
+ * Kerning is added ONLY when the document has it on (`metrics.kerningEnabled`), and then the backend
17
+ * emits a `TJ` with the same per-pair adjustments - so a measured advance always equals the drawn
18
+ * one. With kerning off, `getStringWidth` is the whole advance (its plain glyph sum), byte-identical
19
+ * to before. Folding kerning into the measurement while the output ignored it was the 2026-07-10 bug.
20
+ */
21
+ /** Number of Unicode code points (not UTF-16 units): an astral char is ONE, so it takes one
22
+ * letter-spacing, not two. Matches how `getStringWidth` iterates. */
23
+ export function codePointCount(text) {
24
+ let n = 0;
25
+ for (const _ of text)
26
+ n++;
27
+ return n;
28
+ }
29
+ /** The advance of `text` in points: the plain glyph widths, plus kerning (if the document has it on),
30
+ * plus one `letterSpacing` per code point. */
31
+ export function runAdvance(metrics, text, font, letterSpacing = 0) {
32
+ let advance = metrics.getStringWidth(text, font.fontFamily, font.fontSize, font.fontStyle);
33
+ if (letterSpacing !== 0) {
34
+ // Per DRAWN glyph, which is what `Tc` applies to; only a ligature makes that differ.
35
+ const glyphs = metrics.shapedGlyphCount?.(text, font.fontFamily, font.fontStyle) ?? codePointCount(text);
36
+ advance += glyphs * letterSpacing;
37
+ }
38
+ if (metrics.kerningEnabled) {
39
+ let units = 0;
40
+ for (const k of metrics.getKernPairs(text, font.fontFamily, font.fontStyle))
41
+ units += k;
42
+ advance += (units / 1000) * font.fontSize; // kern units are em/1000
43
+ }
44
+ return advance;
45
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Arabic (and Syriac / N'Ko / Adlam) JOINING: which of the four positional forms each letter takes -
3
+ * the half of shaping Unicode owns. Runs on the LOGICAL string.
4
+ *
5
+ * Name the SIDES, not the neighbours: a letter is joined on its right when the preceding one can join
6
+ * forwards (type D or L), on its left when the following one can join backwards (D or R). Thinking in
7
+ * "previous/next" puts a final form where an initial belongs.
8
+ */
9
+ export type JoiningType = "D" | "R" | "L" | "C" | "T" | "U";
10
+ /** The four positional forms, named as the OpenType features that realise them. */
11
+ export type JoiningForm = "isol" | "init" | "medi" | "fina";
12
+ /** The joining type of one code point. `U` for everything the table does not mention. */
13
+ export declare function joiningType(codePoint: number): JoiningType;
14
+ /** Whether a run contains anything the joining pass would touch. Cheap, and false for Latin. */
15
+ export declare function needsJoining(codePoints: readonly number[]): boolean;
16
+ /**
17
+ * The positional form of every code point, or `null` where none applies (space, digit, mark).
18
+ * `C` (tatweel, ZWJ) counts when deciding a NEIGHBOUR's form but takes none itself.
19
+ */
20
+ export declare function joiningForms(codePoints: readonly number[]): (JoiningForm | null)[];
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Arabic (and Syriac / N'Ko / Adlam) JOINING: which of the four positional forms each letter takes -
3
+ * the half of shaping Unicode owns. Runs on the LOGICAL string.
4
+ *
5
+ * Name the SIDES, not the neighbours: a letter is joined on its right when the preceding one can join
6
+ * forwards (type D or L), on its left when the following one can join backwards (D or R). Thinking in
7
+ * "previous/next" puts a final form where an initial belongs.
8
+ */
9
+ /**
10
+ * Joining types, from Unicode 17.0.0 `ArabicShaping.txt`, packed as `start,length,type` in hex.
11
+ * Anything not listed is `U` (non-joining), which is the standard's own default.
12
+ *
13
+ * `T` (transparent - combining marks) is mostly NOT in that file: the standard says an unlisted mark
14
+ * (category Mn/Me/Cf) is transparent, so those ranges are folded in from the character database.
15
+ */
16
+ const PACKED = "300,6f,5 483,6,5 591,2c,5 5bf,0,5 5c1,1,5 5c4,1,5 5c7,0,5 610,a,5 61c,0,5 620,0,1 622,3,2" +
17
+ " 626,0,1 627,0,2 628,0,1 629,0,2 62a,4,1 62f,3,2 633,c,1 640,0,4 641,6,1 648,0,2 649,1,1 64b,14,5" +
18
+ " 66e,1,1 670,0,5 671,2,2 675,2,2 678,f,1 688,11,2 69a,25,1 6c0,0,2 6c1,1,1 6c3,8,2 6cc,0,1" +
19
+ " 6cd,0,2 6ce,0,1 6cf,0,2 6d0,1,1 6d2,1,2 6d5,0,2 6d6,6,5 6df,5,5 6e7,1,5 6ea,3,5 6ee,1,2 6fa,2,1" +
20
+ " 6ff,0,1 70f,0,5 710,0,2 711,0,5 712,2,1 715,4,2 71a,3,1 71e,0,2 71f,8,1 728,0,2 729,0,1 72a,0,2" +
21
+ " 72b,0,1 72c,0,2 72d,1,1 72f,0,2 730,1a,5 74d,0,2 74e,a,1 759,2,2 75c,e,1 76b,1,2 76d,3,1 771,0,2" +
22
+ " 772,0,1 773,1,2 775,2,1 778,1,2 77a,5,1 7a6,a,5 7ca,20,1 7eb,8,5 7fa,0,4 7fd,0,5 816,3,5 81b,8,5" +
23
+ " 825,2,5 829,4,5 840,0,2 841,4,1 846,1,2 848,0,1 849,0,2 84a,9,1 854,0,2 855,0,1 856,2,2 859,2,5" +
24
+ " 860,0,1 862,3,1 867,0,2 868,0,1 869,1,2 870,12,2 883,2,4 886,0,1 889,4,1 88e,0,2 88f,0,1 897,8,5" +
25
+ " 8a0,9,1 8aa,2,2 8ae,0,2 8af,1,1 8b1,1,2 8b3,5,1 8b9,0,2 8ba,e,1 8ca,17,5 8e3,1c,5 1807,0,1" +
26
+ " 180a,0,4 1820,58,1 1885,1,5 1887,21,1 18aa,0,1 200d,0,4 a840,31,1 a872,0,3 fe00,f,5 fe20,f,5" +
27
+ " 10ac0,4,1 10ac5,0,2 10ac7,0,2 10ac9,1,2 10acd,0,3 10ace,4,2 10ad3,3,1 10ad7,0,3 10ad8,4,1" +
28
+ " 10add,0,2 10ade,2,1 10ae1,0,2 10ae4,0,2 10aeb,3,1 10aef,0,2 10b80,0,1 10b81,0,2 10b82,0,1" +
29
+ " 10b83,2,2 10b86,2,1 10b89,0,2 10b8a,1,1 10b8c,0,2 10b8d,0,1 10b8e,1,2 10b90,0,1 10b91,0,2" +
30
+ " 10ba9,3,2 10bad,1,1 10d00,0,3 10d01,20,1 10d22,0,2 10d23,0,1 10ec2,0,2 10ec3,1,1 10ec6,1,1" +
31
+ " 10f30,2,1 10f33,0,2 10f34,10,1 10f51,2,1 10f54,0,2 10f70,3,1 10f74,1,2 10f76,b,1 10fb0,0,1" +
32
+ " 10fb2,1,1 10fb4,2,2 10fb8,0,1 10fb9,1,2 10fbb,1,1 10fbd,0,2 10fbe,1,1 10fc1,0,1 10fc2,1,2" +
33
+ " 10fc4,0,1 10fc9,0,2 10fca,0,1 10fcb,0,3 1e900,43,1 1e94b,0,5";
34
+ const TYPES = ["U", "D", "R", "L", "C", "T"];
35
+ // [start, end, typeIndex] triples, sorted by start - binary searched, so the table costs one lookup.
36
+ const RANGES = PACKED.trim()
37
+ .split(/\s+/)
38
+ .map((entry) => {
39
+ const [start, length, type] = entry.split(",");
40
+ const from = parseInt(start, 16);
41
+ return [from, from + parseInt(length, 16), Number(type)];
42
+ });
43
+ /** The joining type of one code point. `U` for everything the table does not mention. */
44
+ export function joiningType(codePoint) {
45
+ let lo = 0;
46
+ let hi = RANGES.length - 1;
47
+ while (lo <= hi) {
48
+ const mid = (lo + hi) >> 1;
49
+ const [from, to, type] = RANGES[mid];
50
+ if (codePoint < from)
51
+ hi = mid - 1;
52
+ else if (codePoint > to)
53
+ lo = mid + 1;
54
+ else
55
+ return TYPES[type];
56
+ }
57
+ return "U";
58
+ }
59
+ /** Whether a run contains anything the joining pass would touch. Cheap, and false for Latin. */
60
+ export function needsJoining(codePoints) {
61
+ return codePoints.some((cp) => {
62
+ const t = joiningType(cp);
63
+ return t === "D" || t === "R" || t === "L" || t === "C";
64
+ });
65
+ }
66
+ /**
67
+ * The positional form of every code point, or `null` where none applies (space, digit, mark).
68
+ * `C` (tatweel, ZWJ) counts when deciding a NEIGHBOUR's form but takes none itself.
69
+ */
70
+ export function joiningForms(codePoints) {
71
+ const types = codePoints.map(joiningType);
72
+ /** The nearest neighbour that is not transparent - a mark never breaks a join. */
73
+ const neighbour = (from, step) => {
74
+ for (let i = from + step; i >= 0 && i < types.length; i += step) {
75
+ if (types[i] !== "T")
76
+ return types[i];
77
+ }
78
+ return "U";
79
+ };
80
+ return types.map((type, i) => {
81
+ if (type !== "D" && type !== "R" && type !== "L")
82
+ return null;
83
+ const before = neighbour(i, -1);
84
+ const after = neighbour(i, +1);
85
+ const joinedRight = before === "D" || before === "L" || before === "C";
86
+ const joinedLeft = after === "D" || after === "R" || after === "C";
87
+ if (type === "D") {
88
+ if (joinedRight && joinedLeft)
89
+ return "medi";
90
+ if (joinedRight)
91
+ return "fina";
92
+ if (joinedLeft)
93
+ return "init";
94
+ return "isol";
95
+ }
96
+ // R joins only on its right; L (a handful of Syriac letters) only on its left.
97
+ if (type === "R")
98
+ return joinedRight ? "fina" : "isol";
99
+ return joinedLeft ? "init" : "isol";
100
+ });
101
+ }
@@ -0,0 +1,33 @@
1
+ export type Direction = "ltr" | "rtl";
2
+ /** A piece of a line, already in the order it is drawn. */
3
+ export interface VisualRun {
4
+ /** VISUAL order: hand it to the drawing code as it stands, left to right. */
5
+ text: string;
6
+ /**
7
+ * The same characters in LOGICAL order. SHAPING must use this: a letter's form comes from its
8
+ * neighbours, and reversing swaps them - shaping the visual text gives every letter the wrong form
9
+ * (measured: a word 8pt too wide). Shape this, then reverse the GLYPHS.
10
+ */
11
+ logical: string;
12
+ /** True when this run came out of right-to-left text - the caller may need it for alignment. */
13
+ rtl: boolean;
14
+ }
15
+ /** A run that remembers which input piece it came from, so a span keeps its font and colour. */
16
+ export interface AttributedRun<T> extends VisualRun {
17
+ source: T;
18
+ }
19
+ /** Whether this text could be reordered at all. Cheap, and false for every Latin-only document. */
20
+ export declare const needsBidi: (text: string) => boolean;
21
+ /**
22
+ * Reorder one LINE of pieces into the runs to draw. The algorithm runs over the pieces JOINED, not
23
+ * each on its own, or a Hebrew span beside a Latin one would stay in source order. A run ends where
24
+ * the direction or the source piece changes.
25
+ *
26
+ * Line breaking happens before this, in logical order - what UAX #9 prescribes.
27
+ */
28
+ export declare function visualRunsOf<T>(pieces: {
29
+ text: string;
30
+ source: T;
31
+ }[], base?: Direction): AttributedRun<T>[];
32
+ /** The same for one plain string. An empty string still yields one run, or a blank line would vanish. */
33
+ export declare const visualRuns: (text: string, base?: Direction) => VisualRun[];
@@ -0,0 +1,94 @@
1
+ import bidiFactory from "bidi-js";
2
+ /**
3
+ * Bidirectional text (UAX #9): one logical line in, the runs to draw left to right out. ORDERING
4
+ * only - joining letters into shapes is `text/shape.ts`.
5
+ *
6
+ * The whole seam to `bidi-js`: nothing outside this file knows it exists, so replacing it with our
7
+ * own is a change here alone (`todo.md`).
8
+ */
9
+ const bidi = bidiFactory();
10
+ // Every block whose script runs right to left, plus the explicit bidi control characters. Used only
11
+ // as a fast path: no match and a left-to-right base means the algorithm cannot reorder anything.
12
+ const RTL_OR_CONTROL = /[\u0590-\u08FF\u200F\u202B\u202E\u2067\uFB1D-\uFDFF\uFE70-\uFEFF]|[\uD802\uD803\uD83A\uD83B][\uDC00-\uDFFF]/;
13
+ /** Whether this text could be reordered at all. Cheap, and false for every Latin-only document. */
14
+ export const needsBidi = (text) => RTL_OR_CONTROL.test(text);
15
+ const isHighSurrogate = (c) => c >= "\uD800" && c <= "\uDBFF";
16
+ const isLowSurrogate = (c) => c >= "\uDC00" && c <= "\uDFFF";
17
+ /**
18
+ * Reorder one LINE of pieces into the runs to draw. The algorithm runs over the pieces JOINED, not
19
+ * each on its own, or a Hebrew span beside a Latin one would stay in source order. A run ends where
20
+ * the direction or the source piece changes.
21
+ *
22
+ * Line breaking happens before this, in logical order - what UAX #9 prescribes.
23
+ */
24
+ export function visualRunsOf(pieces, base = "ltr") {
25
+ const text = pieces.map((p) => p.text).join("");
26
+ // Verbatim - same count, same order, empty pieces included - so untouched documents stay
27
+ // byte-identical.
28
+ if (text === "" || (base === "ltr" && !needsBidi(text))) {
29
+ return pieces.map((p) => ({ ...p, logical: p.text, rtl: false }));
30
+ }
31
+ // Which piece each code unit came from, so a visual run can be traced back to its span.
32
+ const owner = new Int32Array(text.length);
33
+ let at = 0;
34
+ pieces.forEach((piece, index) => {
35
+ owner.fill(index, at, at + piece.text.length);
36
+ at += piece.text.length;
37
+ });
38
+ const levels = bidi.getEmbeddingLevels(text, base);
39
+ const order = bidi.getReorderedIndices(text, levels);
40
+ // `.levels`, NOT the result object the library's README passes: it indexes its argument directly,
41
+ // and given the object it silently returns an empty map - no bracket ever mirrored.
42
+ const mirrored = bidi.getMirroredCharactersMap(text, levels.levels);
43
+ const runs = [];
44
+ let current = []; // visual order, mirrored
45
+ let written = []; // the same run as the author wrote it
46
+ let currentRtl = false;
47
+ let currentOwner = -1;
48
+ const flush = () => {
49
+ if (current.length > 0) {
50
+ runs.push({
51
+ text: current.join(""),
52
+ // Tracked separately rather than un-reversing the visual text: that text has been MIRRORED,
53
+ // so a bracket would come back as its twin and shaping - and `ToUnicode` after it - would see
54
+ // a character the author never wrote.
55
+ logical: (currentRtl ? [...written].reverse() : written).join(""),
56
+ rtl: currentRtl,
57
+ source: pieces[currentOwner].source,
58
+ });
59
+ current = [];
60
+ written = [];
61
+ }
62
+ };
63
+ for (let k = 0; k < order.length; k++) {
64
+ const i = order[k];
65
+ const rtl = (levels.levels[i] & 1) === 1;
66
+ if (rtl !== currentRtl || owner[i] !== currentOwner)
67
+ flush();
68
+ currentRtl = rtl;
69
+ currentOwner = owner[i];
70
+ // A surrogate pair is two code units at one level, so reversing splits an astral character (an
71
+ // emoji in Hebrew text) into two broken halves. They arrive adjacent and swapped.
72
+ const next = k + 1 < order.length ? order[k + 1] : -1;
73
+ if (isLowSurrogate(text[i]) && next === i - 1 && isHighSurrogate(text[next])) {
74
+ current.push(text[next], text[i]);
75
+ written.push(text[next], text[i]);
76
+ k++;
77
+ continue;
78
+ }
79
+ current.push(mirrored.get(i) ?? text[i]);
80
+ written.push(text[i]);
81
+ }
82
+ flush();
83
+ return runs;
84
+ }
85
+ /** The same for one plain string. An empty string still yields one run, or a blank line would vanish. */
86
+ export const visualRuns = (text, base = "ltr") => {
87
+ if (text === "")
88
+ return [{ text, logical: text, rtl: false }];
89
+ return visualRunsOf([{ text, source: null }], base).map(({ text: t, logical, rtl }) => ({
90
+ text: t,
91
+ logical,
92
+ rtl,
93
+ }));
94
+ };
@@ -0,0 +1 @@
1
+ export declare function isEmojiCodePoint(codePoint: number): boolean;
@@ -0,0 +1,29 @@
1
+ // Whether a code point is (very likely) an emoji, used ONLY by the image/CDN emoji source: unlike a
2
+ // font source, there is no font to ask "do you have a color glyph for this?", so we decide from the
3
+ // well-known emoji blocks. This is a curated, dependency-free subset - it covers the emoji people
4
+ // actually type, not the full Unicode Emoji property list, and intentionally errs toward the common
5
+ // blocks. A font source does NOT use this (it asks the font).
6
+ // [lo, hi] inclusive ranges of the main emoji / pictograph blocks.
7
+ const RANGES = [
8
+ [0x1f000, 0x1faff], // Mahjong/Dominoes/Cards, Misc Symbols & Pictographs, Emoticons, Transport,
9
+ // Supplemental Symbols & Pictographs, Symbols & Pictographs Extended-A (incl. flags)
10
+ [0x2190, 0x21ff], // Arrows
11
+ [0x2300, 0x23ff], // Misc Technical (⌚ ⏰ ⏳, media controls)
12
+ [0x2460, 0x24ff], // Enclosed Alphanumerics (Ⓜ)
13
+ [0x25a0, 0x25ff], // Geometric Shapes (▪ ◼ ⬛)
14
+ [0x2600, 0x27bf], // Misc Symbols + Dingbats (☀ ★ ✂ ✅ ✈ ✉ ✊ ✋)
15
+ [0x2900, 0x297f], // Supplemental Arrows-B (⤴ ⤵)
16
+ [0x2b00, 0x2bff], // Misc Symbols and Arrows (⭐ ⬅ ⬛)
17
+ [0x3000, 0x303f], // CJK Symbols and Punctuation (〰 〽)
18
+ [0x3200, 0x32ff], // Enclosed CJK Letters and Months (㊗ ㊙)
19
+ ];
20
+ // Emoji-presentation singletons outside the ranges above.
21
+ const SINGLES = new Set([0x00a9, 0x00ae, 0x203c, 0x2049, 0x2122, 0x2139, 0xfe0f]);
22
+ export function isEmojiCodePoint(codePoint) {
23
+ if (SINGLES.has(codePoint))
24
+ return true;
25
+ for (const [lo, hi] of RANGES)
26
+ if (codePoint >= lo && codePoint <= hi)
27
+ return true;
28
+ return false;
29
+ }
@@ -0,0 +1,25 @@
1
+ import type { FontStyle } from "../utils/pdf-object-manager.js";
2
+ import type { FontMetrics } from "../utils/font-metrics.js";
3
+ /**
4
+ * Font FALLBACK: picking, per code point, the first family in a stack that can draw it.
5
+ *
6
+ * `Text({ font: ["Inter", "NotoSansCJK"] })` should just work for mixed-script text. Without this a
7
+ * code point the first family lacks comes out as `.notdef` - the empty box.
8
+ *
9
+ * The output is a list of pieces, each in ONE family, which is exactly the shape a styled `span`
10
+ * already has. So the fallback reuses the whole segment machinery - breaking, bidi, shaping and
11
+ * drawing all handle per-piece fonts already - instead of adding a second one beside it.
12
+ */
13
+ /** A stretch of text that one family can draw. */
14
+ export interface FontRun {
15
+ text: string;
16
+ fontFamily: string;
17
+ }
18
+ /**
19
+ * Split `text` into runs by family. Returns `undefined` when there is nothing to split - no stack, or
20
+ * the first family draws everything - so a document that never asks for a fallback keeps its old path.
21
+ *
22
+ * A code point that NO family in the stack can draw stays with the first one, which then draws its
23
+ * `.notdef`. That is what a browser does too: the fallback chain ends, and the missing glyph shows.
24
+ */
25
+ export declare function splitByFont(text: string, fontFamily: string, fallback: readonly string[], fontStyle: FontStyle, metrics: FontMetrics): FontRun[] | undefined;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Split `text` into runs by family. Returns `undefined` when there is nothing to split - no stack, or
3
+ * the first family draws everything - so a document that never asks for a fallback keeps its old path.
4
+ *
5
+ * A code point that NO family in the stack can draw stays with the first one, which then draws its
6
+ * `.notdef`. That is what a browser does too: the fallback chain ends, and the missing glyph shows.
7
+ */
8
+ export function splitByFont(text, fontFamily, fallback, fontStyle, metrics) {
9
+ if (fallback.length === 0)
10
+ return undefined;
11
+ const familyFor = (codePoint) => {
12
+ if (metrics.hasGlyph(codePoint, fontFamily, fontStyle))
13
+ return fontFamily;
14
+ for (const family of fallback) {
15
+ if (metrics.hasGlyph(codePoint, family, fontStyle))
16
+ return family;
17
+ }
18
+ return fontFamily; // nobody has it - the first family shows the missing glyph
19
+ };
20
+ const runs = [];
21
+ let current = "";
22
+ let currentFamily = fontFamily;
23
+ for (const ch of text) {
24
+ const family = familyFor(ch.codePointAt(0));
25
+ if (current !== "" && family !== currentFamily) {
26
+ runs.push({ text: current, fontFamily: currentFamily });
27
+ current = "";
28
+ }
29
+ currentFamily = family;
30
+ current += ch;
31
+ }
32
+ if (current !== "")
33
+ runs.push({ text: current, fontFamily: currentFamily });
34
+ // Nothing substituted - no runs at all (empty text), or one in the family we started with. Say so,
35
+ // and the caller keeps its cheaper path instead of being handed an empty list to draw.
36
+ const unchanged = runs.length === 0 || (runs.length === 1 && runs[0].fontFamily === fontFamily);
37
+ return unchanged ? undefined : runs;
38
+ }
@@ -1,18 +1,44 @@
1
- import type { FontStyle } from "../utils/pdf-object-manager";
2
- import type { FontMetrics } from "../utils/font-metrics";
3
- import type { TextSegment } from "../elements/text-element";
1
+ import type { FontStyle } from "../utils/pdf-object-manager.js";
2
+ import type { FontMetrics } from "../utils/font-metrics.js";
3
+ import type { TextSegment } from "../elements/text-element.js";
4
+ /**
5
+ * How far a JUSTIFIED line's spaces may be squeezed to keep one more word on it.
6
+ *
7
+ * A quarter, because our breaker is GREEDY: it will use the whole allowance every time that saves a
8
+ * line, so the limit has to be the tightest word space still worth reading - about three quarters of
9
+ * natural, the usual typographic floor. TeX allows a third, but its badness model rarely spends it.
10
+ * For reference, react-pdf reaches -19% in practice (measured: a 3.18pt space squeezed to 2.58pt),
11
+ * which this covers. Its packing can still differ by a word, because it spreads the squeeze over the
12
+ * CHARACTERS as well as the spaces.
13
+ *
14
+ * Only justified text uses it: under any other alignment a squeezed line would simply overflow.
15
+ */
16
+ export declare const MAX_SPACE_SHRINK: number;
17
+ /** The knobs that change where a line ENDS, beyond the font and the box. */
18
+ export interface LineOptions {
19
+ /** Extra advance at every space (CSS `word-spacing`), in points. */
20
+ wordSpacing?: number;
21
+ /** How far the FIRST line starts in (CSS `text-indent`), in points - it has that much less room. */
22
+ indent?: number;
23
+ /** How far a justified line's spaces may be squeezed to keep one more word (0 = not justified). */
24
+ shrink?: number;
25
+ }
4
26
  /** Default font for segments that don't override it. */
5
27
  export interface SegmentDefaults {
6
28
  fontFamily: string;
7
29
  fontSize: number;
8
30
  fontStyle: FontStyle;
31
+ /** Extra space after every glyph, in points; a segment may override it. Default 0. */
32
+ letterSpacing?: number;
9
33
  }
10
- /** One laid-out line of segments. `maxFontSize` is the tallest font ON THIS LINE - its
11
- * leading - matching how real engines (and this lib's plain-string path) space lines. */
34
+ /** What happens to text beyond `maxLines`: `"clip"` drops it, `"ellipsis"` ends the last kept line
35
+ * with an ellipsis. Mirrors Flutter's `TextOverflow`. */
36
+ export type TextOverflow = "clip" | "ellipsis";
37
+ /** One laid-out line of segments. How TALL it is does not live here: the breaker owns the horizontal
38
+ * half, `text/line-metrics.ts` owns the vertical one and derives the box from the fonts on the line. */
12
39
  export interface SegmentLine {
13
40
  segments: TextSegment[];
14
41
  width: number;
15
- maxFontSize: number;
16
42
  }
17
43
  /**
18
44
  * Break a plain string into lines that each fit within `maxWidth`, splitting on
@@ -22,14 +48,26 @@ export interface SegmentLine {
22
48
  * rendering call this, so they can never disagree. Depends only on `FontMetrics`,
23
49
  * not the PDF byte writer - the future fragmentation pass can reuse it.
24
50
  */
25
- export declare function wrapStringIntoLines(text: string, fontFamily: string, fontSize: number, fontStyle: FontStyle, maxWidth: number, metrics: FontMetrics): string[];
26
51
  /**
27
- * Break styled segments into lines that fit within `maxWidth`. Same greedy
28
- * word-splitting as the string breaker, but each line records the tallest font on
29
- * THAT line as its leading (per-line, not a paragraph-global maximum). Single source
30
- * of truth: both height measurement and rendering call this.
52
+ * The width of `text` set as ONE line, accumulated in EXACTLY the order `wrapStringIntoLines` builds a
53
+ * line: the first word, then `+ (space + word)` each time. The order matters because floating-point
54
+ * addition is not associative - a box sized by a differently-grouped sum can be one bit too small for
55
+ * the very line the breaker then measures, and the text wraps inside a box made to fit it. That is not
56
+ * hypothetical: it is what split a footer sized to its own content.
57
+ */
58
+ export declare function singleLineWidth(text: string, font: {
59
+ fontFamily: string;
60
+ fontSize: number;
61
+ fontStyle: FontStyle;
62
+ }, metrics: FontMetrics, letterSpacing?: number, wordSpacing?: number): number;
63
+ export declare function wrapStringIntoLines(text: string, fontFamily: string, fontSize: number, fontStyle: FontStyle, maxWidth: number, metrics: FontMetrics, maxLines?: number, overflow?: TextOverflow, letterSpacing?: number, options?: LineOptions): string[];
64
+ /**
65
+ * Break styled segments into lines that fit within `maxWidth`. Same greedy word-splitting as the
66
+ * string breaker; each line keeps the segments that landed on it, which is what `line-metrics.ts`
67
+ * later derives the line's height from. Single source of truth: both height measurement and
68
+ * rendering call this.
31
69
  */
32
- export declare function breakSegmentsIntoLines(segments: TextSegment[], defaults: SegmentDefaults, maxWidth: number, metrics: FontMetrics): SegmentLine[];
70
+ export declare function breakSegmentsIntoLines(segments: TextSegment[], defaults: SegmentDefaults, maxWidth: number, metrics: FontMetrics, maxLines?: number, overflow?: TextOverflow): SegmentLine[];
33
71
  /**
34
72
  * Inverse of `breakSegmentsIntoLines`: flatten broken lines back into a `TextSegment[]`
35
73
  * that re-wraps to exactly those lines. The wrap consumed the space at each line break,