@worm-vue3-print/core 1.2.2 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -7
- package/dist/browser/index.cjs +2338 -340
- package/dist/browser/index.d.cts +79 -6
- package/dist/browser/index.d.ts +79 -6
- package/dist/browser/index.js +283 -128
- package/dist/{chunk-HLCZHPUF.js → chunk-AKWV6SID.js} +1 -1
- package/dist/chunk-KFRPTLFV.js +3819 -0
- package/dist/client/index.cjs +381 -0
- package/dist/client/index.d.cts +241 -0
- package/dist/client/index.d.ts +241 -0
- package/dist/client/index.js +346 -0
- package/dist/designer/index.cjs +429 -22
- package/dist/designer/index.d.cts +100 -289
- package/dist/designer/index.d.ts +100 -289
- package/dist/designer/index.js +147 -26
- package/dist/dom-executor.iife.global.js +8 -0
- package/dist/driver-Dn_YAzO5.d.cts +935 -0
- package/dist/driver-Dn_YAzO5.d.ts +935 -0
- package/dist/index.cjs +2226 -163
- package/dist/index.d.cts +374 -6
- package/dist/index.d.ts +374 -6
- package/dist/index.js +180 -4
- package/dist/node/index.cjs +41 -0
- package/dist/node/index.d.cts +6 -0
- package/dist/node/index.d.ts +6 -0
- package/dist/node/index.js +10 -0
- package/dist/ports-BvwlA_km.d.ts +102 -0
- package/dist/ports-Cf5sjwls.d.cts +102 -0
- package/package.json +13 -2
- package/dist/chunk-JZIVNXZ7.js +0 -1777
- package/dist/types-BGBAbQD5.d.cts +0 -184
- package/dist/types-BGBAbQD5.d.ts +0 -184
package/dist/browser/index.cjs
CHANGED
|
@@ -30,11 +30,588 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/browser/index.ts
|
|
31
31
|
var browser_exports = {};
|
|
32
32
|
__export(browser_exports, {
|
|
33
|
+
EXECUTOR_VERSION: () => EXECUTOR_VERSION,
|
|
34
|
+
applyTextFit: () => applyTextFit,
|
|
33
35
|
browserCodeRenderer: () => browserCodeRenderer,
|
|
34
|
-
|
|
36
|
+
createBrowserPrintRuntime: () => createBrowserPrintRuntime,
|
|
37
|
+
createIframeDriverFactory: () => createIframeDriverFactory,
|
|
38
|
+
domExecutor: () => domExecutor,
|
|
39
|
+
fitTextNode: () => fitTextNode,
|
|
40
|
+
readContentBottom: () => readContentBottom,
|
|
41
|
+
readMeasurements: () => readMeasurements,
|
|
42
|
+
renderCodes: () => renderCodes,
|
|
43
|
+
renderHtmlPages: () => renderHtmlPages,
|
|
44
|
+
waitReady: () => waitReady
|
|
35
45
|
});
|
|
36
46
|
module.exports = __toCommonJS(browser_exports);
|
|
37
47
|
|
|
48
|
+
// src/print/errors.ts
|
|
49
|
+
var PrintFailure = class extends Error {
|
|
50
|
+
constructor(code, message, cause) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "PrintFailure";
|
|
53
|
+
this.code = code;
|
|
54
|
+
this.cause = cause;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
function toPrintFailure(err, fallbackCode, context) {
|
|
58
|
+
if (err instanceof PrintFailure) return err;
|
|
59
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
60
|
+
return new PrintFailure(fallbackCode, `${context}\uFF1A${detail}`, err);
|
|
61
|
+
}
|
|
62
|
+
async function withTimeout(task, ms, code, message) {
|
|
63
|
+
let timer;
|
|
64
|
+
try {
|
|
65
|
+
return await Promise.race([
|
|
66
|
+
task,
|
|
67
|
+
new Promise((_resolve, reject) => {
|
|
68
|
+
timer = setTimeout(() => reject(new PrintFailure(code, message)), ms);
|
|
69
|
+
})
|
|
70
|
+
]);
|
|
71
|
+
} finally {
|
|
72
|
+
if (timer) clearTimeout(timer);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/print/ports.ts
|
|
77
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
78
|
+
var DEFAULT_READINESS_MS = 5e3;
|
|
79
|
+
|
|
80
|
+
// src/print/dom-host-runtime.ts
|
|
81
|
+
function createDomHostRuntime(factory, bundle) {
|
|
82
|
+
return {
|
|
83
|
+
async withSession(options, fn) {
|
|
84
|
+
const driver = await factory.createDriver();
|
|
85
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
86
|
+
const readinessMs = options.readinessMs ?? DEFAULT_READINESS_MS;
|
|
87
|
+
const budget = () => Math.max(1, deadline - Date.now());
|
|
88
|
+
const fail = (err, code, context) => toPrintFailure(err, code, context);
|
|
89
|
+
let injected = false;
|
|
90
|
+
const ensureExecutor = async () => {
|
|
91
|
+
if (injected) return;
|
|
92
|
+
if (driver.requiresExecutor === false) {
|
|
93
|
+
injected = true;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!bundle) throw new PrintFailure("INTERNAL", "\u7F3A\u5C11 core DOM \u6267\u884C\u5668\u4EA7\u7269");
|
|
97
|
+
await driver.injectExecutor(bundle);
|
|
98
|
+
injected = true;
|
|
99
|
+
};
|
|
100
|
+
const load = async (html, viewport) => {
|
|
101
|
+
await driver.open(viewport);
|
|
102
|
+
await driver.setContent(html);
|
|
103
|
+
injected = false;
|
|
104
|
+
await ensureExecutor();
|
|
105
|
+
await driver.evaluate("waitReady", [readinessMs]);
|
|
106
|
+
};
|
|
107
|
+
const session = {
|
|
108
|
+
async renderCodes(specs) {
|
|
109
|
+
if (specs.length === 0) return /* @__PURE__ */ new Map();
|
|
110
|
+
const ms = budget();
|
|
111
|
+
return withTimeout(
|
|
112
|
+
(async () => {
|
|
113
|
+
try {
|
|
114
|
+
await ensureExecutor();
|
|
115
|
+
const map = await driver.evaluate("renderCodes", [specs]);
|
|
116
|
+
return new Map(Object.entries(map));
|
|
117
|
+
} catch (err) {
|
|
118
|
+
throw fail(err, "INTERNAL", "\u7801\u503C\u6E32\u67D3\u5931\u8D25");
|
|
119
|
+
}
|
|
120
|
+
})(),
|
|
121
|
+
ms,
|
|
122
|
+
"RENDER_TIMEOUT",
|
|
123
|
+
`\u7801\u503C\u6E32\u67D3\u8D85\u8FC7 ${ms}ms`
|
|
124
|
+
);
|
|
125
|
+
},
|
|
126
|
+
async measure(html, viewport) {
|
|
127
|
+
const ms = budget();
|
|
128
|
+
return withTimeout(
|
|
129
|
+
(async () => {
|
|
130
|
+
try {
|
|
131
|
+
await load(html, viewport);
|
|
132
|
+
const fits = await driver.evaluate("applyTextFit") ?? [];
|
|
133
|
+
const measurements = await driver.evaluate("readMeasurements");
|
|
134
|
+
return { measurements, fits };
|
|
135
|
+
} catch (err) {
|
|
136
|
+
throw fail(err, "MEASURE_FAILED", "\u6D4B\u91CF\u5931\u8D25");
|
|
137
|
+
}
|
|
138
|
+
})(),
|
|
139
|
+
ms,
|
|
140
|
+
"RENDER_TIMEOUT",
|
|
141
|
+
`\u6D4B\u91CF\u8D85\u8FC7 ${ms}ms`
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
async probeContentBottom(html, viewport) {
|
|
145
|
+
const ms = budget();
|
|
146
|
+
return withTimeout(
|
|
147
|
+
(async () => {
|
|
148
|
+
try {
|
|
149
|
+
await load(html, viewport);
|
|
150
|
+
return await driver.evaluate("readContentBottom");
|
|
151
|
+
} catch (err) {
|
|
152
|
+
throw fail(err, "MEASURE_FAILED", "\u8FDE\u7EED\u7EB8\u63A2\u9488\u5931\u8D25");
|
|
153
|
+
}
|
|
154
|
+
})(),
|
|
155
|
+
ms,
|
|
156
|
+
"RENDER_TIMEOUT",
|
|
157
|
+
`\u8FDE\u7EED\u7EB8\u63A2\u9488\u8D85\u8FC7 ${ms}ms`
|
|
158
|
+
);
|
|
159
|
+
},
|
|
160
|
+
async toPdf(html, spec, viewport) {
|
|
161
|
+
if (!driver.pdf) throw new PrintFailure("UNSUPPORTED_RUNTIME", "\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u751F\u6210 PDF");
|
|
162
|
+
const ms = budget();
|
|
163
|
+
return withTimeout(
|
|
164
|
+
(async () => {
|
|
165
|
+
try {
|
|
166
|
+
await load(html, viewport);
|
|
167
|
+
return await driver.pdf(html, spec);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
throw fail(err, "PDF_FAILED", "PDF \u751F\u6210\u5931\u8D25");
|
|
170
|
+
}
|
|
171
|
+
})(),
|
|
172
|
+
ms,
|
|
173
|
+
"RENDER_TIMEOUT",
|
|
174
|
+
`PDF \u751F\u6210\u8D85\u8FC7 ${ms}ms`
|
|
175
|
+
);
|
|
176
|
+
},
|
|
177
|
+
async toScreenshot(html, spec, viewport) {
|
|
178
|
+
if (!driver.screenshot) throw new PrintFailure("UNSUPPORTED_RUNTIME", "\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u622A\u56FE");
|
|
179
|
+
const ms = budget();
|
|
180
|
+
return withTimeout(
|
|
181
|
+
(async () => {
|
|
182
|
+
try {
|
|
183
|
+
await load(html, viewport);
|
|
184
|
+
return await driver.screenshot(html, spec);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
throw fail(err, "SCREENSHOT_FAILED", "\u622A\u56FE\u5931\u8D25");
|
|
187
|
+
}
|
|
188
|
+
})(),
|
|
189
|
+
ms,
|
|
190
|
+
"RENDER_TIMEOUT",
|
|
191
|
+
`\u622A\u56FE\u8D85\u8FC7 ${ms}ms`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
try {
|
|
196
|
+
return await fn(session);
|
|
197
|
+
} finally {
|
|
198
|
+
try {
|
|
199
|
+
await driver.close();
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/browser/browser-code-renderer.ts
|
|
208
|
+
var import_jsbarcode = __toESM(require("jsbarcode"), 1);
|
|
209
|
+
var import_qrcode = __toESM(require("qrcode"), 1);
|
|
210
|
+
|
|
211
|
+
// src/render/barcode-dot.ts
|
|
212
|
+
var MM_PER_INCH = 25.4;
|
|
213
|
+
var BARCODE_QUIET_ZONE_MODULES = 10;
|
|
214
|
+
var BARCODE_BAR_HEIGHT_MODULES = 30;
|
|
215
|
+
var BARCODE_TEXT_FONT_SIZE_MODULES = 10;
|
|
216
|
+
var BARCODE_MARGIN_BOTTOM_MODULES = 2;
|
|
217
|
+
var BARCODE_MODULE_WIDTH_MM = 0.25;
|
|
218
|
+
function barcodeUnitsPerModule(barWidth) {
|
|
219
|
+
const raw = typeof barWidth === "number" && Number.isFinite(barWidth) ? barWidth : 2;
|
|
220
|
+
return Math.max(1, raw / 2);
|
|
221
|
+
}
|
|
222
|
+
function barcodePreferredModuleWidthMm(barWidth) {
|
|
223
|
+
return barcodeUnitsPerModule(barWidth) * BARCODE_MODULE_WIDTH_MM;
|
|
224
|
+
}
|
|
225
|
+
function barcodeAvailableBoxMm(boxMm, maxMm) {
|
|
226
|
+
const box = typeof boxMm === "number" && boxMm > 0 ? boxMm : Number.POSITIVE_INFINITY;
|
|
227
|
+
const limit = typeof maxMm === "number" && maxMm > 0 ? Math.min(box, maxMm) : box;
|
|
228
|
+
return Number.isFinite(limit) ? limit : 0;
|
|
229
|
+
}
|
|
230
|
+
function resolveBarcodeSize(input) {
|
|
231
|
+
const unitWidth = Math.max(1, Math.ceil(input.unitWidth));
|
|
232
|
+
const unitHeight = Math.max(1, Math.ceil(input.unitHeight));
|
|
233
|
+
const preferredModuleMm = barcodePreferredModuleWidthMm(input.barWidth);
|
|
234
|
+
const maxModuleMm = Math.min(
|
|
235
|
+
input.boxWidthMm > 0 ? input.boxWidthMm / unitWidth : Number.POSITIVE_INFINITY,
|
|
236
|
+
input.boxHeightMm > 0 ? input.boxHeightMm / unitHeight : Number.POSITIVE_INFINITY
|
|
237
|
+
);
|
|
238
|
+
const dpi = input.dpi;
|
|
239
|
+
if (typeof dpi === "number" && Number.isFinite(dpi) && dpi > 0) {
|
|
240
|
+
const dotsPerMm = dpi / MM_PER_INCH;
|
|
241
|
+
const preferredDots = Math.max(1, Math.round(preferredModuleMm * dotsPerMm));
|
|
242
|
+
const maxDots = Math.floor(maxModuleMm * dotsPerMm);
|
|
243
|
+
if (maxDots >= 1) {
|
|
244
|
+
const dotsPerModule = Math.min(preferredDots, maxDots);
|
|
245
|
+
const moduleWidthMm2 = dotsPerModule / dotsPerMm;
|
|
246
|
+
return {
|
|
247
|
+
moduleWidthMm: moduleWidthMm2,
|
|
248
|
+
widthMm: unitWidth * moduleWidthMm2,
|
|
249
|
+
heightMm: unitHeight * moduleWidthMm2,
|
|
250
|
+
dotsPerModule,
|
|
251
|
+
scaledDown: dotsPerModule < preferredDots
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const moduleWidthMm = Math.min(preferredModuleMm, maxModuleMm);
|
|
256
|
+
return {
|
|
257
|
+
moduleWidthMm,
|
|
258
|
+
widthMm: unitWidth * moduleWidthMm,
|
|
259
|
+
heightMm: unitHeight * moduleWidthMm,
|
|
260
|
+
dotsPerModule: null,
|
|
261
|
+
scaledDown: moduleWidthMm < preferredModuleMm
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/browser/browser-code-renderer.ts
|
|
266
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
267
|
+
function readViewBox(svg) {
|
|
268
|
+
const raw = svg.getAttribute("viewBox");
|
|
269
|
+
if (!raw) return null;
|
|
270
|
+
const parts = raw.trim().split(/[\s,]+/).map(Number);
|
|
271
|
+
if (parts.length !== 4 || parts.some((v) => !Number.isFinite(v))) return null;
|
|
272
|
+
return { width: parts[2], height: parts[3] };
|
|
273
|
+
}
|
|
274
|
+
function renderBarcodeSvg(value, opts) {
|
|
275
|
+
const svg = document.createElementNS(SVG_NS, "svg");
|
|
276
|
+
const unitPerModule = barcodeUnitsPerModule(opts.barWidth);
|
|
277
|
+
(0, import_jsbarcode.default)(svg, value, {
|
|
278
|
+
format: opts.barcodeType || "CODE128",
|
|
279
|
+
width: unitPerModule,
|
|
280
|
+
height: BARCODE_BAR_HEIGHT_MODULES * unitPerModule,
|
|
281
|
+
displayValue: opts.showText !== false,
|
|
282
|
+
fontSize: (opts.fontSize ?? BARCODE_TEXT_FONT_SIZE_MODULES) * unitPerModule,
|
|
283
|
+
// 静区只留左右:上下留白会白白吃掉元素高度(jsbarcode 的 margin 是四边通配)
|
|
284
|
+
margin: 0,
|
|
285
|
+
marginLeft: BARCODE_QUIET_ZONE_MODULES * unitPerModule,
|
|
286
|
+
marginRight: BARCODE_QUIET_ZONE_MODULES * unitPerModule,
|
|
287
|
+
marginTop: 0,
|
|
288
|
+
marginBottom: BARCODE_MARGIN_BOTTOM_MODULES * unitPerModule
|
|
289
|
+
});
|
|
290
|
+
const viewBox = readViewBox(svg);
|
|
291
|
+
if (viewBox) {
|
|
292
|
+
const size = resolveBarcodeSize({
|
|
293
|
+
unitWidth: viewBox.width / unitPerModule,
|
|
294
|
+
unitHeight: viewBox.height / unitPerModule,
|
|
295
|
+
boxWidthMm: opts.targetWidthMm ?? 0,
|
|
296
|
+
boxHeightMm: opts.targetHeightMm ?? 0,
|
|
297
|
+
dpi: opts.printerDpi,
|
|
298
|
+
barWidth: opts.barWidth
|
|
299
|
+
});
|
|
300
|
+
svg.setAttribute("width", `${size.widthMm}mm`);
|
|
301
|
+
svg.setAttribute("height", `${size.heightMm}mm`);
|
|
302
|
+
}
|
|
303
|
+
svg.setAttribute("shape-rendering", "crispEdges");
|
|
304
|
+
return svg.outerHTML;
|
|
305
|
+
}
|
|
306
|
+
function renderQrSvg(value, opts) {
|
|
307
|
+
const level = (opts.qrCodeLevel ?? "M").toUpperCase();
|
|
308
|
+
const qr = import_qrcode.default.create(value, {
|
|
309
|
+
errorCorrectionLevel: ["L", "M", "Q", "H"].includes(level) ? level : "M"
|
|
310
|
+
});
|
|
311
|
+
const modules = qr.modules;
|
|
312
|
+
const size = modules.size;
|
|
313
|
+
const dot = 4;
|
|
314
|
+
const quiet = 4 * dot;
|
|
315
|
+
const dim = size * dot + quiet * 2;
|
|
316
|
+
let rects = "";
|
|
317
|
+
for (let y = 0; y < size; y++) {
|
|
318
|
+
for (let x = 0; x < size; x++) {
|
|
319
|
+
if (modules.get(x, y)) {
|
|
320
|
+
rects += `<rect x="${quiet + x * dot}" y="${quiet + y * dot}" width="${dot}" height="${dot}" fill="#000"/>`;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return `<svg xmlns="${SVG_NS}" viewBox="0 0 ${dim} ${dim}" width="${dim}" height="${dim}" shape-rendering="crispEdges">${rects}</svg>`;
|
|
325
|
+
}
|
|
326
|
+
function renderCodeSvg(value, cellType, opts = {}) {
|
|
327
|
+
if (!value) throw new Error("empty barcode value");
|
|
328
|
+
return cellType === "qrcode" ? renderQrSvg(value, opts) : renderBarcodeSvg(value, opts);
|
|
329
|
+
}
|
|
330
|
+
var browserCodeRenderer = {
|
|
331
|
+
render(value, cellType, opts = {}) {
|
|
332
|
+
return renderCodeSvg(value, cellType, opts);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/designer/utils/units.ts
|
|
337
|
+
function ptToMm(pt) {
|
|
338
|
+
return pt / 2.83464566929;
|
|
339
|
+
}
|
|
340
|
+
function mmToPx(mm2) {
|
|
341
|
+
return mm2 * (96 / 25.4);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/render/text-fit.ts
|
|
345
|
+
var DEFAULT_SHRINK_MIN_FONT_SIZE_PT = 6;
|
|
346
|
+
var MIN_SHRINK_FONT_SIZE_PT = 1;
|
|
347
|
+
var VALID_FITS = ["clip", "shrink", "autoHeight"];
|
|
348
|
+
function normalizeFit(value) {
|
|
349
|
+
return typeof value === "string" && VALID_FITS.includes(value) ? value : void 0;
|
|
350
|
+
}
|
|
351
|
+
var ELEMENT_DEFAULT_FIT = {
|
|
352
|
+
text: "clip",
|
|
353
|
+
longText: "autoHeight"
|
|
354
|
+
};
|
|
355
|
+
function resolveElementTextFit(type, opts) {
|
|
356
|
+
return normalizeFit(opts?.textFit) ?? ELEMENT_DEFAULT_FIT[type] ?? "clip";
|
|
357
|
+
}
|
|
358
|
+
function resolveCellTextFit(cell) {
|
|
359
|
+
return normalizeFit(cell?.textFit) ?? (cell?.wordWrap === false ? "clip" : "autoHeight");
|
|
360
|
+
}
|
|
361
|
+
function resolveShrinkMinFontSize(pt) {
|
|
362
|
+
if (typeof pt !== "number" || !Number.isFinite(pt) || pt <= 0) return DEFAULT_SHRINK_MIN_FONT_SIZE_PT;
|
|
363
|
+
return Math.max(pt, MIN_SHRINK_FONT_SIZE_PT);
|
|
364
|
+
}
|
|
365
|
+
function roundFontSize(pt) {
|
|
366
|
+
return Math.round(pt * 100) / 100;
|
|
367
|
+
}
|
|
368
|
+
function floorFontSize(pt) {
|
|
369
|
+
return Math.floor(pt * 100) / 100;
|
|
370
|
+
}
|
|
371
|
+
function cellFitKey(elementId, kind, rowIndex, colIndex) {
|
|
372
|
+
return `${elementId}#${kind}#${rowIndex}:${colIndex}`;
|
|
373
|
+
}
|
|
374
|
+
function parseCellFitKey(key) {
|
|
375
|
+
const first = key.indexOf("#");
|
|
376
|
+
if (first < 0) return void 0;
|
|
377
|
+
const last = key.lastIndexOf("#");
|
|
378
|
+
const kind = key.slice(first + 1, last);
|
|
379
|
+
if (kind !== "b" && kind !== "st" && kind !== "sm") return void 0;
|
|
380
|
+
const [rowIndex, colIndex] = key.slice(last + 1).split(":").map(Number);
|
|
381
|
+
if (!Number.isFinite(rowIndex) || !Number.isFinite(colIndex)) return void 0;
|
|
382
|
+
return { elementId: key.slice(0, first), kind, rowIndex, colIndex };
|
|
383
|
+
}
|
|
384
|
+
function cellFitWidthMm(colWidths, colIndex, cell, defaultPadding = 1) {
|
|
385
|
+
const span = Math.max(cell?.colspan ?? 1, 1);
|
|
386
|
+
let width = 0;
|
|
387
|
+
for (let i = 0; i < span; i++) {
|
|
388
|
+
width += colWidths[colIndex + i] ?? 0;
|
|
389
|
+
}
|
|
390
|
+
if (width <= 0) return 0;
|
|
391
|
+
const padding = cell?.padding ?? defaultPadding;
|
|
392
|
+
const borderMm = (ptToMm(cell?.borders?.left?.width ?? 0) + ptToMm(cell?.borders?.right?.width ?? 0)) / 2;
|
|
393
|
+
return Math.max(width - padding * 2 - borderMm, 0.5);
|
|
394
|
+
}
|
|
395
|
+
function cellFitCapMm(rows, rowIndex, cell, defaultPadding = 1) {
|
|
396
|
+
const span = Math.max(cell?.rowspan ?? 1, 1);
|
|
397
|
+
let height = 0;
|
|
398
|
+
for (let i = 0; i < span; i++) {
|
|
399
|
+
height += rows[rowIndex + i]?.height ?? 8;
|
|
400
|
+
}
|
|
401
|
+
const padding = cell?.padding ?? defaultPadding;
|
|
402
|
+
const borderMm = (ptToMm(cell?.borders?.top?.width ?? 0) + ptToMm(cell?.borders?.bottom?.width ?? 0)) / 2;
|
|
403
|
+
return Math.max(height - padding * 2 - borderMm, 0.5);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/browser/text-fit-dom.ts
|
|
407
|
+
var FIT_TOLERANCE_PX = 0.5;
|
|
408
|
+
var SEARCH_STEPS = 12;
|
|
409
|
+
function readAttrNumber(el, name) {
|
|
410
|
+
const raw = el.getAttribute(name);
|
|
411
|
+
if (raw === null || raw === "") return void 0;
|
|
412
|
+
const value = Number(raw);
|
|
413
|
+
return Number.isFinite(value) ? value : void 0;
|
|
414
|
+
}
|
|
415
|
+
function overflows(el, targetPx) {
|
|
416
|
+
if (el.scrollWidth > el.clientWidth + FIT_TOLERANCE_PX) return true;
|
|
417
|
+
const content = el.scrollHeight;
|
|
418
|
+
if (targetPx !== void 0) return content > targetPx + FIT_TOLERANCE_PX;
|
|
419
|
+
return content > el.clientHeight + FIT_TOLERANCE_PX;
|
|
420
|
+
}
|
|
421
|
+
function fitTextNode(el) {
|
|
422
|
+
if (el.getAttribute("data-fit") !== "shrink") return void 0;
|
|
423
|
+
const minPt = resolveShrinkMinFontSize(readAttrNumber(el, "data-fit-min"));
|
|
424
|
+
const start = Math.max(readAttrNumber(el, "data-fit-base") ?? minPt, minPt);
|
|
425
|
+
const targetMm = readAttrNumber(el, "data-fit-mm");
|
|
426
|
+
const targetPx = targetMm !== void 0 && targetMm > 0 ? mmToPx(targetMm) : void 0;
|
|
427
|
+
const apply = (pt) => {
|
|
428
|
+
const size = floorFontSize(pt);
|
|
429
|
+
el.style.fontSize = `${size}pt`;
|
|
430
|
+
el.setAttribute("data-fit-size", String(size));
|
|
431
|
+
return size;
|
|
432
|
+
};
|
|
433
|
+
const base = apply(start);
|
|
434
|
+
if (!overflows(el, targetPx)) return base;
|
|
435
|
+
if (start <= minPt) return base;
|
|
436
|
+
let lo = minPt;
|
|
437
|
+
let hi = start;
|
|
438
|
+
for (let i = 0; i < SEARCH_STEPS; i++) {
|
|
439
|
+
const mid = (lo + hi) / 2;
|
|
440
|
+
apply(mid);
|
|
441
|
+
if (overflows(el, targetPx)) hi = mid;
|
|
442
|
+
else lo = mid;
|
|
443
|
+
}
|
|
444
|
+
return apply(lo);
|
|
445
|
+
}
|
|
446
|
+
function applyTextFit(doc) {
|
|
447
|
+
const fits = [];
|
|
448
|
+
doc.querySelectorAll('[data-fit="shrink"]').forEach((el) => {
|
|
449
|
+
const size = fitTextNode(el);
|
|
450
|
+
const key = el.getAttribute("data-fit-key");
|
|
451
|
+
if (size === void 0 || !key) return;
|
|
452
|
+
fits.push({ key, fontSizePt: size });
|
|
453
|
+
});
|
|
454
|
+
return fits;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/browser/dom-executor.ts
|
|
458
|
+
var EXECUTOR_VERSION = "2";
|
|
459
|
+
var DEFAULT_READY_TIMEOUT_MS = 5e3;
|
|
460
|
+
async function waitReady(win, timeoutMs = DEFAULT_READY_TIMEOUT_MS) {
|
|
461
|
+
const doc = win.document;
|
|
462
|
+
const wait = (task) => Promise.race([task, new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
|
|
463
|
+
const ready = (async () => {
|
|
464
|
+
if (doc.readyState !== "complete") {
|
|
465
|
+
await wait(new Promise((resolve) => win.addEventListener("load", () => resolve(), { once: true })));
|
|
466
|
+
}
|
|
467
|
+
const fonts = doc.fonts;
|
|
468
|
+
if (fonts) {
|
|
469
|
+
await wait(Promise.all(declaredFontFaces(doc).map(
|
|
470
|
+
(face) => fonts.load(`${face.style} ${face.weight} 16px "${face.family}"`).catch(() => void 0)
|
|
471
|
+
)));
|
|
472
|
+
if (fonts.ready) await wait(fonts.ready);
|
|
473
|
+
}
|
|
474
|
+
await wait(Promise.all(Array.from(doc.images ?? []).map(
|
|
475
|
+
(img) => img.complete ? Promise.resolve() : new Promise((resolve) => {
|
|
476
|
+
img.addEventListener("load", () => resolve(), { once: true });
|
|
477
|
+
img.addEventListener("error", () => resolve(), { once: true });
|
|
478
|
+
})
|
|
479
|
+
)));
|
|
480
|
+
})();
|
|
481
|
+
await wait(ready);
|
|
482
|
+
}
|
|
483
|
+
function declaredFontFaces(doc) {
|
|
484
|
+
const out = [];
|
|
485
|
+
const CSS_FONT_FACE_RULE = 5;
|
|
486
|
+
for (const sheet of Array.from(doc.styleSheets ?? [])) {
|
|
487
|
+
let rules;
|
|
488
|
+
try {
|
|
489
|
+
rules = sheet.cssRules;
|
|
490
|
+
} catch {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
for (const rule of Array.from(rules ?? [])) {
|
|
494
|
+
if (rule.type !== CSS_FONT_FACE_RULE) continue;
|
|
495
|
+
const style = rule.style;
|
|
496
|
+
const family = (style.getPropertyValue("font-family") || "").replace(/^["']|["']$/g, "");
|
|
497
|
+
if (!family) continue;
|
|
498
|
+
out.push({
|
|
499
|
+
family,
|
|
500
|
+
weight: style.getPropertyValue("font-weight") || "400",
|
|
501
|
+
style: style.getPropertyValue("font-style") || "normal"
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return out;
|
|
506
|
+
}
|
|
507
|
+
function readMeasurements(doc) {
|
|
508
|
+
const result = [];
|
|
509
|
+
doc.querySelectorAll("[data-measure-id]").forEach((node) => {
|
|
510
|
+
const el = node;
|
|
511
|
+
const id = el.getAttribute("data-measure-id");
|
|
512
|
+
if (!id) return;
|
|
513
|
+
const heightPx = el.getBoundingClientRect().height;
|
|
514
|
+
const table = el.querySelector("table.print-table");
|
|
515
|
+
if (!table) {
|
|
516
|
+
result.push({ id, heightPx });
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const rowHeightsPx = [];
|
|
520
|
+
table.querySelectorAll("tbody > tr[data-row-index]").forEach((row) => {
|
|
521
|
+
rowHeightsPx.push(row.getBoundingClientRect().height);
|
|
522
|
+
});
|
|
523
|
+
result.push({ id, heightPx, rowHeightsPx });
|
|
524
|
+
});
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
function readContentBottom(doc) {
|
|
528
|
+
const page = doc.querySelector(".print-page");
|
|
529
|
+
const area = doc.querySelector(".content-area");
|
|
530
|
+
if (!page || !area) return 0;
|
|
531
|
+
const pageTop = page.getBoundingClientRect().top;
|
|
532
|
+
let maxBottom = 0;
|
|
533
|
+
area.querySelectorAll("*").forEach((node) => {
|
|
534
|
+
const rect = node.getBoundingClientRect();
|
|
535
|
+
if (rect.height > 0) maxBottom = Math.max(maxBottom, rect.bottom - pageTop);
|
|
536
|
+
});
|
|
537
|
+
const areaRect = area.getBoundingClientRect();
|
|
538
|
+
if (areaRect.height > 0) maxBottom = Math.max(maxBottom, areaRect.bottom - pageTop);
|
|
539
|
+
return maxBottom;
|
|
540
|
+
}
|
|
541
|
+
function renderCodes(specs) {
|
|
542
|
+
const map = {};
|
|
543
|
+
for (const spec of specs) {
|
|
544
|
+
try {
|
|
545
|
+
map[spec.key] = renderCodeSvg(spec.value, spec.cellType, spec.opts);
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return map;
|
|
550
|
+
}
|
|
551
|
+
var domExecutor = {
|
|
552
|
+
version: EXECUTOR_VERSION,
|
|
553
|
+
waitReady,
|
|
554
|
+
readMeasurements,
|
|
555
|
+
readContentBottom,
|
|
556
|
+
renderCodes,
|
|
557
|
+
/** 自动缩小(data-fit="shrink"):须在 readMeasurements 之前调用,返回需回写的字号清单 */
|
|
558
|
+
applyTextFit
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// src/print/driver.ts
|
|
562
|
+
var EXECUTOR_TARGETS = {
|
|
563
|
+
waitReady: "window",
|
|
564
|
+
readMeasurements: "document",
|
|
565
|
+
readContentBottom: "document",
|
|
566
|
+
renderCodes: "none",
|
|
567
|
+
applyTextFit: "document"
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// src/browser/driver-iframe.ts
|
|
571
|
+
function createIframeDriverFactory() {
|
|
572
|
+
return {
|
|
573
|
+
async createDriver() {
|
|
574
|
+
const iframe = document.createElement("iframe");
|
|
575
|
+
iframe.setAttribute("aria-hidden", "true");
|
|
576
|
+
iframe.style.cssText = "position:fixed;left:-10000px;top:0;border:0;visibility:hidden;pointer-events:none;";
|
|
577
|
+
document.body.appendChild(iframe);
|
|
578
|
+
let doc = iframe.contentDocument ?? document;
|
|
579
|
+
return {
|
|
580
|
+
requiresExecutor: false,
|
|
581
|
+
async open(viewport) {
|
|
582
|
+
iframe.style.width = `${viewport.width}px`;
|
|
583
|
+
iframe.style.height = `${viewport.height}px`;
|
|
584
|
+
doc = iframe.contentDocument ?? document;
|
|
585
|
+
},
|
|
586
|
+
async setContent(html) {
|
|
587
|
+
doc = iframe.contentDocument ?? document;
|
|
588
|
+
doc.open();
|
|
589
|
+
doc.write(html);
|
|
590
|
+
doc.close();
|
|
591
|
+
},
|
|
592
|
+
async injectExecutor(_bundle) {
|
|
593
|
+
},
|
|
594
|
+
async evaluate(method, args) {
|
|
595
|
+
const fn = domExecutor[method];
|
|
596
|
+
const payload = args ?? [];
|
|
597
|
+
const target = EXECUTOR_TARGETS[method];
|
|
598
|
+
if (target === "window") return fn(iframe.contentWindow, ...payload);
|
|
599
|
+
if (target === "document") return fn(doc, ...payload);
|
|
600
|
+
return fn(...payload);
|
|
601
|
+
},
|
|
602
|
+
async close() {
|
|
603
|
+
iframe.remove();
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/browser/browser-runtime.ts
|
|
611
|
+
function createBrowserPrintRuntime() {
|
|
612
|
+
return createDomHostRuntime(createIframeDriverFactory());
|
|
613
|
+
}
|
|
614
|
+
|
|
38
615
|
// src/lexer.ts
|
|
39
616
|
var DANGEROUS_PROPS = /* @__PURE__ */ new Set([
|
|
40
617
|
"constructor",
|
|
@@ -377,6 +954,113 @@ var Parser = class {
|
|
|
377
954
|
}
|
|
378
955
|
};
|
|
379
956
|
|
|
957
|
+
// src/numeric.ts
|
|
958
|
+
var MAX_DIGITS = 20;
|
|
959
|
+
var EXTRA_SCALE = 8;
|
|
960
|
+
function toNumber(value, fallback = 0) {
|
|
961
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : fallback;
|
|
962
|
+
if (typeof value === "string") {
|
|
963
|
+
const s = value.trim();
|
|
964
|
+
if (s === "") return fallback;
|
|
965
|
+
const n = Number(s);
|
|
966
|
+
return Number.isFinite(n) ? n : fallback;
|
|
967
|
+
}
|
|
968
|
+
if (value == null) return fallback;
|
|
969
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
970
|
+
return fallback;
|
|
971
|
+
}
|
|
972
|
+
function isNumericLike(value) {
|
|
973
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
974
|
+
if (typeof value === "string") {
|
|
975
|
+
const s = value.trim();
|
|
976
|
+
return s !== "" && Number.isFinite(Number(s));
|
|
977
|
+
}
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
function normalizeFloat(value) {
|
|
981
|
+
if (!Number.isFinite(value) || Number.isInteger(value)) return value;
|
|
982
|
+
if (Math.abs(value) >= 1e15) return value;
|
|
983
|
+
return Number(value.toPrecision(12));
|
|
984
|
+
}
|
|
985
|
+
function decimalsOf(n) {
|
|
986
|
+
if (!Number.isFinite(n) || Number.isInteger(n)) return 0;
|
|
987
|
+
const s = String(n);
|
|
988
|
+
if (s.includes("e") || s.includes("E")) return 0;
|
|
989
|
+
const i = s.indexOf(".");
|
|
990
|
+
return i === -1 ? 0 : s.length - i - 1;
|
|
991
|
+
}
|
|
992
|
+
function rescale(value, decimals) {
|
|
993
|
+
if (!Number.isFinite(value)) return 0;
|
|
994
|
+
const d = Math.min(Math.max(decimals, 0), 12);
|
|
995
|
+
if (d === 0) return Math.round(value);
|
|
996
|
+
const f = 10 ** d;
|
|
997
|
+
const scaled = value * f;
|
|
998
|
+
if (!Number.isFinite(scaled) || Math.abs(scaled) >= 1e15) return normalizeFloat(value);
|
|
999
|
+
return Math.round(scaled) / f;
|
|
1000
|
+
}
|
|
1001
|
+
function decimalAdd(left, right) {
|
|
1002
|
+
const a = toNumber(left);
|
|
1003
|
+
const b = toNumber(right);
|
|
1004
|
+
const raw = a + b;
|
|
1005
|
+
return rescale(raw, Math.max(decimalsOf(a), decimalsOf(b)));
|
|
1006
|
+
}
|
|
1007
|
+
function decimalSubtract(left, right) {
|
|
1008
|
+
const a = toNumber(left);
|
|
1009
|
+
const b = toNumber(right);
|
|
1010
|
+
const raw = a - b;
|
|
1011
|
+
return rescale(raw, Math.max(decimalsOf(a), decimalsOf(b)));
|
|
1012
|
+
}
|
|
1013
|
+
function decimalMultiply(left, right) {
|
|
1014
|
+
const a = toNumber(left);
|
|
1015
|
+
const b = toNumber(right);
|
|
1016
|
+
const raw = a * b;
|
|
1017
|
+
return rescale(raw, decimalsOf(a) + decimalsOf(b));
|
|
1018
|
+
}
|
|
1019
|
+
function decimalDivide(left, right) {
|
|
1020
|
+
const a = toNumber(left);
|
|
1021
|
+
const b = toNumber(right);
|
|
1022
|
+
if (b === 0) return 0;
|
|
1023
|
+
return normalizeFloat(a / b);
|
|
1024
|
+
}
|
|
1025
|
+
function decimalModulo(left, right) {
|
|
1026
|
+
const a = toNumber(left);
|
|
1027
|
+
const b = toNumber(right);
|
|
1028
|
+
if (b === 0) return 0;
|
|
1029
|
+
return normalizeFloat(a % b);
|
|
1030
|
+
}
|
|
1031
|
+
function normalizeDigits(digits) {
|
|
1032
|
+
if (digits === void 0 || digits === null) return 2;
|
|
1033
|
+
const d = Math.trunc(toNumber(digits, 2));
|
|
1034
|
+
if (!Number.isFinite(d)) return 2;
|
|
1035
|
+
return Math.min(Math.max(d, -MAX_DIGITS), MAX_DIGITS);
|
|
1036
|
+
}
|
|
1037
|
+
function roundTo(value, digits, mode) {
|
|
1038
|
+
const n = toNumber(value, 0);
|
|
1039
|
+
const d = normalizeDigits(digits);
|
|
1040
|
+
if (n === 0) return 0;
|
|
1041
|
+
if (Math.abs(n) >= 1e21) return normalizeFloat(n);
|
|
1042
|
+
const scale = Math.max(d, 0) + EXTRA_SCALE;
|
|
1043
|
+
const fixed = n.toFixed(scale);
|
|
1044
|
+
if (!/^-?\d+(\.\d+)?$/.test(fixed)) return normalizeFloat(n);
|
|
1045
|
+
const negative = fixed.startsWith("-");
|
|
1046
|
+
const digitsStr = fixed.replace("-", "").replace(".", "");
|
|
1047
|
+
let scaled = BigInt(digitsStr || "0");
|
|
1048
|
+
if (negative) scaled = -scaled;
|
|
1049
|
+
const pow10 = 10n ** BigInt(scale - d);
|
|
1050
|
+
const sign = scaled < 0n ? -1n : 1n;
|
|
1051
|
+
const abs = scaled < 0n ? -scaled : scaled;
|
|
1052
|
+
let q = abs / pow10;
|
|
1053
|
+
const r = abs % pow10;
|
|
1054
|
+
const doubled = 2n * r;
|
|
1055
|
+
if (mode === "up") {
|
|
1056
|
+
if (r !== 0n) q += 1n;
|
|
1057
|
+
} else if (mode !== "down" && doubled >= pow10) {
|
|
1058
|
+
if (doubled > pow10 || mode === "half-up" || q % 2n === 1n) q += 1n;
|
|
1059
|
+
}
|
|
1060
|
+
const result = sign * q;
|
|
1061
|
+
return normalizeFloat(d >= 0 ? Number(result) / 10 ** d : Number(result) * 10 ** -d);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
380
1064
|
// src/evaluator.ts
|
|
381
1065
|
var SAFE_GLOBALS = {
|
|
382
1066
|
Math,
|
|
@@ -442,16 +1126,17 @@ function evalBinary(node, context, functions) {
|
|
|
442
1126
|
const left = evaluate(node.left, context, functions);
|
|
443
1127
|
const right = evaluate(node.right, context, functions);
|
|
444
1128
|
switch (node.op) {
|
|
1129
|
+
// 数值运算统一走十进制精确实现:消除 0.1 + 0.2 类浮点噪声、字符串数字按数值处理、除零兜底 0
|
|
445
1130
|
case "+":
|
|
446
|
-
return left
|
|
1131
|
+
return isNumericOperands(left, right) ? decimalAdd(left, right) : concatOperands(left, right);
|
|
447
1132
|
case "-":
|
|
448
|
-
return left
|
|
1133
|
+
return decimalSubtract(left, right);
|
|
449
1134
|
case "*":
|
|
450
|
-
return left
|
|
1135
|
+
return decimalMultiply(left, right);
|
|
451
1136
|
case "/":
|
|
452
|
-
return left
|
|
1137
|
+
return decimalDivide(left, right);
|
|
453
1138
|
case "%":
|
|
454
|
-
return left
|
|
1139
|
+
return decimalModulo(left, right);
|
|
455
1140
|
case "<":
|
|
456
1141
|
return left < right;
|
|
457
1142
|
case ">":
|
|
@@ -480,15 +1165,29 @@ function evalUnary(node, context, functions) {
|
|
|
480
1165
|
const arg = evaluate(node.arg, context, functions);
|
|
481
1166
|
switch (node.op) {
|
|
482
1167
|
case "-":
|
|
483
|
-
return
|
|
1168
|
+
return decimalSubtract(0, arg);
|
|
484
1169
|
case "+":
|
|
485
|
-
return
|
|
1170
|
+
return toNumber(arg);
|
|
486
1171
|
case "!":
|
|
487
1172
|
return !arg;
|
|
488
1173
|
default:
|
|
489
1174
|
throw new Error(`\u672A\u77E5\u4E00\u5143\u8FD0\u7B97\u7B26: ${node.op}`);
|
|
490
1175
|
}
|
|
491
1176
|
}
|
|
1177
|
+
function concatOperands(left, right) {
|
|
1178
|
+
return `${left == null ? "" : String(left)}${right == null ? "" : String(right)}`;
|
|
1179
|
+
}
|
|
1180
|
+
function isNumericOperands(left, right) {
|
|
1181
|
+
const l = operandKind(left);
|
|
1182
|
+
const r = operandKind(right);
|
|
1183
|
+
if (l === "other" || r === "other") return false;
|
|
1184
|
+
return l === "num" || r === "num";
|
|
1185
|
+
}
|
|
1186
|
+
function operandKind(value) {
|
|
1187
|
+
if (value == null || value === "") return "neutral";
|
|
1188
|
+
if (isNumericLike(value)) return "num";
|
|
1189
|
+
return "other";
|
|
1190
|
+
}
|
|
492
1191
|
function evalMember(node, context, functions) {
|
|
493
1192
|
const obj = evaluate(node.object, context, functions);
|
|
494
1193
|
if (obj == null) {
|
|
@@ -608,8 +1307,8 @@ function formatDate(value, fmt) {
|
|
|
608
1307
|
return String(value);
|
|
609
1308
|
}
|
|
610
1309
|
if (isNaN(date.getTime())) return String(value);
|
|
611
|
-
const
|
|
612
|
-
return fmt.replace("YYYY", String(date.getFullYear())).replace("MM",
|
|
1310
|
+
const pad3 = (n) => String(n).padStart(2, "0");
|
|
1311
|
+
return fmt.replace("YYYY", String(date.getFullYear())).replace("MM", pad3(date.getMonth() + 1)).replace("DD", pad3(date.getDate())).replace("HH", pad3(date.getHours())).replace("mm", pad3(date.getMinutes())).replace("ss", pad3(date.getSeconds()));
|
|
613
1312
|
}
|
|
614
1313
|
function toUpperCaseAmount(value) {
|
|
615
1314
|
const num = Number(value);
|
|
@@ -700,6 +1399,34 @@ function max(rows, field) {
|
|
|
700
1399
|
return Math.max(...rows.map((row) => Number(getByPath(row, field)) || 0));
|
|
701
1400
|
}
|
|
702
1401
|
|
|
1402
|
+
// src/functions/math.ts
|
|
1403
|
+
function addNumbers(...values) {
|
|
1404
|
+
return values.reduce((acc, v) => decimalAdd(acc, v), 0);
|
|
1405
|
+
}
|
|
1406
|
+
function subtractNumbers(...values) {
|
|
1407
|
+
if (values.length === 0) return 0;
|
|
1408
|
+
return values.slice(1).reduce((acc, v) => decimalSubtract(acc, v), decimalAdd(values[0], 0));
|
|
1409
|
+
}
|
|
1410
|
+
function multiplyNumbers(...values) {
|
|
1411
|
+
if (values.length === 0) return 0;
|
|
1412
|
+
return values.reduce((acc, v) => decimalMultiply(acc, v), 1);
|
|
1413
|
+
}
|
|
1414
|
+
function divideNumbers(a, b) {
|
|
1415
|
+
return decimalDivide(a, b);
|
|
1416
|
+
}
|
|
1417
|
+
function round(value, digits) {
|
|
1418
|
+
return roundTo(value, digits, "half-up");
|
|
1419
|
+
}
|
|
1420
|
+
function roundUp(value, digits) {
|
|
1421
|
+
return roundTo(value, digits, "up");
|
|
1422
|
+
}
|
|
1423
|
+
function roundDown(value, digits) {
|
|
1424
|
+
return roundTo(value, digits, "down");
|
|
1425
|
+
}
|
|
1426
|
+
function roundHalfEven(value, digits) {
|
|
1427
|
+
return roundTo(value, digits, "half-even");
|
|
1428
|
+
}
|
|
1429
|
+
|
|
703
1430
|
// src/render/expression-eval.ts
|
|
704
1431
|
var RenderEngine = class {
|
|
705
1432
|
constructor() {
|
|
@@ -725,8 +1452,19 @@ var FORMAT_FUNCTIONS = {
|
|
|
725
1452
|
IF: ifFn,
|
|
726
1453
|
CONCAT: (...args) => args.filter((v) => v != null).map(String).join(""),
|
|
727
1454
|
IFEMPTY: (v, d) => v != null && v !== "" ? String(v) : d,
|
|
728
|
-
|
|
729
|
-
|
|
1455
|
+
LEN: (s) => String(s).length,
|
|
1456
|
+
// 四则运算(与 +/-/*// 运算符同一套数值语义,供不方便写运算符的场景使用)
|
|
1457
|
+
ADD: addNumbers,
|
|
1458
|
+
SUB: subtractNumbers,
|
|
1459
|
+
MUL: multiplyNumbers,
|
|
1460
|
+
DIV: divideNumbers,
|
|
1461
|
+
// 数值修约
|
|
1462
|
+
ROUND: round,
|
|
1463
|
+
ROUNDUP: roundUp,
|
|
1464
|
+
CEIL: roundUp,
|
|
1465
|
+
ROUNDDOWN: roundDown,
|
|
1466
|
+
FLOOR: roundDown,
|
|
1467
|
+
ROUNDBANK: roundHalfEven
|
|
730
1468
|
};
|
|
731
1469
|
for (const [name, fn] of Object.entries(FORMAT_FUNCTIONS)) {
|
|
732
1470
|
engine.registerFunction(name, fn);
|
|
@@ -747,6 +1485,9 @@ function aggregate(fn, field, rows) {
|
|
|
747
1485
|
return 0;
|
|
748
1486
|
}
|
|
749
1487
|
}
|
|
1488
|
+
function safeEval(expr, context) {
|
|
1489
|
+
return engine.evaluate(expr, context);
|
|
1490
|
+
}
|
|
750
1491
|
function evaluateTemplate(text, ctx) {
|
|
751
1492
|
if (!text) return "";
|
|
752
1493
|
if (!text.includes("{")) return text;
|
|
@@ -767,9 +1508,8 @@ function evaluateTemplate(text, ctx) {
|
|
|
767
1508
|
}
|
|
768
1509
|
|
|
769
1510
|
// src/render/data-binder.ts
|
|
770
|
-
function bindData(template, printData, baseUrl) {
|
|
771
|
-
const
|
|
772
|
-
const data = Array.isArray(raw) ? raw[0] ?? {} : raw;
|
|
1511
|
+
function bindData(template, printData, baseUrl, fontBaseUrl) {
|
|
1512
|
+
const data = { ...resolveSystemVariables(), ...printData ?? {} };
|
|
773
1513
|
const bound = JSON.parse(JSON.stringify(template));
|
|
774
1514
|
if (bound.header?.elements) {
|
|
775
1515
|
bound.header.elements = bound.header.elements.map((el) => bindElement(el, data, baseUrl));
|
|
@@ -781,12 +1521,29 @@ function bindData(template, printData, baseUrl) {
|
|
|
781
1521
|
bound.firstPageOverlay.elements = bound.firstPageOverlay.elements.map((el) => bindElement(el, data, baseUrl));
|
|
782
1522
|
}
|
|
783
1523
|
bound.elements = bound.elements.map((el) => bindElement(el, data, baseUrl));
|
|
1524
|
+
const fontBase = fontBaseUrl ?? baseUrl;
|
|
1525
|
+
if (fontBase && bound.fonts?.length) {
|
|
1526
|
+
const prefix = fontBase.replace(/\/+$/, "");
|
|
1527
|
+
bound.fonts = bound.fonts.map((font) => ({
|
|
1528
|
+
...font,
|
|
1529
|
+
files: (font.files ?? []).map((file) => ({
|
|
1530
|
+
...file,
|
|
1531
|
+
// 绝对 URL(含协议相对 //)原样使用
|
|
1532
|
+
url: file.url?.startsWith("/") && !file.url.startsWith("//") ? prefix + file.url : file.url
|
|
1533
|
+
}))
|
|
1534
|
+
}));
|
|
1535
|
+
}
|
|
784
1536
|
return bound;
|
|
785
1537
|
}
|
|
786
1538
|
function bindElement(el, data, baseUrl) {
|
|
787
1539
|
const cloned = { ...el, options: { ...el.options } };
|
|
788
1540
|
if (typeof cloned.options.formatter === "string") {
|
|
789
|
-
|
|
1541
|
+
const raw = cloned.options.formatter;
|
|
1542
|
+
if (referencesPageNumbers(raw)) {
|
|
1543
|
+
cloned.options.rawFormatter = raw;
|
|
1544
|
+
} else {
|
|
1545
|
+
cloned.options.formatter = evaluateTemplate(raw, data);
|
|
1546
|
+
}
|
|
790
1547
|
}
|
|
791
1548
|
const isImage = cloned.type === "image" || cloned.printElementType?.type === "image";
|
|
792
1549
|
if (isImage && typeof cloned.options.src === "string") {
|
|
@@ -833,39 +1590,25 @@ function bindTableData(el, data) {
|
|
|
833
1590
|
if (dataStartIdx < 0) dataStartIdx = renderRows.length;
|
|
834
1591
|
const ctx = itemCtx(item);
|
|
835
1592
|
renderRows.push(makeRenderRow(row, (cell) => {
|
|
836
|
-
|
|
837
|
-
if (!formatter) return "";
|
|
838
|
-
return evaluateTemplate(formatter, ctx);
|
|
1593
|
+
return resolveCellText(cell, ctx);
|
|
839
1594
|
}));
|
|
840
1595
|
dataRowCtx.push(ctx);
|
|
841
1596
|
}
|
|
842
1597
|
continue;
|
|
843
1598
|
}
|
|
844
1599
|
if (mode === "dynamic" && row.type === "subtotal") {
|
|
845
|
-
const tpl = makeRenderRow(row, (cell) => {
|
|
846
|
-
const formatter = cell.formatter;
|
|
847
|
-
if (!formatter) return "";
|
|
848
|
-
return evaluateTemplate(formatter, { rows: summaryRows, ...data });
|
|
849
|
-
}, true);
|
|
1600
|
+
const tpl = makeRenderRow(row, (cell) => resolveCellText(cell, { rows: summaryRows, ...data }), true);
|
|
850
1601
|
subtotalTemplates.push(tpl);
|
|
851
1602
|
renderRows.push(tpl);
|
|
852
1603
|
continue;
|
|
853
1604
|
}
|
|
854
1605
|
if (mode === "dynamic" && row.type === "summary") {
|
|
855
|
-
const summaryRow = makeRenderRow(row, (cell) => {
|
|
856
|
-
const formatter = cell.formatter;
|
|
857
|
-
if (!formatter) return "";
|
|
858
|
-
return evaluateTemplate(formatter, { rows: summaryRows, ...data });
|
|
859
|
-
});
|
|
1606
|
+
const summaryRow = makeRenderRow(row, (cell) => resolveCellText(cell, { rows: summaryRows, ...data }));
|
|
860
1607
|
summaryRenderRows.push(summaryRow);
|
|
861
1608
|
renderRows.push(summaryRow);
|
|
862
1609
|
continue;
|
|
863
1610
|
}
|
|
864
|
-
renderRows.push(makeRenderRow(row, (cell) =>
|
|
865
|
-
const formatter = cell.formatter;
|
|
866
|
-
if (!formatter) return "";
|
|
867
|
-
return evaluateTemplate(formatter, data);
|
|
868
|
-
}));
|
|
1611
|
+
renderRows.push(makeRenderRow(row, (cell) => resolveCellText(cell, data)));
|
|
869
1612
|
}
|
|
870
1613
|
opts._renderRows = renderRows;
|
|
871
1614
|
opts._repeatHeaderCount = countRepeatHeader(rows);
|
|
@@ -875,17 +1618,25 @@ function bindTableData(el, data) {
|
|
|
875
1618
|
opts._summaryRows = summaryRenderRows;
|
|
876
1619
|
opts._mainData = data;
|
|
877
1620
|
}
|
|
1621
|
+
function resolveCellText(cell, ctx) {
|
|
1622
|
+
const formatter = cell.formatter;
|
|
1623
|
+
if (!formatter) return "";
|
|
1624
|
+
if (referencesPageNumbers(formatter)) return formatter;
|
|
1625
|
+
return evaluateTemplate(formatter, ctx);
|
|
1626
|
+
}
|
|
878
1627
|
function makeRenderRow(row, resolve, keepRaw = false) {
|
|
879
1628
|
return {
|
|
880
1629
|
type: row.type,
|
|
881
1630
|
height: row.height ?? 8,
|
|
882
1631
|
cells: row.cells.map((cell) => ({
|
|
883
1632
|
content: cell.merged ? "" : resolve(cell),
|
|
884
|
-
|
|
1633
|
+
// 小计行(keepRaw)与引用页码的单元格都保留原始表达式:前者按当页数据行重算,后者按当页页码重算
|
|
1634
|
+
...(keepRaw || referencesPageNumbers(cell.formatter)) && !cell.merged ? { rawFormatter: cell.formatter ?? "" } : {},
|
|
885
1635
|
cellType: cell.cellType,
|
|
886
1636
|
barcodeType: cell.barcodeType,
|
|
887
1637
|
qrCodeLevel: cell.qrCodeLevel,
|
|
888
1638
|
showBarcodeText: cell.showBarcodeText,
|
|
1639
|
+
printerDpi: cell.printerDpi,
|
|
889
1640
|
fit: cell.fit,
|
|
890
1641
|
maxWidth: cell.maxWidth,
|
|
891
1642
|
maxHeight: cell.maxHeight,
|
|
@@ -895,30 +1646,108 @@ function makeRenderRow(row, resolve, keepRaw = false) {
|
|
|
895
1646
|
align: cell.align,
|
|
896
1647
|
valign: cell.valign,
|
|
897
1648
|
fontSize: cell.fontSize,
|
|
1649
|
+
fontFamily: cell.fontFamily,
|
|
898
1650
|
fontWeight: cell.fontWeight,
|
|
899
1651
|
color: cell.color,
|
|
900
1652
|
backgroundColor: cell.backgroundColor,
|
|
901
1653
|
borders: cell.borders,
|
|
902
1654
|
padding: cell.padding,
|
|
903
|
-
wordWrap: cell.wordWrap
|
|
1655
|
+
wordWrap: cell.wordWrap,
|
|
1656
|
+
textFit: cell.textFit,
|
|
1657
|
+
shrinkMinFontSize: cell.shrinkMinFontSize
|
|
904
1658
|
}))
|
|
905
1659
|
};
|
|
906
1660
|
}
|
|
907
1661
|
function countRepeatHeader(rows) {
|
|
1662
|
+
const headerCount = countLeadingHeaderRows(rows);
|
|
1663
|
+
if (headerCount === 0) return 0;
|
|
1664
|
+
const enabled = rows.slice(0, headerCount).some((row) => row.repeatOnPage === true);
|
|
1665
|
+
if (!enabled) return 0;
|
|
1666
|
+
return alignToRowspanBoundary(rows, headerCount);
|
|
1667
|
+
}
|
|
1668
|
+
function countLeadingHeaderRows(rows) {
|
|
908
1669
|
let n = 0;
|
|
909
1670
|
for (const row of rows) {
|
|
910
|
-
if (row
|
|
911
|
-
|
|
912
|
-
} else {
|
|
913
|
-
break;
|
|
914
|
-
}
|
|
1671
|
+
if (row?.type !== "header") break;
|
|
1672
|
+
n++;
|
|
915
1673
|
}
|
|
916
1674
|
return n;
|
|
917
1675
|
}
|
|
918
|
-
function
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1676
|
+
function isRowspanComplete(rows, n) {
|
|
1677
|
+
for (let r = 0; r < n; r++) {
|
|
1678
|
+
for (const cell of rows[r]?.cells ?? []) {
|
|
1679
|
+
if (cell?.merged) continue;
|
|
1680
|
+
if (r + (cell?.rowspan ?? 1) > n) return false;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
return true;
|
|
1684
|
+
}
|
|
1685
|
+
function alignToRowspanBoundary(rows, max2) {
|
|
1686
|
+
for (let n = max2; n >= 1; n--) {
|
|
1687
|
+
if (isRowspanComplete(rows, n)) return n;
|
|
1688
|
+
}
|
|
1689
|
+
return 1;
|
|
1690
|
+
}
|
|
1691
|
+
var pad2 = (n) => String(n).padStart(2, "0");
|
|
1692
|
+
function resolveSystemVariables(now = /* @__PURE__ */ new Date(), page = {}) {
|
|
1693
|
+
return {
|
|
1694
|
+
printDate: `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())}`,
|
|
1695
|
+
printTime: `${pad2(now.getHours())}:${pad2(now.getMinutes())}:${pad2(now.getSeconds())}`,
|
|
1696
|
+
pageIndex: page.pageIndex ?? 1,
|
|
1697
|
+
totalPages: page.totalPages ?? 1
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
function referencesPageNumbers(expr) {
|
|
1701
|
+
return typeof expr === "string" && /\b(pageIndex|totalPages)\b/.test(expr);
|
|
1702
|
+
}
|
|
1703
|
+
function injectSystemVariables(html, now = /* @__PURE__ */ new Date()) {
|
|
1704
|
+
const { printDate, printTime } = resolveSystemVariables(now);
|
|
1705
|
+
return html.replace(/\{printDate\}/g, printDate).replace(/\{printTime\}/g, printTime);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// src/print/fonts.ts
|
|
1709
|
+
function buildFontFaceCss(fonts) {
|
|
1710
|
+
const blocks = [];
|
|
1711
|
+
for (const font of fonts ?? []) {
|
|
1712
|
+
const family = font?.family?.trim();
|
|
1713
|
+
if (!family) continue;
|
|
1714
|
+
const quoted = `"${family.replace(/"/g, "")}"`;
|
|
1715
|
+
for (const file of font.files ?? []) {
|
|
1716
|
+
const url = file?.url?.trim();
|
|
1717
|
+
if (!url) continue;
|
|
1718
|
+
const weight = Number.isFinite(file.weight) ? Number(file.weight) : 400;
|
|
1719
|
+
const style = file.style === "italic" ? "italic" : "normal";
|
|
1720
|
+
blocks.push(
|
|
1721
|
+
`@font-face{font-family:${quoted};src:url("${url}")${fontFormatHint(url)};font-weight:${weight};font-style:${style};font-display:block;}`
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return blocks.length ? `${blocks.join("")}
|
|
1726
|
+
` : "";
|
|
1727
|
+
}
|
|
1728
|
+
function fontFormatHint(url) {
|
|
1729
|
+
const path = url.split(/[?#]/)[0].toLowerCase();
|
|
1730
|
+
if (path.endsWith(".woff2")) return ' format("woff2")';
|
|
1731
|
+
if (path.endsWith(".woff")) return ' format("woff")';
|
|
1732
|
+
if (path.endsWith(".ttf")) return ' format("truetype")';
|
|
1733
|
+
if (path.endsWith(".otf")) return ' format("opentype")';
|
|
1734
|
+
return "";
|
|
1735
|
+
}
|
|
1736
|
+
var FALLBACK_FONT_STACK = [
|
|
1737
|
+
'"Microsoft YaHei"',
|
|
1738
|
+
'"PingFang SC"',
|
|
1739
|
+
'"Helvetica Neue"',
|
|
1740
|
+
"Arial",
|
|
1741
|
+
"sans-serif"
|
|
1742
|
+
];
|
|
1743
|
+
function escapeInlineStyleValue(cssValue) {
|
|
1744
|
+
return cssValue.replace(/&/g, "&").replace(/"/g, """);
|
|
1745
|
+
}
|
|
1746
|
+
function toFontFamilyStack(family) {
|
|
1747
|
+
const name = family?.trim();
|
|
1748
|
+
if (!name) return FALLBACK_FONT_STACK.join(", ");
|
|
1749
|
+
const head = name.includes(",") ? name : `"${name.replace(/"/g, "")}"`;
|
|
1750
|
+
return [head, ...FALLBACK_FONT_STACK].join(", ");
|
|
922
1751
|
}
|
|
923
1752
|
|
|
924
1753
|
// src/render/types.ts
|
|
@@ -928,38 +1757,81 @@ var PAPER_DIMENSIONS = {
|
|
|
928
1757
|
A5: { width: 148, height: 210 },
|
|
929
1758
|
Letter: { width: 216, height: 279 },
|
|
930
1759
|
Legal: { width: 216, height: 356 },
|
|
931
|
-
|
|
1760
|
+
// 针式打印纸(241 系列等分):11 英寸整张 279.4mm 按等分取整
|
|
1761
|
+
DOT_FULL: { width: 241, height: 279.4 },
|
|
1762
|
+
DOT_HALF: { width: 241, height: 139.7 },
|
|
1763
|
+
DOT_THIRD: { width: 241, height: 93.1 },
|
|
1764
|
+
// 标签纸
|
|
1765
|
+
LABEL_80X60: { width: 80, height: 60 },
|
|
1766
|
+
LABEL_60X40: { width: 60, height: 40 },
|
|
1767
|
+
LABEL_40X30: { width: 40, height: 30 },
|
|
1768
|
+
// 小票纸(热敏卷纸):高度仅为设计画布高度,出纸按内容推导
|
|
1769
|
+
THERMAL_57: { width: 57, height: 297 },
|
|
1770
|
+
THERMAL_80: { width: 80, height: 297 },
|
|
1771
|
+
THERMAL_110: { width: 110, height: 297 },
|
|
1772
|
+
CUSTOM: { width: 210, height: 297 },
|
|
1773
|
+
// 连续纸:默认 80mm 热敏;高度仅为设计画布高度,出纸按内容推导
|
|
1774
|
+
CONTINUOUS: { width: 80, height: 297 }
|
|
932
1775
|
};
|
|
1776
|
+
var CONTINUOUS_PAPER_SIZES = /* @__PURE__ */ new Set([
|
|
1777
|
+
"CONTINUOUS",
|
|
1778
|
+
"THERMAL_57",
|
|
1779
|
+
"THERMAL_80",
|
|
1780
|
+
"THERMAL_110"
|
|
1781
|
+
]);
|
|
1782
|
+
function isContinuousPaperSize(paperSize) {
|
|
1783
|
+
return CONTINUOUS_PAPER_SIZES.has(paperSize);
|
|
1784
|
+
}
|
|
933
1785
|
function getPaperDimensions(template) {
|
|
934
|
-
const
|
|
935
|
-
|
|
1786
|
+
const continuous = isContinuousPaperSize(template.paperSize);
|
|
1787
|
+
const base = template.paperSize === "CUSTOM" || continuous ? {
|
|
1788
|
+
width: template.customWidth ?? PAPER_DIMENSIONS[template.paperSize].width,
|
|
1789
|
+
height: template.customHeight ?? PAPER_DIMENSIONS[template.paperSize].height
|
|
1790
|
+
} : PAPER_DIMENSIONS[template.paperSize];
|
|
1791
|
+
if (template.orientation === "landscape" && !continuous) {
|
|
936
1792
|
return { width: base.height, height: base.width };
|
|
937
1793
|
}
|
|
938
1794
|
return { ...base };
|
|
939
1795
|
}
|
|
1796
|
+
function getOutputPaperDimensions(template) {
|
|
1797
|
+
const continuous = isContinuousPaperSize(template.paperSize);
|
|
1798
|
+
if (continuous || template.tiling?.enabled === true) {
|
|
1799
|
+
return getPaperDimensions(template);
|
|
1800
|
+
}
|
|
1801
|
+
const design = getPaperDimensions(template);
|
|
1802
|
+
const swap = template.outputRotation === 90 || template.outputRotation === 270;
|
|
1803
|
+
return swap ? { width: design.height, height: design.width } : design;
|
|
1804
|
+
}
|
|
1805
|
+
function getOutputRotationAngle(template) {
|
|
1806
|
+
const continuous = isContinuousPaperSize(template.paperSize);
|
|
1807
|
+
if (continuous || template.tiling?.enabled === true) return 0;
|
|
1808
|
+
return template.outputRotation ?? 0;
|
|
1809
|
+
}
|
|
1810
|
+
function isContinuousPaper(template) {
|
|
1811
|
+
return isContinuousPaperSize(template.paperSize);
|
|
1812
|
+
}
|
|
940
1813
|
|
|
941
1814
|
// src/render/css-builder.ts
|
|
942
1815
|
function mm(value) {
|
|
943
1816
|
return `${value}mm`;
|
|
944
1817
|
}
|
|
945
|
-
function
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
const
|
|
951
|
-
|
|
1818
|
+
function resolveOuterPaper(template, pageHeightMm) {
|
|
1819
|
+
const out = getOutputPaperDimensions(template);
|
|
1820
|
+
return pageHeightMm && pageHeightMm > 0 && isContinuousPaper(template) ? { width: out.width, height: pageHeightMm } : out;
|
|
1821
|
+
}
|
|
1822
|
+
function resolveAreaPaper(template, pageHeightMm) {
|
|
1823
|
+
const design = getPaperDimensions(template);
|
|
1824
|
+
return pageHeightMm && pageHeightMm > 0 && isContinuousPaper(template) ? { width: design.width, height: pageHeightMm } : design;
|
|
1825
|
+
}
|
|
1826
|
+
function screenBlock() {
|
|
952
1827
|
return `
|
|
953
|
-
/* \u2500\u2500 \u6253\u5370\u7EB8\u5F20\uFF1A\u6D4F\u89C8\u5668\u539F\u751F\u6253\u5370\u6309\u6B64\u5C3A\u5BF8\u5206\u9875\uFF08Playwright page.pdf \u4EE5\u663E\u5F0F\u5BBD\u9AD8\u4E3A\u51C6\uFF0C\u65E0\u526F\u4F5C\u7528\uFF09 \u2500\u2500 */
|
|
954
|
-
@page { size: ${mm(paper.width)} ${mm(paper.height)}; margin: 0; }
|
|
955
|
-
|
|
956
1828
|
/* \u2500\u2500 \u5168\u5C40\u91CD\u7F6E \u2500\u2500 */
|
|
957
1829
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
958
1830
|
|
|
959
1831
|
/* \u6253\u5370\u5FC5\u987B\u4FDD\u7559\u5143\u7D20\u80CC\u666F\u8272\uFF08Chromium \u9ED8\u8BA4\u5254\u9664\u80CC\u666F\uFF0C\u9700\u663E\u5F0F\u58F0\u660E\uFF09 */
|
|
960
1832
|
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
961
1833
|
|
|
962
|
-
body { font-family: "
|
|
1834
|
+
body { font-family: ${FALLBACK_FONT_STACK.join(", ")}; }
|
|
963
1835
|
|
|
964
1836
|
/* \u2500\u2500 \u5C4F\u5E55\u9884\u89C8\uFF1A\u7070\u5E95 + \u7EB8\u5F20\u9634\u5F71/\u9875\u95F4\u8DDD\uFF1B\u6253\u5370\u65F6\u53BB\u9664 \u2500\u2500 */
|
|
965
1837
|
@media screen {
|
|
@@ -970,53 +1842,31 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
|
|
|
970
1842
|
body { background: #fff; }
|
|
971
1843
|
.print-page { margin: 0; box-shadow: none; }
|
|
972
1844
|
}
|
|
973
|
-
|
|
974
|
-
/* \u2500\u2500 \u7EB8\u5F20\u9875\u9762 \u2500\u2500 */
|
|
975
|
-
.print-page {
|
|
976
|
-
width: ${mm(paper.width)};
|
|
977
|
-
min-height: ${mm(paper.height)};
|
|
978
|
-
background: ${template.pageBackground ?? "#fff"};
|
|
979
|
-
padding: ${mm(mt)} ${mm(mr)} ${mm(mb)} ${mm(ml)};
|
|
980
|
-
position: relative;
|
|
981
|
-
page-break-after: always;
|
|
982
|
-
overflow: hidden;
|
|
983
|
-
}
|
|
984
|
-
.print-page:last-child {
|
|
985
|
-
page-break-after: auto;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
/* \u2500\u2500 \u9875\u7709 \u2500\u2500 */
|
|
989
|
-
.page-header {
|
|
990
|
-
width: ${mm(contentWidth)};
|
|
991
|
-
height: ${mm(headerH)};
|
|
992
|
-
position: relative;
|
|
1845
|
+
`;
|
|
993
1846
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
.
|
|
999
|
-
width: ${mm(contentWidth)};
|
|
1000
|
-
height: ${mm(footerH)};
|
|
1847
|
+
function watermarkBlock() {
|
|
1848
|
+
return `
|
|
1849
|
+
/* \u2500\u2500 \u6C34\u5370\u5C42\uFF1A\u8986\u76D6\u6574\u9875\u3001\u4F4D\u4E8E\u6240\u6709\u5185\u5BB9\u4E4B\u4E0B\uFF08\u663E\u5F0F\u77E2\u91CF\u74E6\u7247\uFF0C\u7981\u6B62\u7528 CSS \u5E73\u94FA\u80CC\u666F\uFF1A
|
|
1850
|
+
Chromium \u4F1A\u628A\u5B83\u7F16\u8BD1\u6210 PDF \u5E73\u94FA\u56FE\u6848\uFF0C\u51FA\u7EB8\u94FE\u8DEF\u7684 RIP \u4F1A\u5FFD\u7565\u56FE\u6848\u77E9\u9635\u5BFC\u81F4\u6C34\u5370\u653E\u5927/\u9519\u4F4D\uFF09 \u2500\u2500 */
|
|
1851
|
+
.watermark-layer {
|
|
1001
1852
|
position: absolute;
|
|
1002
|
-
top:
|
|
1853
|
+
top: 0;
|
|
1003
1854
|
left: 0;
|
|
1855
|
+
width: 100%;
|
|
1856
|
+
height: 100%;
|
|
1857
|
+
z-index: 0;
|
|
1858
|
+
pointer-events: none;
|
|
1859
|
+
overflow: hidden;
|
|
1004
1860
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
position: relative;
|
|
1010
|
-
overflow: visible;
|
|
1861
|
+
/* \u5355\u5757\u6C34\u5370\u74E6\u7247\uFF1A\u4F4D\u7F6E/\u5C3A\u5BF8\u7531 core \u7684\u74E6\u7247\u7F51\u683C\u7ED9\u51FA\uFF08mm\uFF09 */
|
|
1862
|
+
.watermark-tile {
|
|
1863
|
+
position: absolute;
|
|
1864
|
+
pointer-events: none;
|
|
1011
1865
|
}
|
|
1012
|
-
|
|
1013
|
-
/* \u2500\u2500 \u9996\u9875\u53E0\u52A0\u533A\u57DF \u2500\u2500 */
|
|
1014
|
-
.first-page-overlay {
|
|
1015
|
-
width: ${mm(contentWidth)};
|
|
1016
|
-
height: ${mm(overlayH)};
|
|
1017
|
-
position: relative;
|
|
1866
|
+
`;
|
|
1018
1867
|
}
|
|
1019
|
-
|
|
1868
|
+
function elementBlock() {
|
|
1869
|
+
return `
|
|
1020
1870
|
/* \u2500\u2500 \u5143\u7D20\u901A\u7528\u5B9A\u4F4D \u2500\u2500 */
|
|
1021
1871
|
.print-element {
|
|
1022
1872
|
position: absolute;
|
|
@@ -1051,46 +1901,380 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
|
|
|
1051
1901
|
height: auto;
|
|
1052
1902
|
overflow: visible;
|
|
1053
1903
|
}
|
|
1054
|
-
|
|
1904
|
+
`;
|
|
1055
1905
|
}
|
|
1056
|
-
function
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
`width:${mm(width)}`
|
|
1062
|
-
];
|
|
1063
|
-
if (height !== void 0 && height > 0) {
|
|
1064
|
-
parts.push(`height:${mm(height)}`);
|
|
1065
|
-
}
|
|
1066
|
-
if (zIndex !== void 0) {
|
|
1067
|
-
parts.push(`z-index:${zIndex}`);
|
|
1068
|
-
}
|
|
1069
|
-
return parts.join(";") + ";";
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
// src/render/pagination-engine.ts
|
|
1073
|
-
function paginationOf(el) {
|
|
1074
|
-
return el.options?.pagination ?? el.pagination;
|
|
1906
|
+
function pageRuleBlock(paper) {
|
|
1907
|
+
return `
|
|
1908
|
+
/* \u2500\u2500 \u6253\u5370\u7EB8\u5F20\uFF1A\u6D4F\u89C8\u5668\u539F\u751F\u6253\u5370\u6309\u6B64\u5C3A\u5BF8\u5206\u9875\uFF08Playwright page.pdf \u4EE5\u663E\u5F0F\u5BBD\u9AD8\u4E3A\u51C6\uFF0C\u65E0\u526F\u4F5C\u7528\uFF09 \u2500\u2500 */
|
|
1909
|
+
@page { size: ${mm(paper.width)} ${mm(paper.height)}; margin: 0; }
|
|
1910
|
+
`;
|
|
1075
1911
|
}
|
|
1076
|
-
function
|
|
1077
|
-
|
|
1912
|
+
function buildRotorCss(area, margins, angle) {
|
|
1913
|
+
const base = `
|
|
1914
|
+
/* \u2500\u2500 \u51FA\u7EB8\u8F6C\u5B50\uFF1A\u8BBE\u8BA1\u7A3F\u6574\u9875\u65CB\u8F6C ${angle}\xB0 \u586B\u5165\u51FA\u7EB8\u7EB8\u5F20 \u2500\u2500 */
|
|
1915
|
+
.print-page-rotor {
|
|
1916
|
+
position: absolute;
|
|
1917
|
+
top: 0;
|
|
1918
|
+
left: 0;
|
|
1919
|
+
width: ${mm(area.width)};
|
|
1920
|
+
height: ${mm(area.height)};
|
|
1921
|
+
padding: ${mm(margins.top)} ${mm(margins.right)} ${mm(margins.bottom)} ${mm(margins.left)};
|
|
1922
|
+
box-sizing: border-box;
|
|
1923
|
+
transform-origin: 0 0;
|
|
1924
|
+
}`;
|
|
1925
|
+
if (angle === 0) return base.trim();
|
|
1926
|
+
const transforms = {
|
|
1927
|
+
90: `translate(${mm(area.height)}, 0mm) rotate(90deg)`,
|
|
1928
|
+
180: `translate(${mm(area.width)}, ${mm(area.height)}) rotate(180deg)`,
|
|
1929
|
+
270: `translate(0mm, ${mm(area.width)}) rotate(270deg)`
|
|
1930
|
+
};
|
|
1931
|
+
const tf = transforms[angle];
|
|
1932
|
+
return [
|
|
1933
|
+
base.trim(),
|
|
1934
|
+
`.print-page-rotor-${angle} {
|
|
1935
|
+
transform: ${tf};
|
|
1936
|
+
}`
|
|
1937
|
+
].join("\n");
|
|
1938
|
+
}
|
|
1939
|
+
function pageGeometryBlock(template, outerPaper, pageSel, rotated) {
|
|
1940
|
+
const { top: mt, right: mr, bottom: mb, left: ml } = template.margins;
|
|
1941
|
+
const padding = rotated ? "0" : `${mm(mt)} ${mm(mr)} ${mm(mb)} ${mm(ml)}`;
|
|
1942
|
+
return `
|
|
1943
|
+
/* \u2500\u2500 \u7EB8\u5F20\u9875\u9762 \u2500\u2500 */
|
|
1944
|
+
${pageSel} {
|
|
1945
|
+
width: ${mm(outerPaper.width)};
|
|
1946
|
+
min-height: ${mm(outerPaper.height)};
|
|
1947
|
+
background: ${template.pageBackground ?? "#fff"};
|
|
1948
|
+
padding: ${padding};
|
|
1949
|
+
position: relative;
|
|
1950
|
+
page-break-after: always;
|
|
1951
|
+
overflow: hidden;
|
|
1078
1952
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
return el.type === "table" || el.printElementType?.type === "table";
|
|
1953
|
+
.print-page:last-child {
|
|
1954
|
+
page-break-after: auto;
|
|
1082
1955
|
}
|
|
1083
|
-
|
|
1084
|
-
const opts = el.options ?? {};
|
|
1085
|
-
const top = opts.top ?? 0;
|
|
1086
|
-
const rows = opts.tableRows ?? [];
|
|
1087
|
-
if (rows.length > 0) {
|
|
1088
|
-
return top + rows.reduce((s, r) => s + (r.height ?? 0), 0);
|
|
1089
|
-
}
|
|
1090
|
-
return top + (opts.height ?? 0);
|
|
1956
|
+
`;
|
|
1091
1957
|
}
|
|
1092
|
-
function
|
|
1093
|
-
const
|
|
1958
|
+
function areaGeometryBlock(template, paper, desc) {
|
|
1959
|
+
const { right: mr, left: ml } = template.margins;
|
|
1960
|
+
const { bottom: mb } = template.margins;
|
|
1961
|
+
const headerH = template.header?.height ?? 0;
|
|
1962
|
+
const footerH = template.footer?.height ?? 0;
|
|
1963
|
+
const overlayH = template.firstPageOverlay?.height ?? 0;
|
|
1964
|
+
const contentWidth = paper.width - ml - mr;
|
|
1965
|
+
return `
|
|
1966
|
+
/* \u2500\u2500 \u9875\u7709 \u2500\u2500 */
|
|
1967
|
+
${desc}.page-header {
|
|
1968
|
+
width: ${mm(contentWidth)};
|
|
1969
|
+
height: ${mm(headerH)};
|
|
1970
|
+
position: relative;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
/* \u2500\u2500 \u9875\u811A\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\u56FA\u5B9A\u5728\u9875\u9762\u5E95\u90E8\uFF08\u5185\u5BB9\u4E0D\u8DB3\u65F6\u4E0D\u968F\u6587\u6863\u6D41\u4E0A\u6D6E\uFF09 \u2500\u2500
|
|
1974
|
+
\u7528\u663E\u5F0F top \u5B9A\u4F4D\u5230\u300C\u7EB8\u9AD8 - \u4E0B\u8FB9\u8DDD - \u9875\u811A\u9AD8\u300D\uFF0C\u4FDD\u8BC1\u4E0B\u8FB9\u8DDD\u751F\u6548\u3001
|
|
1975
|
+
\u4E0E\u8BBE\u8BA1\u5668 CanvasPaper \u7684\u4E09\u533A\u51E0\u4F55\u4E00\u81F4\u3002 */
|
|
1976
|
+
${desc}.page-footer {
|
|
1977
|
+
width: ${mm(contentWidth)};
|
|
1978
|
+
height: ${mm(footerH)};
|
|
1979
|
+
position: absolute;
|
|
1980
|
+
top: ${mm(paper.height - mb - footerH)};
|
|
1981
|
+
left: 0;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
/* \u2500\u2500 \u5185\u5BB9\u533A \u2500\u2500 */
|
|
1985
|
+
${desc}.content-area {
|
|
1986
|
+
width: ${mm(contentWidth)};
|
|
1987
|
+
position: relative;
|
|
1988
|
+
overflow: visible;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
/* \u2500\u2500 \u9996\u9875\u53E0\u52A0\u533A\u57DF \u2500\u2500 */
|
|
1992
|
+
${desc}.first-page-overlay {
|
|
1993
|
+
width: ${mm(contentWidth)};
|
|
1994
|
+
height: ${mm(overlayH)};
|
|
1995
|
+
position: relative;
|
|
1996
|
+
}
|
|
1997
|
+
`;
|
|
1998
|
+
}
|
|
1999
|
+
function buildBasePageCss() {
|
|
2000
|
+
return [screenBlock(), watermarkBlock(), elementBlock()].join("").trim();
|
|
2001
|
+
}
|
|
2002
|
+
function buildPageRuleCss(template, pageHeightMm) {
|
|
2003
|
+
return pageRuleBlock(resolveOuterPaper(template, pageHeightMm)).trim();
|
|
2004
|
+
}
|
|
2005
|
+
function buildPageGeometryCss(template, scope, pageHeightMm) {
|
|
2006
|
+
const angle = getOutputRotationAngle(template);
|
|
2007
|
+
const rotated = angle !== 0;
|
|
2008
|
+
const outer = resolveOuterPaper(template, pageHeightMm);
|
|
2009
|
+
const area = resolveAreaPaper(template, pageHeightMm);
|
|
2010
|
+
const pageSel = scope ? `${scope}.print-page` : ".print-page";
|
|
2011
|
+
const desc = scope ? `${scope} ` : "";
|
|
2012
|
+
return [
|
|
2013
|
+
pageGeometryBlock(template, outer, pageSel, rotated),
|
|
2014
|
+
areaGeometryBlock(template, area, desc),
|
|
2015
|
+
...rotated ? [buildRotorCss(area, template.margins, angle)] : []
|
|
2016
|
+
].join("").trim();
|
|
2017
|
+
}
|
|
2018
|
+
function buildPageCss(template, pageHeightMm) {
|
|
2019
|
+
const angle = getOutputRotationAngle(template);
|
|
2020
|
+
const rotated = angle !== 0;
|
|
2021
|
+
const outer = resolveOuterPaper(template, pageHeightMm);
|
|
2022
|
+
const area = resolveAreaPaper(template, pageHeightMm);
|
|
2023
|
+
return [
|
|
2024
|
+
pageRuleBlock(outer),
|
|
2025
|
+
screenBlock(),
|
|
2026
|
+
pageGeometryBlock(template, outer, ".print-page", rotated),
|
|
2027
|
+
watermarkBlock(),
|
|
2028
|
+
areaGeometryBlock(template, area, ""),
|
|
2029
|
+
elementBlock(),
|
|
2030
|
+
...rotated ? [buildRotorCss(area, template.margins, angle)] : []
|
|
2031
|
+
].join("").trim();
|
|
2032
|
+
}
|
|
2033
|
+
var COPY_BREAK_CSS = ".print-copy:not(:last-child){break-after:page;page-break-after:always;}";
|
|
2034
|
+
function buildBatchPageCss(template, copies) {
|
|
2035
|
+
if (!isContinuousPaper(template)) {
|
|
2036
|
+
return `${buildPageCss(template)}
|
|
2037
|
+
${COPY_BREAK_CSS}`;
|
|
2038
|
+
}
|
|
2039
|
+
const { bottom: mb } = template.margins;
|
|
2040
|
+
const footerH = template.footer?.height ?? 0;
|
|
2041
|
+
const width = getPaperDimensions(template).width;
|
|
2042
|
+
const base = buildPageCss(template, copies[0]?.heightMm);
|
|
2043
|
+
const scoped = copies.map((copy, i) => {
|
|
2044
|
+
const h = copy.heightMm;
|
|
2045
|
+
const rules = [
|
|
2046
|
+
`@page copy${i} { size: ${mm(width)} ${h ? mm(h) : "auto"}; margin: 0; }`,
|
|
2047
|
+
`.print-copy-${i} { page: copy${i}; }`
|
|
2048
|
+
];
|
|
2049
|
+
if (h) {
|
|
2050
|
+
rules.push(`.print-copy-${i} .print-page { min-height: ${mm(h)}; }`);
|
|
2051
|
+
rules.push(`.print-copy-${i} .page-footer { top: ${mm(h - mb - footerH)}; }`);
|
|
2052
|
+
}
|
|
2053
|
+
return rules.join("\n");
|
|
2054
|
+
}).join("\n");
|
|
2055
|
+
return `${base}
|
|
2056
|
+
${scoped}
|
|
2057
|
+
${COPY_BREAK_CSS}`;
|
|
2058
|
+
}
|
|
2059
|
+
function buildSheetPageCss(layout) {
|
|
2060
|
+
return `
|
|
2061
|
+
/* \u2500\u2500 \u62FC\u7248\u7EB8\u5F20\uFF1A@page \u5C3A\u5BF8 = \u76EE\u6807\u7EB8\u3002\u670D\u52A1\u7AEF/\u5BA2\u6237\u7AEF\u94FE\u8DEF\u4EE5 paperMm \u663E\u5F0F\u5B9A\u5C3A\u5BF8\u3001\u4E0D\u770B\u8FD9\u91CC\uFF0C
|
|
2062
|
+
\u4F46\u6D4F\u89C8\u5668\u94FE\u8DEF\u53EA\u770B @page\uFF0C\u6545\u8FD9\u6761\u662F\u786C\u9700\u6C42 \u2500\u2500 */
|
|
2063
|
+
@page { size: ${mm(layout.sheet.width)} ${mm(layout.sheet.height)}; margin: 0; }
|
|
2064
|
+
|
|
2065
|
+
/* \u2500\u2500 \u4E00\u5F20\u76EE\u6807\u7EB8 \u2500\u2500 */
|
|
2066
|
+
.print-sheet {
|
|
2067
|
+
width: ${mm(layout.sheet.width)};
|
|
2068
|
+
height: ${mm(layout.sheet.height)};
|
|
2069
|
+
position: relative;
|
|
2070
|
+
overflow: hidden;
|
|
2071
|
+
break-after: page;
|
|
2072
|
+
page-break-after: always;
|
|
2073
|
+
}
|
|
2074
|
+
.print-sheet:last-child { break-after: auto; page-break-after: auto; }
|
|
2075
|
+
|
|
2076
|
+
/* \u2500\u2500 \u4E00\u683C\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\uFF0C\u4F4D\u7F6E\u7531 tilePosition() \u4EE5\u884C\u5185 style \u7ED9\u51FA \u2500\u2500 */
|
|
2077
|
+
.print-tile {
|
|
2078
|
+
position: absolute;
|
|
2079
|
+
width: ${mm(layout.tile.width)};
|
|
2080
|
+
height: ${mm(layout.tile.height)};
|
|
2081
|
+
overflow: hidden;
|
|
2082
|
+
}
|
|
2083
|
+
/* \u9632\u5FA1\u6027\u58F0\u660E\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D + overflow:hidden \u5BB9\u5668\u5185\u7684\u540E\u4EE3\u4E0D\u4EA7\u751F\u5206\u9875\u70B9\uFF0C\u5F53\u524D\u5E03\u5C40\u4E0B\u65E0\u5B9E\u9645\u4F5C\u7528\uFF1B
|
|
2084
|
+
\u82E5\u5C06\u6765\u6539\u7528 flex/grid \u5E03\u5C40\uFF0C\u683C\u5185\u6574\u9875\u4F1A\u91CD\u65B0\u53C2\u4E0E\u5206\u9875\uFF0C\u6545\u4FDD\u7559 */
|
|
2085
|
+
.print-tile > .print-page { break-after: auto; page-break-after: auto; }
|
|
2086
|
+
|
|
2087
|
+
@media screen {
|
|
2088
|
+
.print-sheet { margin: 12px auto; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18); }
|
|
2089
|
+
/* \u5FC5\u9700\uFF1A\u62B5\u6D88\u6807\u7B7E CSS \u7684 @media screen{.print-page{margin:12px auto}}\uFF0C
|
|
2090
|
+
\u5426\u5219\u8BBE\u8BA1\u5668\u9884\u89C8\u9519\u4F4D 3.17mm\u3001\u4E0E\u51FA\u7EB8\u4E0D\u4E00\u81F4 */
|
|
2091
|
+
.print-tile > .print-page { margin: 0; box-shadow: none; }
|
|
2092
|
+
}
|
|
2093
|
+
`;
|
|
2094
|
+
}
|
|
2095
|
+
function elementPositionStyle(left, top, width, height, zIndex) {
|
|
2096
|
+
const parts = [
|
|
2097
|
+
`position:absolute`,
|
|
2098
|
+
`left:${mm(left)}`,
|
|
2099
|
+
`top:${mm(top)}`,
|
|
2100
|
+
`width:${mm(width)}`
|
|
2101
|
+
];
|
|
2102
|
+
if (height !== void 0 && height > 0) {
|
|
2103
|
+
parts.push(`height:${mm(height)}`);
|
|
2104
|
+
}
|
|
2105
|
+
if (zIndex !== void 0) {
|
|
2106
|
+
parts.push(`z-index:${zIndex}`);
|
|
2107
|
+
}
|
|
2108
|
+
return parts.join(";") + ";";
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// src/render/watermark.ts
|
|
2112
|
+
var PX_PER_MM = 96 / 25.4;
|
|
2113
|
+
var MM_PER_PX = 25.4 / 96;
|
|
2114
|
+
var WATERMARK_DEFAULTS = {
|
|
2115
|
+
color: "#cccccc",
|
|
2116
|
+
opacity: 0.15,
|
|
2117
|
+
rotate: -30,
|
|
2118
|
+
/** 瓦片默认尺寸(px):决定平铺疏密,默认 260×180 */
|
|
2119
|
+
tileWidth: 260,
|
|
2120
|
+
tileHeight: 180,
|
|
2121
|
+
/** 瓦片下限(px),防止文字裁剪/异常平铺 */
|
|
2122
|
+
minTileWidth: 140,
|
|
2123
|
+
minTileHeight: 100,
|
|
2124
|
+
/** 瓦片字号固定 16px:密度只改变平铺疏密,不改字体大小 */
|
|
2125
|
+
fontSize: 16
|
|
2126
|
+
};
|
|
2127
|
+
function clampTile(value, fallback, min2) {
|
|
2128
|
+
const n = typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
2129
|
+
return Math.max(min2, Math.round(n));
|
|
2130
|
+
}
|
|
2131
|
+
function resolveTileSize(wm) {
|
|
2132
|
+
return {
|
|
2133
|
+
width: clampTile(wm?.tileWidth, WATERMARK_DEFAULTS.tileWidth, WATERMARK_DEFAULTS.minTileWidth),
|
|
2134
|
+
height: clampTile(wm?.tileHeight, WATERMARK_DEFAULTS.tileHeight, WATERMARK_DEFAULTS.minTileHeight)
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
function isWatermarkVisible(wm) {
|
|
2138
|
+
if (!wm) return false;
|
|
2139
|
+
if (!wm.mode || wm.mode === "fixed") {
|
|
2140
|
+
return !!wm.content && wm.content.trim().length > 0;
|
|
2141
|
+
}
|
|
2142
|
+
return !!wm.binding && wm.binding.trim().length > 0;
|
|
2143
|
+
}
|
|
2144
|
+
function resolveWatermarkText(wm, printData, systemVars2) {
|
|
2145
|
+
if (!wm) return "";
|
|
2146
|
+
let text = "";
|
|
2147
|
+
if (!wm.mode || wm.mode === "fixed") {
|
|
2148
|
+
text = wm.content ?? "";
|
|
2149
|
+
} else if (typeof wm.binding === "string" && wm.binding.trim()) {
|
|
2150
|
+
const binding = wm.binding.trim();
|
|
2151
|
+
const data = Array.isArray(printData) ? printData[0] ?? {} : printData ?? {};
|
|
2152
|
+
const ctx = { ...resolveSystemVariables(), ...systemVars2, ...data };
|
|
2153
|
+
let resolved;
|
|
2154
|
+
if (binding.includes("{")) {
|
|
2155
|
+
resolved = evaluateTemplate(binding, ctx);
|
|
2156
|
+
} else {
|
|
2157
|
+
try {
|
|
2158
|
+
resolved = safeEval(binding, ctx);
|
|
2159
|
+
} catch {
|
|
2160
|
+
resolved = void 0;
|
|
2161
|
+
}
|
|
2162
|
+
if (resolved == null || typeof resolved === "object") {
|
|
2163
|
+
resolved = getByPath(data, binding);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
if (resolved != null && typeof resolved !== "object" && String(resolved) !== binding) {
|
|
2167
|
+
text = String(resolved);
|
|
2168
|
+
} else {
|
|
2169
|
+
text = wm.testData ?? `[${wm.binding}]`;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
if (wm.timestamp && text) {
|
|
2173
|
+
text = `${text} ${formatTimestamp(wm.format)}`;
|
|
2174
|
+
}
|
|
2175
|
+
return text;
|
|
2176
|
+
}
|
|
2177
|
+
function pad(n) {
|
|
2178
|
+
return String(n).padStart(2, "0");
|
|
2179
|
+
}
|
|
2180
|
+
function formatTimestamp(format) {
|
|
2181
|
+
const fmt = format && format.trim() ? format : "YYYY-MM-DD HH:mm";
|
|
2182
|
+
const d = /* @__PURE__ */ new Date();
|
|
2183
|
+
const map = {
|
|
2184
|
+
YYYY: String(d.getFullYear()),
|
|
2185
|
+
MM: pad(d.getMonth() + 1),
|
|
2186
|
+
DD: pad(d.getDate()),
|
|
2187
|
+
HH: pad(d.getHours()),
|
|
2188
|
+
mm: pad(d.getMinutes()),
|
|
2189
|
+
ss: pad(d.getSeconds())
|
|
2190
|
+
};
|
|
2191
|
+
return fmt.replace(/YYYY|MM|DD|HH|mm|ss/g, (k) => map[k] ?? k);
|
|
2192
|
+
}
|
|
2193
|
+
function round4(n) {
|
|
2194
|
+
return Math.round(n * 1e4) / 1e4;
|
|
2195
|
+
}
|
|
2196
|
+
function escXml(text) {
|
|
2197
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2198
|
+
}
|
|
2199
|
+
function resolveWatermarkLayout(wm, printData, paperMm, systemVars2) {
|
|
2200
|
+
if (!isWatermarkVisible(wm)) return null;
|
|
2201
|
+
const text = resolveWatermarkText(wm, printData, systemVars2);
|
|
2202
|
+
if (!text) return null;
|
|
2203
|
+
const tile = resolveTileSize(wm);
|
|
2204
|
+
const tileWidthMm = round4(tile.width * MM_PER_PX);
|
|
2205
|
+
const tileHeightMm = round4(tile.height * MM_PER_PX);
|
|
2206
|
+
const paper = {
|
|
2207
|
+
width: Math.max(0, Number.isFinite(paperMm?.width) ? paperMm.width : 0),
|
|
2208
|
+
height: Math.max(0, Number.isFinite(paperMm?.height) ? paperMm.height : 0)
|
|
2209
|
+
};
|
|
2210
|
+
const columns = Math.max(1, Math.ceil(paper.width / tileWidthMm));
|
|
2211
|
+
const rows = Math.max(1, Math.ceil(paper.height / tileHeightMm));
|
|
2212
|
+
const tiles = [];
|
|
2213
|
+
for (let r = 0; r < rows; r++) {
|
|
2214
|
+
for (let c = 0; c < columns; c++) {
|
|
2215
|
+
tiles.push({
|
|
2216
|
+
leftMm: round4(c * tileWidthMm),
|
|
2217
|
+
topMm: round4(r * tileHeightMm),
|
|
2218
|
+
widthMm: tileWidthMm,
|
|
2219
|
+
heightMm: tileHeightMm
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
return {
|
|
2224
|
+
text,
|
|
2225
|
+
color: wm?.color || WATERMARK_DEFAULTS.color,
|
|
2226
|
+
rotate: wm?.rotate ?? WATERMARK_DEFAULTS.rotate,
|
|
2227
|
+
opacity: wm?.opacity ?? WATERMARK_DEFAULTS.opacity,
|
|
2228
|
+
fontSizePx: WATERMARK_DEFAULTS.fontSize,
|
|
2229
|
+
tileWidthPx: tile.width,
|
|
2230
|
+
tileHeightPx: tile.height,
|
|
2231
|
+
tileWidthMm,
|
|
2232
|
+
tileHeightMm,
|
|
2233
|
+
columns,
|
|
2234
|
+
rows,
|
|
2235
|
+
tiles
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
function renderWatermarkTileSvg(layout, tile) {
|
|
2239
|
+
const tileWidthPx = layout.tileWidthPx;
|
|
2240
|
+
const tileHeightPx = layout.tileHeightPx;
|
|
2241
|
+
const cx = tileWidthPx / 2;
|
|
2242
|
+
const cy = tileHeightPx / 2;
|
|
2243
|
+
const style = `left:${tile.leftMm}mm;top:${tile.topMm}mm;width:${tile.widthMm}mm;height:${tile.heightMm}mm`;
|
|
2244
|
+
return `<svg class="watermark-tile" style="${style}" viewBox="0 0 ${tileWidthPx} ${tileHeightPx}" xmlns="http://www.w3.org/2000/svg"><text x="${cx}" y="${cy}" font-size="${layout.fontSizePx}" fill="${layout.color}" text-anchor="middle" dominant-baseline="middle" transform="rotate(${layout.rotate},${cx},${cy})">${escXml(layout.text)}</text></svg>`;
|
|
2245
|
+
}
|
|
2246
|
+
function renderWatermarkLayerHtml(wm, printData, paperMm, systemVars2) {
|
|
2247
|
+
const layout = resolveWatermarkLayout(wm, printData, paperMm, systemVars2);
|
|
2248
|
+
if (!layout) return "";
|
|
2249
|
+
const tiles = layout.tiles.map((t) => renderWatermarkTileSvg(layout, t)).join("\n");
|
|
2250
|
+
return `
|
|
2251
|
+
<div class="watermark-layer" style="opacity:${layout.opacity}">
|
|
2252
|
+
${tiles}
|
|
2253
|
+
</div>`;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
// src/render/pagination-engine.ts
|
|
2257
|
+
function paginationOf(el) {
|
|
2258
|
+
return el.options?.pagination ?? el.pagination;
|
|
2259
|
+
}
|
|
2260
|
+
function tablePaginationOf(el) {
|
|
2261
|
+
return el.options?.tablePagination ?? el.tablePagination;
|
|
2262
|
+
}
|
|
2263
|
+
var SAFETY_MARGIN = 2;
|
|
2264
|
+
function isTableEl(el) {
|
|
2265
|
+
return el.type === "table" || el.printElementType?.type === "table";
|
|
2266
|
+
}
|
|
2267
|
+
function tableDesignBottom(el) {
|
|
2268
|
+
const opts = el.options ?? {};
|
|
2269
|
+
const top = opts.top ?? 0;
|
|
2270
|
+
const rows = opts.tableRows ?? [];
|
|
2271
|
+
if (rows.length > 0) {
|
|
2272
|
+
return top + rows.reduce((s, r) => s + (r.height ?? 0), 0);
|
|
2273
|
+
}
|
|
2274
|
+
return top + (opts.height ?? 0);
|
|
2275
|
+
}
|
|
2276
|
+
function buildFollowMap(sorted, excludedIds) {
|
|
2277
|
+
const map = /* @__PURE__ */ new Map();
|
|
1094
2278
|
let currentTableId = null;
|
|
1095
2279
|
let currentTableBottom = -1;
|
|
1096
2280
|
for (const el of sorted) {
|
|
@@ -1100,6 +2284,7 @@ function buildFollowMap(sorted) {
|
|
|
1100
2284
|
map.set(el.id, []);
|
|
1101
2285
|
continue;
|
|
1102
2286
|
}
|
|
2287
|
+
if (excludedIds?.has(el.id)) continue;
|
|
1103
2288
|
const top = el.options?.top ?? 0;
|
|
1104
2289
|
if (currentTableId && top >= currentTableBottom) {
|
|
1105
2290
|
map.get(currentTableId).push(el.id);
|
|
@@ -1117,28 +2302,110 @@ function followGroupHeight(el, members, template, _measuredElements) {
|
|
|
1117
2302
|
}
|
|
1118
2303
|
return Math.max(maxBottom - tableBottom, 0);
|
|
1119
2304
|
}
|
|
2305
|
+
function effectiveHeight(el, measured) {
|
|
2306
|
+
return Math.max(el.options?.height ?? 0, measured.get(el.id)?.measuredHeight ?? 0);
|
|
2307
|
+
}
|
|
2308
|
+
function buildPaginationUnits(sorted, measured, groupMap) {
|
|
2309
|
+
const unitOf = /* @__PURE__ */ new Map();
|
|
2310
|
+
for (const members of groupMap.values()) {
|
|
2311
|
+
if (members.length < 2) continue;
|
|
2312
|
+
const unit = {
|
|
2313
|
+
anchorId: members[0].id,
|
|
2314
|
+
ids: members.map((m) => m.id),
|
|
2315
|
+
forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false)
|
|
2316
|
+
};
|
|
2317
|
+
for (const m of members) unitOf.set(m.id, unit);
|
|
2318
|
+
}
|
|
2319
|
+
const eligible = sorted.filter(
|
|
2320
|
+
(el) => !isTableEl(el) && !el.options?.groupId && paginationOf(el)?.pageable !== false
|
|
2321
|
+
);
|
|
2322
|
+
const parent = /* @__PURE__ */ new Map();
|
|
2323
|
+
eligible.forEach((el) => parent.set(el.id, el.id));
|
|
2324
|
+
const find = (id) => {
|
|
2325
|
+
let root = id;
|
|
2326
|
+
while (parent.get(root) !== root) root = parent.get(root);
|
|
2327
|
+
let cur = id;
|
|
2328
|
+
while (parent.get(cur) !== root) {
|
|
2329
|
+
const next = parent.get(cur);
|
|
2330
|
+
parent.set(cur, root);
|
|
2331
|
+
cur = next;
|
|
2332
|
+
}
|
|
2333
|
+
return root;
|
|
2334
|
+
};
|
|
2335
|
+
const union = (a, b) => {
|
|
2336
|
+
const ra = find(a);
|
|
2337
|
+
const rb = find(b);
|
|
2338
|
+
if (ra !== rb) parent.set(rb, ra);
|
|
2339
|
+
};
|
|
2340
|
+
const active = [];
|
|
2341
|
+
for (const el of eligible) {
|
|
2342
|
+
const top = el.options?.top ?? 0;
|
|
2343
|
+
for (let i = active.length - 1; i >= 0; i--) {
|
|
2344
|
+
if (active[i].bottom <= top) active.splice(i, 1);
|
|
2345
|
+
}
|
|
2346
|
+
const bottom = top + effectiveHeight(el, measured);
|
|
2347
|
+
for (const a of active) {
|
|
2348
|
+
if (a.bottom > top) union(el.id, a.id);
|
|
2349
|
+
}
|
|
2350
|
+
active.push({ id: el.id, top, bottom });
|
|
2351
|
+
}
|
|
2352
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
2353
|
+
for (const el of eligible) {
|
|
2354
|
+
const root = find(el.id);
|
|
2355
|
+
const arr = clusters.get(root);
|
|
2356
|
+
if (arr) arr.push(el.id);
|
|
2357
|
+
else clusters.set(root, [el.id]);
|
|
2358
|
+
}
|
|
2359
|
+
for (const ids of clusters.values()) {
|
|
2360
|
+
if (ids.length < 2) continue;
|
|
2361
|
+
const unit = { anchorId: ids[0], ids, forceFirstPage: false };
|
|
2362
|
+
for (const id of ids) unitOf.set(id, unit);
|
|
2363
|
+
}
|
|
2364
|
+
return unitOf;
|
|
2365
|
+
}
|
|
1120
2366
|
function paginate(template, measuredElements) {
|
|
1121
2367
|
const paper = getPaperDimensions(template);
|
|
1122
2368
|
const { top: mt, bottom: mb } = template.margins;
|
|
1123
2369
|
const headerH = template.header?.height ?? 0;
|
|
1124
2370
|
const footerH = template.footer?.height ?? 0;
|
|
1125
2371
|
const overlayH = template.firstPageOverlay?.height ?? 0;
|
|
1126
|
-
const
|
|
1127
|
-
|
|
2372
|
+
const continuous = isContinuousPaper(template);
|
|
2373
|
+
const contentHeight = continuous ? Number.POSITIVE_INFINITY : paper.height - mt - mb - headerH - footerH;
|
|
2374
|
+
if (!continuous && contentHeight <= 0) {
|
|
1128
2375
|
throw new Error(
|
|
1129
2376
|
`\u9875\u9762\u53EF\u7528\u9AD8\u5EA6\u4E0D\u8DB3: paper=${paper.height}mm, margins=${mt + mb}mm, header=${headerH}mm, footer=${footerH}mm`
|
|
1130
2377
|
);
|
|
1131
2378
|
}
|
|
2379
|
+
const orderIndex = /* @__PURE__ */ new Map();
|
|
2380
|
+
template.elements.forEach((e, idx) => orderIndex.set(e.id, idx));
|
|
1132
2381
|
const sorted = [...template.elements].sort((a, b) => {
|
|
1133
2382
|
const topA = a.options?.top ?? 0;
|
|
1134
2383
|
const topB = b.options?.top ?? 0;
|
|
1135
|
-
return topA - topB;
|
|
2384
|
+
if (topA !== topB) return topA - topB;
|
|
2385
|
+
const zA = a.options?.zIndex ?? 0;
|
|
2386
|
+
const zB = b.options?.zIndex ?? 0;
|
|
2387
|
+
if (zA !== zB) return zA - zB;
|
|
2388
|
+
return (orderIndex.get(a.id) ?? 0) - (orderIndex.get(b.id) ?? 0);
|
|
1136
2389
|
});
|
|
1137
|
-
const
|
|
2390
|
+
const explicitGroupIds = /* @__PURE__ */ new Set();
|
|
2391
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
2392
|
+
for (const el of sorted) {
|
|
2393
|
+
if (isTableEl(el)) continue;
|
|
2394
|
+
const gid = el.options?.groupId;
|
|
2395
|
+
if (!gid) continue;
|
|
2396
|
+
explicitGroupIds.add(el.id);
|
|
2397
|
+
const arr = groupMap.get(gid);
|
|
2398
|
+
if (arr) arr.push(el);
|
|
2399
|
+
else groupMap.set(gid, [el]);
|
|
2400
|
+
}
|
|
2401
|
+
const followMap = buildFollowMap(sorted, explicitGroupIds);
|
|
1138
2402
|
const followOwner = /* @__PURE__ */ new Map();
|
|
1139
2403
|
for (const [tableId, members] of followMap) {
|
|
1140
2404
|
for (const m of members) followOwner.set(m, tableId);
|
|
1141
2405
|
}
|
|
2406
|
+
const elById = /* @__PURE__ */ new Map();
|
|
2407
|
+
for (const el of sorted) elById.set(el.id, el);
|
|
2408
|
+
const unitOf = buildPaginationUnits(sorted, measuredElements, groupMap);
|
|
1142
2409
|
const pages = [];
|
|
1143
2410
|
let currentPage = [];
|
|
1144
2411
|
let remaining = contentHeight - overlayH - SAFETY_MARGIN;
|
|
@@ -1150,12 +2417,26 @@ function paginate(template, measuredElements) {
|
|
|
1150
2417
|
function fullPageHeight() {
|
|
1151
2418
|
return isFirstPage ? contentHeight - overlayH - SAFETY_MARGIN : contentHeight - SAFETY_MARGIN;
|
|
1152
2419
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
2420
|
+
let overflowOnCurrent = false;
|
|
2421
|
+
function pushPage() {
|
|
2422
|
+
pages.push(
|
|
2423
|
+
overflowOnCurrent ? { pageIndex: pages.length, sections: [...currentPage], overflow: true } : { pageIndex: pages.length, sections: [...currentPage] }
|
|
2424
|
+
);
|
|
2425
|
+
}
|
|
2426
|
+
function noteOverflow(bottom) {
|
|
2427
|
+
if (bottom > contentHeight) overflowOnCurrent = true;
|
|
2428
|
+
}
|
|
2429
|
+
function finishPage(overflow = false) {
|
|
2430
|
+
if (currentPage.length === 0) {
|
|
2431
|
+
overflowOnCurrent = overflowOnCurrent || overflow;
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
pushPage();
|
|
1155
2435
|
currentPage = [];
|
|
1156
2436
|
remaining = contentHeight - SAFETY_MARGIN;
|
|
1157
2437
|
isFirstPage = false;
|
|
1158
2438
|
pageBroken = true;
|
|
2439
|
+
overflowOnCurrent = false;
|
|
1159
2440
|
}
|
|
1160
2441
|
let i = 0;
|
|
1161
2442
|
while (i < sorted.length) {
|
|
@@ -1164,19 +2445,32 @@ function paginate(template, measuredElements) {
|
|
|
1164
2445
|
i++;
|
|
1165
2446
|
continue;
|
|
1166
2447
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
2448
|
+
const unit = unitOf.get(el.id);
|
|
2449
|
+
if (unit && unit.anchorId !== el.id) {
|
|
2450
|
+
i++;
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
if (paginationOf(el)?.pageable === false || unit?.forceFirstPage) {
|
|
2454
|
+
if (unit) {
|
|
2455
|
+
for (const id of unit.ids) {
|
|
2456
|
+
currentPage.push({ elementId: id, type: "element", renderTop: elById.get(id)?.options?.top ?? 0 });
|
|
2457
|
+
}
|
|
2458
|
+
} else {
|
|
2459
|
+
currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
|
|
2460
|
+
}
|
|
1169
2461
|
i++;
|
|
1170
2462
|
continue;
|
|
1171
2463
|
}
|
|
1172
2464
|
if (isTableEl(el)) {
|
|
1173
2465
|
i = paginateTable(el, measuredElements, i);
|
|
2466
|
+
} else if (unit) {
|
|
2467
|
+
i = paginateUnit(unit, i);
|
|
1174
2468
|
} else {
|
|
1175
2469
|
i = paginateNonTable(el, measuredElements, sorted, i);
|
|
1176
2470
|
}
|
|
1177
2471
|
}
|
|
1178
2472
|
if (currentPage.length > 0) {
|
|
1179
|
-
|
|
2473
|
+
pushPage();
|
|
1180
2474
|
}
|
|
1181
2475
|
if (pages.length === 0) {
|
|
1182
2476
|
pages.push({ pageIndex: 0, sections: [] });
|
|
@@ -1185,6 +2479,8 @@ function paginate(template, measuredElements) {
|
|
|
1185
2479
|
function paginateNonTable(el, measured, sortedList, idx) {
|
|
1186
2480
|
const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
|
|
1187
2481
|
if (elHeight <= remaining) {
|
|
2482
|
+
const top2 = pageBroken ? 0 : el.options?.top ?? 0;
|
|
2483
|
+
noteOverflow(top2 + elHeight);
|
|
1188
2484
|
currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
|
|
1189
2485
|
remaining -= elHeight;
|
|
1190
2486
|
if (paginationOf(el)?.keepWithNext && idx + 1 < sortedList.length) {
|
|
@@ -1202,11 +2498,43 @@ function paginate(template, measuredElements) {
|
|
|
1202
2498
|
}
|
|
1203
2499
|
return idx + 1;
|
|
1204
2500
|
}
|
|
1205
|
-
|
|
2501
|
+
const top = pageBroken ? 0 : el.options?.top ?? 0;
|
|
2502
|
+
finishPage(top + elHeight > contentHeight);
|
|
1206
2503
|
currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
|
|
1207
2504
|
remaining -= elHeight;
|
|
1208
2505
|
return idx + 1;
|
|
1209
2506
|
}
|
|
2507
|
+
function paginateUnit(unit, idx) {
|
|
2508
|
+
const members = unit.ids.map((id) => elById.get(id)).filter((m) => !!m);
|
|
2509
|
+
let minTop = Number.POSITIVE_INFINITY;
|
|
2510
|
+
let maxBottom = Number.NEGATIVE_INFINITY;
|
|
2511
|
+
for (const m of members) {
|
|
2512
|
+
const top2 = m.options?.top ?? 0;
|
|
2513
|
+
minTop = Math.min(minTop, top2);
|
|
2514
|
+
maxBottom = Math.max(maxBottom, top2 + effectiveHeight(m, measuredElements));
|
|
2515
|
+
}
|
|
2516
|
+
const unitHeight = Math.max(maxBottom - minTop, 0);
|
|
2517
|
+
const place = () => {
|
|
2518
|
+
const offset = pageBroken ? -minTop : 0;
|
|
2519
|
+
noteOverflow(minTop + offset + unitHeight);
|
|
2520
|
+
for (const m of members) {
|
|
2521
|
+
currentPage.push({
|
|
2522
|
+
elementId: m.id,
|
|
2523
|
+
type: "element",
|
|
2524
|
+
renderTop: (m.options?.top ?? 0) + offset
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
remaining -= unitHeight;
|
|
2528
|
+
};
|
|
2529
|
+
if (unitHeight <= remaining) {
|
|
2530
|
+
place();
|
|
2531
|
+
return idx + 1;
|
|
2532
|
+
}
|
|
2533
|
+
const top = pageBroken ? 0 : minTop;
|
|
2534
|
+
finishPage(top + unitHeight > contentHeight);
|
|
2535
|
+
place();
|
|
2536
|
+
return idx + 1;
|
|
2537
|
+
}
|
|
1210
2538
|
function paginateTable(el, measured, idx) {
|
|
1211
2539
|
const opts = el.options ?? {};
|
|
1212
2540
|
const renderRows = opts._renderRows ?? [];
|
|
@@ -1373,17 +2701,27 @@ function specialRowHeight(renderRows, rowHeights, type) {
|
|
|
1373
2701
|
}
|
|
1374
2702
|
|
|
1375
2703
|
// src/render/html-generator.ts
|
|
2704
|
+
function pageVarsContext(vars) {
|
|
2705
|
+
return { ...vars.data ?? {}, pageIndex: vars.pageIndex, totalPages: vars.totalPages };
|
|
2706
|
+
}
|
|
2707
|
+
function pageVarsData(printData) {
|
|
2708
|
+
if (Array.isArray(printData)) return printData[0] ?? {};
|
|
2709
|
+
return printData ?? {};
|
|
2710
|
+
}
|
|
1376
2711
|
function generateHtml(template, pageLayouts, printData, options) {
|
|
1377
|
-
const css = buildPageCss(template);
|
|
2712
|
+
const css = buildFontFaceCss(template.fonts) + buildPageCss(template, options?.pageHeightMm);
|
|
1378
2713
|
const isMeasure = options?.isMeasurementPass === true;
|
|
1379
2714
|
const totalPages = isMeasure ? 1 : pageLayouts.length;
|
|
1380
|
-
const ctx = {
|
|
2715
|
+
const ctx = {
|
|
2716
|
+
codeRenderer: options?.codeRenderer,
|
|
2717
|
+
pageHeightMm: options?.pageHeightMm
|
|
2718
|
+
};
|
|
1381
2719
|
if (isMeasure) {
|
|
1382
|
-
return generateMeasurementHtml(template, css, ctx);
|
|
2720
|
+
return generateMeasurementHtml(template, css, ctx, printData);
|
|
1383
2721
|
}
|
|
1384
|
-
return generateFinalHtml(template, pageLayouts, css, totalPages, ctx);
|
|
2722
|
+
return generateFinalHtml(template, pageLayouts, css, totalPages, ctx, printData);
|
|
1385
2723
|
}
|
|
1386
|
-
function generateMeasurementHtml(template, css, ctx) {
|
|
2724
|
+
function generateMeasurementHtml(template, css, ctx, printData) {
|
|
1387
2725
|
const paper = getPaperDims(template);
|
|
1388
2726
|
const contentWidth = paper.width - template.margins.left - template.margins.right;
|
|
1389
2727
|
const elementsHtml = template.elements.map((el) => renderElement(el, true, void 0, void 0, ctx)).join("\n");
|
|
@@ -1405,6 +2743,7 @@ function generateMeasurementHtml(template, css, ctx) {
|
|
|
1405
2743
|
</head>
|
|
1406
2744
|
<body class="measure-mode">
|
|
1407
2745
|
<section class="print-page" data-measure-page="0">
|
|
2746
|
+
${renderWatermarkLayerHtml(template.watermark, printData, getPaperDims(template))}
|
|
1408
2747
|
<div class="page-header">${headerHtml}</div>
|
|
1409
2748
|
<div class="first-page-overlay">${overlayHtml}</div>
|
|
1410
2749
|
<div class="content-area" style="height:auto;overflow:visible;">
|
|
@@ -1416,54 +2755,111 @@ function generateMeasurementHtml(template, css, ctx) {
|
|
|
1416
2755
|
html = injectSystemVariables(html);
|
|
1417
2756
|
return html;
|
|
1418
2757
|
}
|
|
1419
|
-
function
|
|
2758
|
+
function renderFinalPages(template, pageLayouts, printData, ctx, options) {
|
|
2759
|
+
const pageOffset = options?.pageOffset ?? 0;
|
|
2760
|
+
const totalPages = options?.totalPages ?? pageLayouts.length;
|
|
1420
2761
|
const pagesHtml = pageLayouts.map((page) => {
|
|
1421
|
-
const pageNum = page.pageIndex + 1;
|
|
1422
|
-
return renderPage(template, page, pageNum, totalPages, ctx);
|
|
2762
|
+
const pageNum = page.pageIndex + 1 + pageOffset;
|
|
2763
|
+
return renderPage(template, page, pageNum, totalPages, ctx, printData, options?.pageClass);
|
|
1423
2764
|
}).join("\n");
|
|
1424
|
-
|
|
2765
|
+
return injectSystemVariables(pagesHtml);
|
|
2766
|
+
}
|
|
2767
|
+
function wrapHtmlDocument(css, bodyInnerHtml, bodyClass) {
|
|
2768
|
+
const html = `<!DOCTYPE html>
|
|
1425
2769
|
<html lang="zh-CN">
|
|
1426
2770
|
<head>
|
|
1427
2771
|
<meta charset="UTF-8">
|
|
1428
2772
|
<style>${css}</style>
|
|
1429
2773
|
</head>
|
|
1430
|
-
<body>
|
|
1431
|
-
${
|
|
2774
|
+
<body${bodyClass ? ` class="${bodyClass}"` : ""}>
|
|
2775
|
+
${bodyInnerHtml}
|
|
1432
2776
|
</body>
|
|
1433
2777
|
</html>`;
|
|
1434
|
-
|
|
1435
|
-
|
|
2778
|
+
return injectSystemVariables(html);
|
|
2779
|
+
}
|
|
2780
|
+
function generateFinalHtml(template, pageLayouts, css, _totalPages, ctx, printData) {
|
|
2781
|
+
const bodyInner = renderFinalPages(template, pageLayouts, printData ?? {}, ctx);
|
|
2782
|
+
return wrapHtmlDocument(
|
|
2783
|
+
css,
|
|
2784
|
+
bodyInner,
|
|
2785
|
+
isContinuousPaper(template) ? "continuous" : void 0
|
|
2786
|
+
);
|
|
1436
2787
|
}
|
|
1437
|
-
function renderPage(template, page, pageNum, totalPages, ctx) {
|
|
2788
|
+
function renderPage(template, page, pageNum, totalPages, ctx, printData, pageClass) {
|
|
1438
2789
|
const paper = getPaperDims(template);
|
|
2790
|
+
const paperMm = {
|
|
2791
|
+
width: paper.width,
|
|
2792
|
+
height: ctx.pageHeightMm && ctx.pageHeightMm > 0 ? ctx.pageHeightMm : paper.height
|
|
2793
|
+
};
|
|
1439
2794
|
const contentWidth = paper.width - template.margins.left - template.margins.right;
|
|
2795
|
+
const pageVars = { pageIndex: pageNum, totalPages, data: pageVarsData(printData) };
|
|
2796
|
+
const scoped = withPageNumbers(template, pageVars);
|
|
2797
|
+
const pageCtx = { ...ctx, pageVars };
|
|
1440
2798
|
const headerHtml = renderAreaElements(
|
|
1441
|
-
|
|
2799
|
+
scoped.header?.elements ?? [],
|
|
1442
2800
|
contentWidth,
|
|
1443
2801
|
pageNum,
|
|
1444
2802
|
totalPages,
|
|
1445
|
-
|
|
2803
|
+
pageCtx
|
|
1446
2804
|
);
|
|
1447
2805
|
const footerHtml = renderAreaElements(
|
|
1448
|
-
|
|
2806
|
+
scoped.footer?.elements ?? [],
|
|
1449
2807
|
contentWidth,
|
|
1450
2808
|
pageNum,
|
|
1451
2809
|
totalPages,
|
|
1452
|
-
|
|
2810
|
+
pageCtx
|
|
1453
2811
|
);
|
|
1454
|
-
const overlayHtml = page.pageIndex === 0 ? `<div class="first-page-overlay">${renderAreaElements(
|
|
1455
|
-
let contentHtml = page.sections.map((section) => renderSection(section,
|
|
2812
|
+
const overlayHtml = page.pageIndex === 0 ? `<div class="first-page-overlay">${renderAreaElements(scoped.firstPageOverlay?.elements ?? [], contentWidth, pageNum, totalPages, pageCtx)}</div>` : "";
|
|
2813
|
+
let contentHtml = page.sections.map((section) => renderSection(section, scoped, pageCtx)).join("\n");
|
|
1456
2814
|
contentHtml = contentHtml.replace(/\{pageIndex\}/g, String(pageNum));
|
|
1457
2815
|
contentHtml = contentHtml.replace(/\{totalPages\}/g, String(totalPages));
|
|
1458
|
-
|
|
2816
|
+
const pageInner = `
|
|
2817
|
+
${renderWatermarkLayerHtml(template.watermark, printData, paperMm, { pageIndex: pageNum, totalPages })}
|
|
1459
2818
|
<div class="page-header">${headerHtml}</div>
|
|
1460
2819
|
${overlayHtml}
|
|
1461
2820
|
<div class="content-area">
|
|
1462
2821
|
${contentHtml}
|
|
1463
2822
|
</div>
|
|
1464
|
-
<div class="page-footer">${footerHtml}</div
|
|
2823
|
+
<div class="page-footer">${footerHtml}</div>`;
|
|
2824
|
+
const rot = getOutputRotationAngle(template);
|
|
2825
|
+
const pageBody = rot !== 0 ? `<div class="print-page-rotor print-page-rotor-${rot}">${pageInner}</div>` : pageInner;
|
|
2826
|
+
return `<section class="print-page${pageClass ? ` ${pageClass}` : ""}" data-page="${pageNum}">
|
|
2827
|
+
${pageBody}
|
|
1465
2828
|
</section>`;
|
|
1466
2829
|
}
|
|
2830
|
+
function withPageNumbers(template, vars) {
|
|
2831
|
+
if (!templateReferencesPageNumbers(template)) return template;
|
|
2832
|
+
const context = pageVarsContext(vars);
|
|
2833
|
+
const mapEl = (el) => {
|
|
2834
|
+
const raw = el.options?.rawFormatter;
|
|
2835
|
+
if (typeof raw !== "string") return el;
|
|
2836
|
+
return { ...el, options: { ...el.options, formatter: evaluateTemplate(raw, context) } };
|
|
2837
|
+
};
|
|
2838
|
+
const mapArea = (area) => area ? { ...area, elements: (area.elements ?? []).map(mapEl) } : area;
|
|
2839
|
+
return {
|
|
2840
|
+
...template,
|
|
2841
|
+
elements: template.elements.map(mapEl),
|
|
2842
|
+
header: mapArea(template.header),
|
|
2843
|
+
footer: mapArea(template.footer),
|
|
2844
|
+
firstPageOverlay: mapArea(template.firstPageOverlay)
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
function templateReferencesPageNumbers(template) {
|
|
2848
|
+
const areas = [
|
|
2849
|
+
template.elements,
|
|
2850
|
+
template.header?.elements,
|
|
2851
|
+
template.footer?.elements,
|
|
2852
|
+
template.firstPageOverlay?.elements
|
|
2853
|
+
];
|
|
2854
|
+
return areas.some((list) => (list ?? []).some((el) => typeof el.options?.rawFormatter === "string"));
|
|
2855
|
+
}
|
|
2856
|
+
function resolveCellContent(cell, ctx) {
|
|
2857
|
+
const raw = cell.rawFormatter;
|
|
2858
|
+
if (typeof raw === "string" && raw !== "" && ctx?.pageVars) {
|
|
2859
|
+
return evaluateTemplate(raw, pageVarsContext(ctx.pageVars));
|
|
2860
|
+
}
|
|
2861
|
+
return cell.content;
|
|
2862
|
+
}
|
|
1467
2863
|
function renderSection(section, template, ctx) {
|
|
1468
2864
|
const el = findElement(template, section.elementId);
|
|
1469
2865
|
if (!el) {
|
|
@@ -1481,8 +2877,9 @@ var V_ALIGN_FLEX = { top: "flex-start", middle: "center", bottom: "flex-end" };
|
|
|
1481
2877
|
var H_ALIGN_FLEX = { left: "flex-start", center: "center", right: "flex-end" };
|
|
1482
2878
|
function textStyle(opts) {
|
|
1483
2879
|
const parts = [];
|
|
1484
|
-
|
|
1485
|
-
if (
|
|
2880
|
+
const fontSize = opts._fitFontSize === void 0 ? opts.fontSize : roundFontSize(opts._fitFontSize);
|
|
2881
|
+
if (fontSize) parts.push(`font-size:${fontSize}pt`);
|
|
2882
|
+
if (opts.fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(opts.fontFamily))}`);
|
|
1486
2883
|
if (opts.fontWeight) parts.push(`font-weight:${opts.fontWeight}`);
|
|
1487
2884
|
if (opts.color) parts.push(`color:${opts.color}`);
|
|
1488
2885
|
if (opts.backgroundColor) parts.push(`background-color:${opts.backgroundColor}`);
|
|
@@ -1495,15 +2892,28 @@ function textStyle(opts) {
|
|
|
1495
2892
|
if (opts.textAlign) parts.push(`text-align:${opts.textAlign}`);
|
|
1496
2893
|
return parts.length ? parts.join(";") + ";" : "";
|
|
1497
2894
|
}
|
|
2895
|
+
function elementFitStyle(fit, opts) {
|
|
2896
|
+
if (fit === "autoHeight") return "overflow:visible;";
|
|
2897
|
+
if (opts.wordWrap === false) return "white-space:nowrap;text-overflow:ellipsis;";
|
|
2898
|
+
return "";
|
|
2899
|
+
}
|
|
2900
|
+
function fitAttrs(fit, key, baseFontSizePt, opts) {
|
|
2901
|
+
if (fit !== "shrink") return "";
|
|
2902
|
+
const min2 = resolveShrinkMinFontSize(opts.shrinkMinFontSize);
|
|
2903
|
+
return ` data-fit="shrink" data-fit-key="${esc(key)}" data-fit-base="${baseFontSizePt}" data-fit-min="${min2}"`;
|
|
2904
|
+
}
|
|
1498
2905
|
function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
|
|
1499
2906
|
const opts = el.options ?? {};
|
|
1500
2907
|
const left = opts.left ?? 0;
|
|
1501
2908
|
const top = overrideTop ?? opts.top ?? 0;
|
|
1502
2909
|
const width = opts.width ?? 100;
|
|
1503
2910
|
const height = opts.height ?? void 0;
|
|
1504
|
-
const style = containerStyle ?? elementPositionStyle(left, top, width, height, opts.zIndex);
|
|
1505
|
-
const measureAttr = isMeasure ? ` data-measure-id="${el.id}"` : "";
|
|
1506
2911
|
const type = el.type || el.printElementType?.type || "text";
|
|
2912
|
+
const fit = resolveElementTextFit(type, opts);
|
|
2913
|
+
const fitHeight = fit === "autoHeight" ? void 0 : height;
|
|
2914
|
+
const style = containerStyle ?? elementPositionStyle(left, top, width, fitHeight, opts.zIndex);
|
|
2915
|
+
const measureAttr = isMeasure ? ` data-measure-id="${el.id}"` : "";
|
|
2916
|
+
const fitAttr = fitAttrs(fit, el.id, opts.fontSize ?? 12, opts);
|
|
1507
2917
|
switch (type) {
|
|
1508
2918
|
case "table":
|
|
1509
2919
|
return renderTableElement(el, isMeasure, measureAttr, ctx);
|
|
@@ -1514,9 +2924,17 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
|
|
|
1514
2924
|
case "barcode":
|
|
1515
2925
|
case "qrcode": {
|
|
1516
2926
|
const codeValue = String(opts.formatter ?? opts.testData ?? "").trim();
|
|
1517
|
-
const fill = type === "barcode";
|
|
1518
2927
|
return `<div class="print-element" style="${style}"${measureAttr}>
|
|
1519
|
-
${codeImgHtml(codeValue, type, opts,
|
|
2928
|
+
${codeImgHtml(codeValue, type, opts, {
|
|
2929
|
+
fallback: `<span>${esc(codeValue)}</span>`,
|
|
2930
|
+
codeRenderer: ctx?.codeRenderer,
|
|
2931
|
+
fit: opts.fit,
|
|
2932
|
+
maxWidth: opts.maxWidth,
|
|
2933
|
+
maxHeight: opts.maxHeight,
|
|
2934
|
+
printerDpi: opts.printerDpi,
|
|
2935
|
+
targetWidthMm: opts.width,
|
|
2936
|
+
targetHeightMm: opts.height
|
|
2937
|
+
})}
|
|
1520
2938
|
</div>`;
|
|
1521
2939
|
}
|
|
1522
2940
|
case "hline":
|
|
@@ -1528,30 +2946,32 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
|
|
|
1528
2946
|
case "oval":
|
|
1529
2947
|
return `<div class="print-element" style="${style};border:${opts.borderWidth ?? 1}px solid ${opts.borderColor ?? "#000"};border-radius:50%;"${measureAttr}></div>`;
|
|
1530
2948
|
case "longText":
|
|
1531
|
-
return `<div class="print-element" style="${style}${textStyle(opts)}
|
|
2949
|
+
return `<div class="print-element" style="${style}${textStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
|
|
1532
2950
|
case "html":
|
|
1533
2951
|
return `<div class="print-element" style="${style}"${measureAttr}>${opts.testData ?? opts.title ?? ""}</div>`;
|
|
1534
2952
|
default:
|
|
1535
|
-
return `<div class="print-element" style="${style}${textStyle(opts)}"${measureAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
|
|
2953
|
+
return `<div class="print-element" style="${style}${textStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
|
|
1536
2954
|
}
|
|
1537
2955
|
}
|
|
1538
|
-
function codeImgHtml(value, cellType, opts,
|
|
1539
|
-
if (!value || !codeRenderer) return
|
|
2956
|
+
function codeImgHtml(value, cellType, opts, io) {
|
|
2957
|
+
if (!value || !io.codeRenderer) return io.fallback;
|
|
2958
|
+
const { fallback, codeRenderer, fit, maxWidth, maxHeight } = io;
|
|
2959
|
+
const isBarcode = cellType === "barcode";
|
|
1540
2960
|
try {
|
|
1541
2961
|
const svg = codeRenderer.render(value, cellType, {
|
|
1542
2962
|
barcodeType: opts.barcodeType,
|
|
1543
2963
|
qrCodeLevel: opts.qrCodeLevel != null ? String(opts.qrCodeLevel) : void 0,
|
|
1544
2964
|
showText: opts.hideTitle !== void 0 ? !opts.hideTitle : opts.showBarcodeText,
|
|
1545
2965
|
barWidth: typeof opts.barWidth === "number" ? opts.barWidth : void 0,
|
|
1546
|
-
fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0
|
|
2966
|
+
fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0,
|
|
2967
|
+
printerDpi: isBarcode ? io.printerDpi : void 0,
|
|
2968
|
+
targetWidthMm: isBarcode ? barcodeAvailableBoxMm(io.targetWidthMm, maxWidth) : void 0,
|
|
2969
|
+
targetHeightMm: isBarcode ? barcodeAvailableBoxMm(io.targetHeightMm, maxHeight) : void 0
|
|
1547
2970
|
});
|
|
2971
|
+
if (isBarcode && /^<svg[\s>]/i.test(svg)) return inlineCodeSvgHtml(svg);
|
|
1548
2972
|
const src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
1549
2973
|
const styleParts = [];
|
|
1550
|
-
|
|
1551
|
-
styleParts.push("width:100%", "height:100%", "object-fit:contain", "display:block", "margin:auto");
|
|
1552
|
-
} else {
|
|
1553
|
-
styleParts.push("max-width:100%", "max-height:100%", "display:block", "margin:auto");
|
|
1554
|
-
}
|
|
2974
|
+
styleParts.push("max-width:100%", "max-height:100%", "display:block", "margin:auto");
|
|
1555
2975
|
if (fit) {
|
|
1556
2976
|
styleParts.push(`object-fit:${fit}`);
|
|
1557
2977
|
}
|
|
@@ -1564,15 +2984,20 @@ function codeImgHtml(value, cellType, opts, fallbackHtml, fill = false, codeRend
|
|
|
1564
2984
|
const style = styleParts.join(";");
|
|
1565
2985
|
return `<img src="${src}" style="${style}" />`;
|
|
1566
2986
|
} catch {
|
|
1567
|
-
return
|
|
2987
|
+
return fallback;
|
|
1568
2988
|
}
|
|
1569
2989
|
}
|
|
2990
|
+
function inlineCodeSvgHtml(svg) {
|
|
2991
|
+
const styled = svg.replace(/<svg\b/, '<svg style="display:block"');
|
|
2992
|
+
return `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${styled}</div>`;
|
|
2993
|
+
}
|
|
1570
2994
|
function matrixCellStyle(cell, opts) {
|
|
1571
2995
|
const parts = [];
|
|
1572
|
-
const fontSize = cell.fontSize ?? opts.tableDefaultFontSize;
|
|
2996
|
+
const fontSize = cell.fittedFontSize ?? cell.fontSize ?? opts.tableDefaultFontSize;
|
|
1573
2997
|
const color = cell.color ?? opts.tableDefaultColor;
|
|
1574
2998
|
const padding = cell.padding ?? opts.tableDefaultPadding ?? 1;
|
|
1575
2999
|
if (fontSize) parts.push(`font-size:${fontSize}pt`);
|
|
3000
|
+
if (cell.fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(cell.fontFamily))}`);
|
|
1576
3001
|
if (cell.fontWeight) parts.push(`font-weight:${cell.fontWeight}`);
|
|
1577
3002
|
if (color) parts.push(`color:${color}`);
|
|
1578
3003
|
if (cell.backgroundColor) parts.push(`background-color:${cell.backgroundColor}`);
|
|
@@ -1587,25 +3012,38 @@ function matrixCellStyle(cell, opts) {
|
|
|
1587
3012
|
}
|
|
1588
3013
|
return parts.join(";");
|
|
1589
3014
|
}
|
|
1590
|
-
function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx) {
|
|
3015
|
+
function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }, rowLimit) {
|
|
1591
3016
|
const trs = [];
|
|
3017
|
+
const defaultPadding = opts.tableDefaultPadding ?? 1;
|
|
1592
3018
|
for (let r = start; r < end; r++) {
|
|
1593
3019
|
const row = renderRows[r];
|
|
1594
3020
|
if (!row) continue;
|
|
1595
3021
|
const idxAttr = withRowIndex ? ` data-row-index="${r}"` : "";
|
|
1596
|
-
const tds = row.cells.filter((cell) => !cell.merged).map((cell) => {
|
|
1597
|
-
const
|
|
3022
|
+
const tds = row.cells.map((cell, ci) => ({ cell, ci })).filter(({ cell }) => !cell.merged).map(({ cell, ci }) => {
|
|
3023
|
+
const rowspan = rowLimit === void 0 ? cell.rowspan ?? 1 : Math.max(1, Math.min(cell.rowspan ?? 1, rowLimit - r));
|
|
3024
|
+
const span = `${rowspan > 1 ? ` rowspan="${rowspan}"` : ""}${cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}`;
|
|
3025
|
+
const colIndex = ci;
|
|
1598
3026
|
let inner;
|
|
1599
3027
|
if (cell.cellType === "barcode" || cell.cellType === "qrcode") {
|
|
1600
|
-
const
|
|
1601
|
-
inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(
|
|
3028
|
+
const codeValue = resolveCellContent(cell, ctx);
|
|
3029
|
+
inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(codeValue, cell.cellType, cell, {
|
|
3030
|
+
fallback: esc(codeValue),
|
|
3031
|
+
codeRenderer: ctx?.codeRenderer,
|
|
3032
|
+
fit: cell.fit,
|
|
3033
|
+
maxWidth: cell.maxWidth,
|
|
3034
|
+
maxHeight: cell.maxHeight,
|
|
3035
|
+
printerDpi: cell.printerDpi,
|
|
3036
|
+
// 可用框 = 单元格内容区(列宽/行高扣除内边距与塌陷边框);列宽缺失时宽度为 0 → 不约束宽度
|
|
3037
|
+
targetWidthMm: cellFitWidthMm(opts.tableColWidths ?? [], ci, cell, defaultPadding),
|
|
3038
|
+
targetHeightMm: cellFitCapMm(renderRows, r, cell, defaultPadding)
|
|
3039
|
+
})}</div>`;
|
|
1602
3040
|
} else if (cell.cellType === "image") {
|
|
1603
3041
|
const fit = cell.fit || "contain";
|
|
1604
3042
|
const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
|
|
1605
3043
|
const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
|
|
1606
|
-
inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;"><img src="${esc(cell
|
|
3044
|
+
inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;"><img src="${esc(resolveCellContent(cell, ctx))}" style="object-fit:${fit};${maxWidth}${maxHeight}display:block;" /></div>`;
|
|
1607
3045
|
} else {
|
|
1608
|
-
inner =
|
|
3046
|
+
inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding, ctx);
|
|
1609
3047
|
}
|
|
1610
3048
|
return `<td${span} style="${matrixCellStyle(cell, opts)}">${inner}</td>`;
|
|
1611
3049
|
}).join("");
|
|
@@ -1613,6 +3051,20 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx) {
|
|
|
1613
3051
|
}
|
|
1614
3052
|
return trs.join("\n");
|
|
1615
3053
|
}
|
|
3054
|
+
function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding, ctx) {
|
|
3055
|
+
const fit = resolveCellTextFit(cell);
|
|
3056
|
+
const text = esc(resolveCellContent(cell, ctx));
|
|
3057
|
+
if (fit === "autoHeight") return text;
|
|
3058
|
+
const capMm = cellFitCapMm(renderRows, rowIndex, cell, defaultPadding);
|
|
3059
|
+
const nowrap = cell.wordWrap === false;
|
|
3060
|
+
const style = [
|
|
3061
|
+
`max-height:${capMm}mm`,
|
|
3062
|
+
"overflow:hidden",
|
|
3063
|
+
...nowrap ? ["white-space:nowrap", "text-overflow:ellipsis"] : []
|
|
3064
|
+
].join(";");
|
|
3065
|
+
const attrs = fit === "shrink" && fitOwner.elementId ? ` data-fit="shrink" data-fit-key="${esc(cellFitKey(fitOwner.elementId, fitOwner.kind, rowIndex, colIndex))}" data-fit-base="${cell.fontSize ?? opts.tableDefaultFontSize ?? 12}" data-fit-min="${resolveShrinkMinFontSize(cell.shrinkMinFontSize)}" data-fit-mm="${capMm}"` : "";
|
|
3066
|
+
return `<div class="cell-fit" style="${style}"${attrs}>${text}</div>`;
|
|
3067
|
+
}
|
|
1616
3068
|
function matrixTableHtml(el, bodyHtml) {
|
|
1617
3069
|
const opts = el.options;
|
|
1618
3070
|
const colWidths = opts.tableColWidths ?? [];
|
|
@@ -1627,7 +3079,7 @@ function renderTableElement(el, isMeasure, measureAttr, ctx) {
|
|
|
1627
3079
|
const opts = el.options;
|
|
1628
3080
|
const style = elementPositionStyle(opts.left ?? 0, opts.top ?? 0, opts.width ?? 100, void 0, opts.zIndex);
|
|
1629
3081
|
const renderRows = opts._renderRows ?? [];
|
|
1630
|
-
const bodyHtml = renderMatrixRows(renderRows, 0, renderRows.length, opts, isMeasure, ctx);
|
|
3082
|
+
const bodyHtml = renderMatrixRows(renderRows, 0, renderRows.length, opts, isMeasure, ctx, { elementId: el.id, kind: "b" });
|
|
1631
3083
|
return `<div class="print-element" style="${style};overflow:visible;"${measureAttr}>
|
|
1632
3084
|
${matrixTableHtml(el, bodyHtml)}
|
|
1633
3085
|
</div>`;
|
|
@@ -1639,8 +3091,8 @@ function renderTableSlice(el, section, ctx) {
|
|
|
1639
3091
|
const startRow = section.startRow ?? 0;
|
|
1640
3092
|
const endRow = section.endRow ?? renderRows.length;
|
|
1641
3093
|
const repeatCount = opts._repeatHeaderCount ?? 0;
|
|
1642
|
-
const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx) : "";
|
|
1643
|
-
const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx);
|
|
3094
|
+
const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
|
|
3095
|
+
const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
|
|
1644
3096
|
const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
|
|
1645
3097
|
const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
|
|
1646
3098
|
return `<div class="print-element" style="${style};overflow:visible;">
|
|
@@ -1660,16 +3112,22 @@ function renderSubtotalRows(el, section, opts, ctx) {
|
|
|
1660
3112
|
const dataStart = Math.max(startRow, dataStartIdx);
|
|
1661
3113
|
const dataEnd = Math.max(endRow, dataStart);
|
|
1662
3114
|
const pageCtx = dataEnd > dataStartIdx ? dataRowCtx.slice(Math.max(dataStart - dataStartIdx, 0), dataEnd - dataStartIdx) : [];
|
|
3115
|
+
const pageVars = ctx?.pageVars ? pageVarsContext(ctx.pageVars) : {};
|
|
1663
3116
|
const rows = templates.map((tpl) => ({
|
|
1664
3117
|
...tpl,
|
|
1665
|
-
cells: tpl.cells.map((cell) => cell.rawFormatter ? {
|
|
3118
|
+
cells: tpl.cells.map((cell) => cell.rawFormatter ? {
|
|
3119
|
+
...cell,
|
|
3120
|
+
content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData, ...pageVars }),
|
|
3121
|
+
// 已按本页上下文求值,清标记避免渲染时再按页码重算一次(会丢掉 rows)
|
|
3122
|
+
rawFormatter: ""
|
|
3123
|
+
} : cell)
|
|
1666
3124
|
}));
|
|
1667
|
-
return renderMatrixRows(rows, 0, rows.length, opts, false, ctx);
|
|
3125
|
+
return renderMatrixRows(rows, 0, rows.length, opts, false, ctx, { elementId: el.id, kind: "st" });
|
|
1668
3126
|
}
|
|
1669
3127
|
function renderSummaryRows(el, opts, ctx) {
|
|
1670
3128
|
const summaryRows = opts._summaryRows ?? [];
|
|
1671
3129
|
if (summaryRows.length === 0) return "";
|
|
1672
|
-
return renderMatrixRows(summaryRows, 0, summaryRows.length, opts, false, ctx);
|
|
3130
|
+
return renderMatrixRows(summaryRows, 0, summaryRows.length, opts, false, ctx, { elementId: el.id, kind: "sm" });
|
|
1673
3131
|
}
|
|
1674
3132
|
function renderFlowGroup(el, section, template, ctx) {
|
|
1675
3133
|
const opts = el.options ?? {};
|
|
@@ -1685,8 +3143,8 @@ function renderFlowGroup(el, section, template, ctx) {
|
|
|
1685
3143
|
if (endRow > startRow || section.subtotal || section.summary) {
|
|
1686
3144
|
const renderRows = opts._renderRows ?? [];
|
|
1687
3145
|
const repeatCount = opts._repeatHeaderCount ?? 0;
|
|
1688
|
-
const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx) : "";
|
|
1689
|
-
const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx);
|
|
3146
|
+
const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
|
|
3147
|
+
const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
|
|
1690
3148
|
const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
|
|
1691
3149
|
const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
|
|
1692
3150
|
sliceHtml = `<div class="flow-slice" style="position:relative;width:${mm(tableWidth)};overflow:visible;">
|
|
@@ -1754,167 +3212,707 @@ function esc(str) {
|
|
|
1754
3212
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1755
3213
|
}
|
|
1756
3214
|
|
|
1757
|
-
// src/
|
|
1758
|
-
var
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
const
|
|
1762
|
-
const
|
|
1763
|
-
|
|
1764
|
-
const html = generateHtml(boundTemplate, pageLayouts, printData, {
|
|
1765
|
-
codeRenderer
|
|
1766
|
-
});
|
|
1767
|
-
return { html, pageCount: pageLayouts.length, pageLayouts };
|
|
3215
|
+
// src/render/continuous-paper.ts
|
|
3216
|
+
var MIN_CONTINUOUS_HEIGHT_MM = 25.4;
|
|
3217
|
+
function composeContinuousHeight(template, contentBottomMm) {
|
|
3218
|
+
const mb = template.margins?.bottom ?? 0;
|
|
3219
|
+
const footerH = template.footer?.height ?? 0;
|
|
3220
|
+
const height = Math.max(contentBottomMm, 0) + footerH + mb;
|
|
3221
|
+
return Math.max(MIN_CONTINUOUS_HEIGHT_MM, Math.round(height * 100) / 100);
|
|
1768
3222
|
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
const measuredMap = /* @__PURE__ */ new Map();
|
|
1789
|
-
const elementIndex = new Map(template.elements.map((el) => [el.id, el]));
|
|
1790
|
-
for (const m of measurements) {
|
|
1791
|
-
const el = elementIndex.get(m.id);
|
|
1792
|
-
const repeatCount = el?.options?._repeatHeaderCount ?? 0;
|
|
1793
|
-
measuredMap.set(m.id, {
|
|
1794
|
-
id: m.id,
|
|
1795
|
-
measuredHeight: m.height,
|
|
1796
|
-
measuredRowHeights: m.rowHeights,
|
|
1797
|
-
repeatHeaderHeight: m.rowHeights && repeatCount > 0 ? m.rowHeights.slice(0, repeatCount).reduce((s, h) => s + h, 0) : 0
|
|
1798
|
-
});
|
|
3223
|
+
|
|
3224
|
+
// src/print/codes.ts
|
|
3225
|
+
function codeSpecKey(value, cellType, opts = {}) {
|
|
3226
|
+
return JSON.stringify([
|
|
3227
|
+
value,
|
|
3228
|
+
cellType,
|
|
3229
|
+
opts.barcodeType ?? null,
|
|
3230
|
+
opts.qrCodeLevel ?? null,
|
|
3231
|
+
opts.showText ?? null,
|
|
3232
|
+
opts.barWidth ?? null,
|
|
3233
|
+
opts.fontSize ?? null
|
|
3234
|
+
]);
|
|
3235
|
+
}
|
|
3236
|
+
function createMapCodeRenderer(map) {
|
|
3237
|
+
return {
|
|
3238
|
+
render(value, cellType, opts) {
|
|
3239
|
+
const svg = map.get(codeSpecKey(value, cellType, opts));
|
|
3240
|
+
if (!svg) throw new Error(`\u7801\u503C\u672A\u6E32\u67D3\uFF1A${value}`);
|
|
3241
|
+
return svg;
|
|
1799
3242
|
}
|
|
1800
|
-
|
|
1801
|
-
} finally {
|
|
1802
|
-
iframe.remove();
|
|
1803
|
-
}
|
|
3243
|
+
};
|
|
1804
3244
|
}
|
|
1805
|
-
function
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
const run = async () => {
|
|
1816
|
-
try {
|
|
1817
|
-
const fonts = win.document.fonts;
|
|
1818
|
-
if (fonts?.ready) {
|
|
1819
|
-
await Promise.race([fonts.ready, timeout(READY_TIMEOUT_MS)]);
|
|
1820
|
-
}
|
|
1821
|
-
await Promise.race([waitForImages(win.document), timeout(READY_TIMEOUT_MS)]);
|
|
1822
|
-
} catch {
|
|
1823
|
-
} finally {
|
|
1824
|
-
clearTimeout(timer);
|
|
1825
|
-
finish();
|
|
3245
|
+
function createCollectingCodeRenderer(base) {
|
|
3246
|
+
const collected = /* @__PURE__ */ new Map();
|
|
3247
|
+
return {
|
|
3248
|
+
renderer: {
|
|
3249
|
+
render(value, cellType, opts = {}) {
|
|
3250
|
+
const key = codeSpecKey(value, cellType, opts);
|
|
3251
|
+
const hit = base?.get(key);
|
|
3252
|
+
if (hit) return hit;
|
|
3253
|
+
collected.set(key, { key, value, cellType, opts });
|
|
3254
|
+
throw new Error("collect");
|
|
1826
3255
|
}
|
|
3256
|
+
},
|
|
3257
|
+
takeSpecs() {
|
|
3258
|
+
const specs = [...collected.values()];
|
|
3259
|
+
collected.clear();
|
|
3260
|
+
return specs;
|
|
3261
|
+
}
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
function mergeCodeMaps(...maps) {
|
|
3265
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3266
|
+
for (const map of maps) {
|
|
3267
|
+
for (const [key, svg] of map) merged.set(key, svg);
|
|
3268
|
+
}
|
|
3269
|
+
return merged;
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
// src/print/units.ts
|
|
3273
|
+
var PX_PER_MM2 = 3.7795275591;
|
|
3274
|
+
function pxToMm(px) {
|
|
3275
|
+
return px / PX_PER_MM2;
|
|
3276
|
+
}
|
|
3277
|
+
function mmToPx2(mm2) {
|
|
3278
|
+
return Math.round(mm2 * PX_PER_MM2);
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
// src/print/paper.ts
|
|
3282
|
+
function positive(value) {
|
|
3283
|
+
return typeof value === "number" && value > 0 ? value : void 0;
|
|
3284
|
+
}
|
|
3285
|
+
function escapeHeightMm(input) {
|
|
3286
|
+
return positive(input.paperHeightMm) ?? positive(input.override?.height);
|
|
3287
|
+
}
|
|
3288
|
+
function resolvePaperMm(input) {
|
|
3289
|
+
const width = positive(input.override?.width) ?? input.paperMm.width;
|
|
3290
|
+
const overrideHeight = positive(input.override?.height);
|
|
3291
|
+
if (input.continuous) {
|
|
3292
|
+
return overrideHeight ? { paperMm: { width, height: overrideHeight }, heightSource: "config" } : { paperMm: { width, height: input.paperMm.height }, heightSource: "derived" };
|
|
3293
|
+
}
|
|
3294
|
+
return { paperMm: { width, height: overrideHeight ?? input.paperMm.height }, heightSource: "config" };
|
|
3295
|
+
}
|
|
3296
|
+
function paperViewportPx(paper) {
|
|
3297
|
+
return { width: mmToPx2(paper.width), height: mmToPx2(paper.height) };
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
// src/print/measure.ts
|
|
3301
|
+
function normalizeMeasurements(raw, template) {
|
|
3302
|
+
const index = new Map(template.elements.map((el) => [el.id, el]));
|
|
3303
|
+
const measured = /* @__PURE__ */ new Map();
|
|
3304
|
+
for (const item of raw) {
|
|
3305
|
+
const element = index.get(item.id);
|
|
3306
|
+
const repeatCount = element?.options?._repeatHeaderCount ?? 0;
|
|
3307
|
+
const rowHeights = item.rowHeightsPx?.map(pxToMm);
|
|
3308
|
+
measured.set(item.id, {
|
|
3309
|
+
id: item.id,
|
|
3310
|
+
measuredHeight: pxToMm(item.heightPx),
|
|
3311
|
+
measuredRowHeights: rowHeights,
|
|
3312
|
+
repeatHeaderHeight: rowHeights && rowHeights.length > 0 && repeatCount > 0 ? rowHeights.slice(0, repeatCount).reduce((sum2, h) => sum2 + h, 0) : 0
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
return measured;
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
// src/print/apply-text-fit.ts
|
|
3319
|
+
var CELL_ROW_SOURCES = {
|
|
3320
|
+
b: "_renderRows",
|
|
3321
|
+
st: "_subtotalTemplates",
|
|
3322
|
+
sm: "_summaryRows"
|
|
3323
|
+
};
|
|
3324
|
+
function applyTextFitSizes(template, fits) {
|
|
3325
|
+
if (!fits || fits.length === 0) return;
|
|
3326
|
+
const index = /* @__PURE__ */ new Map();
|
|
3327
|
+
const collect = (elements) => {
|
|
3328
|
+
for (const el of elements ?? []) index.set(el.id, el);
|
|
3329
|
+
};
|
|
3330
|
+
collect(template.elements);
|
|
3331
|
+
collect(template.header?.elements);
|
|
3332
|
+
collect(template.footer?.elements);
|
|
3333
|
+
collect(template.firstPageOverlay?.elements);
|
|
3334
|
+
for (const fit of fits) {
|
|
3335
|
+
const parsed = parseCellFitKey(fit.key);
|
|
3336
|
+
if (!parsed) {
|
|
3337
|
+
const el2 = index.get(fit.key);
|
|
3338
|
+
if (el2) el2.options._fitFontSize = roundFontSize(fit.fontSizePt);
|
|
3339
|
+
continue;
|
|
3340
|
+
}
|
|
3341
|
+
const el = index.get(parsed.elementId);
|
|
3342
|
+
const rows = el?.options?.[CELL_ROW_SOURCES[parsed.kind]];
|
|
3343
|
+
const cell = rows?.[parsed.rowIndex]?.cells?.[parsed.colIndex];
|
|
3344
|
+
if (cell) cell.fittedFontSize = roundFontSize(fit.fontSizePt);
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
// src/print/normalize-print-data.ts
|
|
3349
|
+
var MAX_BATCH_COPIES = 500;
|
|
3350
|
+
function isPlainRecord(v) {
|
|
3351
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3352
|
+
}
|
|
3353
|
+
function normalizePrintData(raw) {
|
|
3354
|
+
if (raw === void 0) return { mode: "single", data: {} };
|
|
3355
|
+
if (!Array.isArray(raw)) return { mode: "single", data: raw };
|
|
3356
|
+
if (raw.length === 0) {
|
|
3357
|
+
throw new Error("\u6279\u91CF\u6253\u5370\u6570\u636E\u5FC5\u987B\u662F\u975E\u7A7A\u5BF9\u8C61\u6570\u7EC4");
|
|
3358
|
+
}
|
|
3359
|
+
if (raw.length > MAX_BATCH_COPIES) {
|
|
3360
|
+
throw new Error(`\u6279\u91CF\u6253\u5370\u6700\u591A\u652F\u6301 ${MAX_BATCH_COPIES} \u4EFD\uFF0C\u5F53\u524D ${raw.length} \u4EFD`);
|
|
3361
|
+
}
|
|
3362
|
+
for (let i = 0; i < raw.length; i++) {
|
|
3363
|
+
if (!isPlainRecord(raw[i])) {
|
|
3364
|
+
throw new Error(`\u6279\u91CF\u6253\u5370\u6570\u636E\u7B2C ${i + 1} \u9879\u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
return { mode: "batch", dataList: raw };
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
// src/print/batch-compose.ts
|
|
3371
|
+
function composeBatchHtml(copies) {
|
|
3372
|
+
const bound = copies[0].bound;
|
|
3373
|
+
const continuous = isContinuousPaper(bound);
|
|
3374
|
+
const bodyInner = copies.map((copy, i) => {
|
|
3375
|
+
const cls = continuous ? `print-copy print-copy-${i}` : "print-copy";
|
|
3376
|
+
const pages = renderFinalPages(copy.bound, copy.pageLayouts, copy.data, {
|
|
3377
|
+
codeRenderer: copy.codeRenderer,
|
|
3378
|
+
pageHeightMm: copy.derivedHeightMm
|
|
3379
|
+
});
|
|
3380
|
+
return `<section class="${cls}">
|
|
3381
|
+
${pages}
|
|
3382
|
+
</section>`;
|
|
3383
|
+
}).join("\n");
|
|
3384
|
+
const pageCss = continuous ? buildBatchPageCss(bound, copies.map((c) => ({ heightMm: c.derivedHeightMm }))) : `${buildPageCss(bound)}
|
|
3385
|
+
.print-copy:not(:last-child){break-after:page;page-break-after:always;}`;
|
|
3386
|
+
const css = buildFontFaceCss(bound.fonts) + pageCss;
|
|
3387
|
+
return {
|
|
3388
|
+
html: wrapHtmlDocument(
|
|
3389
|
+
css,
|
|
3390
|
+
bodyInner,
|
|
3391
|
+
continuous ? "continuous" : void 0
|
|
3392
|
+
),
|
|
3393
|
+
pageCount: copies.reduce((sum2, c) => sum2 + c.pageLayouts.length, 0),
|
|
3394
|
+
copyPaperMm: copies.map((c) => c.paperMm)
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
// src/print/tiling.ts
|
|
3399
|
+
var TILE_DEFAULTS = {
|
|
3400
|
+
enabled: true,
|
|
3401
|
+
sheetPaperSize: "A4",
|
|
3402
|
+
sheetOrientation: "portrait",
|
|
3403
|
+
sheetMargin: { top: 10, right: 10, bottom: 10, left: 10 },
|
|
3404
|
+
gapX: 2,
|
|
3405
|
+
gapY: 2,
|
|
3406
|
+
columns: 2
|
|
3407
|
+
};
|
|
3408
|
+
var TilingError = class extends Error {
|
|
3409
|
+
constructor(code, message) {
|
|
3410
|
+
super(message);
|
|
3411
|
+
this.name = "TilingError";
|
|
3412
|
+
this.code = code;
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
function roundMm(value) {
|
|
3416
|
+
return Math.round(value * 10) / 10;
|
|
3417
|
+
}
|
|
3418
|
+
function normalizeTilingOptions(t) {
|
|
3419
|
+
const raw = t.tiling;
|
|
3420
|
+
return {
|
|
3421
|
+
...TILE_DEFAULTS,
|
|
3422
|
+
...raw,
|
|
3423
|
+
sheetMargin: { ...TILE_DEFAULTS.sheetMargin, ...raw?.sheetMargin ?? {} }
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
function resolveSheetMm(t, opts) {
|
|
3427
|
+
const cfg = normalizeTilingOptions(t);
|
|
3428
|
+
const ov = opts?.paperOverride;
|
|
3429
|
+
if (ov && typeof ov.width === "number" && ov.width > 0 && typeof ov.height === "number" && ov.height > 0) {
|
|
3430
|
+
return { width: ov.width, height: ov.height };
|
|
3431
|
+
}
|
|
3432
|
+
if (cfg.sheetPaperSize === "CUSTOM") {
|
|
3433
|
+
return {
|
|
3434
|
+
width: cfg.sheetCustomWidth ?? PAPER_DIMENSIONS.A4.width,
|
|
3435
|
+
height: cfg.sheetCustomHeight ?? PAPER_DIMENSIONS.A4.height
|
|
1827
3436
|
};
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
3437
|
+
}
|
|
3438
|
+
const preset = cfg.sheetPaperSize ?? "A4";
|
|
3439
|
+
const base = PAPER_DIMENSIONS[preset] ?? PAPER_DIMENSIONS.A4;
|
|
3440
|
+
return cfg.sheetOrientation === "landscape" ? { width: base.height, height: base.width } : { ...base };
|
|
3441
|
+
}
|
|
3442
|
+
function availableArea(sheet, margin) {
|
|
3443
|
+
return {
|
|
3444
|
+
w: sheet.width - margin.left - margin.right,
|
|
3445
|
+
h: sheet.height - margin.top - margin.bottom
|
|
3446
|
+
};
|
|
3447
|
+
}
|
|
3448
|
+
function calcMaxColumns(availW, labelW, gapX) {
|
|
3449
|
+
return Math.max(0, Math.floor((availW + gapX) / (labelW + gapX)));
|
|
3450
|
+
}
|
|
3451
|
+
function computeTileLayout(t, opts) {
|
|
3452
|
+
const issues = validateTiling(t, opts);
|
|
3453
|
+
if (issues.length) throw new TilingError(issues[0].code, issues[0].message);
|
|
3454
|
+
const cfg = normalizeTilingOptions(t);
|
|
3455
|
+
const sheet = resolveSheetMm(t, opts);
|
|
3456
|
+
const label = getPaperDimensions(t);
|
|
3457
|
+
const avail = availableArea(sheet, cfg.sheetMargin);
|
|
3458
|
+
const rows = Math.floor((avail.h + cfg.gapY) / (label.height + cfg.gapY));
|
|
3459
|
+
return {
|
|
3460
|
+
tile: { width: label.width, height: label.height },
|
|
3461
|
+
sheet,
|
|
3462
|
+
columns: cfg.columns,
|
|
3463
|
+
rows,
|
|
3464
|
+
perSheet: cfg.columns * rows,
|
|
3465
|
+
maxColumns: calcMaxColumns(avail.w, label.width, cfg.gapX),
|
|
3466
|
+
margin: { ...cfg.sheetMargin },
|
|
3467
|
+
gapX: cfg.gapX,
|
|
3468
|
+
gapY: cfg.gapY
|
|
3469
|
+
};
|
|
3470
|
+
}
|
|
3471
|
+
function tilePosition(layout, index) {
|
|
3472
|
+
const slot = index % layout.perSheet;
|
|
3473
|
+
const col = slot % layout.columns;
|
|
3474
|
+
const row = Math.floor(slot / layout.columns);
|
|
3475
|
+
return {
|
|
3476
|
+
left: roundMm(layout.margin.left + col * (layout.tile.width + layout.gapX)),
|
|
3477
|
+
top: roundMm(layout.margin.top + row * (layout.tile.height + layout.gapY))
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
function validateTiling(t, opts) {
|
|
3481
|
+
const issues = [];
|
|
3482
|
+
const cfg = normalizeTilingOptions(t);
|
|
3483
|
+
if (isContinuousPaper(t)) {
|
|
3484
|
+
issues.push({
|
|
3485
|
+
code: "CONTINUOUS_UNSUPPORTED",
|
|
3486
|
+
message: "\u8FDE\u7EED\u7EB8\u4E0D\u652F\u6301\u62FC\u7248\u6253\u5370\uFF0C\u8BF7\u5C06\u6A21\u677F\u7EB8\u5F20\u6539\u4E3A\u56FA\u5B9A\u7EB8\u5F20\u6216\u5173\u95ED\u62FC\u7248"
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
const sheetPaperSize = cfg.sheetPaperSize;
|
|
3490
|
+
const sheetContinuous = isContinuousPaperSize(sheetPaperSize ?? "");
|
|
3491
|
+
if (sheetContinuous) {
|
|
3492
|
+
issues.push({ code: "SHEET_CONTINUOUS", message: "\u62FC\u7248\u76EE\u6807\u7EB8\u5F20\u4E0D\u80FD\u662F\u8FDE\u7EED\u7EB8" });
|
|
3493
|
+
}
|
|
3494
|
+
const customSizeInvalid = cfg.sheetPaperSize === "CUSTOM" && !((cfg.sheetCustomWidth ?? 0) > 0 && (cfg.sheetCustomHeight ?? 0) > 0);
|
|
3495
|
+
if (customSizeInvalid) {
|
|
3496
|
+
issues.push({
|
|
3497
|
+
code: "SHEET_SIZE_INVALID",
|
|
3498
|
+
message: "\u62FC\u7248\u81EA\u5B9A\u4E49\u7EB8\u5F20\u5BBD\u9AD8\u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6570\u503C\uFF08mm\uFF09"
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
if (customSizeInvalid || sheetContinuous) return issues;
|
|
3502
|
+
const sheet = resolveSheetMm(t, opts);
|
|
3503
|
+
const label = getPaperDimensions(t);
|
|
3504
|
+
const avail = availableArea(sheet, cfg.sheetMargin);
|
|
3505
|
+
const marginLR = roundMm(cfg.sheetMargin.left + cfg.sheetMargin.right);
|
|
3506
|
+
if (!Number.isInteger(cfg.columns) || cfg.columns < 1) {
|
|
3507
|
+
issues.push({ code: "COLUMNS_INVALID", message: "\u62FC\u7248\u5217\u6570\u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6574\u6570" });
|
|
3508
|
+
return issues;
|
|
3509
|
+
}
|
|
3510
|
+
const maxColumns = calcMaxColumns(avail.w, label.width, cfg.gapX);
|
|
3511
|
+
if (cfg.columns > maxColumns) {
|
|
3512
|
+
const needW = roundMm(cfg.columns * label.width + (cfg.columns - 1) * cfg.gapX);
|
|
3513
|
+
issues.push({
|
|
3514
|
+
code: "COLUMNS_OVERFLOW",
|
|
3515
|
+
message: "\u62FC\u7248\u5217\u6570 " + cfg.columns + " \u8D85\u51FA\u7EB8\u9762\u53EF\u7528\u5BBD\u5EA6\uFF1A" + roundMm(sheet.width) + "mm \u2212 \u5DE6\u53F3\u7559\u767D " + marginLR + "mm = " + roundMm(avail.w) + "mm\uFF0C\u6700\u591A\u53EF\u653E " + maxColumns + " \u5217\uFF1B\u5F53\u524D " + cfg.columns + " \u5217 " + label.width + "mm \u6807\u7B7E\u542B\u95F4\u8DDD\u9700\u8981 " + needW + "mm"
|
|
3516
|
+
});
|
|
3517
|
+
}
|
|
3518
|
+
if (Math.floor((avail.h + cfg.gapY) / (label.height + cfg.gapY)) < 1) {
|
|
3519
|
+
issues.push({
|
|
3520
|
+
code: "LABEL_TOO_TALL",
|
|
3521
|
+
message: "\u6807\u7B7E\u9AD8\u5EA6 " + label.height + "mm \u8D85\u51FA\u7EB8\u9762\u53EF\u7528\u9AD8\u5EA6 " + roundMm(avail.h) + "mm\uFF0C\u62FC\u7248\u6BCF\u5F20 0 \u884C\uFF1B\u8BF7\u7F29\u5C0F\u6807\u7B7E\u9AD8\u5EA6\u6216\u6539\u7528\u6A2A\u5411\u7EB8"
|
|
3522
|
+
});
|
|
3523
|
+
}
|
|
3524
|
+
return issues;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
// src/print/tile-compose.ts
|
|
3528
|
+
function composeTiledHtml(input) {
|
|
3529
|
+
const { copies, layout } = input;
|
|
3530
|
+
const bound = copies[0].bound;
|
|
3531
|
+
const sheets = [];
|
|
3532
|
+
for (let start = 0; start < copies.length; start += layout.perSheet) {
|
|
3533
|
+
const tiles = copies.slice(start, start + layout.perSheet).map((copy, i) => {
|
|
3534
|
+
const pos = tilePosition(layout, i);
|
|
3535
|
+
const page = renderFinalPages(copy.bound, copy.pageLayouts, copy.data, {
|
|
3536
|
+
codeRenderer: copy.codeRenderer
|
|
3537
|
+
});
|
|
3538
|
+
return `<div class="print-tile" style="left:${pos.left}mm;top:${pos.top}mm">
|
|
3539
|
+
${page}
|
|
3540
|
+
</div>`;
|
|
3541
|
+
}).join("\n");
|
|
3542
|
+
sheets.push(`<section class="print-sheet">
|
|
3543
|
+
${tiles}
|
|
3544
|
+
</section>`);
|
|
3545
|
+
}
|
|
3546
|
+
const css = buildFontFaceCss(bound.fonts) + buildPageCss(bound) + "\n" + buildSheetPageCss(layout);
|
|
3547
|
+
return {
|
|
3548
|
+
html: wrapHtmlDocument(css, sheets.join("\n")),
|
|
3549
|
+
sheetCount: sheets.length,
|
|
3550
|
+
perSheet: layout.perSheet
|
|
3551
|
+
};
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
// src/print/multi-template.ts
|
|
3555
|
+
function pageLabel(p, index) {
|
|
3556
|
+
return p.name ? `\u7B2C ${index + 1} \u9875\u300C${p.name}\u300D` : `\u7B2C ${index + 1} \u9875`;
|
|
3557
|
+
}
|
|
3558
|
+
function normalizeTemplate(templateJson) {
|
|
3559
|
+
const pages = isMultiPageTemplate(templateJson) ? templateJson.pages : [templateJson];
|
|
3560
|
+
if (pages.length === 0) {
|
|
3561
|
+
throw new Error("\u591A\u9875\u9762\u6A21\u677F\u81F3\u5C11\u9700\u8981\u4E00\u9875");
|
|
3562
|
+
}
|
|
3563
|
+
if (pages.length === 1) return pages;
|
|
3564
|
+
const firstPaper = getPaperDimensions(pages[0]);
|
|
3565
|
+
pages.forEach((p, i) => {
|
|
3566
|
+
if (isContinuousPaper(p)) {
|
|
3567
|
+
throw new Error(`\u591A\u9875\u9762\u6A21\u677F\u4E0D\u652F\u6301\u8FDE\u7EED\u7EB8\uFF08${pageLabel(p, i)}\uFF09`);
|
|
3568
|
+
}
|
|
3569
|
+
if (p.tiling?.enabled === true) {
|
|
3570
|
+
throw new Error(`\u591A\u9875\u9762\u6A21\u677F\u4E0D\u652F\u6301\u6807\u7B7E\u62FC\u7248\uFF08${pageLabel(p, i)}\uFF09`);
|
|
3571
|
+
}
|
|
3572
|
+
const d = getPaperDimensions(p);
|
|
3573
|
+
if (d.width !== firstPaper.width || d.height !== firstPaper.height) {
|
|
3574
|
+
throw new Error(
|
|
3575
|
+
`\u591A\u9875\u9762\u6A21\u677F\u5404\u9875\u7EB8\u5F20\u5C3A\u5BF8\u5FC5\u987B\u4E00\u81F4\uFF08${pageLabel(p, i)} ${d.width}\xD7${d.height}mm \u4E0E\u9996\u9875 ${firstPaper.width}\xD7${firstPaper.height}mm \u4E0D\u540C\uFF09`
|
|
3576
|
+
);
|
|
1833
3577
|
}
|
|
1834
3578
|
});
|
|
3579
|
+
return pages;
|
|
1835
3580
|
}
|
|
1836
|
-
function
|
|
1837
|
-
return
|
|
1838
|
-
}
|
|
1839
|
-
function
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
3581
|
+
function isMultiPageTemplate(templateJson) {
|
|
3582
|
+
return Array.isArray(templateJson.pages);
|
|
3583
|
+
}
|
|
3584
|
+
function mergeFontDeclarations(pages) {
|
|
3585
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3586
|
+
const merged = [];
|
|
3587
|
+
for (const p of pages) {
|
|
3588
|
+
for (const f of p.fonts ?? []) {
|
|
3589
|
+
const key = f.family;
|
|
3590
|
+
if (seen.has(key)) continue;
|
|
3591
|
+
seen.add(key);
|
|
3592
|
+
merged.push(f);
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
return merged;
|
|
3596
|
+
}
|
|
3597
|
+
function composeMultiPageDocument(copies) {
|
|
3598
|
+
if (copies.length === 0) {
|
|
3599
|
+
throw new Error("\u591A\u9875\u9762\u6A21\u677F\u81F3\u5C11\u9700\u8981\u4E00\u4EFD\u6570\u636E");
|
|
3600
|
+
}
|
|
3601
|
+
const templates = copies[0].boundPages;
|
|
3602
|
+
const css = buildFontFaceCss(mergeFontDeclarations(templates)) + [
|
|
3603
|
+
buildPageRuleCss(templates[0]),
|
|
3604
|
+
buildBasePageCss(),
|
|
3605
|
+
...templates.map((t, i) => buildPageGeometryCss(t, `.mt-${i}`)),
|
|
3606
|
+
...copies.length > 1 ? [COPY_BREAK_CSS] : []
|
|
3607
|
+
].join("\n\n");
|
|
3608
|
+
const bodyInner = copies.map((copy) => {
|
|
3609
|
+
const totalPages = copy.layoutsPerPage.reduce((s, ls) => s + ls.length, 0);
|
|
3610
|
+
let offset = 0;
|
|
3611
|
+
const fragments = copy.layoutsPerPage.map((layouts, i) => {
|
|
3612
|
+
const html = renderFinalPages(
|
|
3613
|
+
copy.boundPages[i],
|
|
3614
|
+
layouts,
|
|
3615
|
+
copy.data,
|
|
3616
|
+
{ codeRenderer: copy.codeRenderers?.[i] },
|
|
3617
|
+
{
|
|
3618
|
+
pageOffset: offset,
|
|
3619
|
+
totalPages,
|
|
3620
|
+
pageClass: `mt-${i}`
|
|
3621
|
+
}
|
|
3622
|
+
);
|
|
3623
|
+
offset += layouts.length;
|
|
3624
|
+
return html;
|
|
3625
|
+
});
|
|
3626
|
+
const pages = fragments.join("\n");
|
|
3627
|
+
return copies.length > 1 ? `<section class="print-copy">
|
|
3628
|
+
${pages}
|
|
3629
|
+
</section>` : pages;
|
|
3630
|
+
}).join("\n");
|
|
3631
|
+
const pageCount = copies.reduce(
|
|
3632
|
+
(sum2, c) => sum2 + c.layoutsPerPage.reduce((s, ls) => s + ls.length, 0),
|
|
3633
|
+
0
|
|
3634
|
+
);
|
|
3635
|
+
const pageLayouts = [];
|
|
3636
|
+
let g = 0;
|
|
3637
|
+
for (const c of copies) {
|
|
3638
|
+
for (const ls of c.layoutsPerPage) {
|
|
3639
|
+
for (const p of ls) pageLayouts.push({ ...p, pageIndex: g++ });
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
return {
|
|
3643
|
+
html: wrapHtmlDocument(css, bodyInner),
|
|
3644
|
+
pageCount,
|
|
3645
|
+
pageLayouts
|
|
3646
|
+
};
|
|
1849
3647
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
3648
|
+
|
|
3649
|
+
// src/print/pipeline.ts
|
|
3650
|
+
async function prepareDocument(job, runtime) {
|
|
3651
|
+
return runtime.withSession(job, (session) => prepareWithSession(job, session));
|
|
3652
|
+
}
|
|
3653
|
+
async function prepareWithSession(job, session) {
|
|
3654
|
+
const pageTemplates = normalizeTemplate(job.templateJson);
|
|
3655
|
+
if (pageTemplates.length > 1) {
|
|
3656
|
+
const normalized2 = normalizePrintData(job.printData);
|
|
3657
|
+
const rows = normalized2.mode === "batch" ? normalized2.dataList : [normalized2.data];
|
|
3658
|
+
const copies2 = [];
|
|
3659
|
+
for (let i = 0; i < rows.length; i++) {
|
|
3660
|
+
try {
|
|
3661
|
+
copies2.push(await prepareMultiCopy(job, session, rows[i]));
|
|
3662
|
+
} catch (err) {
|
|
3663
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
3664
|
+
throw new Error(`\u7B2C ${i + 1} \u4EFD\u6E32\u67D3\u5931\u8D25\uFF1A${reason}`);
|
|
1864
3665
|
}
|
|
1865
|
-
result.push({ id, height: heightPx / PX_PER_MM, rowHeights });
|
|
1866
|
-
} else {
|
|
1867
|
-
result.push({ id, height: heightPx / PX_PER_MM });
|
|
1868
3666
|
}
|
|
3667
|
+
const doc = composeMultiPageDocument(copies2);
|
|
3668
|
+
return {
|
|
3669
|
+
html: doc.html,
|
|
3670
|
+
pageCount: doc.pageCount,
|
|
3671
|
+
paperMm: getOutputPaperDimensions(pageTemplates[0]),
|
|
3672
|
+
continuous: false,
|
|
3673
|
+
heightSource: "config",
|
|
3674
|
+
pageLayouts: doc.pageLayouts,
|
|
3675
|
+
copies: copies2.length
|
|
3676
|
+
};
|
|
1869
3677
|
}
|
|
1870
|
-
|
|
3678
|
+
const singleJob = pageTemplates[0] !== job.templateJson ? { ...job, templateJson: pageTemplates[0] } : job;
|
|
3679
|
+
const template = pageTemplates[0];
|
|
3680
|
+
const normalized = normalizePrintData(singleJob.printData);
|
|
3681
|
+
if (normalized.mode === "single") {
|
|
3682
|
+
const single = await prepareSingleWithSession(singleJob, session, normalized.data);
|
|
3683
|
+
if (template.tiling?.enabled === true) {
|
|
3684
|
+
return composeTiledPrepared(singleJob, [{
|
|
3685
|
+
bound: single.bound,
|
|
3686
|
+
pageLayouts: single.pageLayouts,
|
|
3687
|
+
data: normalized.data,
|
|
3688
|
+
codeRenderer: single.codeRenderer,
|
|
3689
|
+
derivedHeightMm: single.derivedHeightMm,
|
|
3690
|
+
paperMm: single.paperMm,
|
|
3691
|
+
heightSource: single.heightSource
|
|
3692
|
+
}]);
|
|
3693
|
+
}
|
|
3694
|
+
return toPreparedDocument(single);
|
|
3695
|
+
}
|
|
3696
|
+
const copies = [];
|
|
3697
|
+
for (let i = 0; i < normalized.dataList.length; i++) {
|
|
3698
|
+
try {
|
|
3699
|
+
const single = await prepareSingleWithSession(singleJob, session, normalized.dataList[i]);
|
|
3700
|
+
copies.push({
|
|
3701
|
+
bound: single.bound,
|
|
3702
|
+
pageLayouts: single.pageLayouts,
|
|
3703
|
+
data: normalized.dataList[i],
|
|
3704
|
+
codeRenderer: single.codeRenderer,
|
|
3705
|
+
derivedHeightMm: single.derivedHeightMm,
|
|
3706
|
+
paperMm: single.paperMm,
|
|
3707
|
+
heightSource: single.heightSource
|
|
3708
|
+
});
|
|
3709
|
+
} catch (err) {
|
|
3710
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
3711
|
+
throw new Error(`\u7B2C ${i + 1} \u4EFD\u6E32\u67D3\u5931\u8D25\uFF1A${reason}`);
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
if (template.tiling?.enabled === true) {
|
|
3715
|
+
return composeTiledPrepared(singleJob, copies);
|
|
3716
|
+
}
|
|
3717
|
+
const merged = composeBatchHtml(copies);
|
|
3718
|
+
return {
|
|
3719
|
+
html: merged.html,
|
|
3720
|
+
pageCount: merged.pageCount,
|
|
3721
|
+
paperMm: copies[0].paperMm,
|
|
3722
|
+
continuous: isContinuousPaper(copies[0].bound),
|
|
3723
|
+
// 同模板同参数各份来源必然一致;不能用 derivedHeightMm 是否存在判断——
|
|
3724
|
+
// 连续纸逃生门时该字段有值但来源是 config
|
|
3725
|
+
heightSource: copies[0].heightSource,
|
|
3726
|
+
// 各份 pageIndex 均从 0 开始,批量拼接后该字段仅作调试用途
|
|
3727
|
+
pageLayouts: copies.flatMap((c) => c.pageLayouts),
|
|
3728
|
+
copies: copies.length,
|
|
3729
|
+
copyPaperMm: merged.copyPaperMm
|
|
3730
|
+
};
|
|
1871
3731
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
3732
|
+
function toPreparedDocument(s) {
|
|
3733
|
+
return {
|
|
3734
|
+
html: s.html,
|
|
3735
|
+
pageCount: s.pageCount,
|
|
3736
|
+
paperMm: s.paperMm,
|
|
3737
|
+
continuous: s.continuous,
|
|
3738
|
+
heightSource: s.heightSource,
|
|
3739
|
+
pageLayouts: s.pageLayouts,
|
|
3740
|
+
copies: 1
|
|
3741
|
+
};
|
|
3742
|
+
}
|
|
3743
|
+
function composeTiledPrepared(job, copies) {
|
|
3744
|
+
const bound = copies[0].bound;
|
|
3745
|
+
const layout = computeTileLayout(bound, { paperOverride: job.paperOverride });
|
|
3746
|
+
copies.forEach((copy, i) => {
|
|
3747
|
+
const overflow = copy.pageLayouts[0]?.overflow === true;
|
|
3748
|
+
if (copy.pageLayouts.length !== 1 || overflow) {
|
|
3749
|
+
const detail = overflow ? "\u5185\u5BB9\u8D85\u51FA\u7EB8\u5F20\u9AD8\u5EA6" : `\u6E32\u67D3\u51FA ${copy.pageLayouts.length} \u9875`;
|
|
3750
|
+
throw new Error(
|
|
3751
|
+
`\u62FC\u7248\u8981\u6C42\u6BCF\u4EFD\u6807\u7B7E\u6070\u597D 1 \u9875\uFF0C\u7B2C ${i + 1} \u4EFD${detail}\uFF1B\u8BF7\u7F29\u5C0F\u5185\u5BB9\u6216\u8C03\u6574\u6807\u7B7E\u7EB8\u5F20\u9AD8\u5EA6`
|
|
3752
|
+
);
|
|
3753
|
+
}
|
|
1887
3754
|
});
|
|
1888
|
-
|
|
3755
|
+
const tiled = composeTiledHtml({
|
|
3756
|
+
copies: copies.map((c) => ({
|
|
3757
|
+
bound: c.bound,
|
|
3758
|
+
pageLayouts: c.pageLayouts,
|
|
3759
|
+
data: c.data,
|
|
3760
|
+
codeRenderer: c.codeRenderer
|
|
3761
|
+
})),
|
|
3762
|
+
layout
|
|
3763
|
+
});
|
|
3764
|
+
return {
|
|
3765
|
+
html: tiled.html,
|
|
3766
|
+
// 语义变更:pageCount = 实际输出张数(客户端任务历史、预览「共 N 页」都按此口径)
|
|
3767
|
+
pageCount: tiled.sheetCount,
|
|
3768
|
+
paperMm: { width: layout.sheet.width, height: layout.sheet.height },
|
|
3769
|
+
continuous: false,
|
|
3770
|
+
heightSource: "config",
|
|
3771
|
+
// 调试用途;各份 pageIndex 均从 0 开始
|
|
3772
|
+
pageLayouts: copies.flatMap((c) => c.pageLayouts),
|
|
3773
|
+
copies: copies.length
|
|
3774
|
+
};
|
|
1889
3775
|
}
|
|
1890
|
-
function
|
|
1891
|
-
const
|
|
1892
|
-
const
|
|
1893
|
-
|
|
3776
|
+
async function prepareSingleWithSession(job, session, data) {
|
|
3777
|
+
const bound = bindData(job.templateJson, data, job.baseUrl, job.fontBaseUrl);
|
|
3778
|
+
const continuous = isContinuousPaper(bound);
|
|
3779
|
+
const designPaper = getPaperDimensions(bound);
|
|
3780
|
+
const viewport = paperViewportPx(designPaper);
|
|
3781
|
+
const heightEscape = escapeHeightMm({ paperHeightMm: job.paperHeightMm, override: job.paperOverride });
|
|
3782
|
+
const measurement = await buildHtmlWithCodes({
|
|
3783
|
+
bound,
|
|
3784
|
+
job,
|
|
3785
|
+
session,
|
|
3786
|
+
data,
|
|
3787
|
+
pageLayouts: [],
|
|
3788
|
+
isMeasurementPass: true
|
|
1894
3789
|
});
|
|
1895
|
-
const
|
|
1896
|
-
|
|
1897
|
-
const
|
|
1898
|
-
const
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
3790
|
+
const measurements = await session.measure(measurement.html, viewport);
|
|
3791
|
+
applyTextFitSizes(bound, measurements.fits);
|
|
3792
|
+
const pageLayouts = paginate(bound, normalizeMeasurements(measurements.measurements, bound));
|
|
3793
|
+
const finalBuild = await buildHtmlWithCodes({
|
|
3794
|
+
bound,
|
|
3795
|
+
job,
|
|
3796
|
+
session,
|
|
3797
|
+
data,
|
|
3798
|
+
pageLayouts,
|
|
3799
|
+
isMeasurementPass: false,
|
|
3800
|
+
baseMap: measurement.map
|
|
3801
|
+
});
|
|
3802
|
+
let html = finalBuild.html;
|
|
3803
|
+
let derivedHeightMm;
|
|
3804
|
+
if (continuous) {
|
|
3805
|
+
if (heightEscape && heightEscape > 0) {
|
|
3806
|
+
derivedHeightMm = heightEscape;
|
|
3807
|
+
} else {
|
|
3808
|
+
const bottomPx = await session.probeContentBottom(html, viewport);
|
|
3809
|
+
derivedHeightMm = composeContinuousHeight(bound, pxToMm(bottomPx));
|
|
1906
3810
|
}
|
|
3811
|
+
html = generateHtml(bound, pageLayouts, data, {
|
|
3812
|
+
codeRenderer: finalBuild.codeRenderer,
|
|
3813
|
+
pageHeightMm: derivedHeightMm
|
|
3814
|
+
});
|
|
1907
3815
|
}
|
|
1908
|
-
|
|
3816
|
+
const overrideForPaper = continuous && heightEscape ? { ...job.paperOverride, height: heightEscape } : job.paperOverride;
|
|
3817
|
+
const outputPaper = getOutputPaperDimensions(bound);
|
|
3818
|
+
const { paperMm, heightSource } = resolvePaperMm({
|
|
3819
|
+
paperMm: { width: outputPaper.width, height: derivedHeightMm ?? outputPaper.height },
|
|
3820
|
+
continuous,
|
|
3821
|
+
override: overrideForPaper
|
|
3822
|
+
});
|
|
3823
|
+
return {
|
|
3824
|
+
html,
|
|
3825
|
+
pageCount: pageLayouts.length,
|
|
3826
|
+
paperMm,
|
|
3827
|
+
continuous,
|
|
3828
|
+
heightSource,
|
|
3829
|
+
pageLayouts,
|
|
3830
|
+
copies: 1,
|
|
3831
|
+
bound,
|
|
3832
|
+
codeRenderer: finalBuild.codeRenderer,
|
|
3833
|
+
derivedHeightMm
|
|
3834
|
+
};
|
|
1909
3835
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
3836
|
+
async function prepareMultiCopy(job, session, data) {
|
|
3837
|
+
const templates = normalizeTemplate(job.templateJson);
|
|
3838
|
+
const viewport = paperViewportPx(getPaperDimensions(templates[0]));
|
|
3839
|
+
const boundPages = [];
|
|
3840
|
+
const layoutsPerPage = [];
|
|
3841
|
+
const codeRenderers = [];
|
|
3842
|
+
for (const t of templates) {
|
|
3843
|
+
const bound = bindData(t, data, job.baseUrl, job.fontBaseUrl);
|
|
3844
|
+
const measurement = await buildHtmlWithCodes({ bound, job, session, data, pageLayouts: [], isMeasurementPass: true });
|
|
3845
|
+
const measurements = await session.measure(measurement.html, viewport);
|
|
3846
|
+
applyTextFitSizes(bound, measurements.fits);
|
|
3847
|
+
const layouts = paginate(bound, normalizeMeasurements(measurements.measurements, bound));
|
|
3848
|
+
const final = await buildHtmlWithCodes({ bound, job, session, data, pageLayouts: layouts, isMeasurementPass: false, baseMap: measurement.map });
|
|
3849
|
+
boundPages.push(bound);
|
|
3850
|
+
layoutsPerPage.push(layouts);
|
|
3851
|
+
codeRenderers.push(final.codeRenderer);
|
|
3852
|
+
}
|
|
3853
|
+
return { boundPages, layoutsPerPage, data, codeRenderers };
|
|
3854
|
+
}
|
|
3855
|
+
async function buildHtmlWithCodes(input) {
|
|
3856
|
+
const baseMap = input.baseMap ?? /* @__PURE__ */ new Map();
|
|
3857
|
+
if (input.job.codeRenderer) {
|
|
3858
|
+
const html2 = generateHtml(input.bound, input.pageLayouts, input.data, {
|
|
3859
|
+
isMeasurementPass: input.isMeasurementPass,
|
|
3860
|
+
codeRenderer: input.job.codeRenderer
|
|
3861
|
+
});
|
|
3862
|
+
return { html: html2, codeRenderer: input.job.codeRenderer, map: baseMap };
|
|
1914
3863
|
}
|
|
1915
|
-
|
|
3864
|
+
const collector = createCollectingCodeRenderer(baseMap);
|
|
3865
|
+
const draft = generateHtml(input.bound, input.pageLayouts, input.data, {
|
|
3866
|
+
isMeasurementPass: input.isMeasurementPass,
|
|
3867
|
+
codeRenderer: collector.renderer
|
|
3868
|
+
});
|
|
3869
|
+
const extra = collector.takeSpecs();
|
|
3870
|
+
const rendered = extra.length > 0 ? await input.session.renderCodes(extra) : /* @__PURE__ */ new Map();
|
|
3871
|
+
const map = mergeCodeMaps(baseMap, rendered);
|
|
3872
|
+
const codeRenderer = map.size > 0 ? createMapCodeRenderer(map) : void 0;
|
|
3873
|
+
if (extra.length === 0) return { html: draft, codeRenderer, map };
|
|
3874
|
+
const html = generateHtml(input.bound, input.pageLayouts, input.data, {
|
|
3875
|
+
isMeasurementPass: input.isMeasurementPass,
|
|
3876
|
+
codeRenderer
|
|
3877
|
+
});
|
|
3878
|
+
return { html, codeRenderer, map };
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
// src/browser/browser-pagination.ts
|
|
3882
|
+
async function renderHtmlPages(template, printData, baseUrl, codeRenderer, options) {
|
|
3883
|
+
const prepared = await prepareDocument(
|
|
3884
|
+
{
|
|
3885
|
+
templateJson: template,
|
|
3886
|
+
printData,
|
|
3887
|
+
baseUrl,
|
|
3888
|
+
fontBaseUrl: options?.fontBaseUrl ?? "",
|
|
3889
|
+
paperHeightMm: options?.paperHeightMm,
|
|
3890
|
+
codeRenderer
|
|
3891
|
+
},
|
|
3892
|
+
createBrowserPrintRuntime()
|
|
3893
|
+
);
|
|
3894
|
+
return {
|
|
3895
|
+
html: prepared.html,
|
|
3896
|
+
pageCount: prepared.pageCount,
|
|
3897
|
+
pageLayouts: prepared.pageLayouts,
|
|
3898
|
+
paperMm: prepared.paperMm,
|
|
3899
|
+
continuous: prepared.continuous,
|
|
3900
|
+
copies: prepared.copies,
|
|
3901
|
+
copyPaperMm: prepared.copyPaperMm
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
1916
3904
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1917
3905
|
0 && (module.exports = {
|
|
3906
|
+
EXECUTOR_VERSION,
|
|
3907
|
+
applyTextFit,
|
|
1918
3908
|
browserCodeRenderer,
|
|
1919
|
-
|
|
3909
|
+
createBrowserPrintRuntime,
|
|
3910
|
+
createIframeDriverFactory,
|
|
3911
|
+
domExecutor,
|
|
3912
|
+
fitTextNode,
|
|
3913
|
+
readContentBottom,
|
|
3914
|
+
readMeasurements,
|
|
3915
|
+
renderCodes,
|
|
3916
|
+
renderHtmlPages,
|
|
3917
|
+
waitReady
|
|
1920
3918
|
});
|