@worm-vue3-print/core 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -7
- package/dist/browser/index.cjs +2025 -259
- 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-V4A5623C.js} +1 -1
- package/dist/chunk-VFEURLAZ.js +3579 -0
- package/dist/designer/index.cjs +233 -11
- package/dist/designer/index.d.cts +83 -287
- package/dist/designer/index.d.ts +83 -287
- package/dist/designer/index.js +114 -24
- package/dist/dom-executor.iife.global.js +8 -0
- package/dist/driver-CsWkGK4a.d.cts +932 -0
- package/dist/driver-CsWkGK4a.d.ts +932 -0
- package/dist/index.cjs +1935 -120
- package/dist/index.d.cts +355 -6
- package/dist/index.d.ts +355 -6
- package/dist/index.js +164 -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-B63x7kCX.d.ts +102 -0
- package/dist/ports-q934kIC7.d.cts +102 -0
- package/package.json +8 -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",
|
|
@@ -608,8 +1185,8 @@ function formatDate(value, fmt) {
|
|
|
608
1185
|
return String(value);
|
|
609
1186
|
}
|
|
610
1187
|
if (isNaN(date.getTime())) return String(value);
|
|
611
|
-
const
|
|
612
|
-
return fmt.replace("YYYY", String(date.getFullYear())).replace("MM",
|
|
1188
|
+
const pad3 = (n) => String(n).padStart(2, "0");
|
|
1189
|
+
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
1190
|
}
|
|
614
1191
|
function toUpperCaseAmount(value) {
|
|
615
1192
|
const num = Number(value);
|
|
@@ -747,6 +1324,9 @@ function aggregate(fn, field, rows) {
|
|
|
747
1324
|
return 0;
|
|
748
1325
|
}
|
|
749
1326
|
}
|
|
1327
|
+
function safeEval(expr, context) {
|
|
1328
|
+
return engine.evaluate(expr, context);
|
|
1329
|
+
}
|
|
750
1330
|
function evaluateTemplate(text, ctx) {
|
|
751
1331
|
if (!text) return "";
|
|
752
1332
|
if (!text.includes("{")) return text;
|
|
@@ -767,9 +1347,8 @@ function evaluateTemplate(text, ctx) {
|
|
|
767
1347
|
}
|
|
768
1348
|
|
|
769
1349
|
// src/render/data-binder.ts
|
|
770
|
-
function bindData(template, printData, baseUrl) {
|
|
771
|
-
const
|
|
772
|
-
const data = Array.isArray(raw) ? raw[0] ?? {} : raw;
|
|
1350
|
+
function bindData(template, printData, baseUrl, fontBaseUrl) {
|
|
1351
|
+
const data = printData ?? {};
|
|
773
1352
|
const bound = JSON.parse(JSON.stringify(template));
|
|
774
1353
|
if (bound.header?.elements) {
|
|
775
1354
|
bound.header.elements = bound.header.elements.map((el) => bindElement(el, data, baseUrl));
|
|
@@ -781,6 +1360,18 @@ function bindData(template, printData, baseUrl) {
|
|
|
781
1360
|
bound.firstPageOverlay.elements = bound.firstPageOverlay.elements.map((el) => bindElement(el, data, baseUrl));
|
|
782
1361
|
}
|
|
783
1362
|
bound.elements = bound.elements.map((el) => bindElement(el, data, baseUrl));
|
|
1363
|
+
const fontBase = fontBaseUrl ?? baseUrl;
|
|
1364
|
+
if (fontBase && bound.fonts?.length) {
|
|
1365
|
+
const prefix = fontBase.replace(/\/+$/, "");
|
|
1366
|
+
bound.fonts = bound.fonts.map((font) => ({
|
|
1367
|
+
...font,
|
|
1368
|
+
files: (font.files ?? []).map((file) => ({
|
|
1369
|
+
...file,
|
|
1370
|
+
// 绝对 URL(含协议相对 //)原样使用
|
|
1371
|
+
url: file.url?.startsWith("/") && !file.url.startsWith("//") ? prefix + file.url : file.url
|
|
1372
|
+
}))
|
|
1373
|
+
}));
|
|
1374
|
+
}
|
|
784
1375
|
return bound;
|
|
785
1376
|
}
|
|
786
1377
|
function bindElement(el, data, baseUrl) {
|
|
@@ -886,6 +1477,7 @@ function makeRenderRow(row, resolve, keepRaw = false) {
|
|
|
886
1477
|
barcodeType: cell.barcodeType,
|
|
887
1478
|
qrCodeLevel: cell.qrCodeLevel,
|
|
888
1479
|
showBarcodeText: cell.showBarcodeText,
|
|
1480
|
+
printerDpi: cell.printerDpi,
|
|
889
1481
|
fit: cell.fit,
|
|
890
1482
|
maxWidth: cell.maxWidth,
|
|
891
1483
|
maxHeight: cell.maxHeight,
|
|
@@ -895,12 +1487,15 @@ function makeRenderRow(row, resolve, keepRaw = false) {
|
|
|
895
1487
|
align: cell.align,
|
|
896
1488
|
valign: cell.valign,
|
|
897
1489
|
fontSize: cell.fontSize,
|
|
1490
|
+
fontFamily: cell.fontFamily,
|
|
898
1491
|
fontWeight: cell.fontWeight,
|
|
899
1492
|
color: cell.color,
|
|
900
1493
|
backgroundColor: cell.backgroundColor,
|
|
901
1494
|
borders: cell.borders,
|
|
902
1495
|
padding: cell.padding,
|
|
903
|
-
wordWrap: cell.wordWrap
|
|
1496
|
+
wordWrap: cell.wordWrap,
|
|
1497
|
+
textFit: cell.textFit,
|
|
1498
|
+
shrinkMinFontSize: cell.shrinkMinFontSize
|
|
904
1499
|
}))
|
|
905
1500
|
};
|
|
906
1501
|
}
|
|
@@ -915,10 +1510,63 @@ function countRepeatHeader(rows) {
|
|
|
915
1510
|
}
|
|
916
1511
|
return n;
|
|
917
1512
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1513
|
+
var pad2 = (n) => String(n).padStart(2, "0");
|
|
1514
|
+
function resolveSystemVariables(now = /* @__PURE__ */ new Date(), page = {}) {
|
|
1515
|
+
return {
|
|
1516
|
+
printDate: `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())}`,
|
|
1517
|
+
printTime: `${pad2(now.getHours())}:${pad2(now.getMinutes())}:${pad2(now.getSeconds())}`,
|
|
1518
|
+
pageIndex: page.pageIndex ?? 1,
|
|
1519
|
+
totalPages: page.totalPages ?? 1
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
function injectSystemVariables(html, now = /* @__PURE__ */ new Date()) {
|
|
1523
|
+
const { printDate, printTime } = resolveSystemVariables(now);
|
|
1524
|
+
return html.replace(/\{printDate\}/g, printDate).replace(/\{printTime\}/g, printTime);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// src/print/fonts.ts
|
|
1528
|
+
function buildFontFaceCss(fonts) {
|
|
1529
|
+
const blocks = [];
|
|
1530
|
+
for (const font of fonts ?? []) {
|
|
1531
|
+
const family = font?.family?.trim();
|
|
1532
|
+
if (!family) continue;
|
|
1533
|
+
const quoted = `"${family.replace(/"/g, "")}"`;
|
|
1534
|
+
for (const file of font.files ?? []) {
|
|
1535
|
+
const url = file?.url?.trim();
|
|
1536
|
+
if (!url) continue;
|
|
1537
|
+
const weight = Number.isFinite(file.weight) ? Number(file.weight) : 400;
|
|
1538
|
+
const style = file.style === "italic" ? "italic" : "normal";
|
|
1539
|
+
blocks.push(
|
|
1540
|
+
`@font-face{font-family:${quoted};src:url("${url}")${fontFormatHint(url)};font-weight:${weight};font-style:${style};font-display:block;}`
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
return blocks.length ? `${blocks.join("")}
|
|
1545
|
+
` : "";
|
|
1546
|
+
}
|
|
1547
|
+
function fontFormatHint(url) {
|
|
1548
|
+
const path = url.split(/[?#]/)[0].toLowerCase();
|
|
1549
|
+
if (path.endsWith(".woff2")) return ' format("woff2")';
|
|
1550
|
+
if (path.endsWith(".woff")) return ' format("woff")';
|
|
1551
|
+
if (path.endsWith(".ttf")) return ' format("truetype")';
|
|
1552
|
+
if (path.endsWith(".otf")) return ' format("opentype")';
|
|
1553
|
+
return "";
|
|
1554
|
+
}
|
|
1555
|
+
var FALLBACK_FONT_STACK = [
|
|
1556
|
+
'"Microsoft YaHei"',
|
|
1557
|
+
'"PingFang SC"',
|
|
1558
|
+
'"Helvetica Neue"',
|
|
1559
|
+
"Arial",
|
|
1560
|
+
"sans-serif"
|
|
1561
|
+
];
|
|
1562
|
+
function escapeInlineStyleValue(cssValue) {
|
|
1563
|
+
return cssValue.replace(/&/g, "&").replace(/"/g, """);
|
|
1564
|
+
}
|
|
1565
|
+
function toFontFamilyStack(family) {
|
|
1566
|
+
const name = family?.trim();
|
|
1567
|
+
if (!name) return FALLBACK_FONT_STACK.join(", ");
|
|
1568
|
+
const head = name.includes(",") ? name : `"${name.replace(/"/g, "")}"`;
|
|
1569
|
+
return [head, ...FALLBACK_FONT_STACK].join(", ");
|
|
922
1570
|
}
|
|
923
1571
|
|
|
924
1572
|
// src/render/types.ts
|
|
@@ -928,38 +1576,81 @@ var PAPER_DIMENSIONS = {
|
|
|
928
1576
|
A5: { width: 148, height: 210 },
|
|
929
1577
|
Letter: { width: 216, height: 279 },
|
|
930
1578
|
Legal: { width: 216, height: 356 },
|
|
931
|
-
|
|
1579
|
+
// 针式打印纸(241 系列等分):11 英寸整张 279.4mm 按等分取整
|
|
1580
|
+
DOT_FULL: { width: 241, height: 279.4 },
|
|
1581
|
+
DOT_HALF: { width: 241, height: 139.7 },
|
|
1582
|
+
DOT_THIRD: { width: 241, height: 93.1 },
|
|
1583
|
+
// 标签纸
|
|
1584
|
+
LABEL_80X60: { width: 80, height: 60 },
|
|
1585
|
+
LABEL_60X40: { width: 60, height: 40 },
|
|
1586
|
+
LABEL_40X30: { width: 40, height: 30 },
|
|
1587
|
+
// 小票纸(热敏卷纸):高度仅为设计画布高度,出纸按内容推导
|
|
1588
|
+
THERMAL_57: { width: 57, height: 297 },
|
|
1589
|
+
THERMAL_80: { width: 80, height: 297 },
|
|
1590
|
+
THERMAL_110: { width: 110, height: 297 },
|
|
1591
|
+
CUSTOM: { width: 210, height: 297 },
|
|
1592
|
+
// 连续纸:默认 80mm 热敏;高度仅为设计画布高度,出纸按内容推导
|
|
1593
|
+
CONTINUOUS: { width: 80, height: 297 }
|
|
932
1594
|
};
|
|
1595
|
+
var CONTINUOUS_PAPER_SIZES = /* @__PURE__ */ new Set([
|
|
1596
|
+
"CONTINUOUS",
|
|
1597
|
+
"THERMAL_57",
|
|
1598
|
+
"THERMAL_80",
|
|
1599
|
+
"THERMAL_110"
|
|
1600
|
+
]);
|
|
1601
|
+
function isContinuousPaperSize(paperSize) {
|
|
1602
|
+
return CONTINUOUS_PAPER_SIZES.has(paperSize);
|
|
1603
|
+
}
|
|
933
1604
|
function getPaperDimensions(template) {
|
|
934
|
-
const
|
|
935
|
-
|
|
1605
|
+
const continuous = isContinuousPaperSize(template.paperSize);
|
|
1606
|
+
const base = template.paperSize === "CUSTOM" || continuous ? {
|
|
1607
|
+
width: template.customWidth ?? PAPER_DIMENSIONS[template.paperSize].width,
|
|
1608
|
+
height: template.customHeight ?? PAPER_DIMENSIONS[template.paperSize].height
|
|
1609
|
+
} : PAPER_DIMENSIONS[template.paperSize];
|
|
1610
|
+
if (template.orientation === "landscape" && !continuous) {
|
|
936
1611
|
return { width: base.height, height: base.width };
|
|
937
1612
|
}
|
|
938
1613
|
return { ...base };
|
|
939
1614
|
}
|
|
1615
|
+
function getOutputPaperDimensions(template) {
|
|
1616
|
+
const continuous = isContinuousPaperSize(template.paperSize);
|
|
1617
|
+
if (continuous || template.tiling?.enabled === true) {
|
|
1618
|
+
return getPaperDimensions(template);
|
|
1619
|
+
}
|
|
1620
|
+
const design = getPaperDimensions(template);
|
|
1621
|
+
const swap = template.outputRotation === 90 || template.outputRotation === 270;
|
|
1622
|
+
return swap ? { width: design.height, height: design.width } : design;
|
|
1623
|
+
}
|
|
1624
|
+
function getOutputRotationAngle(template) {
|
|
1625
|
+
const continuous = isContinuousPaperSize(template.paperSize);
|
|
1626
|
+
if (continuous || template.tiling?.enabled === true) return 0;
|
|
1627
|
+
return template.outputRotation ?? 0;
|
|
1628
|
+
}
|
|
1629
|
+
function isContinuousPaper(template) {
|
|
1630
|
+
return isContinuousPaperSize(template.paperSize);
|
|
1631
|
+
}
|
|
940
1632
|
|
|
941
1633
|
// src/render/css-builder.ts
|
|
942
1634
|
function mm(value) {
|
|
943
1635
|
return `${value}mm`;
|
|
944
1636
|
}
|
|
945
|
-
function
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
const
|
|
951
|
-
|
|
1637
|
+
function resolveOuterPaper(template, pageHeightMm) {
|
|
1638
|
+
const out = getOutputPaperDimensions(template);
|
|
1639
|
+
return pageHeightMm && pageHeightMm > 0 && isContinuousPaper(template) ? { width: out.width, height: pageHeightMm } : out;
|
|
1640
|
+
}
|
|
1641
|
+
function resolveAreaPaper(template, pageHeightMm) {
|
|
1642
|
+
const design = getPaperDimensions(template);
|
|
1643
|
+
return pageHeightMm && pageHeightMm > 0 && isContinuousPaper(template) ? { width: design.width, height: pageHeightMm } : design;
|
|
1644
|
+
}
|
|
1645
|
+
function screenBlock() {
|
|
952
1646
|
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
1647
|
/* \u2500\u2500 \u5168\u5C40\u91CD\u7F6E \u2500\u2500 */
|
|
957
1648
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
958
1649
|
|
|
959
1650
|
/* \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
1651
|
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
961
1652
|
|
|
962
|
-
body { font-family: "
|
|
1653
|
+
body { font-family: ${FALLBACK_FONT_STACK.join(", ")}; }
|
|
963
1654
|
|
|
964
1655
|
/* \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
1656
|
@media screen {
|
|
@@ -970,13 +1661,110 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
|
|
|
970
1661
|
body { background: #fff; }
|
|
971
1662
|
.print-page { margin: 0; box-shadow: none; }
|
|
972
1663
|
}
|
|
1664
|
+
`;
|
|
1665
|
+
}
|
|
1666
|
+
function watermarkBlock() {
|
|
1667
|
+
return `
|
|
1668
|
+
/* \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
|
|
1669
|
+
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 */
|
|
1670
|
+
.watermark-layer {
|
|
1671
|
+
position: absolute;
|
|
1672
|
+
top: 0;
|
|
1673
|
+
left: 0;
|
|
1674
|
+
width: 100%;
|
|
1675
|
+
height: 100%;
|
|
1676
|
+
z-index: 0;
|
|
1677
|
+
pointer-events: none;
|
|
1678
|
+
overflow: hidden;
|
|
1679
|
+
}
|
|
1680
|
+
/* \u5355\u5757\u6C34\u5370\u74E6\u7247\uFF1A\u4F4D\u7F6E/\u5C3A\u5BF8\u7531 core \u7684\u74E6\u7247\u7F51\u683C\u7ED9\u51FA\uFF08mm\uFF09 */
|
|
1681
|
+
.watermark-tile {
|
|
1682
|
+
position: absolute;
|
|
1683
|
+
pointer-events: none;
|
|
1684
|
+
}
|
|
1685
|
+
`;
|
|
1686
|
+
}
|
|
1687
|
+
function elementBlock() {
|
|
1688
|
+
return `
|
|
1689
|
+
/* \u2500\u2500 \u5143\u7D20\u901A\u7528\u5B9A\u4F4D \u2500\u2500 */
|
|
1690
|
+
.print-element {
|
|
1691
|
+
position: absolute;
|
|
1692
|
+
overflow: hidden;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/* \u2500\u2500 \u8868\u683C\u6837\u5F0F \u2500\u2500 */
|
|
1696
|
+
.print-table {
|
|
1697
|
+
width: 100%;
|
|
1698
|
+
border-collapse: collapse;
|
|
1699
|
+
table-layout: fixed;
|
|
1700
|
+
}
|
|
1701
|
+
.print-table th,
|
|
1702
|
+
.print-table td {
|
|
1703
|
+
border: 1px solid #ccc;
|
|
1704
|
+
padding: 2mm 3mm;
|
|
1705
|
+
word-break: break-all;
|
|
1706
|
+
overflow: hidden;
|
|
1707
|
+
}
|
|
1708
|
+
.print-table thead th {
|
|
1709
|
+
background-color: #f5f5f5;
|
|
1710
|
+
font-weight: bold;
|
|
1711
|
+
}
|
|
973
1712
|
|
|
1713
|
+
/* \u2500\u2500 \u6D4B\u91CF\u6A21\u5F0F \u2500\u2500 */
|
|
1714
|
+
.measure-mode .print-page {
|
|
1715
|
+
height: auto;
|
|
1716
|
+
min-height: auto;
|
|
1717
|
+
overflow: visible;
|
|
1718
|
+
}
|
|
1719
|
+
.measure-mode .content-area {
|
|
1720
|
+
height: auto;
|
|
1721
|
+
overflow: visible;
|
|
1722
|
+
}
|
|
1723
|
+
`;
|
|
1724
|
+
}
|
|
1725
|
+
function pageRuleBlock(paper) {
|
|
1726
|
+
return `
|
|
1727
|
+
/* \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 */
|
|
1728
|
+
@page { size: ${mm(paper.width)} ${mm(paper.height)}; margin: 0; }
|
|
1729
|
+
`;
|
|
1730
|
+
}
|
|
1731
|
+
function buildRotorCss(area, margins, angle) {
|
|
1732
|
+
const base = `
|
|
1733
|
+
/* \u2500\u2500 \u51FA\u7EB8\u8F6C\u5B50\uFF1A\u8BBE\u8BA1\u7A3F\u6574\u9875\u65CB\u8F6C ${angle}\xB0 \u586B\u5165\u51FA\u7EB8\u7EB8\u5F20 \u2500\u2500 */
|
|
1734
|
+
.print-page-rotor {
|
|
1735
|
+
position: absolute;
|
|
1736
|
+
top: 0;
|
|
1737
|
+
left: 0;
|
|
1738
|
+
width: ${mm(area.width)};
|
|
1739
|
+
height: ${mm(area.height)};
|
|
1740
|
+
padding: ${mm(margins.top)} ${mm(margins.right)} ${mm(margins.bottom)} ${mm(margins.left)};
|
|
1741
|
+
box-sizing: border-box;
|
|
1742
|
+
transform-origin: 0 0;
|
|
1743
|
+
}`;
|
|
1744
|
+
if (angle === 0) return base.trim();
|
|
1745
|
+
const transforms = {
|
|
1746
|
+
90: `translate(${mm(area.height)}, 0mm) rotate(90deg)`,
|
|
1747
|
+
180: `translate(${mm(area.width)}, ${mm(area.height)}) rotate(180deg)`,
|
|
1748
|
+
270: `translate(0mm, ${mm(area.width)}) rotate(270deg)`
|
|
1749
|
+
};
|
|
1750
|
+
const tf = transforms[angle];
|
|
1751
|
+
return [
|
|
1752
|
+
base.trim(),
|
|
1753
|
+
`.print-page-rotor-${angle} {
|
|
1754
|
+
transform: ${tf};
|
|
1755
|
+
}`
|
|
1756
|
+
].join("\n");
|
|
1757
|
+
}
|
|
1758
|
+
function pageGeometryBlock(template, outerPaper, pageSel, rotated) {
|
|
1759
|
+
const { top: mt, right: mr, bottom: mb, left: ml } = template.margins;
|
|
1760
|
+
const padding = rotated ? "0" : `${mm(mt)} ${mm(mr)} ${mm(mb)} ${mm(ml)}`;
|
|
1761
|
+
return `
|
|
974
1762
|
/* \u2500\u2500 \u7EB8\u5F20\u9875\u9762 \u2500\u2500 */
|
|
975
|
-
|
|
976
|
-
width: ${mm(
|
|
977
|
-
min-height: ${mm(
|
|
1763
|
+
${pageSel} {
|
|
1764
|
+
width: ${mm(outerPaper.width)};
|
|
1765
|
+
min-height: ${mm(outerPaper.height)};
|
|
978
1766
|
background: ${template.pageBackground ?? "#fff"};
|
|
979
|
-
padding: ${
|
|
1767
|
+
padding: ${padding};
|
|
980
1768
|
position: relative;
|
|
981
1769
|
page-break-after: always;
|
|
982
1770
|
overflow: hidden;
|
|
@@ -984,9 +1772,18 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
|
|
|
984
1772
|
.print-page:last-child {
|
|
985
1773
|
page-break-after: auto;
|
|
986
1774
|
}
|
|
987
|
-
|
|
1775
|
+
`;
|
|
1776
|
+
}
|
|
1777
|
+
function areaGeometryBlock(template, paper, desc) {
|
|
1778
|
+
const { right: mr, left: ml } = template.margins;
|
|
1779
|
+
const { bottom: mb } = template.margins;
|
|
1780
|
+
const headerH = template.header?.height ?? 0;
|
|
1781
|
+
const footerH = template.footer?.height ?? 0;
|
|
1782
|
+
const overlayH = template.firstPageOverlay?.height ?? 0;
|
|
1783
|
+
const contentWidth = paper.width - ml - mr;
|
|
1784
|
+
return `
|
|
988
1785
|
/* \u2500\u2500 \u9875\u7709 \u2500\u2500 */
|
|
989
|
-
.page-header {
|
|
1786
|
+
${desc}.page-header {
|
|
990
1787
|
width: ${mm(contentWidth)};
|
|
991
1788
|
height: ${mm(headerH)};
|
|
992
1789
|
position: relative;
|
|
@@ -995,7 +1792,7 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
|
|
|
995
1792
|
/* \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
|
|
996
1793
|
\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
|
|
997
1794
|
\u4E0E\u8BBE\u8BA1\u5668 CanvasPaper \u7684\u4E09\u533A\u51E0\u4F55\u4E00\u81F4\u3002 */
|
|
998
|
-
.page-footer {
|
|
1795
|
+
${desc}.page-footer {
|
|
999
1796
|
width: ${mm(contentWidth)};
|
|
1000
1797
|
height: ${mm(footerH)};
|
|
1001
1798
|
position: absolute;
|
|
@@ -1004,54 +1801,115 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
|
|
|
1004
1801
|
}
|
|
1005
1802
|
|
|
1006
1803
|
/* \u2500\u2500 \u5185\u5BB9\u533A \u2500\u2500 */
|
|
1007
|
-
.content-area {
|
|
1804
|
+
${desc}.content-area {
|
|
1008
1805
|
width: ${mm(contentWidth)};
|
|
1009
1806
|
position: relative;
|
|
1010
1807
|
overflow: visible;
|
|
1011
1808
|
}
|
|
1012
1809
|
|
|
1013
1810
|
/* \u2500\u2500 \u9996\u9875\u53E0\u52A0\u533A\u57DF \u2500\u2500 */
|
|
1014
|
-
.first-page-overlay {
|
|
1811
|
+
${desc}.first-page-overlay {
|
|
1015
1812
|
width: ${mm(contentWidth)};
|
|
1016
1813
|
height: ${mm(overlayH)};
|
|
1017
1814
|
position: relative;
|
|
1018
1815
|
}
|
|
1816
|
+
`;
|
|
1817
|
+
}
|
|
1818
|
+
function buildBasePageCss() {
|
|
1819
|
+
return [screenBlock(), watermarkBlock(), elementBlock()].join("").trim();
|
|
1820
|
+
}
|
|
1821
|
+
function buildPageRuleCss(template, pageHeightMm) {
|
|
1822
|
+
return pageRuleBlock(resolveOuterPaper(template, pageHeightMm)).trim();
|
|
1823
|
+
}
|
|
1824
|
+
function buildPageGeometryCss(template, scope, pageHeightMm) {
|
|
1825
|
+
const angle = getOutputRotationAngle(template);
|
|
1826
|
+
const rotated = angle !== 0;
|
|
1827
|
+
const outer = resolveOuterPaper(template, pageHeightMm);
|
|
1828
|
+
const area = resolveAreaPaper(template, pageHeightMm);
|
|
1829
|
+
const pageSel = scope ? `${scope}.print-page` : ".print-page";
|
|
1830
|
+
const desc = scope ? `${scope} ` : "";
|
|
1831
|
+
return [
|
|
1832
|
+
pageGeometryBlock(template, outer, pageSel, rotated),
|
|
1833
|
+
areaGeometryBlock(template, area, desc),
|
|
1834
|
+
...rotated ? [buildRotorCss(area, template.margins, angle)] : []
|
|
1835
|
+
].join("").trim();
|
|
1836
|
+
}
|
|
1837
|
+
function buildPageCss(template, pageHeightMm) {
|
|
1838
|
+
const angle = getOutputRotationAngle(template);
|
|
1839
|
+
const rotated = angle !== 0;
|
|
1840
|
+
const outer = resolveOuterPaper(template, pageHeightMm);
|
|
1841
|
+
const area = resolveAreaPaper(template, pageHeightMm);
|
|
1842
|
+
return [
|
|
1843
|
+
pageRuleBlock(outer),
|
|
1844
|
+
screenBlock(),
|
|
1845
|
+
pageGeometryBlock(template, outer, ".print-page", rotated),
|
|
1846
|
+
watermarkBlock(),
|
|
1847
|
+
areaGeometryBlock(template, area, ""),
|
|
1848
|
+
elementBlock(),
|
|
1849
|
+
...rotated ? [buildRotorCss(area, template.margins, angle)] : []
|
|
1850
|
+
].join("").trim();
|
|
1851
|
+
}
|
|
1852
|
+
var COPY_BREAK_CSS = ".print-copy:not(:last-child){break-after:page;page-break-after:always;}";
|
|
1853
|
+
function buildBatchPageCss(template, copies) {
|
|
1854
|
+
if (!isContinuousPaper(template)) {
|
|
1855
|
+
return `${buildPageCss(template)}
|
|
1856
|
+
${COPY_BREAK_CSS}`;
|
|
1857
|
+
}
|
|
1858
|
+
const { bottom: mb } = template.margins;
|
|
1859
|
+
const footerH = template.footer?.height ?? 0;
|
|
1860
|
+
const width = getPaperDimensions(template).width;
|
|
1861
|
+
const base = buildPageCss(template, copies[0]?.heightMm);
|
|
1862
|
+
const scoped = copies.map((copy, i) => {
|
|
1863
|
+
const h = copy.heightMm;
|
|
1864
|
+
const rules = [
|
|
1865
|
+
`@page copy${i} { size: ${mm(width)} ${h ? mm(h) : "auto"}; margin: 0; }`,
|
|
1866
|
+
`.print-copy-${i} { page: copy${i}; }`
|
|
1867
|
+
];
|
|
1868
|
+
if (h) {
|
|
1869
|
+
rules.push(`.print-copy-${i} .print-page { min-height: ${mm(h)}; }`);
|
|
1870
|
+
rules.push(`.print-copy-${i} .page-footer { top: ${mm(h - mb - footerH)}; }`);
|
|
1871
|
+
}
|
|
1872
|
+
return rules.join("\n");
|
|
1873
|
+
}).join("\n");
|
|
1874
|
+
return `${base}
|
|
1875
|
+
${scoped}
|
|
1876
|
+
${COPY_BREAK_CSS}`;
|
|
1877
|
+
}
|
|
1878
|
+
function buildSheetPageCss(layout) {
|
|
1879
|
+
return `
|
|
1880
|
+
/* \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
|
|
1881
|
+
\u4F46\u6D4F\u89C8\u5668\u94FE\u8DEF\u53EA\u770B @page\uFF0C\u6545\u8FD9\u6761\u662F\u786C\u9700\u6C42 \u2500\u2500 */
|
|
1882
|
+
@page { size: ${mm(layout.sheet.width)} ${mm(layout.sheet.height)}; margin: 0; }
|
|
1019
1883
|
|
|
1020
|
-
/* \u2500\u2500 \
|
|
1021
|
-
.print-
|
|
1022
|
-
|
|
1884
|
+
/* \u2500\u2500 \u4E00\u5F20\u76EE\u6807\u7EB8 \u2500\u2500 */
|
|
1885
|
+
.print-sheet {
|
|
1886
|
+
width: ${mm(layout.sheet.width)};
|
|
1887
|
+
height: ${mm(layout.sheet.height)};
|
|
1888
|
+
position: relative;
|
|
1023
1889
|
overflow: hidden;
|
|
1890
|
+
break-after: page;
|
|
1891
|
+
page-break-after: always;
|
|
1024
1892
|
}
|
|
1893
|
+
.print-sheet:last-child { break-after: auto; page-break-after: auto; }
|
|
1025
1894
|
|
|
1026
|
-
/* \u2500\u2500 \
|
|
1027
|
-
.print-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
}
|
|
1032
|
-
.print-table th,
|
|
1033
|
-
.print-table td {
|
|
1034
|
-
border: 1px solid #ccc;
|
|
1035
|
-
padding: 2mm 3mm;
|
|
1036
|
-
word-break: break-all;
|
|
1895
|
+
/* \u2500\u2500 \u4E00\u683C\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\uFF0C\u4F4D\u7F6E\u7531 tilePosition() \u4EE5\u884C\u5185 style \u7ED9\u51FA \u2500\u2500 */
|
|
1896
|
+
.print-tile {
|
|
1897
|
+
position: absolute;
|
|
1898
|
+
width: ${mm(layout.tile.width)};
|
|
1899
|
+
height: ${mm(layout.tile.height)};
|
|
1037
1900
|
overflow: hidden;
|
|
1038
1901
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
}
|
|
1902
|
+
/* \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
|
|
1903
|
+
\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 */
|
|
1904
|
+
.print-tile > .print-page { break-after: auto; page-break-after: auto; }
|
|
1043
1905
|
|
|
1044
|
-
|
|
1045
|
-
.
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
}
|
|
1050
|
-
.measure-mode .content-area {
|
|
1051
|
-
height: auto;
|
|
1052
|
-
overflow: visible;
|
|
1906
|
+
@media screen {
|
|
1907
|
+
.print-sheet { margin: 12px auto; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18); }
|
|
1908
|
+
/* \u5FC5\u9700\uFF1A\u62B5\u6D88\u6807\u7B7E CSS \u7684 @media screen{.print-page{margin:12px auto}}\uFF0C
|
|
1909
|
+
\u5426\u5219\u8BBE\u8BA1\u5668\u9884\u89C8\u9519\u4F4D 3.17mm\u3001\u4E0E\u51FA\u7EB8\u4E0D\u4E00\u81F4 */
|
|
1910
|
+
.print-tile > .print-page { margin: 0; box-shadow: none; }
|
|
1053
1911
|
}
|
|
1054
|
-
|
|
1912
|
+
`;
|
|
1055
1913
|
}
|
|
1056
1914
|
function elementPositionStyle(left, top, width, height, zIndex) {
|
|
1057
1915
|
const parts = [
|
|
@@ -1069,6 +1927,151 @@ function elementPositionStyle(left, top, width, height, zIndex) {
|
|
|
1069
1927
|
return parts.join(";") + ";";
|
|
1070
1928
|
}
|
|
1071
1929
|
|
|
1930
|
+
// src/render/watermark.ts
|
|
1931
|
+
var PX_PER_MM = 96 / 25.4;
|
|
1932
|
+
var MM_PER_PX = 25.4 / 96;
|
|
1933
|
+
var WATERMARK_DEFAULTS = {
|
|
1934
|
+
color: "#cccccc",
|
|
1935
|
+
opacity: 0.15,
|
|
1936
|
+
rotate: -30,
|
|
1937
|
+
/** 瓦片默认尺寸(px):决定平铺疏密,默认 260×180 */
|
|
1938
|
+
tileWidth: 260,
|
|
1939
|
+
tileHeight: 180,
|
|
1940
|
+
/** 瓦片下限(px),防止文字裁剪/异常平铺 */
|
|
1941
|
+
minTileWidth: 140,
|
|
1942
|
+
minTileHeight: 100,
|
|
1943
|
+
/** 瓦片字号固定 16px:密度只改变平铺疏密,不改字体大小 */
|
|
1944
|
+
fontSize: 16
|
|
1945
|
+
};
|
|
1946
|
+
function clampTile(value, fallback, min2) {
|
|
1947
|
+
const n = typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
1948
|
+
return Math.max(min2, Math.round(n));
|
|
1949
|
+
}
|
|
1950
|
+
function resolveTileSize(wm) {
|
|
1951
|
+
return {
|
|
1952
|
+
width: clampTile(wm?.tileWidth, WATERMARK_DEFAULTS.tileWidth, WATERMARK_DEFAULTS.minTileWidth),
|
|
1953
|
+
height: clampTile(wm?.tileHeight, WATERMARK_DEFAULTS.tileHeight, WATERMARK_DEFAULTS.minTileHeight)
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
function isWatermarkVisible(wm) {
|
|
1957
|
+
if (!wm) return false;
|
|
1958
|
+
if (!wm.mode || wm.mode === "fixed") {
|
|
1959
|
+
return !!wm.content && wm.content.trim().length > 0;
|
|
1960
|
+
}
|
|
1961
|
+
return !!wm.binding && wm.binding.trim().length > 0;
|
|
1962
|
+
}
|
|
1963
|
+
function resolveWatermarkText(wm, printData, systemVars2) {
|
|
1964
|
+
if (!wm) return "";
|
|
1965
|
+
let text = "";
|
|
1966
|
+
if (!wm.mode || wm.mode === "fixed") {
|
|
1967
|
+
text = wm.content ?? "";
|
|
1968
|
+
} else if (typeof wm.binding === "string" && wm.binding.trim()) {
|
|
1969
|
+
const binding = wm.binding.trim();
|
|
1970
|
+
const data = Array.isArray(printData) ? printData[0] ?? {} : printData ?? {};
|
|
1971
|
+
const ctx = { ...resolveSystemVariables(), ...systemVars2, ...data };
|
|
1972
|
+
let resolved;
|
|
1973
|
+
if (binding.includes("{")) {
|
|
1974
|
+
resolved = evaluateTemplate(binding, ctx);
|
|
1975
|
+
} else {
|
|
1976
|
+
try {
|
|
1977
|
+
resolved = safeEval(binding, ctx);
|
|
1978
|
+
} catch {
|
|
1979
|
+
resolved = void 0;
|
|
1980
|
+
}
|
|
1981
|
+
if (resolved == null || typeof resolved === "object") {
|
|
1982
|
+
resolved = getByPath(data, binding);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
if (resolved != null && typeof resolved !== "object" && String(resolved) !== binding) {
|
|
1986
|
+
text = String(resolved);
|
|
1987
|
+
} else {
|
|
1988
|
+
text = wm.testData ?? `[${wm.binding}]`;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
if (wm.timestamp && text) {
|
|
1992
|
+
text = `${text} ${formatTimestamp(wm.format)}`;
|
|
1993
|
+
}
|
|
1994
|
+
return text;
|
|
1995
|
+
}
|
|
1996
|
+
function pad(n) {
|
|
1997
|
+
return String(n).padStart(2, "0");
|
|
1998
|
+
}
|
|
1999
|
+
function formatTimestamp(format) {
|
|
2000
|
+
const fmt = format && format.trim() ? format : "YYYY-MM-DD HH:mm";
|
|
2001
|
+
const d = /* @__PURE__ */ new Date();
|
|
2002
|
+
const map = {
|
|
2003
|
+
YYYY: String(d.getFullYear()),
|
|
2004
|
+
MM: pad(d.getMonth() + 1),
|
|
2005
|
+
DD: pad(d.getDate()),
|
|
2006
|
+
HH: pad(d.getHours()),
|
|
2007
|
+
mm: pad(d.getMinutes()),
|
|
2008
|
+
ss: pad(d.getSeconds())
|
|
2009
|
+
};
|
|
2010
|
+
return fmt.replace(/YYYY|MM|DD|HH|mm|ss/g, (k) => map[k] ?? k);
|
|
2011
|
+
}
|
|
2012
|
+
function round4(n) {
|
|
2013
|
+
return Math.round(n * 1e4) / 1e4;
|
|
2014
|
+
}
|
|
2015
|
+
function escXml(text) {
|
|
2016
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2017
|
+
}
|
|
2018
|
+
function resolveWatermarkLayout(wm, printData, paperMm, systemVars2) {
|
|
2019
|
+
if (!isWatermarkVisible(wm)) return null;
|
|
2020
|
+
const text = resolveWatermarkText(wm, printData, systemVars2);
|
|
2021
|
+
if (!text) return null;
|
|
2022
|
+
const tile = resolveTileSize(wm);
|
|
2023
|
+
const tileWidthMm = round4(tile.width * MM_PER_PX);
|
|
2024
|
+
const tileHeightMm = round4(tile.height * MM_PER_PX);
|
|
2025
|
+
const paper = {
|
|
2026
|
+
width: Math.max(0, Number.isFinite(paperMm?.width) ? paperMm.width : 0),
|
|
2027
|
+
height: Math.max(0, Number.isFinite(paperMm?.height) ? paperMm.height : 0)
|
|
2028
|
+
};
|
|
2029
|
+
const columns = Math.max(1, Math.ceil(paper.width / tileWidthMm));
|
|
2030
|
+
const rows = Math.max(1, Math.ceil(paper.height / tileHeightMm));
|
|
2031
|
+
const tiles = [];
|
|
2032
|
+
for (let r = 0; r < rows; r++) {
|
|
2033
|
+
for (let c = 0; c < columns; c++) {
|
|
2034
|
+
tiles.push({
|
|
2035
|
+
leftMm: round4(c * tileWidthMm),
|
|
2036
|
+
topMm: round4(r * tileHeightMm),
|
|
2037
|
+
widthMm: tileWidthMm,
|
|
2038
|
+
heightMm: tileHeightMm
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
return {
|
|
2043
|
+
text,
|
|
2044
|
+
color: wm?.color || WATERMARK_DEFAULTS.color,
|
|
2045
|
+
rotate: wm?.rotate ?? WATERMARK_DEFAULTS.rotate,
|
|
2046
|
+
opacity: wm?.opacity ?? WATERMARK_DEFAULTS.opacity,
|
|
2047
|
+
fontSizePx: WATERMARK_DEFAULTS.fontSize,
|
|
2048
|
+
tileWidthPx: tile.width,
|
|
2049
|
+
tileHeightPx: tile.height,
|
|
2050
|
+
tileWidthMm,
|
|
2051
|
+
tileHeightMm,
|
|
2052
|
+
columns,
|
|
2053
|
+
rows,
|
|
2054
|
+
tiles
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
function renderWatermarkTileSvg(layout, tile) {
|
|
2058
|
+
const tileWidthPx = layout.tileWidthPx;
|
|
2059
|
+
const tileHeightPx = layout.tileHeightPx;
|
|
2060
|
+
const cx = tileWidthPx / 2;
|
|
2061
|
+
const cy = tileHeightPx / 2;
|
|
2062
|
+
const style = `left:${tile.leftMm}mm;top:${tile.topMm}mm;width:${tile.widthMm}mm;height:${tile.heightMm}mm`;
|
|
2063
|
+
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>`;
|
|
2064
|
+
}
|
|
2065
|
+
function renderWatermarkLayerHtml(wm, printData, paperMm, systemVars2) {
|
|
2066
|
+
const layout = resolveWatermarkLayout(wm, printData, paperMm, systemVars2);
|
|
2067
|
+
if (!layout) return "";
|
|
2068
|
+
const tiles = layout.tiles.map((t) => renderWatermarkTileSvg(layout, t)).join("\n");
|
|
2069
|
+
return `
|
|
2070
|
+
<div class="watermark-layer" style="opacity:${layout.opacity}">
|
|
2071
|
+
${tiles}
|
|
2072
|
+
</div>`;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
1072
2075
|
// src/render/pagination-engine.ts
|
|
1073
2076
|
function paginationOf(el) {
|
|
1074
2077
|
return el.options?.pagination ?? el.pagination;
|
|
@@ -1089,7 +2092,7 @@ function tableDesignBottom(el) {
|
|
|
1089
2092
|
}
|
|
1090
2093
|
return top + (opts.height ?? 0);
|
|
1091
2094
|
}
|
|
1092
|
-
function buildFollowMap(sorted) {
|
|
2095
|
+
function buildFollowMap(sorted, excludedIds) {
|
|
1093
2096
|
const map = /* @__PURE__ */ new Map();
|
|
1094
2097
|
let currentTableId = null;
|
|
1095
2098
|
let currentTableBottom = -1;
|
|
@@ -1100,6 +2103,7 @@ function buildFollowMap(sorted) {
|
|
|
1100
2103
|
map.set(el.id, []);
|
|
1101
2104
|
continue;
|
|
1102
2105
|
}
|
|
2106
|
+
if (excludedIds?.has(el.id)) continue;
|
|
1103
2107
|
const top = el.options?.top ?? 0;
|
|
1104
2108
|
if (currentTableId && top >= currentTableBottom) {
|
|
1105
2109
|
map.get(currentTableId).push(el.id);
|
|
@@ -1117,28 +2121,110 @@ function followGroupHeight(el, members, template, _measuredElements) {
|
|
|
1117
2121
|
}
|
|
1118
2122
|
return Math.max(maxBottom - tableBottom, 0);
|
|
1119
2123
|
}
|
|
2124
|
+
function effectiveHeight(el, measured) {
|
|
2125
|
+
return Math.max(el.options?.height ?? 0, measured.get(el.id)?.measuredHeight ?? 0);
|
|
2126
|
+
}
|
|
2127
|
+
function buildPaginationUnits(sorted, measured, groupMap) {
|
|
2128
|
+
const unitOf = /* @__PURE__ */ new Map();
|
|
2129
|
+
for (const members of groupMap.values()) {
|
|
2130
|
+
if (members.length < 2) continue;
|
|
2131
|
+
const unit = {
|
|
2132
|
+
anchorId: members[0].id,
|
|
2133
|
+
ids: members.map((m) => m.id),
|
|
2134
|
+
forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false)
|
|
2135
|
+
};
|
|
2136
|
+
for (const m of members) unitOf.set(m.id, unit);
|
|
2137
|
+
}
|
|
2138
|
+
const eligible = sorted.filter(
|
|
2139
|
+
(el) => !isTableEl(el) && !el.options?.groupId && paginationOf(el)?.pageable !== false
|
|
2140
|
+
);
|
|
2141
|
+
const parent = /* @__PURE__ */ new Map();
|
|
2142
|
+
eligible.forEach((el) => parent.set(el.id, el.id));
|
|
2143
|
+
const find = (id) => {
|
|
2144
|
+
let root = id;
|
|
2145
|
+
while (parent.get(root) !== root) root = parent.get(root);
|
|
2146
|
+
let cur = id;
|
|
2147
|
+
while (parent.get(cur) !== root) {
|
|
2148
|
+
const next = parent.get(cur);
|
|
2149
|
+
parent.set(cur, root);
|
|
2150
|
+
cur = next;
|
|
2151
|
+
}
|
|
2152
|
+
return root;
|
|
2153
|
+
};
|
|
2154
|
+
const union = (a, b) => {
|
|
2155
|
+
const ra = find(a);
|
|
2156
|
+
const rb = find(b);
|
|
2157
|
+
if (ra !== rb) parent.set(rb, ra);
|
|
2158
|
+
};
|
|
2159
|
+
const active = [];
|
|
2160
|
+
for (const el of eligible) {
|
|
2161
|
+
const top = el.options?.top ?? 0;
|
|
2162
|
+
for (let i = active.length - 1; i >= 0; i--) {
|
|
2163
|
+
if (active[i].bottom <= top) active.splice(i, 1);
|
|
2164
|
+
}
|
|
2165
|
+
const bottom = top + effectiveHeight(el, measured);
|
|
2166
|
+
for (const a of active) {
|
|
2167
|
+
if (a.bottom > top) union(el.id, a.id);
|
|
2168
|
+
}
|
|
2169
|
+
active.push({ id: el.id, top, bottom });
|
|
2170
|
+
}
|
|
2171
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
2172
|
+
for (const el of eligible) {
|
|
2173
|
+
const root = find(el.id);
|
|
2174
|
+
const arr = clusters.get(root);
|
|
2175
|
+
if (arr) arr.push(el.id);
|
|
2176
|
+
else clusters.set(root, [el.id]);
|
|
2177
|
+
}
|
|
2178
|
+
for (const ids of clusters.values()) {
|
|
2179
|
+
if (ids.length < 2) continue;
|
|
2180
|
+
const unit = { anchorId: ids[0], ids, forceFirstPage: false };
|
|
2181
|
+
for (const id of ids) unitOf.set(id, unit);
|
|
2182
|
+
}
|
|
2183
|
+
return unitOf;
|
|
2184
|
+
}
|
|
1120
2185
|
function paginate(template, measuredElements) {
|
|
1121
2186
|
const paper = getPaperDimensions(template);
|
|
1122
2187
|
const { top: mt, bottom: mb } = template.margins;
|
|
1123
2188
|
const headerH = template.header?.height ?? 0;
|
|
1124
2189
|
const footerH = template.footer?.height ?? 0;
|
|
1125
2190
|
const overlayH = template.firstPageOverlay?.height ?? 0;
|
|
1126
|
-
const
|
|
1127
|
-
|
|
2191
|
+
const continuous = isContinuousPaper(template);
|
|
2192
|
+
const contentHeight = continuous ? Number.POSITIVE_INFINITY : paper.height - mt - mb - headerH - footerH;
|
|
2193
|
+
if (!continuous && contentHeight <= 0) {
|
|
1128
2194
|
throw new Error(
|
|
1129
2195
|
`\u9875\u9762\u53EF\u7528\u9AD8\u5EA6\u4E0D\u8DB3: paper=${paper.height}mm, margins=${mt + mb}mm, header=${headerH}mm, footer=${footerH}mm`
|
|
1130
2196
|
);
|
|
1131
2197
|
}
|
|
2198
|
+
const orderIndex = /* @__PURE__ */ new Map();
|
|
2199
|
+
template.elements.forEach((e, idx) => orderIndex.set(e.id, idx));
|
|
1132
2200
|
const sorted = [...template.elements].sort((a, b) => {
|
|
1133
2201
|
const topA = a.options?.top ?? 0;
|
|
1134
2202
|
const topB = b.options?.top ?? 0;
|
|
1135
|
-
return topA - topB;
|
|
2203
|
+
if (topA !== topB) return topA - topB;
|
|
2204
|
+
const zA = a.options?.zIndex ?? 0;
|
|
2205
|
+
const zB = b.options?.zIndex ?? 0;
|
|
2206
|
+
if (zA !== zB) return zA - zB;
|
|
2207
|
+
return (orderIndex.get(a.id) ?? 0) - (orderIndex.get(b.id) ?? 0);
|
|
1136
2208
|
});
|
|
1137
|
-
const
|
|
2209
|
+
const explicitGroupIds = /* @__PURE__ */ new Set();
|
|
2210
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
2211
|
+
for (const el of sorted) {
|
|
2212
|
+
if (isTableEl(el)) continue;
|
|
2213
|
+
const gid = el.options?.groupId;
|
|
2214
|
+
if (!gid) continue;
|
|
2215
|
+
explicitGroupIds.add(el.id);
|
|
2216
|
+
const arr = groupMap.get(gid);
|
|
2217
|
+
if (arr) arr.push(el);
|
|
2218
|
+
else groupMap.set(gid, [el]);
|
|
2219
|
+
}
|
|
2220
|
+
const followMap = buildFollowMap(sorted, explicitGroupIds);
|
|
1138
2221
|
const followOwner = /* @__PURE__ */ new Map();
|
|
1139
2222
|
for (const [tableId, members] of followMap) {
|
|
1140
2223
|
for (const m of members) followOwner.set(m, tableId);
|
|
1141
2224
|
}
|
|
2225
|
+
const elById = /* @__PURE__ */ new Map();
|
|
2226
|
+
for (const el of sorted) elById.set(el.id, el);
|
|
2227
|
+
const unitOf = buildPaginationUnits(sorted, measuredElements, groupMap);
|
|
1142
2228
|
const pages = [];
|
|
1143
2229
|
let currentPage = [];
|
|
1144
2230
|
let remaining = contentHeight - overlayH - SAFETY_MARGIN;
|
|
@@ -1150,12 +2236,26 @@ function paginate(template, measuredElements) {
|
|
|
1150
2236
|
function fullPageHeight() {
|
|
1151
2237
|
return isFirstPage ? contentHeight - overlayH - SAFETY_MARGIN : contentHeight - SAFETY_MARGIN;
|
|
1152
2238
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
2239
|
+
let overflowOnCurrent = false;
|
|
2240
|
+
function pushPage() {
|
|
2241
|
+
pages.push(
|
|
2242
|
+
overflowOnCurrent ? { pageIndex: pages.length, sections: [...currentPage], overflow: true } : { pageIndex: pages.length, sections: [...currentPage] }
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
function noteOverflow(bottom) {
|
|
2246
|
+
if (bottom > contentHeight) overflowOnCurrent = true;
|
|
2247
|
+
}
|
|
2248
|
+
function finishPage(overflow = false) {
|
|
2249
|
+
if (currentPage.length === 0) {
|
|
2250
|
+
overflowOnCurrent = overflowOnCurrent || overflow;
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
pushPage();
|
|
1155
2254
|
currentPage = [];
|
|
1156
2255
|
remaining = contentHeight - SAFETY_MARGIN;
|
|
1157
2256
|
isFirstPage = false;
|
|
1158
2257
|
pageBroken = true;
|
|
2258
|
+
overflowOnCurrent = false;
|
|
1159
2259
|
}
|
|
1160
2260
|
let i = 0;
|
|
1161
2261
|
while (i < sorted.length) {
|
|
@@ -1164,19 +2264,32 @@ function paginate(template, measuredElements) {
|
|
|
1164
2264
|
i++;
|
|
1165
2265
|
continue;
|
|
1166
2266
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
2267
|
+
const unit = unitOf.get(el.id);
|
|
2268
|
+
if (unit && unit.anchorId !== el.id) {
|
|
2269
|
+
i++;
|
|
2270
|
+
continue;
|
|
2271
|
+
}
|
|
2272
|
+
if (paginationOf(el)?.pageable === false || unit?.forceFirstPage) {
|
|
2273
|
+
if (unit) {
|
|
2274
|
+
for (const id of unit.ids) {
|
|
2275
|
+
currentPage.push({ elementId: id, type: "element", renderTop: elById.get(id)?.options?.top ?? 0 });
|
|
2276
|
+
}
|
|
2277
|
+
} else {
|
|
2278
|
+
currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
|
|
2279
|
+
}
|
|
1169
2280
|
i++;
|
|
1170
2281
|
continue;
|
|
1171
2282
|
}
|
|
1172
2283
|
if (isTableEl(el)) {
|
|
1173
2284
|
i = paginateTable(el, measuredElements, i);
|
|
2285
|
+
} else if (unit) {
|
|
2286
|
+
i = paginateUnit(unit, i);
|
|
1174
2287
|
} else {
|
|
1175
2288
|
i = paginateNonTable(el, measuredElements, sorted, i);
|
|
1176
2289
|
}
|
|
1177
2290
|
}
|
|
1178
2291
|
if (currentPage.length > 0) {
|
|
1179
|
-
|
|
2292
|
+
pushPage();
|
|
1180
2293
|
}
|
|
1181
2294
|
if (pages.length === 0) {
|
|
1182
2295
|
pages.push({ pageIndex: 0, sections: [] });
|
|
@@ -1185,6 +2298,8 @@ function paginate(template, measuredElements) {
|
|
|
1185
2298
|
function paginateNonTable(el, measured, sortedList, idx) {
|
|
1186
2299
|
const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
|
|
1187
2300
|
if (elHeight <= remaining) {
|
|
2301
|
+
const top2 = pageBroken ? 0 : el.options?.top ?? 0;
|
|
2302
|
+
noteOverflow(top2 + elHeight);
|
|
1188
2303
|
currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
|
|
1189
2304
|
remaining -= elHeight;
|
|
1190
2305
|
if (paginationOf(el)?.keepWithNext && idx + 1 < sortedList.length) {
|
|
@@ -1202,11 +2317,43 @@ function paginate(template, measuredElements) {
|
|
|
1202
2317
|
}
|
|
1203
2318
|
return idx + 1;
|
|
1204
2319
|
}
|
|
1205
|
-
|
|
2320
|
+
const top = pageBroken ? 0 : el.options?.top ?? 0;
|
|
2321
|
+
finishPage(top + elHeight > contentHeight);
|
|
1206
2322
|
currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
|
|
1207
2323
|
remaining -= elHeight;
|
|
1208
2324
|
return idx + 1;
|
|
1209
2325
|
}
|
|
2326
|
+
function paginateUnit(unit, idx) {
|
|
2327
|
+
const members = unit.ids.map((id) => elById.get(id)).filter((m) => !!m);
|
|
2328
|
+
let minTop = Number.POSITIVE_INFINITY;
|
|
2329
|
+
let maxBottom = Number.NEGATIVE_INFINITY;
|
|
2330
|
+
for (const m of members) {
|
|
2331
|
+
const top2 = m.options?.top ?? 0;
|
|
2332
|
+
minTop = Math.min(minTop, top2);
|
|
2333
|
+
maxBottom = Math.max(maxBottom, top2 + effectiveHeight(m, measuredElements));
|
|
2334
|
+
}
|
|
2335
|
+
const unitHeight = Math.max(maxBottom - minTop, 0);
|
|
2336
|
+
const place = () => {
|
|
2337
|
+
const offset = pageBroken ? -minTop : 0;
|
|
2338
|
+
noteOverflow(minTop + offset + unitHeight);
|
|
2339
|
+
for (const m of members) {
|
|
2340
|
+
currentPage.push({
|
|
2341
|
+
elementId: m.id,
|
|
2342
|
+
type: "element",
|
|
2343
|
+
renderTop: (m.options?.top ?? 0) + offset
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
remaining -= unitHeight;
|
|
2347
|
+
};
|
|
2348
|
+
if (unitHeight <= remaining) {
|
|
2349
|
+
place();
|
|
2350
|
+
return idx + 1;
|
|
2351
|
+
}
|
|
2352
|
+
const top = pageBroken ? 0 : minTop;
|
|
2353
|
+
finishPage(top + unitHeight > contentHeight);
|
|
2354
|
+
place();
|
|
2355
|
+
return idx + 1;
|
|
2356
|
+
}
|
|
1210
2357
|
function paginateTable(el, measured, idx) {
|
|
1211
2358
|
const opts = el.options ?? {};
|
|
1212
2359
|
const renderRows = opts._renderRows ?? [];
|
|
@@ -1374,16 +2521,19 @@ function specialRowHeight(renderRows, rowHeights, type) {
|
|
|
1374
2521
|
|
|
1375
2522
|
// src/render/html-generator.ts
|
|
1376
2523
|
function generateHtml(template, pageLayouts, printData, options) {
|
|
1377
|
-
const css = buildPageCss(template);
|
|
2524
|
+
const css = buildFontFaceCss(template.fonts) + buildPageCss(template, options?.pageHeightMm);
|
|
1378
2525
|
const isMeasure = options?.isMeasurementPass === true;
|
|
1379
2526
|
const totalPages = isMeasure ? 1 : pageLayouts.length;
|
|
1380
|
-
const ctx = {
|
|
2527
|
+
const ctx = {
|
|
2528
|
+
codeRenderer: options?.codeRenderer,
|
|
2529
|
+
pageHeightMm: options?.pageHeightMm
|
|
2530
|
+
};
|
|
1381
2531
|
if (isMeasure) {
|
|
1382
|
-
return generateMeasurementHtml(template, css, ctx);
|
|
2532
|
+
return generateMeasurementHtml(template, css, ctx, printData);
|
|
1383
2533
|
}
|
|
1384
|
-
return generateFinalHtml(template, pageLayouts, css, totalPages, ctx);
|
|
2534
|
+
return generateFinalHtml(template, pageLayouts, css, totalPages, ctx, printData);
|
|
1385
2535
|
}
|
|
1386
|
-
function generateMeasurementHtml(template, css, ctx) {
|
|
2536
|
+
function generateMeasurementHtml(template, css, ctx, printData) {
|
|
1387
2537
|
const paper = getPaperDims(template);
|
|
1388
2538
|
const contentWidth = paper.width - template.margins.left - template.margins.right;
|
|
1389
2539
|
const elementsHtml = template.elements.map((el) => renderElement(el, true, void 0, void 0, ctx)).join("\n");
|
|
@@ -1405,6 +2555,7 @@ function generateMeasurementHtml(template, css, ctx) {
|
|
|
1405
2555
|
</head>
|
|
1406
2556
|
<body class="measure-mode">
|
|
1407
2557
|
<section class="print-page" data-measure-page="0">
|
|
2558
|
+
${renderWatermarkLayerHtml(template.watermark, printData, getPaperDims(template))}
|
|
1408
2559
|
<div class="page-header">${headerHtml}</div>
|
|
1409
2560
|
<div class="first-page-overlay">${overlayHtml}</div>
|
|
1410
2561
|
<div class="content-area" style="height:auto;overflow:visible;">
|
|
@@ -1416,26 +2567,42 @@ function generateMeasurementHtml(template, css, ctx) {
|
|
|
1416
2567
|
html = injectSystemVariables(html);
|
|
1417
2568
|
return html;
|
|
1418
2569
|
}
|
|
1419
|
-
function
|
|
2570
|
+
function renderFinalPages(template, pageLayouts, printData, ctx, options) {
|
|
2571
|
+
const pageOffset = options?.pageOffset ?? 0;
|
|
2572
|
+
const totalPages = options?.totalPages ?? pageLayouts.length;
|
|
1420
2573
|
const pagesHtml = pageLayouts.map((page) => {
|
|
1421
|
-
const pageNum = page.pageIndex + 1;
|
|
1422
|
-
return renderPage(template, page, pageNum, totalPages, ctx);
|
|
2574
|
+
const pageNum = page.pageIndex + 1 + pageOffset;
|
|
2575
|
+
return renderPage(template, page, pageNum, totalPages, ctx, printData, options?.pageClass);
|
|
1423
2576
|
}).join("\n");
|
|
1424
|
-
|
|
2577
|
+
return injectSystemVariables(pagesHtml);
|
|
2578
|
+
}
|
|
2579
|
+
function wrapHtmlDocument(css, bodyInnerHtml, bodyClass) {
|
|
2580
|
+
const html = `<!DOCTYPE html>
|
|
1425
2581
|
<html lang="zh-CN">
|
|
1426
2582
|
<head>
|
|
1427
2583
|
<meta charset="UTF-8">
|
|
1428
2584
|
<style>${css}</style>
|
|
1429
2585
|
</head>
|
|
1430
|
-
<body>
|
|
1431
|
-
${
|
|
2586
|
+
<body${bodyClass ? ` class="${bodyClass}"` : ""}>
|
|
2587
|
+
${bodyInnerHtml}
|
|
1432
2588
|
</body>
|
|
1433
2589
|
</html>`;
|
|
1434
|
-
|
|
1435
|
-
|
|
2590
|
+
return injectSystemVariables(html);
|
|
2591
|
+
}
|
|
2592
|
+
function generateFinalHtml(template, pageLayouts, css, _totalPages, ctx, printData) {
|
|
2593
|
+
const bodyInner = renderFinalPages(template, pageLayouts, printData ?? {}, ctx);
|
|
2594
|
+
return wrapHtmlDocument(
|
|
2595
|
+
css,
|
|
2596
|
+
bodyInner,
|
|
2597
|
+
isContinuousPaper(template) ? "continuous" : void 0
|
|
2598
|
+
);
|
|
1436
2599
|
}
|
|
1437
|
-
function renderPage(template, page, pageNum, totalPages, ctx) {
|
|
2600
|
+
function renderPage(template, page, pageNum, totalPages, ctx, printData, pageClass) {
|
|
1438
2601
|
const paper = getPaperDims(template);
|
|
2602
|
+
const paperMm = {
|
|
2603
|
+
width: paper.width,
|
|
2604
|
+
height: ctx.pageHeightMm && ctx.pageHeightMm > 0 ? ctx.pageHeightMm : paper.height
|
|
2605
|
+
};
|
|
1439
2606
|
const contentWidth = paper.width - template.margins.left - template.margins.right;
|
|
1440
2607
|
const headerHtml = renderAreaElements(
|
|
1441
2608
|
template.header?.elements ?? [],
|
|
@@ -1455,13 +2622,18 @@ function renderPage(template, page, pageNum, totalPages, ctx) {
|
|
|
1455
2622
|
let contentHtml = page.sections.map((section) => renderSection(section, template, ctx)).join("\n");
|
|
1456
2623
|
contentHtml = contentHtml.replace(/\{pageIndex\}/g, String(pageNum));
|
|
1457
2624
|
contentHtml = contentHtml.replace(/\{totalPages\}/g, String(totalPages));
|
|
1458
|
-
|
|
2625
|
+
const pageInner = `
|
|
2626
|
+
${renderWatermarkLayerHtml(template.watermark, printData, paperMm, { pageIndex: pageNum, totalPages })}
|
|
1459
2627
|
<div class="page-header">${headerHtml}</div>
|
|
1460
2628
|
${overlayHtml}
|
|
1461
2629
|
<div class="content-area">
|
|
1462
2630
|
${contentHtml}
|
|
1463
2631
|
</div>
|
|
1464
|
-
<div class="page-footer">${footerHtml}</div
|
|
2632
|
+
<div class="page-footer">${footerHtml}</div>`;
|
|
2633
|
+
const rot = getOutputRotationAngle(template);
|
|
2634
|
+
const pageBody = rot !== 0 ? `<div class="print-page-rotor print-page-rotor-${rot}">${pageInner}</div>` : pageInner;
|
|
2635
|
+
return `<section class="print-page${pageClass ? ` ${pageClass}` : ""}" data-page="${pageNum}">
|
|
2636
|
+
${pageBody}
|
|
1465
2637
|
</section>`;
|
|
1466
2638
|
}
|
|
1467
2639
|
function renderSection(section, template, ctx) {
|
|
@@ -1481,8 +2653,9 @@ var V_ALIGN_FLEX = { top: "flex-start", middle: "center", bottom: "flex-end" };
|
|
|
1481
2653
|
var H_ALIGN_FLEX = { left: "flex-start", center: "center", right: "flex-end" };
|
|
1482
2654
|
function textStyle(opts) {
|
|
1483
2655
|
const parts = [];
|
|
1484
|
-
|
|
1485
|
-
if (
|
|
2656
|
+
const fontSize = opts._fitFontSize === void 0 ? opts.fontSize : roundFontSize(opts._fitFontSize);
|
|
2657
|
+
if (fontSize) parts.push(`font-size:${fontSize}pt`);
|
|
2658
|
+
if (opts.fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(opts.fontFamily))}`);
|
|
1486
2659
|
if (opts.fontWeight) parts.push(`font-weight:${opts.fontWeight}`);
|
|
1487
2660
|
if (opts.color) parts.push(`color:${opts.color}`);
|
|
1488
2661
|
if (opts.backgroundColor) parts.push(`background-color:${opts.backgroundColor}`);
|
|
@@ -1495,15 +2668,28 @@ function textStyle(opts) {
|
|
|
1495
2668
|
if (opts.textAlign) parts.push(`text-align:${opts.textAlign}`);
|
|
1496
2669
|
return parts.length ? parts.join(";") + ";" : "";
|
|
1497
2670
|
}
|
|
2671
|
+
function elementFitStyle(fit, opts) {
|
|
2672
|
+
if (fit === "autoHeight") return "overflow:visible;";
|
|
2673
|
+
if (opts.wordWrap === false) return "white-space:nowrap;text-overflow:ellipsis;";
|
|
2674
|
+
return "";
|
|
2675
|
+
}
|
|
2676
|
+
function fitAttrs(fit, key, baseFontSizePt, opts) {
|
|
2677
|
+
if (fit !== "shrink") return "";
|
|
2678
|
+
const min2 = resolveShrinkMinFontSize(opts.shrinkMinFontSize);
|
|
2679
|
+
return ` data-fit="shrink" data-fit-key="${esc(key)}" data-fit-base="${baseFontSizePt}" data-fit-min="${min2}"`;
|
|
2680
|
+
}
|
|
1498
2681
|
function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
|
|
1499
2682
|
const opts = el.options ?? {};
|
|
1500
2683
|
const left = opts.left ?? 0;
|
|
1501
2684
|
const top = overrideTop ?? opts.top ?? 0;
|
|
1502
2685
|
const width = opts.width ?? 100;
|
|
1503
2686
|
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
2687
|
const type = el.type || el.printElementType?.type || "text";
|
|
2688
|
+
const fit = resolveElementTextFit(type, opts);
|
|
2689
|
+
const fitHeight = fit === "autoHeight" ? void 0 : height;
|
|
2690
|
+
const style = containerStyle ?? elementPositionStyle(left, top, width, fitHeight, opts.zIndex);
|
|
2691
|
+
const measureAttr = isMeasure ? ` data-measure-id="${el.id}"` : "";
|
|
2692
|
+
const fitAttr = fitAttrs(fit, el.id, opts.fontSize ?? 12, opts);
|
|
1507
2693
|
switch (type) {
|
|
1508
2694
|
case "table":
|
|
1509
2695
|
return renderTableElement(el, isMeasure, measureAttr, ctx);
|
|
@@ -1514,9 +2700,17 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
|
|
|
1514
2700
|
case "barcode":
|
|
1515
2701
|
case "qrcode": {
|
|
1516
2702
|
const codeValue = String(opts.formatter ?? opts.testData ?? "").trim();
|
|
1517
|
-
const fill = type === "barcode";
|
|
1518
2703
|
return `<div class="print-element" style="${style}"${measureAttr}>
|
|
1519
|
-
${codeImgHtml(codeValue, type, opts,
|
|
2704
|
+
${codeImgHtml(codeValue, type, opts, {
|
|
2705
|
+
fallback: `<span>${esc(codeValue)}</span>`,
|
|
2706
|
+
codeRenderer: ctx?.codeRenderer,
|
|
2707
|
+
fit: opts.fit,
|
|
2708
|
+
maxWidth: opts.maxWidth,
|
|
2709
|
+
maxHeight: opts.maxHeight,
|
|
2710
|
+
printerDpi: opts.printerDpi,
|
|
2711
|
+
targetWidthMm: opts.width,
|
|
2712
|
+
targetHeightMm: opts.height
|
|
2713
|
+
})}
|
|
1520
2714
|
</div>`;
|
|
1521
2715
|
}
|
|
1522
2716
|
case "hline":
|
|
@@ -1528,30 +2722,32 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
|
|
|
1528
2722
|
case "oval":
|
|
1529
2723
|
return `<div class="print-element" style="${style};border:${opts.borderWidth ?? 1}px solid ${opts.borderColor ?? "#000"};border-radius:50%;"${measureAttr}></div>`;
|
|
1530
2724
|
case "longText":
|
|
1531
|
-
return `<div class="print-element" style="${style}${textStyle(opts)}
|
|
2725
|
+
return `<div class="print-element" style="${style}${textStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
|
|
1532
2726
|
case "html":
|
|
1533
2727
|
return `<div class="print-element" style="${style}"${measureAttr}>${opts.testData ?? opts.title ?? ""}</div>`;
|
|
1534
2728
|
default:
|
|
1535
|
-
return `<div class="print-element" style="${style}${textStyle(opts)}"${measureAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
|
|
2729
|
+
return `<div class="print-element" style="${style}${textStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
|
|
1536
2730
|
}
|
|
1537
2731
|
}
|
|
1538
|
-
function codeImgHtml(value, cellType, opts,
|
|
1539
|
-
if (!value || !codeRenderer) return
|
|
2732
|
+
function codeImgHtml(value, cellType, opts, io) {
|
|
2733
|
+
if (!value || !io.codeRenderer) return io.fallback;
|
|
2734
|
+
const { fallback, codeRenderer, fit, maxWidth, maxHeight } = io;
|
|
2735
|
+
const isBarcode = cellType === "barcode";
|
|
1540
2736
|
try {
|
|
1541
2737
|
const svg = codeRenderer.render(value, cellType, {
|
|
1542
2738
|
barcodeType: opts.barcodeType,
|
|
1543
2739
|
qrCodeLevel: opts.qrCodeLevel != null ? String(opts.qrCodeLevel) : void 0,
|
|
1544
2740
|
showText: opts.hideTitle !== void 0 ? !opts.hideTitle : opts.showBarcodeText,
|
|
1545
2741
|
barWidth: typeof opts.barWidth === "number" ? opts.barWidth : void 0,
|
|
1546
|
-
fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0
|
|
2742
|
+
fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0,
|
|
2743
|
+
printerDpi: isBarcode ? io.printerDpi : void 0,
|
|
2744
|
+
targetWidthMm: isBarcode ? barcodeAvailableBoxMm(io.targetWidthMm, maxWidth) : void 0,
|
|
2745
|
+
targetHeightMm: isBarcode ? barcodeAvailableBoxMm(io.targetHeightMm, maxHeight) : void 0
|
|
1547
2746
|
});
|
|
2747
|
+
if (isBarcode && /^<svg[\s>]/i.test(svg)) return inlineCodeSvgHtml(svg);
|
|
1548
2748
|
const src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
1549
2749
|
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
|
-
}
|
|
2750
|
+
styleParts.push("max-width:100%", "max-height:100%", "display:block", "margin:auto");
|
|
1555
2751
|
if (fit) {
|
|
1556
2752
|
styleParts.push(`object-fit:${fit}`);
|
|
1557
2753
|
}
|
|
@@ -1564,15 +2760,20 @@ function codeImgHtml(value, cellType, opts, fallbackHtml, fill = false, codeRend
|
|
|
1564
2760
|
const style = styleParts.join(";");
|
|
1565
2761
|
return `<img src="${src}" style="${style}" />`;
|
|
1566
2762
|
} catch {
|
|
1567
|
-
return
|
|
2763
|
+
return fallback;
|
|
1568
2764
|
}
|
|
1569
2765
|
}
|
|
2766
|
+
function inlineCodeSvgHtml(svg) {
|
|
2767
|
+
const styled = svg.replace(/<svg\b/, '<svg style="display:block"');
|
|
2768
|
+
return `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${styled}</div>`;
|
|
2769
|
+
}
|
|
1570
2770
|
function matrixCellStyle(cell, opts) {
|
|
1571
2771
|
const parts = [];
|
|
1572
|
-
const fontSize = cell.fontSize ?? opts.tableDefaultFontSize;
|
|
2772
|
+
const fontSize = cell.fittedFontSize ?? cell.fontSize ?? opts.tableDefaultFontSize;
|
|
1573
2773
|
const color = cell.color ?? opts.tableDefaultColor;
|
|
1574
2774
|
const padding = cell.padding ?? opts.tableDefaultPadding ?? 1;
|
|
1575
2775
|
if (fontSize) parts.push(`font-size:${fontSize}pt`);
|
|
2776
|
+
if (cell.fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(cell.fontFamily))}`);
|
|
1576
2777
|
if (cell.fontWeight) parts.push(`font-weight:${cell.fontWeight}`);
|
|
1577
2778
|
if (color) parts.push(`color:${color}`);
|
|
1578
2779
|
if (cell.backgroundColor) parts.push(`background-color:${cell.backgroundColor}`);
|
|
@@ -1587,25 +2788,36 @@ function matrixCellStyle(cell, opts) {
|
|
|
1587
2788
|
}
|
|
1588
2789
|
return parts.join(";");
|
|
1589
2790
|
}
|
|
1590
|
-
function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx) {
|
|
2791
|
+
function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }) {
|
|
1591
2792
|
const trs = [];
|
|
2793
|
+
const defaultPadding = opts.tableDefaultPadding ?? 1;
|
|
1592
2794
|
for (let r = start; r < end; r++) {
|
|
1593
2795
|
const row = renderRows[r];
|
|
1594
2796
|
if (!row) continue;
|
|
1595
2797
|
const idxAttr = withRowIndex ? ` data-row-index="${r}"` : "";
|
|
1596
|
-
const tds = row.cells.filter((cell) => !cell.merged).map((cell) => {
|
|
2798
|
+
const tds = row.cells.map((cell, ci) => ({ cell, ci })).filter(({ cell }) => !cell.merged).map(({ cell, ci }) => {
|
|
1597
2799
|
const span = `${cell.rowspan > 1 ? ` rowspan="${cell.rowspan}"` : ""}${cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}`;
|
|
2800
|
+
const colIndex = ci;
|
|
1598
2801
|
let inner;
|
|
1599
2802
|
if (cell.cellType === "barcode" || cell.cellType === "qrcode") {
|
|
1600
|
-
|
|
1601
|
-
|
|
2803
|
+
inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(cell.content, cell.cellType, cell, {
|
|
2804
|
+
fallback: esc(cell.content),
|
|
2805
|
+
codeRenderer: ctx?.codeRenderer,
|
|
2806
|
+
fit: cell.fit,
|
|
2807
|
+
maxWidth: cell.maxWidth,
|
|
2808
|
+
maxHeight: cell.maxHeight,
|
|
2809
|
+
printerDpi: cell.printerDpi,
|
|
2810
|
+
// 可用框 = 单元格内容区(列宽/行高扣除内边距与塌陷边框);列宽缺失时宽度为 0 → 不约束宽度
|
|
2811
|
+
targetWidthMm: cellFitWidthMm(opts.tableColWidths ?? [], ci, cell, defaultPadding),
|
|
2812
|
+
targetHeightMm: cellFitCapMm(renderRows, r, cell, defaultPadding)
|
|
2813
|
+
})}</div>`;
|
|
1602
2814
|
} else if (cell.cellType === "image") {
|
|
1603
2815
|
const fit = cell.fit || "contain";
|
|
1604
2816
|
const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
|
|
1605
2817
|
const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
|
|
1606
2818
|
inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;"><img src="${esc(cell.content)}" style="object-fit:${fit};${maxWidth}${maxHeight}display:block;" /></div>`;
|
|
1607
2819
|
} else {
|
|
1608
|
-
inner =
|
|
2820
|
+
inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding);
|
|
1609
2821
|
}
|
|
1610
2822
|
return `<td${span} style="${matrixCellStyle(cell, opts)}">${inner}</td>`;
|
|
1611
2823
|
}).join("");
|
|
@@ -1613,6 +2825,20 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx) {
|
|
|
1613
2825
|
}
|
|
1614
2826
|
return trs.join("\n");
|
|
1615
2827
|
}
|
|
2828
|
+
function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding) {
|
|
2829
|
+
const fit = resolveCellTextFit(cell);
|
|
2830
|
+
const text = esc(cell.content);
|
|
2831
|
+
if (fit === "autoHeight") return text;
|
|
2832
|
+
const capMm = cellFitCapMm(renderRows, rowIndex, cell, defaultPadding);
|
|
2833
|
+
const nowrap = cell.wordWrap === false;
|
|
2834
|
+
const style = [
|
|
2835
|
+
`max-height:${capMm}mm`,
|
|
2836
|
+
"overflow:hidden",
|
|
2837
|
+
...nowrap ? ["white-space:nowrap", "text-overflow:ellipsis"] : []
|
|
2838
|
+
].join(";");
|
|
2839
|
+
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}"` : "";
|
|
2840
|
+
return `<div class="cell-fit" style="${style}"${attrs}>${text}</div>`;
|
|
2841
|
+
}
|
|
1616
2842
|
function matrixTableHtml(el, bodyHtml) {
|
|
1617
2843
|
const opts = el.options;
|
|
1618
2844
|
const colWidths = opts.tableColWidths ?? [];
|
|
@@ -1627,7 +2853,7 @@ function renderTableElement(el, isMeasure, measureAttr, ctx) {
|
|
|
1627
2853
|
const opts = el.options;
|
|
1628
2854
|
const style = elementPositionStyle(opts.left ?? 0, opts.top ?? 0, opts.width ?? 100, void 0, opts.zIndex);
|
|
1629
2855
|
const renderRows = opts._renderRows ?? [];
|
|
1630
|
-
const bodyHtml = renderMatrixRows(renderRows, 0, renderRows.length, opts, isMeasure, ctx);
|
|
2856
|
+
const bodyHtml = renderMatrixRows(renderRows, 0, renderRows.length, opts, isMeasure, ctx, { elementId: el.id, kind: "b" });
|
|
1631
2857
|
return `<div class="print-element" style="${style};overflow:visible;"${measureAttr}>
|
|
1632
2858
|
${matrixTableHtml(el, bodyHtml)}
|
|
1633
2859
|
</div>`;
|
|
@@ -1639,8 +2865,8 @@ function renderTableSlice(el, section, ctx) {
|
|
|
1639
2865
|
const startRow = section.startRow ?? 0;
|
|
1640
2866
|
const endRow = section.endRow ?? renderRows.length;
|
|
1641
2867
|
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);
|
|
2868
|
+
const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
|
|
2869
|
+
const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
|
|
1644
2870
|
const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
|
|
1645
2871
|
const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
|
|
1646
2872
|
return `<div class="print-element" style="${style};overflow:visible;">
|
|
@@ -1664,12 +2890,12 @@ function renderSubtotalRows(el, section, opts, ctx) {
|
|
|
1664
2890
|
...tpl,
|
|
1665
2891
|
cells: tpl.cells.map((cell) => cell.rawFormatter ? { ...cell, content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData }) } : cell)
|
|
1666
2892
|
}));
|
|
1667
|
-
return renderMatrixRows(rows, 0, rows.length, opts, false, ctx);
|
|
2893
|
+
return renderMatrixRows(rows, 0, rows.length, opts, false, ctx, { elementId: el.id, kind: "st" });
|
|
1668
2894
|
}
|
|
1669
2895
|
function renderSummaryRows(el, opts, ctx) {
|
|
1670
2896
|
const summaryRows = opts._summaryRows ?? [];
|
|
1671
2897
|
if (summaryRows.length === 0) return "";
|
|
1672
|
-
return renderMatrixRows(summaryRows, 0, summaryRows.length, opts, false, ctx);
|
|
2898
|
+
return renderMatrixRows(summaryRows, 0, summaryRows.length, opts, false, ctx, { elementId: el.id, kind: "sm" });
|
|
1673
2899
|
}
|
|
1674
2900
|
function renderFlowGroup(el, section, template, ctx) {
|
|
1675
2901
|
const opts = el.options ?? {};
|
|
@@ -1685,8 +2911,8 @@ function renderFlowGroup(el, section, template, ctx) {
|
|
|
1685
2911
|
if (endRow > startRow || section.subtotal || section.summary) {
|
|
1686
2912
|
const renderRows = opts._renderRows ?? [];
|
|
1687
2913
|
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);
|
|
2914
|
+
const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
|
|
2915
|
+
const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
|
|
1690
2916
|
const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
|
|
1691
2917
|
const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
|
|
1692
2918
|
sliceHtml = `<div class="flow-slice" style="position:relative;width:${mm(tableWidth)};overflow:visible;">
|
|
@@ -1754,167 +2980,707 @@ function esc(str) {
|
|
|
1754
2980
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1755
2981
|
}
|
|
1756
2982
|
|
|
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 };
|
|
2983
|
+
// src/render/continuous-paper.ts
|
|
2984
|
+
var MIN_CONTINUOUS_HEIGHT_MM = 25.4;
|
|
2985
|
+
function composeContinuousHeight(template, contentBottomMm) {
|
|
2986
|
+
const mb = template.margins?.bottom ?? 0;
|
|
2987
|
+
const footerH = template.footer?.height ?? 0;
|
|
2988
|
+
const height = Math.max(contentBottomMm, 0) + footerH + mb;
|
|
2989
|
+
return Math.max(MIN_CONTINUOUS_HEIGHT_MM, Math.round(height * 100) / 100);
|
|
1768
2990
|
}
|
|
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
|
-
});
|
|
2991
|
+
|
|
2992
|
+
// src/print/codes.ts
|
|
2993
|
+
function codeSpecKey(value, cellType, opts = {}) {
|
|
2994
|
+
return JSON.stringify([
|
|
2995
|
+
value,
|
|
2996
|
+
cellType,
|
|
2997
|
+
opts.barcodeType ?? null,
|
|
2998
|
+
opts.qrCodeLevel ?? null,
|
|
2999
|
+
opts.showText ?? null,
|
|
3000
|
+
opts.barWidth ?? null,
|
|
3001
|
+
opts.fontSize ?? null
|
|
3002
|
+
]);
|
|
3003
|
+
}
|
|
3004
|
+
function createMapCodeRenderer(map) {
|
|
3005
|
+
return {
|
|
3006
|
+
render(value, cellType, opts) {
|
|
3007
|
+
const svg = map.get(codeSpecKey(value, cellType, opts));
|
|
3008
|
+
if (!svg) throw new Error(`\u7801\u503C\u672A\u6E32\u67D3\uFF1A${value}`);
|
|
3009
|
+
return svg;
|
|
1799
3010
|
}
|
|
1800
|
-
|
|
1801
|
-
} finally {
|
|
1802
|
-
iframe.remove();
|
|
1803
|
-
}
|
|
3011
|
+
};
|
|
1804
3012
|
}
|
|
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();
|
|
3013
|
+
function createCollectingCodeRenderer(base) {
|
|
3014
|
+
const collected = /* @__PURE__ */ new Map();
|
|
3015
|
+
return {
|
|
3016
|
+
renderer: {
|
|
3017
|
+
render(value, cellType, opts = {}) {
|
|
3018
|
+
const key = codeSpecKey(value, cellType, opts);
|
|
3019
|
+
const hit = base?.get(key);
|
|
3020
|
+
if (hit) return hit;
|
|
3021
|
+
collected.set(key, { key, value, cellType, opts });
|
|
3022
|
+
throw new Error("collect");
|
|
1826
3023
|
}
|
|
3024
|
+
},
|
|
3025
|
+
takeSpecs() {
|
|
3026
|
+
const specs = [...collected.values()];
|
|
3027
|
+
collected.clear();
|
|
3028
|
+
return specs;
|
|
3029
|
+
}
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
function mergeCodeMaps(...maps) {
|
|
3033
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3034
|
+
for (const map of maps) {
|
|
3035
|
+
for (const [key, svg] of map) merged.set(key, svg);
|
|
3036
|
+
}
|
|
3037
|
+
return merged;
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
// src/print/units.ts
|
|
3041
|
+
var PX_PER_MM2 = 3.7795275591;
|
|
3042
|
+
function pxToMm(px) {
|
|
3043
|
+
return px / PX_PER_MM2;
|
|
3044
|
+
}
|
|
3045
|
+
function mmToPx2(mm2) {
|
|
3046
|
+
return Math.round(mm2 * PX_PER_MM2);
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
// src/print/paper.ts
|
|
3050
|
+
function positive(value) {
|
|
3051
|
+
return typeof value === "number" && value > 0 ? value : void 0;
|
|
3052
|
+
}
|
|
3053
|
+
function escapeHeightMm(input) {
|
|
3054
|
+
return positive(input.paperHeightMm) ?? positive(input.override?.height);
|
|
3055
|
+
}
|
|
3056
|
+
function resolvePaperMm(input) {
|
|
3057
|
+
const width = positive(input.override?.width) ?? input.paperMm.width;
|
|
3058
|
+
const overrideHeight = positive(input.override?.height);
|
|
3059
|
+
if (input.continuous) {
|
|
3060
|
+
return overrideHeight ? { paperMm: { width, height: overrideHeight }, heightSource: "config" } : { paperMm: { width, height: input.paperMm.height }, heightSource: "derived" };
|
|
3061
|
+
}
|
|
3062
|
+
return { paperMm: { width, height: overrideHeight ?? input.paperMm.height }, heightSource: "config" };
|
|
3063
|
+
}
|
|
3064
|
+
function paperViewportPx(paper) {
|
|
3065
|
+
return { width: mmToPx2(paper.width), height: mmToPx2(paper.height) };
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
// src/print/measure.ts
|
|
3069
|
+
function normalizeMeasurements(raw, template) {
|
|
3070
|
+
const index = new Map(template.elements.map((el) => [el.id, el]));
|
|
3071
|
+
const measured = /* @__PURE__ */ new Map();
|
|
3072
|
+
for (const item of raw) {
|
|
3073
|
+
const element = index.get(item.id);
|
|
3074
|
+
const repeatCount = element?.options?._repeatHeaderCount ?? 0;
|
|
3075
|
+
const rowHeights = item.rowHeightsPx?.map(pxToMm);
|
|
3076
|
+
measured.set(item.id, {
|
|
3077
|
+
id: item.id,
|
|
3078
|
+
measuredHeight: pxToMm(item.heightPx),
|
|
3079
|
+
measuredRowHeights: rowHeights,
|
|
3080
|
+
repeatHeaderHeight: rowHeights && rowHeights.length > 0 && repeatCount > 0 ? rowHeights.slice(0, repeatCount).reduce((sum2, h) => sum2 + h, 0) : 0
|
|
3081
|
+
});
|
|
3082
|
+
}
|
|
3083
|
+
return measured;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// src/print/apply-text-fit.ts
|
|
3087
|
+
var CELL_ROW_SOURCES = {
|
|
3088
|
+
b: "_renderRows",
|
|
3089
|
+
st: "_subtotalTemplates",
|
|
3090
|
+
sm: "_summaryRows"
|
|
3091
|
+
};
|
|
3092
|
+
function applyTextFitSizes(template, fits) {
|
|
3093
|
+
if (!fits || fits.length === 0) return;
|
|
3094
|
+
const index = /* @__PURE__ */ new Map();
|
|
3095
|
+
const collect = (elements) => {
|
|
3096
|
+
for (const el of elements ?? []) index.set(el.id, el);
|
|
3097
|
+
};
|
|
3098
|
+
collect(template.elements);
|
|
3099
|
+
collect(template.header?.elements);
|
|
3100
|
+
collect(template.footer?.elements);
|
|
3101
|
+
collect(template.firstPageOverlay?.elements);
|
|
3102
|
+
for (const fit of fits) {
|
|
3103
|
+
const parsed = parseCellFitKey(fit.key);
|
|
3104
|
+
if (!parsed) {
|
|
3105
|
+
const el2 = index.get(fit.key);
|
|
3106
|
+
if (el2) el2.options._fitFontSize = roundFontSize(fit.fontSizePt);
|
|
3107
|
+
continue;
|
|
3108
|
+
}
|
|
3109
|
+
const el = index.get(parsed.elementId);
|
|
3110
|
+
const rows = el?.options?.[CELL_ROW_SOURCES[parsed.kind]];
|
|
3111
|
+
const cell = rows?.[parsed.rowIndex]?.cells?.[parsed.colIndex];
|
|
3112
|
+
if (cell) cell.fittedFontSize = roundFontSize(fit.fontSizePt);
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// src/print/normalize-print-data.ts
|
|
3117
|
+
var MAX_BATCH_COPIES = 500;
|
|
3118
|
+
function isPlainRecord(v) {
|
|
3119
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3120
|
+
}
|
|
3121
|
+
function normalizePrintData(raw) {
|
|
3122
|
+
if (raw === void 0) return { mode: "single", data: {} };
|
|
3123
|
+
if (!Array.isArray(raw)) return { mode: "single", data: raw };
|
|
3124
|
+
if (raw.length === 0) {
|
|
3125
|
+
throw new Error("\u6279\u91CF\u6253\u5370\u6570\u636E\u5FC5\u987B\u662F\u975E\u7A7A\u5BF9\u8C61\u6570\u7EC4");
|
|
3126
|
+
}
|
|
3127
|
+
if (raw.length > MAX_BATCH_COPIES) {
|
|
3128
|
+
throw new Error(`\u6279\u91CF\u6253\u5370\u6700\u591A\u652F\u6301 ${MAX_BATCH_COPIES} \u4EFD\uFF0C\u5F53\u524D ${raw.length} \u4EFD`);
|
|
3129
|
+
}
|
|
3130
|
+
for (let i = 0; i < raw.length; i++) {
|
|
3131
|
+
if (!isPlainRecord(raw[i])) {
|
|
3132
|
+
throw new Error(`\u6279\u91CF\u6253\u5370\u6570\u636E\u7B2C ${i + 1} \u9879\u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
return { mode: "batch", dataList: raw };
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
// src/print/batch-compose.ts
|
|
3139
|
+
function composeBatchHtml(copies) {
|
|
3140
|
+
const bound = copies[0].bound;
|
|
3141
|
+
const continuous = isContinuousPaper(bound);
|
|
3142
|
+
const bodyInner = copies.map((copy, i) => {
|
|
3143
|
+
const cls = continuous ? `print-copy print-copy-${i}` : "print-copy";
|
|
3144
|
+
const pages = renderFinalPages(copy.bound, copy.pageLayouts, copy.data, {
|
|
3145
|
+
codeRenderer: copy.codeRenderer,
|
|
3146
|
+
pageHeightMm: copy.derivedHeightMm
|
|
3147
|
+
});
|
|
3148
|
+
return `<section class="${cls}">
|
|
3149
|
+
${pages}
|
|
3150
|
+
</section>`;
|
|
3151
|
+
}).join("\n");
|
|
3152
|
+
const pageCss = continuous ? buildBatchPageCss(bound, copies.map((c) => ({ heightMm: c.derivedHeightMm }))) : `${buildPageCss(bound)}
|
|
3153
|
+
.print-copy:not(:last-child){break-after:page;page-break-after:always;}`;
|
|
3154
|
+
const css = buildFontFaceCss(bound.fonts) + pageCss;
|
|
3155
|
+
return {
|
|
3156
|
+
html: wrapHtmlDocument(
|
|
3157
|
+
css,
|
|
3158
|
+
bodyInner,
|
|
3159
|
+
continuous ? "continuous" : void 0
|
|
3160
|
+
),
|
|
3161
|
+
pageCount: copies.reduce((sum2, c) => sum2 + c.pageLayouts.length, 0),
|
|
3162
|
+
copyPaperMm: copies.map((c) => c.paperMm)
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
// src/print/tiling.ts
|
|
3167
|
+
var TILE_DEFAULTS = {
|
|
3168
|
+
enabled: true,
|
|
3169
|
+
sheetPaperSize: "A4",
|
|
3170
|
+
sheetOrientation: "portrait",
|
|
3171
|
+
sheetMargin: { top: 10, right: 10, bottom: 10, left: 10 },
|
|
3172
|
+
gapX: 2,
|
|
3173
|
+
gapY: 2,
|
|
3174
|
+
columns: 2
|
|
3175
|
+
};
|
|
3176
|
+
var TilingError = class extends Error {
|
|
3177
|
+
constructor(code, message) {
|
|
3178
|
+
super(message);
|
|
3179
|
+
this.name = "TilingError";
|
|
3180
|
+
this.code = code;
|
|
3181
|
+
}
|
|
3182
|
+
};
|
|
3183
|
+
function roundMm(value) {
|
|
3184
|
+
return Math.round(value * 10) / 10;
|
|
3185
|
+
}
|
|
3186
|
+
function normalizeTilingOptions(t) {
|
|
3187
|
+
const raw = t.tiling;
|
|
3188
|
+
return {
|
|
3189
|
+
...TILE_DEFAULTS,
|
|
3190
|
+
...raw,
|
|
3191
|
+
sheetMargin: { ...TILE_DEFAULTS.sheetMargin, ...raw?.sheetMargin ?? {} }
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
function resolveSheetMm(t, opts) {
|
|
3195
|
+
const cfg = normalizeTilingOptions(t);
|
|
3196
|
+
const ov = opts?.paperOverride;
|
|
3197
|
+
if (ov && typeof ov.width === "number" && ov.width > 0 && typeof ov.height === "number" && ov.height > 0) {
|
|
3198
|
+
return { width: ov.width, height: ov.height };
|
|
3199
|
+
}
|
|
3200
|
+
if (cfg.sheetPaperSize === "CUSTOM") {
|
|
3201
|
+
return {
|
|
3202
|
+
width: cfg.sheetCustomWidth ?? PAPER_DIMENSIONS.A4.width,
|
|
3203
|
+
height: cfg.sheetCustomHeight ?? PAPER_DIMENSIONS.A4.height
|
|
1827
3204
|
};
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
3205
|
+
}
|
|
3206
|
+
const preset = cfg.sheetPaperSize ?? "A4";
|
|
3207
|
+
const base = PAPER_DIMENSIONS[preset] ?? PAPER_DIMENSIONS.A4;
|
|
3208
|
+
return cfg.sheetOrientation === "landscape" ? { width: base.height, height: base.width } : { ...base };
|
|
3209
|
+
}
|
|
3210
|
+
function availableArea(sheet, margin) {
|
|
3211
|
+
return {
|
|
3212
|
+
w: sheet.width - margin.left - margin.right,
|
|
3213
|
+
h: sheet.height - margin.top - margin.bottom
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
function calcMaxColumns(availW, labelW, gapX) {
|
|
3217
|
+
return Math.max(0, Math.floor((availW + gapX) / (labelW + gapX)));
|
|
3218
|
+
}
|
|
3219
|
+
function computeTileLayout(t, opts) {
|
|
3220
|
+
const issues = validateTiling(t, opts);
|
|
3221
|
+
if (issues.length) throw new TilingError(issues[0].code, issues[0].message);
|
|
3222
|
+
const cfg = normalizeTilingOptions(t);
|
|
3223
|
+
const sheet = resolveSheetMm(t, opts);
|
|
3224
|
+
const label = getPaperDimensions(t);
|
|
3225
|
+
const avail = availableArea(sheet, cfg.sheetMargin);
|
|
3226
|
+
const rows = Math.floor((avail.h + cfg.gapY) / (label.height + cfg.gapY));
|
|
3227
|
+
return {
|
|
3228
|
+
tile: { width: label.width, height: label.height },
|
|
3229
|
+
sheet,
|
|
3230
|
+
columns: cfg.columns,
|
|
3231
|
+
rows,
|
|
3232
|
+
perSheet: cfg.columns * rows,
|
|
3233
|
+
maxColumns: calcMaxColumns(avail.w, label.width, cfg.gapX),
|
|
3234
|
+
margin: { ...cfg.sheetMargin },
|
|
3235
|
+
gapX: cfg.gapX,
|
|
3236
|
+
gapY: cfg.gapY
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
function tilePosition(layout, index) {
|
|
3240
|
+
const slot = index % layout.perSheet;
|
|
3241
|
+
const col = slot % layout.columns;
|
|
3242
|
+
const row = Math.floor(slot / layout.columns);
|
|
3243
|
+
return {
|
|
3244
|
+
left: roundMm(layout.margin.left + col * (layout.tile.width + layout.gapX)),
|
|
3245
|
+
top: roundMm(layout.margin.top + row * (layout.tile.height + layout.gapY))
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
function validateTiling(t, opts) {
|
|
3249
|
+
const issues = [];
|
|
3250
|
+
const cfg = normalizeTilingOptions(t);
|
|
3251
|
+
if (isContinuousPaper(t)) {
|
|
3252
|
+
issues.push({
|
|
3253
|
+
code: "CONTINUOUS_UNSUPPORTED",
|
|
3254
|
+
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"
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
3257
|
+
const sheetPaperSize = cfg.sheetPaperSize;
|
|
3258
|
+
const sheetContinuous = isContinuousPaperSize(sheetPaperSize ?? "");
|
|
3259
|
+
if (sheetContinuous) {
|
|
3260
|
+
issues.push({ code: "SHEET_CONTINUOUS", message: "\u62FC\u7248\u76EE\u6807\u7EB8\u5F20\u4E0D\u80FD\u662F\u8FDE\u7EED\u7EB8" });
|
|
3261
|
+
}
|
|
3262
|
+
const customSizeInvalid = cfg.sheetPaperSize === "CUSTOM" && !((cfg.sheetCustomWidth ?? 0) > 0 && (cfg.sheetCustomHeight ?? 0) > 0);
|
|
3263
|
+
if (customSizeInvalid) {
|
|
3264
|
+
issues.push({
|
|
3265
|
+
code: "SHEET_SIZE_INVALID",
|
|
3266
|
+
message: "\u62FC\u7248\u81EA\u5B9A\u4E49\u7EB8\u5F20\u5BBD\u9AD8\u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6570\u503C\uFF08mm\uFF09"
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3269
|
+
if (customSizeInvalid || sheetContinuous) return issues;
|
|
3270
|
+
const sheet = resolveSheetMm(t, opts);
|
|
3271
|
+
const label = getPaperDimensions(t);
|
|
3272
|
+
const avail = availableArea(sheet, cfg.sheetMargin);
|
|
3273
|
+
const marginLR = roundMm(cfg.sheetMargin.left + cfg.sheetMargin.right);
|
|
3274
|
+
if (!Number.isInteger(cfg.columns) || cfg.columns < 1) {
|
|
3275
|
+
issues.push({ code: "COLUMNS_INVALID", message: "\u62FC\u7248\u5217\u6570\u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6574\u6570" });
|
|
3276
|
+
return issues;
|
|
3277
|
+
}
|
|
3278
|
+
const maxColumns = calcMaxColumns(avail.w, label.width, cfg.gapX);
|
|
3279
|
+
if (cfg.columns > maxColumns) {
|
|
3280
|
+
const needW = roundMm(cfg.columns * label.width + (cfg.columns - 1) * cfg.gapX);
|
|
3281
|
+
issues.push({
|
|
3282
|
+
code: "COLUMNS_OVERFLOW",
|
|
3283
|
+
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"
|
|
3284
|
+
});
|
|
3285
|
+
}
|
|
3286
|
+
if (Math.floor((avail.h + cfg.gapY) / (label.height + cfg.gapY)) < 1) {
|
|
3287
|
+
issues.push({
|
|
3288
|
+
code: "LABEL_TOO_TALL",
|
|
3289
|
+
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"
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
return issues;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
// src/print/tile-compose.ts
|
|
3296
|
+
function composeTiledHtml(input) {
|
|
3297
|
+
const { copies, layout } = input;
|
|
3298
|
+
const bound = copies[0].bound;
|
|
3299
|
+
const sheets = [];
|
|
3300
|
+
for (let start = 0; start < copies.length; start += layout.perSheet) {
|
|
3301
|
+
const tiles = copies.slice(start, start + layout.perSheet).map((copy, i) => {
|
|
3302
|
+
const pos = tilePosition(layout, i);
|
|
3303
|
+
const page = renderFinalPages(copy.bound, copy.pageLayouts, copy.data, {
|
|
3304
|
+
codeRenderer: copy.codeRenderer
|
|
3305
|
+
});
|
|
3306
|
+
return `<div class="print-tile" style="left:${pos.left}mm;top:${pos.top}mm">
|
|
3307
|
+
${page}
|
|
3308
|
+
</div>`;
|
|
3309
|
+
}).join("\n");
|
|
3310
|
+
sheets.push(`<section class="print-sheet">
|
|
3311
|
+
${tiles}
|
|
3312
|
+
</section>`);
|
|
3313
|
+
}
|
|
3314
|
+
const css = buildFontFaceCss(bound.fonts) + buildPageCss(bound) + "\n" + buildSheetPageCss(layout);
|
|
3315
|
+
return {
|
|
3316
|
+
html: wrapHtmlDocument(css, sheets.join("\n")),
|
|
3317
|
+
sheetCount: sheets.length,
|
|
3318
|
+
perSheet: layout.perSheet
|
|
3319
|
+
};
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// src/print/multi-template.ts
|
|
3323
|
+
function pageLabel(p, index) {
|
|
3324
|
+
return p.name ? `\u7B2C ${index + 1} \u9875\u300C${p.name}\u300D` : `\u7B2C ${index + 1} \u9875`;
|
|
3325
|
+
}
|
|
3326
|
+
function normalizeTemplate(templateJson) {
|
|
3327
|
+
const pages = isMultiPageTemplate(templateJson) ? templateJson.pages : [templateJson];
|
|
3328
|
+
if (pages.length === 0) {
|
|
3329
|
+
throw new Error("\u591A\u9875\u9762\u6A21\u677F\u81F3\u5C11\u9700\u8981\u4E00\u9875");
|
|
3330
|
+
}
|
|
3331
|
+
if (pages.length === 1) return pages;
|
|
3332
|
+
const firstPaper = getPaperDimensions(pages[0]);
|
|
3333
|
+
pages.forEach((p, i) => {
|
|
3334
|
+
if (isContinuousPaper(p)) {
|
|
3335
|
+
throw new Error(`\u591A\u9875\u9762\u6A21\u677F\u4E0D\u652F\u6301\u8FDE\u7EED\u7EB8\uFF08${pageLabel(p, i)}\uFF09`);
|
|
3336
|
+
}
|
|
3337
|
+
if (p.tiling?.enabled === true) {
|
|
3338
|
+
throw new Error(`\u591A\u9875\u9762\u6A21\u677F\u4E0D\u652F\u6301\u6807\u7B7E\u62FC\u7248\uFF08${pageLabel(p, i)}\uFF09`);
|
|
3339
|
+
}
|
|
3340
|
+
const d = getPaperDimensions(p);
|
|
3341
|
+
if (d.width !== firstPaper.width || d.height !== firstPaper.height) {
|
|
3342
|
+
throw new Error(
|
|
3343
|
+
`\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`
|
|
3344
|
+
);
|
|
1833
3345
|
}
|
|
1834
3346
|
});
|
|
3347
|
+
return pages;
|
|
1835
3348
|
}
|
|
1836
|
-
function
|
|
1837
|
-
return
|
|
1838
|
-
}
|
|
1839
|
-
function
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
3349
|
+
function isMultiPageTemplate(templateJson) {
|
|
3350
|
+
return Array.isArray(templateJson.pages);
|
|
3351
|
+
}
|
|
3352
|
+
function mergeFontDeclarations(pages) {
|
|
3353
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3354
|
+
const merged = [];
|
|
3355
|
+
for (const p of pages) {
|
|
3356
|
+
for (const f of p.fonts ?? []) {
|
|
3357
|
+
const key = f.family;
|
|
3358
|
+
if (seen.has(key)) continue;
|
|
3359
|
+
seen.add(key);
|
|
3360
|
+
merged.push(f);
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
return merged;
|
|
3364
|
+
}
|
|
3365
|
+
function composeMultiPageDocument(copies) {
|
|
3366
|
+
if (copies.length === 0) {
|
|
3367
|
+
throw new Error("\u591A\u9875\u9762\u6A21\u677F\u81F3\u5C11\u9700\u8981\u4E00\u4EFD\u6570\u636E");
|
|
3368
|
+
}
|
|
3369
|
+
const templates = copies[0].boundPages;
|
|
3370
|
+
const css = buildFontFaceCss(mergeFontDeclarations(templates)) + [
|
|
3371
|
+
buildPageRuleCss(templates[0]),
|
|
3372
|
+
buildBasePageCss(),
|
|
3373
|
+
...templates.map((t, i) => buildPageGeometryCss(t, `.mt-${i}`)),
|
|
3374
|
+
...copies.length > 1 ? [COPY_BREAK_CSS] : []
|
|
3375
|
+
].join("\n\n");
|
|
3376
|
+
const bodyInner = copies.map((copy) => {
|
|
3377
|
+
const totalPages = copy.layoutsPerPage.reduce((s, ls) => s + ls.length, 0);
|
|
3378
|
+
let offset = 0;
|
|
3379
|
+
const fragments = copy.layoutsPerPage.map((layouts, i) => {
|
|
3380
|
+
const html = renderFinalPages(
|
|
3381
|
+
copy.boundPages[i],
|
|
3382
|
+
layouts,
|
|
3383
|
+
copy.data,
|
|
3384
|
+
{ codeRenderer: copy.codeRenderers?.[i] },
|
|
3385
|
+
{
|
|
3386
|
+
pageOffset: offset,
|
|
3387
|
+
totalPages,
|
|
3388
|
+
pageClass: `mt-${i}`
|
|
3389
|
+
}
|
|
3390
|
+
);
|
|
3391
|
+
offset += layouts.length;
|
|
3392
|
+
return html;
|
|
3393
|
+
});
|
|
3394
|
+
const pages = fragments.join("\n");
|
|
3395
|
+
return copies.length > 1 ? `<section class="print-copy">
|
|
3396
|
+
${pages}
|
|
3397
|
+
</section>` : pages;
|
|
3398
|
+
}).join("\n");
|
|
3399
|
+
const pageCount = copies.reduce(
|
|
3400
|
+
(sum2, c) => sum2 + c.layoutsPerPage.reduce((s, ls) => s + ls.length, 0),
|
|
3401
|
+
0
|
|
3402
|
+
);
|
|
3403
|
+
const pageLayouts = [];
|
|
3404
|
+
let g = 0;
|
|
3405
|
+
for (const c of copies) {
|
|
3406
|
+
for (const ls of c.layoutsPerPage) {
|
|
3407
|
+
for (const p of ls) pageLayouts.push({ ...p, pageIndex: g++ });
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
return {
|
|
3411
|
+
html: wrapHtmlDocument(css, bodyInner),
|
|
3412
|
+
pageCount,
|
|
3413
|
+
pageLayouts
|
|
3414
|
+
};
|
|
1849
3415
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
3416
|
+
|
|
3417
|
+
// src/print/pipeline.ts
|
|
3418
|
+
async function prepareDocument(job, runtime) {
|
|
3419
|
+
return runtime.withSession(job, (session) => prepareWithSession(job, session));
|
|
3420
|
+
}
|
|
3421
|
+
async function prepareWithSession(job, session) {
|
|
3422
|
+
const pageTemplates = normalizeTemplate(job.templateJson);
|
|
3423
|
+
if (pageTemplates.length > 1) {
|
|
3424
|
+
const normalized2 = normalizePrintData(job.printData);
|
|
3425
|
+
const rows = normalized2.mode === "batch" ? normalized2.dataList : [normalized2.data];
|
|
3426
|
+
const copies2 = [];
|
|
3427
|
+
for (let i = 0; i < rows.length; i++) {
|
|
3428
|
+
try {
|
|
3429
|
+
copies2.push(await prepareMultiCopy(job, session, rows[i]));
|
|
3430
|
+
} catch (err) {
|
|
3431
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
3432
|
+
throw new Error(`\u7B2C ${i + 1} \u4EFD\u6E32\u67D3\u5931\u8D25\uFF1A${reason}`);
|
|
1864
3433
|
}
|
|
1865
|
-
result.push({ id, height: heightPx / PX_PER_MM, rowHeights });
|
|
1866
|
-
} else {
|
|
1867
|
-
result.push({ id, height: heightPx / PX_PER_MM });
|
|
1868
3434
|
}
|
|
3435
|
+
const doc = composeMultiPageDocument(copies2);
|
|
3436
|
+
return {
|
|
3437
|
+
html: doc.html,
|
|
3438
|
+
pageCount: doc.pageCount,
|
|
3439
|
+
paperMm: getOutputPaperDimensions(pageTemplates[0]),
|
|
3440
|
+
continuous: false,
|
|
3441
|
+
heightSource: "config",
|
|
3442
|
+
pageLayouts: doc.pageLayouts,
|
|
3443
|
+
copies: copies2.length
|
|
3444
|
+
};
|
|
1869
3445
|
}
|
|
1870
|
-
|
|
3446
|
+
const singleJob = pageTemplates[0] !== job.templateJson ? { ...job, templateJson: pageTemplates[0] } : job;
|
|
3447
|
+
const template = pageTemplates[0];
|
|
3448
|
+
const normalized = normalizePrintData(singleJob.printData);
|
|
3449
|
+
if (normalized.mode === "single") {
|
|
3450
|
+
const single = await prepareSingleWithSession(singleJob, session, normalized.data);
|
|
3451
|
+
if (template.tiling?.enabled === true) {
|
|
3452
|
+
return composeTiledPrepared(singleJob, [{
|
|
3453
|
+
bound: single.bound,
|
|
3454
|
+
pageLayouts: single.pageLayouts,
|
|
3455
|
+
data: normalized.data,
|
|
3456
|
+
codeRenderer: single.codeRenderer,
|
|
3457
|
+
derivedHeightMm: single.derivedHeightMm,
|
|
3458
|
+
paperMm: single.paperMm,
|
|
3459
|
+
heightSource: single.heightSource
|
|
3460
|
+
}]);
|
|
3461
|
+
}
|
|
3462
|
+
return toPreparedDocument(single);
|
|
3463
|
+
}
|
|
3464
|
+
const copies = [];
|
|
3465
|
+
for (let i = 0; i < normalized.dataList.length; i++) {
|
|
3466
|
+
try {
|
|
3467
|
+
const single = await prepareSingleWithSession(singleJob, session, normalized.dataList[i]);
|
|
3468
|
+
copies.push({
|
|
3469
|
+
bound: single.bound,
|
|
3470
|
+
pageLayouts: single.pageLayouts,
|
|
3471
|
+
data: normalized.dataList[i],
|
|
3472
|
+
codeRenderer: single.codeRenderer,
|
|
3473
|
+
derivedHeightMm: single.derivedHeightMm,
|
|
3474
|
+
paperMm: single.paperMm,
|
|
3475
|
+
heightSource: single.heightSource
|
|
3476
|
+
});
|
|
3477
|
+
} catch (err) {
|
|
3478
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
3479
|
+
throw new Error(`\u7B2C ${i + 1} \u4EFD\u6E32\u67D3\u5931\u8D25\uFF1A${reason}`);
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
if (template.tiling?.enabled === true) {
|
|
3483
|
+
return composeTiledPrepared(singleJob, copies);
|
|
3484
|
+
}
|
|
3485
|
+
const merged = composeBatchHtml(copies);
|
|
3486
|
+
return {
|
|
3487
|
+
html: merged.html,
|
|
3488
|
+
pageCount: merged.pageCount,
|
|
3489
|
+
paperMm: copies[0].paperMm,
|
|
3490
|
+
continuous: isContinuousPaper(copies[0].bound),
|
|
3491
|
+
// 同模板同参数各份来源必然一致;不能用 derivedHeightMm 是否存在判断——
|
|
3492
|
+
// 连续纸逃生门时该字段有值但来源是 config
|
|
3493
|
+
heightSource: copies[0].heightSource,
|
|
3494
|
+
// 各份 pageIndex 均从 0 开始,批量拼接后该字段仅作调试用途
|
|
3495
|
+
pageLayouts: copies.flatMap((c) => c.pageLayouts),
|
|
3496
|
+
copies: copies.length,
|
|
3497
|
+
copyPaperMm: merged.copyPaperMm
|
|
3498
|
+
};
|
|
1871
3499
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
3500
|
+
function toPreparedDocument(s) {
|
|
3501
|
+
return {
|
|
3502
|
+
html: s.html,
|
|
3503
|
+
pageCount: s.pageCount,
|
|
3504
|
+
paperMm: s.paperMm,
|
|
3505
|
+
continuous: s.continuous,
|
|
3506
|
+
heightSource: s.heightSource,
|
|
3507
|
+
pageLayouts: s.pageLayouts,
|
|
3508
|
+
copies: 1
|
|
3509
|
+
};
|
|
3510
|
+
}
|
|
3511
|
+
function composeTiledPrepared(job, copies) {
|
|
3512
|
+
const bound = copies[0].bound;
|
|
3513
|
+
const layout = computeTileLayout(bound, { paperOverride: job.paperOverride });
|
|
3514
|
+
copies.forEach((copy, i) => {
|
|
3515
|
+
const overflow = copy.pageLayouts[0]?.overflow === true;
|
|
3516
|
+
if (copy.pageLayouts.length !== 1 || overflow) {
|
|
3517
|
+
const detail = overflow ? "\u5185\u5BB9\u8D85\u51FA\u7EB8\u5F20\u9AD8\u5EA6" : `\u6E32\u67D3\u51FA ${copy.pageLayouts.length} \u9875`;
|
|
3518
|
+
throw new Error(
|
|
3519
|
+
`\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`
|
|
3520
|
+
);
|
|
3521
|
+
}
|
|
1887
3522
|
});
|
|
1888
|
-
|
|
3523
|
+
const tiled = composeTiledHtml({
|
|
3524
|
+
copies: copies.map((c) => ({
|
|
3525
|
+
bound: c.bound,
|
|
3526
|
+
pageLayouts: c.pageLayouts,
|
|
3527
|
+
data: c.data,
|
|
3528
|
+
codeRenderer: c.codeRenderer
|
|
3529
|
+
})),
|
|
3530
|
+
layout
|
|
3531
|
+
});
|
|
3532
|
+
return {
|
|
3533
|
+
html: tiled.html,
|
|
3534
|
+
// 语义变更:pageCount = 实际输出张数(客户端任务历史、预览「共 N 页」都按此口径)
|
|
3535
|
+
pageCount: tiled.sheetCount,
|
|
3536
|
+
paperMm: { width: layout.sheet.width, height: layout.sheet.height },
|
|
3537
|
+
continuous: false,
|
|
3538
|
+
heightSource: "config",
|
|
3539
|
+
// 调试用途;各份 pageIndex 均从 0 开始
|
|
3540
|
+
pageLayouts: copies.flatMap((c) => c.pageLayouts),
|
|
3541
|
+
copies: copies.length
|
|
3542
|
+
};
|
|
1889
3543
|
}
|
|
1890
|
-
function
|
|
1891
|
-
const
|
|
1892
|
-
const
|
|
1893
|
-
|
|
3544
|
+
async function prepareSingleWithSession(job, session, data) {
|
|
3545
|
+
const bound = bindData(job.templateJson, data, job.baseUrl, job.fontBaseUrl);
|
|
3546
|
+
const continuous = isContinuousPaper(bound);
|
|
3547
|
+
const designPaper = getPaperDimensions(bound);
|
|
3548
|
+
const viewport = paperViewportPx(designPaper);
|
|
3549
|
+
const heightEscape = escapeHeightMm({ paperHeightMm: job.paperHeightMm, override: job.paperOverride });
|
|
3550
|
+
const measurement = await buildHtmlWithCodes({
|
|
3551
|
+
bound,
|
|
3552
|
+
job,
|
|
3553
|
+
session,
|
|
3554
|
+
data,
|
|
3555
|
+
pageLayouts: [],
|
|
3556
|
+
isMeasurementPass: true
|
|
1894
3557
|
});
|
|
1895
|
-
const
|
|
1896
|
-
|
|
1897
|
-
const
|
|
1898
|
-
const
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
3558
|
+
const measurements = await session.measure(measurement.html, viewport);
|
|
3559
|
+
applyTextFitSizes(bound, measurements.fits);
|
|
3560
|
+
const pageLayouts = paginate(bound, normalizeMeasurements(measurements.measurements, bound));
|
|
3561
|
+
const finalBuild = await buildHtmlWithCodes({
|
|
3562
|
+
bound,
|
|
3563
|
+
job,
|
|
3564
|
+
session,
|
|
3565
|
+
data,
|
|
3566
|
+
pageLayouts,
|
|
3567
|
+
isMeasurementPass: false,
|
|
3568
|
+
baseMap: measurement.map
|
|
3569
|
+
});
|
|
3570
|
+
let html = finalBuild.html;
|
|
3571
|
+
let derivedHeightMm;
|
|
3572
|
+
if (continuous) {
|
|
3573
|
+
if (heightEscape && heightEscape > 0) {
|
|
3574
|
+
derivedHeightMm = heightEscape;
|
|
3575
|
+
} else {
|
|
3576
|
+
const bottomPx = await session.probeContentBottom(html, viewport);
|
|
3577
|
+
derivedHeightMm = composeContinuousHeight(bound, pxToMm(bottomPx));
|
|
1906
3578
|
}
|
|
3579
|
+
html = generateHtml(bound, pageLayouts, data, {
|
|
3580
|
+
codeRenderer: finalBuild.codeRenderer,
|
|
3581
|
+
pageHeightMm: derivedHeightMm
|
|
3582
|
+
});
|
|
1907
3583
|
}
|
|
1908
|
-
|
|
3584
|
+
const overrideForPaper = continuous && heightEscape ? { ...job.paperOverride, height: heightEscape } : job.paperOverride;
|
|
3585
|
+
const outputPaper = getOutputPaperDimensions(bound);
|
|
3586
|
+
const { paperMm, heightSource } = resolvePaperMm({
|
|
3587
|
+
paperMm: { width: outputPaper.width, height: derivedHeightMm ?? outputPaper.height },
|
|
3588
|
+
continuous,
|
|
3589
|
+
override: overrideForPaper
|
|
3590
|
+
});
|
|
3591
|
+
return {
|
|
3592
|
+
html,
|
|
3593
|
+
pageCount: pageLayouts.length,
|
|
3594
|
+
paperMm,
|
|
3595
|
+
continuous,
|
|
3596
|
+
heightSource,
|
|
3597
|
+
pageLayouts,
|
|
3598
|
+
copies: 1,
|
|
3599
|
+
bound,
|
|
3600
|
+
codeRenderer: finalBuild.codeRenderer,
|
|
3601
|
+
derivedHeightMm
|
|
3602
|
+
};
|
|
1909
3603
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
3604
|
+
async function prepareMultiCopy(job, session, data) {
|
|
3605
|
+
const templates = normalizeTemplate(job.templateJson);
|
|
3606
|
+
const viewport = paperViewportPx(getPaperDimensions(templates[0]));
|
|
3607
|
+
const boundPages = [];
|
|
3608
|
+
const layoutsPerPage = [];
|
|
3609
|
+
const codeRenderers = [];
|
|
3610
|
+
for (const t of templates) {
|
|
3611
|
+
const bound = bindData(t, data, job.baseUrl, job.fontBaseUrl);
|
|
3612
|
+
const measurement = await buildHtmlWithCodes({ bound, job, session, data, pageLayouts: [], isMeasurementPass: true });
|
|
3613
|
+
const measurements = await session.measure(measurement.html, viewport);
|
|
3614
|
+
applyTextFitSizes(bound, measurements.fits);
|
|
3615
|
+
const layouts = paginate(bound, normalizeMeasurements(measurements.measurements, bound));
|
|
3616
|
+
const final = await buildHtmlWithCodes({ bound, job, session, data, pageLayouts: layouts, isMeasurementPass: false, baseMap: measurement.map });
|
|
3617
|
+
boundPages.push(bound);
|
|
3618
|
+
layoutsPerPage.push(layouts);
|
|
3619
|
+
codeRenderers.push(final.codeRenderer);
|
|
3620
|
+
}
|
|
3621
|
+
return { boundPages, layoutsPerPage, data, codeRenderers };
|
|
3622
|
+
}
|
|
3623
|
+
async function buildHtmlWithCodes(input) {
|
|
3624
|
+
const baseMap = input.baseMap ?? /* @__PURE__ */ new Map();
|
|
3625
|
+
if (input.job.codeRenderer) {
|
|
3626
|
+
const html2 = generateHtml(input.bound, input.pageLayouts, input.data, {
|
|
3627
|
+
isMeasurementPass: input.isMeasurementPass,
|
|
3628
|
+
codeRenderer: input.job.codeRenderer
|
|
3629
|
+
});
|
|
3630
|
+
return { html: html2, codeRenderer: input.job.codeRenderer, map: baseMap };
|
|
1914
3631
|
}
|
|
1915
|
-
|
|
3632
|
+
const collector = createCollectingCodeRenderer(baseMap);
|
|
3633
|
+
const draft = generateHtml(input.bound, input.pageLayouts, input.data, {
|
|
3634
|
+
isMeasurementPass: input.isMeasurementPass,
|
|
3635
|
+
codeRenderer: collector.renderer
|
|
3636
|
+
});
|
|
3637
|
+
const extra = collector.takeSpecs();
|
|
3638
|
+
const rendered = extra.length > 0 ? await input.session.renderCodes(extra) : /* @__PURE__ */ new Map();
|
|
3639
|
+
const map = mergeCodeMaps(baseMap, rendered);
|
|
3640
|
+
const codeRenderer = map.size > 0 ? createMapCodeRenderer(map) : void 0;
|
|
3641
|
+
if (extra.length === 0) return { html: draft, codeRenderer, map };
|
|
3642
|
+
const html = generateHtml(input.bound, input.pageLayouts, input.data, {
|
|
3643
|
+
isMeasurementPass: input.isMeasurementPass,
|
|
3644
|
+
codeRenderer
|
|
3645
|
+
});
|
|
3646
|
+
return { html, codeRenderer, map };
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
// src/browser/browser-pagination.ts
|
|
3650
|
+
async function renderHtmlPages(template, printData, baseUrl, codeRenderer, options) {
|
|
3651
|
+
const prepared = await prepareDocument(
|
|
3652
|
+
{
|
|
3653
|
+
templateJson: template,
|
|
3654
|
+
printData,
|
|
3655
|
+
baseUrl,
|
|
3656
|
+
fontBaseUrl: options?.fontBaseUrl ?? "",
|
|
3657
|
+
paperHeightMm: options?.paperHeightMm,
|
|
3658
|
+
codeRenderer
|
|
3659
|
+
},
|
|
3660
|
+
createBrowserPrintRuntime()
|
|
3661
|
+
);
|
|
3662
|
+
return {
|
|
3663
|
+
html: prepared.html,
|
|
3664
|
+
pageCount: prepared.pageCount,
|
|
3665
|
+
pageLayouts: prepared.pageLayouts,
|
|
3666
|
+
paperMm: prepared.paperMm,
|
|
3667
|
+
continuous: prepared.continuous,
|
|
3668
|
+
copies: prepared.copies,
|
|
3669
|
+
copyPaperMm: prepared.copyPaperMm
|
|
3670
|
+
};
|
|
3671
|
+
}
|
|
1916
3672
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1917
3673
|
0 && (module.exports = {
|
|
3674
|
+
EXECUTOR_VERSION,
|
|
3675
|
+
applyTextFit,
|
|
1918
3676
|
browserCodeRenderer,
|
|
1919
|
-
|
|
3677
|
+
createBrowserPrintRuntime,
|
|
3678
|
+
createIframeDriverFactory,
|
|
3679
|
+
domExecutor,
|
|
3680
|
+
fitTextNode,
|
|
3681
|
+
readContentBottom,
|
|
3682
|
+
readMeasurements,
|
|
3683
|
+
renderCodes,
|
|
3684
|
+
renderHtmlPages,
|
|
3685
|
+
waitReady
|
|
1920
3686
|
});
|