@agile-team/mach-report 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/index.cjs +1663 -0
  2. package/dist/index.d.cts +344 -0
  3. package/dist/index.d.ts +344 -13
  4. package/dist/index.js +1611 -14
  5. package/dist/manager.cjs +122 -0
  6. package/dist/{manager/api.d.ts → manager.d.cts} +55 -3
  7. package/dist/manager.d.ts +97 -5
  8. package/dist/manager.js +119 -6
  9. package/dist/pdf.cjs +52530 -0
  10. package/dist/pdf.d.cts +33 -0
  11. package/dist/pdf.d.ts +31 -4
  12. package/dist/pdf.js +52527 -7
  13. package/dist/sql.cjs +45207 -0
  14. package/dist/sql.d.cts +95 -0
  15. package/dist/sql.d.ts +93 -3
  16. package/dist/sql.js +45194 -6
  17. package/dist/textwrap-DWzM7pt1.d.cts +135 -0
  18. package/dist/textwrap-DWzM7pt1.d.ts +135 -0
  19. package/package.json +44 -15
  20. package/dist/builder/template-builder.d.ts +0 -53
  21. package/dist/builder/template-builder.js +0 -153
  22. package/dist/builder/template-builder.js.map +0 -1
  23. package/dist/compat/jh4j-template.d.ts +0 -12
  24. package/dist/compat/jh4j-template.js +0 -196
  25. package/dist/compat/jh4j-template.js.map +0 -1
  26. package/dist/defaults.d.ts +0 -27
  27. package/dist/defaults.js +0 -28
  28. package/dist/defaults.js.map +0 -1
  29. package/dist/index.js.map +0 -1
  30. package/dist/layout/paginate.d.ts +0 -51
  31. package/dist/layout/paginate.js +0 -197
  32. package/dist/layout/paginate.js.map +0 -1
  33. package/dist/layout/textwrap.d.ts +0 -36
  34. package/dist/layout/textwrap.js +0 -173
  35. package/dist/layout/textwrap.js.map +0 -1
  36. package/dist/manager/api.js +0 -101
  37. package/dist/manager/api.js.map +0 -1
  38. package/dist/manager/index.d.ts +0 -2
  39. package/dist/manager/index.js +0 -3
  40. package/dist/manager/index.js.map +0 -1
  41. package/dist/manager/types.d.ts +0 -50
  42. package/dist/manager/types.js +0 -8
  43. package/dist/manager/types.js.map +0 -1
  44. package/dist/manager.js.map +0 -1
  45. package/dist/pdf/font-loader.d.ts +0 -5
  46. package/dist/pdf/font-loader.js +0 -84
  47. package/dist/pdf/font-loader.js.map +0 -1
  48. package/dist/pdf/index.d.ts +0 -2
  49. package/dist/pdf/index.js +0 -3
  50. package/dist/pdf/index.js.map +0 -1
  51. package/dist/pdf/render-pdf.d.ts +0 -19
  52. package/dist/pdf/render-pdf.js +0 -284
  53. package/dist/pdf/render-pdf.js.map +0 -1
  54. package/dist/pdf.js.map +0 -1
  55. package/dist/render/canvas.d.ts +0 -61
  56. package/dist/render/canvas.js +0 -402
  57. package/dist/render/canvas.js.map +0 -1
  58. package/dist/render/dom.d.ts +0 -10
  59. package/dist/render/dom.js +0 -311
  60. package/dist/render/dom.js.map +0 -1
  61. package/dist/render/grid-geometry.d.ts +0 -41
  62. package/dist/render/grid-geometry.js +0 -131
  63. package/dist/render/grid-geometry.js.map +0 -1
  64. package/dist/render/style.d.ts +0 -40
  65. package/dist/render/style.js +0 -39
  66. package/dist/render/style.js.map +0 -1
  67. package/dist/render/window.d.ts +0 -23
  68. package/dist/render/window.js +0 -58
  69. package/dist/render/window.js.map +0 -1
  70. package/dist/schema/render-plan.d.ts +0 -96
  71. package/dist/schema/render-plan.js +0 -25
  72. package/dist/schema/render-plan.js.map +0 -1
  73. package/dist/schema/validate.d.ts +0 -15
  74. package/dist/schema/validate.js +0 -129
  75. package/dist/schema/validate.js.map +0 -1
  76. package/dist/sql/ast-check.d.ts +0 -14
  77. package/dist/sql/ast-check.js +0 -44
  78. package/dist/sql/ast-check.js.map +0 -1
  79. package/dist/sql/compile.d.ts +0 -31
  80. package/dist/sql/compile.js +0 -145
  81. package/dist/sql/compile.js.map +0 -1
  82. package/dist/sql/expr.d.ts +0 -21
  83. package/dist/sql/expr.js +0 -123
  84. package/dist/sql/expr.js.map +0 -1
  85. package/dist/sql/index.d.ts +0 -4
  86. package/dist/sql/index.js +0 -5
  87. package/dist/sql/index.js.map +0 -1
  88. package/dist/sql/tokenizer.d.ts +0 -25
  89. package/dist/sql/tokenizer.js +0 -124
  90. package/dist/sql/tokenizer.js.map +0 -1
  91. package/dist/sql.js.map +0 -1
  92. package/dist/units.d.ts +0 -11
  93. package/dist/units.js +0 -26
  94. package/dist/units.js.map +0 -1
