@jasy/pdf 1.0.0-alpha.7 → 1.0.0-alpha.9

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 (140) hide show
  1. package/README.md +11 -10
  2. package/dist/api/content.d.ts +5 -4
  3. package/dist/api/content.js +13 -7
  4. package/dist/api/descriptor.js +23 -0
  5. package/dist/api/dimension.d.ts +56 -0
  6. package/dist/api/dimension.js +27 -0
  7. package/dist/api/font-url.d.ts +28 -0
  8. package/dist/api/font-url.js +134 -0
  9. package/dist/api/forms.d.ts +347 -0
  10. package/dist/api/forms.js +189 -0
  11. package/dist/api/gradient.d.ts +53 -0
  12. package/dist/api/gradient.js +97 -0
  13. package/dist/api/index.d.ts +3 -0
  14. package/dist/api/index.js +3 -0
  15. package/dist/api/insets.d.ts +19 -12
  16. package/dist/api/insets.js +18 -7
  17. package/dist/api/layout.d.ts +20 -6
  18. package/dist/api/layout.js +31 -6
  19. package/dist/api/page-builder.d.ts +8 -0
  20. package/dist/api/page-builder.js +12 -0
  21. package/dist/api/structure.d.ts +11 -0
  22. package/dist/api/structure.js +17 -4
  23. package/dist/api/text.d.ts +42 -6
  24. package/dist/api/text.js +29 -5
  25. package/dist/crypto/legacy-handler.d.ts +43 -0
  26. package/dist/crypto/legacy-handler.js +145 -0
  27. package/dist/crypto/security-handler.d.ts +16 -0
  28. package/dist/crypto/security-handler.js +26 -0
  29. package/dist/edit/acroform-reader.d.ts +70 -0
  30. package/dist/edit/acroform-reader.js +126 -0
  31. package/dist/edit/appearance.d.ts +47 -0
  32. package/dist/edit/appearance.js +171 -0
  33. package/dist/edit/document.d.ts +145 -0
  34. package/dist/edit/document.js +642 -0
  35. package/dist/edit/fill.d.ts +65 -0
  36. package/dist/edit/fill.js +424 -0
  37. package/dist/edit/flatten.d.ts +54 -0
  38. package/dist/edit/flatten.js +462 -0
  39. package/dist/edit/index.d.ts +20 -0
  40. package/dist/edit/index.js +18 -0
  41. package/dist/edit/lexer.d.ts +30 -0
  42. package/dist/edit/lexer.js +285 -0
  43. package/dist/edit/objects.d.ts +68 -0
  44. package/dist/edit/objects.js +83 -0
  45. package/dist/edit/writer.d.ts +60 -0
  46. package/dist/edit/writer.js +283 -0
  47. package/dist/elements/container-element.d.ts +21 -1
  48. package/dist/elements/container-element.js +25 -10
  49. package/dist/elements/forms/checkbox-element.d.ts +51 -0
  50. package/dist/elements/forms/checkbox-element.js +48 -0
  51. package/dist/elements/forms/choice-element.d.ts +53 -0
  52. package/dist/elements/forms/choice-element.js +59 -0
  53. package/dist/elements/forms/push-button-element.d.ts +56 -0
  54. package/dist/elements/forms/push-button-element.js +49 -0
  55. package/dist/elements/forms/radio-element.d.ts +52 -0
  56. package/dist/elements/forms/radio-element.js +49 -0
  57. package/dist/elements/forms/signature-element.d.ts +54 -0
  58. package/dist/elements/forms/signature-element.js +47 -0
  59. package/dist/elements/forms/text-field-element.d.ts +61 -0
  60. package/dist/elements/forms/text-field-element.js +54 -0
  61. package/dist/elements/image-element.d.ts +18 -4
  62. package/dist/elements/image-element.js +27 -12
  63. package/dist/elements/index.d.ts +6 -0
  64. package/dist/elements/index.js +6 -0
  65. package/dist/elements/layout/expanded-element.d.ts +3 -1
  66. package/dist/elements/layout/expanded-element.js +12 -3
  67. package/dist/elements/layout/padding-element.d.ts +4 -2
  68. package/dist/elements/layout/padding-element.js +4 -2
  69. package/dist/elements/layout/page-builder-element.js +2 -0
  70. package/dist/elements/pdf-element.d.ts +59 -1
  71. package/dist/elements/pdf-element.js +62 -1
  72. package/dist/elements/rectangle-element.d.ts +20 -6
  73. package/dist/elements/rectangle-element.js +25 -12
  74. package/dist/elements/row-element.d.ts +24 -1
  75. package/dist/elements/row-element.js +25 -10
  76. package/dist/elements/text-element.d.ts +60 -1
  77. package/dist/elements/text-element.js +132 -33
  78. package/dist/forms/acroform.d.ts +63 -0
  79. package/dist/forms/acroform.js +427 -0
  80. package/dist/forms/appearance.d.ts +58 -0
  81. package/dist/forms/appearance.js +204 -0
  82. package/dist/forms/field.d.ts +164 -0
  83. package/dist/forms/field.js +1 -0
  84. package/dist/forms/pdf.d.ts +19 -0
  85. package/dist/forms/pdf.js +28 -0
  86. package/dist/ir/display-list.d.ts +40 -7
  87. package/dist/ir/display-list.js +8 -1
  88. package/dist/layout/alignment.d.ts +10 -0
  89. package/dist/layout/alignment.js +6 -0
  90. package/dist/layout/box-constraints.d.ts +53 -0
  91. package/dist/layout/box-constraints.js +72 -0
  92. package/dist/layout/insets.d.ts +28 -0
  93. package/dist/layout/insets.js +20 -0
  94. package/dist/renderer/form-field-renderer.d.ts +20 -0
  95. package/dist/renderer/form-field-renderer.js +12 -0
  96. package/dist/renderer/image-renderer.js +2 -1
  97. package/dist/renderer/page-renderer.js +8 -2
  98. package/dist/renderer/pdf-backend.d.ts +6 -0
  99. package/dist/renderer/pdf-backend.js +86 -31
  100. package/dist/renderer/pdf-document-renderer.js +8 -1
  101. package/dist/renderer/pdf-renderer.js +19 -1
  102. package/dist/renderer/rectangle-renderer.js +8 -2
  103. package/dist/renderer/text-renderer.d.ts +2 -2
  104. package/dist/renderer/text-renderer.js +150 -42
  105. package/dist/text/advance.js +5 -2
  106. package/dist/text/arabic.d.ts +20 -0
  107. package/dist/text/arabic.js +101 -0
  108. package/dist/text/bidi.d.ts +33 -0
  109. package/dist/text/bidi.js +94 -0
  110. package/dist/text/font-fallback.d.ts +25 -0
  111. package/dist/text/font-fallback.js +38 -0
  112. package/dist/text/line-breaker.d.ts +35 -1
  113. package/dist/text/line-breaker.js +76 -16
  114. package/dist/text/orphans-widows.d.ts +27 -0
  115. package/dist/text/orphans-widows.js +53 -0
  116. package/dist/text/shape.d.ts +29 -0
  117. package/dist/text/shape.js +67 -0
  118. package/dist/text/text-style.d.ts +25 -0
  119. package/dist/text/text-style.js +44 -1
  120. package/dist/utils/dest-registry.d.ts +5 -2
  121. package/dist/utils/dest-registry.js +5 -5
  122. package/dist/utils/flex-layout.d.ts +24 -4
  123. package/dist/utils/flex-layout.js +169 -7
  124. package/dist/utils/font-metrics.d.ts +5 -0
  125. package/dist/utils/gsub.d.ts +36 -0
  126. package/dist/utils/gsub.js +178 -0
  127. package/dist/utils/inflate.d.ts +16 -0
  128. package/dist/utils/inflate.js +52 -0
  129. package/dist/utils/outline.js +1 -2
  130. package/dist/utils/pdf-object-manager.d.ts +39 -0
  131. package/dist/utils/pdf-object-manager.js +166 -15
  132. package/dist/utils/struct-tree.js +2 -3
  133. package/dist/utils/ttf-parser.d.ts +6 -0
  134. package/dist/utils/ttf-parser.js +19 -0
  135. package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
  136. package/dist/utils/utf8-to-windows1252-encoder.js +10 -0
  137. package/dist/utils/woff.d.ts +10 -0
  138. package/dist/utils/woff.js +116 -0
  139. package/dist/validators/element-validator.js +14 -3
  140. package/package.json +10 -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
