@reportwright/viewer 0.12.1 → 0.13.0

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 (81) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +16 -1
  3. package/THIRD-PARTY-NOTICES.md +52 -0
  4. package/dist/{chunk-S2IUUI6N.js → chunk-BF7GCWZY.js} +67 -49
  5. package/dist/chunk-BF7GCWZY.js.map +6 -0
  6. package/dist/chunk-BR5K6SBL.js +324 -0
  7. package/dist/chunk-BR5K6SBL.js.map +6 -0
  8. package/dist/{peer/chunk-BQFI3DHK.js → chunk-CNDUJ7IV.js} +289 -25
  9. package/dist/chunk-CNDUJ7IV.js.map +6 -0
  10. package/dist/{chunk-VNOWPSE6.js → chunk-EK5CJY22.js} +1223 -899
  11. package/dist/chunk-EK5CJY22.js.map +6 -0
  12. package/dist/{chunk-2BPBQMH2.js → chunk-F5EPQTPF.js} +3 -3
  13. package/dist/chunk-FEM2QVOO.js +18 -0
  14. package/dist/{chunk-3D2KFKG2.js → chunk-IFYCJZNS.js} +8 -6
  15. package/dist/chunk-IFYCJZNS.js.map +6 -0
  16. package/dist/{chunk-NR2G4TMG.js → chunk-L6YEIVVD.js} +43 -22
  17. package/dist/chunk-L6YEIVVD.js.map +6 -0
  18. package/dist/{chunk-DPVJEFKP.js → chunk-PQNM6ET7.js} +4 -4
  19. package/dist/{chunk-DPVJEFKP.js.map → chunk-PQNM6ET7.js.map} +1 -1
  20. package/dist/{chunk-Z7MKH66Z.js → chunk-QFLVVM3H.js} +225 -11
  21. package/dist/chunk-QFLVVM3H.js.map +6 -0
  22. package/dist/{chunk-PGP3QA6L.js → chunk-TZXEEVKE.js} +1301 -70
  23. package/dist/chunk-TZXEEVKE.js.map +6 -0
  24. package/dist/{chunk-Q6MMPKQA.js → chunk-UHHURTSV.js} +11 -10
  25. package/dist/chunk-UHHURTSV.js.map +6 -0
  26. package/dist/{chunk-M6AYND5G.js → chunk-UJL7C2AS.js} +16 -7
  27. package/dist/chunk-UJL7C2AS.js.map +6 -0
  28. package/dist/chunk-YIX3OJXU.js +34 -0
  29. package/dist/designer.d.ts +11 -3
  30. package/dist/designer.js +587 -386
  31. package/dist/designer.js.map +3 -3
  32. package/dist/engine.worker.js +16729 -18
  33. package/dist/engine.worker.js.map +3 -3
  34. package/dist/peer/{chunk-6I2EAAVN.js → chunk-2CD5COHN.js} +43 -22
  35. package/dist/peer/chunk-2CD5COHN.js.map +6 -0
  36. package/dist/peer/{chunk-BMAXIBP2.js → chunk-3CBV5SAI.js} +4 -4
  37. package/dist/peer/{chunk-BMAXIBP2.js.map → chunk-3CBV5SAI.js.map} +1 -1
  38. package/dist/peer/{chunk-WSNRD2AQ.js → chunk-45GVDKP5.js} +11 -10
  39. package/dist/{chunk-Q6MMPKQA.js.map → peer/chunk-45GVDKP5.js.map} +1 -1
  40. package/dist/peer/{chunk-R4VX5UC7.js → chunk-LWXQX3NJ.js} +3 -3
  41. package/dist/{chunk-6TPZ7G74.js → peer/chunk-MKY3SDXU.js} +289 -25
  42. package/dist/peer/chunk-MKY3SDXU.js.map +6 -0
  43. package/dist/peer/chunk-S6LSPHSU.js +17 -0
  44. package/dist/peer/chunk-S6LSPHSU.js.map +6 -0
  45. package/dist/peer/{chunk-TSC4RAH3.js → chunk-VZHHNQYB.js} +1301 -70
  46. package/dist/peer/chunk-VZHHNQYB.js.map +6 -0
  47. package/dist/peer/{chunk-WQGENQZO.js → chunk-YITWGAR4.js} +8 -6
  48. package/dist/peer/chunk-YITWGAR4.js.map +6 -0
  49. package/dist/peer/chunk-Z5II55QT.js +33 -0
  50. package/dist/peer/chunk-Z5II55QT.js.map +6 -0
  51. package/dist/peer/designer.js +444 -243
  52. package/dist/peer/designer.js.map +3 -3
  53. package/dist/peer/engine.worker.js +16729 -17
  54. package/dist/peer/engine.worker.js.map +3 -3
  55. package/dist/peer/viewer.js +14 -11
  56. package/dist/peer/viewer.js.map +1 -1
  57. package/dist/viewer.d.ts +2 -1
  58. package/dist/viewer.js +14 -11
  59. package/dist/viewer.js.map +1 -1
  60. package/package.json +4 -2
  61. package/dist/chunk-3D2KFKG2.js.map +0 -6
  62. package/dist/chunk-6TPZ7G74.js.map +0 -6
  63. package/dist/chunk-BRB34AJ2.js +0 -16
  64. package/dist/chunk-M6AYND5G.js.map +0 -6
  65. package/dist/chunk-NR2G4TMG.js.map +0 -6
  66. package/dist/chunk-PGP3QA6L.js.map +0 -6
  67. package/dist/chunk-S2IUUI6N.js.map +0 -6
  68. package/dist/chunk-ULKEEARQ.js +0 -553
  69. package/dist/chunk-ULKEEARQ.js.map +0 -6
  70. package/dist/chunk-VNOWPSE6.js.map +0 -6
  71. package/dist/chunk-Z7MKH66Z.js.map +0 -6
  72. package/dist/peer/chunk-2FZTYEDE.js +0 -15
  73. package/dist/peer/chunk-6I2EAAVN.js.map +0 -6
  74. package/dist/peer/chunk-BQFI3DHK.js.map +0 -6
  75. package/dist/peer/chunk-TSC4RAH3.js.map +0 -6
  76. package/dist/peer/chunk-WQGENQZO.js.map +0 -6
  77. package/dist/peer/chunk-WSNRD2AQ.js.map +0 -6
  78. /package/dist/{chunk-2BPBQMH2.js.map → chunk-F5EPQTPF.js.map} +0 -0
  79. /package/dist/{chunk-BRB34AJ2.js.map → chunk-FEM2QVOO.js.map} +0 -0
  80. /package/dist/{peer/chunk-2FZTYEDE.js.map → chunk-YIX3OJXU.js.map} +0 -0
  81. /package/dist/peer/{chunk-R4VX5UC7.js.map → chunk-LWXQX3NJ.js.map} +0 -0
@@ -1,7 +1,13 @@
1
1
  import {
2
- TextMeasurer,
3
- finishText
4
- } from "./chunk-ULKEEARQ.js";
2
+ NO_CJK_FONTS,
3
+ PAGE_SIZES,
4
+ boundedFetch,
5
+ guardedFetch,
6
+ normBox,
7
+ normalizePageSize,
8
+ redactUrl,
9
+ resolvePage
10
+ } from "./chunk-BR5K6SBL.js";
5
11
  import {
6
12
  safeUrl
7
13
  } from "./chunk-72S6DETS.js";
@@ -27,362 +33,584 @@ import {
27
33
  } from "./chunk-GJS242RR.js";
28
34
  import {
29
35
  CORE_FONT_KEY,
36
+ DEFAULT_FAMILY,
30
37
  FontStore,
31
38
  OPTIONAL_KEYS,
32
39
  PRELOAD_KEYS,
33
40
  PX,
34
41
  SHAPER,
42
+ embeddedFace,
43
+ fallbackKey,
44
+ familyOf,
45
+ fontCss,
35
46
  fontsFor,
36
47
  fontsForDef,
37
48
  imageInfo,
49
+ isStandard,
38
50
  resolveFontKey,
39
51
  resolveImageSrc
40
- } from "./chunk-Z7MKH66Z.js";
52
+ } from "./chunk-QFLVVM3H.js";
41
53
 
