@freereportjs/core 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/freereport.esm.js +62 -62
- package/dist/cdn/freereport.min.js +59 -59
- package/dist/index.d.ts +56 -5
- package/dist/index.js +347 -184
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -186,138 +186,6 @@ function pdfFonts(pages) {
|
|
|
186
186
|
return out;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
// src/version.ts
|
|
190
|
-
var version = "0.9.0";
|
|
191
|
-
var releaseDate = "2026-10-16";
|
|
192
|
-
|
|
193
|
-
// src/license.ts
|
|
194
|
-
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "MiJXJR8TZJ_-0VKk0wIQ-5mzRsyQ6ruGsbcRRdw9jrw", y: "MTdklYSzCM2pganffz7PPMfxSclQkBLqU_9se_VDznM" };
|
|
195
|
-
var PRICING_URL = "https://freereport.app/pricing/";
|
|
196
|
-
var USAGE_URL = "https://freereport.app/api/usage";
|
|
197
|
-
var STATUS_URL = "https://freereport.app/api/domain-status";
|
|
198
|
-
var b64urlDecode = (s) => {
|
|
199
|
-
const bin = atob(s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4));
|
|
200
|
-
return Uint8Array.from(bin, (c) => c.charCodeAt(0));
|
|
201
|
-
};
|
|
202
|
-
function isDevHost(host) {
|
|
203
|
-
const h = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
204
|
-
return !h || h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0" || h.endsWith(".localhost") || h.endsWith(".test") || h.endsWith(".local") || /^(10\.\d+|192\.168|172\.(1[6-9]|2\d|3[01]))\.\d+\.\d+$/.test(h);
|
|
205
|
-
}
|
|
206
|
-
function domainMatches(host, domains) {
|
|
207
|
-
const h = host.toLowerCase().replace(/\.$/, "");
|
|
208
|
-
return domains.some((d) => {
|
|
209
|
-
const raw = d.toLowerCase().replace(/\.$/, "");
|
|
210
|
-
const wildcard = raw.startsWith("*.");
|
|
211
|
-
const dom = wildcard ? raw.slice(2) : raw;
|
|
212
|
-
if (!dom) return false;
|
|
213
|
-
if (h === dom || h === `www.${dom}`) return true;
|
|
214
|
-
return wildcard && h.endsWith(`.${dom}`);
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
var today = (now) => `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
218
|
-
async function verifyLicense(key2, host, opts = {}) {
|
|
219
|
-
const parts = typeof key2 === "string" ? key2.trim().split(".") : [];
|
|
220
|
-
if (parts.length !== 3 || parts[0] !== "FR1") return { ok: false, reason: "format" };
|
|
221
|
-
let info;
|
|
222
|
-
try {
|
|
223
|
-
info = JSON.parse(new TextDecoder().decode(b64urlDecode(parts[1])));
|
|
224
|
-
if (!Array.isArray(info.domains) || typeof info.expires !== "string") return { ok: false, reason: "format" };
|
|
225
|
-
} catch {
|
|
226
|
-
return { ok: false, reason: "format" };
|
|
227
|
-
}
|
|
228
|
-
const subtle = typeof crypto !== "undefined" ? crypto.subtle : void 0;
|
|
229
|
-
if (!subtle) return { ok: false, reason: "unsupported", info };
|
|
230
|
-
try {
|
|
231
|
-
const pub = await subtle.importKey("jwk", opts.publicKey ?? PUBLIC_KEY, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
|
|
232
|
-
const signed = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
|
|
233
|
-
const ok = await subtle.verify({ name: "ECDSA", hash: "SHA-256" }, pub, b64urlDecode(parts[2]), signed);
|
|
234
|
-
if (!ok) return { ok: false, reason: "signature", info };
|
|
235
|
-
} catch {
|
|
236
|
-
return { ok: false, reason: "signature", info };
|
|
237
|
-
}
|
|
238
|
-
if (today(opts.now ?? /* @__PURE__ */ new Date()) > info.expires) return { ok: false, reason: "expired", info };
|
|
239
|
-
if (!domainMatches(host, info.domains)) return { ok: false, reason: "domain", info };
|
|
240
|
-
if (info.updates && (opts.released ?? releaseDate) > info.updates) return { ok: false, reason: "updates", info };
|
|
241
|
-
return { ok: true, info };
|
|
242
|
-
}
|
|
243
|
-
var globalKey;
|
|
244
|
-
var statuses = /* @__PURE__ */ new Map();
|
|
245
|
-
var badgeCheck = null;
|
|
246
|
-
function setLicenseKey(key2) {
|
|
247
|
-
globalKey = key2;
|
|
248
|
-
}
|
|
249
|
-
var NOTICE = {
|
|
250
|
-
missing: `FreeReport is free for personal use. Company use needs a domain license (\u20A9500,000 per domain per year, or \u20A9900,000 perpetual): ${PRICING_URL}
|
|
251
|
-
FreeReport\uB294 \uAC1C\uC778\uC5D0\uAC8C \uBB34\uB8CC\uC785\uB2C8\uB2E4. \uD68C\uC0AC\uC5D0\uC11C \uC4F0\uB824\uBA74 \uB3C4\uBA54\uC778 \uB77C\uC774\uC120\uC2A4(\uB3C4\uBA54\uC778\uB2F9 \uC5F0 50\uB9CC \uC6D0, \uC601\uAD6C 90\uB9CC \uC6D0)\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: ${PRICING_URL}`,
|
|
252
|
-
format: "FreeReport: the license key is not in the right format. / \uB77C\uC774\uC120\uC2A4 \uD0A4 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
253
|
-
signature: "FreeReport: the license key could not be verified. / \uB77C\uC774\uC120\uC2A4 \uD0A4\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
254
|
-
expired: "FreeReport: the license key has expired. / \uB77C\uC774\uC120\uC2A4 \uAE30\uAC04\uC774 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4.",
|
|
255
|
-
domain: "FreeReport: the license key is for a different domain. / \uB2E4\uB978 \uB3C4\uBA54\uC778\uC6A9 \uB77C\uC774\uC120\uC2A4 \uD0A4\uC785\uB2C8\uB2E4.",
|
|
256
|
-
updates: "FreeReport: this version was released after the update period of your perpetual license. / \uC601\uAD6C \uB77C\uC774\uC120\uC2A4\uC758 \uC5C5\uB370\uC774\uD2B8 \uAE30\uAC04 \uB4A4\uC5D0 \uB098\uC628 \uBC84\uC804\uC785\uB2C8\uB2E4."
|
|
257
|
-
};
|
|
258
|
-
function reportUsage(info, host = typeof location !== "undefined" ? location.hostname : "") {
|
|
259
|
-
try {
|
|
260
|
-
if (isDevHost(host) || typeof navigator === "undefined") return;
|
|
261
|
-
const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
262
|
-
const mark = `${host}|${day}|${info.licensed ? 1 : 0}`;
|
|
263
|
-
const store = typeof localStorage !== "undefined" ? localStorage : null;
|
|
264
|
-
if (store?.getItem("freereport:usage") === mark) return;
|
|
265
|
-
store?.setItem("freereport:usage", mark);
|
|
266
|
-
const body = JSON.stringify({ version: info.version, licensed: info.licensed, company: info.company, url: `${location.origin}${location.pathname}` });
|
|
267
|
-
if (typeof navigator.sendBeacon === "function") navigator.sendBeacon(USAGE_URL, body);
|
|
268
|
-
else if (typeof fetch === "function") void fetch(USAGE_URL, { method: "POST", body, mode: "no-cors", keepalive: true }).catch(() => void 0);
|
|
269
|
-
} catch {
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
function checkLicense(key2 = globalKey, host = typeof location !== "undefined" ? location.hostname : "") {
|
|
273
|
-
if (isDevHost(host)) return Promise.resolve(true);
|
|
274
|
-
const k = key2 ?? globalKey;
|
|
275
|
-
const id = `${k ?? ""}@${host}`;
|
|
276
|
-
let status = statuses.get(id);
|
|
277
|
-
if (status) return status;
|
|
278
|
-
if (!k) {
|
|
279
|
-
console.info(NOTICE.missing);
|
|
280
|
-
reportUsage({ version, licensed: false }, host);
|
|
281
|
-
status = Promise.resolve(false);
|
|
282
|
-
} else {
|
|
283
|
-
status = verifyLicense(k, host).then((res) => {
|
|
284
|
-
reportUsage({ version, licensed: res.ok, company: res.ok ? res.info.company : void 0 }, host);
|
|
285
|
-
if (res.ok || res.reason === "unsupported") return res.ok || res.reason === "unsupported";
|
|
286
|
-
const extra = res.info ? ` (${res.info.domains.join(", ")}, ${res.info.expires})` : "";
|
|
287
|
-
console.warn(`${NOTICE[res.reason]}${extra}
|
|
288
|
-
${PRICING_URL}`);
|
|
289
|
-
return false;
|
|
290
|
-
}, () => false);
|
|
291
|
-
}
|
|
292
|
-
statuses.set(id, status);
|
|
293
|
-
return status;
|
|
294
|
-
}
|
|
295
|
-
function badgeRequested(host = typeof location !== "undefined" ? location.hostname : "") {
|
|
296
|
-
if (isDevHost(host) || typeof fetch !== "function") return Promise.resolve(false);
|
|
297
|
-
if (badgeCheck) return badgeCheck;
|
|
298
|
-
const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
299
|
-
let store = null;
|
|
300
|
-
try {
|
|
301
|
-
store = typeof localStorage !== "undefined" ? localStorage : null;
|
|
302
|
-
const cached = JSON.parse(store?.getItem("freereport:badge") ?? "null");
|
|
303
|
-
if (cached && cached.host === host && cached.day === day) return badgeCheck = Promise.resolve(cached.show);
|
|
304
|
-
} catch {
|
|
305
|
-
}
|
|
306
|
-
badgeCheck = fetch(STATUS_URL, { mode: "cors", credentials: "omit" }).then((r) => r.ok ? r.json() : { badge: false }).then((d) => {
|
|
307
|
-
const show = d.badge === true;
|
|
308
|
-
try {
|
|
309
|
-
store?.setItem("freereport:badge", JSON.stringify({ host, day, show }));
|
|
310
|
-
} catch {
|
|
311
|
-
}
|
|
312
|
-
return show;
|
|
313
|
-
}, () => false);
|
|
314
|
-
return badgeCheck;
|
|
315
|
-
}
|
|
316
|
-
async function needsFreeBadge(key2, host = typeof location !== "undefined" ? location.hostname : "") {
|
|
317
|
-
if (await checkLicense(key2, host)) return false;
|
|
318
|
-
return badgeRequested(host);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
189
|
// src/format.ts
|
|
322
190
|
var DIGITS = ["", "\uC77C", "\uC774", "\uC0BC", "\uC0AC", "\uC624", "\uC721", "\uCE60", "\uD314", "\uAD6C"];
|
|
323
191
|
var SMALL = ["", "\uC2ED", "\uBC31", "\uCC9C"];
|
|
@@ -499,6 +367,138 @@ function formatValue(v, format, locale = "ko") {
|
|
|
499
367
|
return String(v);
|
|
500
368
|
}
|
|
501
369
|
|
|
370
|
+
// src/version.ts
|
|
371
|
+
var version = "0.10.1";
|
|
372
|
+
var releaseDate = "2026-10-19";
|
|
373
|
+
|
|
374
|
+
// src/license.ts
|
|
375
|
+
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "MiJXJR8TZJ_-0VKk0wIQ-5mzRsyQ6ruGsbcRRdw9jrw", y: "MTdklYSzCM2pganffz7PPMfxSclQkBLqU_9se_VDznM" };
|
|
376
|
+
var PRICING_URL = "https://freereport.app/pricing/";
|
|
377
|
+
var USAGE_URL = "https://freereport.app/api/usage";
|
|
378
|
+
var STATUS_URL = "https://freereport.app/api/domain-status";
|
|
379
|
+
var b64urlDecode = (s) => {
|
|
380
|
+
const bin = atob(s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4));
|
|
381
|
+
return Uint8Array.from(bin, (c) => c.charCodeAt(0));
|
|
382
|
+
};
|
|
383
|
+
function isDevHost(host) {
|
|
384
|
+
const h = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
385
|
+
return !h || h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0" || h.endsWith(".localhost") || h.endsWith(".test") || h.endsWith(".local") || /^(10\.\d+|192\.168|172\.(1[6-9]|2\d|3[01]))\.\d+\.\d+$/.test(h);
|
|
386
|
+
}
|
|
387
|
+
function domainMatches(host, domains) {
|
|
388
|
+
const h = host.toLowerCase().replace(/\.$/, "");
|
|
389
|
+
return domains.some((d) => {
|
|
390
|
+
const raw = d.toLowerCase().replace(/\.$/, "");
|
|
391
|
+
const wildcard = raw.startsWith("*.");
|
|
392
|
+
const dom = wildcard ? raw.slice(2) : raw;
|
|
393
|
+
if (!dom) return false;
|
|
394
|
+
if (h === dom || h === `www.${dom}`) return true;
|
|
395
|
+
return wildcard && h.endsWith(`.${dom}`);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
var today = (now) => `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
399
|
+
async function verifyLicense(key2, host, opts = {}) {
|
|
400
|
+
const parts = typeof key2 === "string" ? key2.trim().split(".") : [];
|
|
401
|
+
if (parts.length !== 3 || parts[0] !== "FR1") return { ok: false, reason: "format" };
|
|
402
|
+
let info;
|
|
403
|
+
try {
|
|
404
|
+
info = JSON.parse(new TextDecoder().decode(b64urlDecode(parts[1])));
|
|
405
|
+
if (!Array.isArray(info.domains) || typeof info.expires !== "string") return { ok: false, reason: "format" };
|
|
406
|
+
} catch {
|
|
407
|
+
return { ok: false, reason: "format" };
|
|
408
|
+
}
|
|
409
|
+
const subtle = typeof crypto !== "undefined" ? crypto.subtle : void 0;
|
|
410
|
+
if (!subtle) return { ok: false, reason: "unsupported", info };
|
|
411
|
+
try {
|
|
412
|
+
const pub = await subtle.importKey("jwk", opts.publicKey ?? PUBLIC_KEY, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
|
|
413
|
+
const signed = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
|
|
414
|
+
const ok = await subtle.verify({ name: "ECDSA", hash: "SHA-256" }, pub, b64urlDecode(parts[2]), signed);
|
|
415
|
+
if (!ok) return { ok: false, reason: "signature", info };
|
|
416
|
+
} catch {
|
|
417
|
+
return { ok: false, reason: "signature", info };
|
|
418
|
+
}
|
|
419
|
+
if (today(opts.now ?? /* @__PURE__ */ new Date()) > info.expires) return { ok: false, reason: "expired", info };
|
|
420
|
+
if (!domainMatches(host, info.domains)) return { ok: false, reason: "domain", info };
|
|
421
|
+
if (info.updates && (opts.released ?? releaseDate) > info.updates) return { ok: false, reason: "updates", info };
|
|
422
|
+
return { ok: true, info };
|
|
423
|
+
}
|
|
424
|
+
var globalKey;
|
|
425
|
+
var statuses = /* @__PURE__ */ new Map();
|
|
426
|
+
var badgeCheck = null;
|
|
427
|
+
function setLicenseKey(key2) {
|
|
428
|
+
globalKey = key2;
|
|
429
|
+
}
|
|
430
|
+
var NOTICE = {
|
|
431
|
+
missing: `FreeReport is free for personal use. Company use needs a domain license (\u20A9500,000 per domain per year, or \u20A9900,000 perpetual): ${PRICING_URL}
|
|
432
|
+
FreeReport\uB294 \uAC1C\uC778\uC5D0\uAC8C \uBB34\uB8CC\uC785\uB2C8\uB2E4. \uD68C\uC0AC\uC5D0\uC11C \uC4F0\uB824\uBA74 \uB3C4\uBA54\uC778 \uB77C\uC774\uC120\uC2A4(\uB3C4\uBA54\uC778\uB2F9 \uC5F0 50\uB9CC \uC6D0, \uC601\uAD6C 90\uB9CC \uC6D0)\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: ${PRICING_URL}`,
|
|
433
|
+
format: "FreeReport: the license key is not in the right format. / \uB77C\uC774\uC120\uC2A4 \uD0A4 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
434
|
+
signature: "FreeReport: the license key could not be verified. / \uB77C\uC774\uC120\uC2A4 \uD0A4\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
435
|
+
expired: "FreeReport: the license key has expired. / \uB77C\uC774\uC120\uC2A4 \uAE30\uAC04\uC774 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4.",
|
|
436
|
+
domain: "FreeReport: the license key is for a different domain. / \uB2E4\uB978 \uB3C4\uBA54\uC778\uC6A9 \uB77C\uC774\uC120\uC2A4 \uD0A4\uC785\uB2C8\uB2E4.",
|
|
437
|
+
updates: "FreeReport: this version was released after the update period of your perpetual license. / \uC601\uAD6C \uB77C\uC774\uC120\uC2A4\uC758 \uC5C5\uB370\uC774\uD2B8 \uAE30\uAC04 \uB4A4\uC5D0 \uB098\uC628 \uBC84\uC804\uC785\uB2C8\uB2E4."
|
|
438
|
+
};
|
|
439
|
+
function reportUsage(info, host = typeof location !== "undefined" ? location.hostname : "") {
|
|
440
|
+
try {
|
|
441
|
+
if (isDevHost(host) || typeof navigator === "undefined") return;
|
|
442
|
+
const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
443
|
+
const mark = `${host}|${day}|${info.licensed ? 1 : 0}`;
|
|
444
|
+
const store = typeof localStorage !== "undefined" ? localStorage : null;
|
|
445
|
+
if (store?.getItem("freereport:usage") === mark) return;
|
|
446
|
+
store?.setItem("freereport:usage", mark);
|
|
447
|
+
const body = JSON.stringify({ version: info.version, licensed: info.licensed, company: info.company, url: `${location.origin}${location.pathname}` });
|
|
448
|
+
if (typeof navigator.sendBeacon === "function") navigator.sendBeacon(USAGE_URL, body);
|
|
449
|
+
else if (typeof fetch === "function") void fetch(USAGE_URL, { method: "POST", body, mode: "no-cors", keepalive: true }).catch(() => void 0);
|
|
450
|
+
} catch {
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function checkLicense(key2 = globalKey, host = typeof location !== "undefined" ? location.hostname : "") {
|
|
454
|
+
if (isDevHost(host)) return Promise.resolve(true);
|
|
455
|
+
const k = key2 ?? globalKey;
|
|
456
|
+
const id = `${k ?? ""}@${host}`;
|
|
457
|
+
let status = statuses.get(id);
|
|
458
|
+
if (status) return status;
|
|
459
|
+
if (!k) {
|
|
460
|
+
console.info(NOTICE.missing);
|
|
461
|
+
reportUsage({ version, licensed: false }, host);
|
|
462
|
+
status = Promise.resolve(false);
|
|
463
|
+
} else {
|
|
464
|
+
status = verifyLicense(k, host).then((res) => {
|
|
465
|
+
reportUsage({ version, licensed: res.ok, company: res.ok ? res.info.company : void 0 }, host);
|
|
466
|
+
if (res.ok || res.reason === "unsupported") return res.ok || res.reason === "unsupported";
|
|
467
|
+
const extra = res.info ? ` (${res.info.domains.join(", ")}, ${res.info.expires})` : "";
|
|
468
|
+
console.warn(`${NOTICE[res.reason]}${extra}
|
|
469
|
+
${PRICING_URL}`);
|
|
470
|
+
return false;
|
|
471
|
+
}, () => false);
|
|
472
|
+
}
|
|
473
|
+
statuses.set(id, status);
|
|
474
|
+
return status;
|
|
475
|
+
}
|
|
476
|
+
function badgeRequested(host = typeof location !== "undefined" ? location.hostname : "") {
|
|
477
|
+
if (isDevHost(host) || typeof fetch !== "function") return Promise.resolve(false);
|
|
478
|
+
if (badgeCheck) return badgeCheck;
|
|
479
|
+
const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
480
|
+
let store = null;
|
|
481
|
+
try {
|
|
482
|
+
store = typeof localStorage !== "undefined" ? localStorage : null;
|
|
483
|
+
const cached = JSON.parse(store?.getItem("freereport:badge") ?? "null");
|
|
484
|
+
if (cached && cached.host === host && cached.day === day) return badgeCheck = Promise.resolve(cached.show);
|
|
485
|
+
} catch {
|
|
486
|
+
}
|
|
487
|
+
badgeCheck = fetch(STATUS_URL, { mode: "cors", credentials: "omit" }).then((r) => r.ok ? r.json() : { badge: false }).then((d) => {
|
|
488
|
+
const show = d.badge === true;
|
|
489
|
+
try {
|
|
490
|
+
store?.setItem("freereport:badge", JSON.stringify({ host, day, show }));
|
|
491
|
+
} catch {
|
|
492
|
+
}
|
|
493
|
+
return show;
|
|
494
|
+
}, () => false);
|
|
495
|
+
return badgeCheck;
|
|
496
|
+
}
|
|
497
|
+
async function needsFreeBadge(key2, host = typeof location !== "undefined" ? location.hostname : "") {
|
|
498
|
+
if (await checkLicense(key2, host)) return false;
|
|
499
|
+
return badgeRequested(host);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
502
|
// src/expr.ts
|
|
503
503
|
var OPS = ["===", "!==", "==", "!=", "<=", ">=", "&&", "||", "??", "<", ">", "+", "-", "*", "/", "%", "!", "?", ":", "(", ")", ",", ".", "[", "]"];
|
|
504
504
|
function tokenize(src) {
|
|
@@ -933,7 +933,7 @@ function checkExpression(src) {
|
|
|
933
933
|
return e.message;
|
|
934
934
|
}
|
|
935
935
|
}
|
|
936
|
-
function interpolate(template, scope) {
|
|
936
|
+
function interpolate(template, scope, locale) {
|
|
937
937
|
if (!template.includes("{")) return template.replace(/}}/g, "}");
|
|
938
938
|
let out = "";
|
|
939
939
|
let i = 0;
|
|
@@ -961,7 +961,7 @@ function interpolate(template, scope) {
|
|
|
961
961
|
}
|
|
962
962
|
const { expr, format } = splitFormat(template.slice(i + 1, end));
|
|
963
963
|
const v = evaluate(expr, scope);
|
|
964
|
-
out += formatValue(v, format);
|
|
964
|
+
out += formatValue(v, format, locale);
|
|
965
965
|
i = end + 1;
|
|
966
966
|
}
|
|
967
967
|
return out;
|
|
@@ -1038,8 +1038,8 @@ function textMark(text, cx, cy, size, opts) {
|
|
|
1038
1038
|
source: { band: "watermark" }
|
|
1039
1039
|
};
|
|
1040
1040
|
}
|
|
1041
|
-
function watermarkItems(wm, page, scope, measure) {
|
|
1042
|
-
const text = wm.text ? interpolate(wm.text, scope) : "";
|
|
1041
|
+
function watermarkItems(wm, page, scope, measure, locale) {
|
|
1042
|
+
const text = wm.text ? interpolate(wm.text, scope, locale) : "";
|
|
1043
1043
|
const opacity = wm.opacity ?? 0.12;
|
|
1044
1044
|
const angle = wm.angle ?? -35;
|
|
1045
1045
|
const color = wm.color ?? "#808080";
|
|
@@ -1191,7 +1191,7 @@ var shift = (d, dx, dy) => {
|
|
|
1191
1191
|
}
|
|
1192
1192
|
return out;
|
|
1193
1193
|
};
|
|
1194
|
-
function embossItems(em, page, scope, measure) {
|
|
1194
|
+
function embossItems(em, page, scope, measure, locale) {
|
|
1195
1195
|
const w = em.w ?? 32, h = em.h ?? (em.shape === "rect" ? 20 : w);
|
|
1196
1196
|
const x = em.x ?? page.width - page.margins[1] - w - 4;
|
|
1197
1197
|
const y = em.y ?? page.height - page.margins[2] - h - 12;
|
|
@@ -1210,8 +1210,8 @@ function embossItems(em, page, scope, measure) {
|
|
|
1210
1210
|
} else if (shape === "rect") {
|
|
1211
1211
|
shapes.push(rectRing(x, y, w, h, Math.max(0.6, Math.min(w, h) * 0.05)));
|
|
1212
1212
|
}
|
|
1213
|
-
const text = em.text ? interpolate(em.text, scope) : "";
|
|
1214
|
-
const center = em.centerText ? interpolate(em.centerText, scope) : "";
|
|
1213
|
+
const text = em.text ? interpolate(em.text, scope, locale) : "";
|
|
1214
|
+
const center = em.centerText ? interpolate(em.centerText, scope, locale) : "";
|
|
1215
1215
|
if (shape === "circle" && text) {
|
|
1216
1216
|
const r = Math.min(w, h) / 2;
|
|
1217
1217
|
const size = em.fontSize ?? Math.max(5, r * 0.62);
|
|
@@ -1250,11 +1250,11 @@ function decoratePages(pages, marks, geometry, scopeFor, measure) {
|
|
|
1250
1250
|
const back = [], front = [];
|
|
1251
1251
|
for (const wm of wms) {
|
|
1252
1252
|
if (!onPage(wm.pages, i, pages.length) || !wm.text && !wm.image) continue;
|
|
1253
|
-
(wm.layer === "front" ? front : back).push(...watermarkItems(wm, page, scope, measure));
|
|
1253
|
+
(wm.layer === "front" ? front : back).push(...watermarkItems(wm, page, scope, measure, marks.locale));
|
|
1254
1254
|
}
|
|
1255
1255
|
for (const em of ems) {
|
|
1256
1256
|
if (!onPage(em.pages, i, pages.length)) continue;
|
|
1257
|
-
front.push(...embossItems(em, { ...page, margins: geometry.margins }, scope, measure));
|
|
1257
|
+
front.push(...embossItems(em, { ...page, margins: geometry.margins }, scope, measure, marks.locale));
|
|
1258
1258
|
}
|
|
1259
1259
|
page.items = [...back, ...page.items, ...front];
|
|
1260
1260
|
});
|
|
@@ -1335,6 +1335,11 @@ function pathData(d) {
|
|
|
1335
1335
|
return d.map((v) => typeof v === "number" ? n(v) : v).join(" ");
|
|
1336
1336
|
}
|
|
1337
1337
|
var opacityAttr = (o) => o !== void 0 && o < 1 ? ` opacity="${n(o)}"` : "";
|
|
1338
|
+
var sourceAttrs = (it) => {
|
|
1339
|
+
const s = it.source;
|
|
1340
|
+
if (!s?.elementId) return "";
|
|
1341
|
+
return ` data-fr-id="${esc(s.elementId)}" data-fr-kind="${it.kind}"${s.row !== void 0 ? ` data-fr-row="${s.row}"` : ""}`;
|
|
1342
|
+
};
|
|
1338
1343
|
function textSvg(t, clipId) {
|
|
1339
1344
|
const sizeMm = t.font.size * PT;
|
|
1340
1345
|
const family = `${quoteFamily(t.font.family)}, ${FALLBACK_FONTS}`.replace(/"/g, "'");
|
|
@@ -1348,11 +1353,12 @@ function textSvg(t, clipId) {
|
|
|
1348
1353
|
}
|
|
1349
1354
|
const overflow = t.source?.band !== "watermark" && t.lines.some((l) => l.x < t.x - 0.05 || l.x + l.width > t.x + t.w + 0.05 || l.y - sizeMm * 0.8 < t.y - 0.05 || l.y + sizeMm * 0.2 > t.y + t.h + 0.05);
|
|
1350
1355
|
const transform = t.rotate ? ` transform="rotate(${n(t.rotate)} ${n(t.x + t.w / 2)} ${n(t.y + t.h / 2)})"` : "";
|
|
1351
|
-
|
|
1356
|
+
const hooks = sourceAttrs(t) + (t.overflow && t.source?.band !== "watermark" ? ' data-fr-overflow="1"' : "");
|
|
1357
|
+
if (!overflow) return { defs: "", body: `<g ${attrs}${hooks}${transform}${opacityAttr(t.opacity)}>${body}</g>` };
|
|
1352
1358
|
const id = clipId();
|
|
1353
1359
|
return {
|
|
1354
1360
|
defs: `<clipPath id="${id}"><rect x="${n(t.x)}" y="${n(t.y)}" width="${n(t.w)}" height="${n(t.h)}"/></clipPath>`,
|
|
1355
|
-
body: `<g ${attrs} clip-path="url(#${id})"${transform}${opacityAttr(t.opacity)}>${body}</g>`
|
|
1361
|
+
body: `<g ${attrs}${hooks} clip-path="url(#${id})"${transform}${opacityAttr(t.opacity)}>${body}</g>`
|
|
1356
1362
|
};
|
|
1357
1363
|
}
|
|
1358
1364
|
function itemSvg(it, clipId) {
|
|
@@ -1401,7 +1407,8 @@ function pageToSvg(page, opts = {}) {
|
|
|
1401
1407
|
for (const it of page.items) {
|
|
1402
1408
|
const r = itemSvg(it, clipId);
|
|
1403
1409
|
defs += r.defs;
|
|
1404
|
-
|
|
1410
|
+
const hooks = it.kind === "text" ? "" : sourceAttrs(it);
|
|
1411
|
+
body += !hooks || !r.body ? r.body : r.body.indexOf("<", 1) < 0 ? r.body.replace(/^<(\w+)/, (_, tag) => `<${tag}${hooks}`) : `<g${hooks}>${r.body}</g>`;
|
|
1405
1412
|
}
|
|
1406
1413
|
const size = opts.scale ? `width="${n(page.width * opts.scale)}" height="${n(page.height * opts.scale)}"` : `width="${n(page.width)}mm" height="${n(page.height)}mm"`;
|
|
1407
1414
|
return `<svg xmlns="http://www.w3.org/2000/svg" ${size} viewBox="0 0 ${n(page.width)} ${n(page.height)}"${opts.className ? ` class="${opts.className}"` : ""} text-rendering="geometricPrecision">${defs ? `<defs>${defs}</defs>` : ""}<rect width="100%" height="100%" fill="#fff"/>${body}</svg>`;
|
|
@@ -2541,7 +2548,10 @@ function renderChart(el, scope, box, ctx) {
|
|
|
2541
2548
|
const labelStyle = { ...style, padding: [0, 0, 0, 0], letterSpacing: 0, border: {} };
|
|
2542
2549
|
const lh = fontSize * PT * 1.3;
|
|
2543
2550
|
const items = [];
|
|
2544
|
-
const label = (text, x2, y2, w2, align, bold = false, color = "#444444") =>
|
|
2551
|
+
const label = (text, x2, y2, w2, align, bold = false, color = "#444444") => (
|
|
2552
|
+
// chart labels are allowed to run past their slot: not an overflow of the element's text
|
|
2553
|
+
items.push({ ...textItem(x2, y2, w2, lh, [text], { ...font, bold }, { ...labelStyle, align, valign: "middle", color }, ctx.measure), overflow: void 0, source: { band: ctx.kind, elementId: el.id } })
|
|
2554
|
+
);
|
|
2545
2555
|
let { x, y, w, h } = box;
|
|
2546
2556
|
if (el.title) {
|
|
2547
2557
|
label(el.title, x, y, w, "center", true, style.color);
|
|
@@ -2900,7 +2910,7 @@ function prepare(el, scope, ctx, bandCanGrow) {
|
|
|
2900
2910
|
let text = "";
|
|
2901
2911
|
let value;
|
|
2902
2912
|
if (!ctx.blank) {
|
|
2903
|
-
if (el.type === "text") text = interpolate(el.text ?? "", scope);
|
|
2913
|
+
if (el.type === "text") text = interpolate(el.text ?? "", scope, ctx.locale);
|
|
2904
2914
|
else {
|
|
2905
2915
|
value = evaluate(el.expr, scope);
|
|
2906
2916
|
text = value == null || value === "" ? el.nullText ?? "" : formatValue(value, el.format, ctx.locale);
|
|
@@ -2945,6 +2955,7 @@ function textItem(x, y, w, h, lines, font, st, measure) {
|
|
|
2945
2955
|
else if (st.align === "right") lx = x + w - pr - width;
|
|
2946
2956
|
return { text, x: lx, y: top + i * lineH + (lineH - font.size * PT) / 2 + font.size * PT * ASCENT, width };
|
|
2947
2957
|
});
|
|
2958
|
+
const overflow = top < y - 0.05 || top + total > y + h + 0.05 || out.some((l) => l.x < x - 0.05 || l.x + l.width > x + w + 0.05);
|
|
2948
2959
|
return {
|
|
2949
2960
|
kind: "text",
|
|
2950
2961
|
x,
|
|
@@ -2959,6 +2970,7 @@ function textItem(x, y, w, h, lines, font, st, measure) {
|
|
|
2959
2970
|
underline: st.underline || void 0,
|
|
2960
2971
|
strike: st.strike || void 0,
|
|
2961
2972
|
letterSpacing: st.letterSpacing || void 0,
|
|
2973
|
+
overflow: overflow || void 0,
|
|
2962
2974
|
text: lines.join("\n")
|
|
2963
2975
|
};
|
|
2964
2976
|
}
|
|
@@ -3027,25 +3039,106 @@ function barcodeItems(el, x, y, w, h, scope, ctx) {
|
|
|
3027
3039
|
return [textItem(x, y, w, h, ["(barcode?)"], { family: "sans-serif", size: 7, bold: false, italic: false }, { ...resolveStyle(ctx.def, el), color: "#cc0000" }, ctx.measure)];
|
|
3028
3040
|
}
|
|
3029
3041
|
}
|
|
3042
|
+
var topOf = (el) => Math.min(el.y, el.y + el.h);
|
|
3043
|
+
var bottomOf = (el) => Math.max(el.y, el.y + el.h);
|
|
3044
|
+
var leftOf = (el) => Math.min(el.x, el.x + el.w);
|
|
3045
|
+
var rightOf = (el) => Math.max(el.x, el.x + el.w);
|
|
3046
|
+
function shifts(slots) {
|
|
3047
|
+
let changed = false;
|
|
3048
|
+
for (const s of slots) if (s.collapsed || !s.hidden && s.needed > s.el.h + 0.01) {
|
|
3049
|
+
changed = true;
|
|
3050
|
+
break;
|
|
3051
|
+
}
|
|
3052
|
+
if (!changed) return null;
|
|
3053
|
+
const off = /* @__PURE__ */ new Map();
|
|
3054
|
+
const order = [...slots].sort((a, b) => topOf(a.el) - topOf(b.el));
|
|
3055
|
+
const growth = (q) => q.hidden ? 0 : Math.max(0, q.needed - q.el.h);
|
|
3056
|
+
for (const p of order) {
|
|
3057
|
+
if (p.el.shift === false) continue;
|
|
3058
|
+
const pt = topOf(p.el), pl = leftOf(p.el), pr = rightOf(p.el);
|
|
3059
|
+
let nearest3 = null;
|
|
3060
|
+
let floor = -Infinity, pushed = -Infinity;
|
|
3061
|
+
for (const q of order) {
|
|
3062
|
+
if (topOf(q.el) >= pt) break;
|
|
3063
|
+
if (bottomOf(q.el) > pt + 0.01 || leftOf(q.el) >= pr - 0.01 || pl >= rightOf(q.el) - 0.01) continue;
|
|
3064
|
+
if (!nearest3 || bottomOf(q.el) > bottomOf(nearest3.el) + 0.01) nearest3 = q;
|
|
3065
|
+
if (q.collapsed) continue;
|
|
3066
|
+
const by2 = (off.get(q) ?? 0) + growth(q);
|
|
3067
|
+
if (by2 > 0) pushed = Math.max(pushed, by2);
|
|
3068
|
+
floor = Math.max(floor, bottomOf(q.el) + by2 - pt);
|
|
3069
|
+
}
|
|
3070
|
+
if (!nearest3) continue;
|
|
3071
|
+
const base2 = nearest3.collapsed ? topOf(nearest3.el) + (off.get(nearest3) ?? 0) - pt : (off.get(nearest3) ?? 0) + growth(nearest3);
|
|
3072
|
+
const by = Math.max(base2, pushed, floor);
|
|
3073
|
+
if (Math.abs(by) > 1e-9) off.set(p, by);
|
|
3074
|
+
}
|
|
3075
|
+
return off;
|
|
3076
|
+
}
|
|
3077
|
+
function stretchRows(slots) {
|
|
3078
|
+
for (const s of slots) {
|
|
3079
|
+
if (s.el.stretch !== "row" || s.hidden) continue;
|
|
3080
|
+
for (const o of slots) {
|
|
3081
|
+
if (!o.hidden && Math.abs(o.el.y - s.el.y) < 0.01 && Math.abs(o.el.h - s.el.h) < 0.01) s.needed = Math.max(s.needed, o.needed);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
function addAnchor(out, el, ctx, left, top, y, h, hidden) {
|
|
3086
|
+
if (!el.id) return;
|
|
3087
|
+
const line = el.type === "line";
|
|
3088
|
+
out.push({
|
|
3089
|
+
id: el.id,
|
|
3090
|
+
band: ctx.kind,
|
|
3091
|
+
row: ctx.rowIndex,
|
|
3092
|
+
x: left + (line ? Math.min(el.x, el.x + el.w) : el.x),
|
|
3093
|
+
y: top + (line ? Math.min(y, y + h) : y),
|
|
3094
|
+
w: line ? Math.abs(el.w) : el.w,
|
|
3095
|
+
h: line ? Math.abs(h) : h,
|
|
3096
|
+
...hidden ? { hidden } : {}
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3030
3099
|
function renderBand(band, scope, ctx, left, top, minHeight = 0) {
|
|
3031
3100
|
const bandGrow = band.canGrow ?? (ctx.kind === "detail" || ctx.kind === "groupHeader" || ctx.kind === "groupFooter" || ctx.kind === "summary" || ctx.kind === "title");
|
|
3032
|
-
const
|
|
3101
|
+
const slots = [];
|
|
3102
|
+
const anchors = ctx.anchors && !ctx.blank ? [] : void 0;
|
|
3103
|
+
let collapsed = false, rows = false;
|
|
3033
3104
|
for (const el of band.elements) {
|
|
3034
|
-
if (el.visible && !ctx.blank && !truthy(evaluate(el.visible, scope)))
|
|
3035
|
-
|
|
3105
|
+
if (el.visible && !ctx.blank && !truthy(evaluate(el.visible, scope))) {
|
|
3106
|
+
if (el.collapse) collapsed = true;
|
|
3107
|
+
slots.push({ el, style: resolveStyle(ctx.def, el), needed: el.h, hidden: true, collapsed: !!el.collapse });
|
|
3108
|
+
continue;
|
|
3109
|
+
}
|
|
3110
|
+
if (el.stretch === "row") rows = true;
|
|
3111
|
+
slots.push(prepare(el, scope, ctx, bandGrow));
|
|
3036
3112
|
}
|
|
3113
|
+
if (rows) stretchRows(slots);
|
|
3114
|
+
const shift2 = shifts(slots);
|
|
3115
|
+
const yOf = (s) => shift2 ? s.el.y + (shift2.get(s) ?? 0) : s.el.y;
|
|
3037
3116
|
let height = Math.max(band.height, minHeight);
|
|
3038
|
-
|
|
3117
|
+
if (collapsed) {
|
|
3118
|
+
let lowest = 0, lowestNew = 0;
|
|
3119
|
+
for (const s of slots) {
|
|
3120
|
+
lowest = Math.max(lowest, bottomOf(s.el));
|
|
3121
|
+
if (!s.hidden) lowestNew = Math.max(lowestNew, yOf(s) + Math.max(s.needed, 0));
|
|
3122
|
+
}
|
|
3123
|
+
height = Math.max(minHeight, lowestNew + Math.max(0, band.height - lowest));
|
|
3124
|
+
} else for (const s of slots) if (!s.hidden) height = Math.max(height, yOf(s) + s.needed);
|
|
3039
3125
|
const items = [];
|
|
3040
3126
|
const bg = ctx.background ?? band.background;
|
|
3041
3127
|
if (bg) items.push({ kind: "box", x: left, y: top, w: ctx.width, h: height, fill: bg, source: { band: ctx.kind, row: ctx.rowIndex } });
|
|
3042
|
-
const
|
|
3043
|
-
for (const p of prepared) {
|
|
3128
|
+
for (const p of slots) {
|
|
3044
3129
|
const el = p.el;
|
|
3130
|
+
const elY = yOf(p);
|
|
3131
|
+
if (p.hidden) {
|
|
3132
|
+
if (anchors) addAnchor(anchors, el, ctx, left, top, elY, el.h, true);
|
|
3133
|
+
continue;
|
|
3134
|
+
}
|
|
3045
3135
|
const touchesBottom = el.y + el.h >= band.height - 0.01;
|
|
3136
|
+
const follows = el.stretch === "row" ? touchesBottom : el.stretch ?? touchesBottom;
|
|
3046
3137
|
let h = p.needed;
|
|
3047
|
-
if (
|
|
3048
|
-
|
|
3138
|
+
if (follows && el.type !== "line" && el.type !== "table" && el.type !== "crosstab") h = Math.max(h, height - elY);
|
|
3139
|
+
if (el.type === "line") h = el.h === 0 ? 0 : el.stretch === "row" ? p.needed : touchesBottom ? Math.max(el.h, height - elY) : el.h;
|
|
3140
|
+
const x = left + el.x, y = top + elY;
|
|
3141
|
+
if (anchors) addAnchor(anchors, el, ctx, left, top, elY, h);
|
|
3049
3142
|
switch (el.type) {
|
|
3050
3143
|
case "text":
|
|
3051
3144
|
case "field": {
|
|
@@ -3060,8 +3153,7 @@ function renderBand(band, scope, ctx, left, top, minHeight = 0) {
|
|
|
3060
3153
|
break;
|
|
3061
3154
|
}
|
|
3062
3155
|
case "line": {
|
|
3063
|
-
|
|
3064
|
-
items.push({ kind: "line", x1: x, y1: y, x2: x + el.w, y2: y + lh, width: el.width ?? 0.2, color: el.color ?? "#000000", dash: el.dash, source: { band: ctx.kind, elementId: el.id } });
|
|
3156
|
+
items.push({ kind: "line", x1: x, y1: y, x2: x + el.w, y2: y + h, width: el.width ?? 0.2, color: el.color ?? "#000000", dash: el.dash, source: { band: ctx.kind, elementId: el.id } });
|
|
3065
3157
|
break;
|
|
3066
3158
|
}
|
|
3067
3159
|
case "rect": {
|
|
@@ -3098,7 +3190,7 @@ function renderBand(band, scope, ctx, left, top, minHeight = 0) {
|
|
|
3098
3190
|
break;
|
|
3099
3191
|
}
|
|
3100
3192
|
}
|
|
3101
|
-
return { height, items };
|
|
3193
|
+
return anchors ? { height, items, anchors } : { height, items };
|
|
3102
3194
|
}
|
|
3103
3195
|
|
|
3104
3196
|
// src/layout.ts
|
|
@@ -3283,7 +3375,7 @@ function paginate(def, data, opts, knownTotal, docHash) {
|
|
|
3283
3375
|
groupLevel: e.group?.level
|
|
3284
3376
|
}
|
|
3285
3377
|
});
|
|
3286
|
-
const ctxFor = (kind, extra = {}) => ({ def, measure, locale, kind, width, previous, ...extra });
|
|
3378
|
+
const ctxFor = (kind, extra = {}) => ({ def, measure, locale, kind, width, previous, anchors: opts.anchors, ...extra });
|
|
3287
3379
|
const newPage = () => {
|
|
3288
3380
|
if (pages.length >= maxPages) throw new Error(`Report stopped at ${maxPages} pages (maxPages)`);
|
|
3289
3381
|
page = { number: pages.length + 1, total: 0, width: geo.width, height: geo.height, items: [] };
|
|
@@ -3298,13 +3390,17 @@ function paginate(def, data, opts, knownTotal, docHash) {
|
|
|
3298
3390
|
previous.clear();
|
|
3299
3391
|
};
|
|
3300
3392
|
const place = (band, kind, scope, extra = {}, pre) => {
|
|
3301
|
-
const r = pre ? { height: pre.height, items: shiftItems(pre.items, y) } : renderBand(band, scope, ctxFor(kind, extra), ml, y);
|
|
3393
|
+
const r = pre ? { height: pre.height, items: shiftItems(pre.items, y), anchors: pre.anchors?.map((a) => ({ ...a, y: a.y + y })) } : renderBand(band, scope, ctxFor(kind, extra), ml, y);
|
|
3302
3394
|
for (const it of r.items) page.items.push(it);
|
|
3395
|
+
if (r.anchors) {
|
|
3396
|
+
for (const a of r.anchors) if (a.y + a.h > limit + 0.01) a.offPage = true;
|
|
3397
|
+
(page.anchors ?? (page.anchors = [])).push(...r.anchors);
|
|
3398
|
+
}
|
|
3303
3399
|
y += r.height;
|
|
3304
3400
|
hasBody = true;
|
|
3305
3401
|
return r.height;
|
|
3306
3402
|
};
|
|
3307
|
-
const heightOf = (band, kind, scope, extra = {}) => renderBand(band, scope, ctxFor(kind, { ...extra, previous: void 0 }), 0, 0).height;
|
|
3403
|
+
const heightOf = (band, kind, scope, extra = {}) => renderBand(band, scope, ctxFor(kind, { ...extra, previous: void 0, anchors: false }), 0, 0).height;
|
|
3308
3404
|
const renderChunk = (e, scope, extra) => {
|
|
3309
3405
|
const band = e.band;
|
|
3310
3406
|
const n2 = Math.max(1, Math.floor(band.columns ?? 1));
|
|
@@ -3317,7 +3413,7 @@ function paginate(def, data, opts, knownTotal, docHash) {
|
|
|
3317
3413
|
ml + i * (colW + gap),
|
|
3318
3414
|
0
|
|
3319
3415
|
));
|
|
3320
|
-
return { height: Math.max(...parts.map((p) => p.height)), items: parts.flatMap((p) => p.items) };
|
|
3416
|
+
return { height: Math.max(...parts.map((p) => p.height)), items: parts.flatMap((p) => p.items), anchors: opts.anchors ? parts.flatMap((p) => p.anchors ?? []) : void 0 };
|
|
3321
3417
|
};
|
|
3322
3418
|
const isTableEntry = (e) => e.kind === "detail" || e.kind === "groupHeader" || e.kind === "groupFooter";
|
|
3323
3419
|
const placeColumnHeader = (e) => {
|
|
@@ -3413,13 +3509,15 @@ function paginate(def, data, opts, knownTotal, docHash) {
|
|
|
3413
3509
|
const draw = (band, kind, top) => {
|
|
3414
3510
|
if (!band) return;
|
|
3415
3511
|
if (band.visible && !truthy2(evaluate(band.visible, scope))) return;
|
|
3416
|
-
|
|
3512
|
+
const r = renderBand(band, scope, ctxFor(kind, { previous: void 0 }), ml, top, 0);
|
|
3513
|
+
extraItems.push(...r.items);
|
|
3514
|
+
if (r.anchors) (p.anchors ?? (p.anchors = [])).push(...r.anchors);
|
|
3417
3515
|
};
|
|
3418
3516
|
if (!(def.pageHeaderSkipFirst && i === 0)) draw(b.pageHeader, "pageHeader", mt);
|
|
3419
3517
|
draw(b.pageFooter, "pageFooter", geo.height - mb - footerH);
|
|
3420
3518
|
p.items.push(...extraItems);
|
|
3421
3519
|
});
|
|
3422
|
-
decoratePages(pages, { watermark: def.watermark, emboss: def.emboss }, geo, (p, i) => ({
|
|
3520
|
+
decoratePages(pages, { watermark: def.watermark, emboss: def.emboss, locale }, geo, (p, i) => ({
|
|
3423
3521
|
...allScope,
|
|
3424
3522
|
rows: pageRows[i],
|
|
3425
3523
|
pageRows: pageRows[i],
|
|
@@ -3818,10 +3916,10 @@ function dataToXlsx(def, data, opts = {}) {
|
|
|
3818
3916
|
rows.forEach((row, i) => {
|
|
3819
3917
|
const scope = { ...base2, row, vars: { ...base2.vars, rowNum: i + 1, rowIndex: i + 1 } };
|
|
3820
3918
|
out.push(cols.map((c) => {
|
|
3821
|
-
if (c.type === "text") return { v: interpolate(c.text, scope) };
|
|
3919
|
+
if (c.type === "text") return { v: interpolate(c.text, scope, opts.locale) };
|
|
3822
3920
|
const v = evaluate(c.expr, scope);
|
|
3823
3921
|
if (typeof v === "number" || v instanceof Date) return { v, s: { fmt: excelFormat(c.format) } };
|
|
3824
|
-
return { v: v == null ? "" : formatValue(v, c.format) };
|
|
3922
|
+
return { v: v == null ? "" : formatValue(v, c.format, opts.locale) };
|
|
3825
3923
|
}));
|
|
3826
3924
|
});
|
|
3827
3925
|
const widths = cols.map((c, i) => Math.min(60, Math.max(6, String(header[i].v ?? "").length * 2, c.w / MM_PER_CHAR)));
|
|
@@ -3841,7 +3939,7 @@ function dataToCsv(def, data, opts = {}) {
|
|
|
3841
3939
|
const scope = { ...base2, row, vars: { ...base2.vars, rowNum: i + 1 } };
|
|
3842
3940
|
lines.push(fields.map((f2) => {
|
|
3843
3941
|
const v = evaluate(f2.expr, scope);
|
|
3844
|
-
return typeof v === "number" ? q(String(v), true) : q(formatValue(v, f2.format));
|
|
3942
|
+
return typeof v === "number" ? q(String(v), true) : q(formatValue(v, f2.format, opts.locale));
|
|
3845
3943
|
}).join(","));
|
|
3846
3944
|
});
|
|
3847
3945
|
return "\uFEFF" + lines.join("\r\n");
|
|
@@ -5550,8 +5648,10 @@ function createReport(definition, data = {}, opts = {}) {
|
|
|
5550
5648
|
let rows = data;
|
|
5551
5649
|
let params = { ...opts.params };
|
|
5552
5650
|
let cached = null;
|
|
5651
|
+
let anchored = null;
|
|
5553
5652
|
void checkLicense(opts.licenseKey);
|
|
5554
5653
|
const layout = () => cached ?? (cached = layoutReport(def, rows, { ...opts, params }));
|
|
5654
|
+
const withAnchors = () => opts.anchors ? layout() : anchored ?? (anchored = layoutReport(def, rows, { ...opts, params, anchors: true }));
|
|
5555
5655
|
const allowed = async (kind, fileName) => {
|
|
5556
5656
|
if (!opts.onOutput) return true;
|
|
5557
5657
|
const l = layout();
|
|
@@ -5565,12 +5665,16 @@ function createReport(definition, data = {}, opts = {}) {
|
|
|
5565
5665
|
layout,
|
|
5566
5666
|
async ready() {
|
|
5567
5667
|
await ensureFonts(def);
|
|
5568
|
-
cached = null;
|
|
5668
|
+
cached = anchored = null;
|
|
5569
5669
|
return layout();
|
|
5570
5670
|
},
|
|
5671
|
+
anchors(prefix = "") {
|
|
5672
|
+
const l = withAnchors();
|
|
5673
|
+
return l.pages.flatMap((p, i) => (p.anchors ?? []).filter((a) => a.id.startsWith(prefix)).map((a) => ({ ...a, page: i + 1, pageWidth: p.width, pageHeight: p.height })));
|
|
5674
|
+
},
|
|
5571
5675
|
setData(d) {
|
|
5572
5676
|
rows = d;
|
|
5573
|
-
cached = null;
|
|
5677
|
+
cached = anchored = null;
|
|
5574
5678
|
},
|
|
5575
5679
|
fingerprint() {
|
|
5576
5680
|
const l = layout();
|
|
@@ -5578,11 +5682,11 @@ function createReport(definition, data = {}, opts = {}) {
|
|
|
5578
5682
|
},
|
|
5579
5683
|
setParams(p) {
|
|
5580
5684
|
params = { ...p };
|
|
5581
|
-
cached = null;
|
|
5685
|
+
cached = anchored = null;
|
|
5582
5686
|
},
|
|
5583
5687
|
setDefinition(d) {
|
|
5584
5688
|
def = d;
|
|
5585
|
-
cached = null;
|
|
5689
|
+
cached = anchored = null;
|
|
5586
5690
|
},
|
|
5587
5691
|
async print(o = {}) {
|
|
5588
5692
|
if (!await allowed("print")) return false;
|
|
@@ -5608,7 +5712,7 @@ function createReport(definition, data = {}, opts = {}) {
|
|
|
5608
5712
|
},
|
|
5609
5713
|
async toXlsx(mode = "form") {
|
|
5610
5714
|
if (!await allowed(mode === "data" ? "xlsx-data" : "xlsx")) return null;
|
|
5611
|
-
const bytes = mode === "data" ? dataToXlsx(def, rows, { params }) : pagesToXlsx(layout(), def);
|
|
5715
|
+
const bytes = mode === "data" ? dataToXlsx(def, rows, { params, locale: opts.locale }) : pagesToXlsx(layout(), def);
|
|
5612
5716
|
return new Blob([bytes], { type: MIME.xlsx });
|
|
5613
5717
|
},
|
|
5614
5718
|
async toHwpx() {
|
|
@@ -5617,7 +5721,7 @@ function createReport(definition, data = {}, opts = {}) {
|
|
|
5617
5721
|
},
|
|
5618
5722
|
async toCsv() {
|
|
5619
5723
|
if (!await allowed("csv")) return null;
|
|
5620
|
-
return new Blob([dataToCsv(def, rows, { params })], { type: MIME.csv });
|
|
5724
|
+
return new Blob([dataToCsv(def, rows, { params, locale: opts.locale })], { type: MIME.csv });
|
|
5621
5725
|
},
|
|
5622
5726
|
async download(kind, fileName) {
|
|
5623
5727
|
const base2 = safeName(fileName ?? def.name ?? "report");
|
|
@@ -5719,7 +5823,7 @@ var PAGE_GAP = 16;
|
|
|
5719
5823
|
function paramInput(name, p, value, t) {
|
|
5720
5824
|
const id = `fr-p-${name.replace(/[^\w-]/g, "_")}`;
|
|
5721
5825
|
const label = `<label for="${id}">${escapeHtml(p.label ?? name)}${p.required ? ' <b aria-hidden="true">*</b>' : ""}</label>`;
|
|
5722
|
-
const v = value == null ? "" : value instanceof Date ? value
|
|
5826
|
+
const v = value == null ? "" : value instanceof Date ? formatDate(value, "yyyy-MM-dd") : String(value);
|
|
5723
5827
|
const req = p.required ? " required" : "";
|
|
5724
5828
|
if (p.type === "select" || p.options) {
|
|
5725
5829
|
return `<div class="fr-param">${label}<select id="${id}" name="${escapeHtml(name)}"${req}>${(p.options ?? []).map((o) => `<option value="${escapeHtml(String(o.value))}"${String(o.value) === v ? " selected" : ""}>${escapeHtml(String(o.label ?? o.value))}</option>`).join("")}</select></div>`;
|
|
@@ -5784,9 +5888,10 @@ function createViewer(el, options) {
|
|
|
5784
5888
|
const toggle = $(".fr-params-toggle");
|
|
5785
5889
|
const menuBtn = $('[data-act="menu"]');
|
|
5786
5890
|
const menuList = $(".fr-menu-list");
|
|
5891
|
+
const overlayText = $(".fr-overlay-text");
|
|
5787
5892
|
const busy = (text) => {
|
|
5788
5893
|
overlay.hidden = text === null;
|
|
5789
|
-
|
|
5894
|
+
overlayText.textContent = text ?? "";
|
|
5790
5895
|
};
|
|
5791
5896
|
const showParams = (open) => {
|
|
5792
5897
|
if (!form || !toggle) return;
|
|
@@ -5838,6 +5943,7 @@ function createViewer(el, options) {
|
|
|
5838
5943
|
const holder = document.createElement("div");
|
|
5839
5944
|
holder.className = "fr-page";
|
|
5840
5945
|
holder.dataset.index = String(i);
|
|
5946
|
+
holder.dataset.page = String(i + 1);
|
|
5841
5947
|
holder.setAttribute("aria-label", `${t.page} ${i + 1}`);
|
|
5842
5948
|
frag.appendChild(holder);
|
|
5843
5949
|
});
|
|
@@ -5847,7 +5953,11 @@ function createViewer(el, options) {
|
|
|
5847
5953
|
for (const e of entries) if (e.isIntersecting) drawPage(e.target);
|
|
5848
5954
|
updateCurrent();
|
|
5849
5955
|
}, { root: body, rootMargin: "600px 0px" }) : null;
|
|
5850
|
-
pagesEl.querySelectorAll(".fr-page")
|
|
5956
|
+
const holders = pagesEl.querySelectorAll(".fr-page");
|
|
5957
|
+
holders.forEach((p, i) => observer ? observer.observe(p) : i < 3 && drawPage(p));
|
|
5958
|
+
const pageH = result.height * MM_PX * scale + PAGE_GAP;
|
|
5959
|
+
const from = Math.min(holders.length - 1, Math.max(0, Math.floor(body.scrollTop / pageH)));
|
|
5960
|
+
for (let i = from; i < Math.min(holders.length, from + 1 + Math.ceil(body.clientHeight / pageH)); i++) drawPage(holders[i]);
|
|
5851
5961
|
if (totalEl) totalEl.textContent = `/ ${result.pages.length}`;
|
|
5852
5962
|
lastQuery = "";
|
|
5853
5963
|
hits = [];
|
|
@@ -5897,35 +6007,57 @@ function createViewer(el, options) {
|
|
|
5897
6007
|
body.scrollTop = ratio * body.scrollHeight;
|
|
5898
6008
|
};
|
|
5899
6009
|
let runSeq = 0;
|
|
5900
|
-
|
|
5901
|
-
|
|
6010
|
+
let runs = 0;
|
|
6011
|
+
let latest = Promise.resolve();
|
|
6012
|
+
let running = false, deciding = 0;
|
|
6013
|
+
let outcome = null;
|
|
6014
|
+
const showState = () => {
|
|
6015
|
+
if (destroyed) return;
|
|
6016
|
+
el.dataset.frState = running || deciding ? "busy" : outcome ?? "busy";
|
|
6017
|
+
};
|
|
6018
|
+
const finish = (how) => {
|
|
6019
|
+
running = false;
|
|
6020
|
+
outcome = how;
|
|
6021
|
+
el.dataset.frRun = String(++runs);
|
|
6022
|
+
showState();
|
|
6023
|
+
};
|
|
6024
|
+
const runOnce = async (seq2) => {
|
|
5902
6025
|
busy(t.loading);
|
|
6026
|
+
running = true;
|
|
6027
|
+
showState();
|
|
5903
6028
|
let during = "data";
|
|
6029
|
+
const stale = () => seq2 !== runSeq;
|
|
5904
6030
|
try {
|
|
5905
6031
|
if (options.fetchData) {
|
|
5906
6032
|
const fetched = await options.fetchData(params);
|
|
5907
|
-
if (
|
|
6033
|
+
if (stale()) return await latest;
|
|
5908
6034
|
report.setData(fetched);
|
|
5909
6035
|
}
|
|
5910
6036
|
during = "layout";
|
|
5911
6037
|
report.setParams(params);
|
|
5912
6038
|
await ensureFonts(report.definition);
|
|
5913
6039
|
if (destroyed) return;
|
|
6040
|
+
if (stale()) return await latest;
|
|
5914
6041
|
result = report.layout();
|
|
5915
6042
|
reported = -1;
|
|
5916
6043
|
build();
|
|
6044
|
+
finish("ready");
|
|
5917
6045
|
try {
|
|
5918
6046
|
options.onReady?.({ pages: result.pages.length, rows: result.rowCount, ms: result.ms, params: { ...params } });
|
|
5919
6047
|
} catch (e) {
|
|
5920
6048
|
console.error(e);
|
|
5921
6049
|
}
|
|
5922
6050
|
} catch (e) {
|
|
6051
|
+
if (stale()) return await latest;
|
|
6052
|
+
if (destroyed) return;
|
|
5923
6053
|
const err = fail(e, during);
|
|
5924
6054
|
pagesEl.innerHTML = `<p class="fr-empty">${t.error}: ${escapeHtml(err.message)}</p>`;
|
|
6055
|
+
finish("error");
|
|
5925
6056
|
} finally {
|
|
5926
|
-
busy(null);
|
|
6057
|
+
if (!stale()) busy(null);
|
|
5927
6058
|
}
|
|
5928
6059
|
};
|
|
6060
|
+
const run = () => latest = runOnce(++runSeq);
|
|
5929
6061
|
const exportAs = async (kind) => {
|
|
5930
6062
|
busy(t.exporting);
|
|
5931
6063
|
try {
|
|
@@ -6056,14 +6188,22 @@ function createViewer(el, options) {
|
|
|
6056
6188
|
const v = inp.value;
|
|
6057
6189
|
next[inp.name] = type === "number" ? v === "" ? null : Number(v) : type === "boolean" ? v === "true" : v;
|
|
6058
6190
|
});
|
|
6191
|
+
deciding++;
|
|
6192
|
+
showState();
|
|
6059
6193
|
let answer;
|
|
6060
6194
|
try {
|
|
6061
6195
|
answer = await options.onParamsSubmit?.({ ...next });
|
|
6062
6196
|
} catch (x) {
|
|
6063
6197
|
fail(x, "data");
|
|
6198
|
+
deciding--;
|
|
6199
|
+
showState();
|
|
6200
|
+
return;
|
|
6201
|
+
}
|
|
6202
|
+
deciding--;
|
|
6203
|
+
if (answer === false) {
|
|
6204
|
+
showState();
|
|
6064
6205
|
return;
|
|
6065
6206
|
}
|
|
6066
|
-
if (answer === false) return;
|
|
6067
6207
|
params = answer && typeof answer === "object" ? { ...next, ...answer } : next;
|
|
6068
6208
|
void run();
|
|
6069
6209
|
});
|
|
@@ -6175,6 +6315,8 @@ function createViewer(el, options) {
|
|
|
6175
6315
|
listeners.abort();
|
|
6176
6316
|
el.innerHTML = "";
|
|
6177
6317
|
el.classList.remove("fr-viewer");
|
|
6318
|
+
delete el.dataset.frState;
|
|
6319
|
+
delete el.dataset.frRun;
|
|
6178
6320
|
}
|
|
6179
6321
|
};
|
|
6180
6322
|
}
|
|
@@ -6222,7 +6364,7 @@ function ensureIds(def) {
|
|
|
6222
6364
|
for (const s of bandSlots(def)) for (const el of s.band.elements) if (el.id) all.add(el.id);
|
|
6223
6365
|
const seen = /* @__PURE__ */ new Set();
|
|
6224
6366
|
for (const s of bandSlots(def)) for (const el of s.band.elements) {
|
|
6225
|
-
if (el.id && /^[\w
|
|
6367
|
+
if (el.id && /^[\w:.-]{1,64}$/.test(String(el.id)) && !seen.has(el.id)) {
|
|
6226
6368
|
seen.add(el.id);
|
|
6227
6369
|
continue;
|
|
6228
6370
|
}
|
|
@@ -6340,7 +6482,13 @@ var L = {
|
|
|
6340
6482
|
suppress: "\uAC19\uC740 \uAC12 \uBC18\uBCF5 \uC228\uAE30\uAE30",
|
|
6341
6483
|
visible: "\uD45C\uC2DC \uC870\uAC74 (\uC2DD)",
|
|
6342
6484
|
canGrow: "\uB0B4\uC6A9\uC5D0 \uB9DE\uCDB0 \uB298\uB9AC\uAE30",
|
|
6343
|
-
stretch: "\
|
|
6485
|
+
stretch: "\uB192\uC774 \uB9DE\uCD94\uAE30",
|
|
6486
|
+
stretchAuto: "\uAE30\uBCF8 (\uBC34\uB4DC \uBC14\uB2E5\uC5D0 \uB2FF\uC73C\uBA74 \uBC34\uB4DC \uB192\uC774)",
|
|
6487
|
+
stretchBand: "\uBC34\uB4DC \uB192\uC774",
|
|
6488
|
+
stretchRow: "\uAC19\uC740 \uC904\uC758 \uAC00\uC7A5 \uB192\uC740 \uCE78",
|
|
6489
|
+
stretchNone: "\uB9DE\uCD94\uC9C0 \uC54A\uC74C",
|
|
6490
|
+
shift: "\uC704 \uC694\uC18C\uAC00 \uB298\uBA74 \uD568\uAED8 \uB0B4\uB824\uAC00\uAE30",
|
|
6491
|
+
collapse: "\uC228\uC73C\uBA74 \uC790\uB9AC \uC811\uAE30 (\uC544\uB798 \uC694\uC18C\uAC00 \uC62C\uB77C\uC634)",
|
|
6344
6492
|
style: "\uC2A4\uD0C0\uC77C",
|
|
6345
6493
|
bold: "\uAD75\uAC8C",
|
|
6346
6494
|
italic: "\uAE30\uC6B8\uC784",
|
|
@@ -6454,7 +6602,13 @@ var L = {
|
|
|
6454
6602
|
suppress: "Hide repeated values",
|
|
6455
6603
|
visible: "Visible when (expression)",
|
|
6456
6604
|
canGrow: "Grow to fit content",
|
|
6457
|
-
stretch: "
|
|
6605
|
+
stretch: "Height",
|
|
6606
|
+
stretchAuto: "Default (band height when touching its bottom)",
|
|
6607
|
+
stretchBand: "Band height",
|
|
6608
|
+
stretchRow: "Tallest cell on the same row",
|
|
6609
|
+
stretchNone: "Own height",
|
|
6610
|
+
shift: "Move down when an element above grows",
|
|
6611
|
+
collapse: "Give the space back when hidden",
|
|
6458
6612
|
style: "Style",
|
|
6459
6613
|
bold: "Bold",
|
|
6460
6614
|
italic: "Italic",
|
|
@@ -6713,7 +6867,16 @@ function elementFields(els, t, def) {
|
|
|
6713
6867
|
out.push({ label: t.shrink, kind: "check", get: () => !!same((e) => !!e.style?.shrinkToFit), set: (v) => styleSet("shrinkToFit")(v) });
|
|
6714
6868
|
out.push({ label: t.canGrow, kind: "check", get: () => same((e) => e.canGrow !== false), set: (v) => setAll((e) => v ? delete e.canGrow : e.canGrow = false) });
|
|
6715
6869
|
}
|
|
6870
|
+
out.push({
|
|
6871
|
+
label: t.stretch,
|
|
6872
|
+
kind: "select",
|
|
6873
|
+
options: [{ value: "", label: t.stretchAuto }, { value: "band", label: t.stretchBand }, { value: "row", label: t.stretchRow }, { value: "none", label: t.stretchNone }],
|
|
6874
|
+
get: () => same((e) => e.stretch === void 0 ? "" : e.stretch === "row" ? "row" : e.stretch ? "band" : "none"),
|
|
6875
|
+
set: (v) => setAll((e) => v === "row" ? e.stretch = "row" : v === "band" ? e.stretch = true : v === "none" ? e.stretch = false : delete e.stretch)
|
|
6876
|
+
});
|
|
6877
|
+
out.push({ label: t.shift, kind: "check", get: () => same((e) => e.shift !== false), set: (v) => setAll((e) => v ? delete e.shift : e.shift = false) });
|
|
6716
6878
|
out.push({ label: t.visible, kind: "expr", wide: true, get: () => same((e) => e.visible ?? "") ?? "", set: (v) => setAll((e) => v ? e.visible = String(v) : delete e.visible) });
|
|
6879
|
+
out.push({ label: t.collapse, kind: "check", get: () => same((e) => !!e.collapse), set: (v) => setAll((e) => v ? e.collapse = true : delete e.collapse) });
|
|
6717
6880
|
return out;
|
|
6718
6881
|
}
|
|
6719
6882
|
function bandFields(band, label, t, group, isDetail) {
|
|
@@ -7879,7 +8042,7 @@ async function renderPdf(def, data = {}, opts = {}) {
|
|
|
7879
8042
|
return writePdf(layout.pages, { title: def.name, creator: "FreeReport", ...opts.pdf, fonts: { default: first, ...fonts } });
|
|
7880
8043
|
}
|
|
7881
8044
|
async function renderXlsx(def, data = {}, opts = {}) {
|
|
7882
|
-
if (opts.mode === "data") return dataToXlsx(def, data, { params: opts.params });
|
|
8045
|
+
if (opts.mode === "data") return dataToXlsx(def, data, { params: opts.params, locale: opts.locale });
|
|
7883
8046
|
const { layout } = await prepare2(def, data, opts);
|
|
7884
8047
|
return pagesToXlsx(layout, def);
|
|
7885
8048
|
}
|