@jasy/pdf 1.0.0-alpha.1 → 1.0.0-alpha.10
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/LICENSE +21 -0
- package/README.md +35 -21
- package/dist/api/args.d.ts +1 -1
- package/dist/api/args.js +2 -5
- package/dist/api/color.d.ts +4 -4
- package/dist/api/color.js +11 -17
- package/dist/api/content.d.ts +20 -13
- package/dist/api/content.js +44 -29
- package/dist/api/descriptor.d.ts +2 -2
- package/dist/api/descriptor.js +121 -31
- package/dist/api/dimension.d.ts +76 -0
- package/dist/api/dimension.js +43 -0
- package/dist/api/font-url.d.ts +28 -0
- package/dist/api/font-url.js +134 -0
- package/dist/api/forms.d.ts +347 -0
- package/dist/api/forms.js +189 -0
- package/dist/api/gradient.d.ts +53 -0
- package/dist/api/gradient.js +97 -0
- package/dist/api/index.d.ts +13 -8
- package/dist/api/index.js +13 -24
- package/dist/api/insets.d.ts +19 -12
- package/dist/api/insets.js +19 -12
- package/dist/api/layout.d.ts +149 -25
- package/dist/api/layout.js +182 -51
- package/dist/api/page-builder.d.ts +40 -0
- package/dist/api/page-builder.js +44 -0
- package/dist/api/structure.d.ts +100 -13
- package/dist/api/structure.js +179 -88
- package/dist/api/table.d.ts +5 -5
- package/dist/api/table.js +38 -26
- package/dist/api/text.d.ts +90 -4
- package/dist/api/text.js +116 -31
- package/dist/assets/font-data.d.ts +2 -0
- package/dist/assets/font-data.js +21 -0
- package/dist/assets/font-data.ts +37 -0
- package/dist/common/color.js +1 -5
- package/dist/constants/page-sizes.js +3 -6
- package/dist/constants/pdf-parts.js +1 -4
- package/dist/crypto/legacy-handler.d.ts +43 -0
- package/dist/crypto/legacy-handler.js +145 -0
- package/dist/crypto/security-handler.d.ts +62 -0
- package/dist/crypto/security-handler.js +155 -0
- package/dist/crypto/webcrypto.d.ts +11 -0
- package/dist/crypto/webcrypto.js +62 -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 +145 -0
- package/dist/edit/document.js +642 -0
- package/dist/edit/fill.d.ts +65 -0
- package/dist/edit/fill.js +424 -0
- package/dist/edit/flatten.d.ts +54 -0
- package/dist/edit/flatten.js +462 -0
- package/dist/edit/index.d.ts +20 -0
- package/dist/edit/index.js +18 -0
- package/dist/edit/lexer.d.ts +30 -0
- package/dist/edit/lexer.js +285 -0
- package/dist/edit/objects.d.ts +68 -0
- package/dist/edit/objects.js +83 -0
- package/dist/edit/writer.d.ts +60 -0
- package/dist/edit/writer.js +283 -0
- package/dist/elements/container-element.d.ts +50 -5
- package/dist/elements/container-element.js +100 -39
- 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/image-element.d.ts +58 -6
- package/dist/elements/image-element.js +145 -111
- package/dist/elements/index.d.ts +26 -10
- package/dist/elements/index.js +26 -28
- package/dist/elements/layout/anchor-element.d.ts +23 -0
- package/dist/elements/layout/anchor-element.js +27 -0
- package/dist/elements/layout/bookmark-element.d.ts +27 -0
- package/dist/elements/layout/bookmark-element.js +30 -0
- package/dist/elements/layout/default-text-style-element.d.ts +30 -0
- package/dist/elements/layout/default-text-style-element.js +47 -0
- package/dist/elements/layout/deferred-element.d.ts +3 -3
- package/dist/elements/layout/deferred-element.js +4 -8
- package/dist/elements/layout/expanded-element.d.ts +6 -4
- package/dist/elements/layout/expanded-element.js +22 -17
- 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/link-element.d.ts +33 -0
- package/dist/elements/layout/link-element.js +39 -0
- package/dist/elements/layout/padding-element.d.ts +10 -5
- package/dist/elements/layout/padding-element.js +25 -16
- 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/page-builder-element.d.ts +32 -0
- package/dist/elements/layout/page-builder-element.js +45 -0
- package/dist/elements/layout/positioned-element.d.ts +47 -0
- package/dist/elements/layout/positioned-element.js +73 -0
- package/dist/elements/layout/repeating-header-element.d.ts +3 -3
- package/dist/elements/layout/repeating-header-element.js +8 -12
- package/dist/elements/layout/rotated-box-element.d.ts +35 -0
- package/dist/elements/layout/rotated-box-element.js +54 -0
- package/dist/elements/layout/rotated-element.d.ts +32 -0
- package/dist/elements/layout/rotated-element.js +41 -0
- package/dist/elements/layout/sized-container-element.d.ts +2 -2
- package/dist/elements/layout/sized-container-element.js +6 -11
- package/dist/elements/layout/struct-group.d.ts +28 -0
- package/dist/elements/layout/struct-group.js +42 -0
- package/dist/elements/line-element.d.ts +3 -3
- package/dist/elements/line-element.js +5 -10
- package/dist/elements/page-element.d.ts +26 -6
- package/dist/elements/page-element.js +72 -27
- package/dist/elements/pdf-document-element.d.ts +10 -4
- package/dist/elements/pdf-document-element.js +11 -10
- package/dist/elements/pdf-element.d.ts +150 -4
- package/dist/elements/pdf-element.js +138 -20
- package/dist/elements/rectangle-element.d.ts +52 -11
- package/dist/elements/rectangle-element.js +129 -36
- package/dist/elements/row-element.d.ts +45 -4
- package/dist/elements/row-element.js +66 -18
- package/dist/elements/text-element.d.ts +132 -11
- package/dist/elements/text-element.js +206 -56
- 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/index.d.ts +3 -3
- package/dist/index.js +3 -19
- package/dist/ir/display-list.d.ts +197 -8
- package/dist/ir/display-list.js +8 -2
- package/dist/layout/alignment.d.ts +10 -0
- package/dist/layout/alignment.js +6 -0
- package/dist/layout/box-constraints.d.ts +61 -0
- package/dist/layout/box-constraints.js +88 -6
- package/dist/layout/collect-images.d.ts +9 -0
- package/dist/layout/collect-images.js +34 -0
- package/dist/layout/fragmentation.d.ts +20 -1
- package/dist/layout/fragmentation.js +74 -18
- package/dist/layout/insets.d.ts +28 -0
- package/dist/layout/insets.js +20 -0
- package/dist/platform/browser-fs.d.ts +2 -0
- package/dist/platform/browser-fs.js +9 -0
- package/dist/platform/browser-image.d.ts +5 -0
- package/dist/platform/browser-image.js +13 -0
- package/dist/platform/node-fs.d.ts +2 -0
- package/dist/platform/node-fs.js +10 -0
- package/dist/platform/node-image.d.ts +5 -0
- package/dist/platform/node-image.js +9 -0
- package/dist/renderer/anchor-renderer.d.ts +6 -0
- package/dist/renderer/anchor-renderer.js +11 -0
- package/dist/renderer/bookmark-renderer.d.ts +6 -0
- package/dist/renderer/bookmark-renderer.js +11 -0
- package/dist/renderer/container-renderer.d.ts +3 -3
- package/dist/renderer/container-renderer.js +11 -28
- package/dist/renderer/default-text-style-renderer.d.ts +6 -0
- package/dist/renderer/default-text-style-renderer.js +10 -0
- package/dist/renderer/deferred-renderer.d.ts +3 -3
- package/dist/renderer/deferred-renderer.js +8 -23
- package/dist/renderer/emoji-image.d.ts +9 -0
- package/dist/renderer/emoji-image.js +68 -0
- package/dist/renderer/expanded-renderer.d.ts +3 -3
- package/dist/renderer/expanded-renderer.js +6 -21
- package/dist/renderer/form-field-renderer.d.ts +20 -0
- package/dist/renderer/form-field-renderer.js +12 -0
- package/dist/renderer/image-renderer.d.ts +4 -4
- package/dist/renderer/image-renderer.js +86 -75
- package/dist/renderer/index.d.ts +10 -10
- package/dist/renderer/index.js +10 -26
- package/dist/renderer/keep-together-renderer.d.ts +10 -0
- package/dist/renderer/keep-together-renderer.js +12 -0
- package/dist/renderer/line-renderer.d.ts +3 -3
- package/dist/renderer/line-renderer.js +13 -28
- package/dist/renderer/link-renderer.d.ts +6 -0
- package/dist/renderer/link-renderer.js +11 -0
- package/dist/renderer/padding-renderer.d.ts +3 -3
- package/dist/renderer/padding-renderer.js +6 -21
- package/dist/renderer/page-break-renderer.d.ts +14 -0
- package/dist/renderer/page-break-renderer.js +15 -0
- package/dist/renderer/page-builder-renderer.d.ts +6 -0
- package/dist/renderer/page-builder-renderer.js +11 -0
- package/dist/renderer/page-renderer.d.ts +2 -2
- package/dist/renderer/page-renderer.js +115 -77
- package/dist/renderer/pdf-backend.d.ts +31 -4
- package/dist/renderer/pdf-backend.js +287 -49
- package/dist/renderer/pdf-config.js +4 -7
- package/dist/renderer/pdf-document-class.d.ts +5 -5
- package/dist/renderer/pdf-document-class.js +27 -41
- package/dist/renderer/pdf-document-renderer.d.ts +19 -10
- package/dist/renderer/pdf-document-renderer.js +135 -91
- package/dist/renderer/pdf-renderer.d.ts +2 -2
- package/dist/renderer/pdf-renderer.js +165 -90
- package/dist/renderer/positioned-renderer.d.ts +6 -0
- package/dist/renderer/positioned-renderer.js +10 -0
- package/dist/renderer/rectangle-renderer.d.ts +3 -3
- package/dist/renderer/rectangle-renderer.js +49 -44
- package/dist/renderer/repeating-header-renderer.d.ts +3 -3
- package/dist/renderer/repeating-header-renderer.js +10 -26
- package/dist/renderer/rotated-renderer.d.ts +7 -0
- package/dist/renderer/rotated-renderer.js +29 -0
- package/dist/renderer/row-renderer.d.ts +3 -3
- package/dist/renderer/row-renderer.js +11 -28
- package/dist/renderer/struct-group-renderer.d.ts +10 -0
- package/dist/renderer/struct-group-renderer.js +24 -0
- package/dist/renderer/text-renderer.d.ts +9 -5
- package/dist/renderer/text-renderer.js +437 -89
- package/dist/text/advance.d.ts +34 -0
- package/dist/text/advance.js +45 -0
- package/dist/text/arabic.d.ts +20 -0
- package/dist/text/arabic.js +101 -0
- package/dist/text/bidi.d.ts +33 -0
- package/dist/text/bidi.js +94 -0
- package/dist/text/emoji-codepoints.d.ts +1 -0
- package/dist/text/emoji-codepoints.js +29 -0
- package/dist/text/font-fallback.d.ts +25 -0
- package/dist/text/font-fallback.js +38 -0
- package/dist/text/line-breaker.d.ts +50 -12
- package/dist/text/line-breaker.js +158 -37
- package/dist/text/line-metrics.d.ts +49 -0
- package/dist/text/line-metrics.js +57 -0
- package/dist/text/orphans-widows.d.ts +27 -0
- package/dist/text/orphans-widows.js +53 -0
- package/dist/text/shape.d.ts +29 -0
- package/dist/text/shape.js +67 -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 +60 -0
- package/dist/text/text-style.js +79 -0
- package/dist/utils/afm-parser.d.ts +34 -0
- package/dist/utils/afm-parser.js +76 -17
- package/dist/utils/bytes.d.ts +24 -0
- package/dist/utils/bytes.js +76 -0
- package/dist/utils/dest-registry.d.ts +22 -0
- package/dist/utils/dest-registry.js +32 -0
- package/dist/utils/flex-layout.d.ts +36 -9
- package/dist/utils/flex-layout.js +207 -30
- package/dist/utils/font-metrics.d.ts +20 -1
- package/dist/utils/font-metrics.js +1 -2
- package/dist/utils/font-path.js +3 -6
- package/dist/utils/gsub.d.ts +36 -0
- package/dist/utils/gsub.js +178 -0
- package/dist/utils/image-helper.d.ts +6 -5
- package/dist/utils/image-helper.js +101 -111
- package/dist/utils/inflate.d.ts +16 -0
- package/dist/utils/inflate.js +52 -0
- package/dist/utils/md5.d.ts +4 -0
- package/dist/utils/md5.js +79 -0
- package/dist/utils/outline.d.ts +21 -0
- package/dist/utils/outline.js +79 -0
- package/dist/utils/pdf-object-manager.d.ts +110 -8
- package/dist/utils/pdf-object-manager.js +583 -141
- package/dist/utils/renderer-registry.d.ts +13 -1
- package/dist/utils/renderer-registry.js +23 -6
- package/dist/utils/struct-tree.d.ts +44 -0
- package/dist/utils/struct-tree.js +129 -0
- package/dist/utils/ttf-parser.d.ts +134 -2
- package/dist/utils/ttf-parser.js +873 -50
- package/dist/utils/ttf-subsetter.d.ts +1 -1
- package/dist/utils/ttf-subsetter.js +40 -42
- package/dist/utils/ua-xmp.d.ts +9 -0
- package/dist/utils/ua-xmp.js +32 -0
- package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
- package/dist/utils/utf8-to-windows1252-encoder.js +11 -4
- package/dist/utils/woff.d.ts +10 -0
- package/dist/utils/woff.js +116 -0
- package/dist/validators/element-validator.d.ts +2 -2
- package/dist/validators/element-validator.js +31 -26
- package/package.json +22 -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
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
<code>npm i @jasy/
|
|
13
|
+
<code>npm i @jasy/e-invoice</code> · <code>npx @jasy/cli</code> · MIT
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
---
|
|
@@ -37,24 +37,34 @@ 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
|
|
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
|
|
|
44
44
|
```ts
|
|
45
|
-
import { renderZugferd } from "@jasy/
|
|
45
|
+
import { renderZugferd } from "@jasy/e-invoice";
|
|
46
46
|
|
|
47
47
|
const { bytes, xml } = await renderZugferd({
|
|
48
48
|
number: "RE-2026-001",
|
|
49
49
|
issueDate: "2026-06-17",
|
|
50
50
|
currency: "EUR",
|
|
51
|
-
seller: {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
{
|
|
57
|
-
|
|
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
|
|
@@ -107,8 +117,8 @@ const pdf = await renderToBytes(
|
|
|
107
117
|
```
|
|
108
118
|
|
|
109
119
|
Real text layout (Adobe AFM metrics, kerning, word-wrap), flexbox-style `gap` / `justify` / `align`,
|
|
110
|
-
boxes with radius and alpha, images, custom TrueType fonts,
|
|
111
|
-
overflows flows onto the next page, headers and footers repeat.
|
|
120
|
+
boxes with radius and alpha, images, custom TrueType fonts, **AES-256 password encryption**, and **real
|
|
121
|
+
pagination** - content that overflows flows onto the next page, headers and footers repeat.
|
|
112
122
|
|
|
113
123
|
---
|
|
114
124
|
|
|
@@ -122,12 +132,15 @@ verify all of it:
|
|
|
122
132
|
740 KB font ships as a ~76 KB subset, and the text stays copy- and searchable.
|
|
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.
|
|
135
|
+
- **AES-256 encryption, pure JS.** Lock a PDF with `renderToBytes(doc, { encrypt: { userPassword } })` - the
|
|
136
|
+
newest standard handler (AES-256, R6 / ISO 32000-2) via the platform WebCrypto: no native crypto, no deps,
|
|
137
|
+
the same code in Node **and** the browser. Owner password + permissions optional.
|
|
125
138
|
- **Real font metrics.** Text is laid out with the Adobe AFM metrics of the standard-14 fonts - kerning
|
|
126
|
-
and word-wrap are
|
|
139
|
+
and word-wrap are _computed_, not guessed.
|
|
127
140
|
- **PDF/A-3, matched not approximated.** The conformance graph is hand-built and **passes veraPDF**, the
|
|
128
141
|
official ISO 19005 validator.
|
|
129
142
|
- **Byte-exact round-trips.** Generate and parse are inverses: `generate → parse → regenerate` reproduces
|
|
130
|
-
the identical XML.
|
|
143
|
+
the identical XML. Over 900 tests hold the line across the workspace.
|
|
131
144
|
- **Schematron, local.** The official EN-16931 + XRechnung rules run via saxon-js (the real XSLT, in pure
|
|
132
145
|
JS) - no Java, no upload.
|
|
133
146
|
|
|
@@ -135,18 +148,19 @@ verify all of it:
|
|
|
135
148
|
|
|
136
149
|
## Packages
|
|
137
150
|
|
|
138
|
-
| Package
|
|
139
|
-
|
|
|
140
|
-
| **[@jasy/
|
|
141
|
-
| **[@jasy/cli](https://
|
|
142
|
-
| **[@jasy/pdf](https://
|
|
151
|
+
| Package | What it is |
|
|
152
|
+
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
153
|
+
| **[@jasy/e-invoice](https://npmx.dev/@jasy/e-invoice)** | ZUGFeRD / XRechnung: your data → PDF/A-3 + EN-16931 XML, with local validation. **The prize.** |
|
|
154
|
+
| **[@jasy/cli](https://npmx.dev/@jasy/cli)** | the `jasy` terminal: read · validate · export, headless **and** interactive |
|
|
155
|
+
| **[@jasy/pdf](https://npmx.dev/@jasy/pdf)** | the declarative, Flutter-style PDF layout engine that powers them |
|
|
143
156
|
|
|
144
157
|
---
|
|
145
158
|
|
|
146
159
|
## Why you can trust it
|
|
147
160
|
|
|
148
|
-
- **
|
|
149
|
-
`generate → parse → regenerate` reproduces the identical XML. Nothing is
|
|
161
|
+
- **Over 900 tests, green** across the workspace (850 in the engine alone). The generator and the parser
|
|
162
|
+
are **byte-exact inverses**: `generate → parse → regenerate` reproduces the identical XML. Nothing is
|
|
163
|
+
silently lost.
|
|
150
164
|
- **The same rules the authorities use** - the official KoSIT EN-16931 + XRechnung Schematron, and the
|
|
151
165
|
official **veraPDF** for the full ISO 19005 (PDF/A) check.
|
|
152
166
|
- **Proven on real third-party invoices**, not only our own.
|
|
@@ -158,7 +172,7 @@ verify all of it:
|
|
|
158
172
|
|
|
159
173
|
jasy targets the documents that matter here: invoices, reports, quotes, datasheets. It is **not** a
|
|
160
174
|
LaTeX / WeasyPrint replacement - no microtypography, hyphenation or bidi, and arbitrary multi-page flow
|
|
161
|
-
of
|
|
175
|
+
of _any_ content is still maturing. For e-invoices (a table, totals, a footer) it is complete - they
|
|
162
176
|
even paginate. We would rather under-promise and over-deliver in the demo above.
|
|
163
177
|
|
|
164
178
|
> **Status:** young and pre-1.0. The API can still shift between minor versions. Everything shown here
|
package/dist/api/args.d.ts
CHANGED
|
@@ -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
|
|
9
|
+
return { opts: a, children: b ?? [] };
|
|
13
10
|
}
|
package/dist/api/color.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
12
|
+
* so a color means the same thing no matter how it was written.
|
|
18
13
|
*/
|
|
19
|
-
function toColor(input) {
|
|
20
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
package/dist/api/content.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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
|
+
import { BoundsInput, SizeInput, RadiusInput } from "./dimension.js";
|
|
5
6
|
/** A horizontal rule (locked §4). */
|
|
6
7
|
export interface DividerOptions {
|
|
7
|
-
/** Line
|
|
8
|
+
/** Line color (default a light gray). */
|
|
8
9
|
color?: ColorInput;
|
|
9
10
|
/** Line thickness in points (default 1). */
|
|
10
11
|
thickness?: number;
|
|
@@ -14,20 +15,26 @@ export interface DividerOptions {
|
|
|
14
15
|
/**
|
|
15
16
|
* A horizontal rule that spans the parent's width. Maps to a `LineElement` (hiding its
|
|
16
17
|
* `xEnd`/`yEnd` mechanics) wrapped in a `PaddingElement` - the line has no height of its
|
|
17
|
-
* own, so the padding gives it vertical room and
|
|
18
|
+
* own, so the padding gives it vertical room and centers the rule. Use inside a Column.
|
|
18
19
|
*/
|
|
19
20
|
export declare function Divider(opts?: DividerOptions): PDFElement;
|
|
20
|
-
/** An image source: a local file path,
|
|
21
|
-
export type ImageSource = string | CustomImage;
|
|
21
|
+
/** An image source: a local file path (Node), raw bytes (e.g. a browser fetch/upload), or a `CustomImage`. */
|
|
22
|
+
export type ImageSource = string | Uint8Array | CustomImage;
|
|
22
23
|
/** How the image fills its box (locked §4). Mirrors CSS `object-fit`. */
|
|
23
24
|
export type ImageFit = "none" | "contain" | "cover" | "fill";
|
|
24
|
-
export interface ImageOptions {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
export interface ImageOptions extends BoundsInput {
|
|
26
|
+
/** Size on each axis: points (fixed) or a percentage string like `"50%"` (a fraction of the offered
|
|
27
|
+
* space). Pin exactly ONE axis and the other follows the image's aspect ratio (CSS `height: auto`). */
|
|
28
|
+
width?: SizeInput;
|
|
29
|
+
height?: SizeInput;
|
|
30
|
+
/** Fit within the box. Default `none`, except where the box was DERIVED rather than given outright -
|
|
31
|
+
* exactly one axis pinned, or an `aspectRatio` - which defaults to `fill` so the image scales into it. */
|
|
28
32
|
fit?: ImageFit;
|
|
29
33
|
/** Corner radius in points (rounds the image box). */
|
|
30
|
-
radius?:
|
|
34
|
+
radius?: RadiusInput;
|
|
35
|
+
/** Alternate text for accessibility (tagged PDF): describes the image for screen readers. With `alt`
|
|
36
|
+
* the image is a `Figure`; without it (and when rendered `accessible`) it counts as decoration. */
|
|
37
|
+
alt?: string;
|
|
31
38
|
}
|
|
32
39
|
/**
|
|
33
40
|
* An image. `src` is a local file path (wrapped in a `CustomLocalImage`) or a ready
|
package/dist/api/content.js
CHANGED
|
@@ -1,50 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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";
|
|
6
|
+
import { toBounds, toDimension, toRadius, } from "./dimension.js";
|
|
10
7
|
const DEFAULT_DIVIDER_COLOR = "lightgray";
|
|
11
8
|
const DEFAULT_DIVIDER_MARGIN = { y: 6 };
|
|
12
9
|
/**
|
|
13
10
|
* A horizontal rule that spans the parent's width. Maps to a `LineElement` (hiding its
|
|
14
11
|
* `xEnd`/`yEnd` mechanics) wrapped in a `PaddingElement` - the line has no height of its
|
|
15
|
-
* own, so the padding gives it vertical room and
|
|
12
|
+
* own, so the padding gives it vertical room and centers the rule. Use inside a Column.
|
|
16
13
|
*/
|
|
17
|
-
function Divider(opts = {}) {
|
|
18
|
-
|
|
19
|
-
const line = new line_element_1.LineElement({
|
|
14
|
+
export function Divider(opts = {}) {
|
|
15
|
+
const line = new LineElement({
|
|
20
16
|
x: 0,
|
|
21
17
|
y: 0,
|
|
22
18
|
xEnd: 0, // resolved to the parent's width at layout time
|
|
23
19
|
yEnd: 0, // horizontal: no vertical span
|
|
24
|
-
color:
|
|
25
|
-
strokeWidth:
|
|
20
|
+
color: toColor(opts.color ?? DEFAULT_DIVIDER_COLOR),
|
|
21
|
+
strokeWidth: opts.thickness ?? 1,
|
|
26
22
|
});
|
|
27
|
-
return new
|
|
28
|
-
margin:
|
|
23
|
+
return new PaddingElement({
|
|
24
|
+
margin: toEdges(opts.margin ?? DEFAULT_DIVIDER_MARGIN),
|
|
29
25
|
child: line,
|
|
30
26
|
});
|
|
31
27
|
}
|
|
32
28
|
const FIT = {
|
|
33
|
-
none:
|
|
34
|
-
contain:
|
|
35
|
-
cover:
|
|
36
|
-
fill:
|
|
29
|
+
none: BoxFit.none,
|
|
30
|
+
contain: BoxFit.contain,
|
|
31
|
+
cover: BoxFit.cover,
|
|
32
|
+
fill: BoxFit.fill,
|
|
37
33
|
};
|
|
38
34
|
/**
|
|
39
35
|
* An image. `src` is a local file path (wrapped in a `CustomLocalImage`) or a ready
|
|
40
36
|
* `CustomImage` for non-filesystem sources. Maps to an `ImageElement`.
|
|
41
37
|
*/
|
|
42
|
-
function Image(src, opts = {}) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
export function Image(src, opts = {}) {
|
|
39
|
+
const w = opts.width !== undefined ? toDimension(opts.width) : undefined;
|
|
40
|
+
const h = opts.height !== undefined ? toDimension(opts.height) : undefined;
|
|
41
|
+
// ANY explicit size scales the image into the box (fit: fill), which is what `<img width height>`
|
|
42
|
+
// does in HTML and what react-pdf does. Two pinned axes used to keep `none` - drawing the image at
|
|
43
|
+
// its pixel size regardless - so a logo asked for at 48x48 spanned a third of the page.
|
|
44
|
+
// An image with NO size given still draws at its intrinsic size, as an unsized `<img>` does.
|
|
45
|
+
const sized = opts.width !== undefined || opts.height !== undefined || opts.aspectRatio !== undefined;
|
|
46
|
+
const fit = opts.fit ? FIT[opts.fit] : sized ? BoxFit.fill : undefined;
|
|
47
|
+
return new ImageElement({
|
|
48
|
+
image: typeof src === "string"
|
|
49
|
+
? new CustomLocalImage(src)
|
|
50
|
+
: src instanceof Uint8Array
|
|
51
|
+
? new CustomBytesImage(src)
|
|
52
|
+
: src,
|
|
53
|
+
width: w?.points,
|
|
54
|
+
height: h?.points,
|
|
55
|
+
widthFactor: w?.factor,
|
|
56
|
+
heightFactor: h?.factor,
|
|
57
|
+
...toBounds(opts),
|
|
58
|
+
fit,
|
|
59
|
+
radius: opts.radius !== undefined ? toRadius(opts.radius) : undefined,
|
|
60
|
+
alt: opts.alt,
|
|
61
|
+
})
|
|
62
|
+
.withAlignSelf(opts.alignSelf)
|
|
63
|
+
.withOrder(opts.order)
|
|
64
|
+
.withFlexShrink(opts.flexShrink);
|
|
50
65
|
}
|
package/dist/api/descriptor.d.ts
CHANGED
|
@@ -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
|
package/dist/api/descriptor.js
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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, PageBreak, keepTogether, Expanded, Positioned, Link, Anchor, Bookmark, Rotated, RotatedBox, } from "./layout.js";
|
|
3
|
+
import { Divider, Image } from "./content.js";
|
|
4
|
+
import { Page, Document, DefaultTextStyle } from "./structure.js";
|
|
5
|
+
import { PageNumber, PageCount } from "./page-builder.js";
|
|
6
|
+
import { Table } from "./table.js";
|
|
7
|
+
import { Checkbox, Dropdown, ListBox, PushButton, RadioGroup, SignatureField, TextField, } from "./forms.js";
|
|
10
8
|
// Element children (Column/Row/Box/Page/…) → built recursively. Text/Paragraph children are
|
|
11
9
|
// strings or `span` descriptors and become content instead.
|
|
12
10
|
function elementChildren(children) {
|
|
13
11
|
return children.map(build);
|
|
14
12
|
}
|
|
15
13
|
function textOf(node) {
|
|
16
|
-
var _a;
|
|
17
14
|
if (typeof node === "string")
|
|
18
15
|
return node;
|
|
19
|
-
const first =
|
|
16
|
+
const first = node.children?.[0];
|
|
20
17
|
return typeof first === "string" ? first : "";
|
|
21
18
|
}
|
|
22
19
|
function toSegment(c) {
|
|
23
20
|
if (typeof c === "string")
|
|
24
|
-
return
|
|
21
|
+
return span(c);
|
|
25
22
|
if (c.type === "span")
|
|
26
|
-
return
|
|
23
|
+
return span(textOf(c), c.props);
|
|
27
24
|
throw new Error(`A Text child must be a string or a span, got "${c.type}"`);
|
|
28
25
|
}
|
|
29
26
|
function textContent(children) {
|
|
@@ -31,40 +28,133 @@ function textContent(children) {
|
|
|
31
28
|
return children[0];
|
|
32
29
|
return children.map(toSegment);
|
|
33
30
|
}
|
|
31
|
+
// A `<TableCell>`'s content → a `Cell`: a lone string stays a string (the table wraps it in Text);
|
|
32
|
+
// otherwise its children are built (a single element, or a Column of several).
|
|
33
|
+
function buildCell(cell) {
|
|
34
|
+
if (typeof cell === "string")
|
|
35
|
+
return cell;
|
|
36
|
+
const kids = cell.children ?? [];
|
|
37
|
+
if (kids.length === 1 && typeof kids[0] === "string")
|
|
38
|
+
return kids[0];
|
|
39
|
+
if (kids.length === 0)
|
|
40
|
+
return "";
|
|
41
|
+
const els = kids.map(build);
|
|
42
|
+
return els.length === 1 ? els[0] : Column(els);
|
|
43
|
+
}
|
|
44
|
+
// A `<TableRow>`'s `<TableCell>` children → one row of `Cell`s.
|
|
45
|
+
function rowCells(row) {
|
|
46
|
+
return (row.children ?? [])
|
|
47
|
+
.filter((c) => typeof c !== "string" && c.type === "table-cell")
|
|
48
|
+
.map(buildCell);
|
|
49
|
+
}
|
|
50
|
+
// A `#header` / `#footer` slot's content → one element (a Column if it holds several; strings → Text).
|
|
51
|
+
function slotElement(node) {
|
|
52
|
+
const els = (node.children ?? []).map((c) => (typeof c === "string" ? Text(c) : build(c)));
|
|
53
|
+
return els.length === 1 ? els[0] : Column(els);
|
|
54
|
+
}
|
|
55
|
+
// The single child a wrapper takes (`Link`, `Anchor`, `Bookmark`, `Rotated`, ...). Several children are
|
|
56
|
+
// stacked in a Column, so `<JasyLink><Text/><Text/></JasyLink>` behaves like the factory would.
|
|
57
|
+
function wrappedChild(children) {
|
|
58
|
+
const els = children.map((c) => (typeof c === "string" ? Text(c) : build(c)));
|
|
59
|
+
if (els.length === 0)
|
|
60
|
+
throw new Error("A wrapper element needs exactly one child");
|
|
61
|
+
return els.length === 1 ? els[0] : Column(els);
|
|
62
|
+
}
|
|
34
63
|
const REGISTRY = {
|
|
35
|
-
document: (props, children) =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
document: (props, children) => {
|
|
65
|
+
const doc = Document(props, elementChildren(children));
|
|
66
|
+
// A binding can register fonts declaratively: `fonts: { Name: bytes | path | family }`.
|
|
67
|
+
if (props.fonts) {
|
|
68
|
+
for (const [name, src] of Object.entries(props.fonts))
|
|
69
|
+
doc.addFont(name, src);
|
|
70
|
+
}
|
|
71
|
+
return doc;
|
|
72
|
+
},
|
|
73
|
+
page: (props, children) => {
|
|
74
|
+
// `#header` / `#footer` arrive as `page-header` / `page-footer` marker children (read raw); the rest
|
|
75
|
+
// is the body. Both repeat on every physical page the body paginates onto.
|
|
76
|
+
const slot = (type) => {
|
|
77
|
+
const m = children.find((c) => typeof c !== "string" && c.type === type);
|
|
78
|
+
return m ? slotElement(m) : undefined;
|
|
79
|
+
};
|
|
80
|
+
const body = children.filter((c) => typeof c === "string" || (c.type !== "page-header" && c.type !== "page-footer"));
|
|
81
|
+
return Page({ ...props, header: slot("page-header"), footer: slot("page-footer") }, elementChildren(body));
|
|
82
|
+
},
|
|
83
|
+
column: (props, children) => Column(props, elementChildren(children)),
|
|
84
|
+
row: (props, children) => Row(props, elementChildren(children)),
|
|
85
|
+
box: (props, children) => Box(props, elementChildren(children)),
|
|
86
|
+
padding: (props, children) => Padding(props.insets ?? 0, elementChildren(children)[0]),
|
|
87
|
+
expanded: (props, children) => Expanded(props, elementChildren(children)[0]),
|
|
88
|
+
spacer: (props) => Spacer(props?.flex),
|
|
89
|
+
"page-break": () => PageBreak(),
|
|
90
|
+
"keep-together": (_props, children) => keepTogether(elementChildren(children)),
|
|
91
|
+
divider: (props) => Divider(props),
|
|
92
|
+
image: (props) => Image(props.src, props),
|
|
93
|
+
text: (props, children) => Text(textContent(children), props),
|
|
94
|
+
paragraph: (props, children) => Paragraph(textContent(children), props),
|
|
95
|
+
// `<Table>` reads its `<TableRow>`/`<TableCell>` structure raw; one row may be marked `header`.
|
|
96
|
+
table: (props, children) => {
|
|
97
|
+
const rows = children.filter((c) => typeof c !== "string" && c.type === "table-row");
|
|
98
|
+
const header = rows.find((r) => r.props?.header);
|
|
99
|
+
const body = rows.filter((r) => !r.props?.header);
|
|
100
|
+
return Table({ ...props, header: header ? rowCells(header) : undefined }, body.map(rowCells));
|
|
101
|
+
},
|
|
102
|
+
positioned: (props, children) => Positioned(props, elementChildren(children)[0]),
|
|
103
|
+
"default-text-style": (props, children) => DefaultTextStyle(props, elementChildren(children)),
|
|
104
|
+
// Navigation. `Link` takes exactly one of `href` (a URL) or `to` (an `Anchor` name) - the factory
|
|
105
|
+
// enforces that, so a template typo surfaces as an error instead of a dead link.
|
|
106
|
+
link: (props, children) => Link(props, wrappedChild(children)),
|
|
107
|
+
anchor: (props, children) => Anchor(props, wrappedChild(children)),
|
|
108
|
+
bookmark: (props, children) => Bookmark(props, wrappedChild(children)),
|
|
109
|
+
// Transforms.
|
|
110
|
+
rotated: (props, children) => Rotated(props, wrappedChild(children)),
|
|
111
|
+
"rotated-box": (props, children) => RotatedBox(props, wrappedChild(children)),
|
|
112
|
+
// Page numbers. `PageBuilder` itself is deliberately NOT exposed: it takes a closure, which a template
|
|
113
|
+
// cannot express. These two cover what the closure exists for.
|
|
114
|
+
"page-number": (props) => PageNumber(props),
|
|
115
|
+
"page-count": (props) => PageCount(props),
|
|
116
|
+
// Form fields. The three list-like ones take their choices as a SECOND argument in TypeScript; a
|
|
117
|
+
// template has no second argument, so here they arrive as an `options` prop. `choiceList` accepts both
|
|
118
|
+
// the short spelling and the explicit one, because an export value and its label are usually the same
|
|
119
|
+
// string and having to write it twice is noise.
|
|
120
|
+
"text-field": (props) => TextField(props),
|
|
121
|
+
checkbox: (props) => Checkbox(props),
|
|
122
|
+
"radio-group": (props) => RadioGroup(props, radioChoices(props.options)),
|
|
123
|
+
dropdown: (props) => Dropdown(props, choiceList(props.options)),
|
|
124
|
+
"list-box": (props) => ListBox(props, choiceList(props.options)),
|
|
125
|
+
"push-button": (props) => PushButton(props),
|
|
126
|
+
"signature-field": (props) => SignatureField(props),
|
|
47
127
|
};
|
|
128
|
+
/** `["S", "M"]` or `[{ value: "DE", label: "Germany" }]` - both mean the same thing to a choice field. */
|
|
129
|
+
function choiceList(raw) {
|
|
130
|
+
if (!Array.isArray(raw)) {
|
|
131
|
+
throw new Error("jasy: a choice field needs an `options` array (strings or {value, label}).");
|
|
132
|
+
}
|
|
133
|
+
return raw;
|
|
134
|
+
}
|
|
135
|
+
/** A radio button always shows a label, so the short spelling means "label is the value". */
|
|
136
|
+
function radioChoices(raw) {
|
|
137
|
+
return choiceList(raw).map((o) => typeof o === "string" ? { value: o, label: o } : { value: o.value, label: o.label ?? o.value });
|
|
138
|
+
}
|
|
48
139
|
/**
|
|
49
140
|
* Registers a custom element type, so a binding (or a user-defined component) can introduce
|
|
50
141
|
* its own tag that resolves to an engine element through this same seam. Overwrites an
|
|
51
142
|
* existing type of the same name.
|
|
52
143
|
*/
|
|
53
|
-
function registerElement(type, factory) {
|
|
144
|
+
export function registerElement(type, factory) {
|
|
54
145
|
REGISTRY[type] = factory;
|
|
55
146
|
}
|
|
56
147
|
/** Turns one descriptor node (or bare string → `Text`) into an engine element. */
|
|
57
|
-
function build(node) {
|
|
58
|
-
var _a, _b;
|
|
148
|
+
export function build(node) {
|
|
59
149
|
if (typeof node === "string")
|
|
60
|
-
return
|
|
150
|
+
return Text(node);
|
|
61
151
|
const factory = REGISTRY[node.type];
|
|
62
152
|
if (!factory)
|
|
63
153
|
throw new Error(`Unknown element type: "${node.type}"`);
|
|
64
|
-
return factory(
|
|
154
|
+
return factory(node.props ?? {}, node.children ?? []);
|
|
65
155
|
}
|
|
66
156
|
/** Builds a descriptor tree whose root is a `document` into the renderable root element. */
|
|
67
|
-
function buildDocument(root) {
|
|
157
|
+
export function buildDocument(root) {
|
|
68
158
|
if (root.type !== "document")
|
|
69
159
|
throw new Error(`Expected a "document" root, got "${root.type}"`);
|
|
70
160
|
return build(root);
|