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

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 (279) 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 +107 -13
  28. package/dist/api/structure.js +187 -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 +139 -11
  126. package/dist/elements/text-element.js +234 -57
  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/glyph-coverage.d.ts +15 -0
  225. package/dist/text/glyph-coverage.js +77 -0
  226. package/dist/text/line-breaker.d.ts +50 -12
  227. package/dist/text/line-breaker.js +200 -54
  228. package/dist/text/line-metrics.d.ts +49 -0
  229. package/dist/text/line-metrics.js +57 -0
  230. package/dist/text/orphans-widows.d.ts +27 -0
  231. package/dist/text/orphans-widows.js +53 -0
  232. package/dist/text/shape.d.ts +29 -0
  233. package/dist/text/shape.js +67 -0
  234. package/dist/text/text-decoration.d.ts +50 -0
  235. package/dist/text/text-decoration.js +62 -0
  236. package/dist/text/text-style.d.ts +60 -0
  237. package/dist/text/text-style.js +79 -0
  238. package/dist/text/whitespace.d.ts +4 -0
  239. package/dist/text/whitespace.js +43 -0
  240. package/dist/utils/afm-parser.d.ts +34 -0
  241. package/dist/utils/afm-parser.js +76 -17
  242. package/dist/utils/bytes.d.ts +24 -0
  243. package/dist/utils/bytes.js +76 -0
  244. package/dist/utils/dest-registry.d.ts +22 -0
  245. package/dist/utils/dest-registry.js +32 -0
  246. package/dist/utils/flex-layout.d.ts +36 -9
  247. package/dist/utils/flex-layout.js +207 -30
  248. package/dist/utils/font-metrics.d.ts +26 -1
  249. package/dist/utils/font-metrics.js +1 -2
  250. package/dist/utils/font-path.js +3 -6
  251. package/dist/utils/gsub.d.ts +36 -0
  252. package/dist/utils/gsub.js +178 -0
  253. package/dist/utils/image-helper.d.ts +6 -5
  254. package/dist/utils/image-helper.js +101 -111
  255. package/dist/utils/inflate.d.ts +16 -0
  256. package/dist/utils/inflate.js +52 -0
  257. package/dist/utils/md5.d.ts +4 -0
  258. package/dist/utils/md5.js +79 -0
  259. package/dist/utils/outline.d.ts +21 -0
  260. package/dist/utils/outline.js +79 -0
  261. package/dist/utils/pdf-object-manager.d.ts +127 -8
  262. package/dist/utils/pdf-object-manager.js +615 -141
  263. package/dist/utils/renderer-registry.d.ts +13 -1
  264. package/dist/utils/renderer-registry.js +23 -6
  265. package/dist/utils/struct-tree.d.ts +44 -0
  266. package/dist/utils/struct-tree.js +129 -0
  267. package/dist/utils/ttf-parser.d.ts +134 -2
  268. package/dist/utils/ttf-parser.js +873 -50
  269. package/dist/utils/ttf-subsetter.d.ts +1 -1
  270. package/dist/utils/ttf-subsetter.js +40 -42
  271. package/dist/utils/ua-xmp.d.ts +9 -0
  272. package/dist/utils/ua-xmp.js +32 -0
  273. package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
  274. package/dist/utils/utf8-to-windows1252-encoder.js +11 -4
  275. package/dist/utils/woff.d.ts +10 -0
  276. package/dist/utils/woff.js +116 -0
  277. package/dist/validators/element-validator.d.ts +2 -2
  278. package/dist/validators/element-validator.js +31 -26
  279. package/package.json +22 -2
@@ -1,32 +1,141 @@
1
- import { Color } from "../common/color";
2
- import { FontStyle } from "../utils/pdf-object-manager";
3
- import { BoxConstraints, Offset, Size } from "../layout/box-constraints";
4
- import { Fragmentable, FragmentResult } from "../layout/fragmentation";
5
- import { HorizontalAlignment, LayoutContext, SizedPDFElement } from "./pdf-element";
1
+ import { Color } from "../common/color.js";
2
+ import { FontStyle } from "../utils/pdf-object-manager.js";
3
+ import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
4
+ import type { Direction } from "../text/bidi.js";
5
+ import { type TextTransform } from "../text/text-style.js";
6
+ import { Fragmentable, FragmentResult } from "../layout/fragmentation.js";
7
+ import { TextOverflow } from "../text/line-breaker.js";
8
+ import { HorizontalAlignment, LayoutContext, SizedPDFElement } from "./pdf-element.js";
6
9
  export interface TextSegment {
7
10
  content: string;
8
11
  fontStyle?: FontStyle;
9
12
  fontColor?: Color;
10
13
  fontFamily?: string;
14
+ /** The rest of the family stack for THIS run; unset inherits the Text's own. */
15
+ fontFallback?: string[];
11
16
  fontSize?: number;
17
+ /** External URL: this segment becomes an inline hyperlink (a /Link annotation over its glyphs). */
18
+ href?: string;
19
+ /** Internal named destination (an `Anchor`): this segment links to it (a /GoTo /Link annotation). */
20
+ dest?: string;
21
+ /** Unset inherits the Text's own setting; `true`/`false` overrides it for this run only. */
22
+ underline?: boolean;
23
+ strikethrough?: boolean;
24
+ /** Extra space after every glyph, in points; unset inherits the Text's own value. */
25
+ letterSpacing?: number;
26
+ /**
27
+ * CSS `vertical-align`, for a footnote marker or a formula index. Shifts THIS run's baseline;
28
+ * it does not resize the text, and it does not change the line's height - set `fontSize` yourself
29
+ * for the smaller look a browser's `<sup>` gets from its default stylesheet.
30
+ */
31
+ verticalAlign?: VerticalTextAlign;
12
32
  }
