@reportwright/viewer 0.12.0 → 0.12.1

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 (83) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/README.md +27 -5
  3. package/dist/{chunk-KJTRZJHN.js → chunk-2BPBQMH2.js} +4 -3
  4. package/dist/chunk-2BPBQMH2.js.map +6 -0
  5. package/dist/{chunk-ODREGGKA.js → chunk-3D2KFKG2.js} +5 -4
  6. package/dist/chunk-3D2KFKG2.js.map +6 -0
  7. package/dist/chunk-6TPZ7G74.js +494 -0
  8. package/dist/chunk-6TPZ7G74.js.map +6 -0
  9. package/dist/chunk-BRB34AJ2.js +16 -0
  10. package/dist/{chunk-APRPSQOD.js → chunk-DPVJEFKP.js} +28 -9
  11. package/dist/chunk-DPVJEFKP.js.map +6 -0
  12. package/dist/{chunk-RYHJUQSV.js → chunk-DXHLIJEY.js} +2 -2
  13. package/dist/{chunk-SR2TQ2ZK.js → chunk-F6RMRXIN.js} +2 -2
  14. package/dist/chunk-F6RMRXIN.js.map +6 -0
  15. package/dist/{chunk-3D43XGIN.js → chunk-M6AYND5G.js} +8 -638
  16. package/dist/chunk-M6AYND5G.js.map +6 -0
  17. package/dist/{chunk-EOVGKHAS.js → chunk-NR2G4TMG.js} +59 -51
  18. package/dist/chunk-NR2G4TMG.js.map +6 -0
  19. package/dist/{chunk-5N6OFCHJ.js → chunk-PGP3QA6L.js} +22 -10
  20. package/dist/chunk-PGP3QA6L.js.map +6 -0
  21. package/dist/{chunk-FARG6X2U.js → chunk-Q6MMPKQA.js} +14 -9
  22. package/dist/chunk-Q6MMPKQA.js.map +6 -0
  23. package/dist/{chunk-SBD55ZES.js → chunk-S2IUUI6N.js} +76 -32
  24. package/dist/chunk-S2IUUI6N.js.map +6 -0
  25. package/dist/chunk-ULKEEARQ.js +553 -0
  26. package/dist/chunk-ULKEEARQ.js.map +6 -0
  27. package/dist/{chunk-M6GENFAH.js → chunk-VNOWPSE6.js} +527 -1461
  28. package/dist/chunk-VNOWPSE6.js.map +6 -0
  29. package/dist/chunk-Z7MKH66Z.js +642 -0
  30. package/dist/chunk-Z7MKH66Z.js.map +6 -0
  31. package/dist/designer.d.ts +10 -3
  32. package/dist/designer.js +41 -31
  33. package/dist/designer.js.map +2 -2
  34. package/dist/engine.worker.js +71 -0
  35. package/dist/engine.worker.js.map +6 -0
  36. package/dist/peer/chunk-2FZTYEDE.js +15 -0
  37. package/dist/peer/{chunk-NPYKZINU.js → chunk-6I2EAAVN.js} +39 -31
  38. package/dist/peer/chunk-6I2EAAVN.js.map +6 -0
  39. package/dist/peer/{chunk-IPU2YRL2.js → chunk-BMAXIBP2.js} +28 -9
  40. package/dist/peer/chunk-BMAXIBP2.js.map +6 -0
  41. package/dist/peer/chunk-BQFI3DHK.js +494 -0
  42. package/dist/peer/chunk-BQFI3DHK.js.map +6 -0
  43. package/dist/peer/{chunk-76ZABVTI.js → chunk-R4VX5UC7.js} +4 -3
  44. package/dist/peer/{chunk-KBT6ZGGO.js → chunk-TSC4RAH3.js} +22 -10
  45. package/dist/peer/chunk-TSC4RAH3.js.map +6 -0
  46. package/dist/peer/{chunk-M4AGFUWE.js → chunk-V42KN7HD.js} +2 -2
  47. package/dist/peer/{chunk-Y5CAHLXS.js → chunk-WQGENQZO.js} +5 -4
  48. package/dist/peer/{chunk-MCJPN2KL.js → chunk-WSNRD2AQ.js} +14 -9
  49. package/dist/peer/chunk-WSNRD2AQ.js.map +6 -0
  50. package/dist/peer/designer.js +41 -31
  51. package/dist/peer/designer.js.map +2 -2
  52. package/dist/peer/engine.worker.js +70 -0
  53. package/dist/peer/engine.worker.js.map +6 -0
  54. package/dist/peer/viewer.js +12 -5
  55. package/dist/peer/viewer.js.map +1 -1
  56. package/dist/viewer.d.ts +9 -1
  57. package/dist/viewer.js +12 -5
  58. package/dist/viewer.js.map +1 -1
  59. package/package.json +1 -1
  60. package/dist/chunk-3D43XGIN.js.map +0 -6
  61. package/dist/chunk-5N6OFCHJ.js.map +0 -6
  62. package/dist/chunk-AH47FS6Y.js +0 -13
  63. package/dist/chunk-APRPSQOD.js.map +0 -6
  64. package/dist/chunk-EOVGKHAS.js.map +0 -6
  65. package/dist/chunk-FARG6X2U.js.map +0 -6
  66. package/dist/chunk-M6GENFAH.js.map +0 -6
  67. package/dist/chunk-SBD55ZES.js.map +0 -6
  68. package/dist/chunk-SR2TQ2ZK.js.map +0 -6
  69. package/dist/peer/chunk-76ZABVTI.js.map +0 -6
  70. package/dist/peer/chunk-GBITRNHE.js +0 -14806
  71. package/dist/peer/chunk-GBITRNHE.js.map +0 -6
  72. package/dist/peer/chunk-IMQRHDR4.js +0 -12
  73. package/dist/peer/chunk-IPU2YRL2.js.map +0 -6
  74. package/dist/peer/chunk-KBT6ZGGO.js.map +0 -6
  75. package/dist/peer/chunk-MCJPN2KL.js.map +0 -6
  76. package/dist/peer/chunk-NPYKZINU.js.map +0 -6
  77. package/dist/peer/chunk-Y5CAHLXS.js.map +0 -6
  78. /package/dist/{chunk-AH47FS6Y.js.map → chunk-BRB34AJ2.js.map} +0 -0
  79. /package/dist/{chunk-RYHJUQSV.js.map → chunk-DXHLIJEY.js.map} +0 -0
  80. /package/dist/peer/{chunk-IMQRHDR4.js.map → chunk-2FZTYEDE.js.map} +0 -0
  81. /package/dist/{chunk-KJTRZJHN.js.map → peer/chunk-R4VX5UC7.js.map} +0 -0
  82. /package/dist/peer/{chunk-M4AGFUWE.js.map → chunk-V42KN7HD.js.map} +0 -0
  83. /package/dist/{chunk-ODREGGKA.js.map → peer/chunk-WQGENQZO.js.map} +0 -0
@@ -1,3 +1,10 @@
1
+ import {
2
+ TextMeasurer,
3
+ finishText
4
+ } from "./chunk-ULKEEARQ.js";
5
+ import {
6
+ safeUrl
7
+ } from "./chunk-72S6DETS.js";
1
8
  import {
2
9
  ExprError,
3
10
  capText,
@@ -19,115 +26,392 @@ import {
19
26
  validTimeZone
20
27
  } from "./chunk-GJS242RR.js";
21
28
  import {
22
- safeUrl
23
- } from "./chunk-72S6DETS.js";
24
- import {
25
- ALL_FONT_KEYS,
26
29
  CORE_FONT_KEY,
27
- DEFAULT_FAMILY,
28
30
  FontStore,
29
31
  OPTIONAL_KEYS,
30
32
  PRELOAD_KEYS,
31
33
  PX,
32
34
  SHAPER,
33
- fallbackKey,
34
- fontCss,
35
- fontFamilyCss,
36
- fontFile,
37
- fontKeysOf,
38
35
  fontsFor,
39
36
  fontsForDef,
40
37
  imageInfo,
41
38
  resolveFontKey,
42
- resolveImageSrc,
43
- setCustomFonts
44
- } from "./chunk-3D43XGIN.js";
39
+ resolveImageSrc
40
+ } from "./chunk-Z7MKH66Z.js";
45
41
 
46
- // src/viewer/dataFetch.js
47
- function sqlCall(body, sqlPreview) {
48
- let b;
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;
49
106
  try {
50
- b = JSON.parse(String(body));
107
+ u = new URL(input);
51
108
  } catch {
52
- return false;
109
+ throw Object.assign(new Error(`Not a URL: ${String(input).slice(0, 200)}`), { status: 400 });
53
110
  }
54
- if (!b || typeof b !== "object" || Array.isArray(b)) return false;
55
- const keys = Object.keys(b);
56
- if (typeof b.report === "string" && typeof b.source === "string") return keys.every((k) => ["report", "source", "params"].includes(k));
57
- return sqlPreview && typeof b.query === "string" && keys.every((k) => ["connection", "query", "params"].includes(k));
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 {
120
+ }
121
+ if (bad) throw refuse(host);
122
+ return u;
58
123
  }
59
- var DATA = [/^\/api\/sample\//, /^\/api\/data\/proxy$/];
60
- var OURS = /^\/(?:$|api(?:\/|$)|admin(?:\/|$)|designer(?:\/|$)|viewer(?:\/|$)|login(?:\/|$)|embed(?:\/|$)|_next(?:\/|$))/i;
61
- var BUSY = /* @__PURE__ */ new Set([429, 502, 503, 504]);
62
- function withRetry(f4, { onRetry, retries = 3, wait = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
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
+ });
166
+ }
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);
173
+ }
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 };
176
+ }
177
+ function guardedFetch(opt, base) {
178
+ const allow = (opt.allowHosts || []).map((h) => String(h).toLowerCase().replace(/^\[|\]$/g, ""));
179
+ const node = !opt.viaBase && inNode();
63
180
  return (
64
181
  /** @type {any} */
65
- (async (url, init = {}) => {
66
- const method = String(init.method || "GET").toUpperCase();
67
- const read = method === "GET" || method === "HEAD" || method === "POST" && /\/api\/data\/sql$/.test(new URL(String(url), "http://x").pathname);
68
- for (let attempt = 1; ; attempt++) {
69
- const res = await f4(url, init);
70
- if (!read || !BUSY.has(res.status) || attempt > retries) return res;
71
- const after = Number(res.headers?.get?.("retry-after"));
72
- const ms = Number.isFinite(after) && after >= 0 ? Math.min(1e4, after * 1e3) : Math.round(400 * 2 ** (attempt - 1) * (0.75 + Math.random() * 0.5));
73
- onRetry?.({ attempt, max: retries, status: res.status });
74
- await wait(ms);
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;
75
197
  }
76
198
  })
77
199
  );
78
200
  }
79
- function reportFetch(base, f0 = (...a) => fetch(...a), { sqlPreview = false, onRetry, retries = 3, wait } = {}) {
80
- const self = new URL(base).origin;
81
- const f4 = withRetry(f0, { onRetry, retries, wait });
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;
82
204
  return (
83
205
  /** @type {any} */
84
206
  (async (input, init = {}) => {
85
- const u = new URL(String(input instanceof Request ? input.url : input), base);
86
- const method = String(init.method || "GET").toUpperCase();
87
- if (u.origin !== self) return f4(u.href, { ...init, credentials: "omit" });
88
- let path;
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
+ });
89
215
  try {
90
- path = decodeURIComponent(u.pathname).replace(/\/{2,}/g, "/");
91
- } catch {
92
- path = null;
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);
93
220
  }
94
- const read = path !== null && ["GET", "HEAD"].includes(method) && !/%2f|%5c/i.test(u.pathname) && (DATA.some((r) => r.test(path)) || !OURS.test(path));
95
- if (!read && !(method === "POST" && u.pathname === "/api/data/sql" && sqlCall(init.body, sqlPreview))) {
96
- 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 });
221
+ if (!res.body || /** @type {any} */
222
+ init.pwStream) {
223
+ clearTimeout(timer);
224
+ return res;
97
225
  }
98
- const ours = DATA.some((r) => r.test(u.pathname)) || u.pathname === "/api/data/sql";
99
- return f4(u.href, { ...init, credentials: ours ? "same-origin" : "omit", redirect: "error" });
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);
249
+ }
250
+ },
251
+ cancel(r) {
252
+ clearTimeout(timer);
253
+ return reader.cancel(r);
254
+ }
255
+ });
256
+ return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
100
257
  })
101
258
  );
102
259
  }
103
-
104
- // src/engine/expr/evaluate.js
105
- var ROOTS = { fields: "Fields", parameters: "Parameters", globals: "Globals", parent: "Parent", theme: "Theme", partproperties: "PartProperties" };
106
- var astCache = /* @__PURE__ */ new Map();
107
- function compile(src) {
108
- let a = astCache.get(src);
109
- if (!a) {
110
- a = parse(src);
111
- if (astCache.size > 5e3) astCache.clear();
112
- astCache.set(src, a);
113
- }
114
- return a;
115
- }
116
- var tplCache = /* @__PURE__ */ new Map();
117
- function parseValue(v) {
118
- if (typeof v !== "string") return { kind: "lit", v };
119
- let p = tplCache.get(v);
120
- if (p) {
121
- if (p.kind === "err") throw p.error;
122
- return p;
123
- }
124
- try {
125
- p = parseFresh(v);
126
- } catch (e) {
127
- if (!(e instanceof ExprError)) throw e;
128
- p = { kind: "err", error: e };
129
- }
130
- if (tplCache.size > 5e3) tplCache.clear();
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;
311
+ }
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;
375
+ }
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 });
379
+ }
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;
386
+ }
387
+
388
+ // src/engine/expr/evaluate.js
389
+ var ROOTS = { fields: "Fields", parameters: "Parameters", globals: "Globals", parent: "Parent", theme: "Theme", partproperties: "PartProperties" };
390
+ var astCache = /* @__PURE__ */ new Map();
391
+ function compile(src) {
392
+ let a = astCache.get(src);
393
+ if (!a) {
394
+ a = parse(src);
395
+ if (astCache.size > 5e3) astCache.clear();
396
+ astCache.set(src, a);
397
+ }
398
+ return a;
399
+ }
400
+ var tplCache = /* @__PURE__ */ new Map();
401
+ function parseValue(v) {
402
+ if (typeof v !== "string") return { kind: "lit", v };
403
+ let p = tplCache.get(v);
404
+ if (p) {
405
+ if (p.kind === "err") throw p.error;
406
+ return p;
407
+ }
408
+ try {
409
+ p = parseFresh(v);
410
+ } catch (e) {
411
+ if (!(e instanceof ExprError)) throw e;
412
+ p = { kind: "err", error: e };
413
+ }
414
+ if (tplCache.size > 5e3) tplCache.clear();
131
415
  tplCache.set(v, p);
132
416
  if (p.kind === "err") throw p.error;
133
417
  return p;
@@ -1722,334 +2006,86 @@ function checkFunctions(list) {
1722
2006
  errors.push(`${label}: ${e.message}`);
1723
2007
  }
1724
2008
  }
