@peripheries/renderer 0.17.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +1 -0
  2. package/dist/catalog.js +2 -2
  3. package/dist/chunks/{projection-BTt_LUza.js → projection-yvYKzMOS.js} +4 -3
  4. package/dist/chunks/projection-yvYKzMOS.js.map +1 -0
  5. package/dist/chunks/{query-DRdMhtcX.js → query-BPuCJC1C.js} +109 -21
  6. package/dist/chunks/query-BPuCJC1C.js.map +1 -0
  7. package/dist/chunks/{structure-BPBwFPO0.js → structure-o9pYoDjD.js} +29 -3
  8. package/dist/chunks/{structure-BPBwFPO0.js.map → structure-o9pYoDjD.js.map} +1 -1
  9. package/dist/chunks/{validate-XuJViQas.js → validate-4LgH8QMJ.js} +107 -11
  10. package/dist/chunks/{validate-XuJViQas.js.map → validate-4LgH8QMJ.js.map} +1 -1
  11. package/dist/core.js +147 -17
  12. package/dist/core.js.map +1 -1
  13. package/dist/print.js +1391 -0
  14. package/dist/print.js.map +1 -0
  15. package/dist/react.js +62 -13
  16. package/dist/react.js.map +1 -1
  17. package/dist/runtime.js +5 -4
  18. package/dist/runtime.js.map +1 -1
  19. package/dist/styles.css +4 -0
  20. package/dist/testing.js +1 -1
  21. package/dist/types/core/cell-props.d.ts +3 -1
  22. package/dist/types/core/data-context.d.ts +4 -0
  23. package/dist/types/core/diagnostics.d.ts +16 -0
  24. package/dist/types/core/index.d.ts +2 -1
  25. package/dist/types/core/load-page.d.ts +15 -1
  26. package/dist/types/core/references.d.ts +8 -2
  27. package/dist/types/core/sources.d.ts +97 -0
  28. package/dist/types/core/structure.d.ts +16 -0
  29. package/dist/types/core/types.d.ts +34 -0
  30. package/dist/types/core/validate.d.ts +10 -0
  31. package/dist/types/print/document-bindings.d.ts +76 -0
  32. package/dist/types/print/document-profile.d.ts +203 -0
  33. package/dist/types/print/index.d.ts +11 -0
  34. package/dist/types/print/map-staleness.d.ts +75 -0
  35. package/dist/types/print/page-map.d.ts +427 -0
  36. package/dist/types/print/paginate.d.ts +231 -0
  37. package/dist/types/print/print-layout.d.ts +212 -0
  38. package/dist/types/print/print-lint.d.ts +124 -0
  39. package/dist/types/print/print-policy.d.ts +210 -0
  40. package/dist/types/print/print-stylesheet.d.ts +168 -0
  41. package/dist/types/print/running-header.d.ts +105 -0
  42. package/dist/types/print/structure-print.d.ts +302 -0
  43. package/dist/types/react/cell-view.d.ts +5 -0
  44. package/dist/types/react/page-renderer.d.ts +6 -0
  45. package/dist/types/runtime/install.d.ts +9 -3
  46. package/dist/types/testing/components.d.ts +1 -1
  47. package/package.json +6 -2
  48. package/dist/chunks/projection-BTt_LUza.js.map +0 -1
  49. 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