33
+ /** How far a run sits off the baseline, as a fraction of its font size (up is positive). */
34
+ export declare const VERTICAL_TEXT_SHIFT: Record<VerticalTextAlign, number>;
35
+ /** CSS `vertical-align`, the three values that mean something for a run of text. */
36
+ export type VerticalTextAlign = "baseline" | "super" | "sub";
37
+ /** Accessibility role for the tagged structure tree: a heading level or a paragraph (the default). */
38
+ export type TextRole = "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p";
13
39
  interface TextElementParams {
14
40
  id?: string;
15
- fontSize: number;
41
+ /** Unset (undefined) inherits the cascaded size; see ResolvedTextStyle. */
42
+ fontSize?: number;
16
43
  fontFamily?: string;
44
+ /** The rest of the family stack, for code points `fontFamily` cannot draw. */
45
+ fontFallback?: string[];
17
46
  fontStyle?: FontStyle;
18
47
  content: string | TextSegment[];
19
48
  color?: Color;
20
49
  textAlignment?: HorizontalAlignment;
50
+ /** Cap the wrapped lines (default: unlimited / open-end). */
51
+ maxLines?: number;
52
+ /** Minimum lines that must stay behind at a page break (CSS `orphans`, default 2). */
53
+ orphans?: number;
54
+ /** Minimum lines that must carry over to the next page (CSS `widows`, default 2). */
55
+ widows?: number;
56
+ /** What to do past `maxLines`: `"clip"` (default) drops them, `"ellipsis"` ends with "…". */
57
+ overflow?: TextOverflow;
58
+ /** Line-height multiplier: each line is `fontSize * lineHeight` tall. Unset means the font's
59
+ * natural line height (`ascent + descent + lineGap`), like CSS `line-height: normal`. */
60
+ lineHeight?: number;
61
+ /** Draw a line under the text, at the position and thickness the font declares. */
62
+ underline?: boolean;
63
+ /** Draw a line through the text, at half its x-height. */
64
+ strikethrough?: boolean;
65
+ /** Let the underline step around descenders. Needs an embedded font. */
66
+ skipInk?: boolean;
67
+ /** Extra space after every glyph, in points (CSS `letter-spacing`). Default 0. */
68
+ letterSpacing?: number;
69
+ /** Base writing direction (CSS `direction`); decides where a line starts. Default `"ltr"`. */
70
+ direction?: Direction;
71
+ /** CSS `text-transform`; applied before the text is measured or drawn. */
72
+ textTransform?: TextTransform;
73
+ /** CSS `word-spacing`, in points: extra advance at every space. */
74
+ wordSpacing?: number;
75
+ /** CSS `text-indent`, in points: how far the first line starts in. */
76
+ textIndent?: number;
77
+ /** Accessibility role for the tagged structure tree (heading level or paragraph; default `"p"`). */
78
+ role?: TextRole;
21
79
  }
