@peripheries/renderer 0.17.0 → 0.19.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 +1 -0
- package/dist/catalog.js +2 -2
- package/dist/chunks/{projection-BTt_LUza.js → projection-CNtMyJ-p.js} +2 -2
- package/dist/chunks/projection-CNtMyJ-p.js.map +1 -0
- package/dist/chunks/{query-DRdMhtcX.js → query-BPuCJC1C.js} +109 -21
- package/dist/chunks/query-BPuCJC1C.js.map +1 -0
- package/dist/chunks/{structure-BPBwFPO0.js → structure-o9pYoDjD.js} +29 -3
- package/dist/chunks/{structure-BPBwFPO0.js.map → structure-o9pYoDjD.js.map} +1 -1
- package/dist/chunks/{validate-XuJViQas.js → validate-LRaUUXIm.js} +3 -3
- package/dist/chunks/{validate-XuJViQas.js.map → validate-LRaUUXIm.js.map} +1 -1
- package/dist/core.js +130 -17
- package/dist/core.js.map +1 -1
- package/dist/print.js +1391 -0
- package/dist/print.js.map +1 -0
- package/dist/react.js +21 -9
- package/dist/react.js.map +1 -1
- package/dist/runtime.js +5 -4
- package/dist/runtime.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/types/core/cell-props.d.ts +3 -1
- package/dist/types/core/data-context.d.ts +4 -0
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/load-page.d.ts +15 -1
- package/dist/types/core/references.d.ts +8 -2
- package/dist/types/core/sources.d.ts +97 -0
- package/dist/types/core/structure.d.ts +16 -0
- package/dist/types/core/types.d.ts +34 -0
- package/dist/types/print/document-bindings.d.ts +76 -0
- package/dist/types/print/document-profile.d.ts +203 -0
- package/dist/types/print/index.d.ts +11 -0
- package/dist/types/print/map-staleness.d.ts +75 -0
- package/dist/types/print/page-map.d.ts +427 -0
- package/dist/types/print/paginate.d.ts +231 -0
- package/dist/types/print/print-layout.d.ts +212 -0
- package/dist/types/print/print-lint.d.ts +124 -0
- package/dist/types/print/print-policy.d.ts +210 -0
- package/dist/types/print/print-stylesheet.d.ts +168 -0
- package/dist/types/print/running-header.d.ts +105 -0
- package/dist/types/print/structure-print.d.ts +302 -0
- package/dist/types/react/cell-view.d.ts +3 -0
- package/dist/types/react/page-renderer.d.ts +6 -0
- package/dist/types/runtime/install.d.ts +9 -3
- package/dist/types/testing/components.d.ts +1 -1
- package/package.json +6 -2
- package/dist/chunks/projection-BTt_LUza.js.map +0 -1
- package/dist/chunks/query-DRdMhtcX.js.map +0 -1
package/dist/print.js
ADDED
|
@@ -0,0 +1,1391 @@
|
|
|
1
|
+
//#region packages/renderer/src/print/document-profile.ts
|
|
2
|
+
var bundleName = (bundle) => bundle.label ?? bundle.key ?? "(unkeyed)";
|
|
3
|
+
var DEFAULTS = {
|
|
4
|
+
profile: null,
|
|
5
|
+
source: "defaults",
|
|
6
|
+
maxDepth: void 0,
|
|
7
|
+
numbering: "ladder"
|
|
8
|
+
};
|
|
9
|
+
var warned$1 = new Set();
|
|
10
|
+
function onceKey(key, emit) {
|
|
11
|
+
if (warned$1.has(key)) return;
|
|
12
|
+
warned$1.add(key);
|
|
13
|
+
emit();
|
|
14
|
+
}
|
|
15
|
+
function warnOnce$1(key, message) {
|
|
16
|
+
onceKey(key, () => console.warn(message));
|
|
17
|
+
}
|
|
18
|
+
function __clearDocumentProfileWarnings() {
|
|
19
|
+
warned$1.clear();
|
|
20
|
+
}
|
|
21
|
+
function isPlainObject(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function readProfileMap(bundle) {
|
|
25
|
+
const raw = bundle.documentProfiles;
|
|
26
|
+
if (raw === void 0) return null;
|
|
27
|
+
if (!isPlainObject(raw)) {
|
|
28
|
+
warnOnce$1(`map:${bundleName(bundle)}`, `[document-profile] bundle "${bundleName(bundle)}" exports documentProfiles as a ${Array.isArray(raw) ? "array" : typeof raw}, not an object of profiles — every document resolved against it will fall back to host defaults.`);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
function resolveDocumentProfile(registries, profileId) {
|
|
34
|
+
if (!profileId) return DEFAULTS;
|
|
35
|
+
const available = [];
|
|
36
|
+
let shipsProfiles = false;
|
|
37
|
+
for (const bundle of registries) {
|
|
38
|
+
const map = readProfileMap(bundle);
|
|
39
|
+
if (!map) continue;
|
|
40
|
+
shipsProfiles = true;
|
|
41
|
+
for (const id of Object.keys(map)) if (!available.includes(id)) available.push(id);
|
|
42
|
+
const candidate = map[profileId];
|
|
43
|
+
if (candidate === void 0) continue;
|
|
44
|
+
if (!isPlainObject(candidate)) {
|
|
45
|
+
warnOnce$1(`entry:${bundleName(bundle)}:${profileId}`, `[document-profile] bundle "${bundleName(bundle)}" has documentProfiles["${profileId}"] as a ${typeof candidate}, not a profile object — skipping it.`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
profile: candidate,
|
|
50
|
+
source: "bundle",
|
|
51
|
+
maxDepth: readMaxDepth(candidate, bundleName(bundle)),
|
|
52
|
+
numbering: candidate.depth?.numbering === "none" ? "none" : "ladder"
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (registries.length === 0) return DEFAULTS;
|
|
56
|
+
if (shipsProfiles && available.length === 0) {
|
|
57
|
+
warnOnce$1(`emptymap:${profileId}`, `[document-profile] the bundle cascade exports documentProfiles but it is EMPTY. This is not a bad profile name — no profile registered at all, so EVERY document will render on host defaults (depth unbounded, numbering 'ladder'), not just the one asking for "${profileId}". Profiles register by import side effect; a module graph that drops that import, or a bundler that shakes it out, produces exactly this. Check the bundle, not the page.`);
|
|
58
|
+
return DEFAULTS;
|
|
59
|
+
}
|
|
60
|
+
if (!shipsProfiles) {
|
|
61
|
+
if (globalThis.process?.env?.NODE_ENV !== "production") onceKey(`noprofiles:${profileId}`, () => console.debug(`[document-profile] page asks for "${profileId}" but no bundle in the cascade exports documentProfiles — using host defaults (depth unbounded, numbering 'ladder').`));
|
|
62
|
+
return DEFAULTS;
|
|
63
|
+
}
|
|
64
|
+
warnOnce$1(`missing:${profileId}:${available.join(",")}`, `[document-profile] profile "${profileId}" not found; the cascade ships [${available.map((id) => `"${id}"`).join(", ")}]. The document will render with host defaults (depth unbounded, numbering 'ladder'), i.e. in the WRONG document system rather than not at all. Fix the bundle, not the page — the page named a profile.`);
|
|
65
|
+
return DEFAULTS;
|
|
66
|
+
}
|
|
67
|
+
function readMaxDepth(profile, bundleKey) {
|
|
68
|
+
const max = profile.depth?.max;
|
|
69
|
+
if (max === void 0) return void 0;
|
|
70
|
+
if (typeof max !== "number" || !Number.isInteger(max) || max < 1) {
|
|
71
|
+
warnOnce$1(`depth:${bundleKey}:${profile.id}`, `[document-profile] profile "${profile.id}" declares depth.max as ${JSON.stringify(max)}, which is not a positive integer — depth will be treated as unbounded.`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
return max;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region packages/renderer/src/print/document-bindings.ts
|
|
78
|
+
function asRecord(value) {
|
|
79
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
80
|
+
}
|
|
81
|
+
function resolveRegionBindings(page) {
|
|
82
|
+
const declared = asRecord(asRecord(asRecord(page?.metadata)?.document)?.bindings);
|
|
83
|
+
if (!declared) return {
|
|
84
|
+
values: {},
|
|
85
|
+
malformed: []
|
|
86
|
+
};
|
|
87
|
+
const values = {};
|
|
88
|
+
const malformed = [];
|
|
89
|
+
for (const [key, value] of Object.entries(declared)) if (typeof value === "string" && value.length > 0) values[key] = value;
|
|
90
|
+
else malformed.push(key);
|
|
91
|
+
return {
|
|
92
|
+
values,
|
|
93
|
+
malformed
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function resolveProfileId(page) {
|
|
97
|
+
const layout = asRecord(page?.layout);
|
|
98
|
+
const metadata = asRecord(page?.metadata);
|
|
99
|
+
const fromLayout = typeof layout?.profile === "string" ? layout.profile : void 0;
|
|
100
|
+
const fromMetadata = typeof metadata?.profile === "string" ? metadata.profile : void 0;
|
|
101
|
+
if (fromMetadata !== void 0 && fromMetadata !== fromLayout) console.warn(`[document-bindings] page declares layout.profile=${JSON.stringify(fromLayout)} and metadata.profile=${JSON.stringify(fromMetadata)}. **layout wins.** These are two copies of one fact and only one is read — delete metadata.profile rather than keeping them in step by hand.`);
|
|
102
|
+
return fromLayout;
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region packages/renderer/src/print/print-policy.ts
|
|
106
|
+
var BREAK_INSIDE = new Set(["avoid", "auto"]);
|
|
107
|
+
function toCount(value) {
|
|
108
|
+
if (!/^[1-9][0-9]*$/.test(value)) return void 0;
|
|
109
|
+
return Number(value);
|
|
110
|
+
}
|
|
111
|
+
function parsePrintPolicy(raw) {
|
|
112
|
+
const constraints = {};
|
|
113
|
+
const placements = {};
|
|
114
|
+
const unrecognised = [];
|
|
115
|
+
if (!raw || typeof raw !== "object") return {
|
|
116
|
+
constraints,
|
|
117
|
+
placements,
|
|
118
|
+
unrecognised
|
|
119
|
+
};
|
|
120
|
+
for (const [key, rawValue] of Object.entries(raw)) {
|
|
121
|
+
const value = typeof rawValue === "string" ? rawValue : String(rawValue);
|
|
122
|
+
switch (key) {
|
|
123
|
+
case "breakInside":
|
|
124
|
+
if (BREAK_INSIDE.has(value)) constraints.breakInside = value;
|
|
125
|
+
else unrecognised.push(`${key}=${value}`);
|
|
126
|
+
break;
|
|
127
|
+
case "breakAfter":
|
|
128
|
+
case "breakBefore":
|
|
129
|
+
if (value === "avoid" || value === "page") constraints[key] = value;
|
|
130
|
+
else unrecognised.push(`${key}=${value}`);
|
|
131
|
+
break;
|
|
132
|
+
case "orphans":
|
|
133
|
+
case "widows": {
|
|
134
|
+
const n = toCount(value);
|
|
135
|
+
if (n === void 0) unrecognised.push(`${key}=${value}`);
|
|
136
|
+
else constraints[key] = n;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case "atomic":
|
|
140
|
+
if (value === "true") constraints.atomic = true;
|
|
141
|
+
else if (value === "false") constraints.atomic = false;
|
|
142
|
+
else unrecognised.push(`${key}=${value}`);
|
|
143
|
+
break;
|
|
144
|
+
case "region":
|
|
145
|
+
if (value === "page-footer") placements.region = "page-footer";
|
|
146
|
+
else unrecognised.push(`${key}=${value}`);
|
|
147
|
+
break;
|
|
148
|
+
case "display":
|
|
149
|
+
if (value === "none") placements.display = "none";
|
|
150
|
+
else unrecognised.push(`${key}=${value}`);
|
|
151
|
+
break;
|
|
152
|
+
default: unrecognised.push(`${key}=${value}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
constraints,
|
|
157
|
+
placements,
|
|
158
|
+
unrecognised
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function composeConstraints(chain) {
|
|
162
|
+
const out = {};
|
|
163
|
+
for (const link of chain) {
|
|
164
|
+
if (!link) continue;
|
|
165
|
+
if (link.breakInside === "avoid") out.breakInside = "avoid";
|
|
166
|
+
else if (link.breakInside === "auto" && out.breakInside === void 0) out.breakInside = "auto";
|
|
167
|
+
if (link.breakAfter) out.breakAfter = out.breakAfter === "page" ? "page" : link.breakAfter;
|
|
168
|
+
if (link.breakBefore) out.breakBefore = out.breakBefore === "page" ? "page" : link.breakBefore;
|
|
169
|
+
if (typeof link.orphans === "number") out.orphans = Math.max(out.orphans ?? 0, link.orphans);
|
|
170
|
+
if (typeof link.widows === "number") out.widows = Math.max(out.widows ?? 0, link.widows);
|
|
171
|
+
if (link.atomic) out.atomic = true;
|
|
172
|
+
}
|
|
173
|
+
if (out.atomic) out.breakInside = "avoid";
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
function resolvePlacement(chain) {
|
|
177
|
+
const out = {};
|
|
178
|
+
for (const link of chain) {
|
|
179
|
+
if (!link) continue;
|
|
180
|
+
if (link.display === "none") out.display = "none";
|
|
181
|
+
if (link.region) out.region = link.region;
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
function printPolicyToStyle(constraints, placements) {
|
|
186
|
+
const style = {};
|
|
187
|
+
if (placements?.display === "none") {
|
|
188
|
+
style.display = "none";
|
|
189
|
+
return style;
|
|
190
|
+
}
|
|
191
|
+
if (constraints.atomic) style.breakInside = "avoid";
|
|
192
|
+
if (constraints.breakInside === "avoid") style.breakInside = "avoid";
|
|
193
|
+
if (constraints.breakAfter) style.breakAfter = constraints.breakAfter;
|
|
194
|
+
if (constraints.breakBefore) style.breakBefore = constraints.breakBefore;
|
|
195
|
+
if (typeof constraints.orphans === "number") style.orphans = constraints.orphans;
|
|
196
|
+
if (typeof constraints.widows === "number") style.widows = constraints.widows;
|
|
197
|
+
return style;
|
|
198
|
+
}
|
|
199
|
+
var PRINT_POLICY_ATTR = "data-oa-print-policy";
|
|
200
|
+
function serializeConstraints(c) {
|
|
201
|
+
if (!c) return "";
|
|
202
|
+
const parts = [];
|
|
203
|
+
if (c.atomic) parts.push("atomic=true");
|
|
204
|
+
if (c.breakInside) parts.push(`breakInside=${c.breakInside}`);
|
|
205
|
+
if (c.breakBefore) parts.push(`breakBefore=${c.breakBefore}`);
|
|
206
|
+
if (c.breakAfter) parts.push(`breakAfter=${c.breakAfter}`);
|
|
207
|
+
if (typeof c.orphans === "number") parts.push(`orphans=${c.orphans}`);
|
|
208
|
+
if (typeof c.widows === "number") parts.push(`widows=${c.widows}`);
|
|
209
|
+
return parts.join(";");
|
|
210
|
+
}
|
|
211
|
+
function deserializeConstraints(value) {
|
|
212
|
+
if (typeof value !== "string") return void 0;
|
|
213
|
+
const trimmed = value.trim();
|
|
214
|
+
if (trimmed === "") return void 0;
|
|
215
|
+
const raw = {};
|
|
216
|
+
for (const pair of trimmed.split(";")) {
|
|
217
|
+
const eq = pair.indexOf("=");
|
|
218
|
+
if (eq <= 0) continue;
|
|
219
|
+
raw[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
220
|
+
}
|
|
221
|
+
const { constraints } = parsePrintPolicy(raw);
|
|
222
|
+
return Object.keys(constraints).length > 0 ? constraints : void 0;
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region packages/renderer/src/print/page-map.ts
|
|
226
|
+
function collectBlocks(root, printWidthPx, { measureRect, readConstraints, boxFor = defaultBoxFor, selector = "[data-cell-id]", sectionStart, blankBefore }) {
|
|
227
|
+
if (!root) return {
|
|
228
|
+
blocks: [],
|
|
229
|
+
bands: [],
|
|
230
|
+
sections: [],
|
|
231
|
+
measuredWidthPx: 0,
|
|
232
|
+
approximate: true,
|
|
233
|
+
zeroHeightIds: [],
|
|
234
|
+
reconciles: false,
|
|
235
|
+
reconciliation: {
|
|
236
|
+
modelPx: 0,
|
|
237
|
+
containerPx: 0,
|
|
238
|
+
leadingPx: 0
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
const rootRect = measureRect(root);
|
|
242
|
+
const measuredWidthPx = Math.round(rootRect.width);
|
|
243
|
+
const boxes = [];
|
|
244
|
+
const zeroHeightIds = [];
|
|
245
|
+
const openers = new Map();
|
|
246
|
+
for (const cell of Array.from(root.querySelectorAll(selector))) {
|
|
247
|
+
const id = cell.getAttribute("data-cell-id");
|
|
248
|
+
if (!id) continue;
|
|
249
|
+
const box = boxFor(cell);
|
|
250
|
+
const rect = measureRect(box);
|
|
251
|
+
if (Math.round(rect.height) === 0) zeroHeightIds.push(id);
|
|
252
|
+
const constraints = readConstraints?.(box);
|
|
253
|
+
const title = sectionStart?.(cell, constraints);
|
|
254
|
+
if (title !== void 0) openers.set(id, title);
|
|
255
|
+
boxes.push({
|
|
256
|
+
id,
|
|
257
|
+
rect,
|
|
258
|
+
constraints
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const sorted = [...boxes].sort((a, b) => a.rect.top - b.rect.top || a.rect.bottom - b.rect.bottom);
|
|
262
|
+
const bands = [];
|
|
263
|
+
let openBottom = -Infinity;
|
|
264
|
+
for (const box of sorted) {
|
|
265
|
+
const top = box.rect.top;
|
|
266
|
+
const bottom = box.rect.bottom;
|
|
267
|
+
const open = bands[bands.length - 1];
|
|
268
|
+
if (open && top < openBottom) {
|
|
269
|
+
open.members.push(box.id);
|
|
270
|
+
open.id = open.members.join("+");
|
|
271
|
+
openBottom = Math.max(openBottom, bottom);
|
|
272
|
+
open.heightPx = Math.round(openBottom - rootRect.top - open.topPx);
|
|
273
|
+
open.constraints = mergeBandConstraints(open.constraints, box.constraints);
|
|
274
|
+
} else {
|
|
275
|
+
bands.push({
|
|
276
|
+
id: box.id,
|
|
277
|
+
members: [box.id],
|
|
278
|
+
topPx: Math.round(top - rootRect.top),
|
|
279
|
+
heightPx: Math.round(bottom - top),
|
|
280
|
+
constraints: box.constraints
|
|
281
|
+
});
|
|
282
|
+
openBottom = bottom;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const gapBelow = (i) => {
|
|
286
|
+
const next = bands[i + 1];
|
|
287
|
+
if (!next) return 0;
|
|
288
|
+
const band = bands[i];
|
|
289
|
+
return next.topPx - (band.topPx + band.heightPx);
|
|
290
|
+
};
|
|
291
|
+
const sections = [];
|
|
292
|
+
const sectionOfBand = new Map();
|
|
293
|
+
let currentSection;
|
|
294
|
+
for (const band of bands) {
|
|
295
|
+
for (const member of band.members) {
|
|
296
|
+
const title = openers.get(member);
|
|
297
|
+
if (title !== void 0) {
|
|
298
|
+
currentSection = member;
|
|
299
|
+
sections.push({
|
|
300
|
+
cellId: member,
|
|
301
|
+
title
|
|
302
|
+
});
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (currentSection !== void 0) sectionOfBand.set(band.id, currentSection);
|
|
307
|
+
}
|
|
308
|
+
const blocks = bands.map((band, i) => ({
|
|
309
|
+
id: band.id,
|
|
310
|
+
heightPx: band.heightPx + gapBelow(i),
|
|
311
|
+
constraints: band.constraints,
|
|
312
|
+
...sectionOfBand.has(band.id) ? { sectionId: sectionOfBand.get(band.id) } : {},
|
|
313
|
+
...band.members.some((m) => blankBefore?.(m)) ? { blankPageBefore: true } : {}
|
|
314
|
+
}));
|
|
315
|
+
const leadingPx = bands.length > 0 ? bands[0].topPx : 0;
|
|
316
|
+
const modelPx = Math.round(leadingPx + blocks.reduce((s, b) => s + b.heightPx, 0));
|
|
317
|
+
const containerPx = Math.round(rootRect.height);
|
|
318
|
+
return {
|
|
319
|
+
blocks,
|
|
320
|
+
bands,
|
|
321
|
+
sections,
|
|
322
|
+
measuredWidthPx,
|
|
323
|
+
...Math.abs(measuredWidthPx - printWidthPx) <= 1 ? {} : { approximate: true },
|
|
324
|
+
zeroHeightIds,
|
|
325
|
+
reconciles: Math.abs(modelPx - containerPx) <= Math.max(1, bands.length),
|
|
326
|
+
reconciliation: {
|
|
327
|
+
modelPx,
|
|
328
|
+
containerPx,
|
|
329
|
+
leadingPx
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function defaultBoxFor(cell) {
|
|
334
|
+
return cell.parentElement ?? cell;
|
|
335
|
+
}
|
|
336
|
+
function mergeBandConstraints(a, b) {
|
|
337
|
+
if (!a) return b;
|
|
338
|
+
if (!b) return a;
|
|
339
|
+
const out = { ...a };
|
|
340
|
+
if (a.breakInside === "avoid" || b.breakInside === "avoid") out.breakInside = "avoid";
|
|
341
|
+
else if (a.breakInside === "auto" || b.breakInside === "auto") out.breakInside = "auto";
|
|
342
|
+
out.breakAfter = strictestEdge(a.breakAfter, b.breakAfter);
|
|
343
|
+
out.breakBefore = strictestEdge(a.breakBefore, b.breakBefore);
|
|
344
|
+
if (out.breakAfter === void 0) delete out.breakAfter;
|
|
345
|
+
if (out.breakBefore === void 0) delete out.breakBefore;
|
|
346
|
+
if (a.atomic || b.atomic) out.atomic = true;
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
349
|
+
function strictestEdge(a, b) {
|
|
350
|
+
if (a === "page" || b === "page") return "page";
|
|
351
|
+
if (a === "avoid" || b === "avoid") return "avoid";
|
|
352
|
+
}
|
|
353
|
+
function constraintsFromComputedStyle(cs) {
|
|
354
|
+
const out = {};
|
|
355
|
+
if (cs.breakInside === "avoid") out.breakInside = "avoid";
|
|
356
|
+
const after = asEdge(cs.breakAfter);
|
|
357
|
+
const before = asEdge(cs.breakBefore);
|
|
358
|
+
if (after) out.breakAfter = after;
|
|
359
|
+
if (before) out.breakBefore = before;
|
|
360
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
361
|
+
}
|
|
362
|
+
function constraintsFromBox(el, computed) {
|
|
363
|
+
const declared = deserializeConstraints(el.getAttribute(PRINT_POLICY_ATTR));
|
|
364
|
+
if (declared) return declared;
|
|
365
|
+
return constraintsFromComputedStyle(computed());
|
|
366
|
+
}
|
|
367
|
+
function forcedBreaksWithin(box, styleOf) {
|
|
368
|
+
const out = {};
|
|
369
|
+
for (let el = box.children[0]; el; el = el.children[0]) if (asEdge(styleOf(el).breakBefore) === "page") out.breakBefore = "page";
|
|
370
|
+
for (let el = box.children[box.children.length - 1]; el; el = el.children[el.children.length - 1]) if (asEdge(styleOf(el).breakAfter) === "page") out.breakAfter = "page";
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
function withForcedBreaks(constraints, forced) {
|
|
374
|
+
if (!forced.breakBefore && !forced.breakAfter) return constraints;
|
|
375
|
+
return {
|
|
376
|
+
...constraints,
|
|
377
|
+
...forced
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function asEdge(v) {
|
|
381
|
+
if (v === "page" || v === "always" || v === "left" || v === "right") return "page";
|
|
382
|
+
if (v === "avoid") return "avoid";
|
|
383
|
+
}
|
|
384
|
+
function constraintsResolver(policyFor) {
|
|
385
|
+
return (componentName) => {
|
|
386
|
+
if (!componentName) return void 0;
|
|
387
|
+
const own = policyFor(componentName);
|
|
388
|
+
if (!own) return void 0;
|
|
389
|
+
return composeConstraints([own]);
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
var UNIT_PX = {
|
|
393
|
+
px: 1,
|
|
394
|
+
in: 96,
|
|
395
|
+
cm: 96 / 2.54,
|
|
396
|
+
mm: 96 / 25.4,
|
|
397
|
+
q: 96 / 101.6,
|
|
398
|
+
pt: 96 / 72,
|
|
399
|
+
pc: 16
|
|
400
|
+
};
|
|
401
|
+
function cssLengthToPx(value) {
|
|
402
|
+
if (typeof value !== "string") return null;
|
|
403
|
+
const v = value.trim().toLowerCase();
|
|
404
|
+
if (v === "0") return 0;
|
|
405
|
+
const m = /^(-?(?:\d+\.?\d*|\.\d+))([a-z]+)$/.exec(v);
|
|
406
|
+
if (!m) return null;
|
|
407
|
+
const factor = UNIT_PX[m[2]];
|
|
408
|
+
if (factor === void 0) return null;
|
|
409
|
+
const n = Number(m[1]) * factor;
|
|
410
|
+
return Number.isFinite(n) ? n : null;
|
|
411
|
+
}
|
|
412
|
+
function paperGeometryPx(sheet, margins, columns) {
|
|
413
|
+
const w = cssLengthToPx(sheet?.width);
|
|
414
|
+
const h = cssLengthToPx(sheet?.height);
|
|
415
|
+
const t = cssLengthToPx(margins?.top);
|
|
416
|
+
const r = cssLengthToPx(margins?.right);
|
|
417
|
+
const b = cssLengthToPx(margins?.bottom);
|
|
418
|
+
const l = cssLengthToPx(margins?.left);
|
|
419
|
+
if ([
|
|
420
|
+
w,
|
|
421
|
+
h,
|
|
422
|
+
t,
|
|
423
|
+
r,
|
|
424
|
+
b,
|
|
425
|
+
l
|
|
426
|
+
].some((x) => x === null)) return null;
|
|
427
|
+
const textBlockPx = Math.floor(h - t - b);
|
|
428
|
+
const pageBoxWidthPx = Math.floor(w - l - r);
|
|
429
|
+
if (textBlockPx <= 0 || pageBoxWidthPx <= 0) return null;
|
|
430
|
+
const isColumn = columns?.rail?.placement === "column";
|
|
431
|
+
const railRaw = isColumn ? cssLengthToPx(columns?.rail?.width) : null;
|
|
432
|
+
const gutterRaw = isColumn ? cssLengthToPx(columns?.rail?.gutter) : null;
|
|
433
|
+
const railPx = railRaw !== null && railRaw > 0 ? Math.floor(railRaw) : 0;
|
|
434
|
+
const gutterPx = railPx > 0 && gutterRaw !== null && gutterRaw > 0 ? Math.floor(gutterRaw) : 0;
|
|
435
|
+
const measureRaw = cssLengthToPx(columns?.measure);
|
|
436
|
+
const measurePx = measureRaw !== null && measureRaw > 0 ? Math.floor(measureRaw) : null;
|
|
437
|
+
const printWidthPx = measurePx !== null && measurePx <= pageBoxWidthPx ? measurePx : pageBoxWidthPx;
|
|
438
|
+
return {
|
|
439
|
+
textBlockPx,
|
|
440
|
+
printWidthPx,
|
|
441
|
+
pageBoxWidthPx,
|
|
442
|
+
railPx,
|
|
443
|
+
gutterPx,
|
|
444
|
+
columnsReconcile: railPx === 0 || printWidthPx + gutterPx + railPx === pageBoxWidthPx
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region packages/renderer/src/print/paginate.ts
|
|
449
|
+
function mayNotSplit(c) {
|
|
450
|
+
if (!c) return true;
|
|
451
|
+
if (c.atomic) return true;
|
|
452
|
+
if (c.breakInside === "avoid") return true;
|
|
453
|
+
if (c.breakInside === "auto") return false;
|
|
454
|
+
if (typeof c.orphans === "number" || typeof c.widows === "number") return false;
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
function paginate(blocks, { textBlockPx, keepWithNextScaleFloor = .6, sectionFillRatio = .5 }) {
|
|
458
|
+
const pages = [];
|
|
459
|
+
const overflows = [];
|
|
460
|
+
if (!Number.isFinite(textBlockPx) || textBlockPx <= 0) {
|
|
461
|
+
const total = blocks.reduce((s, b) => s + b.heightPx, 0);
|
|
462
|
+
return {
|
|
463
|
+
pages: [{
|
|
464
|
+
index: 1,
|
|
465
|
+
blocks: blocks.map((b) => ({
|
|
466
|
+
id: b.id,
|
|
467
|
+
heightPx: b.heightPx
|
|
468
|
+
})),
|
|
469
|
+
usedPx: total,
|
|
470
|
+
wastePx: 0
|
|
471
|
+
}],
|
|
472
|
+
overflows: [],
|
|
473
|
+
idealPages: 0,
|
|
474
|
+
degenerateGeometry: true
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
let current = {
|
|
478
|
+
index: 1,
|
|
479
|
+
blocks: [],
|
|
480
|
+
usedPx: 0,
|
|
481
|
+
wastePx: textBlockPx
|
|
482
|
+
};
|
|
483
|
+
const commit = () => {
|
|
484
|
+
pages.push({
|
|
485
|
+
...current,
|
|
486
|
+
wastePx: Math.max(0, textBlockPx - current.usedPx)
|
|
487
|
+
});
|
|
488
|
+
current = {
|
|
489
|
+
index: current.index + 1,
|
|
490
|
+
blocks: [],
|
|
491
|
+
usedPx: 0,
|
|
492
|
+
wastePx: textBlockPx
|
|
493
|
+
};
|
|
494
|
+
};
|
|
495
|
+
const breakPage = () => {
|
|
496
|
+
if (current.blocks.length > 0) commit();
|
|
497
|
+
};
|
|
498
|
+
const peekTrailingKeeps = (mayEmptyPage = false) => {
|
|
499
|
+
const out = [];
|
|
500
|
+
let i = current.blocks.length - 1;
|
|
501
|
+
const floor = mayEmptyPage ? 0 : 1;
|
|
502
|
+
while (i >= floor) {
|
|
503
|
+
const blk = current.blocks[i];
|
|
504
|
+
if (blk.continues || blk.spansPages) break;
|
|
505
|
+
if (!keepWithNext.has(blk.id)) break;
|
|
506
|
+
out.unshift(blk);
|
|
507
|
+
i -= 1;
|
|
508
|
+
}
|
|
509
|
+
return out;
|
|
510
|
+
};
|
|
511
|
+
const takeTrailing = (count) => {
|
|
512
|
+
const carried = [];
|
|
513
|
+
for (let i = 0; i < count; i += 1) {
|
|
514
|
+
const last = current.blocks.pop();
|
|
515
|
+
if (!last) break;
|
|
516
|
+
current.usedPx -= last.heightPx;
|
|
517
|
+
carried.unshift(last);
|
|
518
|
+
}
|
|
519
|
+
return carried;
|
|
520
|
+
};
|
|
521
|
+
const keepWithNext = new Set(blocks.filter((b) => b.constraints?.breakAfter === "avoid").map((b) => b.id));
|
|
522
|
+
const sectionOpeners = new Set();
|
|
523
|
+
if (sectionFillRatio > 0) {
|
|
524
|
+
const threshold = textBlockPx * sectionFillRatio;
|
|
525
|
+
let openerId;
|
|
526
|
+
let runId;
|
|
527
|
+
let runHeight = 0;
|
|
528
|
+
const closeRun = () => {
|
|
529
|
+
if (openerId !== void 0 && runHeight >= threshold) sectionOpeners.add(openerId);
|
|
530
|
+
};
|
|
531
|
+
for (const b of blocks) {
|
|
532
|
+
if (b.sectionId !== runId) {
|
|
533
|
+
closeRun();
|
|
534
|
+
runId = b.sectionId;
|
|
535
|
+
openerId = b.sectionId === void 0 ? void 0 : b.id;
|
|
536
|
+
runHeight = 0;
|
|
537
|
+
}
|
|
538
|
+
runHeight += b.heightPx;
|
|
539
|
+
}
|
|
540
|
+
closeRun();
|
|
541
|
+
}
|
|
542
|
+
for (const b of blocks) {
|
|
543
|
+
const c = b.constraints;
|
|
544
|
+
if (b.blankPageBefore) {
|
|
545
|
+
breakPage();
|
|
546
|
+
pages.push({
|
|
547
|
+
index: current.index,
|
|
548
|
+
blocks: [],
|
|
549
|
+
usedPx: 0,
|
|
550
|
+
wastePx: textBlockPx,
|
|
551
|
+
blank: true
|
|
552
|
+
});
|
|
553
|
+
current = {
|
|
554
|
+
index: current.index + 1,
|
|
555
|
+
blocks: [],
|
|
556
|
+
usedPx: 0,
|
|
557
|
+
wastePx: textBlockPx
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
if (c?.breakBefore === "page") breakPage();
|
|
561
|
+
if (sectionOpeners.has(b.id) && c?.breakBefore !== "avoid") breakPage();
|
|
562
|
+
const isHeadingThatFits = c?.breakAfter === "avoid" && b.heightPx <= textBlockPx;
|
|
563
|
+
const unsplittable = mayNotSplit(c) || isHeadingThatFits;
|
|
564
|
+
if (b.heightPx > textBlockPx && unsplittable) {
|
|
565
|
+
const candidates = peekTrailingKeeps(true);
|
|
566
|
+
const availableIfKept = textBlockPx - candidates.reduce((sum, k) => sum + k.heightPx, 0);
|
|
567
|
+
const bring = candidates.length > 0 && availableIfKept / b.heightPx >= keepWithNextScaleFloor;
|
|
568
|
+
const carried = bring ? takeTrailing(candidates.length) : [];
|
|
569
|
+
breakPage();
|
|
570
|
+
for (const k of carried) {
|
|
571
|
+
current.blocks.push(k);
|
|
572
|
+
current.usedPx += k.heightPx;
|
|
573
|
+
}
|
|
574
|
+
current.blocks.push({
|
|
575
|
+
id: b.id,
|
|
576
|
+
heightPx: b.heightPx
|
|
577
|
+
});
|
|
578
|
+
current.usedPx += b.heightPx;
|
|
579
|
+
overflows.push({
|
|
580
|
+
id: b.id,
|
|
581
|
+
heightPx: b.heightPx,
|
|
582
|
+
textBlockPx,
|
|
583
|
+
excessPx: b.heightPx - textBlockPx,
|
|
584
|
+
availablePx: bring ? availableIfKept : textBlockPx
|
|
585
|
+
});
|
|
586
|
+
commit();
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const keepsWithPrevious = c?.breakBefore === "avoid" && current.blocks.length > 0;
|
|
590
|
+
let remaining = textBlockPx - current.usedPx;
|
|
591
|
+
if (b.heightPx > remaining && !keepsWithPrevious) {
|
|
592
|
+
if (unsplittable) {
|
|
593
|
+
const carried = takeTrailing(peekTrailingKeeps(current.blocks.length > 0 && current.blocks.every((x) => keepWithNext.has(x.id) && !x.continues && !x.spansPages)).length);
|
|
594
|
+
breakPage();
|
|
595
|
+
for (const k of carried) {
|
|
596
|
+
current.blocks.push(k);
|
|
597
|
+
current.usedPx += k.heightPx;
|
|
598
|
+
}
|
|
599
|
+
current.blocks.push({
|
|
600
|
+
id: b.id,
|
|
601
|
+
heightPx: b.heightPx
|
|
602
|
+
});
|
|
603
|
+
current.usedPx += b.heightPx;
|
|
604
|
+
} else {
|
|
605
|
+
if (remaining <= 0) {
|
|
606
|
+
commit();
|
|
607
|
+
remaining = textBlockPx;
|
|
608
|
+
}
|
|
609
|
+
if (b.heightPx <= remaining) {
|
|
610
|
+
current.blocks.push({
|
|
611
|
+
id: b.id,
|
|
612
|
+
heightPx: b.heightPx
|
|
613
|
+
});
|
|
614
|
+
current.usedPx += b.heightPx;
|
|
615
|
+
if (c?.breakAfter === "page") breakPage();
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
const overhang = b.heightPx - remaining;
|
|
619
|
+
const extra = Math.ceil(overhang / textBlockPx);
|
|
620
|
+
current.blocks.push({
|
|
621
|
+
id: b.id,
|
|
622
|
+
heightPx: remaining,
|
|
623
|
+
totalHeightPx: b.heightPx,
|
|
624
|
+
spansPages: extra + 1
|
|
625
|
+
});
|
|
626
|
+
current.usedPx = textBlockPx;
|
|
627
|
+
commit();
|
|
628
|
+
for (let i = 1; i < extra; i++) {
|
|
629
|
+
current.blocks.push({
|
|
630
|
+
id: b.id,
|
|
631
|
+
heightPx: textBlockPx,
|
|
632
|
+
totalHeightPx: b.heightPx,
|
|
633
|
+
continues: true
|
|
634
|
+
});
|
|
635
|
+
current.usedPx = textBlockPx;
|
|
636
|
+
commit();
|
|
637
|
+
}
|
|
638
|
+
const remainder = overhang - (extra - 1) * textBlockPx;
|
|
639
|
+
if (remainder > 0) {
|
|
640
|
+
current.blocks.push({
|
|
641
|
+
id: b.id,
|
|
642
|
+
heightPx: remainder,
|
|
643
|
+
totalHeightPx: b.heightPx,
|
|
644
|
+
continues: true
|
|
645
|
+
});
|
|
646
|
+
current.usedPx = remainder;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
current.blocks.push({
|
|
651
|
+
id: b.id,
|
|
652
|
+
heightPx: b.heightPx
|
|
653
|
+
});
|
|
654
|
+
current.usedPx += b.heightPx;
|
|
655
|
+
}
|
|
656
|
+
if (c?.breakAfter === "page") breakPage();
|
|
657
|
+
}
|
|
658
|
+
if (current.blocks.length > 0 || pages.length === 0) commit();
|
|
659
|
+
const total = blocks.reduce((s, b) => s + b.heightPx, 0);
|
|
660
|
+
return {
|
|
661
|
+
pages,
|
|
662
|
+
overflows,
|
|
663
|
+
idealPages: textBlockPx > 0 ? Number((total / textBlockPx).toFixed(2)) : 0
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
//#endregion
|
|
667
|
+
//#region packages/renderer/src/print/running-header.ts
|
|
668
|
+
function resolveRunningValues(map, bands, markers) {
|
|
669
|
+
const markerByCell = new Map(markers.map((m) => [m.cellId, m]));
|
|
670
|
+
const cellsOfBand = new Map(bands.map((b) => [b.id, b.members]));
|
|
671
|
+
const out = [];
|
|
672
|
+
let current;
|
|
673
|
+
let startedOnPage = 0;
|
|
674
|
+
for (const page of map.pages) {
|
|
675
|
+
const atPageTop = current;
|
|
676
|
+
const topStartedEarlier = !!atPageTop && startedOnPage < page.index;
|
|
677
|
+
for (const block of page.blocks) {
|
|
678
|
+
if (block.continues) continue;
|
|
679
|
+
for (const cellId of cellsOfBand.get(block.id) ?? []) {
|
|
680
|
+
const marker = markerByCell.get(cellId);
|
|
681
|
+
if (marker) {
|
|
682
|
+
current = marker;
|
|
683
|
+
startedOnPage = page.index;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const opensWithHeading = startedOnPage === page.index && firstRowIsMarker(page, cellsOfBand, markerByCell);
|
|
688
|
+
const heading = opensWithHeading ? current : atPageTop;
|
|
689
|
+
const continued = opensWithHeading ? false : topStartedEarlier;
|
|
690
|
+
out.push({
|
|
691
|
+
page: page.index,
|
|
692
|
+
sectionTitle: heading?.title,
|
|
693
|
+
continued: !!heading && continued
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
return out;
|
|
697
|
+
}
|
|
698
|
+
function firstRowIsMarker(page, cellsOfBand, markerByCell) {
|
|
699
|
+
const first = page.blocks.find((b) => !b.continues);
|
|
700
|
+
if (!first) return false;
|
|
701
|
+
return (cellsOfBand.get(first.id) ?? []).some((c) => markerByCell.has(c));
|
|
702
|
+
}
|
|
703
|
+
var RUNNING_SLOTS = ["section-title", "section-title-continued"];
|
|
704
|
+
function isRunningSlot(name) {
|
|
705
|
+
return RUNNING_SLOTS.includes(name);
|
|
706
|
+
}
|
|
707
|
+
function runningSlotValues(values, { continuedSuffix = " (continued)" } = {}) {
|
|
708
|
+
if (!values.sectionTitle) return {};
|
|
709
|
+
return {
|
|
710
|
+
"section-title": values.sectionTitle,
|
|
711
|
+
"section-title-continued": values.continued ? `${values.sectionTitle}${continuedSuffix}` : values.sectionTitle
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
//#endregion
|
|
715
|
+
//#region packages/renderer/src/print/print-stylesheet.ts
|
|
716
|
+
var warned = new Set();
|
|
717
|
+
function warnOnce(key, message) {
|
|
718
|
+
if (warned.has(key)) return;
|
|
719
|
+
warned.add(key);
|
|
720
|
+
console.warn(message);
|
|
721
|
+
}
|
|
722
|
+
function __clearPrintStylesheetWarnings() {
|
|
723
|
+
warned.clear();
|
|
724
|
+
}
|
|
725
|
+
var BUILT_IN_SLOTS = {
|
|
726
|
+
"page-x-of-y": "counter(page) \" of \" counter(pages)",
|
|
727
|
+
"page-number": "counter(page)",
|
|
728
|
+
"page-count": "counter(pages)"
|
|
729
|
+
};
|
|
730
|
+
function checkPaginatorBindings(bindings, debugLabel) {
|
|
731
|
+
if (bindings === void 0) return [];
|
|
732
|
+
if (!Array.isArray(bindings)) {
|
|
733
|
+
warnOnce(`paginator-shape:${debugLabel}`, `[print-stylesheet] "${debugLabel}" exports PAGINATOR_BINDINGS as a ${typeof bindings}, not an array — the host cannot cross-check its paginator vocabulary against this bundle.`);
|
|
734
|
+
return [];
|
|
735
|
+
}
|
|
736
|
+
const unknown = bindings.filter((b) => typeof b === "string" && !Object.prototype.hasOwnProperty.call(BUILT_IN_SLOTS, b));
|
|
737
|
+
if (unknown.length > 0) warnOnce(`paginator-unknown:${debugLabel}:${unknown.join(",")}`, `[print-stylesheet] "${debugLabel}" declares paginator binding(s) ${unknown.map((b) => `"${b}"`).join(", ")} that the host has no CSS expression for. A profile placing one in a margin box will render NOTHING there, silently — the host must not supply a paginator binding, so it cannot fall back to a value. Add an expression to BUILT_IN_SLOTS in packages/renderer/src/print/print-stylesheet.ts. Host knows: ${Object.keys(BUILT_IN_SLOTS).join(", ")}.`);
|
|
738
|
+
return unknown;
|
|
739
|
+
}
|
|
740
|
+
var DEFAULT_SHEET = {
|
|
741
|
+
width: "8.5in",
|
|
742
|
+
height: "11in"
|
|
743
|
+
};
|
|
744
|
+
var DEFAULT_MARGINS = {
|
|
745
|
+
top: "0.75in",
|
|
746
|
+
right: "0.75in",
|
|
747
|
+
bottom: "0.75in",
|
|
748
|
+
left: "0.75in"
|
|
749
|
+
};
|
|
750
|
+
var REGION_EDGE = {
|
|
751
|
+
"page-header": "top",
|
|
752
|
+
"page-footer": "bottom"
|
|
753
|
+
};
|
|
754
|
+
var SLOT_SIDE = {
|
|
755
|
+
left: "left",
|
|
756
|
+
centre: "center",
|
|
757
|
+
right: "right"
|
|
758
|
+
};
|
|
759
|
+
function cssString(value) {
|
|
760
|
+
return `"${value.replace(/[\\"<>\u0000-\u001f\u007f]/g, (c) => `\\${c.charCodeAt(0).toString(16).toUpperCase()} `)}"`;
|
|
761
|
+
}
|
|
762
|
+
function cssValue(value) {
|
|
763
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
764
|
+
if (typeof value !== "string") return void 0;
|
|
765
|
+
const v = value.trim();
|
|
766
|
+
return v.length > 0 && v.length <= 120 && /^[#a-zA-Z0-9.,%()\-_/ ]+$/.test(v) ? v : void 0;
|
|
767
|
+
}
|
|
768
|
+
var cssName = (name) => /^[a-z][a-z0-9-]{0,40}$/i.test(name) ? name : null;
|
|
769
|
+
var MissingRequiredBindingError = class extends Error {
|
|
770
|
+
profileId;
|
|
771
|
+
missing;
|
|
772
|
+
constructor(profileId, missing) {
|
|
773
|
+
super(`[print-stylesheet] refusing to build a page stylesheet for profile "${profileId ?? "(none)"}": required binding(s) ${missing.map((m) => `"${m}"`).join(", ")} could not be filled. The profile declares these as required because a document missing them is not merely incomplete — it states something false about itself. Supply them via \`values\`, or pass \`strict: false\` if this render is not paper.`);
|
|
774
|
+
this.name = "MissingRequiredBindingError";
|
|
775
|
+
this.profileId = profileId;
|
|
776
|
+
this.missing = missing;
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
function buildPageStylesheet(profile, options = {}) {
|
|
780
|
+
const { values = {}, debugLabel, strict = true } = options;
|
|
781
|
+
const label = debugLabel ?? profile?.id ?? "(no profile)";
|
|
782
|
+
const sheetIn = profile?.sheet ?? DEFAULT_SHEET;
|
|
783
|
+
const marginsIn = profile?.margins ?? DEFAULT_MARGINS;
|
|
784
|
+
const sheet = {
|
|
785
|
+
width: cssValue(sheetIn.width) ?? DEFAULT_SHEET.width,
|
|
786
|
+
height: cssValue(sheetIn.height) ?? DEFAULT_SHEET.height
|
|
787
|
+
};
|
|
788
|
+
const margins = {
|
|
789
|
+
top: cssValue(marginsIn.top) ?? DEFAULT_MARGINS.top,
|
|
790
|
+
right: cssValue(marginsIn.right) ?? DEFAULT_MARGINS.right,
|
|
791
|
+
bottom: cssValue(marginsIn.bottom) ?? DEFAULT_MARGINS.bottom,
|
|
792
|
+
left: cssValue(marginsIn.left) ?? DEFAULT_MARGINS.left
|
|
793
|
+
};
|
|
794
|
+
const unresolvedSlots = [];
|
|
795
|
+
const lines = [
|
|
796
|
+
"@media print {",
|
|
797
|
+
" html, body {",
|
|
798
|
+
" -webkit-print-color-adjust: exact;",
|
|
799
|
+
" print-color-adjust: exact;",
|
|
800
|
+
" }",
|
|
801
|
+
"}",
|
|
802
|
+
"",
|
|
803
|
+
"@page {",
|
|
804
|
+
` size: ${sheet.width} ${sheet.height};`,
|
|
805
|
+
` margin: ${margins.top} ${margins.right} ${margins.bottom} ${margins.left};`
|
|
806
|
+
];
|
|
807
|
+
for (const [regionName, slots] of Object.entries(profile?.regions ?? {})) {
|
|
808
|
+
const edge = REGION_EDGE[regionName];
|
|
809
|
+
if (!edge) {
|
|
810
|
+
warnOnce(`region:${label}:${regionName}`, `[print-stylesheet] "${label}" declares region "${regionName}", which maps to no @page margin box. Known: ${Object.keys(REGION_EDGE).join(", ")}.`);
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
if (!slots) continue;
|
|
814
|
+
for (const [slotKey, slotName] of Object.entries(slots)) {
|
|
815
|
+
if (!slotName) continue;
|
|
816
|
+
const side = SLOT_SIDE[slotKey];
|
|
817
|
+
if (!side) {
|
|
818
|
+
warnOnce(`slotkey:${label}:${regionName}:${String(slotKey)}`, `[print-stylesheet] "${label}" region "${regionName}" declares slot "${String(slotKey)}", which is not a margin-box position and was DROPPED. Known: ${Object.keys(SLOT_SIDE).join(", ")} — note the British "centre", which the host maps to CSS "center".`);
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
if (isRunningSlot(slotName)) continue;
|
|
822
|
+
const builtIn = BUILT_IN_SLOTS[slotName];
|
|
823
|
+
const supplied = values[slotName];
|
|
824
|
+
let content;
|
|
825
|
+
if (builtIn) content = builtIn;
|
|
826
|
+
else if (typeof supplied === "string" && supplied.length > 0) content = cssString(supplied);
|
|
827
|
+
else {
|
|
828
|
+
unresolvedSlots.push(slotName);
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
lines.push(` @${edge}-${side} {`);
|
|
832
|
+
lines.push(` content: ${content};`);
|
|
833
|
+
lines.push(" font: var(--oa-doc-margin-font, 8pt/1.2 Inter, system-ui, sans-serif);");
|
|
834
|
+
lines.push(" color: var(--oa-doc-margin-ink, rgba(0,0,0,0.45));");
|
|
835
|
+
lines.push(" }");
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
lines.push("}");
|
|
839
|
+
const missingRequired = (profile?.requiredBindings ?? []).filter((b) => unresolvedSlots.includes(b));
|
|
840
|
+
if (missingRequired.length > 0 && strict) throw new MissingRequiredBindingError(profile?.id, missingRequired);
|
|
841
|
+
if (unresolvedSlots.length > 0) warnOnce(`slots:${label}:${unresolvedSlots.join(",")}`, `[print-stylesheet] "${label}" names ${unresolvedSlots.length} region slot(s) the host could not fill: ${unresolvedSlots.map((s) => `"${s}"`).join(", ")}. Those margin boxes are OMITTED rather than rendered blank. Supply them via \`values\`, or add them to BUILT_IN_SLOTS if CSS can compute them.`);
|
|
842
|
+
return {
|
|
843
|
+
css: lines.join("\n"),
|
|
844
|
+
unresolvedSlots,
|
|
845
|
+
missingRequired
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
function buildPageVariables(profile) {
|
|
849
|
+
if (!profile) return "";
|
|
850
|
+
const decls = [];
|
|
851
|
+
const measure = cssValue(profile.measure);
|
|
852
|
+
if (measure) decls.push(` --oa-doc-measure: ${measure};`);
|
|
853
|
+
if (typeof profile.bodyMeasureCh === "number" && Number.isFinite(profile.bodyMeasureCh)) decls.push(` --oa-doc-body-measure: ${profile.bodyMeasureCh}ch;`);
|
|
854
|
+
if (typeof profile.floorPx === "number" && Number.isFinite(profile.floorPx)) decls.push(` --oa-doc-type-floor: ${profile.floorPx}px;`);
|
|
855
|
+
for (const [role, raw] of Object.entries(profile.ink ?? {})) {
|
|
856
|
+
const value = cssValue(raw);
|
|
857
|
+
if (value && cssName(role)) decls.push(` --oa-doc-ink-${role}: ${value};`);
|
|
858
|
+
}
|
|
859
|
+
for (const [weight, value] of Object.entries(profile.rules ?? {})) {
|
|
860
|
+
const safe = cssValue(value);
|
|
861
|
+
if (safe && cssName(weight)) decls.push(` --oa-doc-rule-${weight}: ${safe};`);
|
|
862
|
+
}
|
|
863
|
+
return decls.length > 0 ? `:root {\n${decls.join("\n")}\n}` : "";
|
|
864
|
+
}
|
|
865
|
+
//#endregion
|
|
866
|
+
//#region packages/renderer/src/print/print-layout.ts
|
|
867
|
+
var SEPARATOR_MARGIN_PX = 48;
|
|
868
|
+
var SEPARATOR_PADDING_PX = 16;
|
|
869
|
+
var SEPARATOR_BORDER_PX = 2;
|
|
870
|
+
var PAGE_FILL_INSET_PX = 2;
|
|
871
|
+
var BLANK_PAGE_SEPARATOR_PX = 50;
|
|
872
|
+
var SEPARATOR_TOTAL_PX = 66;
|
|
873
|
+
function esc(cellId) {
|
|
874
|
+
return cssString(cellId).slice(1, -1);
|
|
875
|
+
}
|
|
876
|
+
function selectorsFor(cellIsBox) {
|
|
877
|
+
return {
|
|
878
|
+
boxSelector: (cellId) => cellIsBox ? `[data-cell-id="${esc(cellId)}"]` : `:has(> [data-cell-id="${esc(cellId)}"])`,
|
|
879
|
+
contentSelector: (cellId) => cellIsBox ? `[data-cell-id="${esc(cellId)}"] > *` : `[data-cell-id="${esc(cellId)}"]`
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
function buildPrintLayout(map, bands, { textBlockPx, scaleFloor = .6, screenSeparators = true, running, blankPageNotice = "This page was left intentionally blank", cellIsBox = false }) {
|
|
883
|
+
const { boxSelector, contentSelector } = selectorsFor(cellIsBox);
|
|
884
|
+
const byId = new Map(bands.map((b) => [b.id, b]));
|
|
885
|
+
const rules = [];
|
|
886
|
+
const pageStartCellIds = [];
|
|
887
|
+
const scaled = [];
|
|
888
|
+
const refused = [];
|
|
889
|
+
const blankPageBeforeCellIds = [];
|
|
890
|
+
for (const [i, page] of map.pages.entries()) {
|
|
891
|
+
if (!page.blank) continue;
|
|
892
|
+
const first = map.pages.slice(i + 1).find((p) => !p.blank && p.blocks.length > 0)?.blocks[0];
|
|
893
|
+
const cellId = (first ? byId.get(first.id) : void 0)?.members[0];
|
|
894
|
+
if (!cellId) continue;
|
|
895
|
+
blankPageBeforeCellIds.push(cellId);
|
|
896
|
+
rules.push(`${boxSelector(cellId)}::before { content: ${cssString(blankPageNotice)}; display: block; break-before: page !important; break-after: page !important; box-sizing: border-box; height: ${textBlockPx - PAGE_FILL_INSET_PX}px; padding-top: ${Math.round((textBlockPx - PAGE_FILL_INSET_PX) / 2)}px; text-align: center; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: #78716c; }`);
|
|
897
|
+
}
|
|
898
|
+
for (const page of map.pages) {
|
|
899
|
+
if (page.index === 1) continue;
|
|
900
|
+
if (page.blank) continue;
|
|
901
|
+
const first = page.blocks[0];
|
|
902
|
+
if (!first || first.continues) continue;
|
|
903
|
+
const band = byId.get(first.id);
|
|
904
|
+
if (!band || band.members.length === 0) continue;
|
|
905
|
+
const cellId = band.members[0];
|
|
906
|
+
pageStartCellIds.push(cellId);
|
|
907
|
+
rules.push(`${boxSelector(cellId)} { break-before: page !important; }`);
|
|
908
|
+
}
|
|
909
|
+
const splitByDesign = new Set();
|
|
910
|
+
const mayFragment = map.pages.length > 1;
|
|
911
|
+
for (const page of map.pages) for (const block of page.blocks) if (block.continues || block.spansPages) splitByDesign.add(block.id);
|
|
912
|
+
for (const band of mayFragment ? bands : []) {
|
|
913
|
+
if (band.members.length === 0) continue;
|
|
914
|
+
if (splitByDesign.has(band.id)) continue;
|
|
915
|
+
if (band.constraints?.breakInside === "avoid") continue;
|
|
916
|
+
if (band.heightPx > textBlockPx) continue;
|
|
917
|
+
rules.push(`${boxSelector(band.members[0])} { break-inside: avoid !important; }`);
|
|
918
|
+
}
|
|
919
|
+
const fullPageIds = [];
|
|
920
|
+
let screenExtraPx = 0;
|
|
921
|
+
for (const page of map.pages) {
|
|
922
|
+
const sole = page.blocks.length === 1 ? page.blocks[0] : null;
|
|
923
|
+
if (!sole || sole.continues) continue;
|
|
924
|
+
const band = byId.get(sole.id);
|
|
925
|
+
if (!band || band.members.length === 0) continue;
|
|
926
|
+
if (band.constraints?.breakAfter !== "page") continue;
|
|
927
|
+
if (band.heightPx >= textBlockPx) continue;
|
|
928
|
+
fullPageIds.push(band.members[0]);
|
|
929
|
+
const fillPx = textBlockPx - PAGE_FILL_INSET_PX;
|
|
930
|
+
rules.push(`${boxSelector(band.members[0])} { height: ${fillPx}px !important; }`, `${contentSelector(band.members[0])} { height: ${fillPx}px !important; }`);
|
|
931
|
+
screenExtraPx += textBlockPx - PAGE_FILL_INSET_PX - band.heightPx;
|
|
932
|
+
}
|
|
933
|
+
for (const over of map.overflows) {
|
|
934
|
+
const band = byId.get(over.id);
|
|
935
|
+
if (!band || band.members.length === 0) continue;
|
|
936
|
+
const height = band.heightPx;
|
|
937
|
+
if (height <= 0) continue;
|
|
938
|
+
const k = ((over.availablePx ?? textBlockPx) - Math.max(0, over.heightPx - height) - PAGE_FILL_INSET_PX) / height;
|
|
939
|
+
if (k >= 1) continue;
|
|
940
|
+
if (k < scaleFloor) {
|
|
941
|
+
refused.push({
|
|
942
|
+
id: over.id,
|
|
943
|
+
heightPx: height,
|
|
944
|
+
requiredScale: round2(k)
|
|
945
|
+
});
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
const scaledHeightPx = Math.floor(height * k);
|
|
949
|
+
scaled.push({
|
|
950
|
+
id: over.id,
|
|
951
|
+
heightPx: height,
|
|
952
|
+
scale: round2(k),
|
|
953
|
+
scaledHeightPx
|
|
954
|
+
});
|
|
955
|
+
const box = boxSelector(band.members[0]);
|
|
956
|
+
rules.push(`${box} { height: ${scaledHeightPx}px !important; overflow: hidden !important; }`);
|
|
957
|
+
for (const member of band.members) rules.push(`${contentSelector(member)} { transform: scale(${round4(k)}) !important; transform-origin: top left !important; width: ${round2(100 / k)}% !important; }`);
|
|
958
|
+
}
|
|
959
|
+
const individuallyHandled = new Set([...scaled.map((r) => r.id), ...refused.map((r) => r.id)]);
|
|
960
|
+
for (const page of map.pages) {
|
|
961
|
+
if (page.usedPx <= textBlockPx) continue;
|
|
962
|
+
if (page.blocks.some((blk) => blk.continues || blk.spansPages)) continue;
|
|
963
|
+
if (page.blocks.some((blk) => individuallyHandled.has(blk.id))) continue;
|
|
964
|
+
const members = page.blocks.map((blk) => byId.get(blk.id)).filter((band) => !!band && band.members.length > 0);
|
|
965
|
+
if (members.length !== page.blocks.length) continue;
|
|
966
|
+
const bandsSum = members.reduce((sum, band) => sum + band.heightPx, 0);
|
|
967
|
+
const gapsSum = page.usedPx - bandsSum;
|
|
968
|
+
if (bandsSum <= 0) continue;
|
|
969
|
+
const k = (textBlockPx - PAGE_FILL_INSET_PX - gapsSum) / bandsSum;
|
|
970
|
+
if (k >= 1) continue;
|
|
971
|
+
if (k < scaleFloor) {
|
|
972
|
+
refused.push({
|
|
973
|
+
id: members.map((band) => band.id).join(" + "),
|
|
974
|
+
heightPx: page.usedPx,
|
|
975
|
+
requiredScale: round2(k)
|
|
976
|
+
});
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
for (const band of members) {
|
|
980
|
+
const scaledHeightPx = Math.floor(band.heightPx * k);
|
|
981
|
+
scaled.push({
|
|
982
|
+
id: band.id,
|
|
983
|
+
heightPx: band.heightPx,
|
|
984
|
+
scale: round2(k),
|
|
985
|
+
scaledHeightPx
|
|
986
|
+
});
|
|
987
|
+
rules.push(`${boxSelector(band.members[0])} { height: ${scaledHeightPx}px !important; overflow: hidden !important; }`);
|
|
988
|
+
for (const member of band.members) rules.push(`${contentSelector(member)} { transform: scale(${round4(k)}) !important; transform-origin: top left !important; width: ${round2(100 / k)}% !important; }`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
const screenRules = [];
|
|
992
|
+
if (screenSeparators && pageStartCellIds.length > 0) {
|
|
993
|
+
const selector = pageStartCellIds.map((id) => boxSelector(id)).join(",\n ");
|
|
994
|
+
screenRules.push("@media screen {", ` ${selector} {`, ` margin-top: ${SEPARATOR_MARGIN_PX}px !important;`, ` border-top: ${SEPARATOR_BORDER_PX}px dashed rgb(194 65 12) !important;`, ` padding-top: ${SEPARATOR_PADDING_PX}px !important;`, " }", "}");
|
|
995
|
+
if (blankPageBeforeCellIds.length > 0) {
|
|
996
|
+
const blankSel = blankPageBeforeCellIds.map((id) => `${boxSelector(id)}::before`).join(",\n ");
|
|
997
|
+
screenRules.push("@media screen {", ` ${blankSel} {`, ` margin-bottom: ${SEPARATOR_MARGIN_PX}px;`, ` border-bottom: ${SEPARATOR_BORDER_PX}px dashed rgb(194 65 12);`, " }", "}");
|
|
998
|
+
}
|
|
999
|
+
screenExtraPx += pageStartCellIds.length * 66;
|
|
1000
|
+
screenExtraPx += blankPageBeforeCellIds.length * (textBlockPx - PAGE_FILL_INSET_PX + 50);
|
|
1001
|
+
}
|
|
1002
|
+
const runningRules = [];
|
|
1003
|
+
const runningByPage = [];
|
|
1004
|
+
if (running && running.slots.length > 0) {
|
|
1005
|
+
const values = resolveRunningValues(map, bands, running.markers);
|
|
1006
|
+
const font = cssValue(running.marginFont) ?? "var(--oa-doc-margin-font, 8pt/1.2 Inter, system-ui, sans-serif)";
|
|
1007
|
+
const color = cssValue(running.marginColor) ?? "var(--oa-doc-margin-ink, rgba(0,0,0,0.45))";
|
|
1008
|
+
for (const pageValues of values) {
|
|
1009
|
+
runningByPage.push(pageValues);
|
|
1010
|
+
const resolved = runningSlotValues(pageValues, { continuedSuffix: running.continuedSuffix });
|
|
1011
|
+
const boxes = [];
|
|
1012
|
+
for (const placement of running.slots) {
|
|
1013
|
+
if (!isRunningSlot(placement.slot)) continue;
|
|
1014
|
+
const text = resolved[placement.slot];
|
|
1015
|
+
if (!text) continue;
|
|
1016
|
+
boxes.push(` @${placement.edge}-${placement.side} {`, ` content: ${cssString$1(text)};`, ` font: ${font};`, ` color: ${color};`, " }");
|
|
1017
|
+
}
|
|
1018
|
+
if (boxes.length === 0) continue;
|
|
1019
|
+
const name = `oa-page-${pageValues.page}`;
|
|
1020
|
+
runningRules.push(`@page ${name} {`, ...boxes, "}");
|
|
1021
|
+
const first = (map.pages.find((p) => p.index === pageValues.page)?.blocks ?? []).find((b) => !b.continues);
|
|
1022
|
+
const anchor = first ? byId.get(first.id)?.members[0] : void 0;
|
|
1023
|
+
if (anchor) runningRules.push(`${boxSelector(anchor)} { page: ${name} !important; }`);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return {
|
|
1027
|
+
css: [
|
|
1028
|
+
...rules,
|
|
1029
|
+
...runningRules,
|
|
1030
|
+
...screenRules
|
|
1031
|
+
].join("\n"),
|
|
1032
|
+
pageStartCellIds,
|
|
1033
|
+
scaled,
|
|
1034
|
+
refused,
|
|
1035
|
+
fullPageCellIds: fullPageIds,
|
|
1036
|
+
blankPageBeforeCellIds,
|
|
1037
|
+
blankPageScreenOffsetPx: textBlockPx - PAGE_FILL_INSET_PX + (screenSeparators ? 50 : 0),
|
|
1038
|
+
screenExtraPx,
|
|
1039
|
+
runningByPage
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function cssString$1(value) {
|
|
1043
|
+
return cssString(value.replace(/\s*\n\s*/g, " "));
|
|
1044
|
+
}
|
|
1045
|
+
function round2(n) {
|
|
1046
|
+
return Math.round(n * 100) / 100;
|
|
1047
|
+
}
|
|
1048
|
+
function round4(n) {
|
|
1049
|
+
return Math.round(n * 1e4) / 1e4;
|
|
1050
|
+
}
|
|
1051
|
+
//#endregion
|
|
1052
|
+
//#region packages/renderer/src/print/map-staleness.ts
|
|
1053
|
+
function expectedHeightAfterTypesetting(measuredContainerPx, layout) {
|
|
1054
|
+
return measuredContainerPx - layout.scaled.reduce((sum, r) => sum + (r.heightPx - r.scaledHeightPx), 0) + layout.screenExtraPx;
|
|
1055
|
+
}
|
|
1056
|
+
function stalenessTolerancePx(textBlockPx) {
|
|
1057
|
+
return Math.max(120, Math.round(textBlockPx / 2));
|
|
1058
|
+
}
|
|
1059
|
+
function mapStaleness(liveHeightPx, expectedPx, textBlockPx) {
|
|
1060
|
+
const tolerancePx = stalenessTolerancePx(textBlockPx);
|
|
1061
|
+
const deltaPx = liveHeightPx - expectedPx;
|
|
1062
|
+
return {
|
|
1063
|
+
stale: Math.abs(deltaPx) > tolerancePx,
|
|
1064
|
+
deltaPx,
|
|
1065
|
+
tolerancePx
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
//#endregion
|
|
1069
|
+
//#region packages/renderer/src/print/print-lint.ts
|
|
1070
|
+
var VIEWPORT_UNIT = /(?<![\w-])(\d*\.?\d+)(v[hw]|vmin|vmax|[sld]v[hwbi]|vb|vi)(?![\w-])/gi;
|
|
1071
|
+
var IGNORED_PROPERTIES = new Set(["container-type"]);
|
|
1072
|
+
function viewportUnitsIn(cssText, where) {
|
|
1073
|
+
const out = [];
|
|
1074
|
+
for (const decl of cssText.split(";")) {
|
|
1075
|
+
const colon = decl.indexOf(":");
|
|
1076
|
+
if (colon <= 0) continue;
|
|
1077
|
+
const lhs = decl.slice(0, colon);
|
|
1078
|
+
const property = lhs.slice(Math.max(lhs.lastIndexOf("{"), lhs.lastIndexOf("}")) + 1).trim().toLowerCase();
|
|
1079
|
+
const value = decl.slice(colon + 1).trim();
|
|
1080
|
+
if (!property || IGNORED_PROPERTIES.has(property)) continue;
|
|
1081
|
+
VIEWPORT_UNIT.lastIndex = 0;
|
|
1082
|
+
if (VIEWPORT_UNIT.test(value)) out.push({
|
|
1083
|
+
rule: "viewport-unit",
|
|
1084
|
+
where,
|
|
1085
|
+
property,
|
|
1086
|
+
value
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
return out;
|
|
1090
|
+
}
|
|
1091
|
+
function mediaReachesPrint(condition) {
|
|
1092
|
+
if (!condition) return true;
|
|
1093
|
+
const c = condition.toLowerCase();
|
|
1094
|
+
const mentionsPrint = /\bprint\b/.test(c);
|
|
1095
|
+
if (/\bscreen\b/.test(c) && !mentionsPrint) return false;
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
function auditPrintLint(root, { computedStyle, rules }) {
|
|
1099
|
+
const out = [];
|
|
1100
|
+
if (!root) return out;
|
|
1101
|
+
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
1102
|
+
if (computedStyle(el).position === "fixed") out.push({
|
|
1103
|
+
rule: "position-fixed",
|
|
1104
|
+
where: describe(el),
|
|
1105
|
+
property: "position",
|
|
1106
|
+
value: "fixed"
|
|
1107
|
+
});
|
|
1108
|
+
const inline = el.getAttribute("style");
|
|
1109
|
+
if (inline) out.push(...viewportUnitsIn(inline, describe(el)));
|
|
1110
|
+
}
|
|
1111
|
+
for (const [label, cssText] of rules) out.push(...viewportUnitsIn(cssText, label));
|
|
1112
|
+
return out;
|
|
1113
|
+
}
|
|
1114
|
+
function describe(el) {
|
|
1115
|
+
const cellId = el.getAttribute("data-cell-id");
|
|
1116
|
+
if (cellId) return `[data-cell-id="${cellId}"]`;
|
|
1117
|
+
const component = el.getAttribute("data-component");
|
|
1118
|
+
if (component) return `<${el.tagName.toLowerCase()} data-component="${component}">`;
|
|
1119
|
+
if (el.id) return `#${el.id}`;
|
|
1120
|
+
const cls = el.getAttribute("class");
|
|
1121
|
+
if (cls) return `${el.tagName.toLowerCase()}.${cls.trim().split(/\s+/).slice(0, 2).join(".")}`;
|
|
1122
|
+
return el.tagName.toLowerCase();
|
|
1123
|
+
}
|
|
1124
|
+
//#endregion
|
|
1125
|
+
//#region packages/renderer/src/print/structure-print.ts
|
|
1126
|
+
function setRowPageIntent(structure, cellId, intent) {
|
|
1127
|
+
if (!structure || !cellId) return {
|
|
1128
|
+
structure: structure ?? [],
|
|
1129
|
+
changed: false
|
|
1130
|
+
};
|
|
1131
|
+
let changed = false;
|
|
1132
|
+
const visit = (entries) => entries.map((entry) => {
|
|
1133
|
+
if (typeof entry === "string") {
|
|
1134
|
+
if (entry !== cellId || changed) return entry;
|
|
1135
|
+
const next = applyIntent({ cellId: entry }, intent);
|
|
1136
|
+
changed = true;
|
|
1137
|
+
return next;
|
|
1138
|
+
}
|
|
1139
|
+
const node = entry;
|
|
1140
|
+
const children = node.children ? visit(node.children) : void 0;
|
|
1141
|
+
const childrenChanged = children !== void 0 && children !== node.children;
|
|
1142
|
+
if (node.cellId === cellId && !changed) {
|
|
1143
|
+
changed = true;
|
|
1144
|
+
return applyIntent({
|
|
1145
|
+
...node,
|
|
1146
|
+
...children ? { children } : {}
|
|
1147
|
+
}, intent);
|
|
1148
|
+
}
|
|
1149
|
+
if (childrenChanged) return {
|
|
1150
|
+
...node,
|
|
1151
|
+
children
|
|
1152
|
+
};
|
|
1153
|
+
return node;
|
|
1154
|
+
});
|
|
1155
|
+
const next = visit(structure);
|
|
1156
|
+
return {
|
|
1157
|
+
structure: changed ? next : structure,
|
|
1158
|
+
changed
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function applyIntent(node, intent) {
|
|
1162
|
+
const print = printBlockForIntent(node.print, intent);
|
|
1163
|
+
const next = { ...node };
|
|
1164
|
+
if (print) next.print = print;
|
|
1165
|
+
else delete next.print;
|
|
1166
|
+
return next;
|
|
1167
|
+
}
|
|
1168
|
+
function printBlockForIntent(existing, intent) {
|
|
1169
|
+
const print = { ...existing ?? {} };
|
|
1170
|
+
delete print.breakBefore;
|
|
1171
|
+
delete print.keepWithPrevious;
|
|
1172
|
+
if (intent === "break-before") print.breakBefore = true;
|
|
1173
|
+
if (intent === "keep-with-previous") print.keepWithPrevious = true;
|
|
1174
|
+
return Object.keys(print).length > 0 ? print : void 0;
|
|
1175
|
+
}
|
|
1176
|
+
function intentOfPrintBlock(print) {
|
|
1177
|
+
if (print?.breakBefore) return "break-before";
|
|
1178
|
+
if (print?.keepWithPrevious) return "keep-with-previous";
|
|
1179
|
+
return "auto";
|
|
1180
|
+
}
|
|
1181
|
+
function rowPageIntent(structure, cellId) {
|
|
1182
|
+
const node = findNode(structure, cellId);
|
|
1183
|
+
if (!node || typeof node === "string") return "auto";
|
|
1184
|
+
return intentOfPrintBlock(node.print);
|
|
1185
|
+
}
|
|
1186
|
+
function findNode(entries, cellId) {
|
|
1187
|
+
for (const entry of entries ?? []) {
|
|
1188
|
+
if (typeof entry === "string") {
|
|
1189
|
+
if (entry === cellId) return entry;
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
if (entry.cellId === cellId) return entry;
|
|
1193
|
+
const found = findNode(entry.children, cellId);
|
|
1194
|
+
if (found) return found;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function printByCellId(structure) {
|
|
1198
|
+
const out = new Map();
|
|
1199
|
+
const visit = (entries) => {
|
|
1200
|
+
for (const entry of entries ?? []) {
|
|
1201
|
+
if (typeof entry === "string") continue;
|
|
1202
|
+
if (entry.cellId && entry.print && Object.keys(entry.print).length > 0) {
|
|
1203
|
+
if (!out.has(entry.cellId)) out.set(entry.cellId, entry.print);
|
|
1204
|
+
}
|
|
1205
|
+
if (entry.children) visit(entry.children);
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
visit(structure);
|
|
1209
|
+
return out;
|
|
1210
|
+
}
|
|
1211
|
+
function nodePrintConstraints(node) {
|
|
1212
|
+
const p = node.print;
|
|
1213
|
+
if (!p) return void 0;
|
|
1214
|
+
const out = {};
|
|
1215
|
+
if (p.breakBefore) out.breakBefore = "page";
|
|
1216
|
+
else if (p.keepWithPrevious) out.breakBefore = "avoid";
|
|
1217
|
+
if (p.breakInside === "avoid") out.breakInside = "avoid";
|
|
1218
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
1219
|
+
}
|
|
1220
|
+
function setRowBlankPage(structure, cellId, present) {
|
|
1221
|
+
let changed = false;
|
|
1222
|
+
const visit = (entries) => entries.map((entry) => {
|
|
1223
|
+
const node = typeof entry === "string" ? { cellId: entry } : entry;
|
|
1224
|
+
const children = node.children ? visit(node.children) : void 0;
|
|
1225
|
+
const childrenChanged = children !== void 0 && children !== node.children;
|
|
1226
|
+
if (node.cellId === cellId && !changed) {
|
|
1227
|
+
if (node.print?.blankPageBefore === true === present) return childrenChanged ? {
|
|
1228
|
+
...node,
|
|
1229
|
+
children
|
|
1230
|
+
} : entry;
|
|
1231
|
+
changed = true;
|
|
1232
|
+
const print = { ...node.print ?? {} };
|
|
1233
|
+
if (present) print.blankPageBefore = true;
|
|
1234
|
+
else delete print.blankPageBefore;
|
|
1235
|
+
const next = {
|
|
1236
|
+
...node,
|
|
1237
|
+
...children ? { children } : {}
|
|
1238
|
+
};
|
|
1239
|
+
if (Object.keys(print).length > 0) next.print = print;
|
|
1240
|
+
else delete next.print;
|
|
1241
|
+
return next;
|
|
1242
|
+
}
|
|
1243
|
+
if (childrenChanged) return {
|
|
1244
|
+
...node,
|
|
1245
|
+
children
|
|
1246
|
+
};
|
|
1247
|
+
return entry;
|
|
1248
|
+
});
|
|
1249
|
+
const next = visit(structure ?? []);
|
|
1250
|
+
return {
|
|
1251
|
+
structure: changed ? next : structure ?? [],
|
|
1252
|
+
changed
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
function rowHasBlankPage(structure, cellId) {
|
|
1256
|
+
const node = findNode(structure, cellId);
|
|
1257
|
+
if (!node || typeof node === "string") return false;
|
|
1258
|
+
return node.print?.blankPageBefore === true;
|
|
1259
|
+
}
|
|
1260
|
+
function diffRowPrint(base, next) {
|
|
1261
|
+
const a = printByCellId(base);
|
|
1262
|
+
const b = printByCellId(next);
|
|
1263
|
+
const out = [];
|
|
1264
|
+
const same = (x, y) => JSON.stringify(x ?? null) === JSON.stringify(y ?? null);
|
|
1265
|
+
for (const [cellId, print] of b) if (!same(a.get(cellId), print)) out.push({
|
|
1266
|
+
cellId,
|
|
1267
|
+
print
|
|
1268
|
+
});
|
|
1269
|
+
for (const [cellId] of a) if (!b.has(cellId)) out.push({ cellId });
|
|
1270
|
+
return out;
|
|
1271
|
+
}
|
|
1272
|
+
function applyRowPrint(structure, changes) {
|
|
1273
|
+
const byId = new Map(changes.map((c) => [c.cellId, c]));
|
|
1274
|
+
const applied = new Set();
|
|
1275
|
+
let changed = false;
|
|
1276
|
+
const visit = (entries) => entries.map((entry) => {
|
|
1277
|
+
const node = typeof entry === "string" ? { cellId: entry } : entry;
|
|
1278
|
+
const children = node.children ? visit(node.children) : void 0;
|
|
1279
|
+
const childrenChanged = children !== void 0 && children !== node.children;
|
|
1280
|
+
const change = node.cellId ? byId.get(node.cellId) : void 0;
|
|
1281
|
+
if (change && !applied.has(change.cellId)) {
|
|
1282
|
+
applied.add(change.cellId);
|
|
1283
|
+
const before = JSON.stringify(node.print ?? null);
|
|
1284
|
+
const after = JSON.stringify(change.print ?? null);
|
|
1285
|
+
const out = {
|
|
1286
|
+
...node,
|
|
1287
|
+
...children ? { children } : {}
|
|
1288
|
+
};
|
|
1289
|
+
if (change.print && Object.keys(change.print).length > 0) out.print = change.print;
|
|
1290
|
+
else delete out.print;
|
|
1291
|
+
if (before !== after) changed = true;
|
|
1292
|
+
return out;
|
|
1293
|
+
}
|
|
1294
|
+
if (childrenChanged) return {
|
|
1295
|
+
...node,
|
|
1296
|
+
children
|
|
1297
|
+
};
|
|
1298
|
+
return entry;
|
|
1299
|
+
});
|
|
1300
|
+
const nextStructure = visit(structure ?? []);
|
|
1301
|
+
const missing = changes.filter((c) => !applied.has(c.cellId)).map((c) => c.cellId);
|
|
1302
|
+
return {
|
|
1303
|
+
structure: changed ? nextStructure : structure ?? [],
|
|
1304
|
+
changed,
|
|
1305
|
+
missing
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
function findSlottedCellIds(structure, slot) {
|
|
1309
|
+
const found = [];
|
|
1310
|
+
const seen = new Set();
|
|
1311
|
+
const visit = (entries) => {
|
|
1312
|
+
for (const entry of entries) {
|
|
1313
|
+
if (typeof entry === "string") continue;
|
|
1314
|
+
if (entry.slot === slot && typeof entry.cellId === "string" && entry.cellId) {
|
|
1315
|
+
if (!seen.has(entry.cellId)) {
|
|
1316
|
+
seen.add(entry.cellId);
|
|
1317
|
+
found.push(entry.cellId);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
if (Array.isArray(entry.children)) visit(entry.children);
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
visit(structure ?? []);
|
|
1324
|
+
return found;
|
|
1325
|
+
}
|
|
1326
|
+
function pruneStructure(entries, keep) {
|
|
1327
|
+
const out = [];
|
|
1328
|
+
for (const entry of entries) {
|
|
1329
|
+
if (typeof entry === "string") {
|
|
1330
|
+
if (keep(entry)) out.push(entry);
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
const children = entry.children ? pruneStructure(entry.children, keep) : void 0;
|
|
1334
|
+
if (typeof entry.cellId === "string" && entry.cellId) {
|
|
1335
|
+
if (keep(entry.cellId)) out.push(children ? {
|
|
1336
|
+
...entry,
|
|
1337
|
+
children
|
|
1338
|
+
} : entry);
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
if (children && children.length > 0) out.push({
|
|
1342
|
+
...entry,
|
|
1343
|
+
children
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
return out;
|
|
1347
|
+
}
|
|
1348
|
+
function splitMarginalia(page, marginaliaCellId, hasReservedTrack) {
|
|
1349
|
+
const untouched = (reason) => ({
|
|
1350
|
+
bodyPage: page,
|
|
1351
|
+
marginaliaPage: null,
|
|
1352
|
+
reason
|
|
1353
|
+
});
|
|
1354
|
+
if (!page || !marginaliaCellId) return untouched("no-slot");
|
|
1355
|
+
if (!hasReservedTrack) return untouched("no-track");
|
|
1356
|
+
const cells = page.content?.cells ?? [];
|
|
1357
|
+
const railCell = cells.find((c) => c?.id === marginaliaCellId);
|
|
1358
|
+
if (!railCell) return untouched("dangling");
|
|
1359
|
+
const structure = page.layout?.structure ?? [];
|
|
1360
|
+
const bodyStructure = pruneStructure(structure, (id) => id !== marginaliaCellId);
|
|
1361
|
+
const railStructure = pruneStructure(structure, (id) => id === marginaliaCellId);
|
|
1362
|
+
return {
|
|
1363
|
+
bodyPage: {
|
|
1364
|
+
...page,
|
|
1365
|
+
content: {
|
|
1366
|
+
...page.content,
|
|
1367
|
+
cells: cells.filter((c) => c?.id !== marginaliaCellId)
|
|
1368
|
+
},
|
|
1369
|
+
layout: {
|
|
1370
|
+
...page.layout,
|
|
1371
|
+
structure: bodyStructure
|
|
1372
|
+
}
|
|
1373
|
+
},
|
|
1374
|
+
marginaliaPage: {
|
|
1375
|
+
...page,
|
|
1376
|
+
content: {
|
|
1377
|
+
...page.content,
|
|
1378
|
+
cells: [railCell]
|
|
1379
|
+
},
|
|
1380
|
+
layout: {
|
|
1381
|
+
...page.layout,
|
|
1382
|
+
structure: railStructure
|
|
1383
|
+
}
|
|
1384
|
+
},
|
|
1385
|
+
reason: null
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
//#endregion
|
|
1389
|
+
export { BLANK_PAGE_SEPARATOR_PX, DEFAULT_MARGINS, DEFAULT_SHEET, MissingRequiredBindingError, PRINT_POLICY_ATTR, RUNNING_SLOTS, SEPARATOR_TOTAL_PX, __clearDocumentProfileWarnings, __clearPrintStylesheetWarnings, applyRowPrint, auditPrintLint, buildPageStylesheet, buildPageVariables, buildPrintLayout, checkPaginatorBindings, collectBlocks, composeConstraints, constraintsFromBox, constraintsFromComputedStyle, constraintsResolver, cssLengthToPx, cssString, cssValue, deserializeConstraints, diffRowPrint, expectedHeightAfterTypesetting, findSlottedCellIds, forcedBreaksWithin, intentOfPrintBlock, isRunningSlot, mapStaleness, mediaReachesPrint, nodePrintConstraints, paginate, paperGeometryPx, parsePrintPolicy, printBlockForIntent, printByCellId, printPolicyToStyle, resolveDocumentProfile, resolvePlacement, resolveProfileId, resolveRegionBindings, resolveRunningValues, rowHasBlankPage, rowPageIntent, runningSlotValues, serializeConstraints, setRowBlankPage, setRowPageIntent, splitMarginalia, stalenessTolerancePx, viewportUnitsIn, withForcedBreaks };
|
|
1390
|
+
|
|
1391
|
+
//# sourceMappingURL=print.js.map
|