@jasy/pdf 1.0.0-alpha.2 → 1.0.0-alpha.4

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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -14
  3. package/dist/api/args.d.ts +1 -1
  4. package/dist/api/args.js +2 -5
  5. package/dist/api/color.d.ts +4 -4
  6. package/dist/api/color.js +11 -17
  7. package/dist/api/content.d.ts +8 -8
  8. package/dist/api/content.js +23 -24
  9. package/dist/api/descriptor.d.ts +2 -2
  10. package/dist/api/descriptor.js +75 -31
  11. package/dist/api/index.d.ts +8 -8
  12. package/dist/api/index.js +8 -24
  13. package/dist/api/insets.js +4 -8
  14. package/dist/api/layout.d.ts +18 -16
  15. package/dist/api/layout.js +41 -52
  16. package/dist/api/structure.d.ts +65 -13
  17. package/dist/api/structure.js +140 -88
  18. package/dist/api/table.d.ts +5 -5
  19. package/dist/api/table.js +28 -24
  20. package/dist/api/text.d.ts +27 -2
  21. package/dist/api/text.js +45 -27
  22. package/dist/assets/font-data.d.ts +2 -0
  23. package/dist/assets/font-data.js +21 -0
  24. package/dist/assets/font-data.ts +37 -0
  25. package/dist/common/color.js +1 -5
  26. package/dist/constants/page-sizes.js +3 -6
  27. package/dist/constants/pdf-parts.js +1 -4
  28. package/dist/crypto/security-handler.d.ts +46 -0
  29. package/dist/crypto/security-handler.js +129 -0
  30. package/dist/crypto/webcrypto.d.ts +11 -0
  31. package/dist/crypto/webcrypto.js +62 -0
  32. package/dist/elements/container-element.d.ts +4 -4
  33. package/dist/elements/container-element.js +9 -13
  34. package/dist/elements/image-element.d.ts +18 -2
  35. package/dist/elements/image-element.js +81 -105
  36. package/dist/elements/index.d.ts +12 -11
  37. package/dist/elements/index.js +12 -29
  38. package/dist/elements/layout/default-text-style-element.d.ts +30 -0
  39. package/dist/elements/layout/default-text-style-element.js +47 -0
  40. package/dist/elements/layout/deferred-element.d.ts +3 -3
  41. package/dist/elements/layout/deferred-element.js +4 -8
  42. package/dist/elements/layout/expanded-element.d.ts +3 -3
  43. package/dist/elements/layout/expanded-element.js +10 -14
  44. package/dist/elements/layout/padding-element.d.ts +3 -3
  45. package/dist/elements/layout/padding-element.js +9 -14
  46. package/dist/elements/layout/positioned-element.d.ts +17 -4
  47. package/dist/elements/layout/positioned-element.js +29 -25
  48. package/dist/elements/layout/repeating-header-element.d.ts +3 -3
  49. package/dist/elements/layout/repeating-header-element.js +8 -12
  50. package/dist/elements/layout/sized-container-element.d.ts +2 -2
  51. package/dist/elements/layout/sized-container-element.js +6 -11
  52. package/dist/elements/line-element.d.ts +3 -3
  53. package/dist/elements/line-element.js +5 -10
  54. package/dist/elements/page-element.d.ts +8 -6
  55. package/dist/elements/page-element.js +20 -23
  56. package/dist/elements/pdf-document-element.d.ts +10 -4
  57. package/dist/elements/pdf-document-element.js +11 -10
  58. package/dist/elements/pdf-element.d.ts +12 -3
  59. package/dist/elements/pdf-element.js +10 -19
  60. package/dist/elements/rectangle-element.d.ts +5 -5
  61. package/dist/elements/rectangle-element.js +19 -25
  62. package/dist/elements/row-element.d.ts +3 -3
  63. package/dist/elements/row-element.js +7 -11
  64. package/dist/elements/text-element.d.ts +37 -11
  65. package/dist/elements/text-element.js +64 -39
  66. package/dist/index.d.ts +3 -3
  67. package/dist/index.js +3 -19
  68. package/dist/ir/display-list.d.ts +4 -2
  69. package/dist/ir/display-list.js +1 -2
  70. package/dist/layout/box-constraints.js +2 -6
  71. package/dist/layout/fragmentation.d.ts +8 -1
  72. package/dist/layout/fragmentation.js +22 -10
  73. package/dist/platform/browser-fs.d.ts +2 -0
  74. package/dist/platform/browser-fs.js +9 -0
  75. package/dist/platform/browser-image.d.ts +5 -0
  76. package/dist/platform/browser-image.js +13 -0
  77. package/dist/platform/node-fs.d.ts +2 -0
  78. package/dist/platform/node-fs.js +10 -0
  79. package/dist/platform/node-image.d.ts +5 -0
  80. package/dist/platform/node-image.js +9 -0
  81. package/dist/renderer/container-renderer.d.ts +3 -3
  82. package/dist/renderer/container-renderer.js +12 -27
  83. package/dist/renderer/default-text-style-renderer.d.ts +6 -0
  84. package/dist/renderer/default-text-style-renderer.js +10 -0
  85. package/dist/renderer/deferred-renderer.d.ts +3 -3
  86. package/dist/renderer/deferred-renderer.js +8 -23
  87. package/dist/renderer/expanded-renderer.d.ts +3 -3
  88. package/dist/renderer/expanded-renderer.js +6 -21
  89. package/dist/renderer/image-renderer.d.ts +3 -3
  90. package/dist/renderer/image-renderer.js +77 -75
  91. package/dist/renderer/index.d.ts +10 -10
  92. package/dist/renderer/index.js +10 -26
  93. package/dist/renderer/line-renderer.d.ts +3 -3
  94. package/dist/renderer/line-renderer.js +13 -28
  95. package/dist/renderer/padding-renderer.d.ts +3 -3
  96. package/dist/renderer/padding-renderer.js +6 -21
  97. package/dist/renderer/page-renderer.d.ts +2 -2
  98. package/dist/renderer/page-renderer.js +61 -77
  99. package/dist/renderer/pdf-backend.d.ts +2 -2
  100. package/dist/renderer/pdf-backend.js +21 -19
  101. package/dist/renderer/pdf-config.js +4 -7
  102. package/dist/renderer/pdf-document-class.d.ts +5 -5
  103. package/dist/renderer/pdf-document-class.js +24 -41
  104. package/dist/renderer/pdf-document-renderer.d.ts +3 -3
  105. package/dist/renderer/pdf-document-renderer.js +71 -85
  106. package/dist/renderer/pdf-renderer.d.ts +2 -2
  107. package/dist/renderer/pdf-renderer.js +85 -93
  108. package/dist/renderer/positioned-renderer.d.ts +3 -3
  109. package/dist/renderer/positioned-renderer.js +8 -23
  110. package/dist/renderer/rectangle-renderer.d.ts +3 -3
  111. package/dist/renderer/rectangle-renderer.js +45 -52
  112. package/dist/renderer/repeating-header-renderer.d.ts +3 -3
  113. package/dist/renderer/repeating-header-renderer.js +11 -26
  114. package/dist/renderer/row-renderer.d.ts +3 -3
  115. package/dist/renderer/row-renderer.js +12 -27
  116. package/dist/renderer/text-renderer.d.ts +6 -5
  117. package/dist/renderer/text-renderer.js +33 -42
  118. package/dist/text/line-breaker.d.ts +8 -5
  119. package/dist/text/line-breaker.js +67 -16
  120. package/dist/text/text-style.d.ts +25 -0
  121. package/dist/text/text-style.js +29 -0
  122. package/dist/utils/afm-parser.js +3 -13
  123. package/dist/utils/bytes.d.ts +24 -0
  124. package/dist/utils/bytes.js +76 -0
  125. package/dist/utils/flex-layout.d.ts +2 -2
  126. package/dist/utils/flex-layout.js +15 -20
  127. package/dist/utils/font-metrics.d.ts +1 -1
  128. package/dist/utils/font-metrics.js +1 -2
  129. package/dist/utils/font-path.js +3 -6
  130. package/dist/utils/image-helper.d.ts +6 -5
  131. package/dist/utils/image-helper.js +101 -111
  132. package/dist/utils/md5.d.ts +4 -0
  133. package/dist/utils/md5.js +79 -0
  134. package/dist/utils/pdf-object-manager.d.ts +18 -6
  135. package/dist/utils/pdf-object-manager.js +0 -0
  136. package/dist/utils/renderer-registry.js +1 -5
  137. package/dist/utils/ttf-parser.d.ts +2 -2
  138. package/dist/utils/ttf-parser.js +32 -36
  139. package/dist/utils/ttf-subsetter.d.ts +1 -1
  140. package/dist/utils/ttf-subsetter.js +40 -42
  141. package/dist/utils/utf8-to-windows1252-encoder.js +1 -4
  142. package/dist/validators/element-validator.d.ts +2 -2
  143. package/dist/validators/element-validator.js +9 -13
  144. package/package.json +14 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Florian Heuberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -37,7 +37,7 @@ account, no upload, nothing leaves your machine.