+ }
@@ -1,3 +1,4 @@
1
+ import type { Radii } from "../ir/display-list.js";
1
2
  import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
2
3
  import { LayoutContext, SizedPDFElement } from "./pdf-element.js";
3
4
  export declare enum BoxFit {
@@ -55,9 +56,20 @@ interface ImageElementParams {
55
56
  widthFactor?: number;
56
57
  /** Height as a fraction (0..1) of the offered height; see `widthFactor`. */
57
58
  heightFactor?: number;
59
+ /** Lower / upper bounds per axis, in points or as a fraction; see `SizingParams`. */
60
+ minWidth?: number;
61
+ maxWidth?: number;
62
+ minHeight?: number;
63
+ maxHeight?: number;
64
+ minWidthFactor?: number;
65
+ maxWidthFactor?: number;
66
+ minHeightFactor?: number;
67
+ maxHeightFactor?: number;
68
+ /** width / height; overrides the image's OWN ratio (CSS `aspect-ratio`), `fit` then places it. */
69
+ aspectRatio?: number;
58
70
  fit?: BoxFit;
59
- /** Corner radius in points; rounds the image box (0 = sharp, default). */
60
- radius?: number;
71
+ /** Corner radius in points - one number or per corner; rounds the image box (0 = sharp, default). */
72
+ radius?: number | Radii;
61
73
  /** Alternate text for accessibility (tagged PDF). With `alt` the image is a Figure; without, decoration. */
62
74
  alt?: string;
63
75
  }
@@ -69,8 +81,10 @@ export declare class ImageElement extends SizedPDFElement {
69
81
  private radius;
70
82
  private readonly alt?;
71
83
  private requested;
84
+ /** min/max/aspectRatio, kept as given so every layout pass re-resolves them. */
85
+ private sizing;
72
86
  private intrinsic?;
73
- constructor({ image, width, height, widthFactor, heightFactor, fit, radius, alt, }: ImageElementParams);
87
+ constructor({ image, width, height, widthFactor, heightFactor, fit, radius, alt, ...sizing }: ImageElementParams);
74
88
  /**
75
89
  * Loads the image and records its intrinsic pixel size. Runs in the async pre-layout pass (layout
76
90
  * itself is synchronous and cannot await jimp) so `calculateLayout` can turn a width-only image
@@ -87,7 +101,7 @@ export declare class ImageElement extends SizedPDFElement {
87
101
  height: number | undefined;
88
102
  image: CustomImage;
89
103
  fit: BoxFit;
90
- radius: number;
104
+ radius: number | Radii;
91
105
  alt: string | undefined;
92
106
  };
93
107
  }
@@ -1,7 +1,7 @@
1
1
  import { getImageDimensions } from "../utils/image-helper.js";
2
2
  import { latin1FromBytes } from "../utils/bytes.js";
3
3
  import { readFileBytesAsync } from "../platform/node-fs.js";
4
- import { resolveExtent } from "../layout/box-constraints.js";
4
+ import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
5
5
  import { SizedPDFElement } from "./pdf-element.js";
6
6
  // path.extname without node:path (browser-safe): the substring from the last dot, if it sits after the
7
7
  // last slash (so a dot in a directory name does not count). Enough for image file extensions.
@@ -100,7 +100,7 @@ export class CustomBytesImage extends CustomImage {
100
100
  }
101
101
  }
102
102
  export class ImageElement extends SizedPDFElement {
103
- constructor({ image, width, height, widthFactor, heightFactor, fit = BoxFit.none, radius, alt, }) {
103
+ constructor({ image, width, height, widthFactor, heightFactor, fit = BoxFit.none, radius, alt, ...sizing }) {
104
104
  // Seed this.width/height from the request so getProps() reflects it before layout; calculateLayout
105
105
  // then reads `requested` (never the mutated fields) and overwrites these with the laid-out size.
106
106
  super({ x: 0, y: 0, width });
@@ -109,6 +109,7 @@ export class ImageElement extends SizedPDFElement {
109
109
  this.requested = { width, height };
110
110
  this.widthFactor = widthFactor;
111
111
  this.heightFactor = heightFactor;
112
+ this.sizing = sizing;
112
113
  this.fit = fit;
113
114
  this.radius = radius ?? 0;
114
115
  this.alt = alt;
@@ -136,22 +137,36 @@ export class ImageElement extends SizedPDFElement {
136
137
  this.y = offset.y;
137
138
  // Relative sizing: a fixed size or a fraction of the offered box (fraction only in a bounded axis).
138
139
  // Read from `requested` (not this.width/height, which we overwrite below) so every pass re-resolves.
139
- let w = resolveExtent(this.requested.width, this.widthFactor, constraints.maxWidth, constraints.hasBoundedWidth);
140
- let h = resolveExtent(this.requested.height, this.heightFactor, constraints.maxHeight, constraints.hasBoundedHeight);
141
- // Aspect auto-size: when the user pinned exactly ONE axis, derive the other from the intrinsic
142
- // ratio (CSS `width: 50%; height: auto`). Only fires once the pre-pass resolved the pixel size.
143
- if (this.intrinsic && this.intrinsic.width > 0 && this.intrinsic.height > 0) {
140
+ const specs = extentSpecs({
141
+ ...this.sizing,
142
+ width: this.requested.width,
143
+ height: this.requested.height,
144
+ widthFactor: this.widthFactor,
145
+ heightFactor: this.heightFactor,
146
+ });
147
+ // An explicit `aspectRatio` wins over the image's own, as it does in CSS - and only it may size a
148
+ // box with NEITHER axis pinned. Left to the intrinsic ratio that would change what an unsized image
149
+ // has always done here (fill both axes and let `fit` sort it out).
150
+ const resolved = resolveSize(specs.width, specs.height, this.sizing.aspectRatio, constraints);
151
+ let w = resolved.width;
152
+ let h = resolved.height;
153
+ const bounds = resolved.constraints;
154
+ // Aspect auto-size: when exactly ONE axis is pinned, derive the other from the intrinsic ratio
155
+ // (CSS `width: 50%; height: auto`). Only fires once the pre-pass resolved the pixel size.
156
+ if (this.sizing.aspectRatio === undefined &&
157
+ this.intrinsic &&
158
+ this.intrinsic.width > 0 &&
159
+ this.intrinsic.height > 0) {
144
160
  const ratio = this.intrinsic.width / this.intrinsic.height;
145
161
  if (w !== undefined && h === undefined)
146
- h = w / ratio;
162
+ h = bounds.constrainHeight(w / ratio);
147
163
  else if (h !== undefined && w === undefined)
148
- w = h * ratio;
164
+ w = bounds.constrainWidth(h * ratio);
149
165
  }
150
166
  // Fall back to the prior behavior for an unpinned axis: a bounded axis fills, otherwise keep the
151
167
  // requested fixed size (or undefined). Read from `requested`, never the mutated this.width/height.
152
- this.width = w ?? (constraints.hasBoundedWidth ? constraints.maxWidth : this.requested.width);
153
- this.height =
154
- h ?? (constraints.hasBoundedHeight ? constraints.maxHeight : this.requested.height);
168
+ this.width = w ?? (bounds.hasBoundedWidth ? bounds.maxWidth : this.requested.width);
169
+ this.height = h ?? (bounds.hasBoundedHeight ? bounds.maxHeight : this.requested.height);
155
170
  // Top-left coordinates; the fit logic (renderer) and the Y-flip (seam) run later.
156
171
  return { width: this.width ?? 0, height: this.height ?? 0 };
157
172
  }
@@ -8,6 +8,12 @@ export * from "./layout/expanded-element.js";
8
8
  export * from "./layout/padding-element.js";
9
9
  export * from "./layout/page-break-element.js";
10
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";
11
17
  export * from "./layout/default-text-style-element.js";
12
18
  export * from "./layout/positioned-element.js";
13
19
  export * from "./layout/link-element.js";
@@ -8,6 +8,12 @@ export * from "./layout/expanded-element.js";
8
8
  export * from "./layout/padding-element.js";
9
9
  export * from "./layout/page-break-element.js";
10
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";
11
17
  export * from "./layout/default-text-style-element.js";
12
18
  export * from "./layout/positioned-element.js";
13
19
  export * from "./layout/link-element.js";
@@ -9,7 +9,7 @@ export declare class ExpandedElement extends FlexiblePDFElement implements Fragm
9
9
  private y;
10
10
  private width;
11
11
  private height;
12
- constructor({ flex, child }: ExpandedElementParams);
12
+ constructor({ flex, basis, basisFactor, child }: ExpandedElementParams);
13
13
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
14
14
  /**
15
15
  * When the column paginates, a flex region can't "fill" across pages, so it delegates to
@@ -24,6 +24,8 @@ export declare class ExpandedElement extends FlexiblePDFElement implements Fragm
24
24
  y: number;
25
25
  width: number;
26
26
  height: number;
27
+ basis: number;
28
+ basisFactor: number | undefined;
27
29
  child: PDFElement;
28
30
  };
29
31
  }
@@ -3,8 +3,8 @@ import { FlexiblePDFElement, } from "../pdf-element.js";
3
3
  import { BoxConstraints } from "../../layout/box-constraints.js";
4
4
  import { isFragmentable } from "../../layout/fragmentation.js";
5
5
  export class ExpandedElement extends FlexiblePDFElement {
6
- constructor({ flex, child }) {
7
- super({ flex });
6
+ constructor({ flex, basis, basisFactor, child }) {
7
+ super({ flex, basis, basisFactor });
8
8
  this.x = 0;
9
9
  this.y = 0;
10
10
  this.width = 0;
@@ -50,7 +50,14 @@ export class ExpandedElement extends FlexiblePDFElement {
50
50
  };
51
51
  }
52
52
  cloneWithChild(child) {
53
- return new ExpandedElement({ flex: this.flex, child });
53
+ // The basis has to come along: a fragment that lost it would claim only its share of the leftover,
54
+ // so a `flexBasis` slot would silently change width on the page it continues onto.
55
+ return new ExpandedElement({
56
+ flex: this.flex,
57
+ basis: this.basis,
58
+ basisFactor: this.basisFactor,
59
+ child,
60
+ });
54
61
  }
55
62
  getProps() {
56
63
  return {
@@ -58,6 +65,8 @@ export class ExpandedElement extends FlexiblePDFElement {
58
65
  y: this.y,
59
66
  width: this.width,
60
67
  height: this.height,
68
+ basis: this.basis,
69
+ basisFactor: this.basisFactor,
61
70
  child: this.child,
62
71
  };
63
72
  }
@@ -1,8 +1,10 @@
1
1
  import { PDFElement, LayoutContext, WithChild, SizedPDFElement } from "../pdf-element.js";
2
2
  import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
3
3
  import { Fragmentable, FragmentResult } from "../../layout/fragmentation.js";
4
+ import { type EdgeSpecs } from "../../layout/insets.js";
4
5
  interface PaddingElementParams extends WithChild {
5
- margin: [number, number, number, number];
6
+ /** Still unresolved: a side may be a percentage of the offered WIDTH (see `Insets`). */
7
+ margin: EdgeSpecs;
6
8
  }
7
9
  export declare class PaddingElement extends SizedPDFElement implements Fragmentable {
8
10
  private child;
@@ -25,7 +27,7 @@ export declare class PaddingElement extends SizedPDFElement implements Fragmenta
25
27
  y: number;
26
28
  width: number | undefined;
27
29
  height: number | undefined;
28
- margin: [number, number, number, number];
30
+ margin: EdgeSpecs;
29
31
  child: PDFElement;
30
32
  };
31
33
  }