1725
- return errors;
1726
- }
1727
- var METHODS = {
1728
- padleft: "padleft",
1729
- padright: "padright",
1730
- substring: "substring",
1731
- toupper: "ucase",
1732
- toupperinvariant: "ucase",
1733
- tolower: "lcase",
1734
- tolowerinvariant: "lcase",
1735
- trim: "trim",
1736
- trimstart: "trimstart",
1737
- trimend: "trimend",
1738
- startswith: "startswith",
1739
- endswith: "endswith",
1740
- indexof: "indexof",
1741
- lastindexof: "lastindexof",
1742
- contains: "contains",
1743
- replace: "replace",
1744
- split: "split",
1745
- tostring: "tostring"
1746
- };
1747
- function netFormat(f4, args, ctx) {
1748
- let out = "";
1749
- for (let i = 0; i < f4.length; i++) {
1750
- const c = f4[i];
1751
- if (c === "{" && f4[i + 1] === "{") {
1752
- out += "{";
1753
- i++;
1754
- continue;
1755
- }
1756
- if (c === "}" && f4[i + 1] === "}") {
1757
- out += "}";
1758
- i++;
1759
- continue;
1760
- }
1761
- if (c === "}") throw new ExprError('String.Format: a "}" without its "{"');
1762
- if (c !== "{") {
1763
- out += c;
1764
- continue;
1765
- }
1766
- const e = f4.indexOf("}", i);
1767
- if (e < 0) throw new ExprError('String.Format: a "{" without its "}"');
1768
- const m = /^\s*(\d{1,3})\s*(?:,\s*(-?\d{1,4}))?\s*(?::([^}]*))?$/.exec(f4.slice(i + 1, e));
1769
- if (!m) throw new ExprError(`String.Format: "{${f4.slice(i + 1, e).slice(0, 20)}}" is not a placeholder`);
1770
- const k = Number(m[1]);
1771
- if (k >= args.length) throw new ExprError(`String.Format: {${k}} has no argument`);
1772
- let t = toText(args[k], m[3] || null, ctx);
1773
- const w = m[2] ? Number(m[2]) : 0;
1774
- if (Math.abs(w) > t.length) t = w < 0 ? t.padEnd(-w) : t.padStart(w);
1775
- capText("String.Format", out.length + t.length + f4.length - e);
1776
- out += t;
1777
- i = e;
1778
- }
1779
- return out;
1780
- }
1781
- function callCustom(n, name, ctx) {
1782
- if (n.obj && n.obj.type === "id" && n.obj.name.toLowerCase() === "string" && name === "format") {
1783
- if (!n.args.length) throw new ExprError("String.Format needs a format", n.p);
1784
- const [f5, ...rest] = n.args.map((a) => evaluate(a, ctx));
1785
- return netFormat(S(f5), rest, ctx);
1786
- }
1787
- const viaValue = n.obj && !(n.obj.type === "id" && n.obj.name.toLowerCase() === "code");
1788
- if (viaValue && Object.hasOwn(METHODS, name)) {
1789
- return FUNCTIONS[METHODS[name]](ctx, evaluate(n.obj, ctx), ...n.args.map((a) => evaluate(a, ctx)));
1790
- }
1791
- if (viaValue && n.name in Object.prototype) throw new ExprError(`The value has no member ${n.name}`, n.p);
1792
- const viaCode = !!n.obj;
1793
- if (viaCode && !(n.obj.type === "id" && n.obj.name.toLowerCase() === "code")) throw new ExprError(`"${n.name}(…)" after "." is not supported; report functions are called as Code.${n.name}(…)`, n.p);
1794
- const table = ctx.functions;
1795
- const f4 = table?.byName.get(name);
1796
- if (!f4 || !viaCode && builtin(name)) throw new ExprError(viaCode ? `The report has no function "${n.name}"` : `Unknown function "${n.name}"`, n.p);
1797
- const params = Array.isArray(f4.params) ? f4.params : [];
1798
- if (n.args.length !== params.length) throw new ExprError(`${f4.name} needs ${params.length} argument${params.length === 1 ? "" : "s"}`, n.p);
1799
- const depth = (ctx.fnDepth || 0) + 1;
1800
- if (depth > MAX_FN_DEPTH) throw new ExprError(`${f4.name}: functions call each other more than ${MAX_FN_DEPTH} deep`, n.p);
1801
- if (++table.count.n > MAX_FN_CALLS) throw new ExprError(`Report functions were called more than 1,000,000 times in this render`, n.p);
1802
- const locals = /* @__PURE__ */ Object.create(null);
1803
- for (let i = 0; i < params.length; i++) locals[String(params[i]).toLowerCase()] = evaluate(n.args[i], ctx);
1804
- return evaluate(compile(String(f4.body).replace(/^=/, "")), Object.assign({}, ctx, { locals, fnDepth: depth }));
1805
- }
1806
-
1807
- // src/engine/data/guard.js
1808
- var FETCH_DEFAULTS = { timeoutMs: 3e4, maxBytes: 64 * 1048576, hops: 5 };
1809
- var V4 = [
1810
- ["0.0.0.0", 8],
1811
- ["10.0.0.0", 8],
1812
- ["100.64.0.0", 10],
1813
- ["127.0.0.0", 8],
1814
- ["169.254.0.0", 16],
1815
- ["172.16.0.0", 12],
1816
- ["192.0.0.0", 24],
1817
- ["192.0.2.0", 24],
1818
- ["192.168.0.0", 16],
1819
- ["198.18.0.0", 15],
1820
- ["198.51.100.0", 24],
1821
- ["203.0.113.0", 24],
1822
- ["224.0.0.0", 3]
1823
- ].map(([a, p]) => [v4num(String(a)), Number(p)]);
1824
- function v4num(ip) {
1825
- return ip.split(".").reduce((n, x) => n * 256 + Number(x), 0);
1826
- }
1827
- function privateV4(ip) {
1828
- const n = v4num(ip);
1829
- return V4.some(([a, p]) => Math.floor(n / 2 ** (32 - p)) === Math.floor(a / 2 ** (32 - p)));
1830
- }
1831
- function groups6(ip) {
1832
- const [h, t = ""] = ip.split("::");
1833
- const a = h ? h.split(":") : [], b = t ? t.split(":") : [];
1834
- if (b.length && b[b.length - 1].includes(".")) {
1835
- const q = b.pop().split(".").map(Number);
1836
- b.push((q[0] << 8 | q[1]).toString(16), (q[2] << 8 | q[3]).toString(16));
1837
- }
1838
- return [...a, ...Array(Math.max(0, 8 - a.length - b.length)).fill("0"), ...b].map((x) => parseInt(x, 16) || 0);
1839
- }
1840
- function privateV6(ip) {
1841
- const g = groups6(ip);
1842
- const v4 = () => `${g[6] >> 8}.${g[6] & 255}.${g[7] >> 8}.${g[7] & 255}`;
1843
- if (g.slice(0, 6).every((x) => x === 0)) return true;
1844
- if (g.slice(0, 5).every((x) => x === 0) && g[5] === 65535) return privateV4(v4());
1845
- if (g[0] === 100 && g[1] === 65435) return g[2] === 1 || privateV4(v4());
1846
- if (g[0] === 8194 || g[0] === 8193 && g[1] === 0) return true;
1847
- if ((g[0] & 65024) === 64512 || (g[0] & 65472) === 65152 || (g[0] & 65472) === 65216 || (g[0] & 65280) === 65280) return true;
1848
- return false;
1849
- }
1850
- function privateHost(host) {
1851
- const h = host.replace(/^\[|\]$/g, "").toLowerCase();
1852
- if (h.includes(":")) return privateV6(h);
1853
- if (/^\d+\.\d+\.\d+\.\d+$/.test(h)) return privateV4(h);
1854
- return h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local") || h.endsWith(".internal");
1855
- }
1856
- 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" });
1857
- function nodeMod(name) {
1858
- const p = (
1859
- /** @type {any} */
1860
- globalThis.process
1861
- );
1862
- if (!p?.versions?.node) return null;
1863
- 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 });
1864
- return p.getBuiltinModule(name);
1865
- }
1866
- var inNode = () => !!/** @type {any} */
1867
- globalThis.process?.versions?.node;
1868
- var addrHost = (a) => a.family === 6 || String(a.address).includes(":") ? `[${a.address}]` : String(a.address);
1869
- function checkUrl(input, allow) {
1870
- let u;
1871
- try {
1872
- u = new URL(input);
1873
- } catch {
1874
- throw Object.assign(new Error(`Not a URL: ${String(input).slice(0, 200)}`), { status: 400 });
1875
- }
1876
- 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 });
1877
- if (u.username || u.password) throw Object.assign(new Error("A user name or password in a data URL is not allowed"), { status: 400 });
1878
- const host = u.hostname.replace(/^\[|\]$/g, "").toLowerCase();
1879
- if (!host) throw Object.assign(new Error("A data URL needs a host"), { status: 400 });
1880
- if (allow.includes(host)) return u;
1881
- let bad = true;
1882
- try {
1883
- bad = privateHost(u.hostname);
1884
- } catch {
1885
- }
1886
- if (bad) throw refuse(host);
1887
- return u;
1888
- }
1889
- function nodeRequest(url, init, trusted) {
1890
- const http = nodeMod("node:http"), https = nodeMod("node:https"), dns = nodeMod("node:dns"), stream = nodeMod("node:stream");
1891
- const lookup = (name, o, cb) => dns.lookup(name, { ...o, all: true }, (err, list) => {
1892
- if (err || !Array.isArray(list) || !list.length) return cb(err || refuse(name));
1893
- let bad = true;
1894
- try {
1895
- bad = !trusted && list.some((a) => privateHost(addrHost(a)));
1896
- } catch {
1897
- }
1898
- if (bad) return cb(refuse(name));
1899
- return o.all ? cb(null, list) : cb(null, list[0].address, list[0].family);
1900
- });
1901
- const headers = Object.fromEntries(new Headers(init.headers || {}));
1902
- headers["accept-encoding"] = "identity";
1903
- return new Promise((resolve, reject) => {
1904
- const req = (url.protocol === "https:" ? https : http).request(url, { method: init.method || "GET", headers, lookup, signal: init.signal }, (res) => {
1905
- const pairs = [];
1906
- for (let i = 0; i < res.rawHeaders.length; i += 2) pairs.push([res.rawHeaders[i], res.rawHeaders[i + 1]]);
1907
- const none = [101, 204, 205, 304].includes(res.statusCode) || (init.method || "GET").toUpperCase() === "HEAD";
1908
- if (none) res.resume();
1909
- resolve(new Response(none ? null : stream.Readable.toWeb(res), { status: res.statusCode, statusText: res.statusMessage, headers: (
1910
- /** @type {any} */
1911
- pairs
1912
- ) }));
1913
- });
1914
- req.on("error", reject);
1915
- req.end(init.body ?? void 0);
1916
- });
1917
- }
1918
- var CROSS_ORIGIN_KEEP = /* @__PURE__ */ new Set(["accept", "accept-language", "content-type"]);
1919
- function redirectRequest(from, to, req, status) {
1920
- if (from.protocol === "https:" && to.protocol !== "https:") throw Object.assign(new Error(`${from.host} redirects from https to http; not followed`), { status: 403 });
1921
- const headers = new Headers(req.headers || {});
1922
- if (to.origin !== from.origin) {
1923
- for (const k of [...headers.keys()]) if (!CROSS_ORIGIN_KEEP.has(k)) headers.delete(k);
1924
- }
1925
- const get = status === 303 || (status === 301 || status === 302) && (req.method || "GET").toUpperCase() !== "GET";
1926
- return get ? { ...req, method: "GET", body: void 0, headers } : { ...req, headers };
1927
- }
1928
- function guardedFetch(opt, base) {
1929
- const allow = (opt.allowHosts || []).map((h) => String(h).toLowerCase().replace(/^\[|\]$/g, ""));
1930
- const node = inNode();
1931
- return (
1932
- /** @type {any} */
1933
- (async (input, init = {}) => {
1934
- let url = checkUrl(String(input), allow);
1935
- let req = { ...init };
1936
- for (let hop = 0; ; hop++) {
1937
- const trusted = allow.includes(url.hostname.replace(/^\[|\]$/g, "").toLowerCase());
1938
- const res = node ? await nodeRequest(url, req, trusted) : await base(url.href, { ...req, redirect: "manual" });
1939
- if (res.type === "opaqueredirect") throw new Error(`${url.host} redirects; a redirect cannot be checked here (pass your own fetch to render)`);
1940
- const loc = res.status >= 300 && res.status < 400 ? res.headers.get("location") : null;
1941
- if (!loc) return res;
1942
- await res.body?.cancel().catch(() => {
1943
- });
1944
- if (hop >= FETCH_DEFAULTS.hops) throw new Error(`Too many redirects from ${url.host}`);
1945
- const next = checkUrl(new URL(loc, url).href, allow);
1946
- req = redirectRequest(url, next, req, res.status);
1947
- url = next;
1948
- }
1949
- })
1950
- );
1951
- }
1952
- function boundedFetch(f4, opt) {
1953
- const ms0 = Number(opt.fetchTimeoutMs) > 0 ? Number(opt.fetchTimeoutMs) : FETCH_DEFAULTS.timeoutMs;
1954
- const max = Number(opt.maxFetchBytes) > 0 ? Number(opt.maxFetchBytes) : FETCH_DEFAULTS.maxBytes;
1955
- return (
1956
- /** @type {any} */
1957
- (async (input, init = {}) => {
1958
- const ms = Math.max(1, Math.min(ms0, opt.deadline ? opt.deadline - Date.now() : Infinity));
1959
- const ctl = new AbortController();
1960
- const timer = setTimeout(() => ctl.abort(Object.assign(new Error(`The data request took longer than ${Math.round(ms)} ms`), { status: 504 })), ms);
1961
- const signal = init.signal ? anySignal([init.signal, ctl.signal]) : ctl.signal;
1962
- let res;
1963
- const stop = new Promise((_, rej) => signal.addEventListener("abort", () => rej(signal.reason), { once: true }));
1964
- stop.catch(() => {
1965
- });
1966
- try {
1967
- res = await Promise.race([f4(input, { ...init, signal }), stop]);
1968
- } catch (e) {
1969
- clearTimeout(timer);
1970
- throw ctl.signal.aborted ? ctl.signal.reason : fetchFailure(e, input);
1971
- }
1972
- if (!res.body || /** @type {any} */
1973
- init.pwStream) {
1974
- clearTimeout(timer);
1975
- return res;
1976
- }
1977
- let n = 0;
1978
- const reader = res.body.getReader();
1979
- const body = new ReadableStream({
1980
- async pull(c) {
1981
- try {
1982
- const { done, value } = await reader.read();
1983
- if (done) {
1984
- clearTimeout(timer);
1985
- c.close();
1986
- return;
1987
- }
1988
- n += value.byteLength;
1989
- if (n > max) {
1990
- clearTimeout(timer);
1991
- reader.cancel().catch(() => {
1992
- });
1993
- c.error(Object.assign(new Error(`The data response is larger than ${Math.round(max / 1048576)} MB (maxFetchBytes)`), { status: 413 }));
1994
- return;
1995
- }
1996
- c.enqueue(value);
1997
- } catch (e) {
1998
- clearTimeout(timer);
1999
- c.error(ctl.signal.aborted ? ctl.signal.reason : e);
2000
- }
2001
- },
2002
- cancel(r) {
2003
- clearTimeout(timer);
2004
- return reader.cancel(r);
2005
- }
2006
- });
2007
- return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
2008
- })
2009
- );
2010
- }
2011
- var CAUSES = {
2012
- ENOTFOUND: "the host name did not resolve",
2013
- EAI_AGAIN: "DNS lookup timed out",
2014
- ECONNREFUSED: "connection refused",
2015
- ECONNRESET: "connection reset",
2016
- EPIPE: "connection closed",
2017
- UND_ERR_SOCKET: "connection closed early",
2018
- ETIMEDOUT: "timed out",
2019
- UND_ERR_CONNECT_TIMEOUT: "connect timed out",
2020
- EPROTO: "TLS error",
2021
- ERR_TLS_CERT_ALTNAME_INVALID: "TLS certificate does not match the host",
2022
- DEPTH_ZERO_SELF_SIGNED_CERT: "TLS certificate is self-signed",
2023
- SELF_SIGNED_CERT_IN_CHAIN: "TLS certificate chain is untrusted",
2024
- UNABLE_TO_VERIFY_LEAF_SIGNATURE: "TLS certificate chain is untrusted",
2025
- CERT_HAS_EXPIRED: "TLS certificate has expired"
2026
- };
2027
- function redactUrl(s) {
2028
- const str = String(s);
2029
- const q = str.search(/[?#]/);
2030
- const base = (q >= 0 ? str.slice(0, q) : str).replace(/^([a-z][\w+.-]*:\/\/)[^/?#]*@/i, "$1");
2031
- return q >= 0 ? `${base}${str[q]}…` : base;
2009
+ return errors;
2032
2010
  }
2033
- function fetchFailure(e, input) {
2034
- if (!e || e.status) return e;
2035
- const code = String(e.cause?.code || e.code || "");
2036
- if (!code && e.message !== "fetch failed") return e;
2037
- const why = CAUSES[code] || (/^ERR_TLS|CERT_|SSL/.test(code) ? "TLS error" : "");
2038
- const at = redactUrl(input?.href ?? input);
2039
- return Object.assign(new Error(`the request to ${at} failed${why ? `: ${why}` : ""}${code ? ` (${code})` : ""}`), { code: e.code });
2011
+ var METHODS = {
2012
+ padleft: "padleft",
2013
+ padright: "padright",
2014
+ substring: "substring",
2015
+ toupper: "ucase",
2016
+ toupperinvariant: "ucase",
2017
+ tolower: "lcase",
2018
+ tolowerinvariant: "lcase",
2019
+ trim: "trim",
2020
+ trimstart: "trimstart",
2021
+ trimend: "trimend",
2022
+ startswith: "startswith",
2023
+ endswith: "endswith",
2024
+ indexof: "indexof",
2025
+ lastindexof: "lastindexof",
2026
+ contains: "contains",
2027
+ replace: "replace",
2028
+ split: "split",
2029
+ tostring: "tostring"
2030
+ };
2031
+ function netFormat(f4, args, ctx) {
2032
+ let out = "";
2033
+ for (let i = 0; i < f4.length; i++) {
2034
+ const c = f4[i];
2035
+ if (c === "{" && f4[i + 1] === "{") {
2036
+ out += "{";
2037
+ i++;
2038
+ continue;
2039
+ }
2040
+ if (c === "}" && f4[i + 1] === "}") {
2041
+ out += "}";
2042
+ i++;
2043
+ continue;
2044
+ }
2045
+ if (c === "}") throw new ExprError('String.Format: a "}" without its "{"');
2046
+ if (c !== "{") {
2047
+ out += c;
2048
+ continue;
2049
+ }
2050
+ const e = f4.indexOf("}", i);
2051
+ if (e < 0) throw new ExprError('String.Format: a "{" without its "}"');
2052
+ const m = /^\s*(\d{1,3})\s*(?:,\s*(-?\d{1,4}))?\s*(?::([^}]*))?$/.exec(f4.slice(i + 1, e));
2053
+ if (!m) throw new ExprError(`String.Format: "{${f4.slice(i + 1, e).slice(0, 20)}}" is not a placeholder`);
2054
+ const k = Number(m[1]);
2055
+ if (k >= args.length) throw new ExprError(`String.Format: {${k}} has no argument`);
2056
+ let t = toText(args[k], m[3] || null, ctx);
2057
+ const w = m[2] ? Number(m[2]) : 0;
2058
+ if (Math.abs(w) > t.length) t = w < 0 ? t.padEnd(-w) : t.padStart(w);
2059
+ capText("String.Format", out.length + t.length + f4.length - e);
2060
+ out += t;
2061
+ i = e;
2062
+ }
2063
+ return out;
2040
2064
  }
2041
- function anySignal(list) {
2042
- const S3 = (
2043
- /** @type {any} */
2044
- AbortSignal
2045
- );
2046
- if (S3.any) return S3.any(list);
2047
- const c = new AbortController();
2048
- for (const s of list) {
2049
- if (s.aborted) c.abort(s.reason);
2050
- else s.addEventListener("abort", () => c.abort(s.reason), { once: true });
2065
+ function callCustom(n, name, ctx) {
2066
+ if (n.obj && n.obj.type === "id" && n.obj.name.toLowerCase() === "string" && name === "format") {
2067
+ if (!n.args.length) throw new ExprError("String.Format needs a format", n.p);
2068
+ const [f5, ...rest] = n.args.map((a) => evaluate(a, ctx));
2069
+ return netFormat(S(f5), rest, ctx);
2051
2070
  }
2052
- return c.signal;
2071
+ const viaValue = n.obj && !(n.obj.type === "id" && n.obj.name.toLowerCase() === "code");
2072
+ if (viaValue && Object.hasOwn(METHODS, name)) {
2073
+ return FUNCTIONS[METHODS[name]](ctx, evaluate(n.obj, ctx), ...n.args.map((a) => evaluate(a, ctx)));
2074
+ }
2075
+ if (viaValue && n.name in Object.prototype) throw new ExprError(`The value has no member ${n.name}`, n.p);
2076
+ const viaCode = !!n.obj;
2077
+ if (viaCode && !(n.obj.type === "id" && n.obj.name.toLowerCase() === "code")) throw new ExprError(`"${n.name}(…)" after "." is not supported; report functions are called as Code.${n.name}(…)`, n.p);
2078
+ const table = ctx.functions;
2079
+ const f4 = table?.byName.get(name);
2080
+ if (!f4 || !viaCode && builtin(name)) throw new ExprError(viaCode ? `The report has no function "${n.name}"` : `Unknown function "${n.name}"`, n.p);
2081
+ const params = Array.isArray(f4.params) ? f4.params : [];
2082
+ if (n.args.length !== params.length) throw new ExprError(`${f4.name} needs ${params.length} argument${params.length === 1 ? "" : "s"}`, n.p);
2083
+ const depth = (ctx.fnDepth || 0) + 1;
2084
+ if (depth > MAX_FN_DEPTH) throw new ExprError(`${f4.name}: functions call each other more than ${MAX_FN_DEPTH} deep`, n.p);
2085
+ if (++table.count.n > MAX_FN_CALLS) throw new ExprError(`Report functions were called more than 1,000,000 times in this render`, n.p);
2086
+ const locals = /* @__PURE__ */ Object.create(null);
2087
+ for (let i = 0; i < params.length; i++) locals[String(params[i]).toLowerCase()] = evaluate(n.args[i], ctx);
2088
+ return evaluate(compile(String(f4.body).replace(/^=/, "")), Object.assign({}, ctx, { locals, fnDepth: depth }));
2053
2089
  }
2054
2090
 
2055
2091
  // src/importers/xml.js
@@ -2850,7 +2886,10 @@ function fetcherFor(opt) {
2850
2886
  return boundedFetch(f4, opt);
2851
2887
  }
2852
2888
  function checkHostSource(src, url, opt) {
2853
- if (opt.fetch || opt.unsafeFetch) return;
2889
+ const rule = opt.fetch ? opt.fetch.hostRule : opt.unsafeFetch ? null : opt;
2890
+ if (!rule) return;
2891
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(String(url).trim());
2892
+ if (scheme && !/^https?$/i.test(scheme[1])) throw Object.assign(new Error(`Only http and https URLs can be fetched, not ${scheme[1].toLowerCase()}:`), { status: 400 });
2854
2893
  const t = String(src.url ?? "").trim();
2855
2894
  if (/^\/(?![/\\])/.test(t)) return;
2856
2895
  const m = /^https?:\/\/([^/?#\\]*)/i.exec(t);
@@ -2861,7 +2900,7 @@ function checkHostSource(src, url, opt) {
2861
2900
  host = new URL(url).hostname.replace(/^\[|\]$/g, "").toLowerCase();
2862
2901
  } catch {
2863
2902
  }
2864
- if (whole && host && (opt.allowHosts || []).map((h) => String(h).toLowerCase()).includes(host)) return;
2903
+ if (whole && host && (rule.allowHosts || []).map((h) => String(h).toLowerCase()).includes(host)) return;
2865
2904
  throw Object.assign(new Error("its host is made by a parameter or an expression: write the scheme and host out (parameters may fill the path and query), or make the whole URL a parameter and list its host in allowHosts"), { status: 403 });
2866
2905
  }
2867
2906
  async function loadSources(def, opt) {
@@ -3142,18 +3181,18 @@ function buildDataSets(def, sources, baseCtx) {
3142
3181
  });
3143
3182
  if (calc.length) {
3144
3183
  const c = { ...baseCtx, dataSets: { ...baseCtx.dataSets || {}, ...sets }, fields: null };
3145
- const failed = /* @__PURE__ */ new Set();
3184
+ const failed2 = /* @__PURE__ */ new Set();
3146
3185
  for (const r of rows) {
3147
3186
  c.fields = r;
3148
3187
  for (const fd of calc) {
3149
- if (failed.has(fd.name)) {
3188
+ if (failed2.has(fd.name)) {
3150
3189
  r[fd.name] = null;
3151
3190
  continue;
3152
3191
  }
3153
3192
  try {
3154
3193
  r[fd.name] = coerce(evalValue(fd.expr, c), fd.type, opts.get(fd));
3155
3194
  } catch (e) {
3156
- failed.add(fd.name);
3195
+ failed2.add(fd.name);
3157
3196
  r[fd.name] = null;
3158
3197
  baseCtx.warn?.(`Data set "${ds.name}", field "${fd.name}": ${e.message}`);
3159
3198
  }
@@ -3701,543 +3740,6 @@ function normBox(v) {
3701
3740
  return [a[0], a[1], a[2], a[3]];
3702
3741
  }
3703
3742
 
3704
- // src/engine/text/measure.js
3705
- import bidiFactory from "bidi-js";
3706
- var bidi = bidiFactory();
3707
- function special(cp) {
3708
- if (cp < 1424) return false;
3709
- if (cp <= 7423) return true;
3710
- if (cp >= 8192 && cp <= 8303) return cp >= 8203 && cp <= 8207 || cp >= 8234 && cp <= 8238 || cp >= 8288;
3711
- 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;
3712
- }
3713
- var cjk = (cp) => cp >= 11904 && cp <= 40959 || cp >= 44032 && cp <= 55215 || cp >= 63744 && cp <= 64255 || cp >= 65280 && cp <= 65519 || cp >= 131072 && cp <= 262143;
3714
- var NO_START = new Set("、。,.・:;?!ー)」』】〕〉》〗〙〟’”}⦆ぁぃぅぇぉっゃゅょゎゕゖァィゥェォッャュョヮヵヶ々〻‐゠–〜~…‥,.!?:;)]}%ゝゞヽヾ゛゜ㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ。」、・ー%]⦆");
3715
- var NO_END = new Set("(「『【〔〈《〖〘〝‘“{⦅([{[「$¥£");
3716
- var MAYBE = /[^\u0000-\u058f]/;
3717
- var RTL_OR_CONTROLS = /[֐-ࣿיִ-﷿ﹰ-﻾‎‏‪-‮⁦-⁩]|[\uD802\uD803\uD83A]/;
3718
- var SCRIPTS = [
3719
- "Latin",
3720
- "Greek",
3721
- "Cyrillic",
3722
- "Armenian",
3723
- "Hebrew",
3724
- "Arabic",
3725
- "Syriac",
3726
- "Thaana",
3727
- "Devanagari",
3728
- "Bengali",
3729
- "Gurmukhi",
3730
- "Gujarati",
3731
- "Oriya",
3732
- "Tamil",
3733
- "Telugu",
3734
- "Kannada",
3735
- "Malayalam",
3736
- "Sinhala",
3737
- "Thai",
3738
- "Lao",
3739
- "Tibetan",
3740
- "Myanmar",
3741
- "Georgian",
3742
- "Hangul",
3743
- "Ethiopic",
3744
- "Khmer",
3745
- "Mongolian",
3746
- "Han",
3747
- "Hiragana",
3748
- "Katakana",
3749
- "Bopomofo"
3750
- ].map((s) => (
3751
- /** @type {[string, RegExp]} */
3752
- [s, new RegExp(`\\p{Script=${s}}`, "u")]
3753
- ));
3754
- var SIMPLE = /* @__PURE__ */ new Set(["Latin", "Greek", "Cyrillic", "Armenian", "Georgian", "Hangul", "Han", "Hiragana", "Katakana", "Bopomofo", "Common"]);
3755
- var STICKY = /[\p{M}\p{Cf}]/u;
3756
- var SPACE = /\s/u;
3757
- var scriptCache = /* @__PURE__ */ new Map();
3758
- function scriptOf(cp) {
3759
- let s = scriptCache.get(cp);
3760
- if (s === void 0) {
3761
- const ch = String.fromCodePoint(cp);
3762
- s = "Common";
3763
- for (const [name, re] of SCRIPTS) if (re.test(ch)) {
3764
- s = name;
3765
- break;
3766
- }
3767
- if (scriptCache.size >= 2e4) scriptCache.clear();
3768
- scriptCache.set(cp, s);
3769
- }
3770
- return s;
3771
- }
3772
- var INTL = typeof Intl !== "undefined" && typeof Intl.Segmenter === "function";
3773
- var GRAPHEMES = INTL ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
3774
- var JOINS = /[\p{M}\u200c\u200d\ufe00-\ufe0f]/u;
3775
- function clusters(s, intl = true) {
3776
- if (intl && GRAPHEMES) return [...GRAPHEMES.segment(s)].map((x) => x.segment);
3777
- const out = [];
3778
- for (const ch of s) {
3779
- if (out.length && (JOINS.test(ch) || out[out.length - 1].endsWith("‍"))) out[out.length - 1] += ch;
3780
- else out.push(ch);
3781
- }
3782
- return out;
3783
- }
3784
- var graphemes = (s) => clusters(s);
3785
- var SA = /[\u0e00-\u0eff\u1000-\u109f\u1780-\u17ff\u19e0-\u19ff\u1a20-\u1aaf\ua9e0-\ua9ff\uaa60-\uaadf]/;
3786
- var WORDS = INTL ? new Intl.Segmenter(void 0, { granularity: "word" }) : null;
3787
- function saBreaks(text, segmenter = WORDS) {
3788
- const out = /* @__PURE__ */ new Set();
3789
- if (!segmenter) return out;
3790
- for (const { index } of segmenter.segment(text)) {
3791
- if (index > 0 && (SA.test(text[index]) || SA.test(text[index - 1]))) out.add(index);
3792
- }
3793
- return out;
3794
- }
3795
- var TextMeasurer = class {
3796
- /** @param {import('./fonts.js').FontStore} store */
3797
- constructor(store) {
3798
- this.store = store;
3799
- this.faces = /* @__PURE__ */ new Map();
3800
- this.boxes = /* @__PURE__ */ new WeakMap();
3801
- this.boxCount = 0;
3802
- this.looks = /* @__PURE__ */ new WeakMap();
3803
- this.cover = /* @__PURE__ */ new Map();
3804
- this.cache = /* @__PURE__ */ new Map();
3805
- this.missing = /* @__PURE__ */ new Set();
3806
- this.noGlyph = /* @__PURE__ */ new Set();
3807
- }
3808
- /**
3809
- * The key to measure with: `key` when loaded; else (not loaded yet, or failed) the bundled face of the same weight
3810
- * and style when that is loaded, else the core regular face. A key not yet loaded is asked for in the next pass.
3811
- */
3812
- usable(key) {
3813
- if (this.store.has(key)) return key;
3814
- if (!this.store.failed.has(key)) this.missing.add(key);
3815
- const c = fontCss(key);
3816
- const same2 = resolveFontKey(DEFAULT_FAMILY, c.weight, c.style);
3817
- return this.store.has(same2) ? same2 : CORE_FONT_KEY;
3818
- }
3819
- /**
3820
- * A loaded face's numbers, read once: fontkit's getters (unitsPerEm, ascent…) decode tables on every read.
3821
- * adv: advances per code point (-1 = not on the fast path); words: fast-path units per string (a table repeats them).
3822
- * @param {string} key a loaded key
3823
- */
3824
- face(key) {
3825
- let f4 = this.faces.get(key);
3826
- if (!f4) {
3827
- const font = this.store.get(key);
3828
- f4 = { font, upm: font.unitsPerEm, ascent: font.ascent, descent: font.descent, adv: /* @__PURE__ */ new Map(), words: /* @__PURE__ */ new Map(), met: /* @__PURE__ */ new Map() };
3829
- this.faces.set(key, f4);
3830
- }
3831
- return f4;
3832
- }
3833
- /** Font units of `text` on the fast path, or -1 when it needs the complex path. */
3834
- fast(text, key) {
3835
- const f4 = this.face(key);
3836
- const hit = f4.words.get(text);
3837
- if (hit !== void 0) return hit;
3838
- const m = f4.adv, font = f4.font;
3839
- let units = 0;
3840
- for (let i = 0; i < text.length; i++) {
3841
- let cp = text.charCodeAt(i);
3842
- if (cp >= 55296 && cp <= 56319) {
3843
- const lo = text.charCodeAt(i + 1);
3844
- if (lo >= 56320 && lo <= 57343) {
3845
- cp = (cp - 55296) * 1024 + lo - 56320 + 65536;
3846
- i++;
3847
- }
3848
- }
3849
- let a = m.get(cp);
3850
- if (a === void 0) {
3851
- a = special(cp) || cp >= 32 && !font.hasGlyphForCodePoint(cp) ? -1 : font.glyphForCodePoint(cp).advanceWidth || 0;
3852
- m.set(cp, a);
3853
- }
3854
- if (a < 0) {
3855
- units = -1;
3856
- break;
3857
- }
3858
- units += a;
3859
- }
3860
- if (text.length <= 256) {
3861
- if (f4.words.size >= 5e4) f4.words.clear();
3862
- f4.words.set(text, units);
3863
- }
3864
- return units;
3865
- }
3866
- /**
3867
- * Advance width in points. @param {string} text @param {string} key @param {number} size
3868
- * @param {'ltr'|'rtl'} [dir] paragraph direction (default: from the first strong character)
3869
- */
3870
- width(text, key, size, dir) {
3871
- const k = this.store.has(key) ? key : this.usable(key);
3872
- const u = this.fast(text, k);
3873
- if (u >= 0) return u * size / this.face(k).upm;
3874
- return this.layout(text, k, dir).w * size;
3875
- }
3876
- /**
3877
- * Where underline and strike-through go, from the font's own tables (post and OS/2), in points:
3878
- * y offsets of the stroke centre from the baseline (positive = down, as on the page) and thicknesses.
3879
- * A font without the tables falls back to proportions of its descent and x-height.
3880
- * @param {string} key @param {number} size
3881
- */
3882
- decoration(key, size) {
3883
- const f4 = this.store.get(this.usable(key));
3884
- const u = f4.unitsPerEm, os2 = f4["OS/2"] || {};
3885
- const ut = f4.underlineThickness > 0 ? f4.underlineThickness : u / 14;
3886
- const up = f4.underlinePosition < 0 ? f4.underlinePosition : f4.descent / 2;
3887
- const st = os2.yStrikeoutSize > 0 ? os2.yStrikeoutSize : ut;
3888
- const sp = os2.yStrikeoutPosition > 0 ? os2.yStrikeoutPosition : (f4.xHeight > 0 ? f4.xHeight : f4.ascent * 0.5) / 2 + st / 2;
3889
- const k = size / u;
3890
- return { underlineY: -(up - ut / 2) * k, underlineWidth: ut * k, strikeY: -(sp - st / 2) * k, strikeWidth: st * k };
3891
- }
3892
- /**
3893
- * Superscript and subscript from the font's OS/2 table, in em: the size of the small text and how far its
3894
- * baseline moves (rise up for superscript, drop down for subscript). Fonts without the values get the
3895
- * proportions most fonts use.
3896
- * @param {string} key
3897
- */
3898
- script(key) {
3899
- const f4 = this.store.get(this.usable(key));
3900
- const u = f4.unitsPerEm, o = f4["OS/2"] || {};
3901
- const frac = (v, d) => v > 0 && v < u * 2 ? v / u : d;
3902
- 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) };
3903
- }
3904
- /** @param {string} key @param {number} size */
3905
- metrics(key, size) {
3906
- const f4 = this.face(this.usable(key));
3907
- let r = f4.met.get(size);
3908
- if (!r) {
3909
- r = Object.freeze({ ascent: f4.ascent * size / f4.upm, descent: -f4.descent * size / f4.upm });
3910
- f4.met.set(size, r);
3911
- }
3912
- return r;
3913
- }
3914
- /**
3915
- * The visually ordered runs of one line, or null when the line is simple (draw `text` as it is).
3916
- * @param {string} text @param {string} key @param {number} size @param {'ltr'|'rtl'} [dir]
3917
- * @returns {Run[]|null}
3918
- */
3919
- runs(text, key, size, dir) {
3920
- if (!dir && !MAYBE.test(text)) return null;
3921
- const k = this.usable(key);
3922
- if (this.fast(text, k) >= 0) return null;
3923
- return this.layout(text, k, dir).runs.map((r) => ({
3924
- ...r,
3925
- x: r.x * size,
3926
- w: r.w * size,
3927
- glyphs: r.glyphs.map((v, i) => i % 4 === 1 || i % 4 === 2 ? v * size : v)
3928
- }));
3929
- }
3930
- /** Paragraph direction of text that needs shaping, bidi or CJK breaks, else undefined. */
3931
- direction(text, key) {
3932
- if (!MAYBE.test(text)) return void 0;
3933
- const k = this.usable(key);
3934
- if (this.fast(text, k) >= 0) return void 0;
3935
- return this.layout(text, k, void 0).rtl ? "rtl" : "ltr";
3936
- }
3937
- /**
3938
- * Greedy line breaking. Simple paragraphs break at spaces (a word longer than the line breaks by
3939
- * character), exactly as before. Others also break between CJK characters (kinsoku applied), never
3940
- * inside a grapheme cluster (a conjunct, a letter and its marks), and remember their direction.
3941
- * @param {string} text @param {string} key @param {number} size @param {number} maxWidth
3942
- * @returns {{text: string, width: number, dir?: 'ltr'|'rtl'}[]}
3943
- */
3944
- wrap(text, key, size, maxWidth) {
3945
- const out = [];
3946
- const k = this.usable(key);
3947
- const sp = this.width(" ", k, size);
3948
- const str = String(text);
3949
- for (const para of str.includes("\n") ? str.split(/\r?\n/) : [str]) {
3950
- const dir = this.direction(para, k);
3951
- if (dir) {
3952
- this.wrapComplex(para, k, size, maxWidth, dir, sp, out);
3953
- continue;
3954
- }
3955
- if (!para.includes(" ")) {
3956
- const ww = this.width(para, k, size);
3957
- if (ww <= maxWidth || maxWidth <= 0) {
3958
- out.push({ text: para, width: ww, end: true });
3959
- continue;
3960
- }
3961
- }
3962
- const words = para.split(/ +/);
3963
- let line2 = "", lw = 0;
3964
- const push = () => {
3965
- out.push({ text: line2, width: lw });
3966
- line2 = "";
3967
- lw = 0;
3968
- };
3969
- for (let wi = 0; wi < words.length; wi++) {
3970
- const w = words[wi];
3971
- if (w === "" && wi > 0) continue;
3972
- const ww = this.width(w, k, size);
3973
- if (line2 === "") {
3974
- if (ww <= maxWidth || maxWidth <= 0) {
3975
- line2 = w;
3976
- lw = ww;
3977
- continue;
3978
- }
3979
- } else if (lw + sp + ww <= maxWidth) {
3980
- line2 += " " + w;
3981
- lw += sp + ww;
3982
- continue;
3983
- } else {
3984
- push();
3985
- if (ww <= maxWidth) {
3986
- line2 = w;
3987
- lw = ww;
3988
- continue;
3989
- }
3990
- }
3991
- let after = -1;
3992
- for (const ch of w) {
3993
- const cw = this.width(ch, k, size);
3994
- if (lw + cw > maxWidth && line2 !== "") {
3995
- if (after > 0 && after < line2.length) {
3996
- const rest = line2.slice(after);
3997
- line2 = line2.slice(0, after);
3998
- lw = this.width(line2, k, size);
3999
- push();
4000
- line2 = rest;
4001
- lw = this.width(rest, k, size);
4002
- } else push();
4003
- after = -1;
4004
- }
4005
- line2 += ch;
4006
- lw += cw;
4007
- if ((ch === "/" || ch === "-") && line2.length > 1) after = line2.length;
4008
- }
4009
- }
4010
- push();
4011
- out[out.length - 1].end = true;
4012
- }
4013
- return out;
4014
- }
4015
- /** @param {string} para @param {string} key @param {number} size @param {number} maxWidth @param {'ltr'|'rtl'} dir @param {number} sp @param {any[]} out */
4016
- wrapComplex(para, key, size, maxWidth, dir, sp, out) {
4017
- const tokens = [];
4018
- const sa = SA.test(para) ? saBreaks(para) : null;
4019
- let cur = "", space = false, prev = "", at = 0;
4020
- for (const ch of para) {
4021
- const i = at;
4022
- at += ch.length;
4023
- if (ch === " ") {
4024
- if (cur) {
4025
- tokens.push({ text: cur, space });
4026
- cur = "";
4027
- }
4028
- space = tokens.length > 0;
4029
- prev = "";
4030
- continue;
4031
- }
4032
- const cp = (
4033
- /** @type {number} */
4034
- ch.codePointAt(0)
4035
- );
4036
- const pcp = prev ? (
4037
- /** @type {number} */
4038
- prev.codePointAt(0)
4039
- ) : 0;
4040
- if (cur && (sa?.has(i) || (cjk(cp) || cjk(pcp)) && !NO_START.has(ch) && !NO_END.has(prev) && !STICKY.test(ch))) {
4041
- tokens.push({ text: cur, space });
4042
- cur = "";
4043
- space = false;
4044
- }
4045
- cur += ch;
4046
- prev = ch;
4047
- }
4048
- if (cur) tokens.push({ text: cur, space });
4049
- let line2 = "", lw = 0;
4050
- const push = () => {
4051
- out.push({ text: line2, width: this.width(line2, key, size, dir), dir });
4052
- line2 = "";
4053
- lw = 0;
4054
- };
4055
- for (const t of tokens) {
4056
- const tw = this.width(t.text, key, size, dir);
4057
- const gap = line2 && t.space ? sp : 0;
4058
- if (line2 === "" ? tw <= maxWidth || maxWidth <= 0 : lw + gap + tw <= maxWidth) {
4059
- line2 += (line2 && t.space ? " " : "") + t.text;
4060
- lw += gap + tw;
4061
- continue;
4062
- }
4063
- if (line2) push();
4064
- if (tw <= maxWidth) {
4065
- line2 = t.text;
4066
- lw = tw;
4067
- continue;
4068
- }
4069
- for (const g of graphemes(t.text)) {
4070
- const gw = this.width(g, key, size, dir);
4071
- if (lw + gw > maxWidth && line2 !== "") push();
4072
- line2 += g;
4073
- lw += gw;
4074
- }
4075
- }
4076
- push();
4077
- }
4078
- /** Cut a line so it fits, then add "…". */
4079
- ellipsize(text, key, size, maxWidth, dir) {
4080
- const ell = "…";
4081
- if (this.width(text, key, size, dir) <= maxWidth) return text;
4082
- const ew = this.width(ell, key, size);
4083
- if (this.direction(text, key)) {
4084
- const gs = graphemes(text);
4085
- while (gs.length && this.width(gs.join(""), key, size, dir) + ew > maxWidth) gs.pop();
4086
- return gs.join("").trimEnd() + ell;
4087
- }
4088
- let s = text;
4089
- while (s.length && this.width(s, key, size) + ew > maxWidth) s = s.slice(0, -1);
4090
- return s.trimEnd() + ell;
4091
- }
4092
- /** Does this font have a glyph for the character? */
4093
- has(key, cp) {
4094
- let m = this.cover.get(key);
4095
- if (!m) {
4096
- m = /* @__PURE__ */ new Map();
4097
- this.cover.set(key, m);
4098
- }
4099
- let h = m.get(cp);
4100
- if (h === void 0) {
4101
- h = !!this.store.get(key).hasGlyphForCodePoint(cp);
4102
- m.set(cp, h);
4103
- }
4104
- return h;
4105
- }
4106
- /** The font for one character: the chosen one, else the bundled fallback for its script, else the one before. */
4107
- pick(cp, key, prev) {
4108
- if (cp < 32 || this.has(key, cp)) return key;
4109
- const fb = fallbackKey(cp, key);
4110
- if (fb && !this.store.failed.has(fb)) {
4111
- if (!this.store.has(fb)) {
4112
- this.missing.add(fb);
4113
- return key;
4114
- }
4115
- if (this.has(fb, cp)) return fb;
4116
- }
4117
- if (prev && prev !== key && this.has(prev, cp)) return prev;
4118
- if (!this.missing.size) this.noGlyph.add(String.fromCodePoint(cp));
4119
- return key;
4120
- }
4121
- /**
4122
- * Bidi levels, a font per character, runs (one font, level, script, and space or not), shaping,
4123
- * then visual order. Cached at size 1.
4124
- * @param {string} text @param {string} key a loaded key @param {'ltr'|'rtl'|undefined} dir
4125
- * @returns {{ w: number, rtl: boolean, runs: Run[] }}
4126
- */
4127
- layout(text, key, dir) {
4128
- const ck = `${key}${dir || ""}${text}`;
4129
- const hit = this.cache.get(ck);
4130
- if (hit) return hit;
4131
- let levels = null, rtl = dir === "rtl";
4132
- if (dir === "rtl" || RTL_OR_CONTROLS.test(text)) {
4133
- const e = bidi.getEmbeddingLevels(text, dir);
4134
- levels = e.levels;
4135
- rtl = ((e.paragraphs[0]?.level ?? 0) & 1) === 1;
4136
- }
4137
- const segs = [];
4138
- let cur = null, prevFont = "";
4139
- for (let i = 0; i < text.length; ) {
4140
- const cp = (
4141
- /** @type {number} */
4142
- text.codePointAt(i)
4143
- );
4144
- const n = cp > 65535 ? 2 : 1;
4145
- const ch = text.slice(i, i + n);
4146
- const font = STICKY.test(ch) && prevFont ? prevFont : this.pick(cp, key, prevFont);
4147
- const level = levels ? levels[i] : 0;
4148
- const ws = SPACE.test(ch);
4149
- const script = scriptOf(cp);
4150
- if (!cur || font !== cur.font || level !== cur.level || ws !== cur.ws || script !== "Common" && cur.script !== "Common" && script !== cur.script) {
4151
- cur = { start: i, end: i + n, font, level, script, ws };
4152
- segs.push(cur);
4153
- } else {
4154
- cur.end = i + n;
4155
- if (cur.script === "Common") cur.script = script;
4156
- }
4157
- prevFont = font;
4158
- i += n;
4159
- }
4160
- const runs = segs.map((s) => this.shapeRun(text.slice(s.start, s.end), s.font, (s.level & 1) === 1, s.script));
4161
- const order = runs.map((r, i) => ({ r, level: segs[i].level }));
4162
- let max = 0, minOdd = Infinity;
4163
- for (const o of order) {
4164
- max = Math.max(max, o.level);
4165
- if (o.level & 1) minOdd = Math.min(minOdd, o.level);
4166
- }
4167
- for (let l = max; l >= minOdd; l--) {
4168
- for (let i = 0; i < order.length; ) {
4169
- if (order[i].level < l) {
4170
- i++;
4171
- continue;
4172
- }
4173
- let j = i;
4174
- while (j < order.length && order[j].level >= l) j++;
4175
- const rev = order.slice(i, j).reverse();
4176
- order.splice(i, j - i, ...rev);
4177
- i = j;
4178
- }
4179
- }
4180
- let x = 0;
4181
- for (const o of order) {
4182
- o.r.x = x;
4183
- x += o.r.w;
4184
- }
4185
- const out = { w: x, rtl, runs: order.map((o) => o.r) };
4186
- if (this.cache.size > 2e4) this.cache.clear();
4187
- this.cache.set(ck, out);
4188
- return out;
4189
- }
4190
- /** One run at size 1: HarfBuzz for complex scripts and right-to-left text, else character-map glyphs. @returns {Run} */
4191
- shapeRun(text, key, rtl, script) {
4192
- const complex = !SIMPLE.has(script);
4193
- const store = this.store;
4194
- const glyphs = [];
4195
- let pen = 0;
4196
- if ((complex || rtl) && store.shaper) {
4197
- const hf = store.hbFont(key);
4198
- const g = store.shaper.shape(hf, text, rtl, !complex);
4199
- for (let i = 0; i < g.length; i += 5) {
4200
- glyphs.push(g[i], (pen + g[i + 2]) / hf.upem, g[i + 3] / hf.upem, g[i + 4]);
4201
- pen += g[i + 1];
4202
- }
4203
- return { font: key, x: 0, w: pen / hf.upem, text, ...rtl ? { rtl } : {}, ...complex ? { shaped: true } : {}, glyphs };
4204
- }
4205
- if ((complex || rtl) && !store.failed.has(SHAPER)) this.missing.add(SHAPER);
4206
- else if (complex || rtl) this.unshaped = true;
4207
- const f4 = store.get(key);
4208
- const list = [];
4209
- for (let i = 0; i < text.length; ) {
4210
- const cp = (
4211
- /** @type {number} */
4212
- text.codePointAt(i)
4213
- );
4214
- const g = f4.glyphForCodePoint(cp);
4215
- list.push([g.id, g.advanceWidth || 0, i]);
4216
- i += cp > 65535 ? 2 : 1;
4217
- }
4218
- if (rtl) list.reverse();
4219
- for (const [id, a, c] of list) {
4220
- glyphs.push(id, pen / f4.unitsPerEm, 0, c);
4221
- pen += a;
4222
- }
4223
- return { font: key, x: 0, w: pen / f4.unitsPerEm, text, ...rtl ? { rtl } : {}, glyphs };
4224
- }
4225
- };
4226
- function finishText(items, m) {
4227
- for (const it of items) {
4228
- if (it.t === "field") {
4229
- finishText(it.draw, m);
4230
- continue;
4231
- }
4232
- if (it.t !== "text") continue;
4233
- it.font = m.usable(it.font);
4234
- for (const l of it.lines) {
4235
- const r = m.runs(l.text, it.font, it.size, l.dir);
4236
- if (r) l.runs = r;
4237
- }
4238
- }
4239
- }
4240
-
4241
3743
  // src/engine/style.js
4242
3744
  var BASE_STYLE = {
4243
3745
  fontFamily: "Inter",
@@ -4669,6 +4171,11 @@ function niceScale(min, max, ticks = 5) {
4669
4171
  const step3 = niceNum(range / Math.max(1, ticks - 1), true);
4670
4172
  return { lo: Math.floor(min / step3) * step3, hi: Math.ceil(max / step3) * step3, step: step3 };
4671
4173
  }
4174
+ function niceScaleSpan(min, max, ticks = 5) {
4175
+ if (!Number.isFinite(min) || !Number.isFinite(max) || !(max > min)) return niceScale(min, max, ticks);
4176
+ const step3 = niceNum((max - min) / Math.max(1, ticks - 1), true);
4177
+ return { lo: Math.floor(min / step3) * step3, hi: Math.ceil(max / step3) * step3, step: step3 };
4178
+ }
4672
4179
  function niceNum(x, round) {
4673
4180
  const e = Math.floor(Math.log10(x));
4674
4181
  const f4 = x / 10 ** e;
@@ -5625,7 +5132,7 @@ var byName2 = (lc, pal) => (name) => {
5625
5132
  };
5626
5133
  function chartData(item, ctx, lc, pal = paletteOf(item.palette)) {
5627
5134
  let rows = item.dataSet ? related(lc.dataSets[item.dataSet] || [], ctx) : ctx.aggRows || [];
5628
- if (item.dataSet && !lc.dataSets[item.dataSet]) lc.warn(`${item.name}: the data set "${item.dataSet}" does not exist`);
5135
+ if (item.dataSet && !lc.dataSets[item.dataSet]) lc.warn(`${item.name || item.type}: the data set "${item.dataSet}" does not exist`);
5629
5136
  if (item.filters?.length) rows = applyFilters(rows, item.filters, ctx);
5630
5137
  if (item.sort?.length) rows = applySort(rows, item.sort, ctx);
5631
5138
  const who = item.name || "Chart";
@@ -5913,13 +5420,15 @@ function valueAxis(spec, lo, hi, minPos, { includeZero = true, len = 100, fs = 7
5913
5420
  lo = lo < 0 ? -1 : 0;
5914
5421
  hi = 1;
5915
5422
  }
5423
+ let padded = false;
5916
5424
  if (pad2 > 0 && hi > lo) {
5917
5425
  const d = (hi - lo) * pad2;
5918
- if (smin == null) lo -= d;
5919
- if (smax == null) hi += d;
5426
+ if (smin == null) lo = lo >= 0 && lo - d < 0 ? 0 : lo - d;
5427
+ if (smax == null) hi = hi <= 0 && hi + d > 0 ? 0 : hi + d;
5428
+ padded = true;
5920
5429
  }
5921
5430
  const want = len < fs * 6 ? 3 : 5;
5922
- const sc = niceScale(smin ?? lo, smax ?? hi, want);
5431
+ const sc = padded && smin == null && smax == null && (lo >= 0 || hi <= 0) ? niceScaleSpan(lo, hi, want) : niceScale(smin ?? lo, smax ?? hi, want);
5923
5432
  let a = sc.lo, b = sc.hi, ticks;
5924
5433
  if (smin != null || smax != null) {
5925
5434
  a = smin ?? sc.lo;
@@ -6347,31 +5856,27 @@ function rowPoints(type, g) {
6347
5856
  rows = rows.slice(0, cap);
6348
5857
  }
6349
5858
  const blank2 = (v) => v == null || v === "";
6350
- const missing = /* @__PURE__ */ new Set();
5859
+ const drawn = /* @__PURE__ */ new Set();
6351
5860
  const sets = data.series.map((s, si) => {
6352
5861
  const pts = [];
6353
5862
  rows.forEach((r, ri) => {
6354
5863
  c.fields = r;
6355
5864
  const rx = safe2(item.xValue, c, lc, who), ry = safe2(s.def.value || "=0", { ...c, aggRows: [r] }, lc, who);
6356
- if (blank2(rx) || blank2(ry)) {
6357
- missing.add(ri);
6358
- return;
6359
- }
5865
+ if (blank2(rx) || blank2(ry)) return;
6360
5866
  const x = Number(rx), y = Number(ry);
6361
- if (!Number.isFinite(x) || !Number.isFinite(y)) {
6362
- missing.add(ri);
6363
- return;
6364
- }
5867
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return;
6365
5868
  let size = 1;
6366
5869
  if (type === "bubble") {
6367
5870
  size = Number(safe2(s.def.size || "=1", { ...c, aggRows: [r] }, lc, who));
6368
5871
  if (!Number.isFinite(size) || size <= 0) return;
6369
5872
  }
6370
5873
  pts.push({ x, y, size });
5874
+ drawn.add(ri);
6371
5875
  });
6372
5876
  return { s, pts, i: si };
6373
5877
  });
6374
- if (missing.size) lc.warn(`${who}: ${missing.size} of ${rows.length} rows have no x or y value and are not drawn`);
5878
+ 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`);
6375
5880
  return sets;
6376
5881
  }
6377
5882
  function paintXY(type, g) {
@@ -7652,12 +7157,12 @@ function parseRich(html, opt = {}) {
7652
7157
  i = e + 1;
7653
7158
  continue;
7654
7159
  }
7655
- const tag4 = readTag(s, i);
7656
- if (!tag4) break;
7657
- i = tag4.end;
7658
- const name = tag4.name;
7160
+ const tag3 = readTag(s, i);
7161
+ if (!tag3) break;
7162
+ i = tag3.end;
7163
+ const name = tag3.name;
7659
7164
  if (!name) continue;
7660
- if (!tag4.close && SKIP.has(name)) {
7165
+ if (!tag3.close && SKIP.has(name)) {
7661
7166
  const e = low.indexOf(`</${name}`, i);
7662
7167
  if (e < 0) break;
7663
7168
  const g = s.indexOf(">", e);
@@ -7666,7 +7171,7 @@ function parseRich(html, opt = {}) {
7666
7171
  continue;
7667
7172
  }
7668
7173
  if (name === "br") {
7669
- if (tag4.close) continue;
7174
+ if (tag3.close) continue;
7670
7175
  if (++nBreaks > RICH_LIMITS.maxBreaks) {
7671
7176
  cutAt(`${RICH_LIMITS.maxBreaks} line breaks`);
7672
7177
  continue;
@@ -7679,7 +7184,7 @@ function parseRich(html, opt = {}) {
7679
7184
  if (unknown.size < 20) unknown.add(name.slice(0, 20));
7680
7185
  continue;
7681
7186
  }
7682
- if (tag4.close) {
7187
+ if (tag3.close) {
7683
7188
  if (ignored > 0) {
7684
7189
  ignored--;
7685
7190
  continue;
@@ -7697,7 +7202,7 @@ function parseRich(html, opt = {}) {
7697
7202
  stack2.length = k;
7698
7203
  continue;
7699
7204
  }
7700
- if (tag4.self && name !== "li") continue;
7205
+ if (tag3.self && name !== "li") continue;
7701
7206
  if (stack2.length > RICH_LIMITS.maxDepth) {
7702
7207
  ignored++;
7703
7208
  if (!deepWarned) {
@@ -7731,7 +7236,7 @@ function parseRich(html, opt = {}) {
7731
7236
  st.script = "sub";
7732
7237
  break;
7733
7238
  case "a": {
7734
- const h = decode2(tag4.attrs.href ?? "");
7239
+ const h = decode2(tag3.attrs.href ?? "");
7735
7240
  const ok = safeHref(h);
7736
7241
  if (ok) st.href = ok;
7737
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.`);
@@ -7743,7 +7248,7 @@ function parseRich(html, opt = {}) {
7743
7248
  st.fontWeight = "bold";
7744
7249
  }
7745
7250
  }
7746
- if (tag4.attrs.style) applyCss(tag4.attrs.style, st);
7251
+ if (tag3.attrs.style) applyCss(tag3.attrs.style, st);
7747
7252
  if (BLOCK.has(name)) {
7748
7253
  flush();
7749
7254
  if (name === "ul" || name === "ol") lists.push({ ordered: name === "ol", n: 0 });
@@ -8792,7 +8297,7 @@ function hidden(item, ctx, lc) {
8792
8297
  const r = evalSafe(item.hidden, ctx, lc, item.name || item.type);
8793
8298
  h = !r.err && truthy(r.v);
8794
8299
  }
8795
- return item.name && lc.state?.toggles?.[`item:${item.name}`] ? !h : h;
8300
+ return item.name && lc.state?.toggles?.[`item:${item.name || item.type}`] ? !h : h;
8796
8301
  }
8797
8302
  var SHRINK_DEFAULTS = { minFontSize: 4, step: 0.5 };
8798
8303
  function rotationOf(item) {
@@ -9128,7 +8633,7 @@ register("table", {
9128
8633
  else {
9129
8634
  rows = item.dataSet && lc.dataSets[item.dataSet] || [];
9130
8635
  rows = related(rows, tctx);
9131
- if (item.dataSet && !lc.dataSets[item.dataSet]) lc.warn(`${item.name || "Table"}: the data set "${item.dataSet}" does not exist`);
8636
+ if (item.dataSet && !lc.dataSets[item.dataSet]) lc.warn(`${item.name || item.type}: the data set "${item.dataSet}" does not exist`);
9132
8637
  if (item.filters?.length) rows = applyFilters(rows, item.filters, tctx);
9133
8638
  const sort = lc.state?.sort?.[item.name] || item.sort;
9134
8639
  if (sort?.length) rows = applySort(rows, sort, tctx);
@@ -9144,11 +8649,11 @@ register("table", {
9144
8649
  if (overflow === "shrink" && room > 0) {
9145
8650
  const k = room / designedW;
9146
8651
  cols = cols.map((c) => ({ ...c, width: (Number(c.width) || 0) * k }));
9147
- lc.warn(`${item.name || "Table"}: its columns are ${Math.round(designedW)}pt wide, wider than the page (${Math.round(room)}pt), so they are narrowed to fit`);
8652
+ lc.warn(`${item.name || item.type}: its columns are ${Math.round(designedW)}pt wide, wider than the page (${Math.round(room)}pt), so they are narrowed to fit`);
9148
8653
  } else if (overflow === "clip" || overflow === "paginate" && lc.fixed) {
9149
- lc.warn(`${item.name || "Table"}: its columns are ${Math.round(designedW)}pt wide, wider than the page (${Math.round(room)}pt), so the columns past the page edge are cut`, true);
8654
+ lc.warn(`${item.name || item.type}: its columns are ${Math.round(designedW)}pt wide, wider than the page (${Math.round(room)}pt), so the columns past the page edge are cut`, true);
9150
8655
  } else if (overflow === "paginate" && item.overflow !== "paginate") {
9151
- lc.warn(`${item.name || "Table"}: its columns are ${Math.round(designedW)}pt wide, wider than the page (${Math.round(room)}pt), so they continue on the next pages`);
8656
+ lc.warn(`${item.name || item.type}: its columns are ${Math.round(designedW)}pt wide, wider than the page (${Math.round(room)}pt), so they continue on the next pages`);
9152
8657
  }
9153
8658
  const colX = [];
9154
8659
  let acc = item.x;
@@ -9158,6 +8663,7 @@ register("table", {
9158
8663
  }
9159
8664
  const width = acc - item.x;
9160
8665
  const region = { name: item.name, columns: cols.map((c) => c.width), rows: [] };
8666
+ if (lc.tablesSeen) lc.tablesSeen.n++;
9161
8667
  const tableScope = { rows, index: -1, run: [] };
9162
8668
  const scopes = { ...tctx.scopes || {}, [item.name]: tableScope };
9163
8669
  const groupChains = {};
@@ -9181,7 +8687,7 @@ register("table", {
9181
8687
  const covered = (ci) => cover[ci] !== null;
9182
8688
  const cellsAt = /* @__PURE__ */ new WeakMap();
9183
8689
  const layRow = (row, ctx, kind, extra) => {
9184
- if (row.hidden != null && row.hidden !== false && row.hidden !== "" && hidden({ hidden: row.hidden, name: `${item.name || "Table"} row` }, ctx, lc)) {
8690
+ if (row.hidden != null && row.hidden !== false && row.hidden !== "" && hidden({ hidden: row.hidden, name: `${item.name || item.type} row` }, ctx, lc)) {
9185
8691
  if (openSpans.length) {
9186
8692
  for (const s of openSpans) s.left--;
9187
8693
  for (const s of openSpans) if (s.left <= 0) closeSpan(s);
@@ -9215,7 +8721,7 @@ register("table", {
9215
8721
  else try {
9216
8722
  v = evalValue(cell.value ?? "", cctx);
9217
8723
  } catch (e) {
9218
- lc.warn(`${item.name || "Table"} cell: ${e.message}`);
8724
+ lc.warn(`${item.name || item.type} cell: ${e.message}`);
9219
8725
  v = "#Error";
9220
8726
  }
9221
8727
  let text = textOf(v, st, cctx, lc);
@@ -9258,7 +8764,7 @@ register("table", {
9258
8764
  cells.forEach((c, k) => {
9259
8765
  if (c.cell.visual?.type) {
9260
8766
  items.push(...paintBox({ x: c.x, y: 0, w: c.w, h, st: c.st, tl: null, m: lc.m }));
9261
- if (first) items.push(...paintVisual(c.cell.visual, { x: c.x, y: 0, w: c.w, h }, c.ctx, lc, c.st, `${item.name} cell`, { cell: true }));
8767
+ if (first) items.push(...paintVisual(c.cell.visual, { x: c.x, y: 0, w: c.w, h }, c.ctx, lc, c.st, `${item.name || item.type} cell`, { cell: true }));
9262
8768
  return;
9263
8769
  }
9264
8770
  if (c.nested) {
@@ -9267,8 +8773,8 @@ register("table", {
9267
8773
  else if (first) items.push(...c.nested.items);
9268
8774
  return;
9269
8775
  }
9270
- const tag4 = lc.regions ? { table: item.name, row: rowId, col: colIdx[c.ci], span: c.span, kind, cols: colWidths, bg: c.st.backgroundColor || null } : null;
9271
- paintBoxAt(c.x, 0, c.w, h, c.st, lines ? { ...c.tl, lines: lines[k] } : c.tl, c.numeric, lc.m, true, items, tag4);
8776
+ const tag3 = lc.regions ? { table: item.name, row: rowId, col: colIdx[c.ci], span: c.span, kind, cols: colWidths, bg: c.st.backgroundColor || null } : null;
8777
+ paintBoxAt(c.x, 0, c.w, h, c.st, lines ? { ...c.tl, lines: lines[k] } : c.tl, c.numeric, lc.m, true, items, tag3);
9272
8778
  if (first) cellLinks(items, c, h, kind, extra?.level);
9273
8779
  });
9274
8780
  if (first) {
@@ -9313,7 +8819,7 @@ register("table", {
9313
8819
  };
9314
8820
  const layoutInCell = (inner, x, cw, st, cctx, row) => {
9315
8821
  const pd = padding(st);
9316
- const it = { ...inner, name: inner.name || `${item.name}_cell`, x: x + pd[3], y: 0, w: Math.max(1, cw - pd[1] - pd[3]), h: Number(inner.h) || Math.max(1, (row.height || 18) - pd[0] - pd[2]) };
8822
+ const it = { ...inner, name: inner.name || `${item.name || item.type}_cell`, x: x + pd[3], y: 0, w: Math.max(1, cw - pd[1] - pd[3]), h: Number(inner.h) || Math.max(1, (row.height || 18) - pd[0] - pd[2]) };
9317
8823
  const scopeRows2 = cctx.aggIndex != null ? [cctx.fields] : cctx.aggRows || [];
9318
8824
  const current = item.dataSet ? { ...cctx.current || {}, [item.dataSet]: scopeRows2 } : cctx.current;
9319
8825
  const child = {
@@ -9333,7 +8839,7 @@ register("table", {
9333
8839
  const reads = (it.dataSet && it.dataSet !== item.dataSet ? child.ctxFor(it).aggRows?.length : scopeRows2.length) || 1;
9334
8840
  if (budget.left < reads) {
9335
8841
  budget.left = 0;
9336
- lc.warn(`${item.name}: more than ${CELL_ITEM_UNITS} rows of items inside cells; the rest are left empty`);
8842
+ lc.warn(`${item.name || item.type}: more than ${CELL_ITEM_UNITS} rows of items inside cells; the rest are left empty`);
9337
8843
  return { items: [], height: 0 };
9338
8844
  }
9339
8845
  budget.left -= reads;
@@ -9367,8 +8873,8 @@ register("table", {
9367
8873
  const st = k === 0 && n === 1 ? s.st : { ...s.st, ...k > 0 ? { borderTop: "none" } : {}, ...k < n - 1 ? { borderBottom: "none" } : {} };
9368
8874
  const add2 = paintBox({ x: s.x, y: 0, w: s.w, h: u.height, st, tl: null, m: lc.m });
9369
8875
  if (k === 0) {
9370
- const tag4 = lc.regions ? { table: item.name, row: u.rowId, col: colX.indexOf(s.x), span: s.cols, rowSpan: n, kind, cols: colWidths, bg: s.st.backgroundColor || null } : null;
9371
- const text = paintBox({ x: s.x, y: 0, w: s.w, h: H, st: { ...s.st, ...NO_BOX }, tl: s.tl, numeric: s.numeric, m: lc.m, cell: tag4 });
8876
+ const tag3 = lc.regions ? { table: item.name, row: u.rowId, col: colX.indexOf(s.x), span: s.cols, rowSpan: n, kind, cols: colWidths, bg: s.st.backgroundColor || null } : null;
8877
+ const text = paintBox({ x: s.x, y: 0, w: s.w, h: H, st: { ...s.st, ...NO_BOX }, tl: s.tl, numeric: s.numeric, m: lc.m, cell: tag3 });
9372
8878
  add2.splice(s.st.backgroundColor ? 1 : 0, 0, ...text);
9373
8879
  cellLinks(add2, s, u.height, kind, s.level);
9374
8880
  if (s.toggle) add2.push(toggleLink(s, s.w, u.height));
@@ -9377,7 +8883,7 @@ register("table", {
9377
8883
  u.items.splice(at < 0 ? u.items.length : at, 0, ...add2);
9378
8884
  });
9379
8885
  if (s.tl.needed > s.units[0].height + GROW_TOLERANCE || s.st.verticalAlign && s.st.verticalAlign !== "top") {
9380
- const id = `${item.name}:span:${lc.nextId()}`;
8886
+ const id = `${item.name || item.type}:span:${lc.nextId()}`;
9381
8887
  for (const u of s.units) u.block || (u.block = id);
9382
8888
  } else {
9383
8889
  let above = s.units[0].height;
@@ -9419,7 +8925,7 @@ register("table", {
9419
8925
  const groups = item.groups || [];
9420
8926
  let rowNo = lc.win?.rowOffset || 0;
9421
8927
  const groupTables = [];
9422
- const tableKey = `${item.name}#${lc.nextId()}`;
8928
+ const tableKey = `${item.name || item.type}#${lc.nextId()}`;
9423
8929
  const emit = (level, grows, ctx, chain, keyPath) => {
9424
8930
  if (level === groups.length) {
9425
8931
  for (let i = 0; i < grows.length; i++) {
@@ -9481,7 +8987,7 @@ register("table", {
9481
8987
  kids = /* @__PURE__ */ new Map();
9482
8988
  roots = [];
9483
8989
  for (const b of buckets) {
9484
- const pk = evalSafe(g.parent, { ...ctx, fields: b.rows[0], aggRows: b.rows, aggIndex: void 0 }, lc, `${item.name} group parent`).v;
8990
+ const pk = evalSafe(g.parent, { ...ctx, fields: b.rows[0], aggRows: b.rows, aggIndex: void 0 }, lc, `${item.name || item.type} group parent`).v;
9485
8991
  const pr = pk == null || pk === "" ? null : byText.get(pk instanceof Date ? String(pk.getTime()) : String(pk));
9486
8992
  if (!pr || pr === b) roots.push(b);
9487
8993
  else {
@@ -9513,7 +9019,7 @@ register("table", {
9513
9019
  if (g.indent) gctx.indent = (ctx.indent || 0) + depth * Number(g.indent);
9514
9020
  }
9515
9021
  const path = `${parentPath}/${bk.text}`;
9516
- const tkey = `${item.name}|${level}|${path}`;
9022
+ const tkey = `${item.name || item.type}|${level}|${path}`;
9517
9023
  const dflt = kids && g.expandLevels != null ? depth < Number(g.expandLevels) : !g.initiallyCollapsed;
9518
9024
  const open = !g.collapsible ? true : lc.state?.toggles?.[tkey] ?? dflt;
9519
9025
  const start = units.length;
@@ -9535,7 +9041,7 @@ register("table", {
9535
9041
  const ch = kids?.get(bk) || [];
9536
9042
  if (open) {
9537
9043
  emit(level + 1, bk.rows, gctx, [...chain, sc], path);
9538
- if (ch.length && depth + 1 >= MAX_TREE_DEPTH) lc.warn(`${item.name}: the group "${gname}" is more than ${MAX_TREE_DEPTH} levels deep; deeper rows are listed at the top level`);
9044
+ if (ch.length && depth + 1 >= MAX_TREE_DEPTH) lc.warn(`${item.name || item.type}: the group "${gname}" is more than ${MAX_TREE_DEPTH} levels deep; deeper rows are listed at the top level`);
9539
9045
  else ch.forEach((c, ci) => node(c, ci, ch, depth + 1, path));
9540
9046
  } else {
9541
9047
  const rs = recOf.get(bk) || bk.rows;
@@ -9564,7 +9070,7 @@ register("table", {
9564
9070
  if (g.newSection) units[start].sectionStart = true;
9565
9071
  if (g.pageBreak === "after" && bi < sibs.length - 1) units[units.length - 1].breakAfter = true;
9566
9072
  if (g.keepTogether) {
9567
- const id = `${item.name}:g${level}:${path}:${lc.nextId()}`;
9073
+ const id = `${item.name || item.type}:g${level}:${path}:${lc.nextId()}`;
9568
9074
  for (let k = start; k < units.length; k++) if (!units[k].block) {
9569
9075
  units[k].block = id;
9570
9076
  if (cont) units[k].blockOpen = true;
@@ -9585,7 +9091,7 @@ register("table", {
9585
9091
  if (kids) {
9586
9092
  const rest = buckets.filter((b) => !shown.has(b));
9587
9093
  if (rest.length) {
9588
- lc.warn(`${item.name}: ${rest.length} rows of the group "${gname}" have no root (a cycle or too deep); they are listed at the top level`);
9094
+ lc.warn(`${item.name || item.type}: ${rest.length} rows of the group "${gname}" have no root (a cycle or too deep); they are listed at the top level`);
9589
9095
  rest.forEach((bk, bi) => node(bk, bi, rest, 0, keyPath));
9590
9096
  }
9591
9097
  }
@@ -9613,7 +9119,7 @@ register("table", {
9613
9119
  units[units.length - 1].breakAfter = true;
9614
9120
  }
9615
9121
  if (item.bookmark && units.length) {
9616
- const b = evalSafe(item.bookmark, base, lc, `${item.name || "Table"} bookmark`).v;
9122
+ const b = evalSafe(item.bookmark, base, lc, `${item.name || item.type} bookmark`).v;
9617
9123
  if (b != null && b !== "" && b !== "#Error") units[0].items.unshift({ t: "bookmark", label: String(b), x: item.x, y: 0, level: item.bookmarkLevel ?? 0 });
9618
9124
  }
9619
9125
  if (!lc.fixed) lc.regions?.push(region);
@@ -9708,7 +9214,7 @@ register("container", {
9708
9214
  const ctx = lc.ctxFor(item);
9709
9215
  if (hidden(item, ctx, lc)) return { height: 0, units: [] };
9710
9216
  const res = lc.layoutItems(item.items, lc, { dx: item.x });
9711
- return frame2(item, lc, ctx, res, item.keepTogether !== false ? `c:${item.name}:${lc.nextId()}` : null);
9217
+ return frame2(item, lc, ctx, res, item.keepTogether !== false ? `c:${item.name || item.type}:${lc.nextId()}` : null);
9712
9218
  }
9713
9219
  });
9714
9220
  function frame2(item, lc, ctx, res, blockId0) {
@@ -9765,7 +9271,7 @@ register("list", {
9765
9271
  let rows = item.dataSet && lc.dataSets[item.dataSet] || [];
9766
9272
  if (item.inheritRows && item.dataSet && ctx.current?.[item.dataSet]) rows = ctx.current[item.dataSet];
9767
9273
  else rows = related(rows, ctx);
9768
- if (item.dataSet && !lc.dataSets[item.dataSet]) lc.warn(`${item.name}: the data set "${item.dataSet}" does not exist`);
9274
+ if (item.dataSet && !lc.dataSets[item.dataSet]) lc.warn(`${item.name || item.type}: the data set "${item.dataSet}" does not exist`);
9769
9275
  if (item.filters?.length) rows = applyFilters(rows, item.filters, ctx);
9770
9276
  if (item.sort?.length) rows = applySort(rows, item.sort, ctx);
9771
9277
  const reps = item.groupBy ? groupRows(rows, item.groupBy, ctx, lc, item.name, item.groupAdjacent === true).map((b) => b.rows) : rows.map((r) => [r]);
@@ -9794,7 +9300,7 @@ register("list", {
9794
9300
  } };
9795
9301
  const k = i % ncol;
9796
9302
  const res = lc.layoutItems(item.items, child, { dx: item.x + k * (item.w + gap) });
9797
- const fr = frame2({ ...item, x: item.x + k * (item.w + gap), name: `${item.name}[${i + 1}]` }, child, rctx, res, item.keepTogether !== false ? `l:${item.name}:${i}:${lc.nextId()}` : null);
9303
+ const fr = frame2({ ...item, x: item.x + k * (item.w + gap), name: `${item.name || item.type}[${i + 1}]` }, child, rctx, res, item.keepTogether !== false ? `l:${item.name || item.type}:${i}:${lc.nextId()}` : null);
9798
9304
  if (ncol > 1) {
9799
9305
  if (k === 0) {
9800
9306
  cells = { top: y, height: 0, items: [], x0: item.x, x1: item.x + ncol * item.w + (ncol - 1) * gap, block: void 0 };
@@ -10265,7 +9771,7 @@ register("subreport", {
10265
9771
  if (hidden(item, ctx, lc)) return { height: 0, units: [] };
10266
9772
  const params = {};
10267
9773
  for (const [k, v] of Object.entries(item.params || {})) {
10268
- const r = evalSafe(v, ctx, lc, `${item.name} parameter ${k}`);
9774
+ const r = evalSafe(v, ctx, lc, `${item.name || item.type} parameter ${k}`);
10269
9775
  params[k] = r.v instanceof Date ? isoDate(r.v, ctx.timeZone) : r.v;
10270
9776
  }
10271
9777
  const key = `${item.report}|${JSON.stringify(params)}`;
@@ -10284,10 +9790,10 @@ register("subreport", {
10284
9790
  const res = sub.results.get(key);
10285
9791
  if (!res) return placeholder(`Subreport "${item.report}" did not load`);
10286
9792
  if (res.error) {
10287
- lc.warn(`${item.name}: ${res.error}`);
9793
+ lc.warn(`${item.name || item.type}: ${res.error}`);
10288
9794
  return placeholder(`Subreport "${item.report}": ${res.error}`);
10289
9795
  }
10290
- const block = item.keepTogether ? `s:${item.name}:${lc.nextId()}` : void 0;
9796
+ const block = item.keepTogether ? `s:${item.name || item.type}:${lc.nextId()}` : void 0;
10291
9797
  const units = res.units.map((u) => ({ ...u, items: shiftItems(u.items, item.x, 0), ...u.cont ? { cont: shiftItems(u.cont, item.x, 0) } : {}, x0: (u.x0 ?? 0) + item.x, x1: (u.x1 ?? item.w) + item.x, block: u.block || block, tableId: void 0 }));
10292
9798
  const tables = res.tables.map((t) => ({ ...t, x0: t.x0 + item.x, x1: t.x1 + item.x, headerItems: shiftItems(t.headerItems, item.x, 0), id: `sub${lc.nextId()}` }));
10293
9799
  for (const r of res.regions || []) lc.regions?.push(r);
@@ -10780,7 +10286,7 @@ function layoutItems(items, lc, { dx = 0 } = {}) {
10780
10286
  const own = L.res.table ? { id: lc.nextId(), ...L.res.table } : null;
10781
10287
  const us = L.res.units;
10782
10288
  const alt = it.alt || L.res.alt;
10783
- const tag4 = alt || it.headingLevel || FIGURES.has(it.type) ? { k: it.type, id: lc.nextId(), alt: alt ? String(alt).slice(0, 2e3) : void 0, h: Number(it.headingLevel) || void 0 } : null;
10289
+ const tag3 = alt || it.headingLevel || FIGURES.has(it.type) ? { k: it.type, id: lc.nextId(), alt: alt ? String(alt).slice(0, 2e3) : void 0, h: Number(it.headingLevel) || void 0 } : null;
10784
10290
  for (let k = 0; k < us.length; k++) {
10785
10291
  const u = us[k];
10786
10292
  u.x0 = u.x0 ?? x0;
@@ -10789,7 +10295,7 @@ function layoutItems(items, lc, { dx = 0 } = {}) {
10789
10295
  u.tableId = own ? own.id : u.tableId;
10790
10296
  u.breakBefore = u.breakBefore || k === 0 && !!it.pageBreakBefore;
10791
10297
  u.breakAfter = u.breakAfter || k === us.length - 1 && !!it.pageBreakAfter;
10792
- if (tag4) for (const d of u.items) d.tag ?? (d.tag = tag4);
10298
+ if (tag3) for (const d of u.items) d.tag ?? (d.tag = tag3);
10793
10299
  units.push(u);
10794
10300
  }
10795
10301
  if (own) tables.push({ ...own, top, bottom: top + L.res.height, x0, x1 });
@@ -13338,6 +12844,7 @@ var SCHEMA_ALIAS = "reportwright/report@1";
13338
12844
  var ITEM_KEYS = new Set("id type name x y w h style styleName items hidden layer locked label alt value dataSet canGrow canShrink action bookmark tooltip keepTogether pageBreakBefore pageBreakAfter pageBreakBetween newSection filters sort groups groupBy groupAdjacent inheritRows columns header detail footer repeatHeader widowControl noRowsText pageBreakEvery cells rows rowGroup rowGroups columnGro\
13339
12845
  up columnGroups values value valueFormat rowSort columnSort rowTotals columnTotals cellStyle headerStyle totalStyle corner partProps library part properties overflow overflowTo src fit headingLevel rotate shrinkToFit minFontSize shrinkStep tocDots tocLink tocPage format chartType series seriesGroup category title legend palette stacked valueAxis xTitle maxCategories outliers categorySort categoryF\
13340
12846
  ormat showValues labelPosition symbology quietZone report params text toggle initiallyCollapsed collapsible expandLevels repeatColumns columnGap rowHeaderWidth anim animation visual comment notes description".split(" "));
12847
+ var NO_SERIES_CHARTS = /* @__PURE__ */ new Set(["gantt"]);
13341
12848
  function looksLikeLink(it) {
13342
12849
  if (it.type !== "textbox" || it.action && it.action.type && it.action.type !== "none") return false;
13343
12850
  const st = it.style && typeof it.style === "object" ? it.style : {};
@@ -13466,6 +12973,9 @@ function validate(def) {
13466
12973
  if (p?.hidden && p.required && (p.default == null || p.default === "") && !p.nullable) {
13467
12974
  warnings.push(`Parameter "${p.name}" is hidden and required but has no default: only a URL, the API or a drill-through can set it`);
13468
12975
  }
12976
+ if (p?.required && p.nullable && !p.multi && (p.default == null || p.default === "")) {
12977
+ 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
+ }
13469
12979
  if (p?.type === "date" && typeof p.default === "string" && p.default.trim() && !p.default.startsWith("=") && !toDate(p.default)) {
13470
12980
  warnings.push(`Parameter "${p.name}": its default "${p.default}" is not a date; write it as 2026-12-31 (year-month-day)`);
13471
12981
  }
@@ -13546,7 +13056,7 @@ function validate(def) {
13546
13056
  warnings.push(`${label}: there is no named style "${it.styleName}"${g ? ` (did you mean "${g}"?)` : ""}; it is ignored`);
13547
13057
  }
13548
13058
  if (Array.isArray(it.sort)) sortIssues(it.sort, label, warnings);
13549
- if (it.type === "chart" && Array.isArray(it.series) && !it.series.length) warnings.push(`${label}: the chart has no series, so it draws nothing; add one, e.g. series: [{ "name": "Value", "value": "=Sum(Fields.Amount)" }]`);
13059
+ 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)" }]`);
13550
13060
  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`);
13551
13061
  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`);
13552
13062
  const own = ownKeys(it.type);
@@ -13589,6 +13099,9 @@ function validate(def) {
13589
13099
  const n = Array.isArray(it.columns) ? it.columns.length : 0;
13590
13100
  const rowsOf = [["header", it.header], ["detail", it.detail], ["footer", it.footer], ...(it.groups || []).flatMap((g, gi) => [[`group ${gi + 1} header`, g?.header], [`group ${gi + 1} footer`, g?.footer]])];
13591
13101
  for (const [part, rows] of rowsOf) (Array.isArray(rows) ? rows : []).forEach((row, ri) => {
13102
+ const cells = Array.isArray(row?.cells) ? row.cells : [];
13103
+ const placed = cells.length < n && cells.some((c) => Number(c?.colSpan) > 1) ? cells.reduce((s, c) => s + Math.max(1, Math.floor(Number(c?.colSpan)) || 1), 0) : cells.length;
13104
+ if (placed < n) warnings.push(`${label} ${part} row ${ri + 1}: the row has ${placed} of ${n} cells and no colSpan covers the other ${n - placed}; add the cells or a colSpan`);
13592
13105
  for (const [k, j] of rowCells(row?.cells, n).covered) warnings.push(`${label} ${part} row ${ri + 1}: cell ${k} is covered by colSpan of cell ${j}, so it is not drawn (leave it null, or omit the spanned cells: a row with fewer cells than columns places each cell after the previous one's span)`);
13593
13106
  });
13594
13107
  }
@@ -13687,7 +13200,7 @@ function* textsOf(def, dataSets) {
13687
13200
  }
13688
13201
  }
13689
13202
  }
13690
- var NO_CJK_FONTS = "Text in Japanese/Chinese/Korean needs the optional CJK fonts: npm run fonts:cjk";
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";
13691
13204
  async function render(def0, opts = {}) {
13692
13205
  const t0 = Date.now();
13693
13206
  const reuseWarnings = [];
@@ -13758,6 +13271,7 @@ async function render(def0, opts = {}) {
13758
13271
  const base = { ...BASE_STYLE, ...def.styles?.base || {} };
13759
13272
  let idSeq = 0;
13760
13273
  const regions = [];
13274
+ const tablesSeen = { n: 0 };
13761
13275
  const imageBudget = opts.imageBudget || { seen: /* @__PURE__ */ new Set(), bytes: 0 };
13762
13276
  const makeLc = (globals, fixed) => {
13763
13277
  const lc = {
@@ -13768,6 +13282,7 @@ async function render(def0, opts = {}) {
13768
13282
  fixed,
13769
13283
  maxUnitHeight: contentH,
13770
13284
  state: opts.state || {},
13285
+ tablesSeen,
13771
13286
  regions: opts.exportData || opts.dataOnly ? regions : null,
13772
13287
  dataOnly: !!opts.dataOnly,
13773
13288
  // exportData: true: table data and cell tags (Excel, CSV, Word, HTML, accessible PDF)
@@ -14070,9 +13585,9 @@ async function render(def0, opts = {}) {
14070
13585
  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.`);
14071
13586
  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).");
14072
13587
  if (m.noGlyph.size) {
14073
- 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));
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));
14074
13589
  const list = [...m.noGlyph].slice(0, 20).join(" ");
14075
- warn(cjk2 ? `${NO_CJK_FONTS} (the app), or npm i @reportwright/fonts-cjk then npx reportwright-fonts-cjk (the npm engine). 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.`);
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.`);
14076
13591
  }
14077
13592
  if (opts.bodyOnly) return { units: result.body.units, tables: result.body.tables, height: result.body.height, regions, warnings, clipped: clipped() };
14078
13593
  if (opts.dataOnly) return {
@@ -14112,6 +13627,7 @@ async function render(def0, opts = {}) {
14112
13627
  headings: result.headings || [],
14113
13628
  pageStarts: result.pageStarts || [],
14114
13629
  regions: opts.exportData ? regions : null,
13630
+ hasTables: tablesSeen.n > 0,
14115
13631
  name: def.name || "",
14116
13632
  partial: cutRows(),
14117
13633
  timeZone: timeZone || null,
@@ -14248,7 +13764,8 @@ async function parameterOptions(def, given = {}, opts = {}) {
14248
13764
  seen.add(k);
14249
13765
  list.push({ value: v instanceof Date ? isoDate(v) : v, label: String(p.optionsFrom.label ? evalValue(p.optionsFrom.label, c) : v) });
14250
13766
  }
14251
- out[p.name] = list;
13767
+ const fixed = out[p.name] || [], have = new Set(fixed.map((o) => o.value));
13768
+ out[p.name] = [...fixed, ...list.filter((o) => !have.has(o.value))];
14252
13769
  }
14253
13770
  return out;
14254
13771
  }
@@ -14296,440 +13813,16 @@ function resolveParameters(def, given, o = {}) {
14296
13813
  return out;
14297
13814
  }
14298
13815
 
14299
- // src/exporters/subset.js
14300
- var HB_MEMORY_MODE_WRITABLE = 2;
14301
- var HB_SUBSET_SETS_DROP_TABLE_TAG = 3;
14302
- var HB_SUBSET_SETS_LAYOUT_FEATURE_TAG = 6;
14303
- var HB_SUBSET_FLAGS_RETAIN_GIDS = 2;
14304
- var HB_SUBSET_FLAGS_NOTDEF_OUTLINE = 64;
14305
- var tag3 = (s) => [...s].reduce((a, c) => (a << 8) + c.charCodeAt(0), 0) >>> 0;
14306
- function createSubsetter(loadWasm) {
14307
- let hbP = null;
14308
- const hb = () => hbP || (hbP = (async () => {
14309
- const bytes = await loadWasm();
14310
- const { instance } = await WebAssembly.instantiate(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes), {});
14311
- const x = (
14312
- /** @type {any} */
14313
- instance.exports
14314
- );
14315
- x._initialize?.();
14316
- return x;
14317
- })());
14318
- return async function subset(font, codePoints, opt = {}) {
14319
- const x = await hb();
14320
- const heap = () => new Uint8Array(x.memory.buffer);
14321
- const input = x.hb_subset_input_create_or_fail();
14322
- if (!input) throw new Error("HarfBuzz: no subset input");
14323
- const buf = x.malloc(font.byteLength);
14324
- heap().set(font, buf);
14325
- const blob = x.hb_blob_create(buf, font.byteLength, HB_MEMORY_MODE_WRITABLE, 0, 0);
14326
- const face = x.hb_face_create(blob, 0);
14327
- x.hb_blob_destroy(blob);
14328
- try {
14329
- if (!opt.layout) {
14330
- x.hb_set_clear(x.hb_subset_input_set(input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG));
14331
- const drop = x.hb_subset_input_set(input, HB_SUBSET_SETS_DROP_TABLE_TAG);
14332
- for (const t of ["GSUB", "GPOS", "GDEF", "kern", "morx", "mort"]) x.hb_set_add(drop, tag3(t));
14333
- }
14334
- x.hb_subset_input_set_flags(input, x.hb_subset_input_get_flags(input) | HB_SUBSET_FLAGS_NOTDEF_OUTLINE);
14335
- const uni = x.hb_subset_input_unicode_set(input);
14336
- for (const cp of codePoints) x.hb_set_add(uni, cp);
14337
- if (opt.glyphs) {
14338
- x.hb_subset_input_set_flags(input, x.hb_subset_input_get_flags(input) | HB_SUBSET_FLAGS_RETAIN_GIDS);
14339
- const gs = x.hb_subset_input_glyph_set(input);
14340
- for (const g of opt.glyphs) x.hb_set_add(gs, g);
14341
- }
14342
- x.hb_subset_input_pin_all_axes_to_default(input, face);
14343
- const sub = x.hb_subset_or_fail(face, input);
14344
- if (!sub) throw new Error("HarfBuzz: subsetting failed");
14345
- const res = x.hb_face_reference_blob(sub);
14346
- const off = x.hb_blob_get_data(res, 0);
14347
- const len = x.hb_blob_get_length(res);
14348
- const out = heap().slice(off, off + len);
14349
- x.hb_blob_destroy(res);
14350
- x.hb_face_destroy(sub);
14351
- if (!len) throw new Error("HarfBuzz: empty subset");
14352
- return out;
14353
- } finally {
14354
- x.hb_subset_input_destroy(input);
14355
- x.hb_face_destroy(face);
14356
- x.free(buf);
14357
- }
14358
- };
14359
- }
14360
-
14361
- // src/viewer/engineClient.js
14362
- var assetBase = "";
14363
- function setAssetBase(url) {
14364
- assetBase = String(url || "").replace(/\/$/, "");
14365
- mainStore = null;
14366
- }
14367
- var origin = () => assetBase || window.location.origin;
14368
- var fontsUrl = null;
14369
- var standalone = false;
14370
- function setStandalone({ standalone: on = false, fontsUrl: f4 = null } = {}) {
14371
- standalone = !!on;
14372
- fontsUrl = f4 ? String(f4).replace(/\/?$/, "/") : null;
14373
- mainStore = null;
14374
- fontList = null;
14375
- fontBytes.clear();
14376
- }
14377
- var fontsBase = () => fontsUrl || `${assetBase}/fonts/`;
14378
- var besideFonts = (file) => fontsUrl ? new URL(`../${file}`, new URL(fontsUrl, typeof location !== "undefined" ? location.href : void 0)).href : `${assetBase}/${file}`;
14379
- var subsetter = createSubsetter(async () => (await fetch(besideFonts("harfbuzz-subset.wasm"))).arrayBuffer());
14380
- var worker = null;
14381
- var workerBroken = false;
14382
- var seq = 0;
14383
- var waiting = /* @__PURE__ */ new Map();
14384
- var mainStore = null;
14385
- var bundledFonts = null;
14386
- var bundledList = null;
14387
- function setBundledFonts(map, list) {
14388
- bundledFonts = map || null;
14389
- bundledList = list || null;
14390
- mainStore = null;
14391
- fontList = null;
14392
- }
14393
- var b64ToBuffer = (b64) => {
14394
- const s = atob(b64);
14395
- const u = new Uint8Array(s.length);
14396
- for (let i = 0; i < s.length; i++) u[i] = s.charCodeAt(i);
14397
- return u.buffer;
14398
- };
14399
- var fontBytes = /* @__PURE__ */ new Map();
14400
- var fetchFont = (k) => {
14401
- if (!fontBytes.has(k)) {
14402
- const p = (async () => {
14403
- const r = await fetch(`${fontsBase()}${fontFile(k)}`);
14404
- if (!r.ok) throw new Error(`Font "${k}" did not load (HTTP ${r.status})`);
14405
- return r.arrayBuffer();
14406
- })();
14407
- p.catch(() => fontBytes.delete(k));
14408
- fontBytes.set(k, p);
14409
- }
14410
- return fontBytes.get(k);
14411
- };
14412
- var getFont = async (k) => {
14413
- if (bundledFonts?.[k]) return b64ToBuffer(bundledFonts[k]);
14414
- return (await fetchFont(k)).slice(0);
14415
- };
14416
- function fontStore() {
14417
- if (!mainStore) mainStore = new FontStore(getFont);
14418
- return mainStore;
14419
- }
14420
- var makeWorker = true ? null : () => new Worker(new URL("../worker/engine.worker.js", import.meta.url), { type: "module" });
14421
- var fontList = null;
14422
- function customFonts(refresh = false) {
14423
- if (!fontList || refresh) {
14424
- fontList = (bundledList ? Promise.resolve(bundledList) : standalone ? Promise.resolve([]) : fetch(`${assetBase}/api/fonts`).then((r) => r.ok ? r.json() : []).catch(() => [])).then((list) => {
14425
- setCustomFonts(list);
14426
- return list;
14427
- });
14428
- }
14429
- return fontList;
14430
- }
14431
- var faces = /* @__PURE__ */ new Map();
14432
- function loadReportFonts(keys) {
14433
- if (typeof document === "undefined" || !document.fonts || typeof FontFace === "undefined") return Promise.resolve([]);
14434
- return Promise.all(keys.filter((k) => k !== SHAPER).map((k) => {
14435
- if (!faces.has(k)) {
14436
- const bytes = bundledFonts?.[k] ? Promise.resolve(b64ToBuffer(bundledFonts[k])) : fetchFont(k).then((b) => b.slice(0));
14437
- faces.set(k, bytes.then((src) => {
14438
- const face = new FontFace(fontFamilyCss(k), src, { display: "block" });
14439
- document.fonts.add(face);
14440
- return face.load();
14441
- }).then(() => null, (error) => {
14442
- faces.delete(k);
14443
- return { key: k, error };
14444
- }));
14445
- }
14446
- return faces.get(k);
14447
- })).then((results) => results.filter(Boolean));
14448
- }
14449
- var loadModelFonts = (model) => loadReportFonts([.../* @__PURE__ */ new Set([...ALL_FONT_KEYS, ...fontKeysOf(model)])]);
14450
- function getWorker() {
14451
- if (!makeWorker || workerBroken || typeof Worker === "undefined" || assetBase || bundledFonts) return null;
14452
- if (worker) return worker;
14453
- try {
14454
- worker = makeWorker();
14455
- worker.onmessage = (e) => {
14456
- const w = waiting.get(e.data.id);
14457
- if (!w) return;
14458
- if (e.data.partial) {
14459
- w.onPartial?.(e.data.model);
14460
- return;
14461
- }
14462
- if (e.data.retry) {
14463
- w.onRetry?.(e.data.retry);
14464
- return;
14465
- }
14466
- waiting.delete(e.data.id);
14467
- if (e.data.ok) w.resolve(e.data.model);
14468
- else w.reject(Object.assign(new Error(e.data.error.message), e.data.error));
14469
- };
14470
- worker.onerror = () => {
14471
- workerBroken = true;
14472
- worker = null;
14473
- for (const [, w] of waiting) w.retry();
14474
- waiting.clear();
14475
- };
14476
- } catch {
14477
- workerBroken = true;
14478
- worker = null;
14479
- }
14480
- return worker;
14481
- }
14482
- var bundled = /* @__PURE__ */ new Map();
14483
- function registerReports(map) {
14484
- for (const [k, v] of Object.entries(map || {})) bundled.set(k, v);
14485
- }
14486
- var loadReport = async (rid) => {
14487
- if (bundled.has(rid)) return structuredClone(bundled.get(rid));
14488
- if (standalone) throw new Error(`Report "${rid}" is not loaded: pass it in reports: { "${rid}": definition }, or set server (a self-hosted ReportWright server)`);
14489
- const r = await fetch(`${assetBase}/api/reports/${encodeURIComponent(rid)}`);
14490
- if (!r.ok) throw new Error(`Report "${rid}" not found`);
14491
- return r.json();
14492
- };
14493
- var fetchReport = loadReport;
14494
- async function renderMain(def, params, state, reportId, exportData = false, onRetry = void 0, dataOnly = false) {
14495
- await customFonts();
14496
- return render(def, { parameters: params, state, reportId, exportData, dataOnly, fontStore: fontStore(), baseUrl: origin(), loadReport, fetch: reportFetch(origin(), void 0, { onRetry }) });
14497
- }
14498
- async function renderReport(def, params = {}, state = {}, reportId = void 0, { onPartial, exportData = false, onRetry, dataOnly = false } = {}) {
14499
- const w = getWorker();
14500
- if (!w) return renderMain(def, params, state, reportId, exportData, onRetry, dataOnly);
14501
- const fonts = await customFonts();
14502
- return new Promise((resolve, reject) => {
14503
- const id = ++seq;
14504
- waiting.set(id, { resolve, reject, onPartial, onRetry, retry: () => renderMain(def, params, state, reportId, exportData, onRetry, dataOnly).then(resolve, reject) });
14505
- w.postMessage({ id, def, params, state, reportId, fonts, firstPages: onPartial && !dataOnly ? 2 : 0, exportData, dataOnly });
14506
- });
14507
- }
14508
- var usingWorker = () => !!worker && !workerBroken;
14509
- async function iccProfile() {
14510
- const r = await fetch(fontsUrl ? besideFonts("icc/sRGB-v2-magic.icc") : `${origin()}/icc/sRGB-v2-magic.icc`);
14511
- if (!r.ok) throw new Error(`the PDF/A colour profile did not load (HTTP ${r.status})`);
14512
- return new Uint8Array(await r.arrayBuffer());
14513
- }
14514
- async function pdfBlob(model, title, o = {}) {
14515
- const store = fontStore();
14516
- await customFonts();
14517
- await store.load(fontKeysOf(model));
14518
- const { exportPdf } = await import("./chunk-5N6OFCHJ.js");
14519
- const bytes = await exportPdf(model, store, {
14520
- title,
14521
- subset: subsetter,
14522
- tagged: o.tagged,
14523
- drillBase: origin(),
14524
- // drill-through links open this viewer, as in the HTML export
14525
- pdfa: o.pdfa ? { icc: await iccProfile() } : void 0,
14526
- // a report's image: a GET under the same rules as its data (no cookies to our other routes or to other sites)
14527
- fetchImage: async (src) => {
14528
- try {
14529
- const r = await reportFetch(origin())(src);
14530
- return r.ok ? new Uint8Array(await r.arrayBuffer()) : null;
14531
- } catch {
14532
- return null;
14533
- }
14534
- }
14535
- });
14536
- return new Blob([bytes], { type: "application/pdf" });
14537
- }
14538
-
14539
- // src/viewer/logic.js
14540
- var TOOLBAR_GROUP_KEYS = {
14541
- export: (i) => i.group === "export" && i.id !== "print",
14542
- search: (i) => i.group === "search",
14543
- zoom: (i) => i.group === "zoom",
14544
- navigation: (i) => i.group === "paging"
14545
- };
14546
- var TOOLBAR_ID_KEYS = { sidebar: ["thumbnails", "documentMap"] };
14547
- function arrangeToolbar(builtIns, { hide = [], add: add2 = [] } = {}) {
14548
- if (!hide.length && !add2.length) return builtIns;
14549
- const out = [...builtIns];
14550
- const taken = new Set(builtIns.map((i) => i.id));
14551
- for (const a of add2) {
14552
- if (!a || !a.id || taken.has(a.id)) continue;
14553
- taken.add(a.id);
14554
- const at = out.findIndex((i) => i.id === a.after);
14555
- const item = { ...a, custom: true, group: at >= 0 ? out[at].group : `custom:${a.id}` };
14556
- if (at >= 0) out.splice(at + 1, 0, item);
14557
- else out.push(item);
14558
- }
14559
- const hiddenIds = /* @__PURE__ */ new Set();
14560
- const hiddenBy = [];
14561
- for (const k of hide) {
14562
- if (Object.hasOwn(TOOLBAR_GROUP_KEYS, k)) hiddenBy.push(TOOLBAR_GROUP_KEYS[k]);
14563
- else for (const id of Object.hasOwn(TOOLBAR_ID_KEYS, k) ? TOOLBAR_ID_KEYS[k] : [k]) hiddenIds.add(id);
14564
- }
14565
- let kept = out.filter((i) => i.custom || !(hiddenIds.has(i.id) || hiddenBy.some((f4) => f4(i))));
14566
- if (!kept.some((i) => i.format)) kept = kept.filter((i) => i.custom || i.id !== "exportOptions");
14567
- return kept;
14568
- }
14569
- function galleyDefinition(def) {
14570
- const pg = resolvePage(def.page);
14571
- if (pg.pageless) return def;
14572
- const m = pg.margins;
14573
- return { ...def, page: { size: "Pageless", width: pg.width, margins: [m.top, m.right, m.bottom, m.left] } };
14574
- }
14575
- function frozenAt(page, y) {
14576
- for (const r of page?.frozen || []) if (y > r.y && y < r.end - r.h) return r;
14577
- return null;
14578
- }
14579
- var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14580
- function findText(model, q, { matchCase = false, wholeWord = false } = {}) {
14581
- if (!q || !model) return [];
14582
- const W2 = "[\\p{L}\\p{M}\\p{N}_]";
14583
- const re = new RegExp(wholeWord ? `(?<!${W2})${escapeRe(q)}(?!${W2})` : escapeRe(q), `u${matchCase ? "" : "i"}`);
14584
- const out = [];
14585
- model.pages.forEach((pg, pi) => {
14586
- const rows = /* @__PURE__ */ new Map();
14587
- pg.items.forEach((it, k) => {
14588
- if (it.t !== "text") return;
14589
- for (const l of it.lines) {
14590
- const key = it.rich ? `r${it.rich.id}${it.rich.l}` : `${k}${l.y}`;
14591
- const r = rows.get(key);
14592
- if (r) r.lines.push(l);
14593
- else rows.set(key, { rich: !!it.rich, size: it.size, lines: [l] });
14594
- }
14595
- });
14596
- for (const { rich, size, lines: row } of rows.values()) {
14597
- row.sort((a, b) => a.x - b.x);
14598
- const text = row.map((l) => l.text).join(rich ? "" : " ");
14599
- if (!re.test(text)) continue;
14600
- const x0 = row[0].x, last = row[row.length - 1];
14601
- const x1 = last.x + (last.w || size * last.text.length * 0.5);
14602
- const y = Math.max(...row.map((l) => l.y));
14603
- out.push({ page: pi, x: x0 - 1, y: y - size * 0.95, w: x1 - x0 + 2, h: size * 1.25, text });
14604
- }
14605
- });
14606
- return out;
14607
- }
14608
- function rangeValue(a, b, p) {
14609
- if (a === "" && b === "") return "";
14610
- const lo = a !== "" ? a : String(p.min ?? b);
14611
- const hi = b !== "" ? b : String(p.max ?? a);
14612
- return `${lo},${hi}`;
14613
- }
14614
- function pathBox(d) {
14615
- let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity, cx = 0, cy = 0;
14616
- const add2 = (x, y) => {
14617
- cx = x;
14618
- cy = y;
14619
- if (x < x0) x0 = x;
14620
- if (x > x1) x1 = x;
14621
- if (y < y0) y0 = y;
14622
- if (y > y1) y1 = y;
14623
- };
14624
- for (const [, c, args] of String(d || "").matchAll(/([MLHVCSQTAZ])([^MLHVCSQTAZ]*)/gi)) {
14625
- const n = (args.match(/-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/gi) || []).map(Number);
14626
- const C = c.toUpperCase();
14627
- if (C === "H") n.forEach((x) => add2(x, cy));
14628
- else if (C === "V") n.forEach((y) => add2(cx, y));
14629
- else if (C === "A") for (let i = 0; i + 6 < n.length; i += 7) add2(n[i + 5], n[i + 6]);
14630
- else if (C !== "Z") for (let i = 0; i + 1 < n.length; i += 2) add2(n[i], n[i + 1]);
14631
- }
14632
- return x0 <= x1 ? { x: x0, y: y0, w: x1 - x0, h: y1 - y0 } : null;
14633
- }
14634
- var itemsOf = (page) => (page?.items || []).flatMap((i) => i.t === "field" ? i.draw || [] : [i]);
14635
- function pageLinks(page) {
14636
- const items = itemsOf(page);
14637
- const texts = items.filter((i) => i.t === "text");
14638
- const out = [];
14639
- for (const it of items) {
14640
- if (it.t !== "link" || !it.action) continue;
14641
- let box = { x: it.x, y: it.y, w: it.w, h: it.h };
14642
- if (it.d) {
14643
- const b = pathBox(it.d);
14644
- if (!b) continue;
14645
- box = { ...b, x: b.x + (it.x - (it.x0 ?? it.x)), y: b.y + (it.y - (it.y0 ?? it.y)) };
14646
- }
14647
- if (!(box.w > 0 && box.h > 0)) continue;
14648
- const words = [];
14649
- for (const t of texts) for (const l of t.lines || []) {
14650
- const top = l.y - (t.size || 9) * 0.9, w = l.w || (t.size || 9) * String(l.text).length * 0.5;
14651
- if (l.x < box.x + box.w && l.x + w > box.x && top < box.y + box.h && l.y > box.y) words.push(String(l.text));
14652
- }
14653
- out.push({ action: it.action, ...box, path: !!it.d, tip: it.tip ? String(it.tip) : "", text: words.join(" ").replace(/^[▶▼]\s*/, "").replace(/\s*[↑↓]$/, "").trim() });
14654
- }
14655
- return out.sort((a, b) => Math.abs(a.y - b.y) > 2 ? a.y - b.y : a.x - b.x);
14656
- }
14657
- function readingText(page) {
14658
- const rows = /* @__PURE__ */ new Map();
14659
- for (const it of itemsOf(page)) {
14660
- if (it.t !== "text") continue;
14661
- for (const l of it.lines || []) {
14662
- const k = Math.round(l.y);
14663
- const r = rows.get(k);
14664
- if (r) r.push(l);
14665
- else rows.set(k, [l]);
14666
- }
14667
- }
14668
- return [...rows.entries()].sort((a, b) => a[0] - b[0]).map(([, ls]) => ls.sort((a, b) => a.x - b.x).map((l) => l.text).join(" ").trim()).filter(Boolean);
14669
- }
14670
- var EXPORT_DEFAULTS = { pdfua: false, pdfa: false, formulas: true, html: "tables" };
14671
- function exportOptionsFrom(stored) {
14672
- const { v, ...rest } = stored && typeof stored === "object" ? stored : {};
14673
- if (v !== 2) delete rest.html;
14674
- return { ...EXPORT_DEFAULTS, ...rest };
14675
- }
14676
- function pruneSnapshot(snapshot, defs) {
14677
- const text = defs.map((d) => JSON.stringify(d)).join("\n");
14678
- const seen = /* @__PURE__ */ new Map();
14679
- const used = (k) => {
14680
- let u = seen.get(k);
14681
- if (u === void 0) {
14682
- u = text.includes(JSON.stringify(k).slice(1, -1));
14683
- seen.set(k, u);
14684
- }
14685
- return u;
14686
- };
14687
- const walk = (x) => {
14688
- if (Array.isArray(x)) return x.map(walk);
14689
- if (!x || typeof x !== "object") return x;
14690
- const o = {};
14691
- for (const [k, y] of Object.entries(x)) if (y && typeof y === "object" || used(k)) o[k] = walk(y);
14692
- return o;
14693
- };
14694
- return Object.fromEntries(Object.entries(snapshot).map(([url, x]) => [url, walk(x)]));
14695
- }
14696
- var SERVER_PDF_ROWS = 2e4;
14697
- var SERVER_PDF_PAGES = 800;
14698
- function serverPdfUrl(model, { reportId, params = {}, origin: origin2, timeZone, dirty = false }) {
14699
- if (!reportId || dirty || !model) return null;
14700
- const rows = Object.values(model.stats?.rows || {}).reduce((a, n) => a + (Number(n) || 0), 0);
14701
- if (rows <= SERVER_PDF_ROWS && (model.pages?.length || 0) <= SERVER_PDF_PAGES) return null;
14702
- const q = new URLSearchParams();
14703
- for (const [k, v0] of Object.entries(params)) {
14704
- const v = Array.isArray(v0) ? v0.length > 1 ? void 0 : v0[0] : v0;
14705
- if (v === void 0 && Array.isArray(v0)) return null;
14706
- if (v == null) continue;
14707
- q.set(k, v instanceof Date ? v.toISOString() : String(v));
14708
- }
14709
- if (timeZone) q.set("timeZone", timeZone);
14710
- const s = q.toString();
14711
- return `${origin2}/api/reports/${encodeURIComponent(reportId)}/pdf${s ? `?${s}` : ""}`;
14712
- }
14713
- function paramChoices(def, fetched) {
14714
- const out = { ...fetched };
14715
- for (const p of def?.parameters || []) {
14716
- if (!Array.isArray(p.options) || !p.optionsFrom || !Array.isArray(fetched?.[p.name])) continue;
14717
- const fixed = p.options.map((o) => typeof o === "object" && o ? { value: o.value, label: String(o.label ?? o.value) } : { value: o, label: String(o) });
14718
- const seen = new Set(fixed.map((o) => String(o.value)));
14719
- out[p.name] = [...fixed, ...fetched[p.name].filter((o) => !seen.has(String(o.value)))];
14720
- }
14721
- return out;
14722
- }
14723
-
14724
13816
  export {
13817
+ setFetchPolicy,
13818
+ policyKeysIn,
13819
+ fetchPolicy,
14725
13820
  reportFetch,
14726
13821
  PAGE_SIZES,
14727
13822
  toPt,
14728
13823
  fromPt,
14729
13824
  resolvePage,
14730
13825
  normBox,
14731
- TextMeasurer,
14732
- finishText,
14733
13826
  isDynamic,
14734
13827
  evalValue,
14735
13828
  toText,
@@ -14774,33 +13867,6 @@ export {
14774
13867
  NO_CJK_FONTS,
14775
13868
  render,
14776
13869
  parameterOptions,
14777
- resolveParameters,
14778
- setAssetBase,
14779
- origin,
14780
- setStandalone,
14781
- fontsBase,
14782
- subsetter,
14783
- setBundledFonts,
14784
- fontStore,
14785
- customFonts,
14786
- loadReportFonts,
14787
- loadModelFonts,
14788
- registerReports,
14789
- fetchReport,
14790
- renderReport,
14791
- usingWorker,
14792
- pdfBlob,
14793
- arrangeToolbar,
14794
- galleyDefinition,
14795
- frozenAt,
14796
- findText,
14797
- rangeValue,
14798
- pageLinks,
14799
- readingText,
14800
- EXPORT_DEFAULTS,
14801
- exportOptionsFrom,
14802
- pruneSnapshot,
14803
- serverPdfUrl,
14804
- paramChoices
13870
+ resolveParameters
14805
13871
  };
14806
- //# sourceMappingURL=chunk-M6GENFAH.js.map
13872
+ //# sourceMappingURL=chunk-VNOWPSE6.js.map