37
37
  ## You bring the line items. jasy does the rest.
38
38
 
39
39
  You never compute a total, a VAT breakdown or a rounding again. You hand jasy the line items - it
40
- **derives** the document totals and the EN-16931 VAT breakdown, spec-correct. That is *why* the
40
+ **derives** the document totals and the EN-16931 VAT breakdown, spec-correct. That is _why_ the
41
41
  invoices validate: the amounts are correct **by construction**, so the single biggest class of
42
42
  EN-16931 failures (the BR-CO total checks) simply cannot happen.
43
43
 
@@ -48,13 +48,23 @@ const { bytes, xml } = await renderZugferd({
48
48
  number: "RE-2026-001",
49
49
  issueDate: "2026-06-17",
50
50
  currency: "EUR",
51
- seller: { name: "Northwind Studio GmbH", vatId: "DE123456789",
52
- address: { city: "Berlin", postCode: "10115", country: "DE" } },
53
- buyer: { name: "Globex Corporation Ltd",
54
- address: { city: "Munich", postCode: "80331", country: "DE" } },
51
+ seller: {
52
+ name: "Northwind Studio GmbH",
53
+ vatId: "DE123456789",
54
+ address: { city: "Berlin", postCode: "10115", country: "DE" },
55
+ },
56
+ buyer: {
57
+ name: "Globex Corporation Ltd",
58
+ address: { city: "Munich", postCode: "80331", country: "DE" },
59
+ },
55
60
  lines: [
56
- { name: "Brand identity design", quantity: 2, unit: "HUR", netUnitPrice: 100,
57
- vat: { category: "S", ratePercent: 19 } },
61
+ {
62
+ name: "Brand identity design",
63
+ quantity: 2,
64
+ unit: "HUR",
65
+ netUnitPrice: 100,
66
+ vat: { category: "S", ratePercent: 19 },
67
+ },
58
68
  ],
59
69
  });
