@rastr/pdf-template 0.0.0-stage → 0.1.0-beta.2
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/LICENSE +21 -0
- package/README.md +34 -2
- package/dist/index.cjs +1135 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +324 -0
- package/dist/index.d.ts +324 -0
- package/dist/index.js +1091 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
MM_PER_PT: () => MM_PER_PT,
|
|
24
|
+
TemplateError: () => TemplateError,
|
|
25
|
+
TemplateStoreError: () => TemplateStoreError,
|
|
26
|
+
compileCondition: () => compileCondition,
|
|
27
|
+
createLocalTemplateStore: () => createLocalTemplateStore,
|
|
28
|
+
createRestTemplateStore: () => createRestTemplateStore,
|
|
29
|
+
defaultText: () => defaultText,
|
|
30
|
+
evaluateText: () => evaluateText,
|
|
31
|
+
formatValue: () => formatValue,
|
|
32
|
+
layoutReport: () => layoutReport,
|
|
33
|
+
layoutReportAsync: () => layoutReportAsync,
|
|
34
|
+
measureText: () => measureText,
|
|
35
|
+
parseExpression: () => parseExpression,
|
|
36
|
+
parseFontMetrics: () => parseFontMetrics,
|
|
37
|
+
round2: () => round2,
|
|
38
|
+
templateLoader: () => templateLoader,
|
|
39
|
+
validateTemplate: () => validateTemplate,
|
|
40
|
+
wrapText: () => wrapText
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(index_exports);
|
|
43
|
+
|
|
44
|
+
// src/errors.ts
|
|
45
|
+
var TemplateError = class extends Error {
|
|
46
|
+
issues;
|
|
47
|
+
constructor(issues) {
|
|
48
|
+
super(issues.map((issue) => issue.path ? `${issue.path}: ${issue.message}` : issue.message).join("; "));
|
|
49
|
+
this.name = "TemplateError";
|
|
50
|
+
this.issues = issues;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function fail(message, path = "") {
|
|
54
|
+
throw new TemplateError([{ path, message }]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/conditions.ts
|
|
58
|
+
var COMPARISON = /^row\.([A-Za-z_][A-Za-z0-9_]*)\s*(==|!=|<=|>=|<|>)\s*(-?[0-9]+(?:\.[0-9]+)?|'[^']*'|true|false|null)$/;
|
|
59
|
+
function parseComparison(source, whole) {
|
|
60
|
+
const match = COMPARISON.exec(source.trim());
|
|
61
|
+
if (!match) fail(`Invalid condition "${whole}"; use row.field == literal joined by && or ||`);
|
|
62
|
+
const [, field, op, raw] = match;
|
|
63
|
+
const literal = raw === "true" ? true : raw === "false" ? false : raw === "null" ? null : raw.startsWith("'") ? raw.slice(1, -1) : Number(raw);
|
|
64
|
+
return { field, op, literal };
|
|
65
|
+
}
|
|
66
|
+
function compare(value, op, literal) {
|
|
67
|
+
const actual = value === void 0 ? null : value;
|
|
68
|
+
if (op === "==" || op === "!=") {
|
|
69
|
+
const equal = actual === literal;
|
|
70
|
+
return op === "==" ? equal : !equal;
|
|
71
|
+
}
|
|
72
|
+
if (!(typeof actual === "number" && typeof literal === "number" || typeof actual === "string" && typeof literal === "string")) return false;
|
|
73
|
+
const a = actual;
|
|
74
|
+
const b = literal;
|
|
75
|
+
switch (op) {
|
|
76
|
+
case "<":
|
|
77
|
+
return a < b;
|
|
78
|
+
case "<=":
|
|
79
|
+
return a <= b;
|
|
80
|
+
case ">":
|
|
81
|
+
return a > b;
|
|
82
|
+
default:
|
|
83
|
+
return a >= b;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function compileCondition(source) {
|
|
87
|
+
if (/'[^']*$/.test(source.replace(/'[^']*'/g, ""))) fail(`Unterminated text in condition "${source}"`);
|
|
88
|
+
const masked = source.replace(/'[^']*'/g, (match) => "\0".repeat(match.length));
|
|
89
|
+
const splitAt = (text, mask, separator) => {
|
|
90
|
+
const parts = [];
|
|
91
|
+
let start = 0;
|
|
92
|
+
for (let i = mask.indexOf(separator); i >= 0; i = mask.indexOf(separator, i + separator.length)) {
|
|
93
|
+
parts.push(text.slice(start, i));
|
|
94
|
+
start = i + separator.length;
|
|
95
|
+
}
|
|
96
|
+
parts.push(text.slice(start));
|
|
97
|
+
return parts;
|
|
98
|
+
};
|
|
99
|
+
const maskedOr = splitAt(masked, masked, "||");
|
|
100
|
+
let offset = 0;
|
|
101
|
+
const groups = maskedOr.map((maskedGroup) => {
|
|
102
|
+
const group2 = source.slice(offset, offset + maskedGroup.length);
|
|
103
|
+
offset += maskedGroup.length + 2;
|
|
104
|
+
let inner = 0;
|
|
105
|
+
return splitAt(maskedGroup, maskedGroup, "&&").map((maskedPart) => {
|
|
106
|
+
const part = group2.slice(inner, inner + maskedPart.length);
|
|
107
|
+
inner += maskedPart.length + 2;
|
|
108
|
+
return parseComparison(part, source);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
return (row) => groups.some((group2) => group2.every((c) => compare(Object.prototype.hasOwnProperty.call(row, c.field) ? row[c.field] : null, c.op, c.literal)));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/format.ts
|
|
115
|
+
var NBSP = "\xA0";
|
|
116
|
+
var ISO_DATE = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/;
|
|
117
|
+
var ISO_DATETIME = /^([0-9]{4})-([0-9]{2})-([0-9]{2})(?:[T ]([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]{1,9}))?)?)?(Z|[+-][0-9]{2}:[0-9]{2})?$/;
|
|
118
|
+
var MIN_MS = -621355968e5;
|
|
119
|
+
var MAX_MS = 253402300799999;
|
|
120
|
+
var RULES = {
|
|
121
|
+
cs: { group: NBSP, decimal: "," },
|
|
122
|
+
en: { group: ",", decimal: "." }
|
|
123
|
+
};
|
|
124
|
+
var FORMAT_NAMES = /* @__PURE__ */ new Set(["currency", "number", "percent", "date", "datetime"]);
|
|
125
|
+
function toNumber(value) {
|
|
126
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
127
|
+
if (typeof value === "string" && value.trim() !== "" && /^-?[0-9]+(\.[0-9]+)?$/.test(value.trim())) return Number(value);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
function fixed(value, decimals) {
|
|
131
|
+
const scaled = Number((Math.abs(value) * 10 ** decimals).toPrecision(15));
|
|
132
|
+
const rounded = Math.floor(scaled + 0.5);
|
|
133
|
+
const digits = rounded.toFixed(0).padStart(decimals + 1, "0");
|
|
134
|
+
const integer = digits.slice(0, digits.length - decimals);
|
|
135
|
+
const fraction = decimals > 0 ? digits.slice(-decimals) : "";
|
|
136
|
+
return { negative: value < 0 && rounded !== 0, integer, fraction };
|
|
137
|
+
}
|
|
138
|
+
function group(integer, separator) {
|
|
139
|
+
return integer.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
|
|
140
|
+
}
|
|
141
|
+
function formatNumber(value, decimals, locale) {
|
|
142
|
+
const places = decimals ?? (Number.isInteger(value) ? 0 : 2);
|
|
143
|
+
const rules = RULES[locale];
|
|
144
|
+
const parts = fixed(value, places);
|
|
145
|
+
const text = group(parts.integer, rules.group) + (places > 0 ? rules.decimal + parts.fraction : "");
|
|
146
|
+
return parts.negative ? `-${text}` : text;
|
|
147
|
+
}
|
|
148
|
+
var zoneFormats = /* @__PURE__ */ new Map();
|
|
149
|
+
function inTimeZone(date, timeZone) {
|
|
150
|
+
let format = zoneFormats.get(timeZone);
|
|
151
|
+
if (!format) {
|
|
152
|
+
try {
|
|
153
|
+
format = new Intl.DateTimeFormat("en-US", {
|
|
154
|
+
timeZone,
|
|
155
|
+
year: "numeric",
|
|
156
|
+
month: "numeric",
|
|
157
|
+
day: "numeric",
|
|
158
|
+
hour: "numeric",
|
|
159
|
+
minute: "numeric",
|
|
160
|
+
hourCycle: "h23"
|
|
161
|
+
});
|
|
162
|
+
} catch {
|
|
163
|
+
fail(`Unknown time zone "${timeZone}"`, "timeZone");
|
|
164
|
+
}
|
|
165
|
+
zoneFormats.set(timeZone, format);
|
|
166
|
+
}
|
|
167
|
+
const parts = {};
|
|
168
|
+
for (const part of format.formatToParts(date)) parts[part.type] = part.value;
|
|
169
|
+
return { year: Number(parts["year"]), month: Number(parts["month"]), day: Number(parts["day"]), hour: Number(parts["hour"]) % 24, minute: Number(parts["minute"]) };
|
|
170
|
+
}
|
|
171
|
+
function toDay(value, timeZone) {
|
|
172
|
+
if (typeof value === "string") {
|
|
173
|
+
const match = ISO_DATE.exec(value);
|
|
174
|
+
if (match) return { year: Number(match[1]), month: Number(match[2]), day: Number(match[3]), hour: 0, minute: 0 };
|
|
175
|
+
}
|
|
176
|
+
const instant = toInstant(value);
|
|
177
|
+
return instant ? inTimeZone(instant, timeZone) : null;
|
|
178
|
+
}
|
|
179
|
+
function inRange(ms) {
|
|
180
|
+
return Number.isFinite(ms) && ms >= MIN_MS && ms <= MAX_MS ? new Date(ms) : null;
|
|
181
|
+
}
|
|
182
|
+
function toInstant(value) {
|
|
183
|
+
if (value instanceof Date) return inRange(value.getTime());
|
|
184
|
+
if (typeof value === "number") return inRange(Math.trunc(value));
|
|
185
|
+
if (typeof value !== "string") return null;
|
|
186
|
+
const match = ISO_DATETIME.exec(value);
|
|
187
|
+
if (!match) return null;
|
|
188
|
+
const [year, month, day, hour, minute, second] = [1, 2, 3, 4, 5, 6].map((i) => Number(match[i] ?? 0));
|
|
189
|
+
const millisecond = Number((match[7] ?? "").padEnd(3, "0").slice(0, 3));
|
|
190
|
+
if (month < 1 || month > 12 || day < 1 || hour > 23 || minute > 59 || second > 59) return null;
|
|
191
|
+
const utc = new Date(Date.UTC(2e3, month - 1, day, hour, minute, second, millisecond));
|
|
192
|
+
utc.setUTCFullYear(year);
|
|
193
|
+
if (utc.getUTCDate() !== day || utc.getUTCMonth() !== month - 1) return null;
|
|
194
|
+
const zone = match[8];
|
|
195
|
+
const offset = zone === void 0 || zone === "Z" ? 0 : (zone.startsWith("-") ? -1 : 1) * (Number(zone.slice(1, 3)) * 60 + Number(zone.slice(4, 6)));
|
|
196
|
+
return inRange(utc.getTime() - offset * 6e4);
|
|
197
|
+
}
|
|
198
|
+
function dateText(time, locale) {
|
|
199
|
+
return locale === "cs" ? `${time.day}.${NBSP}${time.month}.${NBSP}${time.year}` : `${time.month}/${time.day}/${time.year}`;
|
|
200
|
+
}
|
|
201
|
+
function timeText(time, locale) {
|
|
202
|
+
const minute = String(time.minute).padStart(2, "0");
|
|
203
|
+
if (locale === "cs") return `${time.hour}:${minute}`;
|
|
204
|
+
const hour12 = time.hour % 12 === 0 ? 12 : time.hour % 12;
|
|
205
|
+
return `${hour12}:${minute}${NBSP}${time.hour < 12 ? "AM" : "PM"}`;
|
|
206
|
+
}
|
|
207
|
+
function argNumber(arg, spec) {
|
|
208
|
+
if (arg === void 0 || arg === "") return null;
|
|
209
|
+
const parsed = Number(arg);
|
|
210
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 10) fail(`Invalid decimals in "${spec}"`);
|
|
211
|
+
return parsed;
|
|
212
|
+
}
|
|
213
|
+
function checkFormatSpec(spec) {
|
|
214
|
+
const [name, ...args] = spec.split(":");
|
|
215
|
+
if (!name || !FORMAT_NAMES.has(name)) fail(`Unknown format "${spec}"`);
|
|
216
|
+
if (name === "currency") {
|
|
217
|
+
if (args[0] !== void 0 && !/^[A-Z]{3}$/.test(args[0])) fail(`Invalid currency in "${spec}"`);
|
|
218
|
+
argNumber(args[1], spec);
|
|
219
|
+
} else if (name === "number" || name === "percent") {
|
|
220
|
+
argNumber(args[0], spec);
|
|
221
|
+
} else if (args.length > 0) {
|
|
222
|
+
fail(`Format "${name}" takes no arguments`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function formatValue(value, spec, locale, timeZone) {
|
|
226
|
+
if (value === null || value === void 0) return "";
|
|
227
|
+
checkFormatSpec(spec);
|
|
228
|
+
const [name, ...args] = spec.split(":");
|
|
229
|
+
switch (name) {
|
|
230
|
+
case "currency": {
|
|
231
|
+
const number = toNumber(value);
|
|
232
|
+
if (number === null) return String(value);
|
|
233
|
+
const code = args[0] ?? "CZK";
|
|
234
|
+
const text = formatNumber(number, argNumber(args[1], spec) ?? 2, locale);
|
|
235
|
+
const negative = text.startsWith("-");
|
|
236
|
+
const digits = negative ? text.slice(1) : text;
|
|
237
|
+
if (locale === "cs") return `${negative ? "-" : ""}${digits}${NBSP}${code === "CZK" ? "K\u010D" : code}`;
|
|
238
|
+
return `${negative ? "-" : ""}${code}${NBSP}${digits}`;
|
|
239
|
+
}
|
|
240
|
+
case "number": {
|
|
241
|
+
const number = toNumber(value);
|
|
242
|
+
return number === null ? String(value) : formatNumber(number, argNumber(args[0], spec), locale);
|
|
243
|
+
}
|
|
244
|
+
case "percent": {
|
|
245
|
+
const number = toNumber(value);
|
|
246
|
+
if (number === null) return String(value);
|
|
247
|
+
const text = formatNumber(number * 100, argNumber(args[0], spec) ?? 0, locale);
|
|
248
|
+
return locale === "cs" ? `${text}${NBSP}%` : `${text}%`;
|
|
249
|
+
}
|
|
250
|
+
case "date": {
|
|
251
|
+
const day = toDay(value, timeZone);
|
|
252
|
+
return day ? dateText(day, locale) : String(value);
|
|
253
|
+
}
|
|
254
|
+
default: {
|
|
255
|
+
const instant = toInstant(value);
|
|
256
|
+
if (!instant) return String(value);
|
|
257
|
+
const time = inTimeZone(instant, timeZone);
|
|
258
|
+
return `${dateText(time, locale)}${NBSP}${timeText(time, locale)}`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function defaultText(value, locale) {
|
|
263
|
+
if (value === null || value === void 0) return "";
|
|
264
|
+
if (typeof value === "number") return Number.isFinite(value) ? formatNumber(value, null, locale) : "";
|
|
265
|
+
if (typeof value === "boolean") return locale === "cs" ? value ? "Ano" : "Ne" : value ? "Yes" : "No";
|
|
266
|
+
if (typeof value === "string") return value;
|
|
267
|
+
return "";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/expressions.ts
|
|
271
|
+
var FILTER_NAMES = /* @__PURE__ */ new Set([...FORMAT_NAMES, "upper", "default"]);
|
|
272
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
273
|
+
var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*/;
|
|
274
|
+
function parseBody(body) {
|
|
275
|
+
let rest = body.trim();
|
|
276
|
+
const take = (pattern) => {
|
|
277
|
+
const match = pattern.exec(rest);
|
|
278
|
+
if (!match) return null;
|
|
279
|
+
rest = rest.slice(match[0].length).trimStart();
|
|
280
|
+
return match[0];
|
|
281
|
+
};
|
|
282
|
+
const path = [];
|
|
283
|
+
do {
|
|
284
|
+
const key = take(IDENTIFIER);
|
|
285
|
+
if (key === null) fail(`Expected a name in "{{${body}}}"`);
|
|
286
|
+
if (FORBIDDEN_KEYS.has(key)) fail(`"${key}" is not allowed in "{{${body}}}"`);
|
|
287
|
+
path.push(key);
|
|
288
|
+
} while (take(/^\./) !== null);
|
|
289
|
+
const filters = [];
|
|
290
|
+
while (take(/^\|/) !== null) {
|
|
291
|
+
const name = take(IDENTIFIER);
|
|
292
|
+
if (name === null) fail(`Expected a filter name in "{{${body}}}"`);
|
|
293
|
+
if (!FILTER_NAMES.has(name)) fail(`Unknown filter "${name}"`);
|
|
294
|
+
const args = [];
|
|
295
|
+
while (take(/^:/) !== null) {
|
|
296
|
+
const quoted = take(/^'[^']*'/);
|
|
297
|
+
const arg = quoted !== null ? quoted.slice(1, -1) : take(/^-?[A-Za-z0-9_.]+/);
|
|
298
|
+
if (arg === null) fail(`Expected an argument for "${name}" in "{{${body}}}"`);
|
|
299
|
+
args.push(arg);
|
|
300
|
+
}
|
|
301
|
+
if (name === "default" && args.length !== 1) fail(`"default" takes one argument`);
|
|
302
|
+
if (name === "upper" && args.length > 0) fail(`"upper" takes no arguments`);
|
|
303
|
+
if (FORMAT_NAMES.has(name)) checkFormatSpec([name, ...args].join(":"));
|
|
304
|
+
filters.push({ name, args });
|
|
305
|
+
}
|
|
306
|
+
if (rest !== "") fail(`Unexpected "${rest}" in "{{${body}}}"`);
|
|
307
|
+
return { kind: "expr", path, filters };
|
|
308
|
+
}
|
|
309
|
+
function parseExpression(text) {
|
|
310
|
+
const parts = [];
|
|
311
|
+
let index = 0;
|
|
312
|
+
while (index < text.length) {
|
|
313
|
+
const open = text.indexOf("{{", index);
|
|
314
|
+
if (open < 0) {
|
|
315
|
+
parts.push({ kind: "text", value: text.slice(index) });
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
if (open > index) parts.push({ kind: "text", value: text.slice(index, open) });
|
|
319
|
+
const close = text.indexOf("}}", open + 2);
|
|
320
|
+
if (close < 0) fail(`Unterminated "{{" in "${text}"`);
|
|
321
|
+
parts.push(parseBody(text.slice(open + 2, close)));
|
|
322
|
+
index = close + 2;
|
|
323
|
+
}
|
|
324
|
+
return parts;
|
|
325
|
+
}
|
|
326
|
+
function lookup(scope, path) {
|
|
327
|
+
let current = scope;
|
|
328
|
+
for (const key of path) {
|
|
329
|
+
if (current === null || typeof current !== "object" || !Object.prototype.hasOwnProperty.call(current, key)) return void 0;
|
|
330
|
+
current = current[key];
|
|
331
|
+
}
|
|
332
|
+
return current;
|
|
333
|
+
}
|
|
334
|
+
function toUpper(text) {
|
|
335
|
+
let result = "";
|
|
336
|
+
for (const char of text) {
|
|
337
|
+
const upper = char.toUpperCase();
|
|
338
|
+
result += [...upper].length === 1 ? upper : char;
|
|
339
|
+
}
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
var isEmpty = (value) => value === null || value === void 0 || value === "";
|
|
343
|
+
function evaluateText(parts, scope, locale, timeZone) {
|
|
344
|
+
let text = "";
|
|
345
|
+
for (const part of parts) {
|
|
346
|
+
if (part.kind === "text") {
|
|
347
|
+
text += part.value;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
let value = lookup(scope, part.path);
|
|
351
|
+
let formatted = false;
|
|
352
|
+
for (const filter of part.filters) {
|
|
353
|
+
if (filter.name === "default") {
|
|
354
|
+
if (isEmpty(value)) value = filter.args[0];
|
|
355
|
+
} else if (filter.name === "upper") {
|
|
356
|
+
value = toUpper(formatted ? String(value) : defaultText(value, locale));
|
|
357
|
+
formatted = true;
|
|
358
|
+
} else if (!isEmpty(value)) {
|
|
359
|
+
value = formatValue(value, [filter.name, ...filter.args].join(":"), locale, timeZone);
|
|
360
|
+
formatted = true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
text += formatted ? String(value) : defaultText(value, locale);
|
|
364
|
+
}
|
|
365
|
+
return text;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/validate.ts
|
|
369
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
370
|
+
var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
371
|
+
var COLOR = /^#[0-9a-fA-F]{6}$/;
|
|
372
|
+
var BANDS = /* @__PURE__ */ new Set(["reportHeader", "pageHeader", "table", "reportFooter", "pageFooter"]);
|
|
373
|
+
var ALIGNS = /* @__PURE__ */ new Set(["start", "center", "end"]);
|
|
374
|
+
var Checker = class {
|
|
375
|
+
issues = [];
|
|
376
|
+
add(path, message) {
|
|
377
|
+
this.issues.push({ path, message });
|
|
378
|
+
}
|
|
379
|
+
/** Runs a check that throws TemplateError (expressions, conditions, formats) and records it at path. */
|
|
380
|
+
guard(path, check) {
|
|
381
|
+
try {
|
|
382
|
+
check();
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (error instanceof TemplateError) this.add(path, error.issues.map((i) => i.message).join("; "));
|
|
385
|
+
else throw error;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
number(value, path, options = {}) {
|
|
389
|
+
if (value === void 0 && options.optional) return;
|
|
390
|
+
if (!isNumber(value)) this.add(path, "must be a number");
|
|
391
|
+
else if (options.positive && value <= 0) this.add(path, "must be greater than 0");
|
|
392
|
+
else if (options.min !== void 0 && value < options.min) this.add(path, `must be at least ${options.min}`);
|
|
393
|
+
}
|
|
394
|
+
color(value, path) {
|
|
395
|
+
if (value !== void 0 && !(typeof value === "string" && COLOR.test(value))) this.add(path, "must be a color #rrggbb");
|
|
396
|
+
}
|
|
397
|
+
oneOf(value, allowed, path, optional = false) {
|
|
398
|
+
if (value === void 0 && optional) return;
|
|
399
|
+
if (!allowed.has(value)) this.add(path, `must be one of ${[...allowed].join(", ")}`);
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
function checkStyle(c, style, path) {
|
|
403
|
+
if (!isRecord(style)) {
|
|
404
|
+
c.add(path, "must be an object");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (style["font"] !== void 0 && typeof style["font"] !== "string") c.add(`${path}.font`, "must be a text");
|
|
408
|
+
c.number(style["size"], `${path}.size`, { optional: true, positive: true });
|
|
409
|
+
c.oneOf(style["weight"], /* @__PURE__ */ new Set([400, 600, 700]), `${path}.weight`, true);
|
|
410
|
+
c.color(style["color"], `${path}.color`);
|
|
411
|
+
c.color(style["background"], `${path}.background`);
|
|
412
|
+
}
|
|
413
|
+
function checkStyleRef(c, ref, styles, path) {
|
|
414
|
+
if (ref === void 0) return;
|
|
415
|
+
if (typeof ref === "string") {
|
|
416
|
+
if (!Object.prototype.hasOwnProperty.call(styles, ref)) c.add(path, `unknown style "${ref}"`);
|
|
417
|
+
} else {
|
|
418
|
+
checkStyle(c, ref, path);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function checkText(c, value, path) {
|
|
422
|
+
if (typeof value !== "string") c.add(path, "must be a text");
|
|
423
|
+
else c.guard(path, () => parseExpression(value));
|
|
424
|
+
}
|
|
425
|
+
function checkElement(c, element, styles, path) {
|
|
426
|
+
if (!isRecord(element)) {
|
|
427
|
+
c.add(path, "must be an object");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
switch (element["type"]) {
|
|
431
|
+
case "text":
|
|
432
|
+
checkText(c, element["text"], `${path}.text`);
|
|
433
|
+
c.number(element["x"], `${path}.x`);
|
|
434
|
+
c.number(element["y"], `${path}.y`);
|
|
435
|
+
c.number(element["w"], `${path}.w`, { optional: true, positive: true });
|
|
436
|
+
c.oneOf(element["align"], ALIGNS, `${path}.align`, true);
|
|
437
|
+
checkStyleRef(c, element["style"], styles, `${path}.style`);
|
|
438
|
+
return;
|
|
439
|
+
case "image":
|
|
440
|
+
checkText(c, element["src"], `${path}.src`);
|
|
441
|
+
c.number(element["x"], `${path}.x`);
|
|
442
|
+
c.number(element["y"], `${path}.y`);
|
|
443
|
+
c.number(element["w"], `${path}.w`, { positive: true });
|
|
444
|
+
c.number(element["h"], `${path}.h`, { optional: true, positive: true });
|
|
445
|
+
return;
|
|
446
|
+
case "line":
|
|
447
|
+
for (const key of ["x1", "y1", "x2", "y2"]) c.number(element[key], `${path}.${key}`);
|
|
448
|
+
c.color(element["color"], `${path}.color`);
|
|
449
|
+
c.number(element["width"], `${path}.width`, { optional: true, positive: true });
|
|
450
|
+
return;
|
|
451
|
+
case "rect":
|
|
452
|
+
c.number(element["x"], `${path}.x`);
|
|
453
|
+
c.number(element["y"], `${path}.y`);
|
|
454
|
+
c.number(element["w"], `${path}.w`, { positive: true });
|
|
455
|
+
c.number(element["h"], `${path}.h`, { positive: true });
|
|
456
|
+
c.color(element["fill"], `${path}.fill`);
|
|
457
|
+
c.color(element["stroke"], `${path}.stroke`);
|
|
458
|
+
c.number(element["strokeWidth"], `${path}.strokeWidth`, { optional: true, positive: true });
|
|
459
|
+
return;
|
|
460
|
+
default:
|
|
461
|
+
c.add(`${path}.type`, "must be text, image, line or rect");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function checkTable(c, table, styles, path) {
|
|
465
|
+
if (!isRecord(table)) {
|
|
466
|
+
c.add(path, "must be an object");
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const columns = table["columns"];
|
|
470
|
+
if (columns !== "fromGrid" && !(Array.isArray(columns) && columns.every((field) => typeof field === "string"))) {
|
|
471
|
+
c.add(`${path}.columns`, 'must be "fromGrid" or a list of fields');
|
|
472
|
+
}
|
|
473
|
+
const overrides = table["overrides"];
|
|
474
|
+
if (overrides !== void 0) {
|
|
475
|
+
if (!isRecord(overrides)) c.add(`${path}.overrides`, "must be an object");
|
|
476
|
+
else {
|
|
477
|
+
for (const [field, override] of Object.entries(overrides)) {
|
|
478
|
+
const at = `${path}.overrides.${field}`;
|
|
479
|
+
if (!isRecord(override)) {
|
|
480
|
+
c.add(at, "must be an object");
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
c.number(override["width"], `${at}.width`, { optional: true, positive: true });
|
|
484
|
+
c.oneOf(override["align"], ALIGNS, `${at}.align`, true);
|
|
485
|
+
if (override["format"] !== void 0) {
|
|
486
|
+
const format = override["format"];
|
|
487
|
+
if (typeof format !== "string") c.add(`${at}.format`, "must be a text");
|
|
488
|
+
else c.guard(`${at}.format`, () => checkFormatSpec(format));
|
|
489
|
+
}
|
|
490
|
+
if (override["caption"] !== void 0 && typeof override["caption"] !== "string") c.add(`${at}.caption`, "must be a text");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const header = table["header"];
|
|
495
|
+
if (header !== void 0) {
|
|
496
|
+
if (!isRecord(header)) c.add(`${path}.header`, "must be an object");
|
|
497
|
+
else {
|
|
498
|
+
c.color(header["background"], `${path}.header.background`);
|
|
499
|
+
if (header["repeatOnEachPage"] !== void 0 && typeof header["repeatOnEachPage"] !== "boolean") c.add(`${path}.header.repeatOnEachPage`, "must be true or false");
|
|
500
|
+
checkStyleRef(c, header["style"], styles, `${path}.header.style`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
c.color(table["zebra"], `${path}.zebra`);
|
|
504
|
+
const rowStyles = table["rowStyles"];
|
|
505
|
+
if (rowStyles !== void 0) {
|
|
506
|
+
if (!Array.isArray(rowStyles)) c.add(`${path}.rowStyles`, "must be a list");
|
|
507
|
+
else {
|
|
508
|
+
rowStyles.forEach((rule, index) => {
|
|
509
|
+
const at = `${path}.rowStyles[${index}]`;
|
|
510
|
+
if (!isRecord(rule)) {
|
|
511
|
+
c.add(at, "must be an object");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const when = rule["when"];
|
|
515
|
+
if (typeof when !== "string") c.add(`${at}.when`, "must be a text");
|
|
516
|
+
else c.guard(`${at}.when`, () => compileCondition(when));
|
|
517
|
+
checkStyleRef(c, rule["style"], styles, `${at}.style`);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (table["summary"] !== void 0 && typeof table["summary"] !== "boolean") c.add(`${path}.summary`, "must be true or false");
|
|
522
|
+
}
|
|
523
|
+
function validateTemplate(json) {
|
|
524
|
+
const c = new Checker();
|
|
525
|
+
if (!isRecord(json)) throw new TemplateError([{ path: "", message: "Template must be an object" }]);
|
|
526
|
+
if (json["version"] !== 1) c.add("version", "must be 1");
|
|
527
|
+
const page = json["page"];
|
|
528
|
+
if (!isRecord(page)) c.add("page", "must be an object");
|
|
529
|
+
else {
|
|
530
|
+
c.oneOf(page["size"], /* @__PURE__ */ new Set(["A4", "A3", "Letter"]), "page.size");
|
|
531
|
+
c.oneOf(page["orientation"], /* @__PURE__ */ new Set(["portrait", "landscape"]), "page.orientation");
|
|
532
|
+
const margins = page["margins"];
|
|
533
|
+
if (!(Array.isArray(margins) && margins.length === 4 && margins.every((m) => isNumber(m) && m >= 0))) c.add("page.margins", "must be [top, right, bottom, left] in mm");
|
|
534
|
+
c.oneOf(page["unit"], /* @__PURE__ */ new Set(["mm"]), "page.unit");
|
|
535
|
+
}
|
|
536
|
+
const fonts = json["fonts"];
|
|
537
|
+
if (!Array.isArray(fonts)) c.add("fonts", "must be a list");
|
|
538
|
+
else {
|
|
539
|
+
fonts.forEach((font, index) => {
|
|
540
|
+
const at = `fonts[${index}]`;
|
|
541
|
+
if (!isRecord(font)) return c.add(at, "must be an object");
|
|
542
|
+
if (typeof font["family"] !== "string" || font["family"] === "") c.add(`${at}.family`, "must be a name");
|
|
543
|
+
if (typeof font["src"] !== "string" || font["src"] === "") c.add(`${at}.src`, "must be a URL");
|
|
544
|
+
c.oneOf(font["weight"], /* @__PURE__ */ new Set([400, 700]), `${at}.weight`, true);
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
const styles = isRecord(json["styles"]) ? json["styles"] : {};
|
|
548
|
+
if (!isRecord(json["styles"])) c.add("styles", "must be an object");
|
|
549
|
+
for (const [name, style] of Object.entries(styles)) checkStyle(c, style, `styles.${name}`);
|
|
550
|
+
const bands = json["bands"];
|
|
551
|
+
if (!isRecord(bands)) c.add("bands", "must be an object");
|
|
552
|
+
else {
|
|
553
|
+
for (const [name, band] of Object.entries(bands)) {
|
|
554
|
+
if (!BANDS.has(name)) {
|
|
555
|
+
c.add(`bands.${name}`, "unknown band");
|
|
556
|
+
} else if (name === "table") {
|
|
557
|
+
checkTable(c, band, styles, "bands.table");
|
|
558
|
+
} else if (!Array.isArray(band)) {
|
|
559
|
+
c.add(`bands.${name}`, "must be a list of elements");
|
|
560
|
+
} else {
|
|
561
|
+
band.forEach((element, index) => checkElement(c, element, styles, `bands.${name}[${index}]`));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (c.issues.length > 0) throw new TemplateError(c.issues);
|
|
566
|
+
return json;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/font-metrics.ts
|
|
570
|
+
var MM_PER_PT = 25.4 / 72;
|
|
571
|
+
var invalid = (reason) => fail(`Invalid font file: ${reason}`, "font");
|
|
572
|
+
function parseFontMetrics(data) {
|
|
573
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
574
|
+
const u16 = (at) => {
|
|
575
|
+
if (at < 0 || at + 2 > data.byteLength) invalid("truncated");
|
|
576
|
+
return view.getUint16(at);
|
|
577
|
+
};
|
|
578
|
+
const i16 = (at) => {
|
|
579
|
+
if (at < 0 || at + 2 > data.byteLength) invalid("truncated");
|
|
580
|
+
return view.getInt16(at);
|
|
581
|
+
};
|
|
582
|
+
const u32 = (at) => {
|
|
583
|
+
if (at < 0 || at + 4 > data.byteLength) invalid("truncated");
|
|
584
|
+
return view.getUint32(at);
|
|
585
|
+
};
|
|
586
|
+
const tables = /* @__PURE__ */ new Map();
|
|
587
|
+
const count = u16(4);
|
|
588
|
+
for (let i = 0; i < count; i++) {
|
|
589
|
+
const at = 12 + i * 16;
|
|
590
|
+
const tag = String.fromCharCode(data[at], data[at + 1], data[at + 2], data[at + 3]);
|
|
591
|
+
tables.set(tag, { offset: u32(at + 8), length: u32(at + 12) });
|
|
592
|
+
}
|
|
593
|
+
const table = (tag) => {
|
|
594
|
+
const found = tables.get(tag);
|
|
595
|
+
if (!found || found.offset + found.length > data.byteLength) invalid(`missing or truncated "${tag}" table`);
|
|
596
|
+
return found;
|
|
597
|
+
};
|
|
598
|
+
const head = table("head");
|
|
599
|
+
const unitsPerEm = u16(head.offset + 18);
|
|
600
|
+
if (unitsPerEm === 0) invalid("unitsPerEm is 0");
|
|
601
|
+
const hhea = table("hhea");
|
|
602
|
+
const ascender = i16(hhea.offset + 4);
|
|
603
|
+
const descender = i16(hhea.offset + 6);
|
|
604
|
+
const numberOfHMetrics = u16(hhea.offset + 34);
|
|
605
|
+
const hmtx = table("hmtx");
|
|
606
|
+
if (numberOfHMetrics === 0 || numberOfHMetrics * 4 > hmtx.length) invalid("hmtx does not match hhea");
|
|
607
|
+
const cmap = table("cmap");
|
|
608
|
+
let format12 = null;
|
|
609
|
+
let format4 = null;
|
|
610
|
+
const subtables = u16(cmap.offset + 2);
|
|
611
|
+
for (let i = 0; i < subtables; i++) {
|
|
612
|
+
const at = cmap.offset + 4 + i * 8;
|
|
613
|
+
const platform = u16(at);
|
|
614
|
+
const encoding = u16(at + 2);
|
|
615
|
+
const offset = cmap.offset + u32(at + 4);
|
|
616
|
+
const format = u16(offset);
|
|
617
|
+
const unicode = platform === 0 || platform === 3 && (encoding === 1 || encoding === 10);
|
|
618
|
+
if (!unicode) continue;
|
|
619
|
+
if (format === 12 && format12 === null) format12 = offset;
|
|
620
|
+
if (format === 4 && format4 === null) format4 = offset;
|
|
621
|
+
}
|
|
622
|
+
if (format12 === null && format4 === null) invalid("no Unicode cmap");
|
|
623
|
+
const lookup12 = (at, codePoint) => {
|
|
624
|
+
const groups = u32(at + 12);
|
|
625
|
+
let low = 0;
|
|
626
|
+
let high = groups - 1;
|
|
627
|
+
while (low <= high) {
|
|
628
|
+
const mid = low + high >> 1;
|
|
629
|
+
const group2 = at + 16 + mid * 12;
|
|
630
|
+
const start = u32(group2);
|
|
631
|
+
const end = u32(group2 + 4);
|
|
632
|
+
if (codePoint < start) high = mid - 1;
|
|
633
|
+
else if (codePoint > end) low = mid + 1;
|
|
634
|
+
else return u32(group2 + 8) + (codePoint - start);
|
|
635
|
+
}
|
|
636
|
+
return 0;
|
|
637
|
+
};
|
|
638
|
+
const lookup4 = (at, codePoint) => {
|
|
639
|
+
if (codePoint > 65535) return 0;
|
|
640
|
+
const segments = u16(at + 6) / 2;
|
|
641
|
+
const ends = at + 14;
|
|
642
|
+
const starts = ends + segments * 2 + 2;
|
|
643
|
+
const deltas = starts + segments * 2;
|
|
644
|
+
const ranges = deltas + segments * 2;
|
|
645
|
+
for (let i = 0; i < segments; i++) {
|
|
646
|
+
if (codePoint > u16(ends + i * 2)) continue;
|
|
647
|
+
const start = u16(starts + i * 2);
|
|
648
|
+
if (codePoint < start) return 0;
|
|
649
|
+
const delta = i16(deltas + i * 2);
|
|
650
|
+
const rangeOffset = u16(ranges + i * 2);
|
|
651
|
+
if (rangeOffset === 0) return codePoint + delta & 65535;
|
|
652
|
+
const glyph = u16(ranges + i * 2 + rangeOffset + (codePoint - start) * 2);
|
|
653
|
+
return glyph === 0 ? 0 : glyph + delta & 65535;
|
|
654
|
+
}
|
|
655
|
+
return 0;
|
|
656
|
+
};
|
|
657
|
+
const advances = /* @__PURE__ */ new Map();
|
|
658
|
+
return {
|
|
659
|
+
unitsPerEm,
|
|
660
|
+
ascender,
|
|
661
|
+
descender,
|
|
662
|
+
glyphId: (codePoint) => format12 !== null ? lookup12(format12, codePoint) : lookup4(format4, codePoint),
|
|
663
|
+
advance(glyphId) {
|
|
664
|
+
const index = Math.min(glyphId, numberOfHMetrics - 1);
|
|
665
|
+
let advance = advances.get(index);
|
|
666
|
+
if (advance === void 0) {
|
|
667
|
+
advance = u16(hmtx.offset + index * 4);
|
|
668
|
+
advances.set(index, advance);
|
|
669
|
+
}
|
|
670
|
+
return advance;
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
function measureText(text, metrics, sizePt) {
|
|
675
|
+
let units = 0;
|
|
676
|
+
const missing = [];
|
|
677
|
+
for (const char of text) {
|
|
678
|
+
const codePoint = char.codePointAt(0);
|
|
679
|
+
const glyph = metrics.glyphId(codePoint);
|
|
680
|
+
if (glyph === 0 && !missing.includes(codePoint)) missing.push(codePoint);
|
|
681
|
+
units += metrics.advance(glyph);
|
|
682
|
+
}
|
|
683
|
+
return { widthMm: units / metrics.unitsPerEm * sizePt * MM_PER_PT, missing };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/layout.ts
|
|
687
|
+
var PAGE_SIZES = { A4: [210, 297], A3: [297, 420], Letter: [215.9, 279.4] };
|
|
688
|
+
var DEFAULT_STYLE = { font: "Noto Sans", size: 8, weight: 400, color: "#1b1f24" };
|
|
689
|
+
var LINE_HEIGHT = 1.2;
|
|
690
|
+
var CELL_PADDING_X = 1.5;
|
|
691
|
+
var CELL_PADDING_Y = 1;
|
|
692
|
+
var MIN_AUTO_COLUMN = 20;
|
|
693
|
+
var HEADER_RULE_COLOR = "#d5d9df";
|
|
694
|
+
var HEADER_RULE_WIDTH = 0.3;
|
|
695
|
+
var DEFAULT_LINE_WIDTH = 0.2;
|
|
696
|
+
var EPSILON = 1e-9;
|
|
697
|
+
function round2(value) {
|
|
698
|
+
const scaled = Number((Math.abs(value) * 100).toPrecision(15));
|
|
699
|
+
const rounded = Math.floor(scaled + 0.5) / 100;
|
|
700
|
+
return value < 0 && rounded !== 0 ? -rounded : rounded;
|
|
701
|
+
}
|
|
702
|
+
var lineHeight = (size) => size * LINE_HEIGHT * MM_PER_PT;
|
|
703
|
+
function wrapText(text, widthMm, metrics, sizePt) {
|
|
704
|
+
const width = (value) => measureText(value, metrics, sizePt).widthMm;
|
|
705
|
+
const lines = [];
|
|
706
|
+
for (const paragraph of text.split("\n")) {
|
|
707
|
+
let line = "";
|
|
708
|
+
let started = false;
|
|
709
|
+
for (const word of paragraph.split(" ")) {
|
|
710
|
+
const candidate = started ? `${line} ${word}` : word;
|
|
711
|
+
if (width(candidate) <= widthMm + EPSILON) {
|
|
712
|
+
line = candidate;
|
|
713
|
+
started = true;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (started) lines.push(line);
|
|
717
|
+
if (width(word) <= widthMm + EPSILON) {
|
|
718
|
+
line = word;
|
|
719
|
+
started = true;
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
let chunk = "";
|
|
723
|
+
for (const char of word) {
|
|
724
|
+
if (chunk !== "" && width(chunk + char) > widthMm + EPSILON) {
|
|
725
|
+
lines.push(chunk);
|
|
726
|
+
chunk = "";
|
|
727
|
+
}
|
|
728
|
+
chunk += char;
|
|
729
|
+
}
|
|
730
|
+
line = chunk;
|
|
731
|
+
started = true;
|
|
732
|
+
}
|
|
733
|
+
lines.push(line);
|
|
734
|
+
}
|
|
735
|
+
return lines;
|
|
736
|
+
}
|
|
737
|
+
var ROWS_PER_STEP = 500;
|
|
738
|
+
function layoutReport(template, input, fonts) {
|
|
739
|
+
const steps = layoutSteps(template, input, fonts);
|
|
740
|
+
for (; ; ) {
|
|
741
|
+
const step = steps.next();
|
|
742
|
+
if (step.done) return step.value;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
async function layoutReportAsync(template, input, fonts) {
|
|
746
|
+
const steps = layoutSteps(template, input, fonts);
|
|
747
|
+
for (; ; ) {
|
|
748
|
+
const step = steps.next();
|
|
749
|
+
if (step.done) return step.value;
|
|
750
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function* layoutSteps(template, input, fonts) {
|
|
754
|
+
const warnings = [];
|
|
755
|
+
const warn = (message) => {
|
|
756
|
+
if (!warnings.includes(message)) warnings.push(message);
|
|
757
|
+
};
|
|
758
|
+
const [baseWidth, baseHeight] = PAGE_SIZES[template.page.size] ?? fail(`Unknown page size "${template.page.size}"`, "page.size");
|
|
759
|
+
const landscape = template.page.orientation === "landscape";
|
|
760
|
+
const pageWidth = landscape ? baseHeight : baseWidth;
|
|
761
|
+
const pageHeight = landscape ? baseWidth : baseHeight;
|
|
762
|
+
const [marginTop, marginRight, marginBottom, marginLeft] = template.page.margins;
|
|
763
|
+
const contentLeft = marginLeft;
|
|
764
|
+
const contentWidth = pageWidth - marginLeft - marginRight;
|
|
765
|
+
const contentBottom = pageHeight - marginBottom;
|
|
766
|
+
const base = template.styles["base"] ?? {};
|
|
767
|
+
const resolve = (...refs) => {
|
|
768
|
+
let style = { ...DEFAULT_STYLE, ...base };
|
|
769
|
+
for (const ref of refs) {
|
|
770
|
+
if (ref === void 0) continue;
|
|
771
|
+
style = { ...style, ...typeof ref === "string" ? template.styles[ref] ?? {} : ref };
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
font: style.font ?? DEFAULT_STYLE.font,
|
|
775
|
+
size: style.size ?? DEFAULT_STYLE.size,
|
|
776
|
+
weight: (style.weight ?? 400) >= 600 ? 700 : 400,
|
|
777
|
+
color: style.color ?? DEFAULT_STYLE.color,
|
|
778
|
+
background: style.background ?? null
|
|
779
|
+
};
|
|
780
|
+
};
|
|
781
|
+
const metricsOf = (style) => {
|
|
782
|
+
const exact = fonts.get(`${style.font}|${style.weight}`);
|
|
783
|
+
if (exact) return exact;
|
|
784
|
+
const regular = fonts.get(`${style.font}|400`);
|
|
785
|
+
if (!regular) fail(`Font "${style.font}" is not available`, "font");
|
|
786
|
+
warn(`Font "${style.font}" has no bold variant; regular is used`);
|
|
787
|
+
return regular;
|
|
788
|
+
};
|
|
789
|
+
const checkGlyphs = (text, style, metrics) => {
|
|
790
|
+
for (const codePoint of measureText(text, metrics, style.size).missing) {
|
|
791
|
+
if (codePoint === 32 || codePoint === 160) continue;
|
|
792
|
+
warn(`Missing glyph U+${codePoint.toString(16).toUpperCase().padStart(4, "0")} in ${style.font}`);
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
const parsed = /* @__PURE__ */ new Map();
|
|
796
|
+
const evaluate = (text, scope) => {
|
|
797
|
+
let parts = parsed.get(text);
|
|
798
|
+
if (!parts) {
|
|
799
|
+
parts = parseExpression(text);
|
|
800
|
+
parsed.set(text, parts);
|
|
801
|
+
}
|
|
802
|
+
for (const part of parts) {
|
|
803
|
+
if (part.kind === "expr" && part.path[0] === "summary" && lookup(scope, part.path) === void 0) warn(`Summary "${part.path.join(".")}" is not in the data`);
|
|
804
|
+
}
|
|
805
|
+
return evaluateText(parts, scope, input.locale, input.timeZone);
|
|
806
|
+
};
|
|
807
|
+
const textItems = (out, lines, left, top, width, align, style, metrics) => {
|
|
808
|
+
const height = lineHeight(style.size);
|
|
809
|
+
const ascent = metrics.ascender / metrics.unitsPerEm * style.size * MM_PER_PT;
|
|
810
|
+
lines.forEach((line, index) => {
|
|
811
|
+
if (line === "") return;
|
|
812
|
+
checkGlyphs(line, style, metrics);
|
|
813
|
+
const lineWidth = measureText(line, metrics, style.size).widthMm;
|
|
814
|
+
const x = align === "center" ? left + (width - lineWidth) / 2 : align === "end" ? left + width - lineWidth : left;
|
|
815
|
+
const y = top + index * height;
|
|
816
|
+
out.push({ kind: "text", x: round2(x), y: round2(y), baseline: round2(y + ascent), text: line, font: style.font, weight: style.weight, size: style.size, color: style.color });
|
|
817
|
+
});
|
|
818
|
+
};
|
|
819
|
+
const drawBand = (elements, top, scope, out) => {
|
|
820
|
+
let height = 0;
|
|
821
|
+
for (const element of elements ?? []) {
|
|
822
|
+
switch (element.type) {
|
|
823
|
+
case "text": {
|
|
824
|
+
const style = resolve(element.style);
|
|
825
|
+
const metrics = metricsOf(style);
|
|
826
|
+
const width = Math.max(element.w ?? contentWidth - element.x, 1);
|
|
827
|
+
const lines = wrapText(evaluate(element.text, scope), width, metrics, style.size);
|
|
828
|
+
const blockHeight = lines.length * lineHeight(style.size);
|
|
829
|
+
if (out) {
|
|
830
|
+
if (style.background) {
|
|
831
|
+
out.push({ kind: "rect", x: round2(contentLeft + element.x), y: round2(top + element.y), w: round2(width), h: round2(blockHeight), fill: style.background, stroke: null, strokeWidth: 0 });
|
|
832
|
+
}
|
|
833
|
+
textItems(out, lines, contentLeft + element.x, top + element.y, width, element.align ?? "start", style, metrics);
|
|
834
|
+
}
|
|
835
|
+
height = Math.max(height, element.y + blockHeight);
|
|
836
|
+
break;
|
|
837
|
+
}
|
|
838
|
+
case "image": {
|
|
839
|
+
const src = evaluate(element.src, scope);
|
|
840
|
+
const size = input.imageSizes?.[src];
|
|
841
|
+
const imageHeight = element.h ?? (size && size.width > 0 ? element.w * size.height / size.width : element.w);
|
|
842
|
+
if (out && src !== "") out.push({ kind: "image", x: round2(contentLeft + element.x), y: round2(top + element.y), w: round2(element.w), h: round2(imageHeight), src });
|
|
843
|
+
height = Math.max(height, element.y + imageHeight);
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
case "line":
|
|
847
|
+
if (out) {
|
|
848
|
+
out.push({
|
|
849
|
+
kind: "line",
|
|
850
|
+
x1: round2(contentLeft + element.x1),
|
|
851
|
+
y1: round2(top + element.y1),
|
|
852
|
+
x2: round2(contentLeft + element.x2),
|
|
853
|
+
y2: round2(top + element.y2),
|
|
854
|
+
color: element.color ?? resolve().color,
|
|
855
|
+
width: element.width ?? DEFAULT_LINE_WIDTH
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
height = Math.max(height, element.y1, element.y2);
|
|
859
|
+
break;
|
|
860
|
+
case "rect":
|
|
861
|
+
if (out) {
|
|
862
|
+
out.push({
|
|
863
|
+
kind: "rect",
|
|
864
|
+
x: round2(contentLeft + element.x),
|
|
865
|
+
y: round2(top + element.y),
|
|
866
|
+
w: round2(element.w),
|
|
867
|
+
h: round2(element.h),
|
|
868
|
+
fill: element.fill ?? null,
|
|
869
|
+
stroke: element.stroke ?? null,
|
|
870
|
+
strokeWidth: element.strokeWidth ?? DEFAULT_LINE_WIDTH
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
height = Math.max(height, element.y + element.h);
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return height;
|
|
878
|
+
};
|
|
879
|
+
const measureScope = { ...input.context, page: 1, pages: 1 };
|
|
880
|
+
const pageHeaderHeight = drawBand(template.bands.pageHeader, 0, measureScope, null);
|
|
881
|
+
const pageFooterHeight = drawBand(template.bands.pageFooter, 0, measureScope, null);
|
|
882
|
+
const bodyTop = marginTop + pageHeaderHeight;
|
|
883
|
+
const bodyBottom = contentBottom - pageFooterHeight;
|
|
884
|
+
const bodies = [{ items: [] }];
|
|
885
|
+
const deferred = [];
|
|
886
|
+
let cursor = bodyTop;
|
|
887
|
+
const newPage = () => {
|
|
888
|
+
bodies.push({ items: [] });
|
|
889
|
+
cursor = bodyTop;
|
|
890
|
+
};
|
|
891
|
+
const current = () => bodies[bodies.length - 1].items;
|
|
892
|
+
if (template.bands.reportHeader) {
|
|
893
|
+
const height = drawBand(template.bands.reportHeader, cursor, measureScope, null);
|
|
894
|
+
deferred.push({ page: 0, top: cursor, elements: template.bands.reportHeader });
|
|
895
|
+
cursor += height;
|
|
896
|
+
}
|
|
897
|
+
if (template.bands.table) yield* layoutTable(template.bands.table);
|
|
898
|
+
if (template.bands.reportFooter) {
|
|
899
|
+
const height = drawBand(template.bands.reportFooter, 0, measureScope, null);
|
|
900
|
+
if (cursor + height > bodyBottom + EPSILON && cursor > bodyTop + EPSILON) newPage();
|
|
901
|
+
deferred.push({ page: bodies.length - 1, top: cursor, elements: template.bands.reportFooter });
|
|
902
|
+
cursor += height;
|
|
903
|
+
}
|
|
904
|
+
const pages = bodies.length;
|
|
905
|
+
const result = bodies.map((body, index) => {
|
|
906
|
+
const scope = { ...input.context, page: index + 1, pages };
|
|
907
|
+
const items = [];
|
|
908
|
+
drawBand(template.bands.pageHeader, marginTop, scope, items);
|
|
909
|
+
for (const band of deferred.filter((d) => d.page === index && d.elements === template.bands.reportHeader)) drawBand(band.elements, band.top, scope, items);
|
|
910
|
+
items.push(...body.items);
|
|
911
|
+
for (const band of deferred.filter((d) => d.page === index && d.elements === template.bands.reportFooter)) drawBand(band.elements, band.top, scope, items);
|
|
912
|
+
drawBand(template.bands.pageFooter, contentBottom - pageFooterHeight, scope, items);
|
|
913
|
+
return { items };
|
|
914
|
+
});
|
|
915
|
+
return { page: { width: pageWidth, height: pageHeight }, pages: result, fonts: template.fonts, warnings };
|
|
916
|
+
function* layoutTable(table) {
|
|
917
|
+
const fields = table.columns === "fromGrid" ? input.columns.map((c) => c.field) : table.columns;
|
|
918
|
+
const columns = [];
|
|
919
|
+
for (const field of fields) {
|
|
920
|
+
const column = input.columns.find((c) => c.field === field);
|
|
921
|
+
if (column) columns.push(column);
|
|
922
|
+
else warn(`Column "${field}" is not in the data`);
|
|
923
|
+
}
|
|
924
|
+
if (columns.length === 0) return;
|
|
925
|
+
const explicit = columns.map((c) => table.overrides?.[c.field]?.width ?? null);
|
|
926
|
+
const preferred = columns.map((c, i) => explicit[i] !== null ? null : c.widthMm);
|
|
927
|
+
const fixedSum = explicit.reduce((sum, w) => sum + (w ?? 0), 0);
|
|
928
|
+
const gridSum = preferred.reduce((sum, w) => sum + (w ?? 0), 0);
|
|
929
|
+
const autoCount = columns.filter((_, i) => explicit[i] === null && preferred[i] === null).length;
|
|
930
|
+
const autoWidth = autoCount > 0 ? Math.max((contentWidth - fixedSum - gridSum) / autoCount, MIN_AUTO_COLUMN) : 0;
|
|
931
|
+
let widths = columns.map((_, i) => explicit[i] ?? preferred[i] ?? autoWidth);
|
|
932
|
+
const total = widths.reduce((sum, w) => sum + w, 0);
|
|
933
|
+
if (total > contentWidth + EPSILON) {
|
|
934
|
+
const flexible = total - fixedSum;
|
|
935
|
+
const room = contentWidth - fixedSum;
|
|
936
|
+
widths = room > EPSILON && flexible > EPSILON ? widths.map((w, i) => explicit[i] !== null ? w : w * room / flexible) : widths.map((w) => w * contentWidth / total);
|
|
937
|
+
}
|
|
938
|
+
const lefts = [];
|
|
939
|
+
widths.reduce((left, width) => {
|
|
940
|
+
lefts.push(left);
|
|
941
|
+
return left + width;
|
|
942
|
+
}, contentLeft);
|
|
943
|
+
const tableWidth = widths.reduce((sum, w) => sum + w, 0);
|
|
944
|
+
const aligns = columns.map((c) => table.overrides?.[c.field]?.align ?? c.align);
|
|
945
|
+
const headerStyle = resolve({ weight: 700 }, table.header?.style);
|
|
946
|
+
const headerTexts = columns.map((c) => table.overrides?.[c.field]?.caption ?? c.caption);
|
|
947
|
+
const rowStyles = (table.rowStyles ?? []).map((rule) => ({ test: compileCondition(rule.when), style: rule.style }));
|
|
948
|
+
const measureRow = (cells, style) => {
|
|
949
|
+
const metrics = metricsOf(style);
|
|
950
|
+
const lines = cells.map((text, i) => wrapText(text, Math.max(widths[i] - 2 * CELL_PADDING_X, 1), metrics, style.size));
|
|
951
|
+
const height = Math.max(...lines.map((l) => l.length)) * lineHeight(style.size) + 2 * CELL_PADDING_Y;
|
|
952
|
+
return { lines, height, style, metrics };
|
|
953
|
+
};
|
|
954
|
+
const drawRow = (row, top, background, maxHeight) => {
|
|
955
|
+
const out = current();
|
|
956
|
+
const height = Math.min(row.height, maxHeight);
|
|
957
|
+
if (background) out.push({ kind: "rect", x: round2(contentLeft), y: round2(top), w: round2(tableWidth), h: round2(height), fill: background, stroke: null, strokeWidth: 0 });
|
|
958
|
+
const fit = Math.floor((height - 2 * CELL_PADDING_Y) / lineHeight(row.style.size) + EPSILON);
|
|
959
|
+
row.lines.forEach((lines, i) => {
|
|
960
|
+
textItems(out, lines.slice(0, Math.max(fit, 0)), lefts[i] + CELL_PADDING_X, top + CELL_PADDING_Y, widths[i] - 2 * CELL_PADDING_X, aligns[i], row.style, row.metrics);
|
|
961
|
+
});
|
|
962
|
+
};
|
|
963
|
+
const header = measureRow(headerTexts, headerStyle);
|
|
964
|
+
const drawHeader = () => {
|
|
965
|
+
drawRow(header, cursor, table.header?.background ?? null, header.height);
|
|
966
|
+
current().push({ kind: "line", x1: round2(contentLeft), y1: round2(cursor + header.height), x2: round2(contentLeft + tableWidth), y2: round2(cursor + header.height), color: HEADER_RULE_COLOR, width: HEADER_RULE_WIDTH });
|
|
967
|
+
cursor += header.height;
|
|
968
|
+
};
|
|
969
|
+
const repeatHeader = table.header?.repeatOnEachPage ?? true;
|
|
970
|
+
for (let index = 0; index < input.rows.length; index++) {
|
|
971
|
+
if (index > 0 && index % ROWS_PER_STEP === 0) yield;
|
|
972
|
+
const data = input.rows[index];
|
|
973
|
+
const texts = input.texts?.[index];
|
|
974
|
+
const cells = columns.map((column) => {
|
|
975
|
+
const value = data[column.field];
|
|
976
|
+
const format = table.overrides?.[column.field]?.format ?? column.format;
|
|
977
|
+
if (format) return formatValue(value, format, input.locale, input.timeZone);
|
|
978
|
+
const text = texts?.[column.field];
|
|
979
|
+
return text !== void 0 ? text : defaultText(value, input.locale);
|
|
980
|
+
});
|
|
981
|
+
const matched = rowStyles.filter((rule) => rule.test(data)).map((rule) => rule.style);
|
|
982
|
+
const style = resolve(...matched);
|
|
983
|
+
const row = measureRow(cells, style);
|
|
984
|
+
const zebra = index % 2 === 1 ? table.zebra ?? null : null;
|
|
985
|
+
if (index === 0) {
|
|
986
|
+
if (cursor + header.height + row.height > bodyBottom + EPSILON && cursor > bodyTop + EPSILON) newPage();
|
|
987
|
+
drawHeader();
|
|
988
|
+
} else if (cursor + row.height > bodyBottom + EPSILON) {
|
|
989
|
+
newPage();
|
|
990
|
+
if (repeatHeader) drawHeader();
|
|
991
|
+
}
|
|
992
|
+
const room = bodyBottom - cursor;
|
|
993
|
+
if (row.height > room + EPSILON) warn(`Row ${index + 1} is taller than a page and was cut`);
|
|
994
|
+
if (zebra) current().push({ kind: "rect", x: round2(contentLeft), y: round2(cursor), w: round2(tableWidth), h: round2(Math.min(row.height, room)), fill: zebra, stroke: null, strokeWidth: 0 });
|
|
995
|
+
drawRow(row, cursor, style.background, room);
|
|
996
|
+
cursor += Math.min(row.height, room);
|
|
997
|
+
}
|
|
998
|
+
if (input.rows.length === 0) drawHeader();
|
|
999
|
+
if (table.summary === true && input.summaryRow) {
|
|
1000
|
+
const summaryRow = input.summaryRow;
|
|
1001
|
+
const row = measureRow(columns.map((column) => summaryRow[column.field] ?? ""), resolve({ weight: 700 }));
|
|
1002
|
+
if (cursor + row.height > bodyBottom + EPSILON) {
|
|
1003
|
+
newPage();
|
|
1004
|
+
if (repeatHeader) drawHeader();
|
|
1005
|
+
}
|
|
1006
|
+
const room = bodyBottom - cursor;
|
|
1007
|
+
current().push({ kind: "line", x1: round2(contentLeft), y1: round2(cursor), x2: round2(contentLeft + tableWidth), y2: round2(cursor), color: HEADER_RULE_COLOR, width: HEADER_RULE_WIDTH });
|
|
1008
|
+
drawRow(row, cursor, null, room);
|
|
1009
|
+
cursor += Math.min(row.height, room);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/store.ts
|
|
1015
|
+
var TemplateStoreError = class extends Error {
|
|
1016
|
+
status;
|
|
1017
|
+
issues;
|
|
1018
|
+
constructor(message, status, issues = []) {
|
|
1019
|
+
super(message);
|
|
1020
|
+
this.name = "TemplateStoreError";
|
|
1021
|
+
this.status = status;
|
|
1022
|
+
this.issues = issues;
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
var DEFAULT_KEY = "rastr.pdf-templates";
|
|
1026
|
+
function summary(stored) {
|
|
1027
|
+
return { id: stored.id, gridId: stored.gridId, name: stored.name, isDefault: stored.isDefault, shared: stored.shared, updatedAtUtc: stored.updatedAtUtc };
|
|
1028
|
+
}
|
|
1029
|
+
function createLocalTemplateStore(options = {}) {
|
|
1030
|
+
const key = options.key ?? DEFAULT_KEY;
|
|
1031
|
+
const storage = () => options.storage ?? globalThis.localStorage;
|
|
1032
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1033
|
+
const newId = options.newId ?? (() => crypto.randomUUID());
|
|
1034
|
+
const read = () => {
|
|
1035
|
+
try {
|
|
1036
|
+
const parsed = JSON.parse(storage().getItem(key) ?? "[]");
|
|
1037
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1038
|
+
} catch {
|
|
1039
|
+
return [];
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
const write = (templates) => storage().setItem(key, JSON.stringify(templates));
|
|
1043
|
+
return {
|
|
1044
|
+
list: async (gridId) => read().filter((t) => t.gridId === gridId).map(summary),
|
|
1045
|
+
async load(id) {
|
|
1046
|
+
const found = read().find((t) => t.id === id);
|
|
1047
|
+
if (!found) throw new TemplateStoreError(`Template ${id} does not exist`, 404);
|
|
1048
|
+
return found;
|
|
1049
|
+
},
|
|
1050
|
+
async save(input) {
|
|
1051
|
+
const templates = read();
|
|
1052
|
+
const id = input.id ?? newId();
|
|
1053
|
+
const stored = {
|
|
1054
|
+
id,
|
|
1055
|
+
gridId: input.gridId,
|
|
1056
|
+
name: input.name,
|
|
1057
|
+
isDefault: input.isDefault,
|
|
1058
|
+
shared: input.shared,
|
|
1059
|
+
updatedAtUtc: now().toISOString(),
|
|
1060
|
+
template: input.template
|
|
1061
|
+
};
|
|
1062
|
+
const index = templates.findIndex((t) => t.id === id);
|
|
1063
|
+
if (index >= 0) templates[index] = stored;
|
|
1064
|
+
else templates.push(stored);
|
|
1065
|
+
const result = input.isDefault ? templates.map((t) => t.gridId === input.gridId && t.id !== id && t.isDefault ? { ...t, isDefault: false } : t) : templates;
|
|
1066
|
+
write(result);
|
|
1067
|
+
return summary(stored);
|
|
1068
|
+
},
|
|
1069
|
+
async remove(id) {
|
|
1070
|
+
write(read().filter((t) => t.id !== id));
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function isRecord2(value) {
|
|
1075
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1076
|
+
}
|
|
1077
|
+
function createRestTemplateStore(options) {
|
|
1078
|
+
const base = options.url.replace(/\/+$/, "");
|
|
1079
|
+
const request = async (method, path, body2) => {
|
|
1080
|
+
const headers = new Headers(options.headers?.());
|
|
1081
|
+
if (body2 !== void 0) headers.set("Content-Type", "application/json");
|
|
1082
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1083
|
+
const response = await fetchImpl(`${base}${path}`, {
|
|
1084
|
+
method,
|
|
1085
|
+
headers,
|
|
1086
|
+
...body2 !== void 0 ? { body: JSON.stringify(body2) } : {}
|
|
1087
|
+
});
|
|
1088
|
+
if (response.ok) return response;
|
|
1089
|
+
let problem = null;
|
|
1090
|
+
try {
|
|
1091
|
+
problem = await response.json();
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
const issues = isRecord2(problem) && Array.isArray(problem["issues"]) ? problem["issues"] : [];
|
|
1095
|
+
const detail = isRecord2(problem) ? problem["detail"] ?? problem["title"] : null;
|
|
1096
|
+
throw new TemplateStoreError(typeof detail === "string" ? detail : `Template request failed (HTTP ${response.status})`, response.status, issues);
|
|
1097
|
+
};
|
|
1098
|
+
const body = (input) => ({ gridId: input.gridId, name: input.name, isDefault: input.isDefault, shared: input.shared, template: input.template });
|
|
1099
|
+
return {
|
|
1100
|
+
list: async (gridId) => (await request("GET", `?gridId=${encodeURIComponent(gridId)}`)).json(),
|
|
1101
|
+
load: async (id) => (await request("GET", `/${encodeURIComponent(id)}`)).json(),
|
|
1102
|
+
save: async (input) => (input.id === void 0 ? await request("POST", "", body(input)) : await request("PUT", `/${encodeURIComponent(input.id)}`, body(input))).json(),
|
|
1103
|
+
async remove(id) {
|
|
1104
|
+
await request("DELETE", `/${encodeURIComponent(id)}`);
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
function templateLoader(store, gridId) {
|
|
1109
|
+
return async () => {
|
|
1110
|
+
const found = (await store.list(gridId)).find((t) => t.isDefault);
|
|
1111
|
+
return found ? (await store.load(found.id)).template : null;
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1115
|
+
0 && (module.exports = {
|
|
1116
|
+
MM_PER_PT,
|
|
1117
|
+
TemplateError,
|
|
1118
|
+
TemplateStoreError,
|
|
1119
|
+
compileCondition,
|
|
1120
|
+
createLocalTemplateStore,
|
|
1121
|
+
createRestTemplateStore,
|
|
1122
|
+
defaultText,
|
|
1123
|
+
evaluateText,
|
|
1124
|
+
formatValue,
|
|
1125
|
+
layoutReport,
|
|
1126
|
+
layoutReportAsync,
|
|
1127
|
+
measureText,
|
|
1128
|
+
parseExpression,
|
|
1129
|
+
parseFontMetrics,
|
|
1130
|
+
round2,
|
|
1131
|
+
templateLoader,
|
|
1132
|
+
validateTemplate,
|
|
1133
|
+
wrapText
|
|
1134
|
+
});
|
|
1135
|
+
//# sourceMappingURL=index.cjs.map
|