@jasy/pdf 1.0.0-alpha.6 → 1.0.0-alpha.8

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 (116) hide show
  1. package/README.md +7 -7
  2. package/dist/api/descriptor.js +26 -1
  3. package/dist/api/forms.d.ts +347 -0
  4. package/dist/api/forms.js +189 -0
  5. package/dist/api/index.d.ts +1 -0
  6. package/dist/api/index.js +1 -0
  7. package/dist/api/layout.d.ts +47 -14
  8. package/dist/api/layout.js +47 -11
  9. package/dist/api/structure.d.ts +11 -0
  10. package/dist/api/structure.js +2 -0
  11. package/dist/api/text.d.ts +18 -2
  12. package/dist/api/text.js +15 -0
  13. package/dist/crypto/legacy-handler.d.ts +43 -0
  14. package/dist/crypto/legacy-handler.js +145 -0
  15. package/dist/crypto/security-handler.d.ts +16 -0
  16. package/dist/crypto/security-handler.js +26 -0
  17. package/dist/edit/acroform-reader.d.ts +70 -0
  18. package/dist/edit/acroform-reader.js +126 -0
  19. package/dist/edit/appearance.d.ts +47 -0
  20. package/dist/edit/appearance.js +171 -0
  21. package/dist/edit/document.d.ts +135 -0
  22. package/dist/edit/document.js +649 -0
  23. package/dist/edit/fill.d.ts +48 -0
  24. package/dist/edit/fill.js +395 -0
  25. package/dist/edit/flatten.d.ts +42 -0
  26. package/dist/edit/flatten.js +437 -0
  27. package/dist/edit/index.d.ts +18 -0
  28. package/dist/edit/index.js +16 -0
  29. package/dist/edit/lexer.d.ts +30 -0
  30. package/dist/edit/lexer.js +285 -0
  31. package/dist/edit/objects.d.ts +63 -0
  32. package/dist/edit/objects.js +56 -0
  33. package/dist/edit/writer.d.ts +53 -0
  34. package/dist/edit/writer.js +261 -0
  35. package/dist/elements/container-element.d.ts +10 -1
  36. package/dist/elements/container-element.js +26 -8
  37. package/dist/elements/forms/checkbox-element.d.ts +51 -0
  38. package/dist/elements/forms/checkbox-element.js +48 -0
  39. package/dist/elements/forms/choice-element.d.ts +53 -0
  40. package/dist/elements/forms/choice-element.js +59 -0
  41. package/dist/elements/forms/push-button-element.d.ts +56 -0
  42. package/dist/elements/forms/push-button-element.js +49 -0
  43. package/dist/elements/forms/radio-element.d.ts +52 -0
  44. package/dist/elements/forms/radio-element.js +49 -0
  45. package/dist/elements/forms/signature-element.d.ts +54 -0
  46. package/dist/elements/forms/signature-element.js +47 -0
  47. package/dist/elements/forms/text-field-element.d.ts +61 -0
  48. package/dist/elements/forms/text-field-element.js +54 -0
  49. package/dist/elements/index.d.ts +8 -0
  50. package/dist/elements/index.js +8 -0
  51. package/dist/elements/layout/keep-together-element.d.ts +42 -0
  52. package/dist/elements/layout/keep-together-element.js +85 -0
  53. package/dist/elements/layout/padding-element.d.ts +3 -0
  54. package/dist/elements/layout/padding-element.js +12 -0
  55. package/dist/elements/layout/page-break-element.d.ts +17 -0
  56. package/dist/elements/layout/page-break-element.js +24 -0
  57. package/dist/elements/layout/positioned-element.d.ts +10 -7
  58. package/dist/elements/layout/positioned-element.js +21 -9
  59. package/dist/elements/page-element.d.ts +11 -1
  60. package/dist/elements/page-element.js +41 -16
  61. package/dist/elements/pdf-element.d.ts +28 -0
  62. package/dist/elements/pdf-element.js +32 -0
  63. package/dist/elements/rectangle-element.d.ts +10 -1
  64. package/dist/elements/rectangle-element.js +27 -6
  65. package/dist/elements/row-element.d.ts +9 -1
  66. package/dist/elements/row-element.js +9 -1
  67. package/dist/elements/text-element.d.ts +30 -4
  68. package/dist/elements/text-element.js +40 -17
  69. package/dist/forms/acroform.d.ts +63 -0
  70. package/dist/forms/acroform.js +427 -0
  71. package/dist/forms/appearance.d.ts +58 -0
  72. package/dist/forms/appearance.js +204 -0
  73. package/dist/forms/field.d.ts +164 -0
  74. package/dist/forms/field.js +1 -0
  75. package/dist/forms/pdf.d.ts +19 -0
  76. package/dist/forms/pdf.js +28 -0
  77. package/dist/ir/display-list.d.ts +18 -1
  78. package/dist/layout/fragmentation.d.ts +12 -0
  79. package/dist/layout/fragmentation.js +45 -8
  80. package/dist/renderer/form-field-renderer.d.ts +20 -0
  81. package/dist/renderer/form-field-renderer.js +12 -0
  82. package/dist/renderer/keep-together-renderer.d.ts +10 -0
  83. package/dist/renderer/keep-together-renderer.js +12 -0
  84. package/dist/renderer/page-break-renderer.d.ts +14 -0
  85. package/dist/renderer/page-break-renderer.js +15 -0
  86. package/dist/renderer/page-renderer.js +8 -2
  87. package/dist/renderer/pdf-backend.d.ts +7 -0
  88. package/dist/renderer/pdf-backend.js +48 -6
  89. package/dist/renderer/pdf-document-class.js +3 -0
  90. package/dist/renderer/pdf-document-renderer.js +26 -7
  91. package/dist/renderer/pdf-renderer.js +25 -1
  92. package/dist/renderer/text-renderer.d.ts +1 -1
  93. package/dist/renderer/text-renderer.js +107 -59
  94. package/dist/text/advance.d.ts +34 -0
  95. package/dist/text/advance.js +42 -0
  96. package/dist/text/line-breaker.d.ts +9 -8
  97. package/dist/text/line-breaker.js +28 -21
  98. package/dist/text/line-metrics.d.ts +49 -0
  99. package/dist/text/line-metrics.js +57 -0
  100. package/dist/text/text-decoration.d.ts +50 -0
  101. package/dist/text/text-decoration.js +62 -0
  102. package/dist/text/text-style.d.ts +13 -3
  103. package/dist/text/text-style.js +10 -3
  104. package/dist/utils/afm-parser.d.ts +34 -0
  105. package/dist/utils/afm-parser.js +67 -0
  106. package/dist/utils/dest-registry.d.ts +5 -2
  107. package/dist/utils/dest-registry.js +5 -5
  108. package/dist/utils/font-metrics.d.ts +14 -0
  109. package/dist/utils/outline.js +1 -2
  110. package/dist/utils/pdf-object-manager.d.ts +45 -1
  111. package/dist/utils/pdf-object-manager.js +193 -26
  112. package/dist/utils/struct-tree.js +2 -3
  113. package/dist/utils/ttf-parser.d.ts +53 -0
  114. package/dist/utils/ttf-parser.js +368 -0
  115. package/dist/validators/element-validator.js +7 -3
  116. package/package.json +9 -2
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- <code>npm i @jasy/zugferd</code> &nbsp;·&nbsp; <code>npx @jasy/cli</code> &nbsp;·&nbsp; MIT
13
+ <code>npm i @jasy/e-invoice</code> &nbsp;·&nbsp; <code>npx @jasy/cli</code> &nbsp;·&nbsp; MIT
14
14
  </p>
