@jasy/pdf 1.0.0-alpha.7 → 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.
- package/README.md +7 -7
- package/dist/api/descriptor.js +23 -0
- package/dist/api/forms.d.ts +347 -0
- package/dist/api/forms.js +189 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/structure.d.ts +6 -0
- package/dist/api/structure.js +1 -0
- package/dist/crypto/legacy-handler.d.ts +43 -0
- package/dist/crypto/legacy-handler.js +145 -0
- package/dist/crypto/security-handler.d.ts +16 -0
- package/dist/crypto/security-handler.js +26 -0
- package/dist/edit/acroform-reader.d.ts +70 -0
- package/dist/edit/acroform-reader.js +126 -0
- package/dist/edit/appearance.d.ts +47 -0
- package/dist/edit/appearance.js +171 -0
- package/dist/edit/document.d.ts +135 -0
- package/dist/edit/document.js +649 -0
- package/dist/edit/fill.d.ts +48 -0
- package/dist/edit/fill.js +395 -0
- package/dist/edit/flatten.d.ts +42 -0
- package/dist/edit/flatten.js +437 -0
- package/dist/edit/index.d.ts +18 -0
- package/dist/edit/index.js +16 -0
- package/dist/edit/lexer.d.ts +30 -0
- package/dist/edit/lexer.js +285 -0
- package/dist/edit/objects.d.ts +63 -0
- package/dist/edit/objects.js +56 -0
- package/dist/edit/writer.d.ts +53 -0
- package/dist/edit/writer.js +261 -0
- package/dist/elements/forms/checkbox-element.d.ts +51 -0
- package/dist/elements/forms/checkbox-element.js +48 -0
- package/dist/elements/forms/choice-element.d.ts +53 -0
- package/dist/elements/forms/choice-element.js +59 -0
- package/dist/elements/forms/push-button-element.d.ts +56 -0
- package/dist/elements/forms/push-button-element.js +49 -0
- package/dist/elements/forms/radio-element.d.ts +52 -0
- package/dist/elements/forms/radio-element.js +49 -0
- package/dist/elements/forms/signature-element.d.ts +54 -0
- package/dist/elements/forms/signature-element.js +47 -0
- package/dist/elements/forms/text-field-element.d.ts +61 -0
- package/dist/elements/forms/text-field-element.js +54 -0
- package/dist/elements/index.d.ts +6 -0
- package/dist/elements/index.js +6 -0
- package/dist/forms/acroform.d.ts +63 -0
- package/dist/forms/acroform.js +427 -0
- package/dist/forms/appearance.d.ts +58 -0
- package/dist/forms/appearance.js +204 -0
- package/dist/forms/field.d.ts +164 -0
- package/dist/forms/field.js +1 -0
- package/dist/forms/pdf.d.ts +19 -0
- package/dist/forms/pdf.js +28 -0
- package/dist/ir/display-list.d.ts +17 -1
- package/dist/renderer/form-field-renderer.d.ts +20 -0
- package/dist/renderer/form-field-renderer.js +12 -0
- package/dist/renderer/page-renderer.js +8 -2
- package/dist/renderer/pdf-backend.js +8 -0
- package/dist/renderer/pdf-renderer.js +19 -1
- package/dist/utils/dest-registry.d.ts +5 -2
- package/dist/utils/dest-registry.js +5 -5
- package/dist/utils/outline.js +1 -2
- package/dist/utils/pdf-object-manager.d.ts +18 -0
- package/dist/utils/pdf-object-manager.js +59 -6
- package/dist/utils/struct-tree.js +2 -3
- package/dist/validators/element-validator.js +7 -3
- 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/
|
|
13
|
+
<code>npm i @jasy/e-invoice</code> · <code>npx @jasy/cli</code> · 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/
|
|
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
|
|
152
|
-
|
|
|
153
|
-
| **[@jasy/
|
|
154
|
-
| **[@jasy/cli](https://npmx.dev/@jasy/cli)**
|
|
155
|
-
| **[@jasy/pdf](https://npmx.dev/@jasy/pdf)**
|
|
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
|
|
package/dist/api/descriptor.js
CHANGED
|
@@ -4,6 +4,7 @@ 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) {
|
|
@@ -112,7 +113,29 @@ const REGISTRY = {
|
|
|
112
113
|
// cannot express. These two cover what the closure exists for.
|
|
113
114
|
"page-number": (props) => PageNumber(props),
|
|
114
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),
|
|
115
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
|
+
}
|
|
116
139
|
/**
|
|
117
140
|
* Registers a custom element type, so a binding (or a user-defined component) can introduce
|
|
118
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
|
+
}
|
package/dist/api/index.d.ts
CHANGED
package/dist/api/index.js
CHANGED
package/dist/api/structure.d.ts
CHANGED
|
@@ -137,6 +137,12 @@ export interface RenderOptions {
|
|
|
137
137
|
* embedded `GPOS` Type 2, so every font path kerns. Set `false` to opt out (byte-identical to the
|
|
138
138
|
* pre-kerning output). Verified against headless Chrome and measured == drawn to the thousandth. */
|
|
139
139
|
kerning?: boolean;
|
|
140
|
+
/** Bake a form field's VALUE into its own appearance stream (`/AP`) - ON by default. Baked values
|
|
141
|
+
* render identically in every viewer and in print, and are what flattening (later) draws from. Set
|
|
142
|
+
* `false` to leave text and choice values to the viewer instead (`/NeedAppearances`, what
|
|
143
|
+
* react-pdf/pdfkit always do). Checkboxes, radios, buttons and signature boxes always carry their
|
|
144
|
+
* own appearance either way - there is no value to defer. Only meaningful with form fields present. */
|
|
145
|
+
fieldAppearances?: boolean;
|
|
140
146
|
/** What to do when content is taller than a page and cannot break: `"error"` throws (default),
|
|
141
147
|
* `"warn"` logs and clips, `"ignore"` clips silently. It is always clipped either way. */
|
|
142
148
|
onOverflow?: OverflowPolicy;
|
package/dist/api/structure.js
CHANGED
|
@@ -138,6 +138,7 @@ export async function renderPdf(doc, options) {
|
|
|
138
138
|
om.setCompress(options?.compress !== false); // FlateDecode streams by default
|
|
139
139
|
om.setOverflowPolicy(options?.onOverflow ?? "error");
|
|
140
140
|
om.setKerning(options?.kerning !== false); // on by default (experimental); `kerning:false` opts out
|
|
141
|
+
om.acroform.setBakeAppearances(options?.fieldAppearances !== false); // baked unless opted out
|
|
141
142
|
for (const [name, value] of Object.entries(fonts)) {
|
|
142
143
|
if (isFontBytes(value)) {
|
|
143
144
|
om.registerCustomFont(name, value);
|