@reportwright/viewer 0.11.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 +81 -0
  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-HAMBWF6C.js → chunk-NR2G4TMG.js} +39 -31
  18. package/dist/chunk-NR2G4TMG.js.map +6 -0
  19. package/dist/{chunk-66QZ3RDV.js → chunk-PGP3QA6L.js} +526 -508
  20. package/dist/chunk-PGP3QA6L.js.map +6 -0
  21. package/dist/{chunk-N5WHQ4DM.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-WQOBRZAL.js → chunk-VNOWPSE6.js} +579 -1484
  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 +28 -18
  33. package/dist/designer.js.map +1 -1
  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-776OR56A.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-5X4ZSSJT.js → chunk-TSC4RAH3.js} +526 -508
  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-EKWTFHVE.js → chunk-WSNRD2AQ.js} +14 -9
  49. package/dist/peer/chunk-WSNRD2AQ.js.map +6 -0
  50. package/dist/peer/designer.js +28 -18
  51. package/dist/peer/designer.js.map +1 -1
  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-66QZ3RDV.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-HAMBWF6C.js.map +0 -6
  65. package/dist/chunk-N5WHQ4DM.js.map +0 -6
  66. package/dist/chunk-SBD55ZES.js.map +0 -6
  67. package/dist/chunk-SR2TQ2ZK.js.map +0 -6
  68. package/dist/chunk-WQOBRZAL.js.map +0 -6
  69. package/dist/peer/chunk-5X4ZSSJT.js.map +0 -6
  70. package/dist/peer/chunk-6YO42ALN.js +0 -14777
  71. package/dist/peer/chunk-6YO42ALN.js.map +0 -6
  72. package/dist/peer/chunk-76ZABVTI.js.map +0 -6
  73. package/dist/peer/chunk-776OR56A.js.map +0 -6
  74. package/dist/peer/chunk-EKWTFHVE.js.map +0 -6
  75. package/dist/peer/chunk-IMQRHDR4.js +0 -12
  76. package/dist/peer/chunk-IPU2YRL2.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;
@@ -1735,321 +2019,73 @@ var METHODS = {
1735
2019
  trim: "trim",
1736
2020
  trimstart: "trimstart",
1737
2021
  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"
2022
+ startswith: "startswith",
2023
+ endswith: "endswith",
2024
+ indexof: "indexof",
2025
+ lastindexof: "lastindexof",
2026
+ contains: "contains",
2027
+ replace: "replace",
2028
+ split: "split",
2029
+ tostring: "tostring"
2026
2030
  };
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;
2032
- }
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 });
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,13 +8663,14 @@ 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 = {};
9164
8670
  (item.groups || []).forEach((g, i) => {
9165
8671
  if (g.name) groupChains[g.name] = { rows, keys: item.groups.slice(0, i + 1).map((x) => x.expr) };
9166
8672
  });
9167
- const base = { ...tctx, aggRows: rows, aggIndex: void 0, fields: rows[0] || null, scopes, region: tableScope, group: void 0, groupChains: { ...tctx.groupChains || {}, ...groupChains } };
8673
+ const base = { ...tctx, aggRows: rows, aggIndex: void 0, fields: lc.win?.first || rows[0] || null, scopes, region: tableScope, group: void 0, groupChains: { ...tctx.groupChains || {}, ...groupChains } };
9168
8674
  const units = [];
9169
8675
  let y = 0;
9170
8676
  let repeatH = 0;