15
15
 
16
16
  ---
@@ -42,7 +42,7 @@ invoices validate: the amounts are correct **by construction**, so the single bi
42
42
  EN-16931 failures (the BR-CO total checks) simply cannot happen.
43
43
 
44
44
  ```ts
45
- import { renderZugferd } from "@jasy/zugferd";
45
+ import { renderZugferd } from "@jasy/e-invoice";
46
46
 
47
47
  const { bytes, xml } = await renderZugferd({
48
48
  number: "RE-2026-001",
@@ -148,11 +148,11 @@ verify all of it:
148
148
 
149
149
  ## Packages
150
150
 
151
- | Package | What it is |
152
- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
153
- | **[@jasy/zugferd](https://npmx.dev/@jasy/zugferd)** | ZUGFeRD / XRechnung: your data → PDF/A-3 + EN-16931 XML, with local validation. **The prize.** |
154
- | **[@jasy/cli](https://npmx.dev/@jasy/cli)** | the `jasy` terminal: read · validate · export, headless **and** interactive |
155
- | **[@jasy/pdf](https://npmx.dev/@jasy/pdf)** | the declarative, Flutter-style PDF layout engine that powers them |
151
+ | Package | What it is |
152
+ | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
153
+ | **[@jasy/e-invoice](https://npmx.dev/@jasy/e-invoice)** | ZUGFeRD / XRechnung: your data → PDF/A-3 + EN-16931 XML, with local validation. **The prize.** |
154
+ | **[@jasy/cli](https://npmx.dev/@jasy/cli)** | the `jasy` terminal: read · validate · export, headless **and** interactive |
155
+ | **[@jasy/pdf](https://npmx.dev/@jasy/pdf)** | the declarative, Flutter-style PDF layout engine that powers them |
156
156
 
157
157
  ---
158
158
 
@@ -1,9 +1,10 @@
1
1
  import { Text, Paragraph, span } from "./text.js";
2
- import { Column, Row, Box, Padding, Spacer, Expanded, Positioned, Link, Anchor, Bookmark, Rotated, RotatedBox, } from "./layout.js";
2
+ import { Column, Row, Box, Padding, Spacer, PageBreak, keepTogether, Expanded, Positioned, Link, Anchor, Bookmark, Rotated, RotatedBox, } from "./layout.js";
3
3
  import { Divider, Image } from "./content.js";
4
4
  import { Page, Document, DefaultTextStyle } from "./structure.js";
5
5
  import { PageNumber, PageCount } from "./page-builder.js";
6
6
  import { Table } from "./table.js";
7
+ import { Checkbox, Dropdown, ListBox, PushButton, RadioGroup, SignatureField, TextField, } from "./forms.js";
7
8
  // Element children (Column/Row/Box/Page/…) → built recursively. Text/Paragraph children are
8
9
  // strings or `span` descriptors and become content instead.
9
10
  function elementChildren(children) {
@@ -85,6 +86,8 @@ const REGISTRY = {
85
86
  padding: (props, children) => Padding(props.insets ?? 0, elementChildren(children)[0]),
86
87
  expanded: (props, children) => Expanded(props, elementChildren(children)[0]),
87
88
  spacer: (props) => Spacer(props?.flex),
89
+ "page-break": () => PageBreak(),
90
+ "keep-together": (_props, children) => keepTogether(elementChildren(children)),
88
91
  divider: (props) => Divider(props),
89
92
  image: (props) => Image(props.src, props),
90
93
  text: (props, children) => Text(textContent(children), props),
@@ -110,7 +113,29 @@ const REGISTRY = {
110
113
  // cannot express. These two cover what the closure exists for.
111
114
  "page-number": (props) => PageNumber(props),
112
115
  "page-count": (props) => PageCount(props),
116
+ // Form fields. The three list-like ones take their choices as a SECOND argument in TypeScript; a
117
+ // template has no second argument, so here they arrive as an `options` prop. `choiceList` accepts both
118
+ // the short spelling and the explicit one, because an export value and its label are usually the same
119
+ // string and having to write it twice is noise.
120
+ "text-field": (props) => TextField(props),
121
+ checkbox: (props) => Checkbox(props),
122
+ "radio-group": (props) => RadioGroup(props, radioChoices(props.options)),
123
+ dropdown: (props) => Dropdown(props, choiceList(props.options)),
124
+ "list-box": (props) => ListBox(props, choiceList(props.options)),
125
+ "push-button": (props) => PushButton(props),
126
+ "signature-field": (props) => SignatureField(props),
113
127
  };
128
+ /** `["S", "M"]` or `[{ value: "DE", label: "Germany" }]` - both mean the same thing to a choice field. */
129
+ function choiceList(raw) {
130
+ if (!Array.isArray(raw)) {
131
+ throw new Error("jasy: a choice field needs an `options` array (strings or {value, label}).");
132
+ }
133
+ return raw;
134
+ }
135
+ /** A radio button always shows a label, so the short spelling means "label is the value". */
136
+ function radioChoices(raw) {
137
+ return choiceList(raw).map((o) => typeof o === "string" ? { value: o, label: o } : { value: o.value, label: o.label ?? o.value });
138
+ }
114
139
  /**
115
140
  * Registers a custom element type, so a binding (or a user-defined component) can introduce
116
141
  * its own tag that resolves to an engine element through this same seam. Overwrites an
@@ -0,0 +1,347 @@
1
+ import { PDFElement } from "../elements/pdf-element.js";
2
+ import { TextFieldElement } from "../elements/forms/text-field-element.js";
3
+ import { RadioElement } from "../elements/forms/radio-element.js";
4
+ import { ChoiceElement } from "../elements/forms/choice-element.js";
5
+ import { PushButtonElement } from "../elements/forms/push-button-element.js";
6
+ import { SignatureFieldElement } from "../elements/forms/signature-element.js";
7
+ import type { FieldAlign } from "../forms/field.js";
8
+ import { ColorInput } from "./color.js";
9
+ /** A choice option: a plain string (value = label) or `{ value, label }`. */
10
+ export type ChoiceOptionInput = string | {
11
+ value: string;
12
+ label?: string;
13
+ };
14
+ /** Options for a `TextField` - an interactive text input in the generated PDF. */
15
+ export interface TextFieldOptions {
16
+ /** The field name - unique in the document; you read the value back by it. */
17
+ name: string;
18
+ /** Initial value. */
19
+ value?: string;
20
+ /** Tooltip / accessible name shown on hover. */
21
+ tooltip?: string;
22
+ /** Accept multiple lines instead of one (give the box a `height` to match). */
23
+ multiline?: boolean;
24
+ /** Mask the typed characters (a password field). */
25
+ password?: boolean;
26
+ /** How the value sits in the box: left (default), center or right. */
27
+ align?: FieldAlign;
28
+ /** Maximum number of characters. */
29
+ maxLength?: number;
30
+ /** Show the value but do not let the user edit it. */
31
+ readOnly?: boolean;
32
+ /** Mark it as a field that must be filled in before the form is submitted. */
33
+ required?: boolean;
34
+ /** Hide the widget entirely - neither on screen nor in print. */
35
+ hidden?: boolean;
36
+ /** Include the widget when printing (default true). `false` keeps it on screen only. */
37
+ print?: boolean;
38
+ /** Box width in points; omit to fill the offered width. */
39
+ width?: number;
40
+ /** Box height in points; omit for a single-line default. */
41
+ height?: number;
42
+ /** Value font size in points; `0` lets the viewer auto-size. */
43
+ fontSize?: number;
44
+ /** Value text colour (named / hex / rgb). */
45
+ color?: ColorInput;
46
+ /** Box border colour; omit for no border. */
47
+ border?: ColorInput;
48
+ /** Box background fill; omit for transparent. */
49
+ background?: ColorInput;
50
+ /** Border thickness in points (default 1 when a border is set). */
51
+ borderWidth?: number;
52
+ }
53
+ /**
54
+ * An interactive text field the reader of the PDF can type into (AcroForm /Tx). Place it like any element -
55
+ * in a `Column`, `Row`, `Box` or a table cell; it reserves its box and becomes a fillable widget.
56
+ *
57
+ * ```ts
58
+ * TextField({ name: "email", border: "gray", height: 22 })
59
+ * ```
60
+ */
61
+ export declare function TextField(opts: TextFieldOptions): TextFieldElement;
62
+ /** Options for a `Checkbox` - a toggle the reader of the PDF can tick. */
63
+ export interface CheckboxOptions {
64
+ /** The field name - unique in the document. */
65
+ name: string;
66
+ /** Text placed beside the box. Given one, the box and the text become a row - the same shape
67
+ * `RadioGroup` builds for its choices, so a lone check box does not have to be assembled by hand. */
68
+ label?: string;
69
+ /** Size of the label text (default 12). */
70
+ labelSize?: number;
71
+ /** Colour of the label text. */
72
+ labelColor?: ColorInput;
73
+ /** Space between the box and its label, in points (default 8). */
74
+ labelGap?: number;
75
+ /** Whether it starts checked. */
76
+ checked?: boolean;
77
+ /** The "on" export value stored when checked (default `"Yes"`). */
78
+ onValue?: string;
79
+ /** Tooltip / accessible name shown on hover. */
80
+ tooltip?: string;
81
+ /** Show the state but do not let the user toggle it. */
82
+ readOnly?: boolean;
83
+ /** Mark it as a field that must be filled in before the form is submitted. */
84
+ required?: boolean;
85
+ /** Hide the widget entirely - neither on screen nor in print. */
86
+ hidden?: boolean;
87
+ /** Include the widget when printing (default true). `false` keeps it on screen only. */
88
+ print?: boolean;
89
+ /** Box side length in points (default 14). */
90
+ size?: number;
91
+ /** Checkmark colour (default black). */
92
+ color?: ColorInput;
93
+ /** Box border colour (default a mid grey). */
94
+ border?: ColorInput;
95
+ /** Box background fill. */
96
+ background?: ColorInput;
97
+ /** Border thickness in points (default 1). */
98
+ borderWidth?: number;
99
+ }
100
+ /**
101
+ * An interactive checkbox (AcroForm /Btn). It bakes its own checkmark appearance, so the tick shows in
102
+ * any viewer, in print, and under PDF/A - not only in ones that regenerate appearances.
103
+ *
104
+ * ```ts
105
+ * Row({ gap: 6, align: "center" }, [Checkbox({ name: "agree" }), Text("I agree")])
106
+ * ```
107
+ */
108
+ export declare function Checkbox(opts: CheckboxOptions): PDFElement;
109
+ /** Options for a single `Radio` button. Several sharing a `group` are one mutually-exclusive field. */
110
+ export interface RadioOptions {
111
+ /** The shared group name - radios with the same `group` are one field. */
112
+ group: string;
113
+ /** This button's export value - unique within the group. */
114
+ value: string;
115
+ /** Whether it starts selected. */
116
+ selected?: boolean;
117
+ /** Tooltip / accessible name. */
118
+ tooltip?: string;
119
+ /** Show but do not allow changing. */
120
+ readOnly?: boolean;
121
+ /** Mark it as a field that must be filled in before the form is submitted. */
122
+ required?: boolean;
123
+ /** Hide the widget entirely - neither on screen nor in print. */
124
+ hidden?: boolean;
125
+ /** Include the widget when printing (default true). `false` keeps it on screen only. */
126
+ print?: boolean;
127
+ /** Button diameter in points (default 14). */
128
+ size?: number;
129
+ /** Dot colour. */
130
+ color?: ColorInput;
131
+ /** Ring colour. */
132
+ border?: ColorInput;
133
+ /** Fill behind the ring. */
134
+ background?: ColorInput;
135
+ /** Ring thickness. */
136
+ borderWidth?: number;
137
+ }
138
+ /** A single radio button. Use `RadioGroup` for the common labelled-list layout, or this for custom ones. */
139
+ export declare function Radio(opts: RadioOptions): RadioElement;
140
+ /** One option in a `RadioGroup`. */
141
+ export interface RadioChoice {
142
+ /** The export value stored when this option is picked. */
143
+ value: string;
144
+ /** The text shown next to the button. */
145
+ label: string;
146
+ }
147
+ /** Options for a `RadioGroup` - the labelled list of mutually-exclusive buttons. */
148
+ export interface RadioGroupOptions {
149
+ /** The field name (shared by every button). */
150
+ name: string;
151
+ /** Mark the group as required. */
152
+ required?: boolean;
153
+ /** Hide every button - neither on screen nor in print. */
154
+ hidden?: boolean;
155
+ /** Include the buttons when printing (default true). */
156
+ print?: boolean;
157
+ /** The value of the option that starts selected (matches one option's `value`). */
158
+ value?: string;
159
+ /** Vertical space between options (default 8). */
160
+ gap?: number;
161
+ /** Button diameter (default 14). */
162
+ size?: number;
163
+ /** Button ring / dot / background colours, applied to every option. */
164
+ color?: ColorInput;
165
+ border?: ColorInput;
166
+ background?: ColorInput;
167
+ /** Label font size (default 12) and colour. */
168
+ labelSize?: number;
169
+ labelColor?: ColorInput;
170
+ }
171
+ /**
172
+ * A labelled group of mutually-exclusive radio buttons. Sugar over `Radio` + `Text`: it lays each option
173
+ * out as a button-and-label row in a `Column`, and marks the one whose `value` matches the group `value`.
174
+ *
175
+ * ```ts
176
+ * RadioGroup({ name: "size", value: "m" }, [
177
+ * { value: "s", label: "Small" },
178
+ * { value: "m", label: "Medium" },
179
+ * { value: "l", label: "Large" },
180
+ * ])
181
+ * ```
182
+ */
183
+ export declare function RadioGroup(opts: RadioGroupOptions, options: RadioChoice[]): PDFElement;
184
+ /** Style + geometry shared by `Dropdown` and `ListBox`. */
185
+ interface ChoiceStyleOptions {
186
+ /** How the value sits in the box: left (default), center or right. */
187
+ align?: FieldAlign;
188
+ tooltip?: string;
189
+ readOnly?: boolean;
190
+ /** Mark it as a field that must be filled in before the form is submitted. */
191
+ required?: boolean;
192
+ /** Hide the widget entirely - neither on screen nor in print. */
193
+ hidden?: boolean;
194
+ /** Include the widget when printing (default true). `false` keeps it on screen only. */
195
+ print?: boolean;
196
+ width?: number;
197
+ height?: number;
198
+ fontSize?: number;
199
+ color?: ColorInput;
200
+ border?: ColorInput;
201
+ background?: ColorInput;
202
+ borderWidth?: number;
203
+ }
204
+ /** Options for a `Dropdown` (combo box). */
205
+ export interface DropdownOptions extends ChoiceStyleOptions {
206
+ /** The field name. */
207
+ name: string;
208
+ /** The selected value. */
209
+ value?: string;
210
+ /** Let the reader type a value that is not in the list. */
211
+ editable?: boolean;
212
+ }
213
+ /**
214
+ * A dropdown / combo box (AcroForm /Ch). Options are plain strings or `{ value, label }`.
215
+ *
216
+ * ```ts
217
+ * Dropdown({ name: "country", value: "de" }, [{ value: "de", label: "Germany" }, "France"])
218
+ * ```
219
+ */
220
+ export declare function Dropdown(opts: DropdownOptions, options: ChoiceOptionInput[]): ChoiceElement;
221
+ /** `Select` is an alias for `Dropdown`. */
222
+ export declare const Select: typeof Dropdown;
223
+ /** Options for a `ListBox`. */
224
+ export interface ListBoxOptions extends ChoiceStyleOptions {
225
+ /** The field name. */
226
+ name: string;
227
+ /** The selected value (single-select). */
228
+ value?: string;
229
+ /** The selected values (when `multiSelect`). */
230
+ values?: string[];
231
+ /** Allow more than one selection. */
232
+ multiSelect?: boolean;
233
+ }
234
+ /**
235
+ * A scrollable list box (AcroForm /Ch). Give it a `height` to show more rows. Set `multiSelect` for
236
+ * several selections at once.
237
+ */
238
+ export declare function ListBox(opts: ListBoxOptions, options: ChoiceOptionInput[]): ChoiceElement;
239
+ /**
240
+ * What a `PushButton` does when clicked: `"reset"` clears the form, `{ submit: url }` posts the field
241
+ * values, `{ open: url }` opens a link. Omit it for a button that does nothing. Scripted (JavaScript)
242
+ * actions are deliberately not offered - a jasy form is data, not a program.
243
+ *
244
+ * VIEWER SUPPORT. All three actions we emit are spec-correct; what differs is what a viewer implements.
245
+ * PROVEN, not assumed - the same cases built with pdf-lib (an independent producer) behave identically,
246
+ * so none of this is a jasy bug. Measured 2026-07 in pdf.js (VS Code) and poppler/Evince:
247
+ * - `"reset"` - works in both. The safe one.
248
+ * - `{ open: url }` - works in pdf.js, dead in Evince, and NOT fixable from here: poppler's glib
249
+ * layer surfaces actions only through `poppler_page_get_link_mapping` (i.e.
250
+ * /Link annotations); its form-field API has no action getter at all, so a
251
+ * WIDGET's /A can never be reached. **For a plain hyperlink use `Link({ href })`**
252
+ * - the identical URI action on a real /Link annotation works in both.
253
+ * - `{ submit: url }` - Acrobat only. `POPPLER_ACTION_SUBMIT_FORM` does not exist in poppler-glib (no
254
+ * such enum value), and pdf.js does not implement /SubmitForm either.
255
+ * Ruled out as causes: pdf-lib splits field and widget, sets /P and a /D appearance - and still fails
256
+ * the same way, so none of those structural differences matter.
257
+ */
258
+ export type ButtonActionInput = "reset" | {
259
+ submit: string;
260
+ } | {
261
+ open: string;
262
+ };
263
+ /** Options for a `PushButton`. */
264
+ export interface PushButtonOptions {
265
+ /** The field name - unique in the document. */
266
+ name: string;
267
+ /** The caption drawn on the button. */
268
+ label: string;
269
+ /** What clicking it does. */
270
+ action?: ButtonActionInput;
271
+ /** Tooltip / accessible name shown on hover. */
272
+ tooltip?: string;
273
+ /** Draw it but do not let it be clicked. */
274
+ readOnly?: boolean;
275
+ /** Mark it as a field that must be filled in before the form is submitted. */
276
+ required?: boolean;
277
+ /** Hide the widget entirely - neither on screen nor in print. */
278
+ hidden?: boolean;
279
+ /** Include the widget when printing (default true). `false` keeps it on screen only. */
280
+ print?: boolean;
281
+ /** Width in points; omit to fill the offered width. */
282
+ width?: number;
283
+ /** Height in points; omit for a comfortable default. */
284
+ height?: number;
285
+ /** Caption font size (default 12). */
286
+ fontSize?: number;
287
+ /** Caption colour. */
288
+ color?: ColorInput;
289
+ /** Border colour. */
290
+ border?: ColorInput;
291
+ /** Button face fill. */
292
+ background?: ColorInput;
293
+ /** Border thickness in points. */
294
+ borderWidth?: number;
295
+ }
296
+ /**
297
+ * A clickable push button (AcroForm /Btn). It holds no value - it fires an action. Its caption is baked
298
+ * into the appearance, so the button looks the same in every viewer and in print.
299
+ *
300
+ * ```ts
301
+ * PushButton({ name: "send", label: "Submit", action: { submit: "https://example.com/f" }, width: 120 })
302
+ * PushButton({ name: "clear", label: "Reset", action: "reset", width: 90 })
303
+ * ```
304
+ */
305
+ export declare function PushButton(opts: PushButtonOptions): PushButtonElement;
306
+ /** Options for a `SignatureField`. */
307
+ export interface SignatureFieldOptions {
308
+ /** The field name - unique in the document. */
309
+ name: string;
310
+ /** A hint drawn inside the empty box, e.g. "Signature" or "Approved by". */
311
+ label?: string;
312
+ /** Tooltip / accessible name shown on hover. */
313
+ tooltip?: string;
314
+ /** Draw it but do not let it be signed. */
315
+ readOnly?: boolean;
316
+ /** Mark it as a field that must be filled in before the form is submitted. */
317
+ required?: boolean;
318
+ /** Hide the widget entirely - neither on screen nor in print. */
319
+ hidden?: boolean;
320
+ /** Include the widget when printing (default true). `false` keeps it on screen only. */
321
+ print?: boolean;
322
+ /** Width in points; omit to fill the offered width. */
323
+ width?: number;
324
+ /** Height in points (default 48). */
325
+ height?: number;
326
+ /** Hint font size (default 9). */
327
+ fontSize?: number;
328
+ /** Hint colour. */
329
+ color?: ColorInput;
330
+ /** Box border + signing-rule colour. */
331
+ border?: ColorInput;
332
+ /** Box background fill. */
333
+ background?: ColorInput;
334
+ /** Border thickness in points. */
335
+ borderWidth?: number;
336
+ }
337
+ /**
338
+ * A signature field (AcroForm /Sig) - a placeholder someone can sign later in a PDF tool. jasy creates
339
+ * the field and draws its empty "sign here" box; it does not apply signatures itself (real signing needs
340
+ * a certificate and a byte-range digest).
341
+ *
342
+ * ```ts
343
+ * SignatureField({ name: "approver", label: "Signature", width: 240 })
344
+ * ```
345
+ */
346
+ export declare function SignatureField(opts: SignatureFieldOptions): SignatureFieldElement;
347
+ export {};
@@ -0,0 +1,189 @@
1
+ import { TextFieldElement } from "../elements/forms/text-field-element.js";
2
+ import { CheckboxElement } from "../elements/forms/checkbox-element.js";
3
+ import { RadioElement } from "../elements/forms/radio-element.js";
4
+ import { ChoiceElement } from "../elements/forms/choice-element.js";
5
+ import { PushButtonElement } from "../elements/forms/push-button-element.js";
6
+ import { SignatureFieldElement } from "../elements/forms/signature-element.js";
7
+ import { toColor } from "./color.js";
8
+ import { Column, Row } from "./layout.js";
9
+ import { Text } from "./text.js";
10
+ const toOptions = (opts) => opts.map((o) => (typeof o === "string" ? { value: o } : o));
11
+ /**
12
+ * An interactive text field the reader of the PDF can type into (AcroForm /Tx). Place it like any element -
13
+ * in a `Column`, `Row`, `Box` or a table cell; it reserves its box and becomes a fillable widget.
14
+ *
15
+ * ```ts
16
+ * TextField({ name: "email", border: "gray", height: 22 })
17
+ * ```
18
+ */
19
+ export function TextField(opts) {
20
+ return new TextFieldElement({
21
+ ...opts,
22
+ color: opts.color !== undefined ? toColor(opts.color) : undefined,
23
+ border: opts.border !== undefined ? toColor(opts.border) : undefined,
24
+ background: opts.background !== undefined ? toColor(opts.background) : undefined,
25
+ });
26
+ }
27
+ /**
28
+ * An interactive checkbox (AcroForm /Btn). It bakes its own checkmark appearance, so the tick shows in
29
+ * any viewer, in print, and under PDF/A - not only in ones that regenerate appearances.
30
+ *
31
+ * ```ts
32
+ * Row({ gap: 6, align: "center" }, [Checkbox({ name: "agree" }), Text("I agree")])
33
+ * ```
34
+ */
35
+ export function Checkbox(opts) {
36
+ const box = new CheckboxElement({
37
+ ...opts,
38
+ color: opts.color !== undefined ? toColor(opts.color) : undefined,
39
+ border: opts.border !== undefined ? toColor(opts.border) : undefined,
40
+ background: opts.background !== undefined ? toColor(opts.background) : undefined,
41
+ });
42
+ // Without a label the box IS the element, so nothing wraps it and the output is unchanged.
43
+ if (opts.label === undefined)
44
+ return box;
45
+ return Row({ gap: opts.labelGap ?? 8, align: "center" }, [
46
+ box,
47
+ Text(opts.label, { size: opts.labelSize ?? 12, color: opts.labelColor }),
48
+ ]);
49
+ }
50
+ /** A single radio button. Use `RadioGroup` for the common labelled-list layout, or this for custom ones. */
51
+ export function Radio(opts) {
52
+ return new RadioElement({
53
+ ...opts,
54
+ color: opts.color !== undefined ? toColor(opts.color) : undefined,
55
+ border: opts.border !== undefined ? toColor(opts.border) : undefined,
56
+ background: opts.background !== undefined ? toColor(opts.background) : undefined,
57
+ });
58
+ }
59
+ /**
60
+ * A labelled group of mutually-exclusive radio buttons. Sugar over `Radio` + `Text`: it lays each option
61
+ * out as a button-and-label row in a `Column`, and marks the one whose `value` matches the group `value`.
62
+ *
63
+ * ```ts
64
+ * RadioGroup({ name: "size", value: "m" }, [
65
+ * { value: "s", label: "Small" },
66
+ * { value: "m", label: "Medium" },
67
+ * { value: "l", label: "Large" },
68
+ * ])
69
+ * ```
70
+ */
71
+ export function RadioGroup(opts, options) {
72
+ const rows = options.map((o) => Row({ gap: 8, align: "center" }, [
73
+ Radio({
74
+ group: opts.name,
75
+ value: o.value,
76
+ selected: o.value === opts.value,
77
+ required: opts.required,
78
+ hidden: opts.hidden,
79
+ print: opts.print,
80
+ size: opts.size,
81
+ color: opts.color,
82
+ border: opts.border,
83
+ background: opts.background,
84
+ }),
85
+ Text(o.label, { size: opts.labelSize ?? 12, color: opts.labelColor }),
86
+ ]));
87
+ return Column({ gap: opts.gap ?? 8 }, rows);
88
+ }
89
+ const choiceStyle = (o) => ({
90
+ align: o.align,
91
+ tooltip: o.tooltip,
92
+ readOnly: o.readOnly,
93
+ required: o.required,
94
+ hidden: o.hidden,
95
+ print: o.print,
96
+ width: o.width,
97
+ height: o.height,
98
+ fontSize: o.fontSize,
99
+ color: o.color !== undefined ? toColor(o.color) : undefined,
100
+ border: o.border !== undefined ? toColor(o.border) : undefined,
101
+ background: o.background !== undefined ? toColor(o.background) : undefined,
102
+ borderWidth: o.borderWidth,
103
+ });
104
+ /**
105
+ * A dropdown / combo box (AcroForm /Ch). Options are plain strings or `{ value, label }`.
106
+ *
107
+ * ```ts
108
+ * Dropdown({ name: "country", value: "de" }, [{ value: "de", label: "Germany" }, "France"])
109
+ * ```
110
+ */
111
+ export function Dropdown(opts, options) {
112
+ return new ChoiceElement({
113
+ name: opts.name,
114
+ options: toOptions(options),
115
+ combo: true,
116
+ editable: opts.editable,
117
+ value: opts.value,
118
+ ...choiceStyle(opts),
119
+ });
120
+ }
121
+ /** `Select` is an alias for `Dropdown`. */
122
+ export const Select = Dropdown;
123
+ /**
124
+ * A scrollable list box (AcroForm /Ch). Give it a `height` to show more rows. Set `multiSelect` for
125
+ * several selections at once.
126
+ */
127
+ export function ListBox(opts, options) {
128
+ return new ChoiceElement({
129
+ name: opts.name,
130
+ options: toOptions(options),
131
+ combo: false,
132
+ multiSelect: opts.multiSelect,
133
+ value: opts.value,
134
+ values: opts.values,
135
+ ...choiceStyle(opts),
136
+ });
137
+ }
138
+ const toAction = (a) => {
139
+ if (a === undefined)
140
+ return undefined;
141
+ if (a === "reset")
142
+ return { kind: "reset" };
143
+ return "submit" in a ? { kind: "submit", url: a.submit } : { kind: "url", url: a.open };
144
+ };
145
+ /**
146
+ * A clickable push button (AcroForm /Btn). It holds no value - it fires an action. Its caption is baked
147
+ * into the appearance, so the button looks the same in every viewer and in print.
148
+ *
149
+ * ```ts
150
+ * PushButton({ name: "send", label: "Submit", action: { submit: "https://example.com/f" }, width: 120 })
151
+ * PushButton({ name: "clear", label: "Reset", action: "reset", width: 90 })
152
+ * ```
153
+ */
154
+ export function PushButton(opts) {
155
+ return new PushButtonElement({
156
+ name: opts.name,
157
+ label: opts.label,
158
+ action: toAction(opts.action),
159
+ tooltip: opts.tooltip,
160
+ readOnly: opts.readOnly,
161
+ required: opts.required,
162
+ hidden: opts.hidden,
163
+ print: opts.print,
164
+ width: opts.width,
165
+ height: opts.height,
166
+ fontSize: opts.fontSize,
167
+ color: opts.color !== undefined ? toColor(opts.color) : undefined,
168
+ border: opts.border !== undefined ? toColor(opts.border) : undefined,
169
+ background: opts.background !== undefined ? toColor(opts.background) : undefined,
170
+ borderWidth: opts.borderWidth,
171
+ });
172
+ }
173
+ /**
174
+ * A signature field (AcroForm /Sig) - a placeholder someone can sign later in a PDF tool. jasy creates
175
+ * the field and draws its empty "sign here" box; it does not apply signatures itself (real signing needs
176
+ * a certificate and a byte-range digest).
177
+ *
178
+ * ```ts
179
+ * SignatureField({ name: "approver", label: "Signature", width: 240 })
180
+ * ```
181
+ */
182
+ export function SignatureField(opts) {
183
+ return new SignatureFieldElement({
184
+ ...opts,
185
+ color: opts.color !== undefined ? toColor(opts.color) : undefined,
186
+ border: opts.border !== undefined ? toColor(opts.border) : undefined,
187
+ background: opts.background !== undefined ? toColor(opts.background) : undefined,
188
+ });
189
+ }
@@ -7,4 +7,5 @@ export * from "./content.js";
7
7
  export * from "./page-builder.js";
8
8
  export * from "./structure.js";
9
9
  export * from "./table.js";
10
+ export * from "./forms.js";
10
11
  export * from "./descriptor.js";
package/dist/api/index.js CHANGED
@@ -10,4 +10,5 @@ export * from "./content.js";
10
10
  export * from "./page-builder.js";
11
11
  export * from "./structure.js";
12
12
  export * from "./table.js";
13
+ export * from "./forms.js";
13
14
  export * from "./descriptor.js";