@jasy/pdf 1.0.0-beta.4 → 1.0.0-beta.5
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/dist/api/color.d.ts +1 -24
- package/dist/api/color.js +2 -278
- package/dist/api/content.d.ts +7 -2
- package/dist/api/content.js +17 -7
- package/dist/api/descriptor.js +30 -3
- package/dist/api/flow.d.ts +55 -0
- package/dist/api/flow.js +71 -0
- package/dist/api/gradient.d.ts +1 -53
- package/dist/api/gradient.js +2 -97
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/layout.js +4 -3
- package/dist/api/table.js +3 -1
- package/dist/api/text.d.ts +8 -8
- package/dist/api/text.js +6 -0
- package/dist/canvas/painter.d.ts +2 -2
- package/dist/canvas/painter.js +2 -2
- package/dist/common/color-input.d.ts +24 -0
- package/dist/common/color-input.js +278 -0
- package/dist/common/diagnostics.d.ts +9 -1
- package/dist/common/gradient.d.ts +53 -0
- package/dist/common/gradient.js +97 -0
- package/dist/elements/container-element.d.ts +3 -0
- package/dist/elements/container-element.js +17 -5
- package/dist/elements/image-element.d.ts +37 -26
- package/dist/elements/image-element.js +88 -50
- package/dist/elements/index.d.ts +3 -0
- package/dist/elements/index.js +3 -0
- package/dist/elements/layout/column-break-element.d.ts +17 -0
- package/dist/elements/layout/column-break-element.js +26 -0
- package/dist/elements/layout/deferred-element.d.ts +9 -1
- package/dist/elements/layout/deferred-element.js +18 -3
- package/dist/elements/layout/expanded-element.d.ts +4 -0
- package/dist/elements/layout/expanded-element.js +14 -1
- package/dist/elements/layout/flow-element.d.ts +109 -0
- package/dist/elements/layout/flow-element.js +317 -0
- package/dist/elements/layout/keep-together-element.js +14 -2
- package/dist/elements/layout/padding-element.d.ts +3 -0
- package/dist/elements/layout/padding-element.js +9 -2
- package/dist/elements/layout/page-builder-element.js +2 -2
- package/dist/elements/layout/positioned-element.d.ts +9 -2
- package/dist/elements/layout/positioned-element.js +6 -2
- package/dist/elements/layout/proxy-element.d.ts +4 -0
- package/dist/elements/layout/proxy-element.js +12 -0
- package/dist/elements/layout/region-element.d.ts +53 -0
- package/dist/elements/layout/region-element.js +108 -0
- package/dist/elements/layout/repeating-header-element.d.ts +3 -0
- package/dist/elements/layout/repeating-header-element.js +16 -1
- package/dist/elements/layout/rotated-element.d.ts +2 -0
- package/dist/elements/layout/rotated-element.js +8 -0
- package/dist/elements/pdf-element.d.ts +32 -3
- package/dist/elements/pdf-element.js +50 -1
- package/dist/elements/rectangle-element.d.ts +12 -6
- package/dist/elements/rectangle-element.js +31 -12
- package/dist/elements/row-element.d.ts +6 -12
- package/dist/elements/row-element.js +34 -4
- package/dist/elements/svg-element.js +4 -10
- package/dist/elements/text-element.d.ts +17 -1
- package/dist/elements/text-element.js +63 -40
- package/dist/ir/display-list.d.ts +6 -1
- package/dist/layout/box-constraints.d.ts +12 -0
- package/dist/layout/box-constraints.js +17 -0
- package/dist/layout/collect-images.js +8 -4
- package/dist/layout/flex-item.d.ts +2 -0
- package/dist/layout/flex-item.js +14 -2
- package/dist/layout/fragmentation.d.ts +11 -5
- package/dist/layout/fragmentation.js +27 -7
- package/dist/layout/layout-cache.d.ts +8 -8
- package/dist/layout/layout-cache.js +10 -7
- package/dist/platform/browser-fs.d.ts +1 -2
- package/dist/platform/browser-fs.js +3 -6
- package/dist/platform/node-fs.d.ts +0 -1
- package/dist/platform/node-fs.js +0 -4
- package/dist/renderer/column-break-renderer.d.ts +8 -0
- package/dist/renderer/column-break-renderer.js +15 -0
- package/dist/renderer/emoji-image.js +3 -1
- package/dist/renderer/flow-renderer.d.ts +16 -0
- package/dist/renderer/flow-renderer.js +21 -0
- package/dist/renderer/image-renderer.js +31 -54
- package/dist/renderer/index.d.ts +2 -0
- package/dist/renderer/index.js +2 -0
- package/dist/renderer/page-renderer.js +3 -4
- package/dist/renderer/pdf-backend.js +9 -2
- package/dist/renderer/pdf-document-renderer.js +2 -1
- package/dist/renderer/pdf-renderer.js +9 -0
- package/dist/renderer/positioned-renderer.d.ts +14 -2
- package/dist/renderer/positioned-renderer.js +40 -4
- package/dist/renderer/rectangle-renderer.d.ts +1 -1
- package/dist/renderer/rectangle-renderer.js +11 -6
- package/dist/renderer/rotated-renderer.js +3 -2
- package/dist/renderer/text-renderer.js +25 -28
- package/dist/svg/index.d.ts +1 -1
- package/dist/svg/index.js +26 -5
- package/dist/svg/style.js +1 -1
- package/dist/text/line-breaker.d.ts +23 -0
- package/dist/text/line-breaker.js +142 -65
- package/dist/text/line-metrics.d.ts +2 -0
- package/dist/text/line-metrics.js +5 -1
- package/dist/text/word-splitting.js +11 -5
- package/dist/utils/flex-layout.d.ts +3 -1
- package/dist/utils/flex-layout.js +141 -33
- package/dist/utils/image-helper.d.ts +59 -11
- package/dist/utils/image-helper.js +207 -80
- package/dist/utils/pdf-object-manager.d.ts +6 -3
- package/dist/utils/pdf-object-manager.js +31 -7
- package/dist/utils/renderer-registry.d.ts +6 -0
- package/dist/utils/renderer-registry.js +1 -1
- package/dist/utils/ttf-parser.js +3 -2
- package/package.json +1 -1
package/dist/api/color.d.ts
CHANGED
|
@@ -1,24 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Every way to name a color. The FORM picks the convention, so there is never a guess
|
|
4
|
-
* (locked design §2):
|
|
5
|
-
*
|
|
6
|
-
* | form | example | meaning |
|
|
7
|
-
* |---------------------------|--------------------------|--------------------------------|
|
|
8
|
-
* | named (full CSS set) | `"steelblue"`, `"transparent"` | the ~148 CSS color names |
|
|
9
|
-
* | hex 6 / 3 | `"#1450aa"` / `"#14a"` | CSS RGB |
|
|
10
|
-
* | hex 8 / 4 | `"#1450aacc"` / `"#14ac"`| CSS RGBA (alpha LAST) |
|
|
11
|
-
* | number | `0xff1450aa` | Flutter ARGB (alpha FIRST) |
|
|
12
|
-
* | `rgb()` / `rgba()` | `rgb(20,90,170)` | channels 0–255 (`rgba` a=0–1) |
|
|
13
|
-
* | `Color` instance | `new Color(20,90,170)` | the engine layer, still valid |
|
|
14
|
-
*/
|
|
15
|
-
export type ColorInput = string | number | Color;
|
|
16
|
-
/** Channels 0–255, fully opaque. */
|
|
17
|
-
export declare function rgb(r: number, g: number, b: number): Color;
|
|
18
|
-
/** Channels 0–255, alpha 0–1. */
|
|
19
|
-
export declare function rgba(r: number, g: number, b: number, a: number): Color;
|
|
20
|
-
/**
|
|
21
|
-
* Normalizes any `ColorInput` to an engine `Color`. The single funnel every factory uses,
|
|
22
|
-
* so a color means the same thing no matter how it was written.
|
|
23
|
-
*/
|
|
24
|
-
export declare function toColor(input: ColorInput): Color;
|
|
1
|
+
export * from "../common/color-input.js";
|
package/dist/api/color.js
CHANGED
|
@@ -1,278 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export function rgb(r, g, b) {
|
|
4
|
-
return new Color(r, g, b, 1);
|
|
5
|
-
}
|
|
6
|
-
/** Channels 0–255, alpha 0–1. */
|
|
7
|
-
export function rgba(r, g, b, a) {
|
|
8
|
-
return new Color(r, g, b, a);
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Normalizes any `ColorInput` to an engine `Color`. The single funnel every factory uses,
|
|
12
|
-
* so a color means the same thing no matter how it was written.
|
|
13
|
-
*/
|
|
14
|
-
export function toColor(input) {
|
|
15
|
-
if (input instanceof Color)
|
|
16
|
-
return input;
|
|
17
|
-
if (typeof input === "number")
|
|
18
|
-
return fromArgbNumber(input);
|
|
19
|
-
const s = input.trim().toLowerCase();
|
|
20
|
-
if (s.startsWith("#"))
|
|
21
|
-
return fromHex(s);
|
|
22
|
-
const named = CSS_COLORS[s];
|
|
23
|
-
if (named)
|
|
24
|
-
return new Color(named[0], named[1], named[2], named[3] ?? 1);
|
|
25
|
-
const call = /^([a-z-]+)\(([^)]*)\)$/.exec(s);
|
|
26
|
-
if (call)
|
|
27
|
-
return fromFunction(call[1], call[2], input);
|
|
28
|
-
throw new Error(`Unknown color: "${input}"`);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* The CSS colour FUNCTIONS. `rgb()`/`rgba()` and `hsl()`/`hsla()` are the sRGB family, so they convert
|
|
32
|
-
* exactly and are supported; a wide-gamut function (`color()`, `lab()`, `oklch()`) is named and
|
|
33
|
-
* refused rather than silently squashed into sRGB, since the result would be a colour nobody chose.
|
|
34
|
-
*
|
|
35
|
-
* Both syntaxes are accepted, because both occur in the wild: the legacy comma form
|
|
36
|
-
* `rgb(20, 90, 170)` and the modern space form with an optional slash-alpha `rgb(20 90 170 / 50%)`.
|
|
37
|
-
*/
|
|
38
|
-
function fromFunction(name, body, original) {
|
|
39
|
-
if (name !== "rgb" && name !== "rgba" && name !== "hsl" && name !== "hsla") {
|
|
40
|
-
throw new Error(`Unsupported color function "${name}()" in "${String(original)}". ` +
|
|
41
|
-
`Use a hex value, a CSS colour name, rgb()/rgba() or hsl()/hsla().`);
|
|
42
|
-
}
|
|
43
|
-
const [main, alphaPart] = body.split("/");
|
|
44
|
-
const parts = main
|
|
45
|
-
.trim()
|
|
46
|
-
.split(/[\s,]+/)
|
|
47
|
-
.filter(Boolean);
|
|
48
|
-
const alphaText = alphaPart !== undefined ? alphaPart.trim() : parts[3];
|
|
49
|
-
if (parts.length < 3)
|
|
50
|
-
throw new Error(`Unknown color: "${String(original)}"`);
|
|
51
|
-
const alpha = alphaText === undefined ? 1 : ratio(alphaText, original);
|
|
52
|
-
const [a, b, c] = parts;
|
|
53
|
-
if (name === "rgb" || name === "rgba") {
|
|
54
|
-
return new Color(channel(a, original), channel(b, original), channel(c, original), alpha);
|
|
55
|
-
}
|
|
56
|
-
const [r, g, bl] = hslToRgb(number(a, original), ratio(b, original), ratio(c, original));
|
|
57
|
-
return new Color(r, g, bl, alpha);
|
|
58
|
-
}
|
|
59
|
-
const NUMERIC = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
60
|
-
/**
|
|
61
|
-
* One component of a colour function. `Number.parseFloat` stops at the first character it cannot use
|
|
62
|
-
* and returns what it had, so `rgb(20abc, 90, 170)` would silently become `rgb(20, 90, 170)` - a
|
|
63
|
-
* colour nobody wrote. The whole token has to be a number.
|
|
64
|
-
*/
|
|
65
|
-
function number(text, original) {
|
|
66
|
-
const t = text.trim();
|
|
67
|
-
if (!NUMERIC.test(t))
|
|
68
|
-
throw new Error(`Unknown color: "${String(original)}"`);
|
|
69
|
-
return Number(t);
|
|
70
|
-
}
|
|
71
|
-
/** An rgb channel: 0-255, or a percentage of 255. */
|
|
72
|
-
const channel = (text, original) => text.trim().endsWith("%")
|
|
73
|
-
? (number(text.trim().slice(0, -1), original) / 100) * 255
|
|
74
|
-
: number(text, original);
|
|
75
|
-
/** A 0..1 ratio written either as a number or as a percentage (alpha, saturation, lightness). */
|
|
76
|
-
const ratio = (text, original) => text.trim().endsWith("%")
|
|
77
|
-
? number(text.trim().slice(0, -1), original) / 100
|
|
78
|
-
: number(text, original);
|
|
79
|
-
/** CSS hue-saturation-lightness to 0-255 channels. */
|
|
80
|
-
function hslToRgb(hue, s, l) {
|
|
81
|
-
const h = ((hue % 360) + 360) % 360;
|
|
82
|
-
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
83
|
-
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
84
|
-
const m = l - c / 2;
|
|
85
|
-
const sector = Math.floor(h / 60) % 6;
|
|
86
|
-
const rgbPrime = [
|
|
87
|
-
[c, x, 0],
|
|
88
|
-
[x, c, 0],
|
|
89
|
-
[0, c, x],
|
|
90
|
-
[0, x, c],
|
|
91
|
-
[x, 0, c],
|
|
92
|
-
[c, 0, x],
|
|
93
|
-
][sector];
|
|
94
|
-
return rgbPrime.map((v) => Math.round((v + m) * 255));
|
|
95
|
-
}
|
|
96
|
-
/** Flutter ARGB: 0xAARRGGBB, alpha FIRST. A 6-digit number has alpha 0x00 = transparent. */
|
|
97
|
-
function fromArgbNumber(n) {
|
|
98
|
-
const a = (n >>> 24) & 0xff;
|
|
99
|
-
const r = (n >>> 16) & 0xff;
|
|
100
|
-
const g = (n >>> 8) & 0xff;
|
|
101
|
-
const b = n & 0xff;
|
|
102
|
-
return new Color(r, g, b, a / 255);
|
|
103
|
-
}
|
|
104
|
-
/** CSS hex: #RGB, #RGBA, #RRGGBB, #RRGGBBAA. Alpha is LAST. */
|
|
105
|
-
function fromHex(s) {
|
|
106
|
-
const hex = s.slice(1);
|
|
107
|
-
const expand = (h) => h
|
|
108
|
-
.split("")
|
|
109
|
-
.map((c) => c + c)
|
|
110
|
-
.join("");
|
|
111
|
-
let full;
|
|
112
|
-
if (hex.length === 3 || hex.length === 4)
|
|
113
|
-
full = expand(hex);
|
|
114
|
-
else if (hex.length === 6 || hex.length === 8)
|
|
115
|
-
full = hex;
|
|
116
|
-
else
|
|
117
|
-
throw new Error(`Invalid hex color: "${s}"`);
|
|
118
|
-
const r = parseInt(full.slice(0, 2), 16);
|
|
119
|
-
const g = parseInt(full.slice(2, 4), 16);
|
|
120
|
-
const b = parseInt(full.slice(4, 6), 16);
|
|
121
|
-
const a = full.length === 8 ? parseInt(full.slice(6, 8), 16) / 255 : 1;
|
|
122
|
-
if ([r, g, b].some(Number.isNaN))
|
|
123
|
-
throw new Error(`Invalid hex color: "${s}"`);
|
|
124
|
-
return new Color(r, g, b, a);
|
|
125
|
-
}
|
|
126
|
-
// The full CSS named-color set (~148, incl. the synonyms grey/gray and `transparent`).
|
|
127
|
-
// [r, g, b] or [r, g, b, alpha]. Lower-cased keys; `toColor` lower-cases the input.
|
|
128
|
-
const CSS_COLORS = {
|
|
129
|
-
transparent: [0, 0, 0, 0],
|
|
130
|
-
aliceblue: [240, 248, 255],
|
|
131
|
-
antiquewhite: [250, 235, 215],
|
|
132
|
-
aqua: [0, 255, 255],
|
|
133
|
-
aquamarine: [127, 255, 212],
|
|
134
|
-
azure: [240, 255, 255],
|
|
135
|
-
beige: [245, 245, 220],
|
|
136
|
-
bisque: [255, 228, 196],
|
|
137
|
-
black: [0, 0, 0],
|
|
138
|
-
blanchedalmond: [255, 235, 205],
|
|
139
|
-
blue: [0, 0, 255],
|
|
140
|
-
blueviolet: [138, 43, 226],
|
|
141
|
-
brown: [165, 42, 42],
|
|
142
|
-
burlywood: [222, 184, 135],
|
|
143
|
-
cadetblue: [95, 158, 160],
|
|
144
|
-
chartreuse: [127, 255, 0],
|
|
145
|
-
chocolate: [210, 105, 30],
|
|
146
|
-
coral: [255, 127, 80],
|
|
147
|
-
cornflowerblue: [100, 149, 237],
|
|
148
|
-
cornsilk: [255, 248, 220],
|
|
149
|
-
crimson: [220, 20, 60],
|
|
150
|
-
cyan: [0, 255, 255],
|
|
151
|
-
darkblue: [0, 0, 139],
|
|
152
|
-
darkcyan: [0, 139, 139],
|
|
153
|
-
darkgoldenrod: [184, 134, 11],
|
|
154
|
-
darkgray: [169, 169, 169],
|
|
155
|
-
darkgrey: [169, 169, 169],
|
|
156
|
-
darkgreen: [0, 100, 0],
|
|
157
|
-
darkkhaki: [189, 183, 107],
|
|
158
|
-
darkmagenta: [139, 0, 139],
|
|
159
|
-
darkolivegreen: [85, 107, 47],
|
|
160
|
-
darkorange: [255, 140, 0],
|
|
161
|
-
darkorchid: [153, 50, 204],
|
|
162
|
-
darkred: [139, 0, 0],
|
|
163
|
-
darksalmon: [233, 150, 122],
|
|
164
|
-
darkseagreen: [143, 188, 143],
|
|
165
|
-
darkslateblue: [72, 61, 139],
|
|
166
|
-
darkslategray: [47, 79, 79],
|
|
167
|
-
darkslategrey: [47, 79, 79],
|
|
168
|
-
darkturquoise: [0, 206, 209],
|
|
169
|
-
darkviolet: [148, 0, 211],
|
|
170
|
-
deeppink: [255, 20, 147],
|
|
171
|
-
deepskyblue: [0, 191, 255],
|
|
172
|
-
dimgray: [105, 105, 105],
|
|
173
|
-
dimgrey: [105, 105, 105],
|
|
174
|
-
dodgerblue: [30, 144, 255],
|
|
175
|
-
firebrick: [178, 34, 34],
|
|
176
|
-
floralwhite: [255, 250, 240],
|
|
177
|
-
forestgreen: [34, 139, 34],
|
|
178
|
-
fuchsia: [255, 0, 255],
|
|
179
|
-
gainsboro: [220, 220, 220],
|
|
180
|
-
ghostwhite: [248, 248, 255],
|
|
181
|
-
gold: [255, 215, 0],
|
|
182
|
-
goldenrod: [218, 165, 32],
|
|
183
|
-
gray: [128, 128, 128],
|
|
184
|
-
grey: [128, 128, 128],
|
|
185
|
-
green: [0, 128, 0],
|
|
186
|
-
greenyellow: [173, 255, 47],
|
|
187
|
-
honeydew: [240, 255, 240],
|
|
188
|
-
hotpink: [255, 105, 180],
|
|
189
|
-
indianred: [205, 92, 92],
|
|
190
|
-
indigo: [75, 0, 130],
|
|
191
|
-
ivory: [255, 255, 240],
|
|
192
|
-
khaki: [240, 230, 140],
|
|
193
|
-
lavender: [230, 230, 250],
|
|
194
|
-
lavenderblush: [255, 240, 245],
|
|
195
|
-
lawngreen: [124, 252, 0],
|
|
196
|
-
lemonchiffon: [255, 250, 205],
|
|
197
|
-
lightblue: [173, 216, 230],
|
|
198
|
-
lightcoral: [240, 128, 128],
|
|
199
|
-
lightcyan: [224, 255, 255],
|
|
200
|
-
lightgoldenrodyellow: [250, 250, 210],
|
|
201
|
-
lightgray: [211, 211, 211],
|
|
202
|
-
lightgrey: [211, 211, 211],
|
|
203
|
-
lightgreen: [144, 238, 144],
|
|
204
|
-
lightpink: [255, 182, 193],
|
|
205
|
-
lightsalmon: [255, 160, 122],
|
|
206
|
-
lightseagreen: [32, 178, 170],
|
|
207
|
-
lightskyblue: [135, 206, 250],
|
|
208
|
-
lightslategray: [119, 136, 153],
|
|
209
|
-
lightslategrey: [119, 136, 153],
|
|
210
|
-
lightsteelblue: [176, 196, 222],
|
|
211
|
-
lightyellow: [255, 255, 224],
|
|
212
|
-
lime: [0, 255, 0],
|
|
213
|
-
limegreen: [50, 205, 50],
|
|
214
|
-
linen: [250, 240, 230],
|
|
215
|
-
magenta: [255, 0, 255],
|
|
216
|
-
maroon: [128, 0, 0],
|
|
217
|
-
mediumaquamarine: [102, 205, 170],
|
|
218
|
-
mediumblue: [0, 0, 205],
|
|
219
|
-
mediumorchid: [186, 85, 211],
|
|
220
|
-
mediumpurple: [147, 112, 219],
|
|
221
|
-
mediumseagreen: [60, 179, 113],
|
|
222
|
-
mediumslateblue: [123, 104, 238],
|
|
223
|
-
mediumspringgreen: [0, 250, 154],
|
|
224
|
-
mediumturquoise: [72, 209, 204],
|
|
225
|
-
mediumvioletred: [199, 21, 133],
|
|
226
|
-
midnightblue: [25, 25, 112],
|
|
227
|
-
mintcream: [245, 255, 250],
|
|
228
|
-
mistyrose: [255, 228, 225],
|
|
229
|
-
moccasin: [255, 228, 181],
|
|
230
|
-
navajowhite: [255, 222, 173],
|
|
231
|
-
navy: [0, 0, 128],
|
|
232
|
-
oldlace: [253, 245, 230],
|
|
233
|
-
olive: [128, 128, 0],
|
|
234
|
-
olivedrab: [107, 142, 35],
|
|
235
|
-
orange: [255, 165, 0],
|
|
236
|
-
orangered: [255, 69, 0],
|
|
237
|
-
orchid: [218, 112, 214],
|
|
238
|
-
palegoldenrod: [238, 232, 170],
|
|
239
|
-
palegreen: [152, 251, 152],
|
|
240
|
-
paleturquoise: [175, 238, 238],
|
|
241
|
-
palevioletred: [219, 112, 147],
|
|
242
|
-
papayawhip: [255, 239, 213],
|
|
243
|
-
peachpuff: [255, 218, 185],
|
|
244
|
-
peru: [205, 133, 63],
|
|
245
|
-
pink: [255, 192, 203],
|
|
246
|
-
plum: [221, 160, 221],
|
|
247
|
-
powderblue: [176, 224, 230],
|
|
248
|
-
purple: [128, 0, 128],
|
|
249
|
-
rebeccapurple: [102, 51, 153],
|
|
250
|
-
red: [255, 0, 0],
|
|
251
|
-
rosybrown: [188, 143, 143],
|
|
252
|
-
royalblue: [65, 105, 225],
|
|
253
|
-
saddlebrown: [139, 69, 19],
|
|
254
|
-
salmon: [250, 128, 114],
|
|
255
|
-
sandybrown: [244, 164, 96],
|
|
256
|
-
seagreen: [46, 139, 87],
|
|
257
|
-
seashell: [255, 245, 238],
|
|
258
|
-
sienna: [160, 82, 45],
|
|
259
|
-
silver: [192, 192, 192],
|
|
260
|
-
skyblue: [135, 206, 235],
|
|
261
|
-
slateblue: [106, 90, 205],
|
|
262
|
-
slategray: [112, 128, 144],
|
|
263
|
-
slategrey: [112, 128, 144],
|
|
264
|
-
snow: [255, 250, 250],
|
|
265
|
-
springgreen: [0, 255, 127],
|
|
266
|
-
steelblue: [70, 130, 180],
|
|
267
|
-
tan: [210, 180, 140],
|
|
268
|
-
teal: [0, 128, 128],
|
|
269
|
-
thistle: [216, 191, 216],
|
|
270
|
-
tomato: [255, 99, 71],
|
|
271
|
-
turquoise: [64, 224, 208],
|
|
272
|
-
violet: [238, 130, 238],
|
|
273
|
-
wheat: [245, 222, 179],
|
|
274
|
-
white: [255, 255, 255],
|
|
275
|
-
whitesmoke: [245, 245, 245],
|
|
276
|
-
yellow: [255, 255, 0],
|
|
277
|
-
yellowgreen: [154, 205, 50],
|
|
278
|
-
};
|
|
1
|
+
// Lives in `common/` because the engine reads it too; this is its public face.
|
|
2
|
+
export * from "../common/color-input.js";
|
package/dist/api/content.d.ts
CHANGED
|
@@ -31,14 +31,19 @@ export interface SvgOptions extends BoundsInput {
|
|
|
31
31
|
export type ImageSource = string | Uint8Array | CustomImage;
|
|
32
32
|
/** How the image fills its box (locked §4). Mirrors CSS `object-fit`. */
|
|
33
33
|
export type ImageFit = "none" | "contain" | "cover" | "fill";
|
|
34
|
+
/** Where an image sits in its own box - the image's counterpart of a Text's `align`. */
|
|
35
|
+
export type ImageAlign = "center" | "top" | "bottom" | "left" | "right" | "top left" | "top right" | "bottom left" | "bottom right";
|
|
34
36
|
export interface ImageOptions extends BoundsInput {
|
|
35
37
|
/** Size on each axis: points (fixed) or a percentage string like `"50%"` (a fraction of the offered
|
|
36
38
|
* space). Pin exactly ONE axis and the other follows the image's aspect ratio (CSS `height: auto`). */
|
|
37
39
|
width?: SizeInput;
|
|
38
40
|
height?: SizeInput;
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
41
|
+
/** How the image fills its box (CSS `object-fit`). Default `fill`. Given no size, the box is the image's
|
|
42
|
+
* own size, scaled down to fit what is offered (never up) - so `fill` draws it undistorted. */
|
|
41
43
|
fit?: ImageFit;
|
|
44
|
+
/** Where the image sits in its box when `fit` leaves space or cuts it off. Default `"center"`; a
|
|
45
|
+
* portrait in a `cover` box keeps its top with `"top"`. */
|
|
46
|
+
align?: ImageAlign;
|
|
42
47
|
/** Corner radius in points (rounds the image box). */
|
|
43
48
|
radius?: RadiusInput;
|
|
44
49
|
/** Alternate text for accessibility (tagged PDF): describes the image for screen readers. With `alt`
|
package/dist/api/content.js
CHANGED
|
@@ -33,6 +33,17 @@ const FIT = {
|
|
|
33
33
|
cover: BoxFit.cover,
|
|
34
34
|
fill: BoxFit.fill,
|
|
35
35
|
};
|
|
36
|
+
const IMAGE_ALIGN = {
|
|
37
|
+
center: { x: 0.5, y: 0.5 },
|
|
38
|
+
top: { x: 0.5, y: 0 },
|
|
39
|
+
bottom: { x: 0.5, y: 1 },
|
|
40
|
+
left: { x: 0, y: 0.5 },
|
|
41
|
+
right: { x: 1, y: 0.5 },
|
|
42
|
+
"top left": { x: 0, y: 0 },
|
|
43
|
+
"top right": { x: 1, y: 0 },
|
|
44
|
+
"bottom left": { x: 0, y: 1 },
|
|
45
|
+
"bottom right": { x: 1, y: 1 },
|
|
46
|
+
};
|
|
36
47
|
export function Canvas(a, b) {
|
|
37
48
|
const opts = typeof a === "function" ? {} : a;
|
|
38
49
|
const paint = typeof a === "function" ? a : b;
|
|
@@ -118,6 +129,10 @@ export function Image(src, opts = {}) {
|
|
|
118
129
|
if (opts.radius !== undefined) {
|
|
119
130
|
throw new Error("@jasy/pdf: radius is not supported for an SVG yet - wrap it in a Box instead.");
|
|
120
131
|
}
|
|
132
|
+
if (opts.align !== undefined) {
|
|
133
|
+
throw new Error("@jasy/pdf: align is not supported for an SVG yet - it is always drawn centred in its box. " +
|
|
134
|
+
"Size the box to the drawing, or place it with the parent's align.");
|
|
135
|
+
}
|
|
121
136
|
return Svg(src, opts);
|
|
122
137
|
}
|
|
123
138
|
return rasterImage(src, opts);
|
|
@@ -125,12 +140,6 @@ export function Image(src, opts = {}) {
|
|
|
125
140
|
function rasterImage(src, opts = {}) {
|
|
126
141
|
const w = opts.width !== undefined ? toDimension(opts.width) : undefined;
|
|
127
142
|
const h = opts.height !== undefined ? toDimension(opts.height) : undefined;
|
|
128
|
-
// ANY explicit size scales the image into the box (fit: fill), which is what `<img width height>`
|
|
129
|
-
// does in HTML and what react-pdf does. Two pinned axes used to keep `none` - drawing the image at
|
|
130
|
-
// its pixel size regardless - so a logo asked for at 48x48 spanned a third of the page.
|
|
131
|
-
// An image with NO size given still draws at its intrinsic size, as an unsized `<img>` does.
|
|
132
|
-
const sized = opts.width !== undefined || opts.height !== undefined || opts.aspectRatio !== undefined;
|
|
133
|
-
const fit = opts.fit ? FIT[opts.fit] : sized ? BoxFit.fill : undefined;
|
|
134
143
|
return new ImageElement({
|
|
135
144
|
image: typeof src === "string"
|
|
136
145
|
? isImageUrl(src)
|
|
@@ -144,7 +153,8 @@ function rasterImage(src, opts = {}) {
|
|
|
144
153
|
widthFactor: w?.factor,
|
|
145
154
|
heightFactor: h?.factor,
|
|
146
155
|
...toBounds(opts),
|
|
147
|
-
fit,
|
|
156
|
+
fit: opts.fit ? FIT[opts.fit] : undefined,
|
|
157
|
+
align: opts.align ? IMAGE_ALIGN[opts.align] : undefined,
|
|
148
158
|
radius: opts.radius !== undefined ? toRadius(opts.radius) : undefined,
|
|
149
159
|
alt: opts.alt,
|
|
150
160
|
})
|
package/dist/api/descriptor.js
CHANGED
|
@@ -2,8 +2,9 @@ import { Text, Paragraph, span } from "./text.js";
|
|
|
2
2
|
import { Column, Row, Box, Padding, Spacer, PageBreak, keepTogether, Expanded, Positioned, Link, Anchor, Bookmark, Rotated, RotatedBox, } from "./layout.js";
|
|
3
3
|
import { Canvas, Divider, Image, Svg } from "./content.js";
|
|
4
4
|
import { Page, Document, DefaultTextStyle } from "./structure.js";
|
|
5
|
-
import {
|
|
5
|
+
import { PageBuilder, PageCount, PageNumber, SubPageCount, SubPageNumber, } from "./page-builder.js";
|
|
6
6
|
import { Table } from "./table.js";
|
|
7
|
+
import { ColumnBreak, Flow, FlowWithoutRegionError, Region, SplitView, } from "./flow.js";
|
|
7
8
|
import { Checkbox, Dropdown, ListBox, PushButton, RadioGroup, SignatureField, TextField, } from "./forms.js";
|
|
8
9
|
// Element children (Column/Row/Box/Page/…) → built recursively. Text/Paragraph children are
|
|
9
10
|
// strings or `span` descriptors and become content instead.
|
|
@@ -104,6 +105,20 @@ const REGISTRY = {
|
|
|
104
105
|
const body = rows.filter((r) => !r.props?.header);
|
|
105
106
|
return Table({ ...props, header: header ? rowCells(header) : undefined }, body.map(rowCells));
|
|
106
107
|
},
|
|
108
|
+
// A Flow's `page` is a function prop: the binding renders one page of the template per call.
|
|
109
|
+
flow: (props, children) => Flow({
|
|
110
|
+
gap: props.gap,
|
|
111
|
+
balance: props.balance,
|
|
112
|
+
template: (info) => {
|
|
113
|
+
const page = props.page?.(info) ?? [];
|
|
114
|
+
if (page.length === 0)
|
|
115
|
+
throw new FlowWithoutRegionError();
|
|
116
|
+
return wrappedChild(page);
|
|
117
|
+
},
|
|
118
|
+
}, elementChildren(children)),
|
|
119
|
+
region: (props) => Region(props),
|
|
120
|
+
"column-break": () => ColumnBreak(),
|
|
121
|
+
"split-view": (props, children) => SplitView(props, elementChildren(children)),
|
|
107
122
|
positioned: (props, children) => Positioned(props, elementChildren(children)[0]),
|
|
108
123
|
"default-text-style": (props, children) => DefaultTextStyle(props, elementChildren(children)),
|
|
109
124
|
// Navigation. `Link` takes exactly one of `href` (a URL) or `to` (an `Anchor` name) - the factory
|
|
@@ -114,10 +129,22 @@ const REGISTRY = {
|
|
|
114
129
|
// Transforms.
|
|
115
130
|
rotated: (props, children) => Rotated(props, wrappedChild(children)),
|
|
116
131
|
"rotated-box": (props, children) => RotatedBox(props, wrappedChild(children)),
|
|
117
|
-
// Page numbers. `
|
|
118
|
-
//
|
|
132
|
+
// Page numbers. A binding renders `build` per page, like a Flow's `page`; nothing built is an empty
|
|
133
|
+
// Column, so a builder can show something on some pages only.
|
|
134
|
+
"page-builder": (props) => {
|
|
135
|
+
// A descriptor posted as JSON loses its functions; say so here, not mid-layout.
|
|
136
|
+
if (typeof props.build !== "function") {
|
|
137
|
+
throw new Error("A page-builder needs a `build` function prop (lost if the descriptor went through JSON).");
|
|
138
|
+
}
|
|
139
|
+
return PageBuilder((info) => {
|
|
140
|
+
const built = props.build(info) ?? [];
|
|
141
|
+
return built.length === 0 ? Column([]) : wrappedChild(built);
|
|
142
|
+
});
|
|
143
|
+
},
|
|
119
144
|
"page-number": (props) => PageNumber(props),
|
|
120
145
|
"page-count": (props) => PageCount(props),
|
|
146
|
+
"sub-page-number": (props) => SubPageNumber(props),
|
|
147
|
+
"sub-page-count": (props) => SubPageCount(props),
|
|
121
148
|
// Form fields. The three list-like ones take their choices as a SECOND argument in TypeScript; a
|
|
122
149
|
// template has no second argument, so here they arrive as an `options` prop. `choiceList` accepts both
|
|
123
150
|
// the short spelling and the explicit one, because an export value and its label are usually the same
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { FlowElement, type FlowTemplate } from "../elements/layout/flow-element.js";
|
|
2
|
+
import { RegionElement } from "../elements/layout/region-element.js";
|
|
3
|
+
import { ColumnBreakElement } from "../elements/layout/column-break-element.js";
|
|
4
|
+
import { PDFElement } from "../elements/pdf-element.js";
|
|
5
|
+
import { BoundsInput, SizeInput } from "./dimension.js";
|
|
6
|
+
export type { FlowPage, FlowTemplate } from "../elements/layout/flow-element.js";
|
|
7
|
+
export { FlowRegionsEmptyError, FlowUnboundedError, FlowWithoutRegionError, RegionInPageBuilderError, } from "../elements/layout/flow-element.js";
|
|
8
|
+
export { RegionOutsideFlowError, RegionReusedError } from "../elements/layout/region-element.js";
|
|
9
|
+
export interface FlowOptions {
|
|
10
|
+
/** The layout of one page of the Flow, built per page; its `Region`s are where the content goes. */
|
|
11
|
+
template: FlowTemplate;
|
|
12
|
+
/** Space between the content's elements, in points. */
|
|
13
|
+
gap?: number;
|
|
14
|
+
/** On the last page, spread the content so the Regions come out about equally full (default false). */
|
|
15
|
+
balance?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Content flowing through the `Region`s of a template: the first Region full, then the next, then the
|
|
19
|
+
* next page. It takes its height from where it sits - the rest of the page, or a box with a height.
|
|
20
|
+
*
|
|
21
|
+
* @experimental The shape may still change.
|
|
22
|
+
*/
|
|
23
|
+
export declare function Flow(opts: FlowOptions, content: PDFElement[]): FlowElement;
|
|
24
|
+
export interface RegionOptions extends BoundsInput {
|
|
25
|
+
width?: SizeInput;
|
|
26
|
+
height?: SizeInput;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A place in a `Flow`'s template that the content flows through, in tree order. Sized by the layout
|
|
30
|
+
* around it (`Expanded`, `width`, `height`); with nothing given it fills what it is offered.
|
|
31
|
+
*
|
|
32
|
+
* @experimental
|
|
33
|
+
*/
|
|
34
|
+
export declare function Region(opts?: RegionOptions): RegionElement;
|
|
35
|
+
/** Continue in the next `Region` of the Flow it sits in (from the last one, the next page's first). */
|
|
36
|
+
export declare function ColumnBreak(): ColumnBreakElement;
|
|
37
|
+
export interface SplitViewOptions {
|
|
38
|
+
/** How many columns, side by side and equally wide (default 2). */
|
|
39
|
+
columns?: number;
|
|
40
|
+
/** Gap between both the columns and the content's elements (default 0). */
|
|
41
|
+
gap?: number;
|
|
42
|
+
/** Gap between the content's elements (overrides `gap`). */
|
|
43
|
+
rowGap?: number;
|
|
44
|
+
/** Gap between the columns (overrides `gap`). */
|
|
45
|
+
colGap?: number;
|
|
46
|
+
/** On the last page, columns of about equal height, as CSS columns are (default true). */
|
|
47
|
+
balance?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Newspaper columns: the content fills the first column, then the next, then the next page. A `Flow`
|
|
51
|
+
* with a ready template - for anything else (a title area, columns of different widths) build your own.
|
|
52
|
+
*
|
|
53
|
+
* @experimental
|
|
54
|
+
*/
|
|
55
|
+
export declare function SplitView(opts: SplitViewOptions, content: PDFElement[]): FlowElement;
|
package/dist/api/flow.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ContainerElement } from "../elements/container-element.js";
|
|
2
|
+
import { FlowElement } from "../elements/layout/flow-element.js";
|
|
3
|
+
import { RegionElement } from "../elements/layout/region-element.js";
|
|
4
|
+
import { ColumnBreakElement } from "../elements/layout/column-break-element.js";
|
|
5
|
+
import { toBounds, toDimension } from "./dimension.js";
|
|
6
|
+
import { Expanded, Row } from "./layout.js";
|
|
7
|
+
export { FlowRegionsEmptyError, FlowUnboundedError, FlowWithoutRegionError, RegionInPageBuilderError, } from "../elements/layout/flow-element.js";
|
|
8
|
+
export { RegionOutsideFlowError, RegionReusedError } from "../elements/layout/region-element.js";
|
|
9
|
+
/**
|
|
10
|
+
* Content flowing through the `Region`s of a template: the first Region full, then the next, then the
|
|
11
|
+
* next page. It takes its height from where it sits - the rest of the page, or a box with a height.
|
|
12
|
+
*
|
|
13
|
+
* @experimental The shape may still change.
|
|
14
|
+
*/
|
|
15
|
+
export function Flow(opts, content) {
|
|
16
|
+
return new FlowElement({
|
|
17
|
+
content: new ContainerElement({ x: 0, y: 0, children: content, gap: opts.gap }),
|
|
18
|
+
template: opts.template,
|
|
19
|
+
balance: opts.balance,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A place in a `Flow`'s template that the content flows through, in tree order. Sized by the layout
|
|
24
|
+
* around it (`Expanded`, `width`, `height`); with nothing given it fills what it is offered.
|
|
25
|
+
*
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
export function Region(opts = {}) {
|
|
29
|
+
const w = opts.width !== undefined ? toDimension(opts.width) : undefined;
|
|
30
|
+
const h = opts.height !== undefined ? toDimension(opts.height) : undefined;
|
|
31
|
+
return new RegionElement({
|
|
32
|
+
width: w?.points,
|
|
33
|
+
height: h?.points,
|
|
34
|
+
widthFactor: w?.factor,
|
|
35
|
+
heightFactor: h?.factor,
|
|
36
|
+
...toBounds(opts),
|
|
37
|
+
})
|
|
38
|
+
.withAlignSelf(opts.alignSelf)
|
|
39
|
+
.withOrder(opts.order)
|
|
40
|
+
.withFlexShrink(opts.flexShrink);
|
|
41
|
+
}
|
|
42
|
+
/** Continue in the next `Region` of the Flow it sits in (from the last one, the next page's first). */
|
|
43
|
+
export function ColumnBreak() {
|
|
44
|
+
return new ColumnBreakElement();
|
|
45
|
+
}
|
|
46
|
+
/** A value as the author wrote it: `"2"` quoted, `NaN` as NaN. */
|
|
47
|
+
const shown = (value) => typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
48
|
+
/**
|
|
49
|
+
* Newspaper columns: the content fills the first column, then the next, then the next page. A `Flow`
|
|
50
|
+
* with a ready template - for anything else (a title area, columns of different widths) build your own.
|
|
51
|
+
*
|
|
52
|
+
* @experimental
|
|
53
|
+
*/
|
|
54
|
+
export function SplitView(opts, content) {
|
|
55
|
+
const columns = opts.columns ?? 2;
|
|
56
|
+
if (!Number.isInteger(columns) || columns < 1) {
|
|
57
|
+
throw new Error(`Invalid SplitView columns ${shown(columns)}: use a whole number of 1 or more.`);
|
|
58
|
+
}
|
|
59
|
+
for (const key of ["gap", "rowGap", "colGap"]) {
|
|
60
|
+
const value = opts[key];
|
|
61
|
+
if (value !== undefined && !(Number.isFinite(value) && value >= 0)) {
|
|
62
|
+
throw new Error(`Invalid SplitView ${key} ${shown(value)}: use a number of points, 0 or more.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const colGap = opts.colGap ?? opts.gap;
|
|
66
|
+
return Flow({
|
|
67
|
+
gap: opts.rowGap ?? opts.gap,
|
|
68
|
+
balance: opts.balance ?? true,
|
|
69
|
+
template: () => Row({ gap: colGap }, Array.from({ length: columns }, () => Expanded(Region()))),
|
|
70
|
+
}, content);
|
|
71
|
+
}
|
package/dist/api/gradient.d.ts
CHANGED
|
@@ -1,53 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { Gradient } from "../ir/display-list.js";
|
|
3
|
-
/**
|
|
4
|
-
* Gradients for a box background.
|
|
5
|
-
*
|
|
6
|
-
* The public form is BOX-RELATIVE - an angle and some stops - because that is what an author knows
|
|
7
|
-
* when writing the document. The engine's `Gradient` wants absolute page coordinates, so the renderer
|
|
8
|
-
* resolves one against the box it is painting; see `resolveGradient`.
|
|
9
|
-
*/
|
|
10
|
-
/** A stop as written: a colour, optionally pinned to a position 0..1 along the axis. */
|
|
11
|
-
export type StopInput = ColorInput | {
|
|
12
|
-
color: ColorInput;
|
|
13
|
-
at: number;
|
|
14
|
-
};
|
|
15
|
-
export interface LinearGradientInput {
|
|
16
|
-
kind: "linear";
|
|
17
|
-
/** Direction in degrees, CSS convention: 0 = to the top, 90 = to the right (default 180, downwards). */
|
|
18
|
-
angle: number;
|
|
19
|
-
stops: StopInput[];
|
|
20
|
-
}
|
|
21
|
-
export interface RadialGradientInput {
|
|
22
|
-
kind: "radial";
|
|
23
|
-
/** Centre as a fraction of the box, `[0.5, 0.5]` being the middle (the default). */
|
|
24
|
-
center: [number, number];
|
|
25
|
-
/** Outer radius as a fraction of the box's larger side (default 0.5, i.e. it touches the edges). */
|
|
26
|
-
radius: number;
|
|
27
|
-
stops: StopInput[];
|
|
28
|
-
}
|
|
29
|
-
export type GradientInput = LinearGradientInput | RadialGradientInput;
|
|
30
|
-
export declare const isGradientInput: (v: unknown) => v is GradientInput;
|
|
31
|
-
/**
|
|
32
|
-
* A linear gradient. `linearGradient("#fff", "#000")` runs top to bottom; pass `{ angle }` for any
|
|
33
|
-
* other direction, and `{ color, at }` for a stop that is not evenly spaced.
|
|
34
|
-
*/
|
|
35
|
-
export declare function linearGradient(...args: [...StopInput[]] | [{
|
|
36
|
-
angle?: number;
|
|
37
|
-
stops: StopInput[];
|
|
38
|
-
}]): LinearGradientInput;
|
|
39
|
-
/** A radial gradient, centred by default and reaching the edges. */
|
|
40
|
-
export declare function radialGradient(...args: [...StopInput[]] | [{
|
|
41
|
-
center?: [number, number];
|
|
42
|
-
radius?: number;
|
|
43
|
-
stops: StopInput[];
|
|
44
|
-
}]): RadialGradientInput;
|
|
45
|
-
/**
|
|
46
|
-
* Resolve a box-relative gradient against the box it paints, into the absolute page coordinates the
|
|
47
|
-
* IR wants. Called by the renderer, which is the first place the box's geometry is known.
|
|
48
|
-
*
|
|
49
|
-
* The angle follows CSS: 0 points to the top and it turns clockwise, so 90 goes to the right. The
|
|
50
|
-
* axis runs through the box centre and is scaled so it spans the box on that diagonal - which is why
|
|
51
|
-
* a 45-degree gradient reaches the corners rather than stopping short of them.
|
|
52
|
-
*/
|
|
53
|
-
export declare function resolveGradient(g: GradientInput, x: number, y: number, width: number, height: number): Gradient;
|
|
1
|
+
export * from "../common/gradient.js";
|