@@ -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;
@@ -9402,7 +8908,8 @@ register("table", {
9402
8908
  openSpans = [];
9403
8909
  };
9404
8910
  const fl = base.bandRows === "first-last";
9405
- const headerUnits = (item.header || []).map((r) => layRow(r, fl && rows.length ? { ...base, pendingRows: [rows[0]] } : base, "header")).filter(Boolean);
8911
+ const first0 = lc.win?.first || rows[0];
8912
+ const headerUnits = (item.header || []).map((r) => layRow(r, fl && rows.length ? { ...base, pendingRows: [first0] } : base, "header")).filter(Boolean);
9406
8913
  endBand();
9407
8914
  const headerHeight = headerUnits.reduce((s, u) => s + u.height, 0);
9408
8915
  if (item.repeatHeader !== false) repeatH = headerHeight;
@@ -9416,9 +8923,9 @@ register("table", {
9416
8923
  y += tl.needed;
9417
8924
  }
9418
8925
  const groups = item.groups || [];
9419
- let rowNo = 0;
8926
+ let rowNo = lc.win?.rowOffset || 0;
9420
8927
  const groupTables = [];
9421
- const tableKey = `${item.name}#${lc.nextId()}`;
8928
+ const tableKey = `${item.name || item.type}#${lc.nextId()}`;
9422
8929
  const emit = (level, grows, ctx, chain, keyPath) => {
9423
8930
  if (level === groups.length) {
9424
8931
  for (let i = 0; i < grows.length; i++) {
@@ -9436,9 +8943,16 @@ register("table", {
9436
8943
  const dctx = { ...ctx, fields: grows[i], aggRows: grows, aggIndex: i, rowNumber: rowNo };
9437
8944
  const u0 = units.length;
9438
8945
  for (const r of item.detail || []) layRow(r, dctx, "detail", { level: groups.length });
9439
- if (groups.length && item.widowControl !== false) for (let k = u0; k < units.length; k++) units[k].grp = tableKey + keyPath;
8946
+ if (lc.win && units[u0]) {
8947
+ units[u0].rowHead = true;
8948
+ for (let k = u0; k < units.length; k++) units[k].dataRow = rowNo;
8949
+ }
8950
+ if (groups.length && item.widowControl !== false) for (let k = u0; k < units.length; k++) {
8951
+ units[k].grp = tableKey + keyPath;
8952
+ if (ctx.__before) units[k].grpBefore = ctx.__before;
8953
+ }
9440
8954
  const every = Math.trunc(Number(item.pageBreakEvery)) || 0;
9441
- if (every > 0 && rowNo > 1 && (rowNo - 1) % every === 0 && units[u0]) units[u0].breakBefore = true;
8955
+ if (every > 0 && rowNo > (lc.win?.rowOffset || 0) + 1 && (rowNo - 1) % every === 0 && units[u0]) units[u0].breakBefore = true;
9442
8956
  endBand();
9443
8957
  }
9444
8958
  return;
@@ -9473,7 +8987,7 @@ register("table", {
9473
8987
  kids = /* @__PURE__ */ new Map();
9474
8988
  roots = [];
9475
8989
  for (const b of buckets) {
9476
- 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;
9477
8991
  const pr = pk == null || pk === "" ? null : byText.get(pk instanceof Date ? String(pk.getTime()) : String(pk));
9478
8992
  if (!pr || pr === b) roots.push(b);
9479
8993
  else {
@@ -9495,24 +9009,29 @@ register("table", {
9495
9009
  }
9496
9010
  const shown = /* @__PURE__ */ new Set();
9497
9011
  const node = (bk, bi, sibs, depth, parentPath) => {
9498
- var _a;
9499
9012
  if (lc.rowBudget?.cut || shown.has(bk)) return;
9500
9013
  shown.add(bk);
9501
9014
  const sc = { rows: bk.rows, index: -1, run: [], number: bi + 1, prevRows: bi > 0 ? sibs[bi - 1].rows : null, siblings: sibRows(sibs), ...kids ? { level: depth, recRows: recOf.get(bk) } : {} };
9502
- const gctx = { ...ctx, fields: bk.rows[0], aggRows: bk.rows, aggIndex: void 0, scopes: { ...ctx.scopes, [gname]: sc }, groupKey: bk.key, group: sc };
9015
+ const cont = bi === 0 && (level === 0 || ctx.__cont) ? lc.win?.cont?.[level] : null;
9016
+ const gctx = { ...ctx, fields: cont ? cont.first : bk.rows[0], aggRows: bk.rows, aggIndex: void 0, scopes: { ...ctx.scopes, [gname]: sc }, groupKey: bk.key, group: sc, __cont: !!cont, __before: cont && level === groups.length - 1 ? cont.before : 0 };
9503
9017
  if (kids) {
9504
9018
  gctx.level = depth;
9505
9019
  if (g.indent) gctx.indent = (ctx.indent || 0) + depth * Number(g.indent);
9506
9020
  }
9507
9021
  const path = `${parentPath}/${bk.text}`;
9508
- const tkey = `${item.name}|${level}|${path}`;
9022
+ const tkey = `${item.name || item.type}|${level}|${path}`;
9509
9023
  const dflt = kids && g.expandLevels != null ? depth < Number(g.expandLevels) : !g.initiallyCollapsed;
9510
9024
  const open = !g.collapsible ? true : lc.state?.toggles?.[tkey] ?? dflt;
9511
9025
  const start = units.length;
9512
- const hctx = g.header?.length ? { ...gctx, pendingRows: fl ? [bk.rows[0]] : recOf.get(bk) || bk.rows } : gctx;
9513
- const hu = (g.header || []).map((r, ri) => layRow(r, hctx, "groupHeader", { level, ...ri === 0 && g.collapsible ? { toggle: { key: tkey, open } } : {} })).filter(Boolean);
9026
+ const hctx = g.header?.length ? { ...gctx, pendingRows: fl ? [cont ? cont.first : bk.rows[0]] : recOf.get(bk) || bk.rows } : gctx;
9027
+ const hu = (cont && !g.repeatHeader ? [] : g.header || []).map((r, ri) => layRow(r, hctx, "groupHeader", { level, ...ri === 0 && g.collapsible ? { toggle: { key: tkey, open } } : {} })).filter(Boolean);
9514
9028
  endBand();
9515
9029
  const before = units.length;
9030
+ if (lc.win && !cont && before > start) {
9031
+ for (let k = start; k < before; k++) units[k].dataRow = rowNo + 1;
9032
+ units[start].rowHead = true;
9033
+ units[start].headLevel = level;
9034
+ }
9516
9035
  const rep = g.repeatHeader && before > start && !lc.fixed ? [...ctx.__rep || [], (() => {
9517
9036
  const hdr = units.slice(start, before), h0 = hdr[0].top, its = [];
9518
9037
  for (const u of hdr) for (const it of shiftLocal(u.items.filter((i) => i.t !== "link" && i.t !== "bookmark"), u.top - h0)) its.push(it);
@@ -9522,7 +9041,7 @@ register("table", {
9522
9041
  const ch = kids?.get(bk) || [];
9523
9042
  if (open) {
9524
9043
  emit(level + 1, bk.rows, gctx, [...chain, sc], path);
9525
- 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`);
9526
9045
  else ch.forEach((c, ci) => node(c, ci, ch, depth + 1, path));
9527
9046
  } else {
9528
9047
  const rs = recOf.get(bk) || bk.rows;
@@ -9551,8 +9070,11 @@ register("table", {
9551
9070
  if (g.newSection) units[start].sectionStart = true;
9552
9071
  if (g.pageBreak === "after" && bi < sibs.length - 1) units[units.length - 1].breakAfter = true;
9553
9072
  if (g.keepTogether) {
9554
- const id = `${item.name}:g${level}:${path}:${lc.nextId()}`;
9555
- for (let k = start; k < units.length; k++) (_a = units[k]).block || (_a.block = id);
9073
+ const id = `${item.name || item.type}:g${level}:${path}:${lc.nextId()}`;
9074
+ for (let k = start; k < units.length; k++) if (!units[k].block) {
9075
+ units[k].block = id;
9076
+ if (cont) units[k].blockOpen = true;
9077
+ }
9556
9078
  }
9557
9079
  }
9558
9080
  };
@@ -9569,7 +9091,7 @@ register("table", {
9569
9091
  if (kids) {
9570
9092
  const rest = buckets.filter((b) => !shown.has(b));
9571
9093
  if (rest.length) {
9572
- 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`);
9573
9095
  rest.forEach((bk, bi) => node(bk, bi, rest, 0, keyPath));
9574
9096
  }
9575
9097
  }
@@ -9597,7 +9119,7 @@ register("table", {
9597
9119
  units[units.length - 1].breakAfter = true;
9598
9120
  }
9599
9121
  if (item.bookmark && units.length) {
9600
- 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;
9601
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 });
9602
9124
  }
9603
9125
  if (!lc.fixed) lc.regions?.push(region);
@@ -9692,7 +9214,7 @@ register("container", {
9692
9214
  const ctx = lc.ctxFor(item);
9693
9215
  if (hidden(item, ctx, lc)) return { height: 0, units: [] };
9694
9216
  const res = lc.layoutItems(item.items, lc, { dx: item.x });
9695
- 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);
9696
9218
  }
9697
9219
  });
9698
9220
  function frame2(item, lc, ctx, res, blockId0) {
@@ -9749,7 +9271,7 @@ register("list", {
9749
9271
  let rows = item.dataSet && lc.dataSets[item.dataSet] || [];
9750
9272
  if (item.inheritRows && item.dataSet && ctx.current?.[item.dataSet]) rows = ctx.current[item.dataSet];
9751
9273
  else rows = related(rows, ctx);
9752
- 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`);
9753
9275
  if (item.filters?.length) rows = applyFilters(rows, item.filters, ctx);
9754
9276
  if (item.sort?.length) rows = applySort(rows, item.sort, ctx);
9755
9277
  const reps = item.groupBy ? groupRows(rows, item.groupBy, ctx, lc, item.name, item.groupAdjacent === true).map((b) => b.rows) : rows.map((r) => [r]);
@@ -9778,7 +9300,7 @@ register("list", {
9778
9300
  } };
9779
9301
  const k = i % ncol;
9780
9302
  const res = lc.layoutItems(item.items, child, { dx: item.x + k * (item.w + gap) });
9781
- 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);
9782
9304
  if (ncol > 1) {
9783
9305
  if (k === 0) {
9784
9306
  cells = { top: y, height: 0, items: [], x0: item.x, x1: item.x + ncol * item.w + (ncol - 1) * gap, block: void 0 };
@@ -10249,7 +9771,7 @@ register("subreport", {
10249
9771
  if (hidden(item, ctx, lc)) return { height: 0, units: [] };
10250
9772
  const params = {};
10251
9773
  for (const [k, v] of Object.entries(item.params || {})) {
10252
- const r = evalSafe(v, ctx, lc, `${item.name} parameter ${k}`);
9774
+ const r = evalSafe(v, ctx, lc, `${item.name || item.type} parameter ${k}`);
10253
9775
  params[k] = r.v instanceof Date ? isoDate(r.v, ctx.timeZone) : r.v;
10254
9776
  }
10255
9777
  const key = `${item.report}|${JSON.stringify(params)}`;
@@ -10268,10 +9790,10 @@ register("subreport", {
10268
9790
  const res = sub.results.get(key);
10269
9791
  if (!res) return placeholder(`Subreport "${item.report}" did not load`);
10270
9792
  if (res.error) {
10271
- lc.warn(`${item.name}: ${res.error}`);
9793
+ lc.warn(`${item.name || item.type}: ${res.error}`);
10272
9794
  return placeholder(`Subreport "${item.report}": ${res.error}`);
10273
9795
  }
10274
- 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;
10275
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 }));
10276
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()}` }));
10277
9799
  for (const r of res.regions || []) lc.regions?.push(r);
@@ -10478,7 +10000,8 @@ function paginate(units, tables, pageH, warn, stats) {
10478
10000
  if (b) {
10479
10001
  b.top = Math.min(b.top, u.top);
10480
10002
  b.bottom = Math.max(b.bottom, u.top + u.height);
10481
- } else blockMap.set(u.block, { top: u.top, bottom: u.top + u.height });
10003
+ if (u.blockOpen) b.open = true;
10004
+ } else blockMap.set(u.block, { top: u.top, bottom: u.top + u.height, open: !!u.blockOpen });
10482
10005
  }
10483
10006
  const blockUnits = /* @__PURE__ */ new Map();
10484
10007
  for (const u of units) if (u.block) {
@@ -10486,18 +10009,18 @@ function paginate(units, tables, pageH, warn, stats) {
10486
10009
  if (!l) blockUnits.set(u.block, l = []);
10487
10010
  l.push(u);
10488
10011
  }
10489
- const blocks = [...blockMap.values()].filter((b) => b.bottom - b.top <= pageH + EPS2);
10012
+ const blocks = [...blockMap.values()].filter((b) => !b.open && b.bottom - b.top <= pageH + EPS2);
10490
10013
  for (const b of blocks) cand.push(b.top, b.bottom);
10491
10014
  cand.sort((a, b) => a - b);
10492
10015
  const grpRows = /* @__PURE__ */ new Map();
10493
10016
  for (const u of units) if (u.grp) {
10494
10017
  let g = grpRows.get(u.grp);
10495
- if (!g) grpRows.set(u.grp, g = { tops: /* @__PURE__ */ new Set(), first: u.top, last: 0, block: u.block || null });
10018
+ if (!g) grpRows.set(u.grp, g = { tops: /* @__PURE__ */ new Set(), first: u.top, last: 0, block: u.block || null, before: u.grpBefore || 0 });
10496
10019
  g.tops.add(u.top);
10497
10020
  g.last = Math.max(g.last, u.top + u.height);
10498
10021
  if (g.block !== (u.block || null)) g.block = void 0;
10499
10022
  }
10500
- const widowGroups = [...grpRows.values()].map((g) => ({ first: g.first, last: g.last, tops: [...g.tops].sort((a, b) => a - b), block: g.block })).filter((g) => g.tops.length >= 4 || g.tops.length >= 2 && g.last - g.first <= pageH + EPS2).sort((a, b) => a.first - b.first);
10023
+ const widowGroups = [...grpRows.values()].map((g) => ({ first: g.first, last: g.last, tops: [...g.tops].sort((a, b) => a - b), block: g.block, before: g.before, n: g.tops.size + g.before })).filter((g) => g.n >= 4 || !g.before && g.n >= 2 && g.last - g.first <= pageH + EPS2).sort((a, b) => a.first - b.first);
10501
10024
  const wFirst = widowGroups.map((g) => g.first);
10502
10025
  const wMaxLast = [];
10503
10026
  widowGroups.forEach((g, i) => wMaxLast.push(Math.max(i ? wMaxLast[i - 1] : -Infinity, g.last)));
@@ -10524,9 +10047,9 @@ function paginate(units, tables, pageH, warn, stats) {
10524
10047
  tick(1);
10525
10048
  const g = widowGroups[i];
10526
10049
  if (g.last <= y + EPS2 || g.block && !withBlocks) continue;
10527
- if (g.tops.length < 4) return false;
10528
- const above = rowsAbove(g.tops, y);
10529
- if (above < 2 || g.tops.length - above < 2) return false;
10050
+ if (g.n < 4) return false;
10051
+ const above = g.before + rowsAbove(g.tops, y);
10052
+ if (above < 2 || g.n - above < 2) return false;
10530
10053
  }
10531
10054
  return true;
10532
10055
  };
@@ -10581,7 +10104,7 @@ function paginate(units, tables, pageH, warn, stats) {
10581
10104
  if (u.block) {
10582
10105
  const bl = blockMap.get(u.block);
10583
10106
  if (movedBlocks.has(bl)) continue;
10584
- if (!bl || bl.bottom - bl.top > room + EPS2 || bl.top <= top2 + EPS2) return null;
10107
+ if (!bl || bl.open || bl.bottom - bl.top > room + EPS2 || bl.top <= top2 + EPS2) return null;
10585
10108
  movedBlocks.add(bl);
10586
10109
  for (const m of blockUnits.get(u.block)) if (!placed.has(m)) moved.push(m);
10587
10110
  continue;
@@ -10763,7 +10286,7 @@ function layoutItems(items, lc, { dx = 0 } = {}) {
10763
10286
  const own = L.res.table ? { id: lc.nextId(), ...L.res.table } : null;
10764
10287
  const us = L.res.units;
10765
10288
  const alt = it.alt || L.res.alt;
10766
- 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;
10767
10290
  for (let k = 0; k < us.length; k++) {
10768
10291
  const u = us[k];
10769
10292
  u.x0 = u.x0 ?? x0;
@@ -10772,7 +10295,7 @@ function layoutItems(items, lc, { dx = 0 } = {}) {
10772
10295
  u.tableId = own ? own.id : u.tableId;
10773
10296
  u.breakBefore = u.breakBefore || k === 0 && !!it.pageBreakBefore;
10774
10297
  u.breakAfter = u.breakAfter || k === us.length - 1 && !!it.pageBreakAfter;
10775
- 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);
10776
10299
  units.push(u);
10777
10300
  }
10778
10301
  if (own) tables.push({ ...own, top, bottom: top + L.res.height, x0, x1 });
@@ -13321,6 +12844,7 @@ var SCHEMA_ALIAS = "reportwright/report@1";
13321
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\
13322
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\
13323
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"]);
13324
12848
  function looksLikeLink(it) {
13325
12849
  if (it.type !== "textbox" || it.action && it.action.type && it.action.type !== "none") return false;
13326
12850
  const st = it.style && typeof it.style === "object" ? it.style : {};
@@ -13449,6 +12973,9 @@ function validate(def) {
13449
12973
  if (p?.hidden && p.required && (p.default == null || p.default === "") && !p.nullable) {
13450
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`);
13451
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
+ }
13452
12979
  if (p?.type === "date" && typeof p.default === "string" && p.default.trim() && !p.default.startsWith("=") && !toDate(p.default)) {
13453
12980
  warnings.push(`Parameter "${p.name}": its default "${p.default}" is not a date; write it as 2026-12-31 (year-month-day)`);
13454
12981
  }
@@ -13529,7 +13056,7 @@ function validate(def) {
13529
13056
  warnings.push(`${label}: there is no named style "${it.styleName}"${g ? ` (did you mean "${g}"?)` : ""}; it is ignored`);
13530
13057
  }
13531
13058
  if (Array.isArray(it.sort)) sortIssues(it.sort, label, warnings);
13532
- 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)" }]`);
13533
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`);
13534
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`);
13535
13062
  const own = ownKeys(it.type);
@@ -13572,6 +13099,9 @@ function validate(def) {
13572
13099
  const n = Array.isArray(it.columns) ? it.columns.length : 0;
13573
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]])];
13574
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`);
13575
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)`);
13576
13106
  });
13577
13107
  }
@@ -13670,7 +13200,7 @@ function* textsOf(def, dataSets) {
13670
13200
  }
13671
13201
  }
13672
13202
  }
13673
- 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";
13674
13204
  async function render(def0, opts = {}) {
13675
13205
  const t0 = Date.now();
13676
13206
  const reuseWarnings = [];
@@ -13741,6 +13271,7 @@ async function render(def0, opts = {}) {
13741
13271
  const base = { ...BASE_STYLE, ...def.styles?.base || {} };
13742
13272
  let idSeq = 0;
13743
13273
  const regions = [];
13274
+ const tablesSeen = { n: 0 };
13744
13275
  const imageBudget = opts.imageBudget || { seen: /* @__PURE__ */ new Set(), bytes: 0 };
13745
13276
  const makeLc = (globals, fixed) => {
13746
13277
  const lc = {
@@ -13751,6 +13282,7 @@ async function render(def0, opts = {}) {
13751
13282
  fixed,
13752
13283
  maxUnitHeight: contentH,
13753
13284
  state: opts.state || {},
13285
+ tablesSeen,
13754
13286
  regions: opts.exportData || opts.dataOnly ? regions : null,
13755
13287
  dataOnly: !!opts.dataOnly,
13756
13288
  // exportData: true: table data and cell tags (Excel, CSV, Word, HTML, accessible PDF)
@@ -13770,14 +13302,16 @@ async function render(def0, opts = {}) {
13770
13302
  ctxFor: (item) => {
13771
13303
  const ds = item.dataSet || defaultDs;
13772
13304
  const rows = ds && dataSets[ds] || [];
13773
- return { ...baseCtx, globals, fields: rows[0] || null, aggRows: rows };
13305
+ return { ...baseCtx, globals, fields: (opts.pageWindow?.first && ds === defaultDs ? opts.pageWindow.first : rows[0]) || null, aggRows: rows };
13774
13306
  },
13775
13307
  subreports: null,
13776
13308
  bodyWidth: opts.bodyWidth ?? page.bodyWidth,
13777
13309
  images: def.images || {},
13778
13310
  imageBudget,
13779
- cellItems: { left: 1e5 }
13311
+ cellItems: { left: 1e5 },
13780
13312
  // rows of items inside table cells, for the whole render (items/index.js)
13313
+ win: opts.pageWindow || null
13314
+ // paged.js: units carry their row number (dataRow, rowHead), rows and groups continue
13781
13315
  };
13782
13316
  return lc;
13783
13317
  };
@@ -13916,12 +13450,14 @@ async function render(def0, opts = {}) {
13916
13450
  const bodyTop = mt + g.hH;
13917
13451
  const items = [];
13918
13452
  const cut = lc.rowBudget?.cut;
13919
- const globals = { ...baseCtx.globals, PageNumber: n - secStart[n] + 1, TotalPages: cut ? "…" : secLen.get(secStart[n]), OverallPageNumber: n + 1, OverallTotalPages: cut ? "…" : total };
13453
+ const win = opts.pageWindow, off = win ? win.offset : 0, all = win ? win.total ?? "…" : null;
13454
+ const globals = { ...baseCtx.globals, PageNumber: n - secStart[n] + 1 + (secStart[n] ? 0 : off), TotalPages: cut ? "…" : all ?? secLen.get(secStart[n]), OverallPageNumber: n + 1 + off, OverallTotalPages: cut ? "…" : all ?? total };
13455
+ const isFirst = n + off === 0, isLast = n === total - 1 && (!win || win.final);
13920
13456
  const plc = makeLc(globals, true);
13921
- const band = (section, y0, isFirst, isLast) => {
13457
+ const band = (section, y0, isFirst2, isLast2) => {
13922
13458
  if (!section) return;
13923
- if (isFirst && section.printOnFirstPage === false) return;
13924
- if (isLast && section.printOnLastPage === false) return;
13459
+ if (isFirst2 && section.printOnFirstPage === false) return;
13460
+ if (isLast2 && section.printOnLastPage === false) return;
13925
13461
  const capped2 = () => warn(`The page headers and footers would draw more than ${BAND_ITEMS.toLocaleString("en-US")} items in this report; they are left out of the later pages. Make them lighter, or the report shorter.`);
13926
13462
  if (bandWork.left <= 0) {
13927
13463
  capped2();
@@ -13942,7 +13478,7 @@ async function render(def0, opts = {}) {
13942
13478
  for (const u of r.units) if (c.paged) placeItems(u.items, ml, y0 + u.top, items);
13943
13479
  else for (const it of shiftItems(u.items, ml, y0 + u.top)) items.push(it);
13944
13480
  };
13945
- band(g.hdr, mt, g.own.h ? k === 0 : n === 0, g.own.h ? last : n === total - 1);
13481
+ band(g.hdr, mt, g.own.h ? k === 0 : isFirst, g.own.h ? last : isLast);
13946
13482
  const frozen = [];
13947
13483
  for (let c = 0; c < colCount; c++) {
13948
13484
  const p = slices[k * colCount + c];
@@ -13952,30 +13488,38 @@ async function render(def0, opts = {}) {
13952
13488
  for (const t of body.tables) {
13953
13489
  if (!(t.top < p.bottom - EPS4 && t.bottom > p.top + EPS4)) continue;
13954
13490
  const cont = p.repeat.includes(t);
13955
- let off = 0;
13491
+ let off2 = 0;
13956
13492
  if (!cont) {
13957
- for (const r of p.repeat) if (t.x0 < r.x1 - EPS4 && r.x0 < t.x1 - EPS4) off = Math.max(off, r.headerHeight);
13493
+ for (const r of p.repeat) if (t.x0 < r.x1 - EPS4 && r.x0 < t.x1 - EPS4) off2 = Math.max(off2, r.headerHeight);
13958
13494
  }
13959
- const y = cont ? bodyTop : bodyTop + off + t.top - p.top;
13960
- frozen.push({ x: cx + t.x0, y, w: t.x1 - t.x0, h: t.headerHeight, end: bodyTop + (cont ? t.headerHeight : off) + Math.min(t.bottom, p.bottom) - p.top });
13495
+ const y = cont ? bodyTop : bodyTop + off2 + t.top - p.top;
13496
+ frozen.push({ x: cx + t.x0, y, w: t.x1 - t.x0, h: t.headerHeight, end: bodyTop + (cont ? t.headerHeight : off2) + Math.min(t.bottom, p.bottom) - p.top });
13961
13497
  }
13962
13498
  for (const u of p.units) {
13963
- let off = 0;
13964
- for (const t of p.repeat) if (u.x0 < t.x1 - EPS4 && t.x0 < u.x1 - EPS4) off = Math.max(off, t.headerHeight);
13965
- if (u.atBottom) off += Math.max(0, cH - off - (p.bottom - p.top));
13966
- if (u.cont && u.top <= p.top + EPS4) placeItems(u.cont, cx, bodyTop + off, items);
13967
- placeItems(u.items, cx, bodyTop + off + (u.top - p.top), items);
13499
+ let off2 = 0;
13500
+ for (const t of p.repeat) if (u.x0 < t.x1 - EPS4 && t.x0 < u.x1 - EPS4) off2 = Math.max(off2, t.headerHeight);
13501
+ if (u.atBottom) off2 += Math.max(0, cH - off2 - (p.bottom - p.top));
13502
+ if (u.cont && u.top <= p.top + EPS4) placeItems(u.cont, cx, bodyTop + off2, items);
13503
+ placeItems(u.items, cx, bodyTop + off2 + (u.top - p.top), items);
13968
13504
  }
13969
13505
  }
13970
- band(g.ftr, ph - pg.margins.bottom - g.fH, g.own.f ? k === 0 : n === 0, g.own.f ? last : n === total - 1);
13506
+ band(g.ftr, ph - pg.margins.bottom - g.fH, g.own.f ? k === 0 : isFirst, g.own.f ? last : isLast);
13971
13507
  let j = 0;
13972
13508
  for (let i = 0; i < items.length; i++) {
13973
13509
  const it = items[i];
13974
- if (it.t === "bookmark") (it.heading ? headings : bookmarks2).push({ label: it.label, page: n + 1, y: it.y, level: it.level || 0, ...secStart[n] ? { shown: n - secStart[n] + 1 } : {} });
13510
+ if (it.t === "bookmark") (it.heading ? headings : bookmarks2).push({ label: it.label, page: n + 1 + off, y: it.y, level: it.level || 0, ...secStart[n] ? { shown: n - secStart[n] + 1 } : {} });
13975
13511
  else items[j++] = it;
13976
13512
  }
13977
13513
  items.length = j;
13978
13514
  const out = frozen.length ? { items, frozen } : { items };
13515
+ if (win) {
13516
+ const u = slices[k * colCount]?.units[0];
13517
+ if (u?.dataRow != null) {
13518
+ out.firstRow = u.dataRow;
13519
+ out.rowHead = !!u.rowHead;
13520
+ if (u.headLevel != null) out.headLevel = u.headLevel;
13521
+ }
13522
+ }
13979
13523
  if (pg.width !== first.width || ph !== pageH) {
13980
13524
  out.width = pg.width;
13981
13525
  out.height = ph;
@@ -14041,9 +13585,9 @@ async function render(def0, opts = {}) {
14041
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.`);
14042
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).");
14043
13587
  if (m.noGlyph.size) {
14044
- 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));
14045
13589
  const list = [...m.noGlyph].slice(0, 20).join(" ");
14046
- 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.`);
14047
13591
  }
14048
13592
  if (opts.bodyOnly) return { units: result.body.units, tables: result.body.tables, height: result.body.height, regions, warnings, clipped: clipped() };
14049
13593
  if (opts.dataOnly) return {
@@ -14083,6 +13627,7 @@ async function render(def0, opts = {}) {
14083
13627
  headings: result.headings || [],
14084
13628
  pageStarts: result.pageStarts || [],
14085
13629
  regions: opts.exportData ? regions : null,
13630
+ hasTables: tablesSeen.n > 0,
14086
13631
  name: def.name || "",
14087
13632
  partial: cutRows(),
14088
13633
  timeZone: timeZone || null,
@@ -14219,7 +13764,8 @@ async function parameterOptions(def, given = {}, opts = {}) {
14219
13764
  seen.add(k);
14220
13765
  list.push({ value: v instanceof Date ? isoDate(v) : v, label: String(p.optionsFrom.label ? evalValue(p.optionsFrom.label, c) : v) });
14221
13766
  }
14222
- 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))];
14223
13769
  }
14224
13770
  return out;
14225
13771
  }
@@ -14267,440 +13813,16 @@ function resolveParameters(def, given, o = {}) {
14267
13813
  return out;
14268
13814
  }
14269
13815
 
14270
- // src/exporters/subset.js
14271
- var HB_MEMORY_MODE_WRITABLE = 2;
14272
- var HB_SUBSET_SETS_DROP_TABLE_TAG = 3;
14273
- var HB_SUBSET_SETS_LAYOUT_FEATURE_TAG = 6;
14274
- var HB_SUBSET_FLAGS_RETAIN_GIDS = 2;
14275
- var HB_SUBSET_FLAGS_NOTDEF_OUTLINE = 64;
14276
- var tag3 = (s) => [...s].reduce((a, c) => (a << 8) + c.charCodeAt(0), 0) >>> 0;
14277
- function createSubsetter(loadWasm) {
14278
- let hbP = null;
14279
- const hb = () => hbP || (hbP = (async () => {
14280
- const bytes = await loadWasm();
14281
- const { instance } = await WebAssembly.instantiate(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes), {});
14282
- const x = (
14283
- /** @type {any} */
14284
- instance.exports
14285
- );
14286
- x._initialize?.();
14287
- return x;
14288
- })());
14289
- return async function subset(font, codePoints, opt = {}) {
14290
- const x = await hb();
14291
- const heap = () => new Uint8Array(x.memory.buffer);
14292
- const input = x.hb_subset_input_create_or_fail();
14293
- if (!input) throw new Error("HarfBuzz: no subset input");
14294
- const buf = x.malloc(font.byteLength);
14295
- heap().set(font, buf);
14296
- const blob = x.hb_blob_create(buf, font.byteLength, HB_MEMORY_MODE_WRITABLE, 0, 0);
14297
- const face = x.hb_face_create(blob, 0);
14298
- x.hb_blob_destroy(blob);
14299
- try {
14300
- if (!opt.layout) {
14301
- x.hb_set_clear(x.hb_subset_input_set(input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG));
14302
- const drop = x.hb_subset_input_set(input, HB_SUBSET_SETS_DROP_TABLE_TAG);
14303
- for (const t of ["GSUB", "GPOS", "GDEF", "kern", "morx", "mort"]) x.hb_set_add(drop, tag3(t));
14304
- }
14305
- x.hb_subset_input_set_flags(input, x.hb_subset_input_get_flags(input) | HB_SUBSET_FLAGS_NOTDEF_OUTLINE);
14306
- const uni = x.hb_subset_input_unicode_set(input);
14307
- for (const cp of codePoints) x.hb_set_add(uni, cp);
14308
- if (opt.glyphs) {
14309
- x.hb_subset_input_set_flags(input, x.hb_subset_input_get_flags(input) | HB_SUBSET_FLAGS_RETAIN_GIDS);
14310
- const gs = x.hb_subset_input_glyph_set(input);
14311
- for (const g of opt.glyphs) x.hb_set_add(gs, g);
14312
- }
14313
- x.hb_subset_input_pin_all_axes_to_default(input, face);
14314
- const sub = x.hb_subset_or_fail(face, input);
14315
- if (!sub) throw new Error("HarfBuzz: subsetting failed");
14316
- const res = x.hb_face_reference_blob(sub);
14317
- const off = x.hb_blob_get_data(res, 0);
14318
- const len = x.hb_blob_get_length(res);
14319
- const out = heap().slice(off, off + len);
14320
- x.hb_blob_destroy(res);
14321
- x.hb_face_destroy(sub);
14322
- if (!len) throw new Error("HarfBuzz: empty subset");
14323
- return out;
14324
- } finally {
14325
- x.hb_subset_input_destroy(input);
14326
- x.hb_face_destroy(face);
14327
- x.free(buf);
14328
- }
14329
- };
14330
- }
14331
-
14332
- // src/viewer/engineClient.js
14333
- var assetBase = "";
14334
- function setAssetBase(url) {
14335
- assetBase = String(url || "").replace(/\/$/, "");
14336
- mainStore = null;
14337
- }
14338
- var origin = () => assetBase || window.location.origin;
14339
- var fontsUrl = null;
14340
- var standalone = false;
14341
- function setStandalone({ standalone: on = false, fontsUrl: f4 = null } = {}) {
14342
- standalone = !!on;
14343
- fontsUrl = f4 ? String(f4).replace(/\/?$/, "/") : null;
14344
- mainStore = null;
14345
- fontList = null;
14346
- fontBytes.clear();
14347
- }
14348
- var fontsBase = () => fontsUrl || `${assetBase}/fonts/`;
14349
- var besideFonts = (file) => fontsUrl ? new URL(`../${file}`, new URL(fontsUrl, typeof location !== "undefined" ? location.href : void 0)).href : `${assetBase}/${file}`;
14350
- var subsetter = createSubsetter(async () => (await fetch(besideFonts("harfbuzz-subset.wasm"))).arrayBuffer());
14351
- var worker = null;
14352
- var workerBroken = false;
14353
- var seq = 0;
14354
- var waiting = /* @__PURE__ */ new Map();
14355
- var mainStore = null;
14356
- var bundledFonts = null;
14357
- var bundledList = null;
14358
- function setBundledFonts(map, list) {
14359
- bundledFonts = map || null;
14360
- bundledList = list || null;
14361
- mainStore = null;
14362
- fontList = null;
14363
- }
14364
- var b64ToBuffer = (b64) => {
14365
- const s = atob(b64);
14366
- const u = new Uint8Array(s.length);
14367
- for (let i = 0; i < s.length; i++) u[i] = s.charCodeAt(i);
14368
- return u.buffer;
14369
- };
14370
- var fontBytes = /* @__PURE__ */ new Map();
14371
- var fetchFont = (k) => {
14372
- if (!fontBytes.has(k)) {
14373
- const p = (async () => {
14374
- const r = await fetch(`${fontsBase()}${fontFile(k)}`);
14375
- if (!r.ok) throw new Error(`Font "${k}" did not load (HTTP ${r.status})`);
14376
- return r.arrayBuffer();
14377
- })();
14378
- p.catch(() => fontBytes.delete(k));
14379
- fontBytes.set(k, p);
14380
- }
14381
- return fontBytes.get(k);
14382
- };
14383
- var getFont = async (k) => {
14384
- if (bundledFonts?.[k]) return b64ToBuffer(bundledFonts[k]);
14385
- return (await fetchFont(k)).slice(0);
14386
- };
14387
- function fontStore() {
14388
- if (!mainStore) mainStore = new FontStore(getFont);
14389
- return mainStore;
14390
- }
14391
- var makeWorker = true ? null : () => new Worker(new URL("../worker/engine.worker.js", import.meta.url), { type: "module" });
14392
- var fontList = null;
14393
- function customFonts(refresh = false) {
14394
- if (!fontList || refresh) {
14395
- fontList = (bundledList ? Promise.resolve(bundledList) : standalone ? Promise.resolve([]) : fetch(`${assetBase}/api/fonts`).then((r) => r.ok ? r.json() : []).catch(() => [])).then((list) => {
14396
- setCustomFonts(list);
14397
- return list;
14398
- });
14399
- }
14400
- return fontList;
14401
- }
14402
- var faces = /* @__PURE__ */ new Map();
14403
- function loadReportFonts(keys) {
14404
- if (typeof document === "undefined" || !document.fonts || typeof FontFace === "undefined") return Promise.resolve([]);
14405
- return Promise.all(keys.filter((k) => k !== SHAPER).map((k) => {
14406
- if (!faces.has(k)) {
14407
- const bytes = bundledFonts?.[k] ? Promise.resolve(b64ToBuffer(bundledFonts[k])) : fetchFont(k).then((b) => b.slice(0));
14408
- faces.set(k, bytes.then((src) => {
14409
- const face = new FontFace(fontFamilyCss(k), src, { display: "block" });
14410
- document.fonts.add(face);
14411
- return face.load();
14412
- }).then(() => null, (error) => {
14413
- faces.delete(k);
14414
- return { key: k, error };
14415
- }));
14416
- }
14417
- return faces.get(k);
14418
- })).then((results) => results.filter(Boolean));
14419
- }
14420
- var loadModelFonts = (model) => loadReportFonts([.../* @__PURE__ */ new Set([...ALL_FONT_KEYS, ...fontKeysOf(model)])]);
14421
- function getWorker() {
14422
- if (!makeWorker || workerBroken || typeof Worker === "undefined" || assetBase || bundledFonts) return null;
14423
- if (worker) return worker;
14424
- try {
14425
- worker = makeWorker();
14426
- worker.onmessage = (e) => {
14427
- const w = waiting.get(e.data.id);
14428
- if (!w) return;
14429
- if (e.data.partial) {
14430
- w.onPartial?.(e.data.model);
14431
- return;
14432
- }
14433
- if (e.data.retry) {
14434
- w.onRetry?.(e.data.retry);
14435
- return;
14436
- }
14437
- waiting.delete(e.data.id);
14438
- if (e.data.ok) w.resolve(e.data.model);
14439
- else w.reject(Object.assign(new Error(e.data.error.message), e.data.error));
14440
- };
14441
- worker.onerror = () => {
14442
- workerBroken = true;
14443
- worker = null;
14444
- for (const [, w] of waiting) w.retry();
14445
- waiting.clear();
14446
- };
14447
- } catch {
14448
- workerBroken = true;
14449
- worker = null;
14450
- }
14451
- return worker;
14452
- }
14453
- var bundled = /* @__PURE__ */ new Map();
14454
- function registerReports(map) {
14455
- for (const [k, v] of Object.entries(map || {})) bundled.set(k, v);
14456
- }
14457
- var loadReport = async (rid) => {
14458
- if (bundled.has(rid)) return structuredClone(bundled.get(rid));
14459
- if (standalone) throw new Error(`Report "${rid}" is not loaded: pass it in reports: { "${rid}": definition }, or set server (a self-hosted ReportWright server)`);
14460
- const r = await fetch(`${assetBase}/api/reports/${encodeURIComponent(rid)}`);
14461
- if (!r.ok) throw new Error(`Report "${rid}" not found`);
14462
- return r.json();
14463
- };
14464
- var fetchReport = loadReport;
14465
- async function renderMain(def, params, state, reportId, exportData = false, onRetry = void 0, dataOnly = false) {
14466
- await customFonts();
14467
- return render(def, { parameters: params, state, reportId, exportData, dataOnly, fontStore: fontStore(), baseUrl: origin(), loadReport, fetch: reportFetch(origin(), void 0, { onRetry }) });
14468
- }
14469
- async function renderReport(def, params = {}, state = {}, reportId = void 0, { onPartial, exportData = false, onRetry, dataOnly = false } = {}) {
14470
- const w = getWorker();
14471
- if (!w) return renderMain(def, params, state, reportId, exportData, onRetry, dataOnly);
14472
- const fonts = await customFonts();
14473
- return new Promise((resolve, reject) => {
14474
- const id = ++seq;
14475
- waiting.set(id, { resolve, reject, onPartial, onRetry, retry: () => renderMain(def, params, state, reportId, exportData, onRetry, dataOnly).then(resolve, reject) });
14476
- w.postMessage({ id, def, params, state, reportId, fonts, firstPages: onPartial && !dataOnly ? 2 : 0, exportData, dataOnly });
14477
- });
14478
- }
14479
- var usingWorker = () => !!worker && !workerBroken;
14480
- async function iccProfile() {
14481
- const r = await fetch(fontsUrl ? besideFonts("icc/sRGB-v2-magic.icc") : `${origin()}/icc/sRGB-v2-magic.icc`);
14482
- if (!r.ok) throw new Error(`the PDF/A colour profile did not load (HTTP ${r.status})`);
14483
- return new Uint8Array(await r.arrayBuffer());
14484
- }
14485
- async function pdfBlob(model, title, o = {}) {
14486
- const store = fontStore();
14487
- await customFonts();
14488
- await store.load(fontKeysOf(model));
14489
- const { exportPdf } = await import("./chunk-66QZ3RDV.js");
14490
- const bytes = await exportPdf(model, store, {
14491
- title,
14492
- subset: subsetter,
14493
- tagged: o.tagged,
14494
- drillBase: origin(),
14495
- // drill-through links open this viewer, as in the HTML export
14496
- pdfa: o.pdfa ? { icc: await iccProfile() } : void 0,
14497
- // a report's image: a GET under the same rules as its data (no cookies to our other routes or to other sites)
14498
- fetchImage: async (src) => {
14499
- try {
14500
- const r = await reportFetch(origin())(src);
14501
- return r.ok ? new Uint8Array(await r.arrayBuffer()) : null;
14502
- } catch {
14503
- return null;
14504
- }
14505
- }
14506
- });
14507
- return new Blob([bytes], { type: "application/pdf" });
14508
- }
14509
-
14510
- // src/viewer/logic.js
14511
- var TOOLBAR_GROUP_KEYS = {
14512
- export: (i) => i.group === "export" && i.id !== "print",
14513
- search: (i) => i.group === "search",
14514
- zoom: (i) => i.group === "zoom",
14515
- navigation: (i) => i.group === "paging"
14516
- };
14517
- var TOOLBAR_ID_KEYS = { sidebar: ["thumbnails", "documentMap"] };
14518
- function arrangeToolbar(builtIns, { hide = [], add: add2 = [] } = {}) {
14519
- if (!hide.length && !add2.length) return builtIns;
14520
- const out = [...builtIns];
14521
- const taken = new Set(builtIns.map((i) => i.id));
14522
- for (const a of add2) {
14523
- if (!a || !a.id || taken.has(a.id)) continue;
14524
- taken.add(a.id);
14525
- const at = out.findIndex((i) => i.id === a.after);
14526
- const item = { ...a, custom: true, group: at >= 0 ? out[at].group : `custom:${a.id}` };
14527
- if (at >= 0) out.splice(at + 1, 0, item);
14528
- else out.push(item);
14529
- }
14530
- const hiddenIds = /* @__PURE__ */ new Set();
14531
- const hiddenBy = [];
14532
- for (const k of hide) {
14533
- if (Object.hasOwn(TOOLBAR_GROUP_KEYS, k)) hiddenBy.push(TOOLBAR_GROUP_KEYS[k]);
14534
- else for (const id of Object.hasOwn(TOOLBAR_ID_KEYS, k) ? TOOLBAR_ID_KEYS[k] : [k]) hiddenIds.add(id);
14535
- }
14536
- let kept = out.filter((i) => i.custom || !(hiddenIds.has(i.id) || hiddenBy.some((f4) => f4(i))));
14537
- if (!kept.some((i) => i.format)) kept = kept.filter((i) => i.custom || i.id !== "exportOptions");
14538
- return kept;
14539
- }
14540
- function galleyDefinition(def) {
14541
- const pg = resolvePage(def.page);
14542
- if (pg.pageless) return def;
14543
- const m = pg.margins;
14544
- return { ...def, page: { size: "Pageless", width: pg.width, margins: [m.top, m.right, m.bottom, m.left] } };
14545
- }
14546
- function frozenAt(page, y) {
14547
- for (const r of page?.frozen || []) if (y > r.y && y < r.end - r.h) return r;
14548
- return null;
14549
- }
14550
- var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14551
- function findText(model, q, { matchCase = false, wholeWord = false } = {}) {
14552
- if (!q || !model) return [];
14553
- const W2 = "[\\p{L}\\p{M}\\p{N}_]";
14554
- const re = new RegExp(wholeWord ? `(?<!${W2})${escapeRe(q)}(?!${W2})` : escapeRe(q), `u${matchCase ? "" : "i"}`);
14555
- const out = [];
14556
- model.pages.forEach((pg, pi) => {
14557
- const rows = /* @__PURE__ */ new Map();
14558
- pg.items.forEach((it, k) => {
14559
- if (it.t !== "text") return;
14560
- for (const l of it.lines) {
14561
- const key = it.rich ? `r${it.rich.id}${it.rich.l}` : `${k}${l.y}`;
14562
- const r = rows.get(key);
14563
- if (r) r.lines.push(l);
14564
- else rows.set(key, { rich: !!it.rich, size: it.size, lines: [l] });
14565
- }
14566
- });
14567
- for (const { rich, size, lines: row } of rows.values()) {
14568
- row.sort((a, b) => a.x - b.x);
14569
- const text = row.map((l) => l.text).join(rich ? "" : " ");
14570
- if (!re.test(text)) continue;
14571
- const x0 = row[0].x, last = row[row.length - 1];
14572
- const x1 = last.x + (last.w || size * last.text.length * 0.5);
14573
- const y = Math.max(...row.map((l) => l.y));
14574
- out.push({ page: pi, x: x0 - 1, y: y - size * 0.95, w: x1 - x0 + 2, h: size * 1.25, text });
14575
- }
14576
- });
14577
- return out;
14578
- }
14579
- function rangeValue(a, b, p) {
14580
- if (a === "" && b === "") return "";
14581
- const lo = a !== "" ? a : String(p.min ?? b);
14582
- const hi = b !== "" ? b : String(p.max ?? a);
14583
- return `${lo},${hi}`;
14584
- }
14585
- function pathBox(d) {
14586
- let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity, cx = 0, cy = 0;
14587
- const add2 = (x, y) => {
14588
- cx = x;
14589
- cy = y;
14590
- if (x < x0) x0 = x;
14591
- if (x > x1) x1 = x;
14592
- if (y < y0) y0 = y;
14593
- if (y > y1) y1 = y;
14594
- };
14595
- for (const [, c, args] of String(d || "").matchAll(/([MLHVCSQTAZ])([^MLHVCSQTAZ]*)/gi)) {
14596
- const n = (args.match(/-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/gi) || []).map(Number);
14597
- const C = c.toUpperCase();
14598
- if (C === "H") n.forEach((x) => add2(x, cy));
14599
- else if (C === "V") n.forEach((y) => add2(cx, y));
14600
- else if (C === "A") for (let i = 0; i + 6 < n.length; i += 7) add2(n[i + 5], n[i + 6]);
14601
- else if (C !== "Z") for (let i = 0; i + 1 < n.length; i += 2) add2(n[i], n[i + 1]);
14602
- }
14603
- return x0 <= x1 ? { x: x0, y: y0, w: x1 - x0, h: y1 - y0 } : null;
14604
- }
14605
- var itemsOf = (page) => (page?.items || []).flatMap((i) => i.t === "field" ? i.draw || [] : [i]);
14606
- function pageLinks(page) {
14607
- const items = itemsOf(page);
14608
- const texts = items.filter((i) => i.t === "text");
14609
- const out = [];
14610
- for (const it of items) {
14611
- if (it.t !== "link" || !it.action) continue;
14612
- let box = { x: it.x, y: it.y, w: it.w, h: it.h };
14613
- if (it.d) {
14614
- const b = pathBox(it.d);
14615
- if (!b) continue;
14616
- box = { ...b, x: b.x + (it.x - (it.x0 ?? it.x)), y: b.y + (it.y - (it.y0 ?? it.y)) };
14617
- }
14618
- if (!(box.w > 0 && box.h > 0)) continue;
14619
- const words = [];
14620
- for (const t of texts) for (const l of t.lines || []) {
14621
- const top = l.y - (t.size || 9) * 0.9, w = l.w || (t.size || 9) * String(l.text).length * 0.5;
14622
- 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));
14623
- }
14624
- out.push({ action: it.action, ...box, path: !!it.d, tip: it.tip ? String(it.tip) : "", text: words.join(" ").replace(/^[▶▼]\s*/, "").replace(/\s*[↑↓]$/, "").trim() });
14625
- }
14626
- return out.sort((a, b) => Math.abs(a.y - b.y) > 2 ? a.y - b.y : a.x - b.x);
14627
- }
14628
- function readingText(page) {
14629
- const rows = /* @__PURE__ */ new Map();
14630
- for (const it of itemsOf(page)) {
14631
- if (it.t !== "text") continue;
14632
- for (const l of it.lines || []) {
14633
- const k = Math.round(l.y);
14634
- const r = rows.get(k);
14635
- if (r) r.push(l);
14636
- else rows.set(k, [l]);
14637
- }
14638
- }
14639
- 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);
14640
- }
14641
- var EXPORT_DEFAULTS = { pdfua: false, pdfa: false, formulas: true, html: "tables" };
14642
- function exportOptionsFrom(stored) {
14643
- const { v, ...rest } = stored && typeof stored === "object" ? stored : {};
14644
- if (v !== 2) delete rest.html;
14645
- return { ...EXPORT_DEFAULTS, ...rest };
14646
- }
14647
- function pruneSnapshot(snapshot, defs) {
14648
- const text = defs.map((d) => JSON.stringify(d)).join("\n");
14649
- const seen = /* @__PURE__ */ new Map();
14650
- const used = (k) => {
14651
- let u = seen.get(k);
14652
- if (u === void 0) {
14653
- u = text.includes(JSON.stringify(k).slice(1, -1));
14654
- seen.set(k, u);
14655
- }
14656
- return u;
14657
- };
14658
- const walk = (x) => {
14659
- if (Array.isArray(x)) return x.map(walk);
14660
- if (!x || typeof x !== "object") return x;
14661
- const o = {};
14662
- for (const [k, y] of Object.entries(x)) if (y && typeof y === "object" || used(k)) o[k] = walk(y);
14663
- return o;
14664
- };
14665
- return Object.fromEntries(Object.entries(snapshot).map(([url, x]) => [url, walk(x)]));
14666
- }
14667
- var SERVER_PDF_ROWS = 2e4;
14668
- var SERVER_PDF_PAGES = 800;
14669
- function serverPdfUrl(model, { reportId, params = {}, origin: origin2, timeZone, dirty = false }) {
14670
- if (!reportId || dirty || !model) return null;
14671
- const rows = Object.values(model.stats?.rows || {}).reduce((a, n) => a + (Number(n) || 0), 0);
14672
- if (rows <= SERVER_PDF_ROWS && (model.pages?.length || 0) <= SERVER_PDF_PAGES) return null;
14673
- const q = new URLSearchParams();
14674
- for (const [k, v0] of Object.entries(params)) {
14675
- const v = Array.isArray(v0) ? v0.length > 1 ? void 0 : v0[0] : v0;
14676
- if (v === void 0 && Array.isArray(v0)) return null;
14677
- if (v == null) continue;
14678
- q.set(k, v instanceof Date ? v.toISOString() : String(v));
14679
- }
14680
- if (timeZone) q.set("timeZone", timeZone);
14681
- const s = q.toString();
14682
- return `${origin2}/api/reports/${encodeURIComponent(reportId)}/pdf${s ? `?${s}` : ""}`;
14683
- }
14684
- function paramChoices(def, fetched) {
14685
- const out = { ...fetched };
14686
- for (const p of def?.parameters || []) {
14687
- if (!Array.isArray(p.options) || !p.optionsFrom || !Array.isArray(fetched?.[p.name])) continue;
14688
- const fixed = p.options.map((o) => typeof o === "object" && o ? { value: o.value, label: String(o.label ?? o.value) } : { value: o, label: String(o) });
14689
- const seen = new Set(fixed.map((o) => String(o.value)));
14690
- out[p.name] = [...fixed, ...fetched[p.name].filter((o) => !seen.has(String(o.value)))];
14691
- }
14692
- return out;
14693
- }
14694
-
14695
13816
  export {
13817
+ setFetchPolicy,
13818
+ policyKeysIn,
13819
+ fetchPolicy,
14696
13820
  reportFetch,
14697
13821
  PAGE_SIZES,
14698
13822
  toPt,
14699
13823
  fromPt,
14700
13824
  resolvePage,
14701
13825
  normBox,
14702
- TextMeasurer,
14703
- finishText,
14704
13826
  isDynamic,
14705
13827
  evalValue,
14706
13828
  toText,
@@ -14745,33 +13867,6 @@ export {
14745
13867
  NO_CJK_FONTS,
14746
13868
  render,
14747
13869
  parameterOptions,
14748
- resolveParameters,
14749
- setAssetBase,
14750
- origin,
14751
- setStandalone,
14752
- fontsBase,
14753
- subsetter,
14754
- setBundledFonts,
14755
- fontStore,
14756
- customFonts,
14757
- loadReportFonts,
14758
- loadModelFonts,
14759
- registerReports,
14760
- fetchReport,
14761
- renderReport,
14762
- usingWorker,
14763
- pdfBlob,
14764
- arrangeToolbar,
14765
- galleyDefinition,
14766
- frozenAt,
14767
- findText,
14768
- rangeValue,
14769
- pageLinks,
14770
- readingText,
14771
- EXPORT_DEFAULTS,
14772
- exportOptionsFrom,
14773
- pruneSnapshot,
14774
- serverPdfUrl,
14775
- paramChoices
13870
+ resolveParameters
14776
13871
  };
14777
- //# sourceMappingURL=chunk-WQOBRZAL.js.map
13872
+ //# sourceMappingURL=chunk-VNOWPSE6.js.map