@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.
- package/README.md +7 -7
- package/dist/api/descriptor.js +26 -1
- 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/layout.d.ts +47 -14
- package/dist/api/layout.js +47 -11
- package/dist/api/structure.d.ts +11 -0
- package/dist/api/structure.js +2 -0
- package/dist/api/text.d.ts +18 -2
- package/dist/api/text.js +15 -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/container-element.d.ts +10 -1
- package/dist/elements/container-element.js +26 -8
- 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 +8 -0
- package/dist/elements/index.js +8 -0
- package/dist/elements/layout/keep-together-element.d.ts +42 -0
- package/dist/elements/layout/keep-together-element.js +85 -0
- package/dist/elements/layout/padding-element.d.ts +3 -0
- package/dist/elements/layout/padding-element.js +12 -0
- package/dist/elements/layout/page-break-element.d.ts +17 -0
- package/dist/elements/layout/page-break-element.js +24 -0
- package/dist/elements/layout/positioned-element.d.ts +10 -7
- package/dist/elements/layout/positioned-element.js +21 -9
- package/dist/elements/page-element.d.ts +11 -1
- package/dist/elements/page-element.js +41 -16
- package/dist/elements/pdf-element.d.ts +28 -0
- package/dist/elements/pdf-element.js +32 -0
- package/dist/elements/rectangle-element.d.ts +10 -1
- package/dist/elements/rectangle-element.js +27 -6
- package/dist/elements/row-element.d.ts +9 -1
- package/dist/elements/row-element.js +9 -1
- package/dist/elements/text-element.d.ts +30 -4
- package/dist/elements/text-element.js +40 -17
- 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 +18 -1
- package/dist/layout/fragmentation.d.ts +12 -0
- package/dist/layout/fragmentation.js +45 -8
- package/dist/renderer/form-field-renderer.d.ts +20 -0
- package/dist/renderer/form-field-renderer.js +12 -0
- package/dist/renderer/keep-together-renderer.d.ts +10 -0
- package/dist/renderer/keep-together-renderer.js +12 -0
- package/dist/renderer/page-break-renderer.d.ts +14 -0
- package/dist/renderer/page-break-renderer.js +15 -0
- package/dist/renderer/page-renderer.js +8 -2
- package/dist/renderer/pdf-backend.d.ts +7 -0
- package/dist/renderer/pdf-backend.js +48 -6
- package/dist/renderer/pdf-document-class.js +3 -0
- package/dist/renderer/pdf-document-renderer.js +26 -7
- package/dist/renderer/pdf-renderer.js +25 -1
- package/dist/renderer/text-renderer.d.ts +1 -1
- package/dist/renderer/text-renderer.js +107 -59
- package/dist/text/advance.d.ts +34 -0
- package/dist/text/advance.js +42 -0
- package/dist/text/line-breaker.d.ts +9 -8
- package/dist/text/line-breaker.js +28 -21
- package/dist/text/line-metrics.d.ts +49 -0
- package/dist/text/line-metrics.js +57 -0
- package/dist/text/text-decoration.d.ts +50 -0
- package/dist/text/text-decoration.js +62 -0
- package/dist/text/text-style.d.ts +13 -3
- package/dist/text/text-style.js +10 -3
- package/dist/utils/afm-parser.d.ts +34 -0
- package/dist/utils/afm-parser.js +67 -0
- package/dist/utils/dest-registry.d.ts +5 -2
- package/dist/utils/dest-registry.js +5 -5
- package/dist/utils/font-metrics.d.ts +14 -0
- package/dist/utils/outline.js +1 -2
- package/dist/utils/pdf-object-manager.d.ts +45 -1
- package/dist/utils/pdf-object-manager.js +193 -26
- package/dist/utils/struct-tree.js +2 -3
- package/dist/utils/ttf-parser.d.ts +53 -0
- package/dist/utils/ttf-parser.js +368 -0
- package/dist/validators/element-validator.js +7 -3
- package/package.json +9 -2
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Color } from "../../common/color.js";
|
|
2
|
+
import { SizedPDFElement } from "../pdf-element.js";
|
|
3
|
+
/**
|
|
4
|
+
* A push button (AcroForm /Btn, Pushbutton flag). Holds no value - it fires an action. Draws nothing
|
|
5
|
+
* itself: it reserves its box and emits a `formfield` IR node whose caption is baked into an /AP at the
|
|
6
|
+
* seam, so the button looks identical in every viewer and in print.
|
|
7
|
+
*/
|
|
8
|
+
export class PushButtonElement extends SizedPDFElement {
|
|
9
|
+
constructor(p) {
|
|
10
|
+
super({ x: 0, y: 0, width: p.width, height: p.height });
|
|
11
|
+
const fontSize = p.fontSize ?? 12;
|
|
12
|
+
this.spec = {
|
|
13
|
+
kind: "pushbutton",
|
|
14
|
+
name: p.name,
|
|
15
|
+
label: p.label,
|
|
16
|
+
action: p.action,
|
|
17
|
+
tooltip: p.tooltip,
|
|
18
|
+
readOnly: p.readOnly,
|
|
19
|
+
required: p.required,
|
|
20
|
+
hidden: p.hidden,
|
|
21
|
+
print: p.print,
|
|
22
|
+
};
|
|
23
|
+
this.style = {
|
|
24
|
+
border: p.border ?? new Color(154, 164, 178),
|
|
25
|
+
background: p.background ?? new Color(238, 240, 245),
|
|
26
|
+
color: p.color ?? new Color(0, 0, 0),
|
|
27
|
+
fontSize,
|
|
28
|
+
borderWidth: p.borderWidth ?? 1,
|
|
29
|
+
};
|
|
30
|
+
this.requested = { width: p.width, height: p.height, fontSize };
|
|
31
|
+
}
|
|
32
|
+
calculateLayout(constraints, offset, _ctx) {
|
|
33
|
+
this.x = offset.x;
|
|
34
|
+
this.y = offset.y;
|
|
35
|
+
this.width = this.requested.width ?? (constraints.hasBoundedWidth ? constraints.maxWidth : 120);
|
|
36
|
+
this.height = this.requested.height ?? this.requested.fontSize + 14;
|
|
37
|
+
return { width: this.width, height: this.height };
|
|
38
|
+
}
|
|
39
|
+
getProps() {
|
|
40
|
+
return {
|
|
41
|
+
x: this.x,
|
|
42
|
+
y: this.y,
|
|
43
|
+
width: this.width ?? 0,
|
|
44
|
+
height: this.height ?? 0,
|
|
45
|
+
spec: this.spec,
|
|
46
|
+
style: this.style,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Color } from "../../common/color.js";
|
|
2
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
3
|
+
import { LayoutContext, SizedPDFElement } from "../pdf-element.js";
|
|
4
|
+
import type { FieldStyle, RadioSpec } from "../../forms/field.js";
|
|
5
|
+
export interface RadioParams {
|
|
6
|
+
/** The shared group name - radios with the same `group` are one mutually-exclusive field. */
|
|
7
|
+
group: string;
|
|
8
|
+
/** This button's export value - unique within the group. */
|
|
9
|
+
value: string;
|
|
10
|
+
/** Whether this button starts selected (at most one per group should be). */
|
|
11
|
+
selected?: boolean;
|
|
12
|
+
/** Tooltip / accessible name. */
|
|
13
|
+
tooltip?: string;
|
|
14
|
+
/** Show but do not allow changing the group. */
|
|
15
|
+
readOnly?: boolean;
|
|
16
|
+
/** Mark it as a field that must be filled in before submitting. */
|
|
17
|
+
required?: boolean;
|
|
18
|
+
/** Hide the widget entirely - neither on screen nor in print. */
|
|
19
|
+
hidden?: boolean;
|
|
20
|
+
/** Include the widget when printing (default true). */
|
|
21
|
+
print?: boolean;
|
|
22
|
+
/** Button diameter in points (default 14). */
|
|
23
|
+
size?: number;
|
|
24
|
+
/** Dot colour (default black). */
|
|
25
|
+
color?: Color;
|
|
26
|
+
/** Ring colour (default a mid grey). */
|
|
27
|
+
border?: Color;
|
|
28
|
+
/** Fill behind the ring. */
|
|
29
|
+
background?: Color;
|
|
30
|
+
/** Ring thickness (default 1). */
|
|
31
|
+
borderWidth?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* One radio button (AcroForm /Btn, Radio flag). A small round leaf; several sharing a `group` become one
|
|
35
|
+
* mutually-exclusive field (see `RadioGroup` for the ergonomic wrapper). Draws nothing itself - it emits a
|
|
36
|
+
* `formfield` IR node whose radio spec bakes the ring + dot /AP at the seam.
|
|
37
|
+
*/
|
|
38
|
+
export declare class RadioElement extends SizedPDFElement {
|
|
39
|
+
private spec;
|
|
40
|
+
private style;
|
|
41
|
+
private size;
|
|
42
|
+
constructor(p: RadioParams);
|
|
43
|
+
calculateLayout(_constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
|
|
44
|
+
getProps(): {
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
width: number;
|
|
48
|
+
height: number;
|
|
49
|
+
spec: RadioSpec;
|
|
50
|
+
style: FieldStyle;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Color } from "../../common/color.js";
|
|
2
|
+
import { SizedPDFElement } from "../pdf-element.js";
|
|
3
|
+
/**
|
|
4
|
+
* One radio button (AcroForm /Btn, Radio flag). A small round leaf; several sharing a `group` become one
|
|
5
|
+
* mutually-exclusive field (see `RadioGroup` for the ergonomic wrapper). Draws nothing itself - it emits a
|
|
6
|
+
* `formfield` IR node whose radio spec bakes the ring + dot /AP at the seam.
|
|
7
|
+
*/
|
|
8
|
+
export class RadioElement extends SizedPDFElement {
|
|
9
|
+
constructor(p) {
|
|
10
|
+
const size = p.size ?? 14;
|
|
11
|
+
super({ x: 0, y: 0, width: size, height: size });
|
|
12
|
+
this.size = size;
|
|
13
|
+
this.spec = {
|
|
14
|
+
kind: "radio",
|
|
15
|
+
group: p.group,
|
|
16
|
+
value: p.value,
|
|
17
|
+
selected: p.selected,
|
|
18
|
+
tooltip: p.tooltip,
|
|
19
|
+
readOnly: p.readOnly,
|
|
20
|
+
required: p.required,
|
|
21
|
+
hidden: p.hidden,
|
|
22
|
+
print: p.print,
|
|
23
|
+
};
|
|
24
|
+
this.style = {
|
|
25
|
+
border: p.border ?? new Color(102, 102, 102),
|
|
26
|
+
background: p.background,
|
|
27
|
+
color: p.color ?? new Color(0, 0, 0),
|
|
28
|
+
fontSize: 0,
|
|
29
|
+
borderWidth: p.borderWidth ?? 1,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
calculateLayout(_constraints, offset, _ctx) {
|
|
33
|
+
this.x = offset.x;
|
|
34
|
+
this.y = offset.y;
|
|
35
|
+
this.width = this.size;
|
|
36
|
+
this.height = this.size;
|
|
37
|
+
return { width: this.size, height: this.size };
|
|
38
|
+
}
|
|
39
|
+
getProps() {
|
|
40
|
+
return {
|
|
41
|
+
x: this.x,
|
|
42
|
+
y: this.y,
|
|
43
|
+
width: this.width ?? 0,
|
|
44
|
+
height: this.height ?? 0,
|
|
45
|
+
spec: this.spec,
|
|
46
|
+
style: this.style,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Color } from "../../common/color.js";
|
|
2
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
3
|
+
import { LayoutContext, SizedPDFElement } from "../pdf-element.js";
|
|
4
|
+
import type { FieldStyle, SignatureSpec } from "../../forms/field.js";
|
|
5
|
+
export interface SignatureFieldParams {
|
|
6
|
+
/** The field name (/T) - unique in the document. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** A hint drawn inside the empty box, e.g. "Signature". */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Tooltip / accessible name. */
|
|
11
|
+
tooltip?: string;
|
|
12
|
+
/** Draw it but do not let it be signed. */
|
|
13
|
+
readOnly?: boolean;
|
|
14
|
+
/** Mark it as a field that must be filled in before submitting. */
|
|
15
|
+
required?: boolean;
|
|
16
|
+
/** Hide the widget entirely - neither on screen nor in print. */
|
|
17
|
+
hidden?: boolean;
|
|
18
|
+
/** Include the widget when printing (default true). */
|
|
19
|
+
print?: boolean;
|
|
20
|
+
/** Box width in points; omit to fill the offered width. */
|
|
21
|
+
width?: number;
|
|
22
|
+
/** Box height in points (default 48 - room for a signature). */
|
|
23
|
+
height?: number;
|
|
24
|
+
/** Hint font size (default 9). */
|
|
25
|
+
fontSize?: number;
|
|
26
|
+
/** Hint colour (default a muted grey). */
|
|
27
|
+
color?: Color;
|
|
28
|
+
/** Box border + signing-rule colour (default a mid grey). */
|
|
29
|
+
border?: Color;
|
|
30
|
+
/** Box background fill. */
|
|
31
|
+
background?: Color;
|
|
32
|
+
/** Border thickness (default 1). */
|
|
33
|
+
borderWidth?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A signature field (AcroForm /Sig) - a PLACEHOLDER someone can sign later. jasy creates the field and
|
|
37
|
+
* its empty "sign here" appearance; it does not sign (that needs a certificate and a byte-range digest -
|
|
38
|
+
* a separate feature). Draws nothing itself: it reserves its box and emits a `formfield` IR node.
|
|
39
|
+
*/
|
|
40
|
+
export declare class SignatureFieldElement extends SizedPDFElement {
|
|
41
|
+
private spec;
|
|
42
|
+
private style;
|
|
43
|
+
private requested;
|
|
44
|
+
constructor(p: SignatureFieldParams);
|
|
45
|
+
calculateLayout(constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
|
|
46
|
+
getProps(): {
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
spec: SignatureSpec;
|
|
52
|
+
style: FieldStyle;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Color } from "../../common/color.js";
|
|
2
|
+
import { SizedPDFElement } from "../pdf-element.js";
|
|
3
|
+
/**
|
|
4
|
+
* A signature field (AcroForm /Sig) - a PLACEHOLDER someone can sign later. jasy creates the field and
|
|
5
|
+
* its empty "sign here" appearance; it does not sign (that needs a certificate and a byte-range digest -
|
|
6
|
+
* a separate feature). Draws nothing itself: it reserves its box and emits a `formfield` IR node.
|
|
7
|
+
*/
|
|
8
|
+
export class SignatureFieldElement extends SizedPDFElement {
|
|
9
|
+
constructor(p) {
|
|
10
|
+
super({ x: 0, y: 0, width: p.width, height: p.height });
|
|
11
|
+
this.spec = {
|
|
12
|
+
kind: "signature",
|
|
13
|
+
name: p.name,
|
|
14
|
+
label: p.label,
|
|
15
|
+
tooltip: p.tooltip,
|
|
16
|
+
readOnly: p.readOnly,
|
|
17
|
+
required: p.required,
|
|
18
|
+
hidden: p.hidden,
|
|
19
|
+
print: p.print,
|
|
20
|
+
};
|
|
21
|
+
this.style = {
|
|
22
|
+
border: p.border ?? new Color(154, 164, 178),
|
|
23
|
+
background: p.background,
|
|
24
|
+
color: p.color ?? new Color(107, 114, 128),
|
|
25
|
+
fontSize: p.fontSize ?? 9,
|
|
26
|
+
borderWidth: p.borderWidth ?? 1,
|
|
27
|
+
};
|
|
28
|
+
this.requested = { width: p.width, height: p.height };
|
|
29
|
+
}
|
|
30
|
+
calculateLayout(constraints, offset, _ctx) {
|
|
31
|
+
this.x = offset.x;
|
|
32
|
+
this.y = offset.y;
|
|
33
|
+
this.width = this.requested.width ?? (constraints.hasBoundedWidth ? constraints.maxWidth : 240);
|
|
34
|
+
this.height = this.requested.height ?? 48;
|
|
35
|
+
return { width: this.width, height: this.height };
|
|
36
|
+
}
|
|
37
|
+
getProps() {
|
|
38
|
+
return {
|
|
39
|
+
x: this.x,
|
|
40
|
+
y: this.y,
|
|
41
|
+
width: this.width ?? 0,
|
|
42
|
+
height: this.height ?? 0,
|
|
43
|
+
spec: this.spec,
|
|
44
|
+
style: this.style,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Color } from "../../common/color.js";
|
|
2
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
3
|
+
import { LayoutContext, SizedPDFElement } from "../pdf-element.js";
|
|
4
|
+
import type { FieldAlign, FieldStyle, TextFieldSpec } from "../../forms/field.js";
|
|
5
|
+
export interface TextFieldParams {
|
|
6
|
+
/** The field name (/T) - unique in the document; you read the value back by it. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Initial value (/V). */
|
|
9
|
+
value?: string;
|
|
10
|
+
/** Tooltip / accessible name (/TU). */
|
|
11
|
+
tooltip?: string;
|
|
12
|
+
/** Wrap across lines instead of one line. */
|
|
13
|
+
multiline?: boolean;
|
|
14
|
+
/** Mask the input. */
|
|
15
|
+
password?: boolean;
|
|
16
|
+
/** Maximum character count. */
|
|
17
|
+
maxLength?: number;
|
|
18
|
+
/** Show but do not allow editing. */
|
|
19
|
+
readOnly?: boolean;
|
|
20
|
+
/** How the value sits in the box (default left). */
|
|
21
|
+
align?: FieldAlign;
|
|
22
|
+
/** Mark it as a field that must be filled in before submitting. */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
/** Hide the widget entirely - neither on screen nor in print. */
|
|
25
|
+
hidden?: boolean;
|
|
26
|
+
/** Include the widget when printing (default true). */
|
|
27
|
+
print?: boolean;
|
|
28
|
+
/** Box width in points; omit to fill the offered width. */
|
|
29
|
+
width?: number;
|
|
30
|
+
/** Box height in points; omit for a single-line default (`multiline` wants an explicit height). */
|
|
31
|
+
height?: number;
|
|
32
|
+
/** Value font size; `0` = viewer auto-size. */
|
|
33
|
+
fontSize?: number;
|
|
34
|
+
/** Value text colour. */
|
|
35
|
+
color?: Color;
|
|
36
|
+
/** Box border colour (omit for no border). */
|
|
37
|
+
border?: Color;
|
|
38
|
+
/** Box background fill (omit for transparent). */
|
|
39
|
+
background?: Color;
|
|
40
|
+
/** Border thickness in points. */
|
|
41
|
+
borderWidth?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A text input field (AcroForm /Tx). A leaf that draws NOTHING itself: it reserves its box in the layout
|
|
45
|
+
* and emits a `formfield` IR node that becomes a Widget annotation + /AcroForm field at the seam.
|
|
46
|
+
*/
|
|
47
|
+
export declare class TextFieldElement extends SizedPDFElement {
|
|
48
|
+
private spec;
|
|
49
|
+
private style;
|
|
50
|
+
private requested;
|
|
51
|
+
constructor(p: TextFieldParams);
|
|
52
|
+
calculateLayout(constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
|
|
53
|
+
getProps(): {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
spec: TextFieldSpec;
|
|
59
|
+
style: FieldStyle;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Color } from "../../common/color.js";
|
|
2
|
+
import { SizedPDFElement } from "../pdf-element.js";
|
|
3
|
+
/**
|
|
4
|
+
* A text input field (AcroForm /Tx). A leaf that draws NOTHING itself: it reserves its box in the layout
|
|
5
|
+
* and emits a `formfield` IR node that becomes a Widget annotation + /AcroForm field at the seam.
|
|
6
|
+
*/
|
|
7
|
+
export class TextFieldElement extends SizedPDFElement {
|
|
8
|
+
constructor(p) {
|
|
9
|
+
super({ x: 0, y: 0, width: p.width, height: p.height });
|
|
10
|
+
const fontSize = p.fontSize ?? 12;
|
|
11
|
+
this.spec = {
|
|
12
|
+
kind: "text",
|
|
13
|
+
name: p.name,
|
|
14
|
+
value: p.value,
|
|
15
|
+
tooltip: p.tooltip,
|
|
16
|
+
multiline: p.multiline,
|
|
17
|
+
password: p.password,
|
|
18
|
+
maxLength: p.maxLength,
|
|
19
|
+
readOnly: p.readOnly,
|
|
20
|
+
align: p.align,
|
|
21
|
+
required: p.required,
|
|
22
|
+
hidden: p.hidden,
|
|
23
|
+
print: p.print,
|
|
24
|
+
};
|
|
25
|
+
this.style = {
|
|
26
|
+
border: p.border,
|
|
27
|
+
background: p.background,
|
|
28
|
+
color: p.color ?? new Color(0, 0, 0),
|
|
29
|
+
fontSize,
|
|
30
|
+
borderWidth: p.borderWidth ?? 1,
|
|
31
|
+
};
|
|
32
|
+
this.requested = { width: p.width, height: p.height, fontSize };
|
|
33
|
+
}
|
|
34
|
+
calculateLayout(constraints, offset, _ctx) {
|
|
35
|
+
this.x = offset.x;
|
|
36
|
+
this.y = offset.y;
|
|
37
|
+
// Width: explicit, else fill the offered box (a form field with no width spans the column).
|
|
38
|
+
this.width = this.requested.width ?? (constraints.hasBoundedWidth ? constraints.maxWidth : 200);
|
|
39
|
+
// Height: explicit, else a comfortable single line (font size + padding). Multiline without an
|
|
40
|
+
// explicit height still gets the single-line default - the caller should size a multiline box.
|
|
41
|
+
this.height = this.requested.height ?? this.requested.fontSize + 9;
|
|
42
|
+
return { width: this.width, height: this.height };
|
|
43
|
+
}
|
|
44
|
+
getProps() {
|
|
45
|
+
return {
|
|
46
|
+
x: this.x,
|
|
47
|
+
y: this.y,
|
|
48
|
+
width: this.width ?? 0,
|
|
49
|
+
height: this.height ?? 0,
|
|
50
|
+
spec: this.spec,
|
|
51
|
+
style: this.style,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
package/dist/elements/index.d.ts
CHANGED
|
@@ -6,6 +6,14 @@ export * from "./rectangle-element.js";
|
|
|
6
6
|
export * from "./image-element.js";
|
|
7
7
|
export * from "./layout/expanded-element.js";
|
|
8
8
|
export * from "./layout/padding-element.js";
|
|
9
|
+
export * from "./layout/page-break-element.js";
|
|
10
|
+
export * from "./layout/keep-together-element.js";
|
|
11
|
+
export * from "./forms/text-field-element.js";
|
|
12
|
+
export * from "./forms/checkbox-element.js";
|
|
13
|
+
export * from "./forms/radio-element.js";
|
|
14
|
+
export * from "./forms/choice-element.js";
|
|
15
|
+
export * from "./forms/push-button-element.js";
|
|
16
|
+
export * from "./forms/signature-element.js";
|
|
9
17
|
export * from "./layout/default-text-style-element.js";
|
|
10
18
|
export * from "./layout/positioned-element.js";
|
|
11
19
|
export * from "./layout/link-element.js";
|
package/dist/elements/index.js
CHANGED
|
@@ -6,6 +6,14 @@ export * from "./rectangle-element.js";
|
|
|
6
6
|
export * from "./image-element.js";
|
|
7
7
|
export * from "./layout/expanded-element.js";
|
|
8
8
|
export * from "./layout/padding-element.js";
|
|
9
|
+
export * from "./layout/page-break-element.js";
|
|
10
|
+
export * from "./layout/keep-together-element.js";
|
|
11
|
+
export * from "./forms/text-field-element.js";
|
|
12
|
+
export * from "./forms/checkbox-element.js";
|
|
13
|
+
export * from "./forms/radio-element.js";
|
|
14
|
+
export * from "./forms/choice-element.js";
|
|
15
|
+
export * from "./forms/push-button-element.js";
|
|
16
|
+
export * from "./forms/signature-element.js";
|
|
9
17
|
export * from "./layout/default-text-style-element.js";
|
|
10
18
|
export * from "./layout/positioned-element.js";
|
|
11
19
|
export * from "./layout/link-element.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
2
|
+
import { Fragmentable, FragmentResult } from "../../layout/fragmentation.js";
|
|
3
|
+
import { LayoutContext, PDFElement } from "../pdf-element.js";
|
|
4
|
+
/**
|
|
5
|
+
* A layout-TRANSPARENT wrapper that tries to keep its content on ONE page (CSS `break-inside: avoid`).
|
|
6
|
+
* It delegates all layout and rendering to its child, so the visual result is byte-identical to the
|
|
7
|
+
* unwrapped content; only pagination behaves differently.
|
|
8
|
+
*
|
|
9
|
+
* The rule, decided in `fragment`:
|
|
10
|
+
* 1. Fits in the space offered here -> place it whole.
|
|
11
|
+
* 2. Does not fit here but fits on a FRESH -> veto the split: defer the whole group to the next
|
|
12
|
+
* full page page (`fitted: null`), so it lands together there.
|
|
13
|
+
* 3. Taller than a whole page -> drop the veto and split normally. A group that can
|
|
14
|
+
* never fit on one page must break, or pagination
|
|
15
|
+
* could not terminate. Inner `keepTogether`s survive
|
|
16
|
+
* this (they are separate elements, re-evaluated when
|
|
17
|
+
* the child splits) - graceful degrade, outer->inner.
|
|
18
|
+
*
|
|
19
|
+
* A forced page break inside the group (a `PageBreak`, or a `breakBefore`/`breakAfter`) contradicts
|
|
20
|
+
* keeping it together: the break wins (CSS behaves the same), and we WARN once rather than silently
|
|
21
|
+
* swallowing either intent.
|
|
22
|
+
*/
|
|
23
|
+
export declare class KeepTogetherElement extends PDFElement implements Fragmentable {
|
|
24
|
+
private child;
|
|
25
|
+
private warnedBreakConflict;
|
|
26
|
+
constructor({ child }: {
|
|
27
|
+
child: PDFElement;
|
|
28
|
+
});
|
|
29
|
+
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
30
|
+
relativeSizeFactor(horizontal: boolean): number | undefined;
|
|
31
|
+
needsBoundedMain(horizontal: boolean): boolean;
|
|
32
|
+
hasForcedBreak(): boolean;
|
|
33
|
+
breaksBefore(): boolean;
|
|
34
|
+
breaksAfter(): boolean;
|
|
35
|
+
fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
|
|
36
|
+
/** Delegate the actual split to the child, unwrapping - once we have decided to break the group, the
|
|
37
|
+
* pieces just flow (any INNER keepTogether lives in the child subtree and still vetoes there). */
|
|
38
|
+
private splitChild;
|
|
39
|
+
getProps(): {
|
|
40
|
+
child: PDFElement;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { BoxConstraints } from "../../layout/box-constraints.js";
|
|
2
|
+
import { isFragmentable } from "../../layout/fragmentation.js";
|
|
3
|
+
import { PDFElement } from "../pdf-element.js";
|
|
4
|
+
/**
|
|
5
|
+
* A layout-TRANSPARENT wrapper that tries to keep its content on ONE page (CSS `break-inside: avoid`).
|
|
6
|
+
* It delegates all layout and rendering to its child, so the visual result is byte-identical to the
|
|
7
|
+
* unwrapped content; only pagination behaves differently.
|
|
8
|
+
*
|
|
9
|
+
* The rule, decided in `fragment`:
|
|
10
|
+
* 1. Fits in the space offered here -> place it whole.
|
|
11
|
+
* 2. Does not fit here but fits on a FRESH -> veto the split: defer the whole group to the next
|
|
12
|
+
* full page page (`fitted: null`), so it lands together there.
|
|
13
|
+
* 3. Taller than a whole page -> drop the veto and split normally. A group that can
|
|
14
|
+
* never fit on one page must break, or pagination
|
|
15
|
+
* could not terminate. Inner `keepTogether`s survive
|
|
16
|
+
* this (they are separate elements, re-evaluated when
|
|
17
|
+
* the child splits) - graceful degrade, outer->inner.
|
|
18
|
+
*
|
|
19
|
+
* A forced page break inside the group (a `PageBreak`, or a `breakBefore`/`breakAfter`) contradicts
|
|
20
|
+
* keeping it together: the break wins (CSS behaves the same), and we WARN once rather than silently
|
|
21
|
+
* swallowing either intent.
|
|
22
|
+
*/
|
|
23
|
+
export class KeepTogetherElement extends PDFElement {
|
|
24
|
+
constructor({ child }) {
|
|
25
|
+
super();
|
|
26
|
+
this.warnedBreakConflict = false;
|
|
27
|
+
this.child = child;
|
|
28
|
+
}
|
|
29
|
+
calculateLayout(constraints, offset, ctx) {
|
|
30
|
+
return this.child.calculateLayout(constraints, offset, ctx);
|
|
31
|
+
}
|
|
32
|
+
// Transparent: forward the sizing hints the flex parent reads, so a %-sized or Spacer-holding child
|
|
33
|
+
// keeps working when it is wrapped (the wrapper must not swallow these).
|
|
34
|
+
relativeSizeFactor(horizontal) {
|
|
35
|
+
return this.child.relativeSizeFactor(horizontal);
|
|
36
|
+
}
|
|
37
|
+
needsBoundedMain(horizontal) {
|
|
38
|
+
return this.child.needsBoundedMain(horizontal);
|
|
39
|
+
}
|
|
40
|
+
// Transparent to break control: a break-before/after on the wrapped element still applies to the group
|
|
41
|
+
// as a whole, and an inner forced break still forces the parent to fragment us (so `fragment` runs).
|
|
42
|
+
hasForcedBreak() {
|
|
43
|
+
return this.child.hasForcedBreak();
|
|
44
|
+
}
|
|
45
|
+
breaksBefore() {
|
|
46
|
+
return this.child.breaksBefore();
|
|
47
|
+
}
|
|
48
|
+
breaksAfter() {
|
|
49
|
+
return this.child.breaksAfter();
|
|
50
|
+
}
|
|
51
|
+
fragment(maxHeight, width, ctx) {
|
|
52
|
+
// Contradiction: a forced break inside the group beats keeping it together. Split at the break by
|
|
53
|
+
// delegating to the child (which honours it), and say so once - never keep such a group whole.
|
|
54
|
+
if (this.child.hasForcedBreak()) {
|
|
55
|
+
if (!this.warnedBreakConflict) {
|
|
56
|
+
this.warnedBreakConflict = true;
|
|
57
|
+
console.warn("keepTogether contains a forced page break (PageBreak / breakBefore / breakAfter). The break " +
|
|
58
|
+
"wins and the group is split. Remove the break, or the keepTogether, to resolve this.");
|
|
59
|
+
}
|
|
60
|
+
return this.splitChild(maxHeight, width, ctx);
|
|
61
|
+
}
|
|
62
|
+
const natural = this.child.calculateLayout(BoxConstraints.loose(width, Infinity), { x: 0, y: 0 }, ctx).height;
|
|
63
|
+
// 1. Fits in the offered space: keep whole.
|
|
64
|
+
if (natural <= maxHeight)
|
|
65
|
+
return { fitted: this, remainder: null };
|
|
66
|
+
// 2. Would fit on a fresh full page: veto the split, defer the whole group to the next page. Only
|
|
67
|
+
// reachable in a PARTIAL region (something is already on this page); alone on a full page,
|
|
68
|
+
// `maxHeight` IS the page body, so `natural > maxHeight` means case 3, never this.
|
|
69
|
+
const pageBody = ctx.pageBodyHeight ?? maxHeight;
|
|
70
|
+
if (natural <= pageBody)
|
|
71
|
+
return { fitted: null, remainder: this };
|
|
72
|
+
// 3. Bigger than a whole page: it can never be kept together, so split it.
|
|
73
|
+
return this.splitChild(maxHeight, width, ctx);
|
|
74
|
+
}
|
|
75
|
+
/** Delegate the actual split to the child, unwrapping - once we have decided to break the group, the
|
|
76
|
+
* pieces just flow (any INNER keepTogether lives in the child subtree and still vetoes there). */
|
|
77
|
+
splitChild(maxHeight, width, ctx) {
|
|
78
|
+
if (!isFragmentable(this.child))
|
|
79
|
+
return { fitted: null, remainder: this };
|
|
80
|
+
return this.child.fragment(maxHeight, width, ctx);
|
|
81
|
+
}
|
|
82
|
+
getProps() {
|
|
83
|
+
return { child: this.child };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -15,6 +15,9 @@ export declare class PaddingElement extends SizedPDFElement implements Fragmenta
|
|
|
15
15
|
* child can't be split, the whole padding moves on as the remainder.
|
|
16
16
|
*/
|
|
17
17
|
fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
|
|
18
|
+
hasForcedBreak(): boolean;
|
|
19
|
+
breaksBefore(): boolean;
|
|
20
|
+
breaksAfter(): boolean;
|
|
18
21
|
private cloneWithChild;
|
|
19
22
|
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
20
23
|
getProps(): {
|
|
@@ -25,8 +25,20 @@ export class PaddingElement extends SizedPDFElement {
|
|
|
25
25
|
return {
|
|
26
26
|
fitted: split.fitted ? this.cloneWithChild(split.fitted) : null,
|
|
27
27
|
remainder: split.remainder ? this.cloneWithChild(split.remainder) : null,
|
|
28
|
+
forceBreak: split.forceBreak,
|
|
28
29
|
};
|
|
29
30
|
}
|
|
31
|
+
hasForcedBreak() {
|
|
32
|
+
return this.child.hasForcedBreak();
|
|
33
|
+
}
|
|
34
|
+
// Padding is a transparent single-child wrapper, so a break-before/after on the wrapped element must
|
|
35
|
+
// still be seen by the parent packer at this boundary (otherwise it is swallowed).
|
|
36
|
+
breaksBefore() {
|
|
37
|
+
return this.child.breaksBefore();
|
|
38
|
+
}
|
|
39
|
+
breaksAfter() {
|
|
40
|
+
return this.child.breaksAfter();
|
|
41
|
+
}
|
|
30
42
|
cloneWithChild(child) {
|
|
31
43
|
return new PaddingElement({ margin: this.margin, child });
|
|
32
44
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
2
|
+
import { LayoutContext, PDFElement } from "../pdf-element.js";
|
|
3
|
+
/**
|
|
4
|
+
* A forced page break. Placed in a flow (a `Column`/`Row`/`Box`), it makes everything AFTER it start
|
|
5
|
+
* on a fresh page, no matter how much space is left. It takes zero space and draws nothing - the
|
|
6
|
+
* pagination packer consumes it at the cut, so it never reaches the render pass.
|
|
7
|
+
*
|
|
8
|
+
* Nesting works: a break deep inside a container is honoured because the packer fragments any child
|
|
9
|
+
* whose subtree `hasForcedBreak()`, and the resulting `forceBreak` bubbles up so the outer flow stops
|
|
10
|
+
* too. A break wins over a `keepTogether` (a forced break cannot be avoided) - CSS behaves the same.
|
|
11
|
+
*/
|
|
12
|
+
export declare class PageBreakElement extends PDFElement {
|
|
13
|
+
calculateLayout(_constraints: BoxConstraints, _offset: Offset, _ctx: LayoutContext): Size;
|
|
14
|
+
isPageBreak(): boolean;
|
|
15
|
+
hasForcedBreak(): boolean;
|
|
16
|
+
getProps(): Record<string, never>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PDFElement } from "../pdf-element.js";
|
|
2
|
+
/**
|
|
3
|
+
* A forced page break. Placed in a flow (a `Column`/`Row`/`Box`), it makes everything AFTER it start
|
|
4
|
+
* on a fresh page, no matter how much space is left. It takes zero space and draws nothing - the
|
|
5
|
+
* pagination packer consumes it at the cut, so it never reaches the render pass.
|
|
6
|
+
*
|
|
7
|
+
* Nesting works: a break deep inside a container is honoured because the packer fragments any child
|
|
8
|
+
* whose subtree `hasForcedBreak()`, and the resulting `forceBreak` bubbles up so the outer flow stops
|
|
9
|
+
* too. A break wins over a `keepTogether` (a forced break cannot be avoided) - CSS behaves the same.
|
|
10
|
+
*/
|
|
11
|
+
export class PageBreakElement extends PDFElement {
|
|
12
|
+
calculateLayout(_constraints, _offset, _ctx) {
|
|
13
|
+
return { width: 0, height: 0 };
|
|
14
|
+
}
|
|
15
|
+
isPageBreak() {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
hasForcedBreak() {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
getProps() {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -23,14 +23,17 @@ export interface PositionedInsets {
|
|
|
23
23
|
interface PositionedElementParams extends WithChild, PositionedInsets {
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* An out-of-flow child, placed relative to the nearest enclosing positioning frame
|
|
27
|
-
* Box
|
|
28
|
-
* registers a placement closure on the frame. The
|
|
29
|
-
* size, so `right`/`bottom` resolve against the final
|
|
30
|
-
* `top`/`left` makes it poke into / out of the
|
|
26
|
+
* An out-of-flow child, placed relative to the nearest enclosing positioning frame: a `relative`
|
|
27
|
+
* Box, or failing that the page's content box. It takes ZERO space in the normal flow -
|
|
28
|
+
* `calculateLayout` returns `Size(0,0)` and instead registers a placement closure on the frame. The
|
|
29
|
+
* frame runs that closure once it knows its own size, so `right`/`bottom` resolve against the final
|
|
30
|
+
* box and the child can overflow it (a negative `top`/`left` makes it poke into / out of the
|
|
31
|
+
* corner, e.g. a badge or tab).
|
|
31
32
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
33
|
+
* Every page supplies a frame - header, footer and body alike - so within a document there is
|
|
34
|
+
* always one. Outside of one we REFUSE rather than draw: the element used to fall through to its
|
|
35
|
+
* child's default (0, 0), which is how a header watermark landed silently in the page corner
|
|
36
|
+
* (ISSUE-4). Content that lands somewhere unasked is worse than content that does not render.
|
|
34
37
|
*/
|
|
35
38
|
export declare class PositionedElement extends PDFElement {
|
|
36
39
|
private child;
|