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