60
70
  // bytes -> a valid ZUGFeRD PDF/A-3 · xml -> the embedded EN-16931 XML
@@ -123,7 +133,7 @@ verify all of it:
123
133
  - **Compression.** Content streams and images are FlateDecode-compressed; the spreadsheets `jasy export`
124
134
  writes are real `.xlsx` ZIPs we deflate with our own writer and CRC32, zero dependencies.
125
135
  - **Real font metrics.** Text is laid out with the Adobe AFM metrics of the standard-14 fonts - kerning
126
- and word-wrap are *computed*, not guessed.
136
+ and word-wrap are _computed_, not guessed.
127
137
  - **PDF/A-3, matched not approximated.** The conformance graph is hand-built and **passes veraPDF**, the
128
138
  official ISO 19005 validator.
129
139
  - **Byte-exact round-trips.** Generate and parse are inverses: `generate → parse → regenerate` reproduces
@@ -135,11 +145,11 @@ verify all of it:
135
145
 
136
146
  ## Packages
137
147
 
138
- | Package | What it is |
139
- | --- | --- |
140
- | **[@jasy/zugferd](https://www.npmjs.com/package/@jasy/zugferd)** | ZUGFeRD / XRechnung: your data → PDF/A-3 + EN-16931 XML, with local validation. **The prize.** |
141
- | **[@jasy/cli](https://www.npmjs.com/package/@jasy/cli)** | the `jasy` terminal: read · validate · export, headless **and** interactive |
142
- | **[@jasy/pdf](https://www.npmjs.com/package/@jasy/pdf)** | the declarative, Flutter-style PDF layout engine that powers them |
148
+ | Package | What it is |
149
+ | --------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
150
+ | **[@jasy/zugferd](https://npmx.dev/@jasy/zugferd)** | ZUGFeRD / XRechnung: your data → PDF/A-3 + EN-16931 XML, with local validation. **The prize.** |
151
+ | **[@jasy/cli](https://npmx.dev/@jasy/cli)** | the `jasy` terminal: read · validate · export, headless **and** interactive |
152
+ | **[@jasy/pdf](https://npmx.dev/@jasy/pdf)** | the declarative, Flutter-style PDF layout engine that powers them |
143
153
 
144
154
  ---
145
155
 
@@ -158,7 +168,7 @@ verify all of it:
158
168
 
159
169
  jasy targets the documents that matter here: invoices, reports, quotes, datasheets. It is **not** a
160
170
  LaTeX / WeasyPrint replacement - no microtypography, hyphenation or bidi, and arbitrary multi-page flow
161
- of *any* content is still maturing. For e-invoices (a table, totals, a footer) it is complete - they
171
+ of _any_ content is still maturing. For e-invoices (a table, totals, a footer) it is complete - they
162
172
  even paginate. We would rather under-promise and over-deliver in the demo above.
163
173
 
164
174
  > **Status:** young and pre-1.0. The API can still shift between minor versions. Everything shown here
@@ -1,4 +1,4 @@
1
- import { PDFElement } from "../elements/pdf-element";
1
+ import { PDFElement } from "../elements/pdf-element.js";
2
2
  /**
3
3
  * Lets a container factory be called either `F(children)` or `F(opts, children)` - the
4
4
  * same ergonomic shape across Document/Page/Column/Row/Box. When the first argument is the
package/dist/api/args.js CHANGED
@@ -1,13 +1,10 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.splitArgs = splitArgs;
4
1
  /**
5
2
  * Lets a container factory be called either `F(children)` or `F(opts, children)` - the
6
3
  * same ergonomic shape across Document/Page/Column/Row/Box. When the first argument is the
7
4
  * child array, options default to empty.
8
5
  */
9
- function splitArgs(a, b) {
6
+ export function splitArgs(a, b) {
10
7
  if (Array.isArray(a))
11
8
  return { opts: {}, children: a };
12
- return { opts: a, children: b !== null && b !== void 0 ? b : [] };
9
+ return { opts: a, children: b ?? [] };
13
10
  }
@@ -1,11 +1,11 @@
1
- import { Color } from "../common/color";
1
+ import { Color } from "../common/color.js";
2
2
  /**
3
- * Every way to name a colour. The FORM picks the convention, so there is never a guess
3
+ * Every way to name a color. The FORM picks the convention, so there is never a guess
4
4
  * (locked design §2):
5
5
  *
6
6
  * | form | example | meaning |
7
7
  * |---------------------------|--------------------------|--------------------------------|
8
- * | named (full CSS set) | `"steelblue"`, `"transparent"` | the ~148 CSS colour names |
8
+ * | named (full CSS set) | `"steelblue"`, `"transparent"` | the ~148 CSS color names |
9
9
  * | hex 6 / 3 | `"#1450aa"` / `"#14a"` | CSS RGB |
10
10
  * | hex 8 / 4 | `"#1450aacc"` / `"#14ac"`| CSS RGBA (alpha LAST) |
11
11
  * | number | `0xff1450aa` | Flutter ARGB (alpha FIRST) |
@@ -19,6 +19,6 @@ export declare function rgb(r: number, g: number, b: number): Color;
19
19
  export declare function rgba(r: number, g: number, b: number, a: number): Color;
20
20
  /**
21
21
  * Normalizes any `ColorInput` to an engine `Color`. The single funnel every factory uses,
22
- * so a colour means the same thing no matter how it was written.
22
+ * so a color means the same thing no matter how it was written.
23
23
  */
24
24
  export declare function toColor(input: ColorInput): Color;
package/dist/api/color.js CHANGED
@@ -1,24 +1,18 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rgb = rgb;
4
- exports.rgba = rgba;
5
- exports.toColor = toColor;
6
- const color_1 = require("../common/color");
1
+ import { Color } from "../common/color.js";
7
2
  /** Channels 0–255, fully opaque. */
8
- function rgb(r, g, b) {
9
- return new color_1.Color(r, g, b, 1);
3
+ export function rgb(r, g, b) {
4
+ return new Color(r, g, b, 1);
10
5
  }
11
6
  /** Channels 0–255, alpha 0–1. */
12
- function rgba(r, g, b, a) {
13
- return new color_1.Color(r, g, b, a);
7
+ export function rgba(r, g, b, a) {
8
+ return new Color(r, g, b, a);
14
9
  }
15
10
  /**
16
11
  * Normalizes any `ColorInput` to an engine `Color`. The single funnel every factory uses,
17
- * so a colour means the same thing no matter how it was written.
12
+ * so a color means the same thing no matter how it was written.
18
13
  */
19
- function toColor(input) {
20
- var _a;
21
- if (input instanceof color_1.Color)
14
+ export function toColor(input) {
15
+ if (input instanceof Color)
22
16
  return input;
23
17
  if (typeof input === "number")
24
18
  return fromArgbNumber(input);
@@ -27,7 +21,7 @@ function toColor(input) {
27
21
  return fromHex(s);
28
22
  const named = CSS_COLORS[s];
29
23
  if (named)
30
- return new color_1.Color(named[0], named[1], named[2], (_a = named[3]) !== null && _a !== void 0 ? _a : 1);
24
+ return new Color(named[0], named[1], named[2], named[3] ?? 1);
31
25
  throw new Error(`Unknown color: "${input}"`);
32
26
  }
33
27
  /** Flutter ARGB: 0xAARRGGBB, alpha FIRST. A 6-digit number has alpha 0x00 = transparent. */
@@ -36,7 +30,7 @@ function fromArgbNumber(n) {
36
30
  const r = (n >>> 16) & 0xff;
37
31
  const g = (n >>> 8) & 0xff;
38
32
  const b = n & 0xff;
39
- return new color_1.Color(r, g, b, a / 255);
33
+ return new Color(r, g, b, a / 255);
40
34
  }
41
35
  /** CSS hex: #RGB, #RGBA, #RRGGBB, #RRGGBBAA. Alpha is LAST. */
42
36
  function fromHex(s) {
@@ -58,7 +52,7 @@ function fromHex(s) {
58
52
  const a = full.length === 8 ? parseInt(full.slice(6, 8), 16) / 255 : 1;
59
53
  if ([r, g, b].some(Number.isNaN))
60
54
  throw new Error(`Invalid hex color: "${s}"`);
61
- return new color_1.Color(r, g, b, a);
55
+ return new Color(r, g, b, a);
62
56
  }
63
57
  // The full CSS named-color set (~148, incl. the synonyms grey/gray and `transparent`).
64
58
  // [r, g, b] or [r, g, b, alpha]. Lower-cased keys; `toColor` lower-cases the input.
@@ -1,10 +1,10 @@
1
- import { ImageElement, CustomImage } from "../elements/image-element";
2
- import { PDFElement } from "../elements/pdf-element";
3
- import { ColorInput } from "./color";
4
- import { Insets } from "./insets";
1
+ import { ImageElement, CustomImage } from "../elements/image-element.js";
2
+ import { PDFElement } from "../elements/pdf-element.js";
3
+ import { ColorInput } from "./color.js";
4
+ import { Insets } from "./insets.js";
5
5
  /** A horizontal rule (locked §4). */
6
6
  export interface DividerOptions {
7
- /** Line colour (default a light grey). */
7
+ /** Line color (default a light gray). */
8
8
  color?: ColorInput;
9
9
  /** Line thickness in points (default 1). */
10
10
  thickness?: number;
@@ -14,11 +14,11 @@ export interface DividerOptions {
14
14
  /**
15
15
  * A horizontal rule that spans the parent's width. Maps to a `LineElement` (hiding its
16
16
  * `xEnd`/`yEnd` mechanics) wrapped in a `PaddingElement` - the line has no height of its
17
- * own, so the padding gives it vertical room and centres the rule. Use inside a Column.
17
+ * own, so the padding gives it vertical room and centers the rule. Use inside a Column.
18
18
  */
19
19
  export declare function Divider(opts?: DividerOptions): PDFElement;
20
- /** An image source: a local file path, or a `CustomImage` (e.g. a browser-supplied image). */
21
- export type ImageSource = string | CustomImage;
20
+ /** An image source: a local file path (Node), raw bytes (e.g. a browser fetch/upload), or a `CustomImage`. */
21
+ export type ImageSource = string | Uint8Array | CustomImage;
22
22
  /** How the image fills its box (locked §4). Mirrors CSS `object-fit`. */
23
23
  export type ImageFit = "none" | "contain" | "cover" | "fill";
24
24
  export interface ImageOptions {
@@ -1,47 +1,46 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Divider = Divider;
4
- exports.Image = Image;
5
- const line_element_1 = require("../elements/line-element");
6
- const padding_element_1 = require("../elements/layout/padding-element");
7
- const image_element_1 = require("../elements/image-element");
8
- const color_1 = require("./color");
9
- const insets_1 = require("./insets");
1
+ import { LineElement } from "../elements/line-element.js";
2
+ import { PaddingElement } from "../elements/layout/padding-element.js";
3
+ import { ImageElement, CustomLocalImage, CustomBytesImage, BoxFit, } from "../elements/image-element.js";
4
+ import { toColor } from "./color.js";
5
+ import { toEdges } from "./insets.js";
10
6
  const DEFAULT_DIVIDER_COLOR = "lightgray";
11
7
  const DEFAULT_DIVIDER_MARGIN = { y: 6 };
12
8
  /**
13
9
  * A horizontal rule that spans the parent's width. Maps to a `LineElement` (hiding its
14
10
  * `xEnd`/`yEnd` mechanics) wrapped in a `PaddingElement` - the line has no height of its
15
- * own, so the padding gives it vertical room and centres the rule. Use inside a Column.
11
+ * own, so the padding gives it vertical room and centers the rule. Use inside a Column.
16
12
  */
17
- function Divider(opts = {}) {
18
- var _a, _b, _c;
19
- const line = new line_element_1.LineElement({
13
+ export function Divider(opts = {}) {
14
+ const line = new LineElement({
20
15
  x: 0,
21
16
  y: 0,
22
17
  xEnd: 0, // resolved to the parent's width at layout time
23
18
  yEnd: 0, // horizontal: no vertical span
24
- color: (0, color_1.toColor)((_a = opts.color) !== null && _a !== void 0 ? _a : DEFAULT_DIVIDER_COLOR),
25
- strokeWidth: (_b = opts.thickness) !== null && _b !== void 0 ? _b : 1,
19
+ color: toColor(opts.color ?? DEFAULT_DIVIDER_COLOR),
20
+ strokeWidth: opts.thickness ?? 1,
26
21
  });
27
- return new padding_element_1.PaddingElement({
28
- margin: (0, insets_1.toEdges)((_c = opts.margin) !== null && _c !== void 0 ? _c : DEFAULT_DIVIDER_MARGIN),
22
+ return new PaddingElement({
23
+ margin: toEdges(opts.margin ?? DEFAULT_DIVIDER_MARGIN),
29
24
  child: line,
30
25
  });
31
26
  }
32
27
  const FIT = {
33
- none: image_element_1.BoxFit.none,
34
- contain: image_element_1.BoxFit.contain,
35
- cover: image_element_1.BoxFit.cover,
36
- fill: image_element_1.BoxFit.fill,
28
+ none: BoxFit.none,
29
+ contain: BoxFit.contain,
30
+ cover: BoxFit.cover,
31
+ fill: BoxFit.fill,
37
32
  };
38
33
  /**
39
34
  * An image. `src` is a local file path (wrapped in a `CustomLocalImage`) or a ready
40
35
  * `CustomImage` for non-filesystem sources. Maps to an `ImageElement`.
41
36
  */
42
- function Image(src, opts = {}) {
43
- return new image_element_1.ImageElement({
44
- image: typeof src === "string" ? new image_element_1.CustomLocalImage(src) : src,
37
+ export function Image(src, opts = {}) {
38
+ return new ImageElement({
39
+ image: typeof src === "string"
40
+ ? new CustomLocalImage(src)
41
+ : src instanceof Uint8Array
42
+ ? new CustomBytesImage(src)
43
+ : src,
45
44
  width: opts.width,
46
45
  height: opts.height,
47
46
  fit: opts.fit ? FIT[opts.fit] : undefined,
@@ -1,5 +1,5 @@
1
- import { PDFElement } from "../elements/pdf-element";
2
- import { PDFDocumentElement } from "../elements/pdf-document-element";
1
+ import { PDFElement } from "../elements/pdf-element.js";
2
+ import { PDFDocumentElement } from "../elements/pdf-document-element.js";
3
3
  /**
4
4
  * The framework-agnostic contract (the firewall). A binding (Vue/React, or any tree-builder)
5
5
  * produces a tree of these plain descriptors; `build` turns each node into an engine element
@@ -1,29 +1,24 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerElement = registerElement;
4
- exports.build = build;
5
- exports.buildDocument = buildDocument;
6
- const text_1 = require("./text");
7
- const layout_1 = require("./layout");
8
- const content_1 = require("./content");
9
- const structure_1 = require("./structure");
1
+ import { Text, Paragraph, span } from "./text.js";
2
+ import { Column, Row, Box, Padding, Spacer, Expanded, Positioned } from "./layout.js";
3
+ import { Divider, Image } from "./content.js";
4
+ import { Page, Document, DefaultTextStyle } from "./structure.js";
5
+ import { Table } from "./table.js";
10
6
  // Element children (Column/Row/Box/Page/…) → built recursively. Text/Paragraph children are
11
7
  // strings or `span` descriptors and become content instead.
12
8
  function elementChildren(children) {
13
9
  return children.map(build);
14
10
  }
15
11
  function textOf(node) {
16
- var _a;
17
12
  if (typeof node === "string")
18
13
  return node;
19
- const first = (_a = node.children) === null || _a === void 0 ? void 0 : _a[0];
14
+ const first = node.children?.[0];
20
15
  return typeof first === "string" ? first : "";
21
16
  }
22
17
  function toSegment(c) {
23
18
  if (typeof c === "string")
24
- return (0, text_1.span)(c);
19
+ return span(c);
25
20
  if (c.type === "span")
26
- return (0, text_1.span)(textOf(c), c.props);
21
+ return span(textOf(c), c.props);
27
22
  throw new Error(`A Text child must be a string or a span, got "${c.type}"`);
28
23
  }
29
24
  function textContent(children) {
@@ -31,40 +26,89 @@ function textContent(children) {
31
26
  return children[0];
32
27
  return children.map(toSegment);
33
28
  }
29
+ // A `<TableCell>`'s content → a `Cell`: a lone string stays a string (the table wraps it in Text);
30
+ // otherwise its children are built (a single element, or a Column of several).
31
+ function buildCell(cell) {
32
+ if (typeof cell === "string")
33
+ return cell;
34
+ const kids = cell.children ?? [];
35
+ if (kids.length === 1 && typeof kids[0] === "string")
36
+ return kids[0];
37
+ if (kids.length === 0)
38
+ return "";
39
+ const els = kids.map(build);
40
+ return els.length === 1 ? els[0] : Column(els);
41
+ }
42
+ // A `<TableRow>`'s `<TableCell>` children → one row of `Cell`s.
43
+ function rowCells(row) {
44
+ return (row.children ?? [])
45
+ .filter((c) => typeof c !== "string" && c.type === "table-cell")
46
+ .map(buildCell);
47
+ }
48
+ // A `#header` / `#footer` slot's content → one element (a Column if it holds several; strings → Text).
49
+ function slotElement(node) {
50
+ const els = (node.children ?? []).map((c) => (typeof c === "string" ? Text(c) : build(c)));
51
+ return els.length === 1 ? els[0] : Column(els);
52
+ }
34
53
  const REGISTRY = {
35
- document: (props, children) => (0, structure_1.Document)(props, elementChildren(children)),
36
- page: (props, children) => (0, structure_1.Page)(props, elementChildren(children)),
37
- column: (props, children) => (0, layout_1.Column)(props, elementChildren(children)),
38
- row: (props, children) => (0, layout_1.Row)(props, elementChildren(children)),
39
- box: (props, children) => (0, layout_1.Box)(props, elementChildren(children)),
40
- padding: (props, children) => { var _a; return (0, layout_1.Padding)((_a = props.insets) !== null && _a !== void 0 ? _a : 0, elementChildren(children)[0]); },
41
- expanded: (props, children) => (0, layout_1.Expanded)(props, elementChildren(children)[0]),
42
- spacer: (props) => (0, layout_1.Spacer)(props === null || props === void 0 ? void 0 : props.flex),
43
- divider: (props) => (0, content_1.Divider)(props),
44
- image: (props) => (0, content_1.Image)(props.src, props),
45
- text: (props, children) => (0, text_1.Text)(textContent(children), props),
46
- paragraph: (props, children) => (0, text_1.Paragraph)(textContent(children), props),
54
+ document: (props, children) => {
55
+ const doc = Document(props, elementChildren(children));
56
+ // A binding can register fonts declaratively: `fonts: { Name: bytes | path | family }`.
57
+ if (props.fonts) {
58
+ for (const [name, src] of Object.entries(props.fonts))
59
+ doc.addFont(name, src);
60
+ }
61
+ return doc;
62
+ },
63
+ page: (props, children) => {
64
+ // `#header` / `#footer` arrive as `page-header` / `page-footer` marker children (read raw); the rest
65
+ // is the body. Both repeat on every physical page the body paginates onto.
66
+ const slot = (type) => {
67
+ const m = children.find((c) => typeof c !== "string" && c.type === type);
68
+ return m ? slotElement(m) : undefined;
69
+ };
70
+ const body = children.filter((c) => typeof c === "string" || (c.type !== "page-header" && c.type !== "page-footer"));
71
+ return Page({ ...props, header: slot("page-header"), footer: slot("page-footer") }, elementChildren(body));
72
+ },
73
+ column: (props, children) => Column(props, elementChildren(children)),
74
+ row: (props, children) => Row(props, elementChildren(children)),
75
+ box: (props, children) => Box(props, elementChildren(children)),
76
+ padding: (props, children) => Padding(props.insets ?? 0, elementChildren(children)[0]),
77
+ expanded: (props, children) => Expanded(props, elementChildren(children)[0]),
78
+ spacer: (props) => Spacer(props?.flex),
79
+ divider: (props) => Divider(props),
80
+ image: (props) => Image(props.src, props),
81
+ text: (props, children) => Text(textContent(children), props),
82
+ paragraph: (props, children) => Paragraph(textContent(children), props),
83
+ // `<Table>` reads its `<TableRow>`/`<TableCell>` structure raw; one row may be marked `header`.
84
+ table: (props, children) => {
85
+ const rows = children.filter((c) => typeof c !== "string" && c.type === "table-row");
86
+ const header = rows.find((r) => r.props?.header);
87
+ const body = rows.filter((r) => !r.props?.header);
88
+ return Table({ ...props, header: header ? rowCells(header) : undefined }, body.map(rowCells));
89
+ },
90
+ positioned: (props, children) => Positioned(props, elementChildren(children)[0]),
91
+ "default-text-style": (props, children) => DefaultTextStyle(props, elementChildren(children)),
47
92
  };
48
93
  /**
49
94
  * Registers a custom element type, so a binding (or a user-defined component) can introduce
50
95
  * its own tag that resolves to an engine element through this same seam. Overwrites an
51
96
  * existing type of the same name.
52
97
  */
53
- function registerElement(type, factory) {
98
+ export function registerElement(type, factory) {
54
99
  REGISTRY[type] = factory;
55
100
  }
56
101
  /** Turns one descriptor node (or bare string → `Text`) into an engine element. */
57
- function build(node) {
58
- var _a, _b;
102
+ export function build(node) {
59
103
  if (typeof node === "string")
60
- return (0, text_1.Text)(node);
104
+ return Text(node);
61
105
  const factory = REGISTRY[node.type];
62
106
  if (!factory)
63
107
  throw new Error(`Unknown element type: "${node.type}"`);
64
- return factory((_a = node.props) !== null && _a !== void 0 ? _a : {}, (_b = node.children) !== null && _b !== void 0 ? _b : []);
108
+ return factory(node.props ?? {}, node.children ?? []);
65
109
  }
66
110
  /** Builds a descriptor tree whose root is a `document` into the renderable root element. */
67
- function buildDocument(root) {
111
+ export function buildDocument(root) {
68
112
  if (root.type !== "document")
69
113
  throw new Error(`Expected a "document" root, got "${root.type}"`);
70
114
  return build(root);
@@ -1,8 +1,8 @@
1
- export * from "./color";
2
- export * from "./insets";
3
- export * from "./text";
4
- export * from "./layout";
5
- export * from "./content";
6
- export * from "./structure";
7
- export * from "./table";
8
- export * from "./descriptor";
1
+ export * from "./color.js";
2
+ export * from "./insets.js";
3
+ export * from "./text.js";
4
+ export * from "./layout.js";
5
+ export * from "./content.js";
6
+ export * from "./structure.js";
7
+ export * from "./table.js";
8
+ export * from "./descriptor.js";
package/dist/api/index.js CHANGED
@@ -1,27 +1,11 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
1
  // The intuitive API layer: declarative factory functions + input normalizers that compile
18
2
  // down to the core engine elements. A thin, curated surface ON TOP of the engine - the
19
3
  // core classes stay untouched and remain exported for power users.
20
- __exportStar(require("./color"), exports);
21
- __exportStar(require("./insets"), exports);
22
- __exportStar(require("./text"), exports);
23
- __exportStar(require("./layout"), exports);
24
- __exportStar(require("./content"), exports);
25
- __exportStar(require("./structure"), exports);
26
- __exportStar(require("./table"), exports);
27
- __exportStar(require("./descriptor"), exports);
4
+ export * from "./color.js";
5
+ export * from "./insets.js";
6
+ export * from "./text.js";
7
+ export * from "./layout.js";
8
+ export * from "./content.js";
9
+ export * from "./structure.js";
10
+ export * from "./table.js";
11
+ export * from "./descriptor.js";
@@ -1,9 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toEdges = toEdges;
4
1
  /** Normalizes any `Insets` to the engine's `[top, right, bottom, left]`. */
5
- function toEdges(i) {
6
- var _a, _b, _c, _d, _e, _f;
2
+ export function toEdges(i) {
7
3
  if (typeof i === "number")
8
4
  return [i, i, i, i];
9
5
  if (Array.isArray(i))
@@ -13,9 +9,9 @@ function toEdges(i) {
13
9
  // axis interpretation (an empty object is all-zero either way).
14
10
  const o = i;
15
11
  if (o.x !== undefined || o.y !== undefined) {
16
- const x = (_a = o.x) !== null && _a !== void 0 ? _a : 0;
17
- const y = (_b = o.y) !== null && _b !== void 0 ? _b : 0;
12
+ const x = o.x ?? 0;
13
+ const y = o.y ?? 0;
18
14
  return [y, x, y, x];
19
15
  }
20
- return [(_c = o.top) !== null && _c !== void 0 ? _c : 0, (_d = o.right) !== null && _d !== void 0 ? _d : 0, (_e = o.bottom) !== null && _e !== void 0 ? _e : 0, (_f = o.left) !== null && _f !== void 0 ? _f : 0];
16
+ return [o.top ?? 0, o.right ?? 0, o.bottom ?? 0, o.left ?? 0];
21
17
  }
@@ -1,13 +1,13 @@
1
- import { ContainerElement } from "../elements/container-element";
2
- import { RowElement } from "../elements/row-element";
3
- import { RectangleElement } from "../elements/rectangle-element";
4
- import { ExpandedElement } from "../elements/layout/expanded-element";
5
- import { PaddingElement } from "../elements/layout/padding-element";
6
- import { PositionedElement, PositionedInsets } from "../elements/layout/positioned-element";
7
- import { PDFElement } from "../elements/pdf-element";
8
- import { MainAlign, CrossAlign } from "../utils/flex-layout";
9
- import { ColorInput } from "./color";
10
- import { Insets } from "./insets";
1
+ import { ContainerElement } from "../elements/container-element.js";
2
+ import { RowElement } from "../elements/row-element.js";
3
+ import { RectangleElement } from "../elements/rectangle-element.js";
4
+ import { ExpandedElement } from "../elements/layout/expanded-element.js";
5
+ import { PaddingElement } from "../elements/layout/padding-element.js";
6
+ import { PositionedElement, PositionedInsets } from "../elements/layout/positioned-element.js";
7
+ import { PDFElement } from "../elements/pdf-element.js";
8
+ import { MainAlign, CrossAlign } from "../utils/flex-layout.js";
9
+ import { ColorInput } from "./color.js";
10
+ import { Insets } from "./insets.js";
11
11
  /** Options shared by the `Column` and `Row` stacks (locked §4). */
12
12
  export interface StackOptions {
13
13
  /** Space inserted between children, in points. */
@@ -26,9 +26,9 @@ export declare function Row(children: PDFElement[]): RowElement;
26
26
  export declare function Row(opts: StackOptions, children: PDFElement[]): RowElement;
27
27
  /** A bordered / filled box that wraps its children (locked §4). */
28
28
  export interface BoxOptions {
29
- /** Border (stroke) colour. A box has a border only when `border` or `borderWidth` is set. */
29
+ /** Border (stroke) color. A box has a border only when `border` or `borderWidth` is set. */
30
30
  border?: ColorInput;
31
- /** Per-side border colours - override/add to `border`. Any of these makes the box draw
31
+ /** Per-side border colors - override/add to `border`. Any of these makes the box draw
32
32
  * individual side lines (sharp corners) instead of a uniform frame - this is how you get
33
33
  * grid lines (e.g. a cell with only `borderBottom` + `borderRight`). */
34
34
  borderTop?: ColorInput;
@@ -37,7 +37,7 @@ export interface BoxOptions {
37
37
  borderLeft?: ColorInput;
38
38
  /** Border thickness in points (default 1 when a border is present). */
39
39
  borderWidth?: number;
40
- /** Background fill colour. */
40
+ /** Background fill color. */
41
41
  bg?: ColorInput;
42
42
  /** Inner padding between the border and the children. */
43
43
  padding?: Insets;
@@ -63,9 +63,11 @@ export declare function Box(opts: BoxOptions, children: PDFElement[]): Rectangle
63
63
  /** Insets a single child by `padding` (a number / `{x,y}` / `{top,…}` / 4-tuple). */
64
64
  export declare function Padding(padding: Insets, child: PDFElement): PaddingElement;
65
65
  /**
66
- * Places a child OUT OF FLOW, relative to the nearest enclosing `relative` Box. Offsets are from
67
- * the frame's edges (points); a negative `top`/`left` lets the child poke into or out of the corner
68
- * - a badge, a tab, a ribbon. `Positioned({ top, left, right, bottom }, child)`.
66
+ * Places a child OUT OF FLOW, relative to the nearest enclosing `relative` Box. Two ways, pick per
67
+ * axis: pin to EDGES - `Positioned({ top, left, right, bottom }, child)`, where a negative value
68
+ * pokes into / out of the corner (a badge, a tab, a ribbon) and pinning both sides stretches; or
69
+ * ANCHOR + nudge - `Positioned({ h: "center", v: "end", x: -10, y: -8 }, child)`, i.e. centered /
70
+ * end-aligned with a pixel offset. An edge wins over an anchor on the same axis.
69
71
  */
70
72
  export declare function Positioned(opts: PositionedInsets, child: PDFElement): PositionedElement;
71
73
  /**