22
80
  export declare class TextElement extends SizedPDFElement implements Fragmentable {
81
+ private readonly rawFontSize?;
82
+ private readonly rawFontFamily?;
83
+ private readonly rawFontStyle?;
84
+ private readonly rawColor?;
85
+ private readonly rawTextAlignment?;
86
+ private readonly rawLineHeight?;
87
+ private readonly rawUnderline?;
88
+ private readonly rawStrikethrough?;
89
+ private readonly rawSkipInk?;
90
+ private readonly rawLetterSpacing?;
91
+ private readonly rawDirection?;
92
+ private readonly rawFontFallback?;
93
+ private readonly rawTextTransform?;
94
+ private readonly rawWordSpacing?;
95
+ private readonly rawTextIndent?;
23
96
  private fontSize;
24
97
  private fontFamily;
25
98
  private fontStyle;
26
99
  private color;
27
- private content;
28
100
  private textAlignment;
29
- constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, }: TextElementParams);
101
+ private lineHeight?;
102
+ private underline;
103
+ private strikethrough;
104
+ private skipInk;
105
+ private letterSpacing;
106
+ private direction;
107
+ private fontFallback;
108
+ private resolved?;
109
+ private textTransform;
110
+ private wordSpacing;
111
+ private textIndent;
112
+ private content;
113
+ private maxLines?;
114
+ private orphans?;
115
+ private widows?;
116
+ private overflow;
117
+ private readonly role?;
118
+ constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, orphans, widows, overflow, lineHeight, underline, strikethrough, skipInk, letterSpacing, direction, fontFallback, textTransform, wordSpacing, textIndent, role, }: TextElementParams);
119
+ private resolveStyle;
120
+ /** Justified lines may be squeezed to keep a word; every other alignment gets no slack. Read by the
121
+ * breaker in BOTH passes, or a measured line and a drawn one would disagree about where it ends. */
122
+ /**
123
+ * The content as it is measured AND drawn - `text-transform` applied. One place, so a recased
124
+ * paragraph can never be measured in one casing and drawn in another (`ABC` and `abc` are not the
125
+ * same width in most fonts).
126
+ */
127
+ private display;
128
+ /**
129
+ * Drops or substitutes whatever the RESOLVED font cannot draw. Runs last, after the fallback stack
130
+ * has had its say, and before anything is measured - a character removed at draw time only would
131
+ * make the measured line and the drawn one disagree. A font that draws everything gets its input
132
+ * back unchanged, so an ordinary document is byte-identical.
133
+ */
134
+ private covered;
135
+ /** The content with `text-transform` applied, and nothing else. */
136
+ private recased;
137
+ private lineOptions;
138
+ private applyStyle;
30
139
  /**
31
140
  * Splits the paragraph at line boxes (Slice 1). The lines that fit in `maxHeight` stay;
32
141
  * the rest become a remainder `TextElement` re-wrapped on the next page. If not even one
@@ -36,12 +145,18 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
36
145
  fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
37
146
  private fragmentString;
38
147
  private fragmentSegments;
148
+ /** The orphan/widow minimums for this paragraph; unset means the CSS default of 2 lines each. */
149
+ private breakRule;
39
150
  private cloneWithContent;
40
151
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
41
152
  /** The unwrapped single-line width of the content (used when width is unbounded, e.g. inside a Row).
42
- * Mirrors the renderer's advance EXACTLY: per-glyph, WITHOUT kerning. `getStringWidth` subtracts
43
- * kerning, so it under-reserves and the text would wrap inside its own natural-width box - the cause
44
- * of fixed Row children wrapping despite ample space. */
153
+ * Must match the LINE-BREAKER's one-line measure EXACTLY - not just algebraically but BIT-for-bit,
154
+ * so a text laid out at this width never re-wraps inside its own natural-width box. The breaker
155
+ * accumulates `currentWidth += wordWidth + spaceWidth`, grouping the word and its trailing space
156
+ * into one term; we must group the same way. Adding word and space as two separate steps is
157
+ * algebraically equal but, because floating-point addition is not associative, drifts by a sub-ULP
158
+ * - enough to tip a borderline string (e.g. "20 Jun 2026", wider than "04 Jul 2026" only because
159
+ * 'n' beats 'l') one bit over its own width, dropping the last word onto a second line. */
45
160
  private naturalWidth;
46
161
  getProps(): {
47
162
  x: number;
@@ -54,6 +169,19 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
54
169
  color: Color;
55
170
  content: string | TextSegment[];
56
171
  textAlignment: HorizontalAlignment;
172
+ maxLines: number | undefined;
173
+ orphans: number | undefined;
174
+ widows: number | undefined;
175
+ overflow: TextOverflow;
176
+ lineHeight: number | undefined;
177
+ underline: boolean;
178
+ strikethrough: boolean;
179
+ skipInk: boolean;
180
+ letterSpacing: number;
181
+ direction: Direction;
182
+ wordSpacing: number;
183
+ textIndent: number;
184
+ role: TextRole | undefined;
57
185
  };
58
186
  }
59
187
  export {};
@@ -1,24 +1,156 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TextElement = void 0;
4
- const color_1 = require("../common/color");
5
1
  // Import the renderer DIRECTLY, not via the "../renderer" barrel: the barrel pulls in
6
2
  // pdf-renderer (and every element) while this element module is still loading, which under
7
3
  // ESM (Vite/vitest, and the future framework bindings) duplicates the element classes and
8
4
  // breaks the constructor-keyed RendererRegistry. A direct import keeps the graph acyclic.