@@ -2,6 +2,7 @@ import { Validator } from "../../validators/element-validator.js";
2
2
  import { SizedPDFElement } from "../pdf-element.js";
3
3
  import { BoxConstraints } from "../../layout/box-constraints.js";
4
4
  import { isFragmentable } from "../../layout/fragmentation.js";
5
+ import { resolveEdges } from "../../layout/insets.js";
5
6
  export class PaddingElement extends SizedPDFElement {
6
7
  constructor({ margin, child }) {
7
8
  super({ x: 0, y: 0 });
@@ -18,7 +19,7 @@ export class PaddingElement extends SizedPDFElement {
18
19
  if (!isFragmentable(this.child)) {
19
20
  return { fitted: null, remainder: this };
20
21
  }
21
- const [marginTop, marginRight, marginBottom, marginLeft] = this.margin;
22
+ const [marginTop, marginRight, marginBottom, marginLeft] = resolveEdges(this.margin, width);
22
23
  const childWidth = width - marginLeft - marginRight;
23
24
  const childMaxHeight = maxHeight - marginTop - marginBottom;
24
25
  const split = this.child.fragment(childMaxHeight, childWidth, ctx);
@@ -48,7 +49,8 @@ export class PaddingElement extends SizedPDFElement {
48
49
  this.width = constraints.maxWidth;
49
50
  this.x = offset.x;
50
51
  this.y = offset.y;
51
- const [marginTop, marginRight, marginBottom, marginLeft] = this.margin;
52
+ // Percentages resolve against the width we were OFFERED, so every pass gets the same answer.
53
+ const [marginTop, marginRight, marginBottom, marginLeft] = resolveEdges(this.margin, constraints.maxWidth);
52
54
  // The child is inset by the margins: shifted down/right, narrowed by the
53
55
  // horizontal margins, and left height-unbounded so it sizes to its own content.
54
56
  const childOffset = {
@@ -27,6 +27,8 @@ export class PageBuilderElement extends PDFElement {
27
27
  return (ctx.pageInfo ?? {
28
28
  pageNumber: 1,
29
29
  pageCount: 1,
30
+ subPageNumber: 1,
31
+ subPageTotalPages: 1,
30
32
  pageSize: resolvePageSize(ctx.pageConfig),
31
33
  });
32
34
  }