package/dist/index.cjs ADDED
@@ -0,0 +1,1663 @@
1
+ 'use strict';
2
+
3
+ // src/defaults.ts
4
+ var LINE_HEIGHT = 1.35;
5
+ var DEFAULT_FONT_PT = 10.5;
6
+ var DEFAULT_BORDER_PT = 0.75;
7
+ var DEFAULT_STROKE_COLOR = "#333333";
8
+ var DEFAULT_TEXT_COLOR = "#000000";
9
+ var SCHEMA_VERSION_MACH = "1.0.0-mach";
10
+ var SCHEMA_VERSION_LOCAL = "mach-local";
11
+ var SCHEMA_VERSION_JH4J = "jh4j-compatible";
12
+
13
+ // src/units.ts
14
+ var MM_PER_INCH = 25.4;
15
+ var CSS_DPI = 96;
16
+ function mmToPxFactor(dpi = CSS_DPI) {
17
+ return dpi / MM_PER_INCH;
18
+ }
19
+ function mmToPx(value, dpi = CSS_DPI) {
20
+ return (value || 0) * mmToPxFactor(dpi);
21
+ }
22
+ function pxToMm(value, dpi = CSS_DPI) {
23
+ return value / mmToPxFactor(dpi);
24
+ }
25
+ function ptToPx(pt, dpi = CSS_DPI) {
26
+ return pt * dpi / 72;
27
+ }
28
+ function round(value, digits) {
29
+ const f = 10 ** digits;
30
+ return Math.round(value * f) / f;
31
+ }
32
+ var PAPER_SIZES = {
33
+ a3: { widthMm: 297, heightMm: 420 },
34
+ a4: { widthMm: 210, heightMm: 297 },
35
+ a5: { widthMm: 148, heightMm: 210 },
36
+ b4: { widthMm: 250, heightMm: 353 },
37
+ b5: { widthMm: 176, heightMm: 250 }
38
+ };
39
+
40
+ // src/schema/render-plan.ts
41
+ var RENDER_PLAN_SCHEMA_VERSION = SCHEMA_VERSION_MACH;
42
+ function toRenderPlan(response) {
43
+ if (response && response.code != null && response.code !== 200) {
44
+ throw new Error(String(response.message || "\u6E32\u67D3\u8BA1\u5212\u52A0\u8F7D\u5931\u8D25"));
45
+ }
46
+ const pages = response?.data?.pages;
47
+ return {
48
+ schemaVersion: RENDER_PLAN_SCHEMA_VERSION,
49
+ pages: Array.isArray(pages) ? pages : []
50
+ };
51
+ }
52
+ function isImageBackedComponent(comp) {
53
+ return (comp.kind === "image" || comp.kind === "barcode" || comp.kind === "qrcode" || comp.kind === "chart") && !!comp.imageData;
54
+ }
55
+ function isRichText(comp) {
56
+ return comp.kind === "text" && Array.isArray(comp.richParagraphs) && comp.richParagraphs.length > 0;
57
+ }
58
+
59
+ // src/schema/validate.ts
60
+ var KNOWN_KINDS = /* @__PURE__ */ new Set([
61
+ "text",
62
+ "image",
63
+ "barcode",
64
+ "qrcode",
65
+ "chart",
66
+ "line",
67
+ "rect",
68
+ "ellipse"
69
+ ]);
70
+ function isObj(v) {
71
+ return typeof v === "object" && v !== null && !Array.isArray(v);
72
+ }
73
+ function checkNumber(obj, key, path, errors) {
74
+ const v = obj[key];
75
+ if (v === void 0 || v === null) return;
76
+ if (typeof v !== "number" || !Number.isFinite(v)) {
77
+ errors.push({ path: `${path}.${key}`, message: `\u5E94\u4E3A\u6709\u9650\u6570\u5B57\uFF0C\u5B9E\u9645 ${JSON.stringify(v)}` });
78
+ }
79
+ }
80
+ function validateComponent(comp, path, errors, warnings = []) {
81
+ if (!isObj(comp)) {
82
+ errors.push({ path, message: "\u7EC4\u4EF6\u5E94\u4E3A\u5BF9\u8C61" });
83
+ return;
84
+ }
85
+ const kind = comp["kind"];
86
+ if (typeof kind !== "string") {
87
+ errors.push({ path: `${path}.kind`, message: `\u7EC4\u4EF6\u7F3A\u5C11 kind \u5B57\u7B26\u4E32` });
88
+ } else if (!KNOWN_KINDS.has(kind)) {
89
+ warnings.push({
90
+ path: `${path}.kind`,
91
+ message: `\u672A\u77E5\u7EC4\u4EF6\u7C7B\u578B ${JSON.stringify(kind)}\uFF0C\u5C06\u4EE5\u515C\u5E95\u65B9\u5F0F\u6E32\u67D3`
92
+ });
93
+ }
94
+ for (const key of ["leftMm", "topMm", "widthMm", "heightMm"]) {
95
+ checkNumber(comp, key, path, errors);
96
+ }
97
+ if (comp["leftMm"] != null && comp["widthMm"] != null) {
98
+ if (comp["leftMm"] < -100 || comp["widthMm"] < 0) {
99
+ errors.push({ path, message: "\u51E0\u4F55\u6570\u503C\u5F02\u5E38\uFF08leftMm<-100 \u6216 widthMm<0\uFF09" });
100
+ }
101
+ }
102
+ const grid = comp["grid"];
103
+ if (grid !== void 0) {
104
+ if (!isObj(grid) || !Array.isArray(grid["cells"])) {
105
+ errors.push({ path: `${path}.grid.cells`, message: "grid.cells \u5E94\u4E3A\u4E8C\u7EF4\u6570\u7EC4" });
106
+ } else {
107
+ grid["cells"].forEach((row, ri) => {
108
+ if (!Array.isArray(row)) {
109
+ errors.push({ path: `${path}.grid.cells.${ri}`, message: "\u884C\u5E94\u4E3A\u6570\u7EC4" });
110
+ return;
111
+ }
112
+ row.forEach((cell, ci) => {
113
+ if (cell === void 0 || cell === null) return;
114
+ if (isObj(cell) && Array.isArray(cell["children"])) {
115
+ cell["children"].forEach((child, ki) => {
116
+ validateComponent(child, `${path}.grid.cells.${ri}.${ci}.children.${ki}`, errors, warnings);
117
+ });
118
+ }
119
+ });
120
+ });
121
+ }
122
+ }
123
+ const rich = comp["richParagraphs"];
124
+ if (rich !== void 0) {
125
+ if (!Array.isArray(rich)) {
126
+ errors.push({ path: `${path}.richParagraphs`, message: "richParagraphs \u5E94\u4E3A\u6570\u7EC4" });
127
+ } else {
128
+ rich.forEach((para, i) => {
129
+ if (!isObj(para) || !Array.isArray(para["segments"])) {
130
+ errors.push({ path: `${path}.richParagraphs.${i}`, message: "\u6BB5\u843D\u7F3A\u5C11 segments \u6570\u7EC4" });
131
+ }
132
+ });
133
+ }
134
+ }
135
+ }
136
+ function validatePage(page, path, errors, warnings = []) {
137
+ if (!isObj(page)) {
138
+ errors.push({ path, message: "\u9875\u5E94\u4E3A\u5BF9\u8C61" });
139
+ return;
140
+ }
141
+ const w = page["pageWidthMm"];
142
+ const h = page["pageHeightMm"];
143
+ if (typeof w !== "number" || !(w > 0 && w < 3e3)) {
144
+ errors.push({ path: `${path}.pageWidthMm`, message: `\u9875\u5BBD\u975E\u6CD5: ${JSON.stringify(w)}` });
145
+ }
146
+ if (typeof h !== "number" || !(h > 0 && h < 5e3)) {
147
+ errors.push({ path: `${path}.pageHeightMm`, message: `\u9875\u9AD8\u975E\u6CD5: ${JSON.stringify(h)}` });
148
+ }
149
+ const comps = page["components"];
150
+ if (comps !== void 0 && !Array.isArray(comps)) {
151
+ errors.push({ path: `${path}.components`, message: "components \u5E94\u4E3A\u6570\u7EC4" });
152
+ } else if (Array.isArray(comps)) {
153
+ comps.forEach(
154
+ (comp, i) => validateComponent(comp, `${path}.components.${i}`, errors, warnings)
155
+ );
156
+ }
157
+ }
158
+ function validateRenderPlan(input) {
159
+ const errors = [];
160
+ const warnings = [];
161
+ const pages = isObj(input) ? input["pages"] : void 0;
162
+ if (!Array.isArray(pages)) {
163
+ errors.push({
164
+ path: "$.pages",
165
+ message: input == null ? "\u6E32\u67D3\u8BA1\u5212\u4E3A\u7A7A" : "\u6E32\u67D3\u8BA1\u5212\u7F3A\u5C11 pages \u6570\u7EC4"
166
+ });
167
+ } else {
168
+ pages.forEach((page, i) => validatePage(page, `$.pages.${i}`, errors, warnings));
169
+ }
170
+ return { ok: errors.length === 0, errors, warnings };
171
+ }
172
+ function assertRenderPlan(input) {
173
+ const result = validateRenderPlan(input);
174
+ if (!result.ok) {
175
+ const head = result.errors.slice(0, 3).map((e) => `${e.path}: ${e.message}`).join("; ");
176
+ throw new Error(`\u6E32\u67D3\u8BA1\u5212\u6821\u9A8C\u5931\u8D25(${result.errors.length} \u5904): ${head}`);
177
+ }
178
+ return input.pages;
179
+ }
180
+
181
+ // src/layout/textwrap.ts
182
+ var CJK_RE = /[\u3000-\u9fff\uff00-\uffef]/;
183
+ var LATIN_CLASS = new Uint8Array(128);
184
+ {
185
+ for (const ch of "iljtf.,:;'`|!()[]") LATIN_CLASS[ch.charCodeAt(0)] = 1;
186
+ for (const ch of "mwMW@") LATIN_CLASS[ch.charCodeAt(0)] = 2;
187
+ for (let c = 48; c <= 57; c++) LATIN_CLASS[c] = 3;
188
+ for (let c = 65; c <= 90; c++) LATIN_CLASS[c] = 4;
189
+ LATIN_CLASS[32] = 5;
190
+ }
191
+ var LATIN_WIDTH = [0.54, 0.26, 0.95, 0.59, 0.67, 0.3];
192
+ function charWidthEm(ch) {
193
+ const code = ch.charCodeAt(0);
194
+ if (code < 128) return LATIN_WIDTH[LATIN_CLASS[code]];
195
+ if (CJK_RE.test(ch)) return 1;
196
+ if (/[A-Z]/.test(ch)) return 0.67;
197
+ return 0.54;
198
+ }
199
+ var heuristicMeasurer = {
200
+ measureMm(text, options) {
201
+ const { fontSizePt, bold = false, letterSpacingPt = 0 } = options;
202
+ const ptToMm = 25.4 / 72;
203
+ let units = 0;
204
+ for (const ch of text) {
205
+ units += charWidthEm(ch);
206
+ }
207
+ if (bold) units *= 1.04;
208
+ return (units * fontSizePt + letterSpacingPt * text.length) * ptToMm;
209
+ }
210
+ };
211
+ function createHeuristicMeasurer() {
212
+ return heuristicMeasurer;
213
+ }
214
+ function measureTextMm(text, options) {
215
+ return heuristicMeasurer.measureMm(text, options);
216
+ }
217
+ function createCanvasMeasurer(getCtx2d) {
218
+ let ctx;
219
+ return {
220
+ measureMm(text, options) {
221
+ if (ctx === void 0) ctx = getCtx2d();
222
+ if (!ctx) return heuristicMeasurer.measureMm(text, options);
223
+ const px = options.fontSizePt * 96 / 72;
224
+ ctx.font = `${options.bold ? "bold " : ""}${px}px sans-serif`;
225
+ return ctx.measureText(text).width * 25.4 / 96;
226
+ }
227
+ };
228
+ }
229
+ function tokenize(text, options, measurer) {
230
+ const atoms = [];
231
+ let word = "";
232
+ let wordWidth = 0;
233
+ const flush = () => {
234
+ if (word) {
235
+ atoms.push({ text: word, widthMm: wordWidth, cjk: false });
236
+ word = "";
237
+ wordWidth = 0;
238
+ }
239
+ };
240
+ for (const ch of text) {
241
+ if (ch === "\n") {
242
+ flush();
243
+ atoms.push({ text: "\n", widthMm: 0, cjk: false, br: true });
244
+ continue;
245
+ }
246
+ if (ch === " ") {
247
+ flush();
248
+ atoms.push({ text: ch, widthMm: measurer.measureMm(ch, options), cjk: false, space: true });
249
+ continue;
250
+ }
251
+ if (CJK_RE.test(ch)) {
252
+ flush();
253
+ atoms.push({ text: ch, widthMm: measurer.measureMm(ch, options), cjk: true });
254
+ continue;
255
+ }
256
+ word += ch;
257
+ wordWidth += measurer.measureMm(ch, options);
258
+ }
259
+ flush();
260
+ return atoms;
261
+ }
262
+ function wrapText(text, maxWidthMm, options, measurer = heuristicMeasurer) {
263
+ if (!text) return [""];
264
+ const lines = [];
265
+ let current = "";
266
+ let currentWidth = 0;
267
+ const pushLine = () => {
268
+ lines.push(current);
269
+ current = "";
270
+ currentWidth = 0;
271
+ };
272
+ for (const atom of tokenize(text, options, measurer)) {
273
+ if (atom.br) {
274
+ pushLine();
275
+ continue;
276
+ }
277
+ if (currentWidth + atom.widthMm <= maxWidthMm) {
278
+ current += atom.text;
279
+ currentWidth += atom.widthMm;
280
+ continue;
281
+ }
282
+ if (atom.space) {
283
+ pushLine();
284
+ continue;
285
+ }
286
+ if (atom.cjk) {
287
+ if (current.length > 0) pushLine();
288
+ current = atom.text;
289
+ currentWidth = atom.widthMm;
290
+ continue;
291
+ }
292
+ if (current.length > 0) pushLine();
293
+ if (atom.widthMm <= maxWidthMm) {
294
+ current = atom.text;
295
+ currentWidth = atom.widthMm;
296
+ continue;
297
+ }
298
+ for (const ch of atom.text) {
299
+ const chWidth = measurer.measureMm(ch, options);
300
+ if (currentWidth + chWidth > maxWidthMm && current.length > 0) {
301
+ pushLine();
302
+ }
303
+ current += ch;
304
+ currentWidth += chWidth;
305
+ }
306
+ }
307
+ pushLine();
308
+ return lines;
309
+ }
310
+
311
+ // src/layout/paginate.ts
312
+ function makeWorkingPage(page) {
313
+ return {
314
+ widthMm: page.widthMm,
315
+ heightMm: page.heightMm,
316
+ marginTopMm: page.marginTopMm ?? 0,
317
+ marginBottomMm: page.marginBottomMm ?? 0,
318
+ marginLeftMm: page.marginLeftMm ?? 0,
319
+ marginRightMm: page.marginRightMm ?? 0,
320
+ components: []
321
+ };
322
+ }
323
+ function toContentBottom(page, wp) {
324
+ return wp.heightMm - (page.marginBottomMm ?? wp.marginBottomMm);
325
+ }
326
+ function estRowHeightMm(list, lines) {
327
+ const fontSizePt = list.fontSizePt ?? DEFAULT_FONT_PT;
328
+ const lineMm = fontSizePt * LINE_HEIGHT * 25.4 / 72;
329
+ const padding = (list.paddingMm ?? 1) * 2;
330
+ return Math.max(list.rowHeightMm ?? 0, lines * lineMm + padding);
331
+ }
332
+ function cellText(value) {
333
+ if (value == null) return "";
334
+ if (typeof value === "string") return value;
335
+ if (typeof value === "number") return String(value);
336
+ if (typeof value === "boolean") return value ? "\u662F" : "\u5426";
337
+ if (value instanceof Date) return value.toISOString().slice(0, 10);
338
+ try {
339
+ return JSON.stringify(value);
340
+ } catch {
341
+ return String(value);
342
+ }
343
+ }
344
+ function interList(list, measurer) {
345
+ const fontSizePt = list.fontSizePt ?? DEFAULT_FONT_PT;
346
+ const paddingMm = list.paddingMm ?? 1;
347
+ const cellStyles = list.columns.map(
348
+ (col) => col.style ? { align: "left", verticalAlign: "middle", ...col.style } : { align: "left", verticalAlign: "middle" }
349
+ );
350
+ const headerCellStyles = list.columns.map(
351
+ (col) => ({
352
+ bold: list.headerBold ?? true,
353
+ align: "center",
354
+ verticalAlign: "middle",
355
+ backgroundColor: list.headerBackgroundColor,
356
+ ...col.style || {}
357
+ })
358
+ );
359
+ const headerTexts = list.columns.map((col) => col.header);
360
+ const headerLines = headerTexts.map(
361
+ (text, i) => wrapText(
362
+ text,
363
+ list.columns[i].widthMm - paddingMm * 2,
364
+ { fontSizePt, bold: list.headerBold ?? true },
365
+ measurer
366
+ )
367
+ );
368
+ const maxLines = Math.max(...headerLines.map((l) => l.length), 1);
369
+ const headerHeight = estRowHeightMm(list, maxLines);
370
+ const headerCells = headerTexts.map((text, i) => ({
371
+ text,
372
+ style: headerCellStyles[i]
373
+ }));
374
+ const borderStyle = list.border === false ? { borderTop: false, borderRight: false, borderBottom: false, borderLeft: false } : { borderBottom: true, borderLeft: true, borderRight: true, borderTop: true };
375
+ return {
376
+ fontSizePt,
377
+ paddingMm,
378
+ cellStyles,
379
+ borderStyle,
380
+ headerHeightMm: headerHeight,
381
+ headerGrid: { cells: [headerCells], rowHeightsMm: [headerHeight] },
382
+ columns: list.columns
383
+ };
384
+ }
385
+ function buildRowGridFast(inter, row, measurer) {
386
+ const columns = inter.columns;
387
+ const texts = new Array(columns.length);
388
+ let maxLines = 1;
389
+ for (let i = 0; i < columns.length; i++) {
390
+ texts[i] = cellText(row[columns[i].field]);
391
+ }
392
+ const lines = new Array(columns.length);
393
+ for (let i = 0; i < columns.length; i++) {
394
+ const wrapped = wrapText(
395
+ texts[i],
396
+ columns[i].widthMm - inter.paddingMm * 2,
397
+ { fontSizePt: inter.fontSizePt },
398
+ measurer
399
+ );
400
+ lines[i] = wrapped;
401
+ if (wrapped.length > maxLines) maxLines = wrapped.length;
402
+ }
403
+ const lineMm = inter.fontSizePt * LINE_HEIGHT * 25.4 / 72;
404
+ const height = Math.max(
405
+ inter.rowHeightFloorMm,
406
+ maxLines * lineMm + inter.paddingMm * 2
407
+ );
408
+ const cells = new Array(columns.length);
409
+ for (let i = 0; i < columns.length; i++) {
410
+ cells[i] = { text: texts[i], style: inter.cellStyles[i] };
411
+ }
412
+ return { grid: { cells: [cells], rowHeightsMm: [height] }, heightMm: height };
413
+ }
414
+ function paginateTemplate(template, datasets, optionsOrMmPerRow = {}) {
415
+ const options = typeof optionsOrMmPerRow === "number" ? { mmPerRow: optionsOrMmPerRow } : optionsOrMmPerRow;
416
+ const mmPerRow = options.mmPerRow ?? 0;
417
+ const measurer = options.measurer ?? heuristicMeasurer;
418
+ const warnings = [];
419
+ const pages = [];
420
+ let seq = 0;
421
+ const nextId = (prefix) => `${prefix}_${(++seq).toString(36)}`;
422
+ for (const tplPage of template.pages) {
423
+ const current = makeWorkingPage(tplPage);
424
+ const contentBottom = () => toContentBottom(tplPage, current);
425
+ const contentTop = current.marginTopMm;
426
+ const pushCurrent = () => {
427
+ pages.push({
428
+ pageWidthMm: current.widthMm,
429
+ pageHeightMm: current.heightMm,
430
+ marginTopMm: current.marginTopMm,
431
+ marginBottomMm: current.marginBottomMm,
432
+ marginLeftMm: current.marginLeftMm,
433
+ marginRightMm: current.marginRightMm,
434
+ components: current.components
435
+ });
436
+ };
437
+ for (const comp of tplPage.components) {
438
+ if (comp.kind !== "list") {
439
+ current.components.push(comp);
440
+ continue;
441
+ }
442
+ const list = comp;
443
+ const rows = datasets[list.dataset];
444
+ if (!Array.isArray(rows)) {
445
+ warnings.push(`\u6570\u636E\u96C6 ${list.dataset} \u672A\u63D0\u4F9B\u6570\u636E\uFF0C\u5217\u8868 ${list.id ?? ""} \u8F93\u51FA\u4E3A\u7A7A`);
446
+ }
447
+ const dataRows = Array.isArray(rows) ? rows : [];
448
+ const columnsWidth = list.columns.reduce((s, c) => s + c.widthMm, 0);
449
+ const scale = columnsWidth > list.widthMm ? list.widthMm / columnsWidth : 1;
450
+ const scaledList = scale === 1 ? list : { ...list, columns: list.columns.map((c) => ({ ...c, widthMm: c.widthMm * scale })) };
451
+ const inter = {
452
+ ...interList(scaledList, measurer),
453
+ rowHeightFloorMm: scaledList.rowHeightMm ?? 0
454
+ };
455
+ const headerComp = {
456
+ kind: "rect",
457
+ leftMm: list.leftMm,
458
+ topMm: 0,
459
+ widthMm: list.widthMm,
460
+ heightMm: inter.headerHeightMm,
461
+ grid: inter.headerGrid,
462
+ nid: nextId("listh"),
463
+ style: inter.borderStyle
464
+ };
465
+ let y = list.topMm;
466
+ const placeHeader = (target, topMm) => {
467
+ target.components.push({ ...headerComp, topMm });
468
+ };
469
+ placeHeader(current, y);
470
+ y += inter.headerHeightMm + mmPerRow;
471
+ for (let i = 0; i < dataRows.length; i++) {
472
+ const rowGrid = buildRowGridFast(inter, dataRows[i], measurer);
473
+ if (y + rowGrid.heightMm > contentBottom()) {
474
+ pushCurrent();
475
+ const next = makeWorkingPage(tplPage);
476
+ current.components = next.components;
477
+ current.heightMm = next.heightMm;
478
+ if (scaledList.headerEveryPage !== false) {
479
+ placeHeader(current, list.topMm);
480
+ y = list.topMm + inter.headerHeightMm;
481
+ } else {
482
+ y = contentTop;
483
+ }
484
+ }
485
+ current.components.push({
486
+ kind: "rect",
487
+ leftMm: list.leftMm,
488
+ topMm: y,
489
+ widthMm: list.widthMm,
490
+ heightMm: rowGrid.heightMm,
491
+ grid: rowGrid.grid,
492
+ nid: nextId("listr"),
493
+ style: inter.borderStyle
494
+ });
495
+ y += rowGrid.heightMm;
496
+ }
497
+ }
498
+ pushCurrent();
499
+ }
500
+ return {
501
+ plan: { schemaVersion: RENDER_PLAN_SCHEMA_VERSION, pages },
502
+ warnings
503
+ };
504
+ }
505
+
506
+ // src/render/grid-geometry.ts
507
+ function cumulate(sizesMm, totalMm) {
508
+ const n = sizesMm.length;
509
+ if (n === 0) return [];
510
+ const sum = sizesMm.reduce((s, v) => s + v, 0);
511
+ const scale = sum > 0 && totalMm > 0 ? totalMm / sum : 1;
512
+ const out = [];
513
+ let acc = 0;
514
+ for (let i = 0; i < n; i++) {
515
+ const size = Math.max(0, sizesMm[i] * scale);
516
+ out.push({ pos: acc, size });
517
+ acc += size;
518
+ }
519
+ return out;
520
+ }
521
+ function resolveGridLayout(grid, compWidthMm, compHeightMm) {
522
+ const rowsRaw = grid.cells ?? [];
523
+ const rowCount = Math.max(rowsRaw.length, 1);
524
+ const colCount = grid.colWidthsMm?.length ?? Math.max(1, ...rowsRaw.map((r) => Array.isArray(r) ? r.length : 0));
525
+ const widths = grid.colWidthsMm?.length ? cumulate(grid.colWidthsMm, compWidthMm) : Array.from({ length: colCount }, (_, i) => ({
526
+ pos: compWidthMm * i / colCount,
527
+ size: compWidthMm / colCount
528
+ }));
529
+ const colPos = (ci) => ci <= 0 ? 0 : ci >= colCount ? widths[widths.length - 1].pos + widths[widths.length - 1].size : widths[ci].pos;
530
+ const heights = grid.rowHeightsMm?.length ? cumulate(grid.rowHeightsMm, compHeightMm) : Array.from({ length: rowCount }, (_, i) => ({
531
+ pos: compHeightMm * i / rowCount,
532
+ size: compHeightMm / rowCount
533
+ }));
534
+ const rowPos = (ri) => ri <= 0 ? 0 : ri >= rowCount ? heights[heights.length - 1].pos + heights[heights.length - 1].size : heights[ri].pos;
535
+ const occupied = /* @__PURE__ */ new Set();
536
+ const key = (r, c) => `${r}:${c}`;
537
+ const cells = [];
538
+ const vBridge = /* @__PURE__ */ new Set();
539
+ const hBridge = /* @__PURE__ */ new Set();
540
+ const vKey = (c, r) => `v${c}:${r}`;
541
+ const hKey = (r, c) => `h${r}:${c}`;
542
+ rowsRaw.forEach((row, ri) => {
543
+ if (!Array.isArray(row)) return;
544
+ let ci = 0;
545
+ for (const cell of row) {
546
+ while (ci < colCount && occupied.has(key(ri, ci))) ci++;
547
+ if (ci >= colCount) break;
548
+ const colSpan = Math.max(1, Math.min(cell?.colSpan ?? 1, colCount - ci));
549
+ const rowSpan = Math.max(1, cell?.rowSpan ?? 1);
550
+ const rowEnd = Math.min(rowCount, ri + rowSpan);
551
+ const colEnd = Math.min(colCount, ci + colSpan);
552
+ for (let dr = ri; dr < rowEnd; dr++) {
553
+ for (let dc = ci; dc < colEnd; dc++) {
554
+ occupied.add(key(dr, dc));
555
+ }
556
+ }
557
+ for (let c = ci + 1; c < colEnd; c++) {
558
+ for (let r = ri; r < rowEnd; r++) vBridge.add(vKey(c, r));
559
+ }
560
+ for (let r = ri + 1; r < rowEnd; r++) {
561
+ for (let c = ci; c < colEnd; c++) hBridge.add(hKey(r, c));
562
+ }
563
+ const xMm = colPos(ci);
564
+ const yMm = rowPos(ri);
565
+ cells.push({
566
+ ri,
567
+ ci,
568
+ cell: cell ?? {},
569
+ xMm,
570
+ yMm,
571
+ widthMm: colPos(colEnd) - xMm,
572
+ heightMm: rowPos(rowEnd) - yMm
573
+ });
574
+ ci = colEnd;
575
+ }
576
+ });
577
+ const edges = [];
578
+ for (let c = 1; c < colCount; c++) {
579
+ let runStart = -1;
580
+ for (let r = 0; r <= rowCount; r++) {
581
+ const open = r < rowCount && !vBridge.has(vKey(c, r));
582
+ if (open && runStart < 0) runStart = r;
583
+ if (!open && runStart >= 0) {
584
+ edges.push({ kind: "v", pos: colPos(c), from: rowPos(runStart), to: rowPos(r) });
585
+ runStart = -1;
586
+ }
587
+ }
588
+ }
589
+ for (let r = 1; r < rowCount; r++) {
590
+ let runStart = -1;
591
+ for (let c = 0; c <= colCount; c++) {
592
+ const open = c < colCount && !hBridge.has(hKey(r, c));
593
+ if (open && runStart < 0) runStart = c;
594
+ if (!open && runStart >= 0) {
595
+ edges.push({ kind: "h", pos: rowPos(r), from: colPos(runStart), to: colPos(c) });
596
+ runStart = -1;
597
+ }
598
+ }
599
+ }
600
+ return { columns: widths, rows: heights, cells, edges };
601
+ }
602
+
603
+ // src/render/style.ts
604
+ function resolveFontSize(style, dpi = 96, defaultPt = DEFAULT_FONT_PT) {
605
+ if (style?.fontSizePx != null) {
606
+ return { fontSizePt: style.fontSizePx * 72 / dpi, fontSizePx: style.fontSizePx };
607
+ }
608
+ const pt = style?.fontSize ?? defaultPt;
609
+ return { fontSizePt: pt, fontSizePx: ptToPx(pt, dpi) };
610
+ }
611
+ function resolveStroke(style, fallbackColor = DEFAULT_STROKE_COLOR) {
612
+ return {
613
+ color: style?.borderColor ?? style?.lineColor ?? fallbackColor,
614
+ widthPt: style?.lineWidthPt ?? DEFAULT_BORDER_PT
615
+ };
616
+ }
617
+ function resolveTextColor(style, fallback = DEFAULT_TEXT_COLOR) {
618
+ return style?.color ?? fallback;
619
+ }
620
+ function resolveLineHeight(style) {
621
+ return style?.lineHeight ?? LINE_HEIGHT;
622
+ }
623
+ function resolveBoxBorders(style) {
624
+ const top = style?.borderTop !== false;
625
+ const right = style?.borderRight !== false;
626
+ const bottom = style?.borderBottom !== false;
627
+ const left = style?.borderLeft !== false;
628
+ return { top, right, bottom, left, inner: top || right || bottom || left };
629
+ }
630
+
631
+ // src/render/dom.ts
632
+ var CLS = {
633
+ root: "mr-root",
634
+ page: "mr-page",
635
+ comp: "mr-comp",
636
+ text: "mr-text",
637
+ lines: "mr-lines",
638
+ line: "mr-line",
639
+ rich: "mr-rich",
640
+ paragraph: "mr-paragraph",
641
+ segment: "mr-segment",
642
+ image: "mr-image",
643
+ shape: "mr-shape",
644
+ grid: "mr-grid",
645
+ row: "mr-row",
646
+ cell: "mr-cell",
647
+ unknown: "mr-unknown"
648
+ };
649
+ function classOf(key, options) {
650
+ return options?.classNames?.[key] ?? CLS[key];
651
+ }
652
+ var StyleBag = class {
653
+ constructor() {
654
+ this.props = [];
655
+ }
656
+ add(prop, value) {
657
+ if (value === void 0 || value === null || value === false || value === "") return this;
658
+ this.props.push([prop, String(value)]);
659
+ return this;
660
+ }
661
+ mm(prop, valueMm, dpi, digits = 2) {
662
+ return this.add(prop, `${round(mmToPx(valueMm, dpi), digits)}px`);
663
+ }
664
+ toString() {
665
+ let css = "";
666
+ for (const [prop, value] of this.props) {
667
+ css += `${prop}:${value};`;
668
+ }
669
+ return css;
670
+ }
671
+ applyTo(el) {
672
+ const css = this.toString();
673
+ if (css) el.style.cssText = css;
674
+ }
675
+ };
676
+ function applyComponentStyle(bag, comp, dpi) {
677
+ const s = comp.style || {};
678
+ bag.add("color", s.color).add("background-color", s.backgroundColor).add("font-family", s.fontFamily).add("font-weight", s.bold ? "700" : void 0).add("font-style", s.italic ? "italic" : void 0).add("text-decoration", s.underline ? "underline" : void 0).add("letter-spacing", s.letterSpacing != null ? `${s.letterSpacing}pt` : void 0).add("opacity", s.opacity != null ? String(s.opacity) : void 0).add("transform", s.rotateDeg ? `rotate(${s.rotateDeg}deg)` : void 0);
679
+ const font = resolveFontSize(s, dpi);
680
+ bag.add("font-size", `${round(font.fontSizePx, 2)}px`);
681
+ }
682
+ function renderGrid(grid, comp, dpi, options, doc) {
683
+ const table = doc.createElement("table");
684
+ table.className = classOf("grid", options);
685
+ new StyleBag().add("width", "100%").add("height", "100%").add("border-collapse", "collapse").add("table-layout", grid.colWidthsMm ? "fixed" : "auto").applyTo(table);
686
+ if (grid.colWidthsMm) {
687
+ const colGroup = doc.createElement("colgroup");
688
+ for (const w of grid.colWidthsMm) {
689
+ const col = doc.createElement("col");
690
+ new StyleBag().mm("width", w, dpi).applyTo(col);
691
+ colGroup.appendChild(col);
692
+ }
693
+ table.appendChild(colGroup);
694
+ }
695
+ const borders = resolveBoxBorders(comp.style);
696
+ const stroke = resolveStroke(comp.style);
697
+ const border = borders.inner ? `${round(stroke.widthPt * dpi / 72, 2)}px solid ${stroke.color}` : "";
698
+ grid.cells.forEach((row, ri) => {
699
+ const rowEl = doc.createElement("tr");
700
+ rowEl.className = classOf("row", options);
701
+ if (grid.rowHeightsMm?.[ri] != null) {
702
+ new StyleBag().mm("height", grid.rowHeightsMm[ri], dpi).applyTo(rowEl);
703
+ }
704
+ (row || []).forEach((cell) => {
705
+ const cellEl = doc.createElement("td");
706
+ cellEl.className = classOf("cell", options);
707
+ if (cell?.colSpan) cellEl.colSpan = cell.colSpan;
708
+ if (cell?.rowSpan) cellEl.rowSpan = cell.rowSpan;
709
+ const cellBag = new StyleBag();
710
+ if (border) cellBag.add("border", border);
711
+ if (cell?.style) {
712
+ const st = cell.style;
713
+ cellBag.add("text-align", st.align).add("vertical-align", st.verticalAlign).add("background-color", st.backgroundColor).add("color", st.color).add("font-weight", st.bold ? "700" : void 0);
714
+ cellBag.add("font-size", `${round(resolveFontSize(st, dpi).fontSizePx, 2)}px`);
715
+ }
716
+ cellBag.applyTo(cellEl);
717
+ if (cell?.text != null) cellEl.textContent = cell.text;
718
+ if (cell?.children?.length) {
719
+ for (const child of cell.children) {
720
+ cellEl.appendChild(renderComponent(child, dpi, options, doc));
721
+ }
722
+ }
723
+ rowEl.appendChild(cellEl);
724
+ });
725
+ table.appendChild(rowEl);
726
+ });
727
+ return table;
728
+ }
729
+ function renderText(comp, dpi, options, doc) {
730
+ const el = doc.createElement("div");
731
+ el.className = classOf("text", options);
732
+ const s = comp.style || {};
733
+ const bag = new StyleBag().add("text-align", s.align).add("display", "flex").add("flex-direction", "column").add(
734
+ "justify-content",
735
+ s.verticalAlign === "middle" ? "center" : s.verticalAlign === "bottom" ? "flex-end" : "flex-start"
736
+ ).add("overflow", "hidden");
737
+ applyComponentStyle(bag, comp, dpi);
738
+ bag.applyTo(el);
739
+ if (isRichText(comp)) {
740
+ const richBox = doc.createElement("div");
741
+ richBox.className = classOf("rich", options);
742
+ for (const para of comp.richParagraphs) {
743
+ const p = doc.createElement("div");
744
+ p.className = classOf("paragraph", options);
745
+ const pBag = new StyleBag().add("text-align", para.align).add("line-height", para.lineHeight != null ? String(para.lineHeight) : void 0).mm("margin-top", para.spaceBeforeMm ?? 0, dpi).mm("margin-bottom", para.spaceAfterMm ?? 0, dpi).mm("text-indent", para.indentMm ?? 0, dpi);
746
+ pBag.applyTo(p);
747
+ for (const seg of para.segments || []) {
748
+ const segEl = doc.createElement("span");
749
+ segEl.className = classOf("segment", options);
750
+ segEl.textContent = seg.kind === "field" ? seg.field ?? "" : seg.text ?? "";
751
+ if (seg.style) {
752
+ const segBag = new StyleBag().add("color", seg.style.color).add("font-weight", seg.style.bold ? "700" : void 0);
753
+ segBag.add("font-size", `${round(resolveFontSize(seg.style, dpi).fontSizePx, 2)}px`);
754
+ segBag.applyTo(segEl);
755
+ }
756
+ p.appendChild(segEl);
757
+ }
758
+ richBox.appendChild(p);
759
+ }
760
+ el.appendChild(richBox);
761
+ return el;
762
+ }
763
+ if (Array.isArray(comp.lines) && comp.lines.length > 0) {
764
+ const box = doc.createElement("div");
765
+ box.className = classOf("lines", options);
766
+ if (s.lineHeight != null) new StyleBag().add("line-height", String(s.lineHeight)).applyTo(box);
767
+ for (const line of comp.lines) {
768
+ const lineEl = doc.createElement("div");
769
+ lineEl.className = classOf("line", options);
770
+ lineEl.textContent = line;
771
+ box.appendChild(lineEl);
772
+ }
773
+ el.appendChild(box);
774
+ return el;
775
+ }
776
+ el.textContent = comp.text ?? "";
777
+ return el;
778
+ }
779
+ function renderImage(comp, options, doc) {
780
+ const img = doc.createElement("img");
781
+ img.className = classOf("image", options);
782
+ img.src = comp.imageData;
783
+ new StyleBag().add("width", "100%").add("height", "100%").add("object-fit", "contain").add("display", "block").applyTo(img);
784
+ img.alt = comp.kind;
785
+ return img;
786
+ }
787
+ function renderShape(comp, dpi, options, doc) {
788
+ const el = doc.createElement("div");
789
+ el.className = classOf("shape", options);
790
+ const s = comp.style || {};
791
+ const stroke = resolveStroke(s);
792
+ const borders = resolveBoxBorders(s);
793
+ const border = `${round(stroke.widthPt * dpi / 72, 2)}px solid ${stroke.color}`;
794
+ const bag = new StyleBag();
795
+ if (comp.grid) {
796
+ if (borders.top) bag.add("border-top", border);
797
+ if (borders.right) bag.add("border-right", border);
798
+ if (borders.bottom) bag.add("border-bottom", border);
799
+ if (borders.left) bag.add("border-left", border);
800
+ } else if (comp.kind === "rect") {
801
+ if (borders.top) bag.add("border-top", border);
802
+ if (borders.right) bag.add("border-right", border);
803
+ if (borders.bottom) bag.add("border-bottom", border);
804
+ if (borders.left) bag.add("border-left", border);
805
+ } else if (comp.kind === "ellipse") {
806
+ if (borders.inner) bag.add("border", border).add("border-radius", "50%");
807
+ if (s.backgroundColor) bag.add("background-color", s.backgroundColor);
808
+ } else if (comp.kind === "line") {
809
+ bag.add("height", "0").add("border-top", border);
810
+ }
811
+ bag.add("box-sizing", "border-box").add("width", "100%").add("height", "100%");
812
+ bag.applyTo(el);
813
+ return el;
814
+ }
815
+ function renderComponent(comp, dpi, options, doc) {
816
+ const wrap = doc.createElement("div");
817
+ wrap.className = `${classOf("comp", options)} mr-kind-${comp.kind}`;
818
+ const bag = new StyleBag().add("position", "absolute").mm("left", comp.leftMm, dpi).mm("top", comp.topMm, dpi).mm("width", comp.widthMm, dpi).mm("height", comp.heightMm, dpi).add("box-sizing", "border-box");
819
+ applyComponentStyle(bag, comp, dpi);
820
+ bag.applyTo(wrap);
821
+ if (isImageBackedComponent(comp)) {
822
+ wrap.appendChild(renderImage(comp, options, doc));
823
+ return wrap;
824
+ }
825
+ if (comp.kind === "text") {
826
+ wrap.appendChild(renderText(comp, dpi, options, doc));
827
+ return wrap;
828
+ }
829
+ if (comp.grid) {
830
+ const shape = renderShape(comp, dpi, options, doc);
831
+ shape.appendChild(renderGrid(comp.grid, comp, dpi, options, doc));
832
+ wrap.appendChild(shape);
833
+ return wrap;
834
+ }
835
+ if (comp.kind === "line" || comp.kind === "rect" || comp.kind === "ellipse" || comp.kind === "chart") {
836
+ wrap.appendChild(renderShape(comp, dpi, options, doc));
837
+ return wrap;
838
+ }
839
+ const ph = doc.createElement("div");
840
+ ph.className = classOf("unknown", options);
841
+ ph.textContent = comp.fallbackText ?? "";
842
+ wrap.appendChild(ph);
843
+ return wrap;
844
+ }
845
+ function renderPage(page, dpi, options, doc) {
846
+ const el = doc.createElement("div");
847
+ el.className = classOf("page", options);
848
+ new StyleBag().add("position", "relative").mm("width", page.pageWidthMm, dpi).mm("height", page.pageHeightMm, dpi).add("background-color", "#fff").add("overflow", "hidden").add("box-sizing", "border-box").applyTo(el);
849
+ for (const comp of page.components) {
850
+ el.appendChild(renderComponent(comp, dpi, options, doc));
851
+ }
852
+ return el;
853
+ }
854
+ function renderPlan(plan, doc, options = {}) {
855
+ const dpi = options.dpi ?? 96;
856
+ const root = doc.createElement("div");
857
+ root.className = classOf("root", options);
858
+ root.dataset.schemaVersion = plan.schemaVersion;
859
+ plan.pages.forEach((page) => {
860
+ root.appendChild(renderPage(page, dpi, options, doc));
861
+ });
862
+ return root;
863
+ }
864
+
865
+ // src/render/window.ts
866
+ function computePageWindow(input) {
867
+ const { pageHeightsPx, viewportHeightPx, scrollTopPx, overscan = 1, gapPx = 0 } = input;
868
+ const n = pageHeightsPx.length;
869
+ if (n === 0) {
870
+ return { start: 0, end: -1, padTopPx: 0, padBottomPx: 0 };
871
+ }
872
+ const gap = Math.max(0, gapPx);
873
+ const offsets = new Array(n + 1);
874
+ offsets[0] = 0;
875
+ for (let i = 0; i < n; i++) {
876
+ offsets[i + 1] = offsets[i] + Math.max(0, pageHeightsPx[i] ?? 0) + gap;
877
+ }
878
+ const total = offsets[n];
879
+ const top = Math.max(0, scrollTopPx);
880
+ let cover = 0;
881
+ let lo = 0;
882
+ let hi = n - 1;
883
+ while (lo <= hi) {
884
+ const mid = lo + hi >> 1;
885
+ if (offsets[mid] <= top) {
886
+ cover = mid;
887
+ lo = mid + 1;
888
+ } else {
889
+ hi = mid - 1;
890
+ }
891
+ }
892
+ const start = Math.max(0, Math.min(n - 1, cover - overscan));
893
+ const target = offsets[cover] + Math.max(0, viewportHeightPx);
894
+ let visibleEnd = n - 1;
895
+ lo = 0;
896
+ hi = n - 1;
897
+ while (lo <= hi) {
898
+ const mid = lo + hi >> 1;
899
+ if (offsets[mid + 1] < target) {
900
+ lo = mid + 1;
901
+ } else {
902
+ visibleEnd = mid;
903
+ hi = mid - 1;
904
+ }
905
+ }
906
+ const end = Math.min(n - 1, visibleEnd + overscan);
907
+ const padTopPx = offsets[start];
908
+ const padBottomPx = Math.max(0, total - offsets[end + 1]);
909
+ return { start, end, padTopPx, padBottomPx };
910
+ }
911
+
912
+ // src/render/canvas.ts
913
+ var PX_PER_MM_BASE = 96 / 25.4;
914
+ function measurePageSizePx(page, dpi = 96) {
915
+ const pxPerMm = dpi / 96 * PX_PER_MM_BASE;
916
+ return {
917
+ widthPx: Math.max(1, Math.round(page.pageWidthMm * pxPerMm)),
918
+ heightPx: Math.max(1, Math.round(page.pageHeightMm * pxPerMm))
919
+ };
920
+ }
921
+ function createPageCanvas(page, dpr, dpi) {
922
+ const canvas = document.createElement("canvas");
923
+ const { widthPx: w, heightPx: h } = measurePageSizePx(page, dpi);
924
+ canvas.width = Math.round(w * dpr);
925
+ canvas.height = Math.round(h * dpr);
926
+ canvas.style.width = `${w}px`;
927
+ canvas.style.height = `${h}px`;
928
+ const ctx = canvas.getContext("2d");
929
+ if (!ctx) {
930
+ throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Canvas 2D\uFF08\u6D4B\u8BD5\u9700\u771F\u6D4F\u89C8\u5668\uFF0C\u89C1 e2e/canvas.spec.ts\uFF09");
931
+ }
932
+ ctx.scale(dpr, dpr);
933
+ ctx.fillStyle = "#ffffff";
934
+ ctx.fillRect(0, 0, w, h);
935
+ return { canvas, ctx };
936
+ }
937
+ function planFontSizePx(style, dpi = 96) {
938
+ return resolveFontSize(style, dpi).fontSizePx;
939
+ }
940
+ function drawTextBlock(ctx, spec, x, y, w, h, fontFamily, verticalAlign = "top") {
941
+ ctx.font = `${spec.bold ? "bold " : ""}${spec.fontSizePx}px ${fontFamily}`;
942
+ ctx.fillStyle = spec.color;
943
+ ctx.textBaseline = "alphabetic";
944
+ const lineH = spec.fontSizePx * spec.lineHeight;
945
+ const lines = spec.text === "" ? [""] : spec.text.split("\n");
946
+ const totalH = lines.length * lineH;
947
+ let topY = y;
948
+ if (verticalAlign === "middle") topY = y + (h - totalH) / 2;
949
+ else if (verticalAlign === "bottom") topY = y + h - totalH;
950
+ lines.forEach((line, i) => {
951
+ ctx.textAlign = spec.align === "center" ? "center" : spec.align === "right" ? "right" : "left";
952
+ const tx = spec.align === "center" ? x + w / 2 : spec.align === "right" ? x + w : x;
953
+ ctx.fillText(line, tx, topY + i * lineH + spec.fontSizePx * 0.8, w);
954
+ });
955
+ ctx.textAlign = "left";
956
+ }
957
+ function drawGrid(ctx, grid, comp, x, y, w, h, pxPerMm, dpi, fontFamily, measurer) {
958
+ const layout = resolveGridLayout(grid, w / pxPerMm, h / pxPerMm);
959
+ const borders = resolveBoxBorders(comp.style);
960
+ const stroke = resolveStroke(comp.style);
961
+ for (const box of layout.cells) {
962
+ const bg = box.cell.style?.backgroundColor;
963
+ if (bg) {
964
+ ctx.fillStyle = bg;
965
+ ctx.fillRect(
966
+ x + box.xMm * pxPerMm,
967
+ y + box.yMm * pxPerMm,
968
+ box.widthMm * pxPerMm,
969
+ box.heightMm * pxPerMm
970
+ );
971
+ }
972
+ }
973
+ if (borders.inner) {
974
+ ctx.strokeStyle = stroke.color;
975
+ ctx.lineWidth = ptToPx(stroke.widthPt, dpi);
976
+ for (const e of layout.edges) {
977
+ ctx.beginPath();
978
+ if (e.kind === "v") {
979
+ ctx.moveTo(x + e.pos * pxPerMm, y + e.from * pxPerMm);
980
+ ctx.lineTo(x + e.pos * pxPerMm, y + e.to * pxPerMm);
981
+ } else {
982
+ ctx.moveTo(x + e.from * pxPerMm, y + e.pos * pxPerMm);
983
+ ctx.lineTo(x + e.to * pxPerMm, y + e.pos * pxPerMm);
984
+ }
985
+ ctx.stroke();
986
+ }
987
+ ctx.beginPath();
988
+ if (borders.top) {
989
+ ctx.moveTo(x, y);
990
+ ctx.lineTo(x + w, y);
991
+ }
992
+ if (borders.right) {
993
+ ctx.moveTo(x + w, y);
994
+ ctx.lineTo(x + w, y + h);
995
+ }
996
+ if (borders.bottom) {
997
+ ctx.moveTo(x + w, y + h);
998
+ ctx.lineTo(x, y + h);
999
+ }
1000
+ if (borders.left) {
1001
+ ctx.moveTo(x, y + h);
1002
+ ctx.lineTo(x, y);
1003
+ }
1004
+ ctx.stroke();
1005
+ }
1006
+ for (const box of layout.cells) {
1007
+ const text = typeof box.cell.text === "string" ? box.cell.text : "";
1008
+ if (!text) continue;
1009
+ const st = box.cell.style;
1010
+ const fontSizePt = st?.fontSize ?? DEFAULT_FONT_PT;
1011
+ const lines = wrapText(
1012
+ text,
1013
+ box.widthMm - 1,
1014
+ { fontSizePt },
1015
+ measurer
1016
+ ).join("\n");
1017
+ drawTextBlock(
1018
+ ctx,
1019
+ {
1020
+ text: lines,
1021
+ fontSizePx: resolveFontSize(st, dpi).fontSizePx,
1022
+ bold: st?.bold ?? false,
1023
+ color: st?.color ?? resolveTextColor(comp.style, DEFAULT_TEXT_COLOR),
1024
+ align: st?.align ?? "left",
1025
+ lineHeight: LINE_HEIGHT
1026
+ },
1027
+ x + (box.xMm + 0.5) * pxPerMm,
1028
+ y + box.yMm * pxPerMm,
1029
+ Math.max(0, (box.widthMm - 1) * pxPerMm),
1030
+ box.heightMm * pxPerMm,
1031
+ fontFamily,
1032
+ st?.verticalAlign ?? "middle"
1033
+ );
1034
+ }
1035
+ }
1036
+ function drawComponent(ctx, comp, pxPerMm, dpi, fontFamily, measurer) {
1037
+ const x = comp.leftMm * pxPerMm;
1038
+ const y = comp.topMm * pxPerMm;
1039
+ const w = comp.widthMm * pxPerMm;
1040
+ const h = comp.heightMm * pxPerMm;
1041
+ const s = comp.style ?? {};
1042
+ if (comp.grid) {
1043
+ drawGrid(ctx, comp.grid, comp, x, y, w, h, pxPerMm, dpi, fontFamily, measurer);
1044
+ return;
1045
+ }
1046
+ switch (comp.kind) {
1047
+ case "text": {
1048
+ const fontSizePt = s.fontSize ?? DEFAULT_FONT_PT;
1049
+ const text = isRichText(comp) ? comp.richParagraphs.map((p) => (p.segments ?? []).map((seg) => seg.text ?? seg.field ?? "").join("")).join("\n") : Array.isArray(comp.lines) && comp.lines.length > 0 ? comp.lines.join("\n") : wrapText(comp.text ?? "", comp.widthMm, { fontSizePt }, measurer).join("\n");
1050
+ drawTextBlock(
1051
+ ctx,
1052
+ {
1053
+ text,
1054
+ fontSizePx: resolveFontSize(s, dpi).fontSizePx,
1055
+ bold: s.bold ?? false,
1056
+ color: resolveTextColor(s),
1057
+ align: s.align ?? "left",
1058
+ lineHeight: resolveLineHeight(s)
1059
+ },
1060
+ x,
1061
+ y,
1062
+ w,
1063
+ h,
1064
+ fontFamily,
1065
+ s.verticalAlign
1066
+ );
1067
+ break;
1068
+ }
1069
+ case "rect": {
1070
+ const stroke = resolveStroke(s);
1071
+ const borders = resolveBoxBorders(s);
1072
+ if (s.backgroundColor) {
1073
+ ctx.fillStyle = s.backgroundColor;
1074
+ ctx.fillRect(x, y, w, h);
1075
+ }
1076
+ if (borders.inner) {
1077
+ ctx.strokeStyle = stroke.color;
1078
+ ctx.lineWidth = ptToPx(stroke.widthPt, dpi);
1079
+ ctx.beginPath();
1080
+ if (borders.top) {
1081
+ ctx.moveTo(x, y);
1082
+ ctx.lineTo(x + w, y);
1083
+ }
1084
+ if (borders.right) {
1085
+ ctx.moveTo(x + w, y);
1086
+ ctx.lineTo(x + w, y + h);
1087
+ }
1088
+ if (borders.bottom) {
1089
+ ctx.moveTo(x + w, y + h);
1090
+ ctx.lineTo(x, y + h);
1091
+ }
1092
+ if (borders.left) {
1093
+ ctx.moveTo(x, y + h);
1094
+ ctx.lineTo(x, y);
1095
+ }
1096
+ ctx.stroke();
1097
+ }
1098
+ break;
1099
+ }
1100
+ case "line": {
1101
+ const stroke = resolveStroke({ ...s, borderColor: s.lineColor ?? s.borderColor });
1102
+ ctx.strokeStyle = stroke.color;
1103
+ ctx.lineWidth = ptToPx(stroke.widthPt, dpi);
1104
+ ctx.beginPath();
1105
+ ctx.moveTo(x, y + h / 2);
1106
+ ctx.lineTo(x + w, y + h / 2);
1107
+ ctx.stroke();
1108
+ break;
1109
+ }
1110
+ case "ellipse": {
1111
+ const stroke = resolveStroke(s);
1112
+ ctx.strokeStyle = stroke.color;
1113
+ ctx.lineWidth = ptToPx(stroke.widthPt, dpi);
1114
+ if (s.backgroundColor) {
1115
+ ctx.fillStyle = s.backgroundColor;
1116
+ ctx.beginPath();
1117
+ ctx.ellipse(x + w / 2, y + h / 2, w / 2, h / 2, 0, 0, Math.PI * 2);
1118
+ ctx.fill();
1119
+ }
1120
+ ctx.beginPath();
1121
+ ctx.ellipse(x + w / 2, y + h / 2, w / 2, h / 2, 0, 0, Math.PI * 2);
1122
+ ctx.stroke();
1123
+ break;
1124
+ }
1125
+ }
1126
+ }
1127
+ function renderPlanToCanvas(plan, options = {}) {
1128
+ const dpr = options.dpr ?? (typeof devicePixelRatio === "number" ? devicePixelRatio : 1);
1129
+ const dpi = options.dpi ?? 96;
1130
+ const pxPerMm = dpi / 96 * PX_PER_MM_BASE;
1131
+ const fontFamily = options.fontFamily ?? '"Microsoft YaHei", sans-serif';
1132
+ const measurer = options.measurer ?? heuristicMeasurer;
1133
+ const start = Math.max(0, options.start ?? 0);
1134
+ const end = Math.min(plan.pages.length - 1, options.end ?? plan.pages.length - 1);
1135
+ const canvases = [];
1136
+ for (let i = start; i <= end; i++) {
1137
+ const page = plan.pages[i];
1138
+ const { canvas, ctx } = createPageCanvas(page, dpr, dpi);
1139
+ for (const comp of page.components) {
1140
+ drawComponent(ctx, comp, pxPerMm, dpi, fontFamily, measurer);
1141
+ }
1142
+ canvases.push(canvas);
1143
+ }
1144
+ return { canvases };
1145
+ }
1146
+ function createCanvasPager(plan, options = {}) {
1147
+ const dpr = options.dpr ?? (typeof devicePixelRatio === "number" ? devicePixelRatio : 1);
1148
+ const dpi = options.dpi ?? 96;
1149
+ const pxPerMm = dpi / 96 * PX_PER_MM_BASE;
1150
+ const fontFamily = options.fontFamily ?? '"Microsoft YaHei", sans-serif';
1151
+ const measurer = options.measurer ?? heuristicMeasurer;
1152
+ const overscan = options.overscan ?? 1;
1153
+ const gapPx = options.gapPx ?? 18;
1154
+ const pagesPerFrame = options.pagesPerFrame ?? 1;
1155
+ const pageHeightsPx = plan.pages.map((p) => p.pageHeightMm * pxPerMm);
1156
+ const maxPageWidthPx = Math.max(1, ...plan.pages.map((p) => p.pageWidthMm * pxPerMm));
1157
+ const windowState = { start: 0, end: -1 };
1158
+ let viewport = null;
1159
+ let contentEl = null;
1160
+ let topSpacer = null;
1161
+ let bottomSpacer = null;
1162
+ let slots = [];
1163
+ let raf = 0;
1164
+ let queued = [];
1165
+ function ensureLayout() {
1166
+ const vp = viewport;
1167
+ vp.innerHTML = "";
1168
+ contentEl = document.createElement("div");
1169
+ contentEl.style.cssText = `width:${maxPageWidthPx}px;margin:0 auto;`;
1170
+ topSpacer = document.createElement("div");
1171
+ bottomSpacer = document.createElement("div");
1172
+ contentEl.appendChild(topSpacer);
1173
+ slots = [];
1174
+ const slotCount = Math.min(plan.pages.length, 3 + overscan * 2);
1175
+ for (let i = 0; i < slotCount; i++) {
1176
+ const slot = document.createElement("div");
1177
+ slot.style.cssText = `margin:0 auto ${gapPx}px;box-shadow:0 2px 8px rgba(0,0,0,.35);background:#fff;`;
1178
+ contentEl.appendChild(slot);
1179
+ slots.push(slot);
1180
+ }
1181
+ contentEl.appendChild(bottomSpacer);
1182
+ vp.appendChild(contentEl);
1183
+ }
1184
+ function drawPageInto(slot, pageIndex) {
1185
+ const page = plan.pages[pageIndex];
1186
+ const { widthPx: w, heightPx: h } = measurePageSizePx(page, dpi);
1187
+ let canvas = slot.firstElementChild;
1188
+ if (!canvas || canvas.tagName !== "CANVAS") {
1189
+ canvas = document.createElement("canvas");
1190
+ slot.innerHTML = "";
1191
+ slot.appendChild(canvas);
1192
+ }
1193
+ canvas.width = Math.round(w * dpr);
1194
+ canvas.height = Math.round(h * dpr);
1195
+ canvas.style.width = `${w}px`;
1196
+ canvas.style.height = `${h}px`;
1197
+ const ctx = canvas.getContext("2d");
1198
+ if (!ctx) return;
1199
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1200
+ ctx.scale(dpr, dpr);
1201
+ ctx.fillStyle = "#ffffff";
1202
+ ctx.fillRect(0, 0, w, h);
1203
+ for (const comp of page.components) {
1204
+ drawComponent(ctx, comp, pxPerMm, dpi, fontFamily, measurer);
1205
+ }
1206
+ canvas.dataset.canvasPage = String(pageIndex);
1207
+ slot.style.height = `${h}px`;
1208
+ slot.style.width = `${w}px`;
1209
+ }
1210
+ function applyWindow(win) {
1211
+ windowState.start = win.start;
1212
+ windowState.end = win.end;
1213
+ topSpacer.style.height = `${win.start * gapPx + pageHeightsPx.slice(0, win.start).reduce((s, h) => s + h, 0)}px`;
1214
+ const below = plan.pages.length - 1 - win.end;
1215
+ bottomSpacer.style.height = `${below * gapPx + pageHeightsPx.slice(win.end + 1).reduce((s, h) => s + h, 0)}px`;
1216
+ const used = win.end - win.start + 1;
1217
+ slots.forEach((slot, i) => {
1218
+ if (i < used) {
1219
+ slot.style.display = "";
1220
+ const pageIndex = win.start + i;
1221
+ slot.style.height = `${pageHeightsPx[pageIndex] ?? 0}px`;
1222
+ } else {
1223
+ slot.style.display = "none";
1224
+ }
1225
+ });
1226
+ queued = [];
1227
+ for (let i = win.start; i <= win.end; i++) {
1228
+ const slot = slots[i - win.start];
1229
+ if (!slot) continue;
1230
+ const already = slot.firstElementChild;
1231
+ if (already?.dataset?.canvasPage !== String(i)) queued.push(i);
1232
+ }
1233
+ scheduleFrame();
1234
+ }
1235
+ function scheduleFrame() {
1236
+ if (raf || queued.length === 0) return;
1237
+ raf = requestAnimationFrame(() => {
1238
+ raf = 0;
1239
+ let budget = Math.max(1, pagesPerFrame);
1240
+ while (queued.length > 0 && budget-- > 0) {
1241
+ const pageIndex = queued.shift();
1242
+ const slot = slots[pageIndex - windowState.start];
1243
+ if (slot) drawPageInto(slot, pageIndex);
1244
+ }
1245
+ if (queued.length > 0) scheduleFrame();
1246
+ });
1247
+ }
1248
+ function onScroll() {
1249
+ if (raf) return;
1250
+ raf = requestAnimationFrame(() => {
1251
+ raf = 0;
1252
+ const vp = viewport;
1253
+ const win = computePageWindow({
1254
+ pageHeightsPx,
1255
+ viewportHeightPx: vp.clientHeight,
1256
+ scrollTopPx: vp.scrollTop,
1257
+ overscan,
1258
+ gapPx
1259
+ });
1260
+ if (win.start !== windowState.start || win.end !== windowState.end) {
1261
+ applyWindow(win);
1262
+ } else {
1263
+ scheduleFrame();
1264
+ }
1265
+ });
1266
+ }
1267
+ return {
1268
+ attach(vp) {
1269
+ viewport = vp;
1270
+ ensureLayout();
1271
+ vp.addEventListener("scroll", onScroll, { passive: true });
1272
+ onScroll();
1273
+ },
1274
+ destroy() {
1275
+ if (raf) cancelAnimationFrame(raf);
1276
+ raf = 0;
1277
+ viewport?.removeEventListener("scroll", onScroll);
1278
+ queued = [];
1279
+ contentEl?.remove();
1280
+ viewport = null;
1281
+ },
1282
+ window() {
1283
+ return { ...windowState };
1284
+ }
1285
+ };
1286
+ }
1287
+
1288
+ // src/compat/jh4j-template.ts
1289
+ function num(v, fallback) {
1290
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
1291
+ }
1292
+ function str(v, fallback = "") {
1293
+ return typeof v === "string" ? v : fallback;
1294
+ }
1295
+ function isRaw(v) {
1296
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1297
+ }
1298
+ function mapAlign(h) {
1299
+ if (h === "Center") return "center";
1300
+ if (h === "Right") return "right";
1301
+ return "left";
1302
+ }
1303
+ function mapVAlign(v) {
1304
+ if (v === "Center") return "middle";
1305
+ if (v === "Bottom") return "bottom";
1306
+ return "top";
1307
+ }
1308
+ function baseStyle(node) {
1309
+ const fontSize = parseFloat(str(node["FontSize"], "10.5"));
1310
+ return {
1311
+ fontSize: Number.isFinite(fontSize) ? fontSize : 10.5,
1312
+ fontFamily: str(node["FontFamily"], "\u9ED1\u4F53") || void 0,
1313
+ bold: str(node["FontWeight"]) === "bold" || void 0,
1314
+ italic: str(node["FontStyle"]) === "italic" || void 0,
1315
+ underline: node["Underline"] === true || void 0,
1316
+ color: str(node["FontColor"]) || void 0,
1317
+ backgroundColor: str(node["BackgroundColor"]) || void 0,
1318
+ align: mapAlign(str(node["HorAlignment"], "Left")),
1319
+ verticalAlign: mapVAlign(str(node["VertAlignment"], "Top")),
1320
+ lineHeight: (() => {
1321
+ const lh = parseFloat(str(node["RowSpace"], "1"));
1322
+ return Number.isFinite(lh) && lh !== 1 ? lh : void 0;
1323
+ })(),
1324
+ borderColor: str(node["BorderColor"]) || void 0,
1325
+ borderTop: node["BorderTop"] !== false,
1326
+ borderRight: node["BorderRight"] !== false,
1327
+ borderBottom: node["BorderBottom"] !== false,
1328
+ borderLeft: node["BorderLeft"] !== false
1329
+ };
1330
+ }
1331
+ function convertElement(node, warnings) {
1332
+ const uitype = str(node["uitype"]);
1333
+ const comp = {
1334
+ kind: "text",
1335
+ leftMm: num(node["left"], 0),
1336
+ topMm: num(node["top"], 0),
1337
+ widthMm: num(node["width"], 40),
1338
+ heightMm: num(node["height"], 8),
1339
+ nid: str(node["nid"]) || void 0,
1340
+ style: baseStyle(node)
1341
+ };
1342
+ switch (uitype) {
1343
+ case "text": {
1344
+ comp.kind = "text";
1345
+ const text = node["Text"] ?? node["text"] ?? node["Value"];
1346
+ comp.text = text == null ? "" : String(text);
1347
+ const lines = node["Lines"] ?? node["lines"];
1348
+ if (Array.isArray(lines)) {
1349
+ comp.lines = lines.map((l) => String(l));
1350
+ }
1351
+ return comp;
1352
+ }
1353
+ case "richtext":
1354
+ comp.kind = "text";
1355
+ comp.text = str(node["Text"]);
1356
+ warnings.push(`\u5BCC\u6587\u672C ${comp.nid ?? ""} \u5BFC\u5165\u4E3A\u7EAF\u6587\u672C\uFF08\u7248\u5F0F\u7EC6\u8282\u53EF\u80FD\u635F\u5931\uFF09`);
1357
+ return comp;
1358
+ case "image":
1359
+ case "barcode":
1360
+ case "qrcode":
1361
+ case "chart":
1362
+ comp.kind = uitype === "image" ? "image" : uitype;
1363
+ comp.fallbackText = `[${uitype}]`;
1364
+ warnings.push(`\u56FE\u7247\u7C7B\u7EC4\u4EF6 ${comp.nid ?? ""} \u9700\u8FD0\u884C\u65F6\u6570\u636E\uFF08imageData\uFF09\uFF0C\u5DF2\u5BFC\u5165\u4E3A\u5360\u4F4D`);
1365
+ return comp;
1366
+ case "table": {
1367
+ comp.kind = "rect";
1368
+ const cellsRaw = node["Cells"] ?? node["cells"];
1369
+ if (Array.isArray(cellsRaw)) {
1370
+ const grid = {
1371
+ cells: cellsRaw.map(
1372
+ (row) => Array.isArray(row) ? row.map((cell) => ({
1373
+ text: isRaw(cell) ? str(cell["Text"] ?? cell["text"]) : "",
1374
+ colSpan: isRaw(cell) ? num(cell["ColSpan"], 1) : 1
1375
+ })) : []
1376
+ )
1377
+ };
1378
+ comp.grid = grid;
1379
+ } else {
1380
+ comp.fallbackText = "[table]";
1381
+ warnings.push(`\u8868\u683C ${comp.nid ?? ""} \u7F3A\u5C11 Cells\uFF0C\u5BFC\u5165\u4E3A\u7A7A\u6846`);
1382
+ }
1383
+ return comp;
1384
+ }
1385
+ case "subreport":
1386
+ warnings.push(`\u5B50\u62A5\u8868 ${comp.nid ?? ""} \u6682\u4E0D\u652F\u6301\u5BFC\u5165\uFF0C\u5DF2\u8DF3\u8FC7`);
1387
+ return null;
1388
+ default: {
1389
+ if (str(node["ShapeType"]) === "hline" || uitype === "hline") {
1390
+ comp.kind = "line";
1391
+ return comp;
1392
+ }
1393
+ if (uitype === "vline") {
1394
+ comp.kind = "line";
1395
+ return comp;
1396
+ }
1397
+ if (uitype === "rect" || uitype === "oval" || uitype === "shape") {
1398
+ comp.kind = uitype === "oval" ? "ellipse" : "rect";
1399
+ return comp;
1400
+ }
1401
+ warnings.push(`\u672A\u77E5\u7EC4\u4EF6\u7C7B\u578B ${uitype}\uFF0C\u5DF2\u8DF3\u8FC7`);
1402
+ return null;
1403
+ }
1404
+ }
1405
+ }
1406
+ function importJh4jTemplateContent(content) {
1407
+ const warnings = [];
1408
+ let root = null;
1409
+ if (typeof content === "string") {
1410
+ try {
1411
+ const parsed = JSON.parse(content);
1412
+ if (isRaw(parsed)) root = parsed;
1413
+ } catch {
1414
+ return {
1415
+ template: { pages: [] },
1416
+ warnings: ["content \u4E0D\u662F\u5408\u6CD5 JSON"]
1417
+ };
1418
+ }
1419
+ } else if (isRaw(content)) {
1420
+ root = content;
1421
+ }
1422
+ if (!root) {
1423
+ return { template: { pages: [] }, warnings: ["content \u4E3A\u7A7A"] };
1424
+ }
1425
+ const globalConfig = isRaw(root["GlobalConfig"]) ? root["GlobalConfig"] : {};
1426
+ const defaultWidth = num(globalConfig["paperWidthMm"], 210);
1427
+ const defaultHeight = num(globalConfig["paperHeightMm"], 297);
1428
+ const defaultMargins = {
1429
+ top: num(globalConfig["marginTop"], 10),
1430
+ bottom: num(globalConfig["marginBottom"], 10),
1431
+ left: num(globalConfig["marginLeft"], 10),
1432
+ right: num(globalConfig["marginRight"], 10)
1433
+ };
1434
+ const pageNodes = Array.isArray(root["children"]) ? root["children"].filter((c) => isRaw(c) && str(c["uitype"]) === "page") : [];
1435
+ if (pageNodes.length === 0) {
1436
+ warnings.push("content \u4E2D\u6CA1\u6709 uitype=page \u7684\u9875\u8282\u70B9");
1437
+ }
1438
+ const pages = pageNodes.map((raw) => {
1439
+ const node = raw;
1440
+ const elements = Array.isArray(node["children"]) ? node["children"] : [];
1441
+ const components = [];
1442
+ for (const el of elements) {
1443
+ if (!isRaw(el)) continue;
1444
+ const converted = convertElement(el, warnings);
1445
+ if (converted) components.push(converted);
1446
+ }
1447
+ return {
1448
+ widthMm: num(node["width"], defaultWidth),
1449
+ heightMm: num(node["DesignHeight"], defaultHeight),
1450
+ marginTopMm: num(node["MarginTop"], defaultMargins.top),
1451
+ marginBottomMm: num(node["MarginBottom"], defaultMargins.bottom),
1452
+ marginLeftMm: num(node["MarginLeft"], defaultMargins.left),
1453
+ marginRightMm: num(node["MarginRight"], defaultMargins.right),
1454
+ components
1455
+ };
1456
+ });
1457
+ if (pages.length === 0) {
1458
+ warnings.push("\u8F6C\u6362\u7ED3\u679C\u4E3A\u7A7A\u9875\uFF0C\u5DF2\u56DE\u9000\u4E00\u5F20\u7A7A\u767D\u9875");
1459
+ pages.push({
1460
+ widthMm: defaultWidth,
1461
+ heightMm: defaultHeight,
1462
+ ...defaultMargins,
1463
+ components: []
1464
+ });
1465
+ }
1466
+ return { template: { pages }, warnings };
1467
+ }
1468
+
1469
+ // src/builder/template-builder.ts
1470
+ function resolvePaper(paper, heightArg, landscape) {
1471
+ let widthMm;
1472
+ let heightMm;
1473
+ if (typeof paper === "string") {
1474
+ const preset = PAPER_SIZES[paper];
1475
+ if (!preset) throw new Error(`\u672A\u77E5\u7EB8\u5F20\u9884\u8BBE "${paper}"\uFF08\u53EF\u7528\uFF1A${Object.keys(PAPER_SIZES).join("/")}\uFF09`);
1476
+ widthMm = preset.widthMm;
1477
+ heightMm = preset.heightMm;
1478
+ } else if (typeof paper === "number") {
1479
+ widthMm = paper;
1480
+ heightMm = heightArg ?? 297;
1481
+ } else {
1482
+ widthMm = paper.widthMm;
1483
+ heightMm = paper.heightMm;
1484
+ }
1485
+ return landscape ? { widthMm: heightMm, heightMm: widthMm } : { widthMm, heightMm };
1486
+ }
1487
+ var TemplateBuilder = class {
1488
+ constructor() {
1489
+ this.pages = [];
1490
+ this.pending = null;
1491
+ }
1492
+ /**
1493
+ * 新建页。纸张支持三种写法:
1494
+ * - 预设名:.page("a4", { landscape: true, margins: {...} })
1495
+ * - 数字:.page(210, 297)(第三参数兼容旧 margins 对象或新 options)
1496
+ * - 对象:.page({ widthMm: 210, heightMm: 297 })
1497
+ */
1498
+ page(paper = "a4", optionsOrHeight = {}, maybeOptions) {
1499
+ this.collect();
1500
+ const legacyMargins = maybeOptions != null && !("margins" in maybeOptions) && !("landscape" in maybeOptions) && ("marginTopMm" in maybeOptions || "marginBottomMm" in maybeOptions || "marginLeftMm" in maybeOptions || "marginRightMm" in maybeOptions);
1501
+ const options = legacyMargins ? { margins: maybeOptions } : maybeOptions ?? {};
1502
+ const { widthMm, heightMm } = resolvePaper(
1503
+ paper,
1504
+ typeof optionsOrHeight === "number" ? optionsOrHeight : void 0,
1505
+ options.landscape
1506
+ );
1507
+ const margins = options.margins ?? {};
1508
+ this.pending = new PageBuilder(this, {
1509
+ widthMm,
1510
+ heightMm,
1511
+ marginTopMm: margins.marginTopMm ?? 10,
1512
+ marginBottomMm: margins.marginBottomMm ?? 10,
1513
+ marginLeftMm: margins.marginLeftMm ?? 10,
1514
+ marginRightMm: margins.marginRightMm ?? 10
1515
+ });
1516
+ return this.pending;
1517
+ }
1518
+ collect() {
1519
+ if (this.pending) {
1520
+ this.pages.push(this.pending.buildPage());
1521
+ this.pending = null;
1522
+ }
1523
+ }
1524
+ build() {
1525
+ this.collect();
1526
+ if (this.pages.length === 0) {
1527
+ throw new Error("\u6A21\u677F\u81F3\u5C11\u9700\u8981\u4E00\u9875\uFF1A\u5148\u8C03\u7528 .page() \u6DFB\u52A0\u5185\u5BB9");
1528
+ }
1529
+ return { pages: this.pages };
1530
+ }
1531
+ };
1532
+ var PageBuilder = class {
1533
+ constructor(parent, page) {
1534
+ this.parent = parent;
1535
+ this.page = page;
1536
+ this.components = [];
1537
+ }
1538
+ text(content, geometry = {}, style = {}) {
1539
+ this.components.push({
1540
+ kind: "text",
1541
+ leftMm: geometry.leftMm ?? 0,
1542
+ topMm: geometry.topMm ?? 0,
1543
+ widthMm: geometry.widthMm ?? 60,
1544
+ heightMm: geometry.heightMm ?? 8,
1545
+ text: content,
1546
+ style
1547
+ });
1548
+ return this;
1549
+ }
1550
+ list(dataset, geometry, columns, options = {}) {
1551
+ this.components.push({
1552
+ kind: "list",
1553
+ leftMm: geometry.leftMm ?? 10,
1554
+ topMm: geometry.topMm ?? 30,
1555
+ widthMm: geometry.widthMm ?? 180,
1556
+ dataset,
1557
+ columns,
1558
+ ...options
1559
+ });
1560
+ return this;
1561
+ }
1562
+ rect(geometry, style = {}) {
1563
+ this.components.push({ kind: "rect", ...geometry, style });
1564
+ return this;
1565
+ }
1566
+ line(leftMm, topMm, widthMm, style = {}) {
1567
+ this.components.push({
1568
+ kind: "line",
1569
+ leftMm,
1570
+ topMm,
1571
+ widthMm,
1572
+ heightMm: 0.5,
1573
+ style
1574
+ });
1575
+ return this;
1576
+ }
1577
+ barcode(content, geometry) {
1578
+ this.components.push({
1579
+ kind: "barcode",
1580
+ leftMm: geometry.leftMm ?? 0,
1581
+ topMm: geometry.topMm ?? 0,
1582
+ widthMm: geometry.widthMm ?? 40,
1583
+ heightMm: geometry.heightMm ?? 12,
1584
+ fallbackText: content
1585
+ });
1586
+ return this;
1587
+ }
1588
+ qrcode(content, sizeMm, leftMm, topMm) {
1589
+ this.components.push({
1590
+ kind: "qrcode",
1591
+ leftMm,
1592
+ topMm,
1593
+ widthMm: sizeMm,
1594
+ heightMm: sizeMm,
1595
+ fallbackText: content
1596
+ });
1597
+ return this;
1598
+ }
1599
+ buildPage() {
1600
+ return { ...this.page, components: this.components };
1601
+ }
1602
+ done() {
1603
+ return this.parent;
1604
+ }
1605
+ build() {
1606
+ return this.done().build();
1607
+ }
1608
+ };
1609
+ function createTemplate() {
1610
+ return new TemplateBuilder();
1611
+ }
1612
+
1613
+ exports.CSS_DPI = CSS_DPI;
1614
+ exports.DEFAULT_BORDER_PT = DEFAULT_BORDER_PT;
1615
+ exports.DEFAULT_FONT_PT = DEFAULT_FONT_PT;
1616
+ exports.DEFAULT_STROKE_COLOR = DEFAULT_STROKE_COLOR;
1617
+ exports.DEFAULT_TEXT_COLOR = DEFAULT_TEXT_COLOR;
1618
+ exports.LINE_HEIGHT = LINE_HEIGHT;
1619
+ exports.MM_PER_INCH = MM_PER_INCH;
1620
+ exports.PAPER_SIZES = PAPER_SIZES;
1621
+ exports.PageBuilder = PageBuilder;
1622
+ exports.RENDER_PLAN_SCHEMA_VERSION = RENDER_PLAN_SCHEMA_VERSION;
1623
+ exports.SCHEMA_VERSION_JH4J = SCHEMA_VERSION_JH4J;
1624
+ exports.SCHEMA_VERSION_LOCAL = SCHEMA_VERSION_LOCAL;
1625
+ exports.SCHEMA_VERSION_MACH = SCHEMA_VERSION_MACH;
1626
+ exports.TemplateBuilder = TemplateBuilder;
1627
+ exports.assertRenderPlan = assertRenderPlan;
1628
+ exports.charWidthEm = charWidthEm;
1629
+ exports.classOf = classOf;
1630
+ exports.computePageWindow = computePageWindow;
1631
+ exports.createCanvasMeasurer = createCanvasMeasurer;
1632
+ exports.createCanvasPager = createCanvasPager;
1633
+ exports.createHeuristicMeasurer = createHeuristicMeasurer;
1634
+ exports.createTemplate = createTemplate;
1635
+ exports.heuristicMeasurer = heuristicMeasurer;
1636
+ exports.importJh4jTemplateContent = importJh4jTemplateContent;
1637
+ exports.isImageBackedComponent = isImageBackedComponent;
1638
+ exports.isRichText = isRichText;
1639
+ exports.measurePageSizePx = measurePageSizePx;
1640
+ exports.measureTextMm = measureTextMm;
1641
+ exports.mmToPx = mmToPx;
1642
+ exports.mmToPxFactor = mmToPxFactor;
1643
+ exports.paginateTemplate = paginateTemplate;
1644
+ exports.planFontSizePx = planFontSizePx;
1645
+ exports.ptToPx = ptToPx;
1646
+ exports.pxToMm = pxToMm;
1647
+ exports.renderComponent = renderComponent;
1648
+ exports.renderPage = renderPage;
1649
+ exports.renderPlan = renderPlan;
1650
+ exports.renderPlanToCanvas = renderPlanToCanvas;
1651
+ exports.resolveBoxBorders = resolveBoxBorders;
1652
+ exports.resolveFontSize = resolveFontSize;
1653
+ exports.resolveGridLayout = resolveGridLayout;
1654
+ exports.resolveLineHeight = resolveLineHeight;
1655
+ exports.resolvePaper = resolvePaper;
1656
+ exports.resolveStroke = resolveStroke;
1657
+ exports.resolveTextColor = resolveTextColor;
1658
+ exports.round = round;
1659
+ exports.toRenderPlan = toRenderPlan;
1660
+ exports.validateComponent = validateComponent;
1661
+ exports.validatePage = validatePage;
1662
+ exports.validateRenderPlan = validateRenderPlan;
1663
+ exports.wrapText = wrapText;