42
- // src/engine/data/guard.js
43
- var FETCH_DEFAULTS = { timeoutMs: 3e4, maxBytes: 64 * 1048576, hops: 5 };
44
- var V4 = [
45
- ["0.0.0.0", 8],
46
- ["10.0.0.0", 8],
47
- ["100.64.0.0", 10],
48
- ["127.0.0.0", 8],
49
- ["169.254.0.0", 16],
50
- ["172.16.0.0", 12],
51
- ["192.0.0.0", 24],
52
- ["192.0.2.0", 24],
53
- ["192.168.0.0", 16],
54
- ["198.18.0.0", 15],
55
- ["198.51.100.0", 24],
56
- ["203.0.113.0", 24],
57
- ["224.0.0.0", 3]
58
- ].map(([a, p]) => [v4num(String(a)), Number(p)]);
59
- function v4num(ip) {
60
- return ip.split(".").reduce((n, x) => n * 256 + Number(x), 0);
61
- }
62
- function privateV4(ip) {
63
- const n = v4num(ip);
64
- return V4.some(([a, p]) => Math.floor(n / 2 ** (32 - p)) === Math.floor(a / 2 ** (32 - p)));
65
- }
66
- function groups6(ip) {
67
- const [h, t = ""] = ip.split("::");
68
- const a = h ? h.split(":") : [], b = t ? t.split(":") : [];
69
- if (b.length && b[b.length - 1].includes(".")) {
70
- const q = b.pop().split(".").map(Number);
71
- b.push((q[0] << 8 | q[1]).toString(16), (q[2] << 8 | q[3]).toString(16));
72
- }
73
- return [...a, ...Array(Math.max(0, 8 - a.length - b.length)).fill("0"), ...b].map((x) => parseInt(x, 16) || 0);
74
- }
75
- function privateV6(ip) {
76
- const g = groups6(ip);
77
- const v4 = () => `${g[6] >> 8}.${g[6] & 255}.${g[7] >> 8}.${g[7] & 255}`;
78
- if (g.slice(0, 6).every((x) => x === 0)) return true;
79
- if (g.slice(0, 5).every((x) => x === 0) && g[5] === 65535) return privateV4(v4());
80
- if (g[0] === 100 && g[1] === 65435) return g[2] === 1 || privateV4(v4());
81
- if (g[0] === 8194 || g[0] === 8193 && g[1] === 0) return true;
82
- if ((g[0] & 65024) === 64512 || (g[0] & 65472) === 65152 || (g[0] & 65472) === 65216 || (g[0] & 65280) === 65280) return true;
83
- return false;
84
- }
85
- function privateHost(host) {
86
- const h = host.replace(/^\[|\]$/g, "").toLowerCase();
87
- if (h.includes(":")) return privateV6(h);
88
- if (/^\d+\.\d+\.\d+\.\d+$/.test(h)) return privateV4(h);
89
- return h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local") || h.endsWith(".internal");
90
- }
91
- var refuse = (host) => Object.assign(new Error(`"${host}" is a private or local address; a data source may not reach it (pass allowHosts: ["${host}"] to render to allow it)`), { status: 403, code: "EPRIVATE" });
92
- function nodeMod(name) {
93
- const p = (
94
- /** @type {any} */
95
- globalThis.process
96
- );
97
- if (!p?.versions?.node) return null;
98
- if (!p.getBuiltinModule) throw Object.assign(new Error("The engine's fetch guard needs Node 20.16 or later (process.getBuiltinModule); or pass your own fetch to render"), { status: 500 });
99
- return p.getBuiltinModule(name);
100
- }
101
- var inNode = () => !!/** @type {any} */
102
- globalThis.process?.versions?.node;
103
- var addrHost = (a) => a.family === 6 || String(a.address).includes(":") ? `[${a.address}]` : String(a.address);
104
- function checkUrl(input, allow) {
105
- let u;
106
- try {
107
- u = new URL(input);
108
- } catch {
109
- throw Object.assign(new Error(`Not a URL: ${String(input).slice(0, 200)}`), { status: 400 });
110
- }
111
- if (u.protocol !== "http:" && u.protocol !== "https:") throw Object.assign(new Error(`Only http and https URLs can be fetched, not ${u.protocol}`), { status: 400 });
112
- if (u.username || u.password) throw Object.assign(new Error("A user name or password in a data URL is not allowed"), { status: 400 });
113
- const host = u.hostname.replace(/^\[|\]$/g, "").toLowerCase();
114
- if (!host) throw Object.assign(new Error("A data URL needs a host"), { status: 400 });
115
- if (allow.includes(host)) return u;
116
- let bad = true;
117
- try {
118
- bad = privateHost(u.hostname);
119
- } catch {
54
+ // src/engine/text/measure.js
55
+ import bidiFactory from "bidi-js";
56
+ var bidi = bidiFactory();
57
+ function special(cp) {
58
+ if (cp < 1424) return false;
59
+ if (cp <= 7423) return true;
60
+ if (cp >= 8192 && cp <= 8303) return cp >= 8203 && cp <= 8207 || cp >= 8234 && cp <= 8238 || cp >= 8288;
61
+ return cp >= 11904 && cp <= 42191 || cp >= 43056 && cp <= 44031 || cp >= 44032 && cp <= 55215 || cp >= 63744 && cp <= 64255 || cp >= 64285 && cp <= 65023 || cp >= 65024 && cp <= 65039 || cp >= 65072 && cp <= 65103 || cp >= 65136 && cp <= 65278 || cp >= 65280 && cp <= 65519 || cp >= 65536;
62
+ }
63
+ var FORMAT_CHAR = /^\p{Cf}$/u;
64
+ var cjk = (cp) => cp >= 11904 && cp <= 40959 || cp >= 44032 && cp <= 55215 || cp >= 63744 && cp <= 64255 || cp >= 65280 && cp <= 65519 || cp >= 131072 && cp <= 262143;
65
+ var NO_START = new Set("、。,.・:;?!ー)」』】〕〉》〗〙〟’”}⦆ぁぃぅぇぉっゃゅょゎゕゖァィゥェォッャュョヮヵヶ々〻‐゠–〜~…‥,.!?:;)]}%ゝゞヽヾ゛゜ㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ。」、・ー%]⦆");
66
+ var NO_END = new Set("(「『【〔〈《〖〘〝‘“{⦅([{[「$¥£");
67
+ var MAYBE = /[^\u0000-\u058f]/;
68
+ var RTL_OR_CONTROLS = /[֐-ࣿיִ-﷿ﹰ-﻾‎‏‪-‮⁦-⁩]|[\uD802\uD803\uD83A]/;
69
+ var SCRIPTS = [
70
+ "Latin",
71
+ "Greek",
72
+ "Cyrillic",
73
+ "Armenian",
74
+ "Hebrew",
75
+ "Arabic",
76
+ "Syriac",
77
+ "Thaana",
78
+ "Devanagari",
79
+ "Bengali",
80
+ "Gurmukhi",
81
+ "Gujarati",
82
+ "Oriya",
83
+ "Tamil",
84
+ "Telugu",
85
+ "Kannada",
86
+ "Malayalam",
87
+ "Sinhala",
88
+ "Thai",
89
+ "Lao",
90
+ "Tibetan",
91
+ "Myanmar",
92
+ "Georgian",
93
+ "Hangul",
94
+ "Ethiopic",
95
+ "Khmer",
96
+ "Mongolian",
97
+ "Han",
98
+ "Hiragana",
99
+ "Katakana",
100
+ "Bopomofo"
101
+ ].map((s) => (
102
+ /** @type {[string, RegExp]} */
103
+ [s, new RegExp(`\\p{Script=${s}}`, "u")]
104
+ ));
105
+ var SIMPLE = /* @__PURE__ */ new Set(["Latin", "Greek", "Cyrillic", "Armenian", "Georgian", "Hangul", "Han", "Hiragana", "Katakana", "Bopomofo", "Common"]);
106
+ var STICKY = /[\p{M}\p{Cf}]/u;
107
+ var SPACE = /\s/u;
108
+ var scriptCache = /* @__PURE__ */ new Map();
109
+ function scriptOf(cp) {
110
+ let s = scriptCache.get(cp);
111
+ if (s === void 0) {
112
+ const ch = String.fromCodePoint(cp);
113
+ s = "Common";
114
+ for (const [name, re] of SCRIPTS) if (re.test(ch)) {
115
+ s = name;
116
+ break;
117
+ }
118
+ if (scriptCache.size >= 2e4) scriptCache.clear();
119
+ scriptCache.set(cp, s);
120
120
  }
121
- if (bad) throw refuse(host);
122
- return u;
121
+ return s;
123
122
  }
124
- function nodeRequest(url, init, trusted) {
125
- const http = nodeMod("node:http"), https = nodeMod("node:https"), dns = nodeMod("node:dns"), stream = nodeMod("node:stream");
126
- const lookup = (name, o, cb) => dns.lookup(name, { ...o, all: true }, (err, list) => {
127
- if (err || !Array.isArray(list) || !list.length) return cb(err || refuse(name));
128
- let bad = true;
129
- try {
130
- bad = !trusted && list.some((a) => privateHost(addrHost(a)));
131
- } catch {
132
- }
133
- if (bad) return cb(refuse(name));
134
- return o.all ? cb(null, list) : cb(null, list[0].address, list[0].family);
135
- });
136
- const headers = Object.fromEntries(new Headers(init.headers || {}));
137
- headers["accept-encoding"] = "identity";
138
- return new Promise((resolve, reject) => {
139
- const req = (url.protocol === "https:" ? https : http).request(url, { method: init.method || "GET", headers, lookup, signal: init.signal }, (res) => {
140
- const pairs = [];
141
- for (let i = 0; i < res.rawHeaders.length; i += 2) pairs.push([res.rawHeaders[i], res.rawHeaders[i + 1]]);
142
- const none = [101, 204, 205, 304].includes(res.statusCode) || (init.method || "GET").toUpperCase() === "HEAD";
143
- if (none) res.resume();
144
- const enc = String(res.headers["content-encoding"] || "").trim().toLowerCase();
145
- let body = res, decoded = false;
146
- if (!none && enc && enc !== "identity") {
147
- const zlib = nodeMod("node:zlib");
148
- const dec2 = enc === "gzip" || enc === "x-gzip" ? zlib.createGunzip() : enc === "deflate" ? zlib.createInflate() : enc === "br" ? zlib.createBrotliDecompress() : null;
149
- if (!dec2) {
150
- res.resume();
151
- return reject(Object.assign(new Error(`the response is encoded as "${enc}", which the data fetch cannot decode`), { status: 415 }));
152
- }
153
- body = stream.pipeline(res, dec2, () => {
154
- });
155
- decoded = true;
156
- }
157
- const kept = decoded ? pairs.filter(([k]) => !/^(content-encoding|content-length)$/i.test(k)) : pairs;
158
- resolve(new Response(none ? null : stream.Readable.toWeb(body), { status: res.statusCode, statusText: res.statusMessage, headers: (
159
- /** @type {any} */
160
- kept
161
- ) }));
162
- });
163
- req.on("error", reject);
164
- req.end(init.body ?? void 0);
165
- });
123
+ var INTL = typeof Intl !== "undefined" && typeof Intl.Segmenter === "function";
124
+ var GRAPHEMES = INTL ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
125
+ var JOINS = /[\p{M}\u200c\u200d\ufe00-\ufe0f]/u;
126
+ function clusters(s, intl = true) {
127
+ if (intl && GRAPHEMES) return [...GRAPHEMES.segment(s)].map((x) => x.segment);
128
+ const out = [];
129
+ for (const ch of s) {
130
+ if (out.length && (JOINS.test(ch) || out[out.length - 1].endsWith("‍"))) out[out.length - 1] += ch;
131
+ else out.push(ch);
132
+ }
133
+ return out;
166
134
  }
167
- var CROSS_ORIGIN_KEEP = /* @__PURE__ */ new Set(["accept", "accept-language", "content-type"]);
168
- function redirectRequest(from, to, req, status) {
169
- if (from.protocol === "https:" && to.protocol !== "https:") throw Object.assign(new Error(`${from.host} redirects from https to http; not followed`), { status: 403 });
170
- const headers = new Headers(req.headers || {});
171
- if (to.origin !== from.origin) {
172
- for (const k of [...headers.keys()]) if (!CROSS_ORIGIN_KEEP.has(k)) headers.delete(k);
135
+ var graphemes = (s) => clusters(s);
136
+ var SA = /[\u0e00-\u0eff\u1000-\u109f\u1780-\u17ff\u19e0-\u19ff\u1a20-\u1aaf\ua9e0-\ua9ff\uaa60-\uaadf]/;
137
+ var WORDS = INTL ? new Intl.Segmenter(void 0, { granularity: "word" }) : null;
138
+ function saBreaks(text, segmenter = WORDS) {
139
+ const out = /* @__PURE__ */ new Set();
140
+ if (!segmenter) return out;
141
+ for (const { index } of segmenter.segment(text)) {
142
+ if (index > 0 && (SA.test(text[index]) || SA.test(text[index - 1]))) out.add(index);
173
143
  }
174
- const get = status === 303 || (status === 301 || status === 302) && (req.method || "GET").toUpperCase() !== "GET";
175
- return get ? { ...req, method: "GET", body: void 0, headers } : { ...req, headers };
144
+ return out;
176
145
  }
177
- function guardedFetch(opt, base) {
178
- const allow = (opt.allowHosts || []).map((h) => String(h).toLowerCase().replace(/^\[|\]$/g, ""));
179
- const node = !opt.viaBase && inNode();
180
- return (
181
- /** @type {any} */
182
- (async (input, init = {}) => {
183
- let url = checkUrl(String(input), allow);
184
- let req = { ...init };
185
- for (let hop = 0; ; hop++) {
186
- const trusted = allow.includes(url.hostname.replace(/^\[|\]$/g, "").toLowerCase());
187
- const res = node ? await nodeRequest(url, req, trusted) : await base(url.href, { ...req, redirect: "manual" });
188
- if (res.type === "opaqueredirect") throw new Error(`${url.host} redirects; a redirect cannot be checked here (pass your own fetch to render)`);
189
- const loc = res.status >= 300 && res.status < 400 ? res.headers.get("location") : null;
190
- if (!loc) return res;
191
- await res.body?.cancel().catch(() => {
192
- });
193
- if (hop >= FETCH_DEFAULTS.hops) throw new Error(`Too many redirects from ${url.host}`);
194
- const next = checkUrl(new URL(loc, url).href, allow);
195
- req = redirectRequest(url, next, req, res.status);
196
- url = next;
146
+ var TextMeasurer = class {
147
+ /** @param {import('./fonts.js').FontStore} store */
148
+ constructor(store) {
149
+ this.store = store;
150
+ this.faces = /* @__PURE__ */ new Map();
151
+ this.boxes = /* @__PURE__ */ new WeakMap();
152
+ this.boxCount = 0;
153
+ this.looks = /* @__PURE__ */ new WeakMap();
154
+ this.cover = /* @__PURE__ */ new Map();
155
+ this.cache = /* @__PURE__ */ new Map();
156
+ this.missing = /* @__PURE__ */ new Set();
157
+ this.noGlyph = /* @__PURE__ */ new Set();
158
+ this.unknownFonts = /* @__PURE__ */ new Set();
159
+ this.substituted = /* @__PURE__ */ new Map();
160
+ }
161
+ /**
162
+ * The key to measure with: `key` when loaded; else (not loaded yet, or failed) the bundled face of the same weight
163
+ * and style when that is loaded, else the core regular face. A key not yet loaded is asked for in the next pass.
164
+ */
165
+ usable(key) {
166
+ if (this.store.has(key)) return key;
167
+ if (!this.store.failed.has(key)) this.missing.add(key);
168
+ const c = fontCss(key);
169
+ const same2 = resolveFontKey(DEFAULT_FAMILY, c.weight, c.style);
170
+ if (this.store.has(same2)) return same2;
171
+ if (this.store.has(CORE_FONT_KEY)) return CORE_FONT_KEY;
172
+ this.missing.add(this.store.failed.has(same2) ? CORE_FONT_KEY : same2);
173
+ return this.store.fonts.keys().next().value ?? CORE_FONT_KEY;
174
+ }
175
+ /**
176
+ * A loaded face's numbers, read once: fontkit's getters (unitsPerEm, ascent…) decode tables on every read.
177
+ * adv: advances per code point (-1 = not on the fast path); words: fast-path units per string (a table repeats them).
178
+ * @param {string} key a loaded key
179
+ */
180
+ face(key) {
181
+ let f4 = this.faces.get(key);
182
+ if (!f4) {
183
+ const font = this.store.get(key);
184
+ f4 = { font, upm: font.unitsPerEm, ascent: font.ascent, descent: font.descent, adv: /* @__PURE__ */ new Map(), words: /* @__PURE__ */ new Map(), met: /* @__PURE__ */ new Map() };
185
+ this.faces.set(key, f4);
186
+ }
187
+ return f4;
188
+ }
189
+ /** Font units of `text` on the fast path, or -1 when it needs the complex path. */
190
+ fast(text, key) {
191
+ const f4 = this.face(key);
192
+ const hit = f4.words.get(text);
193
+ if (hit !== void 0) return hit;
194
+ const m = f4.adv, font = f4.font;
195
+ let units = 0;
196
+ for (let i = 0; i < text.length; i++) {
197
+ let cp = text.charCodeAt(i);
198
+ if (cp >= 55296 && cp <= 56319) {
199
+ const lo = text.charCodeAt(i + 1);
200
+ if (lo >= 56320 && lo <= 57343) {
201
+ cp = (cp - 55296) * 1024 + lo - 56320 + 65536;
202
+ i++;
203
+ }
197
204
  }
198
- })
199
- );
200
- }
201
- function boundedFetch(f4, opt) {
202
- const ms0 = Number(opt.fetchTimeoutMs) > 0 ? Number(opt.fetchTimeoutMs) : FETCH_DEFAULTS.timeoutMs;
203
- const max = Number(opt.maxFetchBytes) > 0 ? Number(opt.maxFetchBytes) : FETCH_DEFAULTS.maxBytes;
204
- return (
205
- /** @type {any} */
206
- (async (input, init = {}) => {
207
- const ms = Math.max(1, Math.min(ms0, opt.deadline ? opt.deadline - Date.now() : Infinity));
208
- const ctl = new AbortController();
209
- const timer = setTimeout(() => ctl.abort(Object.assign(new Error(`The data request took longer than ${Math.round(ms)} ms`), { status: 504 })), ms);
210
- const signal = init.signal ? anySignal([init.signal, ctl.signal]) : ctl.signal;
211
- let res;
212
- const stop = new Promise((_, rej) => signal.addEventListener("abort", () => rej(signal.reason), { once: true }));
213
- stop.catch(() => {
214
- });
215
- try {
216
- res = await Promise.race([f4(input, { ...init, signal }), stop]);
217
- } catch (e) {
218
- clearTimeout(timer);
219
- throw ctl.signal.aborted ? ctl.signal.reason : fetchFailure(e, input);
220
- }
221
- if (!res.body || /** @type {any} */
222
- init.pwStream) {
223
- clearTimeout(timer);
224
- return res;
225
- }
226
- let n = 0;
227
- const reader = res.body.getReader();
228
- const body = new ReadableStream({
229
- async pull(c) {
230
- try {
231
- const { done, value } = await reader.read();
232
- if (done) {
233
- clearTimeout(timer);
234
- c.close();
235
- return;
236
- }
237
- n += value.byteLength;
238
- if (n > max) {
239
- clearTimeout(timer);
240
- reader.cancel().catch(() => {
241
- });
242
- c.error(Object.assign(new Error(`The data response is larger than ${Math.round(max / 1048576)} MB (maxFetchBytes)`), { status: 413 }));
243
- return;
244
- }
245
- c.enqueue(value);
246
- } catch (e) {
247
- clearTimeout(timer);
248
- c.error(ctl.signal.aborted ? ctl.signal.reason : e);
205
+ let a = m.get(cp);
206
+ if (a === void 0) {
207
+ a = special(cp) || cp >= 32 && !font.hasGlyphForCodePoint(cp) ? -1 : font.glyphForCodePoint(cp).advanceWidth || 0;
208
+ m.set(cp, a);
209
+ }
210
+ if (a < 0) {
211
+ units = -1;
212
+ break;
213
+ }
214
+ units += a;
215
+ }
216
+ if (text.length <= 256) {
217
+ if (f4.words.size >= 5e4) f4.words.clear();
218
+ f4.words.set(text, units);
219
+ }
220
+ return units;
221
+ }
222
+ /**
223
+ * Advance width in points. @param {string} text @param {string} key @param {number} size
224
+ * @param {'ltr'|'rtl'} [dir] paragraph direction (default: from the first strong character)
225
+ */
226
+ width(text, key, size, dir) {
227
+ const k = this.store.has(key) ? key : this.usable(key);
228
+ const u = this.fast(text, k);
229
+ if (u >= 0) return u * size / this.face(k).upm;
230
+ return this.layout(text, k, dir).w * size;
231
+ }
232
+ /**
233
+ * Where underline and strike-through go, from the font's own tables (post and OS/2), in points:
234
+ * y offsets of the stroke centre from the baseline (positive = down, as on the page) and thicknesses.
235
+ * A font without the tables falls back to proportions of its descent and x-height.
236
+ * @param {string} key @param {number} size
237
+ */
238
+ decoration(key, size) {
239
+ const f4 = this.store.get(this.usable(key));
240
+ const u = f4.unitsPerEm, os2 = f4["OS/2"] || {};
241
+ const ut = f4.underlineThickness > 0 ? f4.underlineThickness : u / 14;
242
+ const up = f4.underlinePosition < 0 ? f4.underlinePosition : f4.descent / 2;
243
+ const st = os2.yStrikeoutSize > 0 ? os2.yStrikeoutSize : ut;
244
+ const sp = os2.yStrikeoutPosition > 0 ? os2.yStrikeoutPosition : (f4.xHeight > 0 ? f4.xHeight : f4.ascent * 0.5) / 2 + st / 2;
245
+ const k = size / u;
246
+ return { underlineY: -(up - ut / 2) * k, underlineWidth: ut * k, strikeY: -(sp - st / 2) * k, strikeWidth: st * k };
247
+ }
248
+ /**
249
+ * Superscript and subscript from the font's OS/2 table, in em: the size of the small text and how far its
250
+ * baseline moves (rise up for superscript, drop down for subscript). Fonts without the values get the
251
+ * proportions most fonts use.
252
+ * @param {string} key
253
+ */
254
+ script(key) {
255
+ const f4 = this.store.get(this.usable(key));
256
+ const u = f4.unitsPerEm, o = f4["OS/2"] || {};
257
+ const frac = (v, d) => v > 0 && v < u * 2 ? v / u : d;
258
+ return { supScale: frac(o.ySuperscriptYSize, 0.65), supRise: frac(o.ySuperscriptYOffset, 0.35), subScale: frac(o.ySubscriptYSize, 0.65), subDrop: frac(o.ySubscriptYOffset, 0.15) };
259
+ }
260
+ /** @param {string} key @param {number} size */
261
+ metrics(key, size) {
262
+ const f4 = this.face(this.usable(key));
263
+ let r = f4.met.get(size);
264
+ if (!r) {
265
+ r = Object.freeze({ ascent: f4.ascent * size / f4.upm, descent: -f4.descent * size / f4.upm });
266
+ f4.met.set(size, r);
267
+ }
268
+ return r;
269
+ }
270
+ /**
271
+ * The visually ordered runs of one line, or null when the line is simple (draw `text` as it is).
272
+ * @param {string} text @param {string} key @param {number} size @param {'ltr'|'rtl'} [dir]
273
+ * @returns {Run[]|null}
274
+ */
275
+ runs(text, key, size, dir) {
276
+ if (!dir && !MAYBE.test(text) && !isStandard(key)) return null;
277
+ const k = this.usable(key);
278
+ if (this.fast(text, k) >= 0) return null;
279
+ return this.layout(text, k, dir).runs.map((r) => ({
280
+ ...r,
281
+ x: r.x * size,
282
+ w: r.w * size,
283
+ glyphs: r.glyphs.map((v, i) => i % 4 === 1 || i % 4 === 2 ? v * size : v)
284
+ }));
285
+ }
286
+ /** Paragraph direction of text that needs shaping, bidi or CJK breaks, else undefined. */
287
+ direction(text, key) {
288
+ if (!MAYBE.test(text)) return void 0;
289
+ const k = this.usable(key);
290
+ if (this.fast(text, k) >= 0) return void 0;
291
+ return this.layout(text, k, void 0).rtl ? "rtl" : "ltr";
292
+ }
293
+ /**
294
+ * Greedy line breaking. Simple paragraphs break at spaces (a word longer than the line breaks by
295
+ * character), exactly as before. Others also break between CJK characters (kinsoku applied), never
296
+ * inside a grapheme cluster (a conjunct, a letter and its marks), and remember their direction.
297
+ * @param {string} text @param {string} key @param {number} size @param {number} maxWidth
298
+ * @returns {{text: string, width: number, dir?: 'ltr'|'rtl'}[]}
299
+ */
300
+ wrap(text, key, size, maxWidth) {
301
+ if (text === "") return [{ text: "", width: 0, end: true }];
302
+ const out = [];
303
+ const k = this.usable(key);
304
+ const sp = this.width(" ", k, size);
305
+ const str = String(text);
306
+ for (const para of str.includes("\n") ? str.split(/\r?\n/) : [str]) {
307
+ const dir = this.direction(para, k);
308
+ if (dir) {
309
+ this.wrapComplex(para, k, size, maxWidth, dir, sp, out);
310
+ continue;
311
+ }
312
+ if (!para.includes(" ")) {
313
+ const ww = this.width(para, k, size);
314
+ if (ww <= maxWidth || maxWidth <= 0) {
315
+ out.push({ text: para, width: ww, end: true });
316
+ continue;
317
+ }
318
+ }
319
+ const words = para.split(/ +/);
320
+ let line2 = "", lw = 0;
321
+ const push = () => {
322
+ out.push({ text: line2, width: lw });
323
+ line2 = "";
324
+ lw = 0;
325
+ };
326
+ for (let wi = 0; wi < words.length; wi++) {
327
+ const w = words[wi];
328
+ if (w === "" && wi > 0) continue;
329
+ const ww = this.width(w, k, size);
330
+ if (line2 === "") {
331
+ if (ww <= maxWidth || maxWidth <= 0) {
332
+ line2 = w;
333
+ lw = ww;
334
+ continue;
335
+ }
336
+ } else if (lw + sp + ww <= maxWidth) {
337
+ line2 += " " + w;
338
+ lw += sp + ww;
339
+ continue;
340
+ } else {
341
+ push();
342
+ if (ww <= maxWidth) {
343
+ line2 = w;
344
+ lw = ww;
345
+ continue;
249
346
  }
250
- },
251
- cancel(r) {
252
- clearTimeout(timer);
253
- return reader.cancel(r);
254
347
  }
255
- });
256
- return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
257
- })
258
- );
259
- }
260
- var CAUSES = {
261
- ENOTFOUND: "the host name did not resolve",
262
- EAI_AGAIN: "DNS lookup timed out",
263
- ECONNREFUSED: "connection refused",
264
- ECONNRESET: "connection reset",
265
- EPIPE: "connection closed",
266
- UND_ERR_SOCKET: "connection closed early",
267
- ETIMEDOUT: "timed out",
268
- UND_ERR_CONNECT_TIMEOUT: "connect timed out",
269
- EPROTO: "TLS error",
270
- ERR_TLS_CERT_ALTNAME_INVALID: "TLS certificate does not match the host",
271
- DEPTH_ZERO_SELF_SIGNED_CERT: "TLS certificate is self-signed",
272
- SELF_SIGNED_CERT_IN_CHAIN: "TLS certificate chain is untrusted",
273
- UNABLE_TO_VERIFY_LEAF_SIGNATURE: "TLS certificate chain is untrusted",
274
- CERT_HAS_EXPIRED: "TLS certificate has expired"
275
- };
276
- function redactUrl(s) {
277
- const str = String(s);
278
- const q = str.search(/[?#]/);
279
- const base = (q >= 0 ? str.slice(0, q) : str).replace(/^([a-z][\w+.-]*:\/\/)[^/?#]*@/i, "$1");
280
- return q >= 0 ? `${base}${str[q]}…` : base;
281
- }
282
- function fetchFailure(e, input) {
283
- if (!e || e.status) return e;
284
- const code = String(e.cause?.code || e.code || "");
285
- if (!code && e.message !== "fetch failed") return e;
286
- const why = CAUSES[code] || (/^ERR_TLS|CERT_|SSL/.test(code) ? "TLS error" : "");
287
- const at = redactUrl(input?.href ?? input);
288
- return Object.assign(new Error(`the request to ${at} failed${why ? `: ${why}` : ""}${code ? ` (${code})` : ""}`), { code: e.code });
289
- }
290
- function anySignal(list) {
291
- const S3 = (
292
- /** @type {any} */
293
- AbortSignal
294
- );
295
- if (S3.any) return S3.any(list);
296
- const c = new AbortController();
297
- for (const s of list) {
298
- if (s.aborted) c.abort(s.reason);
299
- else s.addEventListener("abort", () => c.abort(s.reason), { once: true });
300
- }
301
- return c.signal;
302
- }
303
-
304
- // src/viewer/dataFetch.js
305
- function sqlCall(body, sqlPreview) {
306
- let b;
307
- try {
308
- b = JSON.parse(String(body));
309
- } catch {
310
- return false;
348
+ let after = -1;
349
+ for (const ch of w) {
350
+ const cw = this.width(ch, k, size);
351
+ if (lw + cw > maxWidth && line2 !== "") {
352
+ if (after > 0 && after < line2.length) {
353
+ const rest = line2.slice(after);
354
+ line2 = line2.slice(0, after);
355
+ lw = this.width(line2, k, size);
356
+ push();
357
+ line2 = rest;
358
+ lw = this.width(rest, k, size);
359
+ } else push();
360
+ after = -1;
361
+ }
362
+ line2 += ch;
363
+ lw += cw;
364
+ if ((ch === "/" || ch === "-") && line2.length > 1) after = line2.length;
365
+ }
366
+ }
367
+ push();
368
+ out[out.length - 1].end = true;
369
+ }
370
+ return out;
311
371
  }
312
- if (!b || typeof b !== "object" || Array.isArray(b)) return false;
313
- const keys = Object.keys(b);
314
- if (typeof b.report === "string" && typeof b.source === "string") return keys.every((k) => ["report", "source", "params"].includes(k));
315
- return sqlPreview && typeof b.query === "string" && keys.every((k) => ["connection", "query", "params"].includes(k));
316
- }
317
- var policy = { allowHosts: [], unsafeFetch: false, fetch: null };
318
- function setFetchPolicy({ allowHosts, unsafeFetch = false, fetch: f4 = null } = {}) {
319
- policy = { allowHosts: (allowHosts || []).map((h) => String(h).toLowerCase().replace(/^\[|\]$/g, "")), unsafeFetch: !!unsafeFetch, fetch: typeof f4 === "function" ? f4 : null };
320
- }
321
- var POLICY_KEYS = ["allowHosts", "unsafeFetch", "allowPrivate"];
322
- function policyKeysIn(def) {
323
- const has = (o) => POLICY_KEYS.filter((k) => o && typeof o === "object" && Object.hasOwn(o, k));
324
- return [...has(def), ...(Array.isArray(def?.dataSources) ? def.dataSources : []).flatMap((s) => has(s).map((k) => `${s.name}.${k}`))];
325
- }
326
- var fetchPolicy = () => policy;
327
- function failed(e, url) {
328
- if (e?.status || !(e instanceof TypeError)) return e;
329
- return Object.assign(new Error(`the request to ${redactUrl(url)} failed (${e.message}): the server may not allow this page to read it (CORS: it must send Access-Control-Allow-Origin), the network or the server may be down, or the browser blocked it (a content security policy or an extension)`), { cause: e });
330
- }
331
- var DATA = [/^\/api\/sample\//, /^\/api\/data\/proxy$/];
332
- var OURS = /^\/(?:$|api(?:\/|$)|admin(?:\/|$)|designer(?:\/|$)|viewer(?:\/|$)|login(?:\/|$)|embed(?:\/|$)|_next(?:\/|$))/i;
333
- var BUSY = /* @__PURE__ */ new Set([429, 502, 503, 504]);
334
- function withRetry(f4, { onRetry, retries = 3, wait = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
335
- return (
336
- /** @type {any} */
337
- (async (url, init = {}) => {
338
- const method = String(init.method || "GET").toUpperCase();
339
- const read = method === "GET" || method === "HEAD" || method === "POST" && /\/api\/data\/sql$/.test(new URL(String(url), "http://x").pathname);
340
- for (let attempt = 1; ; attempt++) {
341
- const res = await f4(url, init);
342
- if (!read || !BUSY.has(res.status) || attempt > retries) return res;
343
- const after = Number(res.headers?.get?.("retry-after"));
344
- const ms = Number.isFinite(after) && after >= 0 ? Math.min(1e4, after * 1e3) : Math.round(400 * 2 ** (attempt - 1) * (0.75 + Math.random() * 0.5));
345
- onRetry?.({ attempt, max: retries, status: res.status });
346
- await wait(ms);
347
- }
348
- })
349
- );
350
- }
351
- function reportFetch(base, f0 = policy.fetch || ((...a) => fetch(...a)), { sqlPreview = false, onRetry, retries = 3, wait } = {}) {
352
- const self = new URL(base).origin;
353
- const { allowHosts, unsafeFetch } = policy;
354
- const send = (url, init) => f0(url, init).catch((e) => {
355
- throw failed(e, url);
356
- });
357
- const f4 = withRetry(send, { onRetry, retries, wait });
358
- const guarded = unsafeFetch ? f4 : guardedFetch({ allowHosts, viaBase: true }, f4);
359
- const other = (url, init) => guarded(url, init).catch((e) => {
360
- if (e?.code === "EPRIVATE") e.message = e.message.replace(/pass allowHosts: (\[[^\]]*\]) to render/, "mountViewer({ allowHosts: $1 })");
361
- else if (e && typeof e === "object") e.message = String(e.message).replace("(pass your own fetch to render)", "(a browser cannot see where it leads: use the URL it redirects to, or render on the server)");
362
- throw e;
363
- });
364
- const out = (
365
- /** @type {any} */
366
- (async (input, init = {}) => {
367
- const u = new URL(String(input instanceof Request ? input.url : input), base);
368
- const method = String(init.method || "GET").toUpperCase();
369
- if (u.origin !== self) return other(u.href, { ...init, credentials: "omit" });
370
- let path;
371
- try {
372
- path = decodeURIComponent(u.pathname).replace(/\/{2,}/g, "/");
373
- } catch {
374
- path = null;
372
+ /** @param {string} para @param {string} key @param {number} size @param {number} maxWidth @param {'ltr'|'rtl'} dir @param {number} sp @param {any[]} out */
373
+ wrapComplex(para, key, size, maxWidth, dir, sp, out) {
374
+ const tokens = [];
375
+ const sa = SA.test(para) ? saBreaks(para) : null;
376
+ let cur = "", space = false, prev = "", at = 0;
377
+ for (const ch of para) {
378
+ const i = at;
379
+ at += ch.length;
380
+ if (ch === " ") {
381
+ if (cur) {
382
+ tokens.push({ text: cur, space });
383
+ cur = "";
384
+ }
385
+ space = tokens.length > 0;
386
+ prev = "";
387
+ continue;
388
+ }
389
+ const cp = (
390
+ /** @type {number} */
391
+ ch.codePointAt(0)
392
+ );
393
+ const pcp = prev ? (
394
+ /** @type {number} */
395
+ prev.codePointAt(0)
396
+ ) : 0;
397
+ if (cur && (sa?.has(i) || (cjk(cp) || cjk(pcp)) && !NO_START.has(ch) && !NO_END.has(prev) && !STICKY.test(ch))) {
398
+ tokens.push({ text: cur, space });
399
+ cur = "";
400
+ space = false;
401
+ }
402
+ cur += ch;
403
+ prev = ch;
404
+ }
405
+ if (cur) tokens.push({ text: cur, space });
406
+ let line2 = "", lw = 0;
407
+ const push = () => {
408
+ out.push({ text: line2, width: this.width(line2, key, size, dir), dir });
409
+ line2 = "";
410
+ lw = 0;
411
+ };
412
+ for (const t of tokens) {
413
+ const tw = this.width(t.text, key, size, dir);
414
+ const gap = line2 && t.space ? sp : 0;
415
+ if (line2 === "" ? tw <= maxWidth || maxWidth <= 0 : lw + gap + tw <= maxWidth) {
416
+ line2 += (line2 && t.space ? " " : "") + t.text;
417
+ lw += gap + tw;
418
+ continue;
375
419
  }
376
- const read = path !== null && ["GET", "HEAD"].includes(method) && !/%2f|%5c/i.test(u.pathname) && (DATA.some((r) => r.test(path)) || !OURS.test(path));
377
- if (!read && !(method === "POST" && u.pathname === "/api/data/sql" && sqlCall(init.body, sqlPreview))) {
378
- throw Object.assign(new Error(`A data source may only read data from this ReportWright server (sample data, the data proxy, saved SQL), not ${method} ${path}.`), { status: 403 });
420
+ if (line2) push();
421
+ if (tw <= maxWidth) {
422
+ line2 = t.text;
423
+ lw = tw;
424
+ continue;
379
425
  }
380
- const ours = DATA.some((r) => r.test(u.pathname)) || u.pathname === "/api/data/sql";
381
- return f4(u.href, { ...init, credentials: ours ? "same-origin" : "omit", redirect: "error" });
382
- })
383
- );
384
- if (!unsafeFetch) out.hostRule = { allowHosts };
385
- return out;
426
+ for (const g of graphemes(t.text)) {
427
+ const gw = this.width(g, key, size, dir);
428
+ if (lw + gw > maxWidth && line2 !== "") push();
429
+ line2 += g;
430
+ lw += gw;
431
+ }
432
+ }
433
+ push();
434
+ }
435
+ /** Cut a line so it fits, then add "…". */
436
+ ellipsize(text, key, size, maxWidth, dir) {
437
+ const ell = "…";
438
+ if (this.width(text, key, size, dir) <= maxWidth) return text;
439
+ const ew = this.width(ell, key, size);
440
+ if (this.direction(text, key)) {
441
+ const gs = graphemes(text);
442
+ while (gs.length && this.width(gs.join(""), key, size, dir) + ew > maxWidth) gs.pop();
443
+ return gs.join("").trimEnd() + ell;
444
+ }
445
+ let s = text;
446
+ while (s.length && this.width(s, key, size) + ew > maxWidth) s = s.slice(0, -1);
447
+ return s.trimEnd() + ell;
448
+ }
449
+ /** Does this font have a glyph for the character? */
450
+ has(key, cp) {
451
+ let m = this.cover.get(key);
452
+ if (!m) {
453
+ m = /* @__PURE__ */ new Map();
454
+ this.cover.set(key, m);
455
+ }
456
+ let h = m.get(cp);
457
+ if (h === void 0) {
458
+ h = !!this.store.get(key).hasGlyphForCodePoint(cp);
459
+ m.set(cp, h);
460
+ }
461
+ return h;
462
+ }
463
+ /** The font for one character: the chosen one, else the bundled fallback for its script, else the one before. */
464
+ pick(cp, key, prev) {
465
+ if (cp < 32 || this.has(key, cp) || FORMAT_CHAR.test(String.fromCodePoint(cp))) return key;
466
+ if (isStandard(key)) {
467
+ const sub = embeddedFace(key);
468
+ if (!this.store.has(sub)) {
469
+ if (!this.store.failed.has(sub)) {
470
+ this.missing.add(sub);
471
+ return key;
472
+ }
473
+ } else {
474
+ if (this.has(sub, cp)) {
475
+ let s = this.substituted.get(key);
476
+ if (!s) this.substituted.set(key, s = /* @__PURE__ */ new Set());
477
+ if (s.size < 20) s.add(String.fromCodePoint(cp));
478
+ return sub;
479
+ }
480
+ key = sub;
481
+ }
482
+ }
483
+ const fb = fallbackKey(cp, key);
484
+ if (fb && !this.store.failed.has(fb)) {
485
+ if (!this.store.has(fb)) {
486
+ this.missing.add(fb);
487
+ return key;
488
+ }
489
+ if (this.has(fb, cp)) return fb;
490
+ }
491
+ if (prev && prev !== key && this.has(prev, cp)) return prev;
492
+ if (!this.missing.size) this.noGlyph.add(String.fromCodePoint(cp));
493
+ return key;
494
+ }
495
+ /**
496
+ * Bidi levels, a font per character, runs (one font, level, script, and space or not), shaping,
497
+ * then visual order. Cached at size 1.
498
+ * @param {string} text @param {string} key a loaded key @param {'ltr'|'rtl'|undefined} dir
499
+ * @returns {{ w: number, rtl: boolean, runs: Run[] }}
500
+ */
501
+ layout(text, key, dir) {
502
+ const ck = `${key}${dir || ""}${text}`;
503
+ const hit = this.cache.get(ck);
504
+ if (hit) return hit;
505
+ let levels = null, rtl = dir === "rtl";
506
+ if (dir === "rtl" || RTL_OR_CONTROLS.test(text)) {
507
+ const e = bidi.getEmbeddingLevels(text, dir);
508
+ levels = e.levels;
509
+ rtl = ((e.paragraphs[0]?.level ?? 0) & 1) === 1;
510
+ }
511
+ const segs = [];
512
+ let cur = null, prevFont = "";
513
+ for (let i = 0; i < text.length; ) {
514
+ const cp = (
515
+ /** @type {number} */
516
+ text.codePointAt(i)
517
+ );
518
+ const n = cp > 65535 ? 2 : 1;
519
+ const ch = text.slice(i, i + n);
520
+ const font = STICKY.test(ch) && prevFont ? prevFont : this.pick(cp, key, prevFont);
521
+ const level = levels ? levels[i] : 0;
522
+ const ws = SPACE.test(ch);
523
+ const script = scriptOf(cp);
524
+ if (!cur || font !== cur.font || level !== cur.level || ws !== cur.ws || script !== "Common" && cur.script !== "Common" && script !== cur.script) {
525
+ cur = { start: i, end: i + n, font, level, script, ws };
526
+ segs.push(cur);
527
+ } else {
528
+ cur.end = i + n;
529
+ if (cur.script === "Common") cur.script = script;
530
+ }
531
+ prevFont = font;
532
+ i += n;
533
+ }
534
+ const runs = segs.map((s) => this.shapeRun(text.slice(s.start, s.end), s.font, (s.level & 1) === 1, s.script));
535
+ const order = runs.map((r, i) => ({ r, level: segs[i].level }));
536
+ let max = 0, minOdd = Infinity;
537
+ for (const o of order) {
538
+ max = Math.max(max, o.level);
539
+ if (o.level & 1) minOdd = Math.min(minOdd, o.level);
540
+ }
541
+ for (let l = max; l >= minOdd; l--) {
542
+ for (let i = 0; i < order.length; ) {
543
+ if (order[i].level < l) {
544
+ i++;
545
+ continue;
546
+ }
547
+ let j = i;
548
+ while (j < order.length && order[j].level >= l) j++;
549
+ const rev = order.slice(i, j).reverse();
550
+ order.splice(i, j - i, ...rev);
551
+ i = j;
552
+ }
553
+ }
554
+ let x = 0;
555
+ for (const o of order) {
556
+ o.r.x = x;
557
+ x += o.r.w;
558
+ }
559
+ const out = { w: x, rtl, runs: order.map((o) => o.r) };
560
+ if (this.cache.size > 2e4) this.cache.clear();
561
+ this.cache.set(ck, out);
562
+ return out;
563
+ }
564
+ /** One run at size 1: HarfBuzz for complex scripts and right-to-left text, else character-map glyphs. @returns {Run} */
565
+ shapeRun(text, key, rtl, script) {
566
+ const store = this.store;
567
+ const complex = !SIMPLE.has(script);
568
+ const shape = (complex || rtl) && !store.get(key).standard;
569
+ const glyphs = [];
570
+ let pen = 0;
571
+ if (shape && store.shaper) {
572
+ const hf = store.hbFont(key);
573
+ const g = store.shaper.shape(hf, text, rtl, !complex);
574
+ for (let i = 0; i < g.length; i += 5) {
575
+ glyphs.push(g[i], (pen + g[i + 2]) / hf.upem, g[i + 3] / hf.upem, g[i + 4]);
576
+ pen += g[i + 1];
577
+ }
578
+ return { font: key, x: 0, w: pen / hf.upem, text, ...rtl ? { rtl } : {}, ...complex ? { shaped: true } : {}, glyphs };
579
+ }
580
+ if (shape && !store.failed.has(SHAPER)) this.missing.add(SHAPER);
581
+ else if (shape) this.unshaped = true;
582
+ const f4 = store.get(key);
583
+ const list = [];
584
+ for (let i = 0; i < text.length; ) {
585
+ const cp = (
586
+ /** @type {number} */
587
+ text.codePointAt(i)
588
+ );
589
+ const g = f4.glyphForCodePoint(cp);
590
+ list.push([g.id, g.advanceWidth || 0, i]);
591
+ i += cp > 65535 ? 2 : 1;
592
+ }
593
+ if (rtl) list.reverse();
594
+ for (const [id, a, c] of list) {
595
+ glyphs.push(id, pen / f4.unitsPerEm, 0, c);
596
+ pen += a;
597
+ }
598
+ return { font: key, x: 0, w: pen / f4.unitsPerEm, text, ...rtl ? { rtl } : {}, glyphs };
599
+ }
600
+ };
601
+ function finishText(items, m) {
602
+ for (const it of items) {
603
+ if (it.t === "field") {
604
+ finishText(it.draw, m);
605
+ continue;
606
+ }
607
+ if (it.t !== "text") continue;
608
+ it.font = m.usable(it.font);
609
+ for (const l of it.lines) {
610
+ const r = m.runs(l.text, it.font, it.size, l.dir);
611
+ if (r) l.runs = r;
612
+ }
613
+ }
386
614
  }
387
615
 
388
616
  // src/engine/expr/evaluate.js
@@ -557,8 +785,8 @@ function member(o, prop, ctx) {
557
785
  if (Object.hasOwn(src, prop)) return src[prop];
558
786
  const lower = prop.toLowerCase();
559
787
  for (const k of Object.keys(src)) if (k.toLowerCase() === lower) return src[k];
560
- if (o.__root === "Fields" && ctx.fields && Object.keys(ctx.fields).length) throw new ExprError(`The field "${prop}" does not exist`);
561
- if (o.__root === "Parameters") throw new ExprError(`The parameter "${prop}" does not exist`);
788
+ if (o.__root === "Fields" && ctx.fields && Object.keys(ctx.fields).length) throw new ExprError(`The field "${prop}" does not exist; check the spelling, or the data set that has it`);
789
+ if (o.__root === "Parameters") throw new ExprError(`The parameter "${prop}" does not exist; declare it under parameters, or check the spelling`);
562
790
  return null;
563
791
  }
564
792
  if (o == null) return null;
@@ -869,6 +1097,7 @@ function mapRows(rows, ast, ctx) {
869
1097
  }
870
1098
  return out;
871
1099
  }
1100
+ var isAggregate = (name) => Object.hasOwn(AGG, name);
872
1101
  var AGG = {
873
1102
  // text that is not a number is #Error, as in SSRS (it printed NaN)
874
1103
  sum: (vals, ctx) => {
@@ -2745,7 +2974,10 @@ function jsonPathRows(obj, path) {
2745
2974
  return spread ? { rows: cur, parents: par } : { rows: cur[0], parents: null };
2746
2975
  }
2747
2976
  function coerce(v, type, opt) {
2748
- if (v == null || v === "") return v === "" && type === "string" ? "" : null;
2977
+ if (v == null || v === "") {
2978
+ if (type === "number" || type === "date") opt?.empty?.();
2979
+ return v === "" && type === "string" ? "" : null;
2980
+ }
2749
2981
  switch (type) {
2750
2982
  case "number": {
2751
2983
  const n = typeof v === "number" ? v : typeof v === "boolean" ? v ? 1 : 0 : parseNumber(String(v), opt?.locale);
@@ -2775,6 +3007,32 @@ function coerce(v, type, opt) {
2775
3007
  return v;
2776
3008
  }
2777
3009
  }
3010
+ function badValues(dsName, warn) {
3011
+ const bad = /* @__PURE__ */ new Map();
3012
+ let row = 0;
3013
+ return {
3014
+ /** the row being typed, 1-based */
3015
+ at: (n) => {
3016
+ row = n;
3017
+ },
3018
+ /** coerce() options for one field, on top of base */
3019
+ opt: (fd, base) => ({
3020
+ ...base,
3021
+ bad: (v) => {
3022
+ const b = bad.get(fd.name);
3023
+ if (b) b.n++;
3024
+ else bad.set(fd.name, { n: 1, v, row, type: fd.type, empty: 0 });
3025
+ },
3026
+ empty: () => {
3027
+ const b = bad.get(fd.name) || (bad.set(fd.name, { n: 0, v: null, row: 0, type: fd.type, empty: 0 }), bad.get(fd.name));
3028
+ b.empty++;
3029
+ }
3030
+ }),
3031
+ done: () => {
3032
+ for (const [f4, b] of bad) if (b.n) warn(`Data set "${dsName}", field "${f4}": ${b.n} value${b.n > 1 ? "s do" : " does"} not read as ${b.type === "date" ? "a date" : "a number"} (for example "${String(b.v).slice(0, 40)}"), so ${b.n > 1 ? "they are" : "it is"} blank. First: row ${b.row}.${b.empty ? ` ${b.empty} ${b.empty > 1 ? "are" : "is"} empty.` : ""}`);
3033
+ }
3034
+ };
3035
+ }
2778
3036
  function detectFields(rows, { dates = true } = {}) {
2779
3037
  const out = /* @__PURE__ */ new Map();
2780
3038
  for (const r of rows.slice(0, 50)) {
@@ -3115,7 +3373,7 @@ function buildDataSets(def, sources, baseCtx) {
3115
3373
  const sets = {};
3116
3374
  const raws = new Set((def.dataSets || []).filter((d) => d.relation).map((d) => d.relation.parent));
3117
3375
  for (const ds of def.dataSets || []) {
3118
- let list, parents = null, xdoc = null, xels = null;
3376
+ let list, parents = null, xdoc = null, xels = null, asObject = null;
3119
3377
  if (ds.relation) {
3120
3378
  const prows = sets[ds.relation.parent];
3121
3379
  if (!prows) throw new Error(`Data set "${ds.name}": its parent "${ds.relation.parent}" must be a data set declared above it`);
@@ -3141,12 +3399,24 @@ function buildDataSets(def, sources, baseCtx) {
3141
3399
  } else {
3142
3400
  ({ rows: list, parents } = jsonPathRows(raw, xmlSrc ? "$" : ds.path || "$"));
3143
3401
  if (list == null) list = [];
3144
- if (!Array.isArray(list)) list = [list];
3402
+ if (!Array.isArray(list)) {
3403
+ asObject = list;
3404
+ list = [list];
3405
+ }
3145
3406
  }
3146
3407
  }
3147
3408
  if (!ds.relation && !xdoc) {
3148
3409
  const rawSrc = sources[ds.source];
3149
3410
  if (!list.length && ds.path && ds.path !== "$" && rawSrc != null && typeof rawSrc === "object" && !(Array.isArray(rawSrc) && !rawSrc.length) && jsonPath(rawSrc, ds.path) == null) baseCtx.warn?.(`Data set "${ds.name}": the path ${ds.path} matches nothing in the source "${ds.source}", so it has no rows`);
3411
+ if (asObject && typeof asObject === "object") {
3412
+ const inner = Object.keys(asObject).filter((k) => Array.isArray(asObject[k]) && asObject[k].some((x) => x && typeof x === "object"));
3413
+ const declared = (ds.fields || []).filter((fd) => !fd.expr && !fd.path);
3414
+ const hasFields = Object.values(asObject).some((v) => v != null && typeof v !== "object");
3415
+ if (inner.length && !hasFields && !declared.some((fd) => Object.hasOwn(asObject, fd.name))) {
3416
+ const at = ds.path && ds.path !== "$" ? ds.path : `$.${ds.source}`;
3417
+ baseCtx.warn?.(`Data set "${ds.name}": ${at} is an object, not a list of rows, so it is one row; expected an array of rows (did you mean ${at}.${inner[0]}?)`);
3418
+ }
3419
+ }
3150
3420
  const keys = /* @__PURE__ */ new Set();
3151
3421
  for (const r of list.slice(0, 200)) if (r && typeof r === "object" && !Array.isArray(r)) for (const k of Object.keys(r)) keys.add(k);
3152
3422
  if (keys.size) {
@@ -3165,14 +3435,11 @@ function buildDataSets(def, sources, baseCtx) {
3165
3435
  const plain2 = fields.filter((fd) => !fd.expr);
3166
3436
  const calc = fields.filter((fd) => fd.expr);
3167
3437
  const keepRaw = raws.has(ds.name);
3168
- const bad = /* @__PURE__ */ new Map();
3169
- const optFor = (fd) => ({ locale: ds.locale || baseCtx.locale, timeZone: baseCtx.timeZone, bad: (v) => {
3170
- const b = bad.get(fd.name);
3171
- if (b) b.n++;
3172
- else bad.set(fd.name, { n: 1, v, type: fd.type });
3173
- } });
3174
- const opts = new Map(fields.map((fd) => [fd, optFor(fd)]));
3438
+ const watch = badValues(ds.name, baseCtx.warn || (() => {
3439
+ }));
3440
+ const opts = new Map(fields.map((fd) => [fd, watch.opt(fd, { locale: ds.locale || baseCtx.locale, timeZone: baseCtx.timeZone })]));
3175
3441
  let rows = list.map((r, i) => {
3442
+ watch.at(i + 1);
3176
3443
  const o = {};
3177
3444
  for (const fd of plain2) o[fd.name] = coerce(fd.path ? xels ? xmlValue(xdoc, xels[i], fd.path) : jsonPath(r, fd.path) : r?.[fd.name], fd.type, opts.get(fd));
3178
3445
  if (parents && parents[i] != null) ROW_PARENT.set(o, parents[i]);
@@ -3181,25 +3448,25 @@ function buildDataSets(def, sources, baseCtx) {
3181
3448
  });
3182
3449
  if (calc.length) {
3183
3450
  const c = { ...baseCtx, dataSets: { ...baseCtx.dataSets || {}, ...sets }, fields: null };
3184
- const failed2 = /* @__PURE__ */ new Set();
3451
+ const failed = /* @__PURE__ */ new Set();
3185
3452
  for (const r of rows) {
3186
3453
  c.fields = r;
3187
3454
  for (const fd of calc) {
3188
- if (failed2.has(fd.name)) {
3455
+ if (failed.has(fd.name)) {
3189
3456
  r[fd.name] = null;
3190
3457
  continue;
3191
3458
  }
3192
3459
  try {
3193
3460
  r[fd.name] = coerce(evalValue(fd.expr, c), fd.type, opts.get(fd));
3194
3461
  } catch (e) {
3195
- failed2.add(fd.name);
3462
+ failed.add(fd.name);
3196
3463
  r[fd.name] = null;
3197
3464
  baseCtx.warn?.(`Data set "${ds.name}", field "${fd.name}": ${e.message}`);
3198
3465
  }
3199
3466
  }
3200
3467
  }
3201
3468
  }
3202
- for (const [f4, b] of bad) baseCtx.warn?.(`Data set "${ds.name}", field "${f4}": ${b.n} value${b.n > 1 ? "s do" : " does"} not read as ${b.type === "date" ? "a date" : "a number"} (for example "${String(b.v).slice(0, 40)}"), so ${b.n > 1 ? "they are" : "it is"} blank.`);
3469
+ watch.done();
3203
3470
  const setCtx = { ...baseCtx, dataSets: { ...baseCtx.dataSets, ...sets }, ...ds.caseSensitive != null ? { caseSensitive: ds.caseSensitive === true } : {} };
3204
3471
  rows = applyFilters(rows, ds.filters, setCtx);
3205
3472
  rows = applySort(rows, ds.sort, setCtx);
@@ -3345,472 +3612,77 @@ function sourceRows(raw, path) {
3345
3612
  }
3346
3613
  var sourceUrl = (url, params) => urlFrom(url || "", { params: params || {}, globals: {}, dataSets: {}, fields: null });
3347
3614
 
3348
- // src/engine/reuse.js
3349
- var REUSE_LIMITS = { depth: 4, reports: 50, items: 2e4, reportItems: 2e4, parts: 500, layers: 200, styleDepth: 10, repeats: 500 };
3350
- var fmt = (n) => n.toLocaleString("en-US");
3351
- function countItems(def, stop = Infinity) {
3352
- let n = 0;
3353
- eachItem(def, () => {
3354
- n++;
3355
- }, () => n > stop);
3356
- return n;
3357
- }
3358
- var ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
3359
- var isId = (v) => typeof v === "string" && ID.test(v);
3360
- var isSubreportId = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(v);
3361
- function makeLoader(load, cache = /* @__PURE__ */ new Map()) {
3362
- return (id) => {
3363
- if (!isId(id)) return Promise.reject(new Error(`"${String(id).slice(0, 80)}" is not a report id`));
3364
- let p = cache.get(id);
3365
- if (!p) {
3366
- if (!load) return Promise.reject(new Error("this render has no report loader (render it on the server or in the viewer)"));
3367
- if (cache.size >= REUSE_LIMITS.reports) return Promise.reject(new Error(`the report uses more than ${REUSE_LIMITS.reports} reports`));
3368
- p = Promise.resolve().then(() => load(id)).then((d) => {
3369
- if (!d || typeof d !== "object" || Array.isArray(d)) throw new Error("it is not a report");
3370
- if (countItems(d, REUSE_LIMITS.reportItems) > REUSE_LIMITS.reportItems) throw new Error(`it has more than ${fmt(REUSE_LIMITS.reportItems)} items`);
3371
- return d;
3372
- });
3373
- cache.set(id, p);
3615
+ // src/engine/style.js
3616
+ var BASE_STYLE = {
3617
+ fontFamily: "Inter",
3618
+ fontSize: 9,
3619
+ fontWeight: "normal",
3620
+ fontStyle: "normal",
3621
+ color: "#1f2328",
3622
+ backgroundColor: null,
3623
+ backgroundImage: null,
3624
+ // URL or data URI (an expression is fine)
3625
+ backgroundFit: "cover",
3626
+ // cover | contain | fill
3627
+ textAlign: "auto",
3628
+ // auto = numbers right, text left
3629
+ verticalAlign: "top",
3630
+ padding: 2,
3631
+ lineHeight: 1.25,
3632
+ border: null,
3633
+ // "1 solid #ccc" for all sides
3634
+ borderTop: null,
3635
+ borderRight: null,
3636
+ borderBottom: null,
3637
+ borderLeft: null,
3638
+ format: null,
3639
+ wrap: true,
3640
+ textDecoration: "none",
3641
+ // underline | line-through | both
3642
+ textDecorationColor: null,
3643
+ // the line's colour; null = the text colour
3644
+ writingMode: "lr-tb",
3645
+ // tb-rl = vertical: columns right to left, CJK upright, other text turned 90°
3646
+ // shapes and lines
3647
+ fill: null,
3648
+ stroke: "#1f2328",
3649
+ strokeWidth: 1,
3650
+ strokeDash: "solid",
3651
+ radius: 0
3652
+ };
3653
+ function resolveStyle(layers, ctx) {
3654
+ let node = null, allStatic = true;
3655
+ for (const l of layers) {
3656
+ if (!l || typeof l !== "object") continue;
3657
+ if (hasDynamic(l)) {
3658
+ allStatic = false;
3659
+ break;
3374
3660
  }
3375
- return p;
3376
- };
3377
- }
3378
- function reuseRefs(def) {
3379
- const out = /* @__PURE__ */ new Set();
3380
- if (!def || typeof def !== "object") return out;
3381
- for (const v of [def.master, def.styles?.sheet, def.theme]) if (isId(v)) out.add(v);
3382
- eachItem(def, (it) => {
3383
- if (it.type === "part" && isId(it.library)) out.add(it.library);
3384
- });
3385
- return out;
3386
- }
3387
- function drillRefs(def) {
3388
- const out = [], seen = /* @__PURE__ */ new Set();
3389
- const add2 = (report, where) => {
3390
- if (isId(report) && !seen.has(`${report}|${where}`)) {
3391
- seen.add(`${report}|${where}`);
3392
- out.push({ report, where });
3393
- }
3394
- };
3395
- const go = (n, where, depth) => {
3396
- if (!n || typeof n !== "object" || depth > 64) return;
3397
- if (Array.isArray(n)) {
3398
- for (const x of n) go(x, where, depth + 1);
3399
- return;
3400
- }
3401
- const here = n.type && n.name ? n.name : where;
3402
- if (n.type === "subreport") add2(n.report, here);
3403
- for (const [k, v] of Object.entries(n)) {
3404
- if (["action", "pointAction", "totalAction"].includes(k) && v?.type === "report") add2(v.report, k === "action" && here === where && !n.type ? `${here} cell` : here);
3405
- else if (v && typeof v === "object") go(v, here, depth + 1);
3406
- }
3407
- };
3408
- for (const [k, s] of Object.entries(def?.sections || {})) go(s, k, 0);
3409
- return out;
3410
- }
3411
- function eachItem(def, fn, stop = () => false) {
3412
- const go = (list, depth) => {
3413
- if (!Array.isArray(list) || depth > 64) return;
3414
- for (const it of list) {
3415
- if (stop()) return;
3416
- if (!it || typeof it !== "object") continue;
3417
- fn(it);
3418
- go(it.items, depth + 1);
3419
- }
3420
- };
3421
- for (const s of Object.values(def.sections || {})) for (const b of Array.isArray(s) ? s : [s]) go(b?.items, 0);
3422
- }
3423
- function findItemNamed(def, name) {
3424
- let hit = null;
3425
- eachItem(def, (it) => {
3426
- if (!hit && it.name === name) hit = it;
3427
- });
3428
- return hit;
3429
- }
3430
- function byName(a, b) {
3431
- const al = Array.isArray(a) ? a : [], bl = Array.isArray(b) ? b : [];
3432
- const names = new Set(bl.map((x) => x?.name));
3433
- return [...al.filter((x) => !names.has(x?.name)), ...bl];
3434
- }
3435
- async function applyMaster(def, get, warn, chain = []) {
3436
- if (def.master == null || def.master === "") return def;
3437
- const { master: id, ...content } = def;
3438
- if (chain.includes(id)) {
3439
- warn(`Master "${id}" would include itself; it is left out.`);
3440
- return content;
3441
- }
3442
- if (chain.length >= REUSE_LIMITS.depth) {
3443
- warn(`Master reports are nested more than ${REUSE_LIMITS.depth} levels; "${id}" is left out.`);
3444
- return content;
3445
- }
3446
- let m;
3447
- try {
3448
- m = await get(id);
3449
- } catch (e) {
3450
- warn(`The master report "${id}" did not load: ${e.message}`);
3451
- return content;
3452
- }
3453
- m = await applyMaster(m, get, warn, [...chain, id]);
3454
- return mergeMaster(content, m, warn);
3455
- }
3456
- function mergeMaster(c, m, warn = (_m) => {
3457
- }) {
3458
- const ms = m.sections || {}, cs = c.sections || {};
3459
- const mBody = (Array.isArray(ms.body) ? ms.body[0] : ms.body) || {};
3460
- const mItems = Array.isArray(mBody.items) ? mBody.items : [];
3461
- const phs = mItems.filter((i) => i?.type === "placeholder");
3462
- const ph = phs[0];
3463
- const others = mItems.filter((i) => i && i.type !== "placeholder");
3464
- const named = (Array.isArray(cs.content) ? cs.content : []).filter((x) => x && typeof x === "object" && !Array.isArray(x));
3465
- const extra = phs.slice(1);
3466
- for (const e of named) if (!extra.some((p) => p.name != null && p.name === e.placeholder)) warn(`The master has no other content placeholder "${String(e.placeholder ?? "").slice(0, 80)}"; its content is left out.`);
3467
- const blank2 = { border: "none", backgroundColor: null, backgroundImage: null, padding: 0 };
3468
- const boxOf = (p) => ({ x: Number(p.x) || 0, y: Number(p.y) || 0, w: Number(p.w) || 0, h: Number(p.h) || 0 });
3469
- const below = others.length ? Math.max(...others.map((i) => (Number(i.y) || 0) + (Number(i.h) || 0))) + 6 : 0;
3470
- const wrap = (b, k) => {
3471
- const items = Array.isArray(b?.items) ? b.items : [];
3472
- const box = ph ? boxOf(ph) : { x: 0, y: below, w: Math.max(0, ...items.map((i) => (Number(i.x) || 0) + (Number(i.w) || 0))), h: Math.max(0, ...items.map((i) => (Number(i.y) || 0) + (Number(i.h) || 0))) };
3473
- const content = { type: "container", name: k ? `MasterContent${k + 1}` : "MasterContent", ...box, keepTogether: false, style: blank2, items };
3474
- const mine = k ? others.map((i) => i.name ? { ...i, name: `${i.name}_${k + 1}` } : i) : others;
3475
- const more = k ? [] : extra.map((p, j) => {
3476
- const e = named.find((x) => p.name != null && x.placeholder === p.name);
3477
- return { type: "container", name: `MasterContent_${String(p.name ?? j + 2).replace(/\W/g, "_").slice(0, 64)}`, ...boxOf(p), keepTogether: false, style: blank2, items: Array.isArray(e?.items) ? e.items : [] };
3478
- });
3479
- return { ...b || {}, items: [...mine, content, ...more] };
3480
- };
3481
- const body = Array.isArray(cs.body) ? cs.body.map(wrap) : wrap(cs.body, 0);
3482
- const st = (x) => x.styles || {};
3483
- return {
3484
- ...c,
3485
- $schema: c.$schema ?? m.$schema,
3486
- locale: c.locale ?? m.locale,
3487
- currency: c.currency ?? m.currency,
3488
- nullPropagation: c.nullPropagation ?? m.nullPropagation,
3489
- timeZone: c.timeZone ?? m.timeZone,
3490
- caseSensitive: c.caseSensitive ?? m.caseSensitive,
3491
- naturalSort: c.naturalSort ?? m.naturalSort,
3492
- nullOrder: c.nullOrder ?? m.nullOrder,
3493
- collation: c.collation ?? m.collation,
3494
- arithmetic: c.arithmetic ?? m.arithmetic,
3495
- bandRows: c.bandRows ?? m.bandRows,
3496
- page: m.page || c.page,
3497
- parameters: byName(m.parameters, c.parameters),
3498
- dataSources: byName(m.dataSources, c.dataSources),
3499
- // the content's data sets first: its unscoped aggregates and items use the first data set, not the master's (N3)
3500
- dataSets: [...Array.isArray(c.dataSets) ? c.dataSets : [], ...byName(m.dataSets, []).filter((x) => !(c.dataSets || []).some((y) => y?.name === x?.name))],
3501
- functions: byName(m.functions, c.functions),
3502
- layers: byName(m.layers, c.layers),
3503
- images: { ...m.images || {}, ...c.images || {} },
3504
- styles: { ...st(m), ...st(c), base: { ...st(m).base || {}, ...st(c).base || {} }, named: { ...st(m).named || {}, ...st(c).named || {} } },
3505
- theme: c.theme ?? m.theme,
3506
- themeDef: c.themeDef ?? m.themeDef,
3507
- // the content's own page header and footer are replaced by the master's (its items are not in the report)
3508
- sections: { ...cs, content: void 0, pageHeader: ms.pageHeader || { height: 0, items: [] }, pageFooter: ms.pageFooter || { height: 0, items: [] }, body }
3509
- };
3510
- }
3511
- function flattenNamed(named, warn = (_m) => {
3512
- }) {
3513
- const out = {};
3514
- if (!named || typeof named !== "object") return out;
3515
- const get = (name, seen) => {
3516
- if (out[name]) return out[name];
3517
- const s = named[name];
3518
- if (!s || typeof s !== "object") return null;
3519
- const { parent, ...own } = s;
3520
- let base = null;
3521
- if (parent != null && parent !== "") {
3522
- if (seen.includes(parent)) warn(`Named style "${name}": its parent "${parent}" leads back to itself; the chain is cut there.`);
3523
- else if (seen.length >= REUSE_LIMITS.styleDepth) warn(`Named style "${name}": more than ${REUSE_LIMITS.styleDepth} parents; the chain is cut there.`);
3524
- else if (!named[parent]) warn(`Named style "${name}": there is no named style "${parent}" to inherit from.`);
3525
- else base = get(parent, [...seen, parent]);
3526
- }
3527
- return out[name] = base ? { ...base, ...own } : own;
3528
- };
3529
- for (const k of Object.keys(named)) get(k, [k]);
3530
- return out;
3531
- }
3532
- async function applySheet(def, get, warn) {
3533
- const id = def.styles?.sheet;
3534
- if (id == null || id === "") return def;
3535
- let sheet;
3536
- try {
3537
- sheet = await get(id);
3538
- } catch (e) {
3539
- warn(`The stylesheet "${id}" did not load: ${e.message}`);
3540
- return def;
3541
- }
3542
- const s = sheet.styles || {};
3543
- return { ...def, styles: { ...def.styles, base: { ...s.base || {}, ...def.styles.base || {} }, named: { ...s.named || {}, ...def.styles.named || {} } } };
3544
- }
3545
- async function loadParts(def, get, warn, budget) {
3546
- const parts = /* @__PURE__ */ new Map();
3547
- const libs = /* @__PURE__ */ new Map();
3548
- const keyOf2 = (it) => `${it.library}\0${it.part}`;
3549
- const resolve = async (lib, name, chain) => {
3550
- const key = `${lib}\0${name}`;
3551
- if (parts.has(key)) return;
3552
- if (chain.length >= REUSE_LIMITS.depth) {
3553
- parts.set(key, { error: `parts are nested more than ${REUSE_LIMITS.depth} levels` });
3554
- return;
3555
- }
3556
- let d;
3557
- try {
3558
- d = await get(lib);
3559
- } catch (e) {
3560
- parts.set(key, { error: `the library "${lib}" did not load: ${e.message}` });
3561
- return;
3562
- }
3563
- const p = (Array.isArray(d.parts) ? d.parts.slice(0, REUSE_LIMITS.parts) : []).find((x) => x?.name === name);
3564
- const src = p && findItemNamed(d, p.item);
3565
- if (!p || !src) {
3566
- parts.set(key, { error: `the library "${lib}" has no part "${name}"` });
3567
- return;
3568
- }
3569
- const item = structuredClone(src);
3570
- const props = {};
3571
- for (const q of Array.isArray(p.properties) ? p.properties : []) if (q?.name) props[q.name] = q.default ?? null;
3572
- const inner = [];
3573
- let own = 0;
3574
- const walk = (list) => {
3575
- for (const x of list || []) {
3576
- if (!x || typeof x !== "object") continue;
3577
- own++;
3578
- if (x.type === "part") inner.push(x);
3579
- walk(x.items);
3580
- }
3581
- };
3582
- walk([item]);
3583
- parts.set(key, { item, props, own, inner: inner.filter((x) => x !== item) });
3584
- libs.set(lib, d);
3585
- for (const x of inner) {
3586
- if (x === item) continue;
3587
- const k2 = keyOf2(x);
3588
- if (k2 === key || chain.includes(k2)) {
3589
- x.__cycle = true;
3590
- warn(`Part "${name}" of "${lib}" would include itself; that inner part is left out.`);
3591
- } else await resolve(x.library, x.part, [...chain, key]);
3592
- }
3593
- };
3594
- const used = [];
3595
- eachItem(def, (it) => {
3596
- if (it.type === "part") used.push(it);
3597
- });
3598
- for (const it of used) await resolve(String(it.library ?? ""), String(it.part ?? ""), []);
3599
- const size = /* @__PURE__ */ new Map();
3600
- const sizeOf = (key) => {
3601
- if (size.has(key)) return size.get(key);
3602
- size.set(key, 1);
3603
- const e = parts.get(key);
3604
- let n = 1;
3605
- if (e && !e.error) {
3606
- n = e.own;
3607
- for (const x of e.inner) if (!x.__cycle) n += sizeOf(`${x.library}\0${x.part}`);
3608
- }
3609
- size.set(key, n);
3610
- return n;
3611
- };
3612
- let total = 0;
3613
- for (const it of used) total += sizeOf(`${String(it.library ?? "")}\0${String(it.part ?? "")}`);
3614
- budget.items += total;
3615
- if (budget.items > REUSE_LIMITS.items) {
3616
- const msg = `the report parts would draw more than ${fmt(REUSE_LIMITS.items)} items (with subreports); they are left out`;
3617
- for (const k of parts.keys()) parts.set(k, { error: msg });
3618
- warn(`Report parts: ${msg}.`);
3619
- }
3620
- return { parts, libs: [...libs.values()] };
3621
- }
3622
- function mergeLibraries(def, libs) {
3623
- let d = def;
3624
- for (const l of libs) {
3625
- d = {
3626
- ...d,
3627
- dataSources: byName(l.dataSources, d.dataSources),
3628
- dataSets: byName(l.dataSets, d.dataSets),
3629
- parameters: byName(l.parameters, d.parameters),
3630
- functions: byName(l.functions, d.functions),
3631
- images: { ...l.images || {}, ...d.images || {} },
3632
- styles: { ...d.styles || {}, named: { ...l.styles?.named || {}, ...d.styles?.named || {} } }
3633
- };
3634
- }
3635
- return d;
3636
- }
3637
- async function resolveReuse(def, get, warn, chain = [], budget = { items: 0 }) {
3638
- let d = await applyMaster(def, get, warn, chain);
3639
- d = await applySheet(d, get, warn);
3640
- if (Array.isArray(d.layers) && d.layers.length > REUSE_LIMITS.layers) {
3641
- warn(`The report has more than ${REUSE_LIMITS.layers} layers; the others are ignored.`);
3642
- d = { ...d, layers: d.layers.slice(0, REUSE_LIMITS.layers) };
3643
- }
3644
- let parts = null;
3645
- let hasPart = false;
3646
- eachItem(d, (it) => {
3647
- if (it.type === "part") hasPart = true;
3648
- });
3649
- if (hasPart) {
3650
- const r = await loadParts(d, get, warn, budget);
3651
- parts = r.parts;
3652
- d = mergeLibraries(d, r.libs);
3653
- }
3654
- return { def: d, parts };
3655
- }
3656
- var DEFAULT_THEME = {
3657
- colors: { Dark1: "#000000", Light1: "#FFFFFF", Dark2: "#44546A", Light2: "#E7E6E6", Accent1: "#4472C4", Accent2: "#ED7D31", Accent3: "#A5A5A5", Accent4: "#FFC000", Accent5: "#5B9BD5", Accent6: "#70AD47", Hyperlink: "#0563C1" },
3658
- fonts: { major: { family: "Inter", size: 14, weight: "bold", style: "normal" }, minor: { family: "Inter", size: 9, weight: "normal", style: "normal" } },
3659
- constants: {}
3660
- };
3661
- var THEME_COLORS = Object.keys(DEFAULT_THEME.colors);
3662
- function themeContext(td) {
3663
- const t = td && typeof td === "object" ? td : {};
3664
- const font = (k) => {
3665
- const f4 = { ...DEFAULT_THEME.fonts[k], ...t.fonts?.[k] && typeof t.fonts[k] === "object" ? t.fonts[k] : {} };
3666
- return { Family: f4.family, Size: f4.size, Weight: f4.weight, Style: f4.style };
3667
- };
3668
- const consts = t.constants && typeof t.constants === "object" && !Array.isArray(t.constants) ? t.constants : {};
3669
- return { Colors: { ...DEFAULT_THEME.colors, ...t.colors && typeof t.colors === "object" ? t.colors : {} }, Fonts: { MajorFont: font("major"), MinorFont: font("minor") }, Constants: { ...consts } };
3670
- }
3671
- async function resolveTheme(def, ctx, get, warn) {
3672
- let td = def.themeDef;
3673
- const src = def.theme;
3674
- if (typeof src === "string" && src.trim()) {
3675
- let id = src;
3676
- if (src.startsWith("=")) {
3677
- try {
3678
- id = evalValue(src, ctx);
3679
- } catch (e) {
3680
- warn(`Theme: ${e.message}`);
3681
- id = null;
3682
- }
3683
- }
3684
- if (id != null && id !== "") {
3685
- try {
3686
- const t = await get(String(id));
3687
- if (t.themeDef && typeof t.themeDef === "object") td = t.themeDef;
3688
- else warn(`The report "${id}" has no theme (set its colours and fonts under Report → Theme).`);
3689
- } catch (e) {
3690
- warn(`The theme "${id}" did not load: ${e.message}`);
3691
- }
3692
- }
3693
- }
3694
- return themeContext(td);
3695
- }
3696
-
3697
- // src/engine/units.js
3698
- var PAGE_SIZES = {
3699
- A4: [595.28, 841.89],
3700
- A5: [419.53, 595.28],
3701
- A3: [841.89, 1190.55],
3702
- Letter: [612, 792],
3703
- Legal: [612, 1008]
3704
- };
3705
- var UNITS = { pt: 1, in: 72, cm: 72 / 2.54, mm: 72 / 25.4, px: 0.75 };
3706
- var PAGELESS_MAX = 14400;
3707
- function toPt(v, unit) {
3708
- return v * UNITS[unit];
3709
- }
3710
- function fromPt(pt, unit) {
3711
- return pt / UNITS[unit];
3712
- }
3713
- function normalizePageSize(size) {
3714
- if (typeof size !== "string") return void 0;
3715
- const t = size.trim().toLowerCase();
3716
- if (t === "pageless") return "Pageless";
3717
- return Object.keys(PAGE_SIZES).find((k) => k.toLowerCase() === t);
3718
- }
3719
- function resolvePage(page = {}) {
3720
- const size = normalizePageSize(page.size);
3721
- if (size === "Pageless") {
3722
- const w2 = Number(page.width) || 960;
3723
- const m2 = normBox(page.margins ?? 24);
3724
- return { width: w2, height: PAGELESS_MAX, margins: { top: m2[0], right: m2[1], bottom: m2[2], left: m2[3] }, bodyWidth: w2 - m2[1] - m2[3], pageless: true, minHeight: Number(page.minHeight) || 0 };
3725
- }
3726
- let [w, h] = size ? PAGE_SIZES[size] : [page.width || PAGE_SIZES.A4[0], page.height || PAGE_SIZES.A4[1]];
3727
- if (page.orientation === "landscape" && h > w) [w, h] = [h, w];
3728
- const m = normBox(page.margins ?? 36);
3729
- return { width: w, height: h, margins: { top: m[0], right: m[1], bottom: m[2], left: m[3] }, bodyWidth: w - m[1] - m[3] };
3730
- }
3731
- function normBox(v) {
3732
- if (v == null) return [0, 0, 0, 0];
3733
- if (typeof v === "number") return [v, v, v, v];
3734
- if (typeof v === "string") v = v.trim().split(/[\s,]+/).map(Number);
3735
- if (!Array.isArray(v)) return [0, 0, 0, 0];
3736
- const a = v.map((x) => Number(x) || 0);
3737
- if (a.length === 1) return [a[0], a[0], a[0], a[0]];
3738
- if (a.length === 2) return [a[0], a[1], a[0], a[1]];
3739
- if (a.length === 3) return [a[0], a[1], a[2], a[1]];
3740
- return [a[0], a[1], a[2], a[3]];
3741
- }
3742
-
3743
- // src/engine/style.js
3744
- var BASE_STYLE = {
3745
- fontFamily: "Inter",
3746
- fontSize: 9,
3747
- fontWeight: "normal",
3748
- fontStyle: "normal",
3749
- color: "#1f2328",
3750
- backgroundColor: null,
3751
- backgroundImage: null,
3752
- // URL or data URI (an expression is fine)
3753
- backgroundFit: "cover",
3754
- // cover | contain | fill
3755
- textAlign: "auto",
3756
- // auto = numbers right, text left
3757
- verticalAlign: "top",
3758
- padding: 2,
3759
- lineHeight: 1.25,
3760
- border: null,
3761
- // "1 solid #ccc" for all sides
3762
- borderTop: null,
3763
- borderRight: null,
3764
- borderBottom: null,
3765
- borderLeft: null,
3766
- format: null,
3767
- wrap: true,
3768
- textDecoration: "none",
3769
- // underline | line-through | both
3770
- textDecorationColor: null,
3771
- // the line's colour; null = the text colour
3772
- writingMode: "lr-tb",
3773
- // tb-rl = vertical: columns right to left, CJK upright, other text turned 90°
3774
- // shapes and lines
3775
- fill: null,
3776
- stroke: "#1f2328",
3777
- strokeWidth: 1,
3778
- strokeDash: "solid",
3779
- radius: 0
3780
- };
3781
- function resolveStyle(layers, ctx) {
3782
- let node = null, allStatic = true;
3783
- for (const l of layers) {
3784
- if (!l || typeof l !== "object") continue;
3785
- if (hasDynamic(l)) {
3786
- allStatic = false;
3787
- break;
3788
- }
3789
- }
3790
- if (allStatic) {
3791
- let map = CACHE;
3792
- for (const l of layers) {
3793
- if (!l || typeof l !== "object") continue;
3794
- let next = map.get(l);
3795
- if (!next) {
3796
- next = { map: /* @__PURE__ */ new WeakMap(), value: null };
3797
- map.set(l, next);
3798
- }
3799
- node = next;
3800
- map = next.map;
3801
- }
3802
- if (node?.value) return node.value;
3803
- }
3804
- const out = {};
3805
- for (const l of layers) {
3806
- if (!l || typeof l !== "object") continue;
3807
- for (const k in l) {
3808
- const v = l[k];
3809
- if (v === void 0) continue;
3810
- out[k] = isDynamic(v) ? safe(v, ctx) : v;
3811
- }
3812
- }
3813
- if (allStatic && node) node.value = Object.freeze(out);
3661
+ }
3662
+ if (allStatic) {
3663
+ let map = CACHE;
3664
+ for (const l of layers) {
3665
+ if (!l || typeof l !== "object") continue;
3666
+ let next = map.get(l);
3667
+ if (!next) {
3668
+ next = { map: /* @__PURE__ */ new WeakMap(), value: null };
3669
+ map.set(l, next);
3670
+ }
3671
+ node = next;
3672
+ map = next.map;
3673
+ }
3674
+ if (node?.value) return node.value;
3675
+ }
3676
+ const out = {};
3677
+ for (const l of layers) {
3678
+ if (!l || typeof l !== "object") continue;
3679
+ for (const k in l) {
3680
+ const v = l[k];
3681
+ if (v === void 0) continue;
3682
+ out[k] = isDynamic(v) ? safe(v, ctx) : v;
3683
+ }
3684
+ }
3685
+ if (allStatic && node) node.value = Object.freeze(out);
3814
3686
  return out;
3815
3687
  }
3816
3688
  var CACHE = /* @__PURE__ */ new WeakMap();
@@ -3911,7 +3783,7 @@ function lookOf(st, m) {
3911
3783
  let l = m.looks && Object.isFrozen(st) ? m.looks.get(st) : void 0;
3912
3784
  if (!l) {
3913
3785
  const size = Number(st.fontSize) || 9;
3914
- l = { key: resolveFontKey(st.fontFamily, st.fontWeight, st.fontStyle), size, pad: padding(st), lh: size * (Number(st.lineHeight) || 1.25) };
3786
+ l = { key: resolveFontKey(st.fontFamily, st.fontWeight, st.fontStyle, m.unknownFonts), size, pad: padding(st), lh: size * (Number(st.lineHeight) || 1.25) };
3915
3787
  if (m.looks && Object.isFrozen(st)) m.looks.set(st, l);
3916
3788
  }
3917
3789
  return l;
@@ -3969,18 +3841,22 @@ function paintBoxAt(x, y, w, h, st, tl, numeric, m, ellipsis, items, cell = null
3969
3841
  lines[lines.length - 1] = { ...last, text: t, width: m.width(t, key, size, last.dir) };
3970
3842
  }
3971
3843
  }
3972
- const { ascent, descent } = m.metrics(key, size);
3844
+ let ascent = 0, half = 0;
3973
3845
  const blockH = lines.length * lh;
3974
3846
  let top = y + pad2[0];
3975
3847
  if (st.verticalAlign === "middle") top = y + pad2[0] + (avail - blockH) / 2;
3976
3848
  else if (st.verticalAlign === "bottom") top = y + h - pad2[2] - blockH;
3977
3849
  const auto = st.textAlign === "auto" || !st.textAlign;
3978
- const half = (lh - (ascent + descent)) / 2;
3979
3850
  const cut = lines.length < tl.lines.length;
3980
3851
  const out = [];
3981
3852
  for (let i = 0; i < lines.length; i++) {
3982
3853
  const ln = lines[i];
3983
3854
  if (ln.text === "") continue;
3855
+ if (!ascent) {
3856
+ const mt = m.metrics(key, size);
3857
+ ascent = mt.ascent;
3858
+ half = (lh - (ascent + mt.descent)) / 2;
3859
+ }
3984
3860
  const align = auto ? numeric || ln.dir === "rtl" ? "right" : "left" : st.textAlign;
3985
3861
  const ly = top + i * lh + half + ascent;
3986
3862
  if (align === "justify" && !ln.end && !ln.dir && !(cut && i === lines.length - 1)) {
@@ -5121,7 +4997,7 @@ var tag2 = (o, a) => {
5121
4997
  return o;
5122
4998
  };
5123
4999
  var f2 = (v) => Math.round(v * 100) / 100;
5124
- var byName2 = (lc, pal) => (name) => {
5000
+ var byName = (lc, pal) => (name) => {
5125
5001
  const m = lc.categoryColors || (lc.categoryColors = /* @__PURE__ */ new Map());
5126
5002
  const k = String(name);
5127
5003
  if (!m.has(k)) m.set(k, m.size);
@@ -5212,7 +5088,7 @@ function chartData(item, ctx, lc, pal = paletteOf(item.palette)) {
5212
5088
  groups.forEach((gr, gIdx) => {
5213
5089
  for (const sd of defs) {
5214
5090
  const name = defs.length > 1 ? `${toText(gr.key, null, ctx)} · ${sd.name}` : toText(gr.key, null, ctx);
5215
- series.push(make(sd, name, item.colorByCategory ? byName2(lc, pal)(name) : pal[ci % pal.length], (cat, i) => split[i][gIdx]));
5091
+ series.push(make(sd, name, item.colorByCategory ? byName(lc, pal)(name) : pal[ci % pal.length], (cat, i) => split[i][gIdx]));
5216
5092
  ci++;
5217
5093
  }
5218
5094
  });
@@ -5248,7 +5124,7 @@ function paintChart(item, ctx, lc, base, meta = {}) {
5248
5124
  let pal = paletteOf(item.palette);
5249
5125
  const type = CHART_TYPES.includes(item.chartType) ? item.chartType : "column";
5250
5126
  const data = chartData({ ...item, chartType: type, ...MORE_TYPES.has(type) ? { seriesGroup: void 0 } : {} }, ctx, lc, pal);
5251
- if (item.colorByCategory && (type === "pie" || type === "donut" || type === "funnel") && data.categories.length) pal = data.categories.map(byName2(lc, pal));
5127
+ if (item.colorByCategory && (type === "pie" || type === "donut" || type === "funnel") && data.categories.length) pal = data.categories.map(byName(lc, pal));
5252
5128
  const links = [];
5253
5129
  const pointCtx = (ci) => {
5254
5130
  const b = data.buckets[ci];
@@ -5876,7 +5752,7 @@ function rowPoints(type, g) {
5876
5752
  return { s, pts, i: si };
5877
5753
  });
5878
5754
  const missing = rows.length - drawn.size;
5879
- if (missing > 0) lc.warn(`${who}: ${missing} of ${rows.length} rows have no x or y value and are not drawn`);
5755
+ if (missing > 0 && !(type === "polar" && item.xValue == null)) lc.warn(`${who}: ${missing} of ${rows.length} rows have no x or y value and are not drawn`);
5880
5756
  return sets;
5881
5757
  }
5882
5758
  function paintXY(type, g) {
@@ -7059,7 +6935,7 @@ function parseRich(html, opt = {}) {
7059
6935
  s = s.slice(0, RICH_LIMITS.maxLength);
7060
6936
  const lt = s.lastIndexOf("<");
7061
6937
  if (lt >= 0 && s.indexOf(">", lt) < 0) s = s.slice(0, lt);
7062
- warn(`Rich text: the markup is longer than ${RICH_LIMITS.maxLength} characters and was cut.`);
6938
+ warn(`the markup is longer than ${RICH_LIMITS.maxLength} characters and was cut.`);
7063
6939
  }
7064
6940
  const low = s.toLowerCase();
7065
6941
  const blocks = [];
@@ -7070,7 +6946,7 @@ function parseRich(html, opt = {}) {
7070
6946
  const cutAt = (what) => {
7071
6947
  if (!full) {
7072
6948
  full = true;
7073
- warn(`Rich text: more than ${what}; the rest is cut.`);
6949
+ warn(`more than ${what}; the rest is cut.`);
7074
6950
  }
7075
6951
  };
7076
6952
  let cur = null;
@@ -7207,7 +7083,7 @@ function parseRich(html, opt = {}) {
7207
7083
  ignored++;
7208
7084
  if (!deepWarned) {
7209
7085
  deepWarned = true;
7210
- warn(`Rich text: tags nest more than ${RICH_LIMITS.maxDepth} deep; the deeper ones are ignored (their text is kept).`);
7086
+ warn(`tags nest more than ${RICH_LIMITS.maxDepth} deep; the deeper ones are ignored (their text is kept).`);
7211
7087
  }
7212
7088
  continue;
7213
7089
  }
@@ -7239,7 +7115,7 @@ function parseRich(html, opt = {}) {
7239
7115
  const h = decode2(tag3.attrs.href ?? "");
7240
7116
  const ok = safeHref(h);
7241
7117
  if (ok) st.href = ok;
7242
- else if (h) warn(`Rich text: the link "${h.slice(0, 40)}" is not allowed (only http, https, mailto and / links); its text is kept.`);
7118
+ else if (h) warn(`the link "${h.slice(0, 40)}" is not allowed (only http, https, mailto and / links); its text is kept.`);
7243
7119
  break;
7244
7120
  }
7245
7121
  default:
@@ -7260,7 +7136,7 @@ function parseRich(html, opt = {}) {
7260
7136
  stack2.push({ name, st });
7261
7137
  }
7262
7138
  flush();
7263
- if (unknown.size) warn(`Rich text: these tags are not supported and were left out (their text is kept): ${[...unknown].slice(0, 10).join(", ")}.`);
7139
+ if (unknown.size) warn(`these tags are not supported and were left out (their text is kept): ${[...unknown].slice(0, 10).join(", ")}.`);
7264
7140
  return { blocks };
7265
7141
  }
7266
7142
 
@@ -7302,7 +7178,7 @@ function layoutRich(blocks, st, width, m, opt = {}) {
7302
7178
  const lookOf2 = (rs) => {
7303
7179
  let l = looks.get(rs);
7304
7180
  if (l) return l;
7305
- const key = resolveFontKey(rs.fontFamily || st.fontFamily, rs.fontWeight || st.fontWeight, rs.fontStyle || st.fontStyle);
7181
+ const key = resolveFontKey(rs.fontFamily || st.fontFamily, rs.fontWeight || st.fontWeight, rs.fontStyle || st.fontStyle, m.unknownFonts);
7306
7182
  const lineSize = clamp(rs.fontSize || baseSize, SIZE.min, SIZE.max);
7307
7183
  let size = lineSize, rise = 0;
7308
7184
  if (rs.script) {
@@ -7360,7 +7236,7 @@ function layoutRich(blocks, st, width, m, opt = {}) {
7360
7236
  if (full) return;
7361
7237
  if (lines.length >= RICH_DEFAULTS.maxLines || fragCount >= RICH_DEFAULTS.maxFragments) {
7362
7238
  full = true;
7363
- warn(`Rich text: more than ${RICH_DEFAULTS.maxLines} lines or ${RICH_DEFAULTS.maxFragments} styled pieces; the rest is cut.`);
7239
+ warn(`more than ${RICH_DEFAULTS.maxLines} lines or ${RICH_DEFAULTS.maxFragments} styled pieces; the rest is cut.`);
7364
7240
  return;
7365
7241
  }
7366
7242
  const frags = [];
@@ -9431,6 +9307,7 @@ register("field", {
9431
9307
  props: [
9432
9308
  { key: "kind", label: "Kind", type: "select", options: FIELD_KINDS, category: "Field" },
9433
9309
  { key: "fieldName", label: "Field name (unique; {Fields.x} allowed)", type: "expr", category: "Field" },
9310
+ { key: "label", label: "Label (the name a screen reader says)", type: "expr", category: "Field" },
9434
9311
  { key: "value", label: "Starting value", type: "expr", category: "Field" },
9435
9312
  { key: "options", label: "Choices (comma-separated or =expression)", type: "expr", category: "Field" },
9436
9313
  { key: "required", label: "Required", type: "bool", category: "Field" },
@@ -9445,6 +9322,8 @@ register("field", {
9445
9322
  const st = resolveStyle([lc.base, { border: "0.75 solid #9ca3af", padding: "2 3", backgroundColor: "#ffffff" }, lc.named?.[item.styleName], item.style], ctx);
9446
9323
  const kind = FIELD_KINDS.includes(item.kind) ? item.kind : "text";
9447
9324
  const name = String(evalSafe(item.fieldName || item.name || "field", ctx, lc, who).v || "field").replace(/[.\s]+/g, "_");
9325
+ const lab = item.label ?? item.tooltip;
9326
+ const label = lab == null || lab === "" ? "" : String(evalSafe(lab, ctx, lc, who).v ?? "");
9448
9327
  const raw = evalSafe(item.value ?? "", ctx, lc, who).v;
9449
9328
  let options = [];
9450
9329
  if (kind === "choice") {
@@ -9466,6 +9345,7 @@ register("field", {
9466
9345
  t: "field",
9467
9346
  kind,
9468
9347
  name,
9348
+ ...label ? { label } : {},
9469
9349
  value,
9470
9350
  options,
9471
9351
  required: !!item.required,
@@ -9915,7 +9795,9 @@ register("toc", {
9915
9795
  if (item.title) {
9916
9796
  const tst = { ...st, fontWeight: "bold", fontSize: (Number(st.fontSize) || 9) * 1.5, padding: [0, 0, 6, 0] };
9917
9797
  const tl = layoutTextLines(String(item.title), tst, item.w, lc.m);
9918
- units.push({ top: 0, height: tl.needed, items: paintBox({ x: item.x, y: 0, w: item.w, h: tl.needed, st: tst, tl, m: lc.m }), keepWithNext: true });
9798
+ const ti = paintBox({ x: item.x, y: 0, w: item.w, h: tl.needed, st: tst, tl, m: lc.m });
9799
+ for (const x of ti) if (x.t === "text") x.tocTitle = true;
9800
+ units.push({ top: 0, height: tl.needed, items: ti, keepWithNext: true });
9919
9801
  y += tl.needed;
9920
9802
  }
9921
9803
  const fromHeadings = item.source === "headings";
@@ -9939,10 +9821,10 @@ register("toc", {
9939
9821
  const lw = lc.m.width(label, key, size);
9940
9822
  const base = (lh - size) / 2 + ascent * 0.9;
9941
9823
  const items = [
9942
- { t: "text", font: key, size, color: st.color || "#111", lines: [{ x: item.x + indent, y: base, text: label, w: lw }] },
9824
+ { t: "text", font: key, size, color: st.color || "#111", lines: [{ x: item.x + indent, y: base, text: label, w: lw }], tocEntry: e.i },
9943
9825
  { t: "line", x1: item.x + indent + lw + 4, y1: base, x2: item.x + item.w - nw - 4, y2: base, stroke: "#9ca3af", strokeWidth: 0.6, dash: [0.6, 2.4], tocDots: e.i },
9944
- { t: "text", font: key, size, color: st.color || "#111", lines: [{ x: item.x + item.w - nw, y: base, text: num4, w: nw }], tocPage: e.i },
9945
- { t: "link", x: item.x, y: 0, w: item.w, h: lh, action: e.heading && typeof e.page === "number" ? { type: "bookmark", target: e.label, page: e.page, y: e.y } : { type: "bookmark", target: e.label }, tocLink: e.i }
9826
+ { t: "text", font: key, size, color: st.color || "#111", lines: [{ x: item.x + item.w - nw, y: base, text: num4, w: nw }], tocPage: e.i, tocEntry: e.i },
9827
+ { t: "link", x: item.x, y: 0, w: item.w, h: lh, action: e.heading && typeof e.page === "number" ? { type: "bookmark", target: e.label, page: e.page, y: e.y } : { type: "bookmark", target: e.label }, tocLink: e.i, tocEntry: e.i }
9946
9828
  ];
9947
9829
  units.push({ top: y, height: lh, items });
9948
9830
  y += lh;
@@ -11581,6 +11463,9 @@ var REPORT_SCHEMA = {
11581
11463
  "fieldName": {
11582
11464
  "x-ts": "Expr<string>"
11583
11465
  },
11466
+ "label": {
11467
+ "x-ts": "Expr<string>"
11468
+ },
11584
11469
  "value": {
11585
11470
  "x-ts": "Expr"
11586
11471
  },
@@ -12949,6 +12834,7 @@ var ownKeys = (type) => {
12949
12834
  function validate(def) {
12950
12835
  const errors = [], warnings = [], exprErrors = [], checkOnly = [];
12951
12836
  if (!def || typeof def !== "object") return { errors: ["The report definition is empty"], warnings, exprErrors, checkOnly };
12837
+ if (!def.$schema) warnings.push(`The report has no "$schema", so an editor cannot check it; add "$schema": "${SCHEMA_ID}" at the top. It renders as before.`);
12952
12838
  if (def.$schema && def.$schema !== SCHEMA_ID && def.$schema !== SCHEMA_ALIAS) warnings.push(`Unknown schema "${def.$schema}". Expected "${SCHEMA_ID}".`);
12953
12839
  for (const k of Object.keys(def)) if (!DEF_KEYS.has(k) && !k.startsWith("_")) {
12954
12840
  const g = didYouMean(k, DEF_KEYS);
@@ -12976,6 +12862,9 @@ function validate(def) {
12976
12862
  if (p?.required && p.nullable && !p.multi && (p.default == null || p.default === "")) {
12977
12863
  warnings.push(`Parameter "${p.name}" is required and nullable with no default: when no value is given it is null and the report can draw blank; give it a default, or drop "nullable" to require a value`);
12978
12864
  }
12865
+ if (p && !p.required && !p.multi && (p.nullable === false || p.allowBlank === false) && (p.default == null || p.default === "")) {
12866
+ warnings.push(`Parameter "${p.name}" is ${p.nullable === false ? "nullable: false" : "allowBlank: false"} but is not required and has no default, so when no value is given it is null; add "required": true or a default`);
12867
+ }
12979
12868
  if (p?.type === "date" && typeof p.default === "string" && p.default.trim() && !p.default.startsWith("=") && !toDate(p.default)) {
12980
12869
  warnings.push(`Parameter "${p.name}": its default "${p.default}" is not a date; write it as 2026-12-31 (year-month-day)`);
12981
12870
  }
@@ -13027,6 +12916,7 @@ function validate(def) {
13027
12916
  }
13028
12917
  };
13029
12918
  const lists = [];
12919
+ const fields = inlineFields(def), params = new Set((Array.isArray(def.parameters) ? def.parameters : []).map((p) => p?.name));
13030
12920
  for (const where of ["pageHeader", "body", "pageFooter"]) {
13031
12921
  const sec = def.sections?.[where];
13032
12922
  if (!sec) continue;
@@ -13043,6 +12933,7 @@ function validate(def) {
13043
12933
  warnings.push(`Unknown item type "${it.type}"`);
13044
12934
  continue;
13045
12935
  }
12936
+ walkItems(it, (x) => itemRefs(x, warnings, fields, params));
13046
12937
  if (it.name) {
13047
12938
  if (names.has(it.name)) warnings.push(`Two items have the name "${it.name}"`);
13048
12939
  names.add(it.name);
@@ -13056,7 +12947,12 @@ function validate(def) {
13056
12947
  warnings.push(`${label}: there is no named style "${it.styleName}"${g ? ` (did you mean "${g}"?)` : ""}; it is ignored`);
13057
12948
  }
13058
12949
  if (Array.isArray(it.sort)) sortIssues(it.sort, label, warnings);
12950
+ if (it.type === "chart" && it.legend != null && it.legend !== "bottom" && it.legend !== "none") {
12951
+ const shown = typeof it.legend === "object" ? `is an object (${JSON.stringify(it.legend).slice(0, 60)})` : `is ${JSON.stringify(it.legend)}`;
12952
+ warnings.push(`${label}: "legend" ${shown}; only "bottom" (show the legend) or "none" (hide it) is supported, and the legend is drawn at the bottom`);
12953
+ }
13059
12954
  if (it.type === "chart" && !NO_SERIES_CHARTS.has(it.chartType) && !(Array.isArray(it.series) && it.series.length)) warnings.push(`${label}: the chart has no series, so it plots a count of its rows; add one, e.g. series: [{ "name": "Value", "value": "=Sum(Fields.Amount)" }]`);
12955
+ if (it.type === "chart" && it.chartType === "polar" && it.xValue == null) warnings.push(`${label}: a polar chart needs xValue, the angle of each point in degrees (0 at the top, clockwise); without it no point is drawn`);
13060
12956
  if (it.type === "image" && typeof it.src === "string" && !it.src.startsWith("=") && LOCAL_IMAGE.test(it.src.trim())) warnings.push(`${label}: "${it.src}" is a local file path, which a browser cannot load; embed the image (images: { name: "data:image/png;base64,..." } and src: "embedded:name") or use an https:// address`);
13061
12957
  if (it.style != null && (typeof it.style !== "object" || Array.isArray(it.style))) warnings.push(`${label}: "style" must be an object of style properties, such as { "fontWeight": "bold" }; ${JSON.stringify(it.style).slice(0, 40)} is ignored`);
13062
12958
  const own = ownKeys(it.type);
@@ -13066,6 +12962,7 @@ function validate(def) {
13066
12962
  warnings.push(`${label}: unknown property "${k}"${g ? ` (did you mean "${g}"?)` : ""}; it is ignored`);
13067
12963
  }
13068
12964
  for (const [k, sch] of SCHEMA_ITEMS.get(it.type) || []) {
12965
+ if (it.type === "chart" && k === "legend") continue;
13069
12966
  const list = k === "type" ? null : offEnum(sch, it[k]);
13070
12967
  if (list) warnings.push(`${label}: "${k}" is ${JSON.stringify(it[k])}; expected one of ${list.map((x) => JSON.stringify(x)).join(", ")}`);
13071
12968
  }
@@ -13107,66 +13004,486 @@ function validate(def) {
13107
13004
  }
13108
13005
  }
13109
13006
  }
13110
- const custom = new Set((Array.isArray(def.functions) ? def.functions : []).map((f4) => String(f4?.name ?? "").toLowerCase()));
13111
- const callErr = (label, msg) => {
13112
- const e = `${label}: ${msg}`;
13113
- if (!errors.includes(e)) {
13114
- errors.push(e);
13115
- exprErrors.push(e);
13116
- checkOnly.push(e);
13007
+ const custom = new Set((Array.isArray(def.functions) ? def.functions : []).map((f4) => String(f4?.name ?? "").toLowerCase()));
13008
+ const callErr = (label, msg) => {
13009
+ const e = `${label}: ${msg}`;
13010
+ if (!errors.includes(e)) {
13011
+ errors.push(e);
13012
+ exprErrors.push(e);
13013
+ checkOnly.push(e);
13014
+ }
13015
+ };
13016
+ const asts = (v, key) => {
13017
+ if (typeof v !== "string" || !(v.startsWith("=") || TEMPLATE_KEYS.has(key) && v.includes("{"))) return [];
13018
+ try {
13019
+ const p = parseValue(v);
13020
+ return p.kind === "expr" ? [p.ast] : p.kind === "tpl" ? p.parts.filter((x) => typeof x === "object").map((x) => x.ast) : [];
13021
+ } catch {
13022
+ return [];
13023
+ }
13024
+ };
13025
+ const calls = (n, label, depth, key, items = false) => {
13026
+ if (depth > 64 || n == null) return;
13027
+ if (typeof n === "string") {
13028
+ for (const a of asts(n, key)) for (const m of callIssues(a, custom, didYouMean)) callErr(label, m);
13029
+ return;
13030
+ }
13031
+ if (Array.isArray(n)) {
13032
+ for (const x of n) calls(x, label, depth + 1, key, items);
13033
+ return;
13034
+ }
13035
+ if (typeof n === "object") for (const [k, v] of Object.entries(n)) calls(v, items && n.type && n.name ? n.name : label, depth + 1, k, items);
13036
+ };
13037
+ for (const [, items] of lists) for (const it of Array.isArray(items) ? items : []) {
13038
+ calls(it, it?.name || it?.type, 0, null, true);
13039
+ walkItems(it, (x) => {
13040
+ if (x.type === "barcode" && x.symbology != null) {
13041
+ const e = symbologyError(x.symbology);
13042
+ if (e) callErr(x.name || "Barcode", e);
13043
+ }
13044
+ });
13045
+ }
13046
+ const REGIONS = /* @__PURE__ */ new Set(["table", "list", "matrix", "chart", "subreport", "part"]);
13047
+ const bare = (n, out, depth = 0) => {
13048
+ if (!n || typeof n !== "object" || depth > 200) return;
13049
+ if (Array.isArray(n)) {
13050
+ for (const x of n) bare(x, out, depth + 1);
13051
+ return;
13052
+ }
13053
+ if (n.type === "call" && !n.obj && isAggregate(String(n.name).toLowerCase()) && n.args?.length === 1) out.add(n.name);
13054
+ for (const k in n) if (k !== "p" && n[k] && typeof n[k] === "object") bare(n[k], out, depth + 1);
13055
+ };
13056
+ const strs = (n, f4, key = null, depth = 0) => {
13057
+ if (depth > 64 || n == null) return;
13058
+ if (typeof n === "string") {
13059
+ f4(n, key);
13060
+ return;
13061
+ }
13062
+ if (Array.isArray(n)) {
13063
+ for (const x of n) strs(x, f4, key, depth + 1);
13064
+ return;
13065
+ }
13066
+ if (typeof n === "object") {
13067
+ for (const [k, v] of Object.entries(n)) if (k !== "items") strs(v, f4, k, depth + 1);
13068
+ }
13069
+ };
13070
+ const unscoped = (it) => {
13071
+ if (!it || typeof it !== "object" || it.dataSet || REGIONS.has(it.type)) return;
13072
+ const found = /* @__PURE__ */ new Set();
13073
+ strs(it, (v, key) => {
13074
+ for (const a of asts(v, key)) bare(a, found);
13075
+ });
13076
+ if (found.size) warnings.push(`${it.name || it.type}: ${[...found].join(", ")} is outside a table or list and names no scope, so it uses the first data set; name one as the last argument, e.g. ${[...found][0]}(Fields.x, "DataSetName"), or give the item a dataSet`);
13077
+ for (const c of Array.isArray(it.items) ? it.items : []) unscoped(c);
13078
+ };
13079
+ for (const [, items] of lists) for (const it of Array.isArray(items) ? items : []) unscoped(it);
13080
+ for (const d of def.dataSets || []) calls(d, `Data set "${d?.name}"`, 0, null);
13081
+ for (const p of def.parameters || []) calls(p, `Parameter "${p?.name}"`, 0, null);
13082
+ for (const f4 of Array.isArray(def.functions) ? def.functions : []) {
13083
+ if (typeof f4?.body !== "string") continue;
13084
+ try {
13085
+ for (const m of callIssues(compile(f4.body.replace(/^=/, "")), custom, didYouMean)) callErr(`Function "${f4.name}"`, m);
13086
+ } catch {
13087
+ }
13088
+ }
13089
+ return { errors, warnings, exprErrors, checkOnly };
13090
+ }
13091
+ var TEMPLATE_KEYS = /* @__PURE__ */ new Set(["value", "text", "label", "tooltip", "title", "noRowsText"]);
13092
+ function inlineFields(def) {
13093
+ const srcs = Array.isArray(def.dataSources) ? def.dataSources : [];
13094
+ if (!srcs.length || srcs.some((x) => x?.type !== "json" || !x.data || typeof x.data !== "object")) return null;
13095
+ const names = /* @__PURE__ */ new Set();
13096
+ for (const x of srcs) for (const v of Object.values(x.data)) for (const r of Array.isArray(v) ? v.slice(0, 200) : [v]) if (r && typeof r === "object" && !Array.isArray(r)) for (const k of Object.keys(r)) names.add(k);
13097
+ for (const d of Array.isArray(def.dataSets) ? def.dataSets : []) for (const f4 of Array.isArray(d?.fields) ? d.fields : []) if (f4?.name) names.add(f4.name);
13098
+ return names.size ? names : null;
13099
+ }
13100
+ function itemRefs(it, warnings, fields, params) {
13101
+ const label = it.name || it.type;
13102
+ if (!/line/i.test(String(it.type))) for (const [k, what] of [["w", "width"], ["h", "height"]]) {
13103
+ if (typeof it[k] === "number" && it[k] <= 0) warnings.push(`${label}: ${what} is ${it[k]}, so the item is not drawn; give it a positive ${what}`);
13104
+ }
13105
+ const seen = /* @__PURE__ */ new Set();
13106
+ const scan = (node, depth) => {
13107
+ if (depth > 64 || node == null) return;
13108
+ if (typeof node === "string") {
13109
+ if (!(node.startsWith("=") || node.includes("{"))) return;
13110
+ for (const m of node.matchAll(/\b(Fields|Parameters)\.([A-Za-z_]\w*)/g)) {
13111
+ const key = `${m[1]}.${m[2]}`;
13112
+ if (seen.has(key)) continue;
13113
+ seen.add(key);
13114
+ if (m[1] === "Parameters" && !params.has(m[2])) warnings.push(`${label}: The parameter "${m[2]}" does not exist; declare it under parameters, or check the spelling`);
13115
+ if (m[1] === "Fields" && fields && !fields.has(m[2])) warnings.push(`${label}: The field "${m[2]}" does not exist; check the spelling, or the data set that has it`);
13116
+ }
13117
+ return;
13118
+ }
13119
+ if (Array.isArray(node)) {
13120
+ for (const x of node) scan(x, depth + 1);
13121
+ return;
13122
+ }
13123
+ if (typeof node === "object") {
13124
+ for (const [k, v] of Object.entries(node)) if (k !== "items" && !(v && typeof v === "object" && v.type && v.name)) scan(v, depth + 1);
13125
+ }
13126
+ };
13127
+ scan(it, 0);
13128
+ }
13129
+ function walkItems(it, fn, depth = 0) {
13130
+ if (!it || typeof it !== "object" || depth > 64) return;
13131
+ if (it.type) fn(it);
13132
+ for (const v of Object.values(it)) {
13133
+ if (Array.isArray(v)) for (const x of v) {
13134
+ if (x && typeof x === "object") walkItems(x, fn, depth + 1);
13135
+ }
13136
+ else if (v && typeof v === "object") walkItems(v, fn, depth + 1);
13137
+ }
13138
+ }
13139
+
13140
+ // src/engine/reuse.js
13141
+ var REUSE_LIMITS = { depth: 4, reports: 50, items: 2e4, reportItems: 2e4, parts: 500, layers: 200, styleDepth: 10, repeats: 500 };
13142
+ var fmt = (n) => n.toLocaleString("en-US");
13143
+ function countItems(def, stop = Infinity) {
13144
+ let n = 0;
13145
+ eachItem(def, () => {
13146
+ n++;
13147
+ }, () => n > stop);
13148
+ return n;
13149
+ }
13150
+ var ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
13151
+ var isId = (v) => typeof v === "string" && ID.test(v);
13152
+ var isSubreportId = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(v);
13153
+ function makeLoader(load, cache = /* @__PURE__ */ new Map()) {
13154
+ return (id) => {
13155
+ if (!isId(id)) return Promise.reject(new Error(`"${String(id).slice(0, 80)}" is not a report id`));
13156
+ let p = cache.get(id);
13157
+ if (!p) {
13158
+ if (!load) return Promise.reject(new Error("this render has no report loader (render it on the server or in the viewer)"));
13159
+ if (cache.size >= REUSE_LIMITS.reports) return Promise.reject(new Error(`the report uses more than ${REUSE_LIMITS.reports} reports`));
13160
+ p = Promise.resolve().then(() => load(id)).then((d) => {
13161
+ if (!d || typeof d !== "object" || Array.isArray(d)) throw new Error("it is not a report");
13162
+ if (countItems(d, REUSE_LIMITS.reportItems) > REUSE_LIMITS.reportItems) throw new Error(`it has more than ${fmt(REUSE_LIMITS.reportItems)} items`);
13163
+ return d;
13164
+ });
13165
+ cache.set(id, p);
13166
+ }
13167
+ return p;
13168
+ };
13169
+ }
13170
+ function reuseRefs(def) {
13171
+ const out = /* @__PURE__ */ new Set();
13172
+ if (!def || typeof def !== "object") return out;
13173
+ for (const v of [def.master, def.styles?.sheet, def.theme]) if (isId(v)) out.add(v);
13174
+ eachItem(def, (it) => {
13175
+ if (it.type === "part" && isId(it.library)) out.add(it.library);
13176
+ });
13177
+ return out;
13178
+ }
13179
+ function drillRefs(def) {
13180
+ const out = [], seen = /* @__PURE__ */ new Set();
13181
+ const add2 = (report, where) => {
13182
+ if (isId(report) && !seen.has(`${report}|${where}`)) {
13183
+ seen.add(`${report}|${where}`);
13184
+ out.push({ report, where });
13185
+ }
13186
+ };
13187
+ const go = (n, where, depth) => {
13188
+ if (!n || typeof n !== "object" || depth > 64) return;
13189
+ if (Array.isArray(n)) {
13190
+ for (const x of n) go(x, where, depth + 1);
13191
+ return;
13192
+ }
13193
+ const here = n.type && n.name ? n.name : where;
13194
+ if (n.type === "subreport") add2(n.report, here);
13195
+ for (const [k, v] of Object.entries(n)) {
13196
+ if (["action", "pointAction", "totalAction"].includes(k) && v?.type === "report") add2(v.report, k === "action" && here === where && !n.type ? `${here} cell` : here);
13197
+ else if (v && typeof v === "object") go(v, here, depth + 1);
13198
+ }
13199
+ };
13200
+ for (const [k, s] of Object.entries(def?.sections || {})) go(s, k, 0);
13201
+ return out;
13202
+ }
13203
+ function eachItem(def, fn, stop = () => false) {
13204
+ const go = (list, depth) => {
13205
+ if (!Array.isArray(list) || depth > 64) return;
13206
+ for (const it of list) {
13207
+ if (stop()) return;
13208
+ if (!it || typeof it !== "object") continue;
13209
+ fn(it);
13210
+ go(it.items, depth + 1);
13211
+ }
13212
+ };
13213
+ for (const s of Object.values(def.sections || {})) for (const b of Array.isArray(s) ? s : [s]) go(b?.items, 0);
13214
+ }
13215
+ function findItemNamed(def, name) {
13216
+ let hit = null;
13217
+ eachItem(def, (it) => {
13218
+ if (!hit && it.name === name) hit = it;
13219
+ });
13220
+ return hit;
13221
+ }
13222
+ function byName2(a, b) {
13223
+ const al = Array.isArray(a) ? a : [], bl = Array.isArray(b) ? b : [];
13224
+ const names = new Set(bl.map((x) => x?.name));
13225
+ return [...al.filter((x) => !names.has(x?.name)), ...bl];
13226
+ }
13227
+ async function applyMaster(def, get, warn, chain = []) {
13228
+ if (def.master == null || def.master === "") return def;
13229
+ const { master: id, ...content } = def;
13230
+ if (chain.includes(id)) {
13231
+ warn(`Master "${id}" would include itself; it is left out.`);
13232
+ return content;
13233
+ }
13234
+ if (chain.length >= REUSE_LIMITS.depth) {
13235
+ warn(`Master reports are nested more than ${REUSE_LIMITS.depth} levels; "${id}" is left out.`);
13236
+ return content;
13237
+ }
13238
+ let m;
13239
+ try {
13240
+ m = await get(id);
13241
+ } catch (e) {
13242
+ warn(`The master report "${id}" did not load: ${e.message}`);
13243
+ return content;
13244
+ }
13245
+ m = await applyMaster(m, get, warn, [...chain, id]);
13246
+ return mergeMaster(content, m, warn);
13247
+ }
13248
+ function mergeMaster(c, m, warn = (_m) => {
13249
+ }) {
13250
+ const ms = m.sections || {}, cs = c.sections || {};
13251
+ const mBody = (Array.isArray(ms.body) ? ms.body[0] : ms.body) || {};
13252
+ const mItems = Array.isArray(mBody.items) ? mBody.items : [];
13253
+ const phs = mItems.filter((i) => i?.type === "placeholder");
13254
+ const ph = phs[0];
13255
+ const others = mItems.filter((i) => i && i.type !== "placeholder");
13256
+ const named = (Array.isArray(cs.content) ? cs.content : []).filter((x) => x && typeof x === "object" && !Array.isArray(x));
13257
+ const extra = phs.slice(1);
13258
+ for (const e of named) if (!extra.some((p) => p.name != null && p.name === e.placeholder)) warn(`The master has no other content placeholder "${String(e.placeholder ?? "").slice(0, 80)}"; its content is left out.`);
13259
+ const blank2 = { border: "none", backgroundColor: null, backgroundImage: null, padding: 0 };
13260
+ const boxOf = (p) => ({ x: Number(p.x) || 0, y: Number(p.y) || 0, w: Number(p.w) || 0, h: Number(p.h) || 0 });
13261
+ const below = others.length ? Math.max(...others.map((i) => (Number(i.y) || 0) + (Number(i.h) || 0))) + 6 : 0;
13262
+ const wrap = (b, k) => {
13263
+ const items = Array.isArray(b?.items) ? b.items : [];
13264
+ const box = ph ? boxOf(ph) : { x: 0, y: below, w: Math.max(0, ...items.map((i) => (Number(i.x) || 0) + (Number(i.w) || 0))), h: Math.max(0, ...items.map((i) => (Number(i.y) || 0) + (Number(i.h) || 0))) };
13265
+ const content = { type: "container", name: k ? `MasterContent${k + 1}` : "MasterContent", ...box, keepTogether: false, style: blank2, items };
13266
+ const mine = k ? others.map((i) => i.name ? { ...i, name: `${i.name}_${k + 1}` } : i) : others;
13267
+ const more = k ? [] : extra.map((p, j) => {
13268
+ const e = named.find((x) => p.name != null && x.placeholder === p.name);
13269
+ return { type: "container", name: `MasterContent_${String(p.name ?? j + 2).replace(/\W/g, "_").slice(0, 64)}`, ...boxOf(p), keepTogether: false, style: blank2, items: Array.isArray(e?.items) ? e.items : [] };
13270
+ });
13271
+ return { ...b || {}, items: [...mine, content, ...more] };
13272
+ };
13273
+ const body = Array.isArray(cs.body) ? cs.body.map(wrap) : wrap(cs.body, 0);
13274
+ const st = (x) => x.styles || {};
13275
+ return {
13276
+ ...c,
13277
+ $schema: c.$schema ?? m.$schema,
13278
+ locale: c.locale ?? m.locale,
13279
+ currency: c.currency ?? m.currency,
13280
+ nullPropagation: c.nullPropagation ?? m.nullPropagation,
13281
+ timeZone: c.timeZone ?? m.timeZone,
13282
+ caseSensitive: c.caseSensitive ?? m.caseSensitive,
13283
+ naturalSort: c.naturalSort ?? m.naturalSort,
13284
+ nullOrder: c.nullOrder ?? m.nullOrder,
13285
+ collation: c.collation ?? m.collation,
13286
+ arithmetic: c.arithmetic ?? m.arithmetic,
13287
+ bandRows: c.bandRows ?? m.bandRows,
13288
+ page: m.page || c.page,
13289
+ parameters: byName2(m.parameters, c.parameters),
13290
+ dataSources: byName2(m.dataSources, c.dataSources),
13291
+ // the content's data sets first: its unscoped aggregates and items use the first data set, not the master's (N3)
13292
+ dataSets: [...Array.isArray(c.dataSets) ? c.dataSets : [], ...byName2(m.dataSets, []).filter((x) => !(c.dataSets || []).some((y) => y?.name === x?.name))],
13293
+ functions: byName2(m.functions, c.functions),
13294
+ layers: byName2(m.layers, c.layers),
13295
+ images: { ...m.images || {}, ...c.images || {} },
13296
+ styles: { ...st(m), ...st(c), base: { ...st(m).base || {}, ...st(c).base || {} }, named: { ...st(m).named || {}, ...st(c).named || {} } },
13297
+ theme: c.theme ?? m.theme,
13298
+ themeDef: c.themeDef ?? m.themeDef,
13299
+ // the content's own page header and footer are replaced by the master's (its items are not in the report)
13300
+ sections: { ...cs, content: void 0, pageHeader: ms.pageHeader || { height: 0, items: [] }, pageFooter: ms.pageFooter || { height: 0, items: [] }, body }
13301
+ };
13302
+ }
13303
+ function flattenNamed(named, warn = (_m) => {
13304
+ }) {
13305
+ const out = {};
13306
+ if (!named || typeof named !== "object") return out;
13307
+ const get = (name, seen) => {
13308
+ if (out[name]) return out[name];
13309
+ const s = named[name];
13310
+ if (!s || typeof s !== "object") return null;
13311
+ const { parent, ...own } = s;
13312
+ let base = null;
13313
+ if (parent != null && parent !== "") {
13314
+ if (seen.includes(parent)) warn(`Named style "${name}": its parent "${parent}" leads back to itself; the chain is cut there.`);
13315
+ else if (seen.length >= REUSE_LIMITS.styleDepth) warn(`Named style "${name}": more than ${REUSE_LIMITS.styleDepth} parents; the chain is cut there.`);
13316
+ else if (!named[parent]) warn(`Named style "${name}": there is no named style "${parent}" to inherit from.`);
13317
+ else base = get(parent, [...seen, parent]);
13318
+ }
13319
+ return out[name] = base ? { ...base, ...own } : own;
13320
+ };
13321
+ for (const k of Object.keys(named)) get(k, [k]);
13322
+ return out;
13323
+ }
13324
+ async function applySheet(def, get, warn) {
13325
+ const id = def.styles?.sheet;
13326
+ if (id == null || id === "") return def;
13327
+ let sheet;
13328
+ try {
13329
+ sheet = await get(id);
13330
+ } catch (e) {
13331
+ warn(`The stylesheet "${id}" did not load: ${e.message}`);
13332
+ return def;
13333
+ }
13334
+ const s = sheet.styles || {};
13335
+ return { ...def, styles: { ...def.styles, base: { ...s.base || {}, ...def.styles.base || {} }, named: { ...s.named || {}, ...def.styles.named || {} } } };
13336
+ }
13337
+ async function loadParts(def, get, warn, budget) {
13338
+ const parts = /* @__PURE__ */ new Map();
13339
+ const libs = /* @__PURE__ */ new Map();
13340
+ const keyOf2 = (it) => `${it.library}\0${it.part}`;
13341
+ const resolve = async (lib, name, chain) => {
13342
+ const key = `${lib}\0${name}`;
13343
+ if (parts.has(key)) return;
13344
+ if (chain.length >= REUSE_LIMITS.depth) {
13345
+ parts.set(key, { error: `parts are nested more than ${REUSE_LIMITS.depth} levels` });
13346
+ return;
13117
13347
  }
13118
- };
13119
- const asts = (v, key) => {
13120
- if (typeof v !== "string" || !(v.startsWith("=") || TEMPLATE_KEYS.has(key) && v.includes("{"))) return [];
13348
+ let d;
13121
13349
  try {
13122
- const p = parseValue(v);
13123
- return p.kind === "expr" ? [p.ast] : p.kind === "tpl" ? p.parts.filter((x) => typeof x === "object").map((x) => x.ast) : [];
13124
- } catch {
13125
- return [];
13126
- }
13127
- };
13128
- const calls = (n, label, depth, key, items = false) => {
13129
- if (depth > 64 || n == null) return;
13130
- if (typeof n === "string") {
13131
- for (const a of asts(n, key)) for (const m of callIssues(a, custom, didYouMean)) callErr(label, m);
13350
+ d = await get(lib);
13351
+ } catch (e) {
13352
+ parts.set(key, { error: `the library "${lib}" did not load: ${e.message}` });
13132
13353
  return;
13133
13354
  }
13134
- if (Array.isArray(n)) {
13135
- for (const x of n) calls(x, label, depth + 1, key, items);
13355
+ const p = (Array.isArray(d.parts) ? d.parts.slice(0, REUSE_LIMITS.parts) : []).find((x) => x?.name === name);
13356
+ const src = p && findItemNamed(d, p.item);
13357
+ if (!p || !src) {
13358
+ parts.set(key, { error: `the library "${lib}" has no part "${name}"` });
13136
13359
  return;
13137
13360
  }
13138
- if (typeof n === "object") for (const [k, v] of Object.entries(n)) calls(v, items && n.type && n.name ? n.name : label, depth + 1, k, items);
13139
- };
13140
- for (const [, items] of lists) for (const it of Array.isArray(items) ? items : []) {
13141
- calls(it, it?.name || it?.type, 0, null, true);
13142
- walkItems(it, (x) => {
13143
- if (x.type === "barcode" && x.symbology != null) {
13144
- const e = symbologyError(x.symbology);
13145
- if (e) callErr(x.name || "Barcode", e);
13361
+ const item = structuredClone(src);
13362
+ const props = {};
13363
+ for (const q of Array.isArray(p.properties) ? p.properties : []) if (q?.name) props[q.name] = q.default ?? null;
13364
+ const inner = [];
13365
+ let own = 0;
13366
+ const walk = (list) => {
13367
+ for (const x of list || []) {
13368
+ if (!x || typeof x !== "object") continue;
13369
+ own++;
13370
+ if (x.type === "part") inner.push(x);
13371
+ walk(x.items);
13146
13372
  }
13147
- });
13148
- }
13149
- for (const d of def.dataSets || []) calls(d, `Data set "${d?.name}"`, 0, null);
13150
- for (const p of def.parameters || []) calls(p, `Parameter "${p?.name}"`, 0, null);
13151
- for (const f4 of Array.isArray(def.functions) ? def.functions : []) {
13152
- if (typeof f4?.body !== "string") continue;
13153
- try {
13154
- for (const m of callIssues(compile(f4.body.replace(/^=/, "")), custom, didYouMean)) callErr(`Function "${f4.name}"`, m);
13155
- } catch {
13373
+ };
13374
+ walk([item]);
13375
+ parts.set(key, { item, props, own, inner: inner.filter((x) => x !== item) });
13376
+ libs.set(lib, d);
13377
+ for (const x of inner) {
13378
+ if (x === item) continue;
13379
+ const k2 = keyOf2(x);
13380
+ if (k2 === key || chain.includes(k2)) {
13381
+ x.__cycle = true;
13382
+ warn(`Part "${name}" of "${lib}" would include itself; that inner part is left out.`);
13383
+ } else await resolve(x.library, x.part, [...chain, key]);
13384
+ }
13385
+ };
13386
+ const used = [];
13387
+ eachItem(def, (it) => {
13388
+ if (it.type === "part") used.push(it);
13389
+ });
13390
+ for (const it of used) await resolve(String(it.library ?? ""), String(it.part ?? ""), []);
13391
+ const size = /* @__PURE__ */ new Map();
13392
+ const sizeOf = (key) => {
13393
+ if (size.has(key)) return size.get(key);
13394
+ size.set(key, 1);
13395
+ const e = parts.get(key);
13396
+ let n = 1;
13397
+ if (e && !e.error) {
13398
+ n = e.own;
13399
+ for (const x of e.inner) if (!x.__cycle) n += sizeOf(`${x.library}\0${x.part}`);
13156
13400
  }
13401
+ size.set(key, n);
13402
+ return n;
13403
+ };
13404
+ let total = 0;
13405
+ for (const it of used) total += sizeOf(`${String(it.library ?? "")}\0${String(it.part ?? "")}`);
13406
+ budget.items += total;
13407
+ if (budget.items > REUSE_LIMITS.items) {
13408
+ const msg = `the report parts would draw more than ${fmt(REUSE_LIMITS.items)} items (with subreports); they are left out`;
13409
+ for (const k of parts.keys()) parts.set(k, { error: msg });
13410
+ warn(`Report parts: ${msg}.`);
13157
13411
  }
13158
- return { errors, warnings, exprErrors, checkOnly };
13412
+ return { parts, libs: [...libs.values()] };
13159
13413
  }
13160
- var TEMPLATE_KEYS = /* @__PURE__ */ new Set(["value", "text", "label", "tooltip", "title", "noRowsText"]);
13161
- function walkItems(it, fn, depth = 0) {
13162
- if (!it || typeof it !== "object" || depth > 64) return;
13163
- if (it.type) fn(it);
13164
- for (const v of Object.values(it)) {
13165
- if (Array.isArray(v)) for (const x of v) {
13166
- if (x && typeof x === "object") walkItems(x, fn, depth + 1);
13414
+ function mergeLibraries(def, libs) {
13415
+ let d = def;
13416
+ for (const l of libs) {
13417
+ d = {
13418
+ ...d,
13419
+ dataSources: byName2(l.dataSources, d.dataSources),
13420
+ dataSets: byName2(l.dataSets, d.dataSets),
13421
+ parameters: byName2(l.parameters, d.parameters),
13422
+ functions: byName2(l.functions, d.functions),
13423
+ images: { ...l.images || {}, ...d.images || {} },
13424
+ styles: { ...d.styles || {}, named: { ...l.styles?.named || {}, ...d.styles?.named || {} } }
13425
+ };
13426
+ }
13427
+ return d;
13428
+ }
13429
+ async function resolveReuse(def, get, warn, chain = [], budget = { items: 0 }) {
13430
+ let d = await applyMaster(def, get, warn, chain);
13431
+ d = await applySheet(d, get, warn);
13432
+ if (Array.isArray(d.layers) && d.layers.length > REUSE_LIMITS.layers) {
13433
+ warn(`The report has more than ${REUSE_LIMITS.layers} layers; the others are ignored.`);
13434
+ d = { ...d, layers: d.layers.slice(0, REUSE_LIMITS.layers) };
13435
+ }
13436
+ let parts = null;
13437
+ let hasPart = false;
13438
+ eachItem(d, (it) => {
13439
+ if (it.type === "part") hasPart = true;
13440
+ });
13441
+ if (hasPart) {
13442
+ const r = await loadParts(d, get, warn, budget);
13443
+ parts = r.parts;
13444
+ d = mergeLibraries(d, r.libs);
13445
+ }
13446
+ return { def: d, parts };
13447
+ }
13448
+ var DEFAULT_THEME = {
13449
+ colors: { Dark1: "#000000", Light1: "#FFFFFF", Dark2: "#44546A", Light2: "#E7E6E6", Accent1: "#4472C4", Accent2: "#ED7D31", Accent3: "#A5A5A5", Accent4: "#FFC000", Accent5: "#5B9BD5", Accent6: "#70AD47", Hyperlink: "#0563C1" },
13450
+ fonts: { major: { family: "Inter", size: 14, weight: "bold", style: "normal" }, minor: { family: "Inter", size: 9, weight: "normal", style: "normal" } },
13451
+ constants: {}
13452
+ };
13453
+ var THEME_COLORS = Object.keys(DEFAULT_THEME.colors);
13454
+ function themeContext(td) {
13455
+ const t = td && typeof td === "object" ? td : {};
13456
+ const font = (k) => {
13457
+ const f4 = { ...DEFAULT_THEME.fonts[k], ...t.fonts?.[k] && typeof t.fonts[k] === "object" ? t.fonts[k] : {} };
13458
+ return { Family: f4.family, Size: f4.size, Weight: f4.weight, Style: f4.style };
13459
+ };
13460
+ const consts = t.constants && typeof t.constants === "object" && !Array.isArray(t.constants) ? t.constants : {};
13461
+ return { Colors: { ...DEFAULT_THEME.colors, ...t.colors && typeof t.colors === "object" ? t.colors : {} }, Fonts: { MajorFont: font("major"), MinorFont: font("minor") }, Constants: { ...consts } };
13462
+ }
13463
+ async function resolveTheme(def, ctx, get, warn) {
13464
+ let td = def.themeDef;
13465
+ const src = def.theme;
13466
+ if (typeof src === "string" && src.trim()) {
13467
+ let id = src;
13468
+ if (src.startsWith("=")) {
13469
+ try {
13470
+ id = evalValue(src, ctx);
13471
+ } catch (e) {
13472
+ warn(`Theme: ${e.message}`);
13473
+ id = null;
13474
+ }
13475
+ }
13476
+ if (id != null && id !== "") {
13477
+ try {
13478
+ const t = await get(String(id));
13479
+ if (t.themeDef && typeof t.themeDef === "object") td = t.themeDef;
13480
+ else warn(`The report "${id}" has no theme (set its colours and fonts under Report → Theme).`);
13481
+ } catch (e) {
13482
+ warn(`The theme "${id}" did not load: ${e.message}`);
13483
+ }
13167
13484
  }
13168
- else if (v && typeof v === "object") walkItems(v, fn, depth + 1);
13169
13485
  }
13486
+ return themeContext(td);
13170
13487
  }
13171
13488
 
13172
13489
  // src/engine/index.js
@@ -13200,7 +13517,6 @@ function* textsOf(def, dataSets) {
13200
13517
  }
13201
13518
  }
13202
13519
  }
13203
- var NO_CJK_FONTS = "Text in Japanese/Chinese/Korean needs the optional CJK fonts: npm i @reportwright/fonts-cjk, then npx reportwright-fonts-cjk (npm); in the app, npm run fonts:cjk";
13204
13520
  async function render(def0, opts = {}) {
13205
13521
  const t0 = Date.now();
13206
13522
  const reuseWarnings = [];
@@ -13231,11 +13547,15 @@ async function render(def0, opts = {}) {
13231
13547
  timeZone = void 0;
13232
13548
  }
13233
13549
  const params = resolveParameters(def, opts.parameters || {}, { now: opts.now, timeZone, locale: def.locale });
13550
+ const unknownFonts = /* @__PURE__ */ new Set();
13234
13551
  const store = opts.fontStore || new FontStore(
13235
13552
  /** @type {any} */
13236
13553
  opts.loadFont
13237
13554
  );
13238
- await store.load([.../* @__PURE__ */ new Set([CORE_FONT_KEY, ...PRELOAD_KEYS, ...fontsForDef(def)])]);
13555
+ const defFonts = fontsForDef(def, unknownFonts);
13556
+ const standardOnly = defFonts.length > 0 && defFonts.every(isStandard);
13557
+ await store.load([.../* @__PURE__ */ new Set([...standardOnly ? [] : [CORE_FONT_KEY, ...opts.lazyFonts ? [] : PRELOAD_KEYS], ...defFonts])]);
13558
+ if (!store.fonts.size) await store.load([CORE_FONT_KEY]);
13239
13559
  let m = new TextMeasurer(store);
13240
13560
  const deadline = opts.deadline ?? (Number(opts.timeoutMs) > 0 ? t0 + Number(opts.timeoutMs) : null);
13241
13561
  const clock = deadline ? { until: deadline, n: 0, over: false } : null;
@@ -13583,11 +13903,19 @@ async function render(def0, opts = {}) {
13583
13903
  overTime();
13584
13904
  if (opts.stats) opts.stats.layoutPasses = layoutPasses;
13585
13905
  for (const k of store.failed) if (k !== SHAPER && !OPTIONAL_KEYS.includes(k)) warn(`The font "${k}" did not load, so its text is drawn in a bundled face instead.`);
13906
+ for (const f4 of [...unknownFonts, ...m.unknownFonts]) warn(`Font "${f4}" is not available; using ${familyOf(f4).family}.`);
13907
+ const lacking = /* @__PURE__ */ new Map();
13908
+ for (const [k, chars] of m.substituted) {
13909
+ const f4 = fontCss(k).family, l = lacking.get(f4) || { sub: fontCss(embeddedFace(k)).family, chars: /* @__PURE__ */ new Set() };
13910
+ for (const c of chars) l.chars.add(c);
13911
+ lacking.set(f4, l);
13912
+ }
13913
+ for (const [f4, l] of lacking) warn(`Font "${f4}" can't show "${[...l.chars].slice(0, 20).join("")}"; used ${l.sub} for them.`);
13586
13914
  if (m.unshaped) warn("The text shaper (harfbuzz.wasm) did not load, so Arabic, Hebrew, Indic and Thai text is not shaped (letters unjoined, marks misplaced). A font loader must map each key with fontFile(key).");
13587
13915
  if (m.noGlyph.size) {
13588
- const cjk = [...m.noGlyph].some((c) => /[\u1100-\u11ff\u2600-\u27bf\u2b00-\u2bff\u3000-\u9fff\uac00-\ud7af\uf900-\ufaff\uff00-\uffef]|[\u{1f000}-\u{1faff}]/u.test(c));
13916
+ const cjk2 = [...m.noGlyph].some((c) => /[\u1100-\u11ff\u2600-\u27bf\u2b00-\u2bff\u3000-\u9fff\uac00-\ud7af\uf900-\ufaff\uff00-\uffef]|[\u{1f000}-\u{1faff}]/u.test(c));
13589
13917
  const list = [...m.noGlyph].slice(0, 20).join(" ");
13590
- warn(cjk ? `${NO_CJK_FONTS}. Until then these characters show as boxes: ${list}.` : `No font has these characters, so they show as boxes: ${list}. Upload a font that has them and use it for this text.`);
13918
+ warn(cjk2 ? `${NO_CJK_FONTS}. Until then these characters show as boxes: ${list}.` : `No font has these characters, so they show as boxes: ${list}. Upload a font that has them and use it for this text.`);
13591
13919
  }
13592
13920
  if (opts.bodyOnly) return { units: result.body.units, tables: result.body.tables, height: result.body.height, regions, warnings, clipped: clipped() };
13593
13921
  if (opts.dataOnly) return {
@@ -13814,15 +14142,9 @@ function resolveParameters(def, given, o = {}) {
13814
14142
  }
13815
14143
 
13816
14144
  export {
13817
- setFetchPolicy,
13818
- policyKeysIn,
13819
- fetchPolicy,
13820
- reportFetch,
13821
- PAGE_SIZES,
13822
- toPt,
13823
- fromPt,
13824
- resolvePage,
13825
- normBox,
14145
+ FORMAT_CHAR,
14146
+ TextMeasurer,
14147
+ finishText,
13826
14148
  isDynamic,
13827
14149
  evalValue,
13828
14150
  toText,
@@ -13864,9 +14186,11 @@ export {
13864
14186
  DEFAULT_THEME,
13865
14187
  THEME_COLORS,
13866
14188
  themeContext,
13867
- NO_CJK_FONTS,
14189
+ ReportError,
14190
+ BAND_ITEMS,
13868
14191
  render,
14192
+ baseContext,
13869
14193
  parameterOptions,
13870
14194
  resolveParameters
13871
14195
  };
13872
- //# sourceMappingURL=chunk-VNOWPSE6.js.map
14196
+ //# sourceMappingURL=chunk-EK5CJY22.js.map