9
- const text_renderer_1 = require("../renderer/text-renderer");
10
- const pdf_object_manager_1 = require("../utils/pdf-object-manager");
11
- const line_breaker_1 = require("../text/line-breaker");
12
- const pdf_element_1 = require("./pdf-element");
13
- class TextElement extends pdf_element_1.SizedPDFElement {
14
- constructor({ fontSize, content, fontFamily = "Helvetica", fontStyle = pdf_object_manager_1.FontStyle.Normal, color = new color_1.Color(0, 0, 0), textAlignment = pdf_element_1.HorizontalAlignment.left, }) {
5
+ import { TextRenderer } from "../renderer/text-renderer.js";
6
+ import { DEFAULT_TEXT_STYLE } from "../text/text-style.js";
7
+ import { applyTextTransform } from "../text/text-style.js";
8
+ import { splitByFont } from "../text/font-fallback.js";
9
+ import { singleLineWidth, MAX_SPACE_SHRINK, wrapStringIntoLines, breakSegmentsIntoLines, segmentLinesToSegments, } from "../text/line-breaker.js";
10
+ import { adjustForOrphansWidows, DEFAULT_ORPHANS, DEFAULT_WIDOWS, } from "../text/orphans-widows.js";
11
+ import { lineBoxForSegmentLine, lineBoxForString } from "../text/line-metrics.js";
12
+ import { HorizontalAlignment, SizedPDFElement } from "./pdf-element.js";
13
+ import { normalizeContent } from "../text/whitespace.js";
14
+ import { coverText } from "../text/glyph-coverage.js";
15
+ /** How far a run sits off the baseline, as a fraction of its font size (up is positive). */
16
+ export const VERTICAL_TEXT_SHIFT = {
17
+ baseline: 0,
18
+ super: 1 / 3,
19
+ sub: -1 / 5,
20
+ };
21
+ /**
22
+ * A measurement in points has to be a real number. A NaN or an Infinity would travel all the way into
23
+ * the content stream as a position - the backend refuses it there, but by then the message names a
24
+ * coordinate rather than the property that was wrong. Rejected here, where the caller can see it.
25
+ */
26
+ function finitePoints(value, name) {
27
+ if (value !== undefined && !Number.isFinite(value)) {
28
+ throw new Error(`@jasy/pdf: Invalid ${name} ${value}: it must be a finite number of points.`);
29
+ }
30
+ return value;
31
+ }
32
+ export class TextElement extends SizedPDFElement {
33
+ constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, orphans, widows, overflow = "clip", lineHeight, underline, strikethrough, skipInk, letterSpacing, direction, fontFallback, textTransform, wordSpacing, textIndent, role, }) {
15
34
  super({ x: 0, y: 0 });
16
- this.fontSize = fontSize;
17
- this.fontFamily = fontFamily;
18
- this.fontStyle = fontStyle;
19
- this.color = color;
20
- this.content = content;
21
- this.textAlignment = textAlignment;
35
+ this.role = role;
36
+ this.rawFontSize = fontSize;
37
+ this.rawFontFamily = fontFamily;
38
+ this.rawFontStyle = fontStyle;
39
+ this.rawColor = color;
40
+ this.rawTextAlignment = textAlignment;
41
+ this.rawLineHeight = lineHeight;
42
+ this.rawUnderline = underline;
43
+ this.rawStrikethrough = strikethrough;
44
+ this.rawSkipInk = skipInk;
45
+ this.rawLetterSpacing = letterSpacing;
46
+ this.rawDirection = direction;
47
+ this.rawFontFallback = fontFallback;
48
+ this.rawTextTransform = textTransform;
49
+ this.rawWordSpacing = finitePoints(wordSpacing, "wordSpacing");
50
+ this.rawTextIndent = finitePoints(textIndent, "textIndent");
51
+ // Control characters have no glyph and would be DRAWN as .notdef boxes; `\n` survives
52
+ // because the breaker treats it as a hard break. See text/whitespace.ts.
53
+ this.content = normalizeContent(content);
54
+ this.maxLines = maxLines;
55
+ this.orphans = orphans;
56
+ this.widows = widows;
57
+ this.overflow = overflow;
58
+ this.applyStyle(DEFAULT_TEXT_STYLE);
59
+ }
60
+ // Resolve the author-set values against the cascade: explicit > inherited (ctx) > built-in default.
61
+ resolveStyle(ctx) {
62
+ this.applyStyle(ctx.textStyle ?? DEFAULT_TEXT_STYLE);
63
+ }
64
+ /** Justified lines may be squeezed to keep a word; every other alignment gets no slack. Read by the
65
+ * breaker in BOTH passes, or a measured line and a drawn one would disagree about where it ends. */
66
+ /**
67
+ * The content as it is measured AND drawn - `text-transform` applied. One place, so a recased
68
+ * paragraph can never be measured in one casing and drawn in another (`ABC` and `abc` are not the
69
+ * same width in most fonts).
70
+ */
71
+ display(metrics) {
72
+ const cased = this.recased();
73
+ // A SPAN may bring its own stack, so the element's being empty is not enough to skip the pass.
74
+ const anyStack = this.fontFallback.length > 0 ||
75
+ (typeof cased !== "string" && cased.some((seg) => (seg.fontFallback?.length ?? 0) > 0));
76
+ if (!metrics)
77
+ return cased;
78
+ if (!anyStack)
79
+ return this.covered(cased, metrics);
80
+ // Font fallback turns the content into spans - one per family - which every later pass already
81
+ // knows how to handle. Nothing new downstream.
82
+ const pieces = typeof cased === "string" ? [{ content: cased }] : cased;
83
+ const out = [];
84
+ for (const seg of pieces) {
85
+ const runs = splitByFont(seg.content, seg.fontFamily ?? this.fontFamily, seg.fontFallback ?? this.fontFallback, seg.fontStyle ?? this.fontStyle, metrics);
86
+ if (!runs)
87
+ out.push(seg);
88
+ else
89
+ for (const run of runs)
90
+ out.push({ ...seg, content: run.text, fontFamily: run.fontFamily });
91
+ }
92
+ return this.covered(out, metrics);
93
+ }
94
+ /**
95
+ * Drops or substitutes whatever the RESOLVED font cannot draw. Runs last, after the fallback stack
96
+ * has had its say, and before anything is measured - a character removed at draw time only would
97
+ * make the measured line and the drawn one disagree. A font that draws everything gets its input
98
+ * back unchanged, so an ordinary document is byte-identical.
99
+ */
100
+ covered(content, metrics) {
101
+ const report = (dropped) => metrics.reportMissingGlyph?.(dropped);
102
+ if (typeof content === "string") {
103
+ const { text, dropped } = coverText(content, this.fontFamily, this.fontStyle, metrics);
104
+ if (dropped.length > 0)
105
+ report(dropped);
106
+ return text;
107
+ }
108
+ return content.map((seg) => {
109
+ const { text, dropped } = coverText(seg.content, seg.fontFamily ?? this.fontFamily, seg.fontStyle ?? this.fontStyle, metrics);
110
+ if (dropped.length > 0)
111
+ report(dropped);
112
+ return text === seg.content ? seg : { ...seg, content: text };
113
+ });
114
+ }
115
+ /** The content with `text-transform` applied, and nothing else. */
116
+ recased() {
117
+ if (this.textTransform === "none")
118
+ return this.content;
119
+ if (typeof this.content === "string") {
120
+ return applyTextTransform(this.content, this.textTransform).text;
121
+ }
122
+ // The capitalisation state runs THROUGH the spans: `span("hel") + span("lo")` is one word.
123
+ let atWordStart = true;
124
+ return this.content.map((seg) => {
125
+ const done = applyTextTransform(seg.content, this.textTransform, atWordStart);
126
+ atWordStart = done.atWordStart;
127
+ return { ...seg, content: done.text };
128
+ });
129
+ }
130
+ lineOptions() {
131
+ return {
132
+ wordSpacing: this.wordSpacing,
133
+ indent: this.textIndent,
134
+ // Justified lines may be squeezed to keep a word; every other alignment gets no slack.
135
+ shrink: this.textAlignment === HorizontalAlignment.justify ? MAX_SPACE_SHRINK : 0,
136
+ };
137
+ }
138
+ applyStyle(ts) {
139
+ this.fontSize = this.rawFontSize ?? ts.fontSize;
140
+ this.fontFamily = this.rawFontFamily ?? ts.fontFamily;
141
+ this.fontStyle = this.rawFontStyle ?? ts.fontStyle;
142
+ this.color = this.rawColor ?? ts.color;
143
+ this.textAlignment = this.rawTextAlignment ?? ts.textAlignment;
144
+ this.lineHeight = this.rawLineHeight ?? ts.lineHeight;
145
+ this.underline = this.rawUnderline ?? ts.underline;
146
+ this.strikethrough = this.rawStrikethrough ?? ts.strikethrough;
147
+ this.skipInk = this.rawSkipInk ?? ts.skipInk;
148
+ this.letterSpacing = this.rawLetterSpacing ?? ts.letterSpacing;
149
+ this.direction = this.rawDirection ?? ts.direction;
150
+ this.fontFallback = this.rawFontFallback ?? ts.fontFallback;
151
+ this.textTransform = this.rawTextTransform ?? ts.textTransform;
152
+ this.wordSpacing = this.rawWordSpacing ?? ts.wordSpacing;
153
+ this.textIndent = this.rawTextIndent ?? ts.textIndent;
22
154
  }
23
155
  /**
24
156
  * Splits the paragraph at line boxes (Slice 1). The lines that fit in `maxHeight` stay;
@@ -27,52 +159,70 @@ class TextElement extends pdf_element_1.SizedPDFElement {
27
159
  * the whole element on for progress. Handles both plain strings and styled segments.
28
160
  */
29
161
  fragment(maxHeight, width, ctx) {
30
- return typeof this.content === "string"
31
- ? this.fragmentString(this.content, maxHeight, width, ctx)
32
- : this.fragmentSegments(this.content, maxHeight, width, ctx);
162
+ this.resolveStyle(ctx);
163
+ const content = this.display(ctx.metrics);
164
+ return typeof content === "string"
165
+ ? this.fragmentString(content, maxHeight, width, ctx)
166
+ : this.fragmentSegments(content, maxHeight, width, ctx);
33
167
  }
34
- // Plain string: every wrapped line is `fontSize` tall (matches calculateTextHeight),
35
- // so `floor(maxHeight / fontSize)` lines fit.
168
+ // Plain string: every wrapped line gets the same box (the same one calculateTextHeight uses),
169
+ // so `floor(maxHeight / lineBox)` lines fit.
36
170
  fragmentString(content, maxHeight, width, ctx) {
37
- const lines = (0, line_breaker_1.wrapStringIntoLines)(content, this.fontFamily, this.fontSize, this.fontStyle, width, ctx.metrics);
38
- const fittedLineCount = Math.floor(maxHeight / this.fontSize);
171
+ const lines = wrapStringIntoLines(content, this.fontFamily, this.fontSize, this.fontStyle, width, ctx.metrics, this.maxLines, this.overflow, this.letterSpacing, this.lineOptions());
172
+ const box = lineBoxForString(ctx.metrics, this.fontFamily, this.fontStyle, this.fontSize, this.lineHeight);
173
+ const fittedLineCount = adjustForOrphansWidows(Math.floor(maxHeight / box.height), lines.length, this.breakRule());
39
174
  if (fittedLineCount <= 0)
40
175
  return { fitted: null, remainder: this };
41
176
  if (fittedLineCount >= lines.length)
42
177
  return { fitted: this, remainder: null };
43
178
  return {
44
179
  fitted: this.cloneWithContent(lines.slice(0, fittedLineCount).join(" ")),
45
- remainder: this.cloneWithContent(lines.slice(fittedLineCount).join(" ")),
180
+ // A continuation is no longer the paragraph's FIRST line, so it carries no indent.
181
+ remainder: this.cloneWithContent(lines.slice(fittedLineCount).join(" "), 0),
46
182
  };
47
183
  }
48
- // Styled segments: each line's height is its tallest font (maxFontSize), so pack lines
49
- // by cumulative leading. Rebuild the fitted/remainder halves back into TextSegment[].
184
+ // Styled segments: each line's height comes from the fonts on it, so pack lines by cumulative
185
+ // box height. Rebuild the fitted/remainder halves back into TextSegment[].
50
186
  fragmentSegments(content, maxHeight, width, ctx) {
51
- const lines = (0, line_breaker_1.breakSegmentsIntoLines)(content, {
187
+ const defaults = {
52
188
  fontFamily: this.fontFamily,
53
189
  fontSize: this.fontSize,
54
190
  fontStyle: this.fontStyle,
55
- }, width, ctx.metrics);
191
+ letterSpacing: this.letterSpacing,
192
+ direction: this.direction,
193
+ wordSpacing: this.wordSpacing,
194
+ textIndent: this.textIndent,
195
+ };
196
+ const lines = breakSegmentsIntoLines(content, defaults, width, ctx.metrics, this.maxLines, this.overflow);
56
197
  let used = 0;
57
- let fittedLineCount = 0;
198
+ let packed = 0;
58
199
  for (const line of lines) {
59
- if (used + line.maxFontSize > maxHeight)
200
+ const lineBox = lineBoxForSegmentLine(line, defaults, ctx.metrics, this.lineHeight).height;
201
+ if (used + lineBox > maxHeight)
60
202
  break;
61
- used += line.maxFontSize;
62
- fittedLineCount++;
203
+ used += lineBox;
204
+ packed++;
63
205
  }
206
+ const fittedLineCount = adjustForOrphansWidows(packed, lines.length, this.breakRule());
64
207
  if (fittedLineCount <= 0)
65
208
  return { fitted: null, remainder: this };
66
209
  if (fittedLineCount >= lines.length)
67
210
  return { fitted: this, remainder: null };
68
211
  return {
69
- fitted: this.cloneWithContent((0, line_breaker_1.segmentLinesToSegments)(lines.slice(0, fittedLineCount))),
70
- remainder: this.cloneWithContent((0, line_breaker_1.segmentLinesToSegments)(lines.slice(fittedLineCount))),
212
+ fitted: this.cloneWithContent(segmentLinesToSegments(lines.slice(0, fittedLineCount))),
213
+ remainder: this.cloneWithContent(segmentLinesToSegments(lines.slice(fittedLineCount)), 0),
214
+ };
215
+ }
216
+ /** The orphan/widow minimums for this paragraph; unset means the CSS default of 2 lines each. */
217
+ breakRule() {
218
+ return {
219
+ orphans: this.orphans ?? DEFAULT_ORPHANS,
220
+ widows: this.widows ?? DEFAULT_WIDOWS,
71
221
  };
72
222
  }
73
223
  // A copy carrying the same style but different (already-wrapped) content. Re-wrapping at
74
224
  // the same width reproduces exactly those lines (greedy is deterministic).
75
- cloneWithContent(content) {
225
+ cloneWithContent(content, indent = this.textIndent) {
76
226
  return new TextElement({
77
227
  content,
78
228
  fontSize: this.fontSize,
@@ -80,44 +230,59 @@ class TextElement extends pdf_element_1.SizedPDFElement {
80
230
  fontStyle: this.fontStyle,
81
231
  color: this.color,
82
232
  textAlignment: this.textAlignment,
83
- });
233
+ maxLines: this.maxLines,
234
+ orphans: this.orphans,
235
+ widows: this.widows,
236
+ overflow: this.overflow,
237
+ lineHeight: this.lineHeight,
238
+ underline: this.underline,
239
+ strikethrough: this.strikethrough,
240
+ skipInk: this.skipInk,
241
+ letterSpacing: this.letterSpacing,
242
+ direction: this.direction,
243
+ wordSpacing: this.wordSpacing,
244
+ textIndent: indent,
245
+ role: this.role,
246
+ }).adoptStructId(this); // a wrapped remainder is the SAME logical paragraph (one P across pages)
84
247
  }
85
248
  calculateLayout(constraints, offset, ctx) {
86
- var _a;
249
+ this.resolveStyle(ctx);
87
250
  this.x = offset.x;
88
251
  this.y = offset.y;
89
252
  // Bounded width (e.g. inside a Column) wraps to that width; an unbounded width
90
253
  // (e.g. inside a Row) means the text takes its natural single-line width and does
91
254
  // not wrap. Columns always bound the width, so this leaves their layout untouched.
255
+ this.resolved = this.display(ctx.metrics);
92
256
  this.width = constraints.hasBoundedWidth
93
257
  ? constraints.maxWidth
94
258
  : this.naturalWidth(ctx.metrics);
95
- const wrapWidth = (_a = this.width) !== null && _a !== void 0 ? _a : 0;
96
- this.height = text_renderer_1.TextRenderer.calculateTextHeight(this.content, this.fontSize, this.fontFamily, this.fontStyle, ctx.metrics, wrapWidth);
259
+ const wrapWidth = this.width ?? 0;
260
+ this.height = TextRenderer.calculateTextHeight(this.resolved, this.fontSize, this.fontFamily, this.fontStyle, ctx.metrics, wrapWidth, this.maxLines, this.overflow, this.lineHeight, this.letterSpacing, this.lineOptions());
97
261
  // Top-left coordinates (y = top of the text box). The baseline offset and the
98
262
  // Y-flip are applied downstream (the line-builder positions baselines, the seam
99
263
  // flips to PDF), so the element stays coordinate-system-blind.
100
264
  return { width: wrapWidth, height: this.height };
101
265
  }
102
266
  /** The unwrapped single-line width of the content (used when width is unbounded, e.g. inside a Row).
103
- * Mirrors the renderer's advance EXACTLY: per-glyph, WITHOUT kerning. `getStringWidth` subtracts
104
- * kerning, so it under-reserves and the text would wrap inside its own natural-width box - the cause
105
- * of fixed Row children wrapping despite ample space. */
267
+ * Must match the LINE-BREAKER's one-line measure EXACTLY - not just algebraically but BIT-for-bit,
268
+ * so a text laid out at this width never re-wraps inside its own natural-width box. The breaker
269
+ * accumulates `currentWidth += wordWidth + spaceWidth`, grouping the word and its trailing space
270
+ * into one term; we must group the same way. Adding word and space as two separate steps is
271
+ * algebraically equal but, because floating-point addition is not associative, drifts by a sub-ULP
272
+ * - enough to tip a borderline string (e.g. "20 Jun 2026", wider than "04 Jul 2026" only because
273
+ * 'n' beats 'l') one bit over its own width, dropping the last word onto a second line. */
106
274
  naturalWidth(metrics) {
107
- const advance = (text, family, size, style) => {
108
- let width = 0;
109
- for (const ch of text)
110
- width += metrics.getCharWidth(ch, size, undefined, family, style);
111
- return width;
112
- };
113
- if (typeof this.content === "string") {
114
- return advance(this.content, this.fontFamily, this.fontSize, this.fontStyle);
275
+ // The breaker's own sum, to the last bit - see `singleLineWidth`.
276
+ const oneLine = (text, family, size, style, letterSpacing) => singleLineWidth(text, { fontFamily: family, fontSize: size, fontStyle: style }, metrics, letterSpacing, this.wordSpacing);
277
+ // The FIRST line starts `textIndent` in, so an unbounded box has to be that much wider.
278
+ const indent = Math.max(0, this.textIndent);
279
+ const content = this.display(metrics);
280
+ if (typeof content === "string") {
281
+ return (indent +
282
+ oneLine(content, this.fontFamily, this.fontSize, this.fontStyle, this.letterSpacing));
115
283
  }
116
- return this.content.reduce((sum, seg) => {
117
- var _a, _b, _c;
118
- return sum +
119
- advance(seg.content, (_a = seg.fontFamily) !== null && _a !== void 0 ? _a : this.fontFamily, (_b = seg.fontSize) !== null && _b !== void 0 ? _b : this.fontSize, (_c = seg.fontStyle) !== null && _c !== void 0 ? _c : this.fontStyle);
120
- }, 0);
284
+ return content.reduce((sum, seg) => sum +
285
+ oneLine(seg.content, seg.fontFamily ?? this.fontFamily, seg.fontSize ?? this.fontSize, seg.fontStyle ?? this.fontStyle, seg.letterSpacing ?? this.letterSpacing), indent);
121
286
  }
122
287
  getProps() {
123
288
  return {
@@ -129,9 +294,21 @@ class TextElement extends pdf_element_1.SizedPDFElement {
129
294
  fontFamily: this.fontFamily,
130
295
  fontStyle: this.fontStyle,
131
296
  color: this.color,
132
- content: this.content,
297
+ content: this.resolved ?? this.content,
133
298
  textAlignment: this.textAlignment,
299
+ maxLines: this.maxLines,
300
+ orphans: this.orphans,
301
+ widows: this.widows,
302
+ overflow: this.overflow,
303
+ lineHeight: this.lineHeight,
304
+ underline: this.underline,
305
+ strikethrough: this.strikethrough,
306
+ skipInk: this.skipInk,
307
+ letterSpacing: this.letterSpacing,
308
+ direction: this.direction,
309
+ wordSpacing: this.wordSpacing,
310
+ textIndent: this.textIndent,
311
+ role: this.role,
134
312
  };
135
313
  }
136
314
  }
137
- exports.TextElement = TextElement;
@@ -0,0 +1,63 @@
1
+ import type { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
+ import type { FormFieldNode } from "../ir/display-list.js";
3
+ /**
4
+ * Collects the document's form fields as pages render, then emits the catalog `/AcroForm` dictionary at
5
+ * finalize. Mirrors `OutlineBuilder` / `DestRegistry`: a no-op returning "" when no field was placed, so
6
+ * a document without a form stays byte-identical.
7
+ */
8
+ export declare class AcroFormCollector {
9
+ private fieldRefs;
10
+ private radioGroups;
11
+ private needAppearances;
12
+ private helvNum?;
13
+ private zadbNum?;
14
+ private hasSignature;
15
+ private usedNames;
16
+ private bake;
17
+ /** Turn appearance baking off (`renderToBytes(doc, { fieldAppearances: false })`). */
18
+ setBakeAppearances(on: boolean): void;
19
+ /** The size to draw a value at. A field may ask for `0` - the PDF convention for "auto-size"; we then
20
+ * fit the capitals comfortably inside the box, which is what a viewer's auto-size does. */
21
+ private drawSize;
22
+ /** Measure one line for the appearance generator. */
23
+ private line;
24
+ /** Bake a text field's value into an appearance stream; returns the XObject number. */
25
+ private bakeText;
26
+ /** Bake a choice field: a combo shows its current value on one line, a list box shows its options
27
+ * with the selected rows highlighted. */
28
+ private bakeChoice;
29
+ get isEmpty(): boolean;
30
+ private helv;
31
+ /**
32
+ * ZapfDingbats, the font a check mark lives in. Needed even though we bake the appearance ourselves:
33
+ * a viewer told to regenerate (`/NeedAppearances`, which filling a form sets) redraws a check box from
34
+ * its `/DA`, and the conventional `/DA` for one names `/ZaDb`. Without the matching `/DR` entry the
35
+ * viewer cannot resolve the font - poppler says "Unknown font tag 'ZaDb'" and draws no check at all.
36
+ * No encoding: ZapfDingbats brings its own.
37
+ */
38
+ private zadb;
39
+ /**
40
+ * A field name is its identity: two fields sharing one are ONE field to every viewer, showing the same
41
+ * value in both places and overwriting each other as the reader types. The format allows it and says
42
+ * nothing, so the mistake surfaces only when someone fills the form. Named here instead.
43
+ *
44
+ * A radio GROUP is the exception - its buttons deliberately share a name, which is what makes them
45
+ * mutually exclusive - so it never comes through here.
46
+ */
47
+ private claimName;
48
+ /** Emit the widget object for one form-field IR node, register it as a field, and return its object
49
+ * number so the PageRenderer can add it to that page's /Annots. */
50
+ addField(node: FormFieldNode, om: PDFObjectManager): number;
51
+ /** A push button (/Btn, Pushbutton flag): no value, an optional action, and a baked caption. The
52
+ * caption is measured here (the appearance module stays free of font metrics) and drawn into an
53
+ * appearance stream that carries its own font resource. */
54
+ private buildPushButtonWidget;
55
+ /** A radio button: one KID widget of its group's shared field. The group field is reserved on first
56
+ * sight (and added to /Fields); each button adds itself to that field's /Kids. Returns the kid widget
57
+ * number, which goes into the PAGE /Annots (the parent field does not). */
58
+ private addRadio;
59
+ /** A signature field (/Sig): an EMPTY placeholder with a baked "sign here" face. No /V - an unsigned
60
+ * field has no value; a signing tool fills that in (and replaces this appearance) later. */
61
+ private buildSignatureWidget;
62
+ finalize(om: PDFObjectManager): string;
63
+ }