@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
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Writing the pagination decision back as CSS, so the browser breaks where the
3
+ * host decided.
4
+ *
5
+ * ── The gap this closes ─────────────────────────────────────────────────────
6
+ *
7
+ * The host has been able to say where pages should break for a while:
8
+ * `collectBlocks` measures the document's rows, `paginate` assigns them to
9
+ * pages, and the result reconciles exactly against the measured height. None of
10
+ * it reached the paper. Layout mode *drew* the breaks; Chrome went on choosing
11
+ * its own from `break-inside` alone.
12
+ *
13
+ * The owner, three times, in their own words: the layout button should switch to
14
+ * a mode that **adjusts the breaks, not just shows them**, and the result should
15
+ * be what prints. Each time I answered with a better instrument. The step I had
16
+ * not taken is this one, and it is small: emit `break-before: page` on the first
17
+ * row of every page, and the browser's choices become the host's choices.
18
+ *
19
+ * ── Why a stylesheet and not inline styles ─────────────────────────────────
20
+ *
21
+ * The element that must carry the break is the cell's grid item, and
22
+ * `CellRenderer` writes that element's `style` attribute itself
23
+ * (`grid-column`, `break-inside`, `position`, `container`). Mutating it from an
24
+ * effect means racing React for the same attribute: it wins on the next render
25
+ * and the breaks vanish with no error. It is also the exact defect that defeated
26
+ * the bundle's cover fix — an inline style no author rule could beat.
27
+ *
28
+ * So the decision becomes a `<style>` element. React does not manage it, a
29
+ * re-render cannot clobber it, and it is part of the document that gets printed.
30
+ *
31
+ * `:has(> [data-cell-id="x"])` selects that grid item without needing a class on
32
+ * it, because the item is exactly *the parent of the cell* and CSS has no parent
33
+ * combinator otherwise. Chrome has supported `:has()` since 105; this route is
34
+ * Chrome-only in practice (it is what prints and what a render service drives).
35
+ *
36
+ * `!important` throughout, and not as a reflex: the grid item's own inline
37
+ * `break-inside: avoid` would otherwise outrank a `break-before` rule in the
38
+ * cascade, and an inline `height` on a scaled row would outrank the height that
39
+ * makes the scale add up.
40
+ *
41
+ * ── Resizing, which is the owner's other requirement ───────────────────────
42
+ *
43
+ * *"If it's not a text component or something that can be split across pages
44
+ * and still look good then we need to figure out how to resize the component to
45
+ * fit a page."*
46
+ *
47
+ * A row taller than a whole page is scaled by `k = pageHeight / rowHeight`. The
48
+ * row's box is shortened to `k × height` and its contents scaled from the top
49
+ * left, so the visual geometry is preserved exactly and the layout box shrinks
50
+ * with it. This is what print engines have always done with an oversized figure.
51
+ *
52
+ * **There is a floor.** Below it, type stops being readable and a shrunk
53
+ * component is a worse outcome than a reported one — the honest answer there is
54
+ * that the component needs a print variant from its owner. A silently
55
+ * illegible page is the failure this whole thread keeps producing in other
56
+ * forms: something that looks finished and misleads.
57
+ *
58
+ * ── What this still does NOT do ────────────────────────────────────────────
59
+ *
60
+ * It does not verify that Chrome honours the breaks. Nothing available here can:
61
+ * `@media print` is inert outside printing, there is no print emulation, and no
62
+ * PDF has been opened by anyone but the owner. The rules are asserted to be
63
+ * *emitted and correct*; whether the paper agrees is one look away and has not
64
+ * been taken.
65
+ */
66
+ import type { Band } from './page-map.js';
67
+ import type { PageMap } from './paginate.js';
68
+ import { type SectionMarker } from './running-header.js';
69
+ export interface PrintLayoutOptions {
70
+ /** Usable page height in CSS px — the sheet minus its top and bottom margins. */
71
+ textBlockPx: number;
72
+ /**
73
+ * Also emit a `@media screen` block that separates the pages visually.
74
+ *
75
+ * On by default. Without it the print view is a single long column on screen
76
+ * that breaks correctly on paper — which is precisely the disconnect this
77
+ * whole feature exists to remove: *"the layout button would switch to a
78
+ * layout mode that adjusts the breaks not just shows them"*. If the screen
79
+ * cannot show the pages, the mode is still only telling you about them.
80
+ *
81
+ * Screen-only, and that scoping is load-bearing: the separation is margin,
82
+ * and margin on paper would shift every subsequent break — the layout would
83
+ * be wrong in exactly the way it is being fixed.
84
+ */
85
+ screenSeparators?: boolean;
86
+ /**
87
+ * Smallest scale a row may be shrunk to before it is refused instead.
88
+ *
89
+ * 0.6 by default: a 12px label becomes 7.2px, which is about the floor of
90
+ * comfortable print. Below that, shrinking produces a page nobody can read
91
+ * and hides the problem from the person who could fix it.
92
+ */
93
+ scaleFloor?: number;
94
+ /**
95
+ * What a deliberate blank page says. Default is the printer's convention.
96
+ *
97
+ * A blank sheet with nothing on it reads as a printing fault, and the reader
98
+ * concluding that is the failure this text exists to prevent.
99
+ */
100
+ blankPageNotice?: string;
101
+ /**
102
+ * The `[data-cell-id]` element IS the fragmentation box (the host's cell frame, which is the grid
103
+ * item), rather than the box's only child (the PWA's `.cell-renderer`). Rules then target the
104
+ * frame, and a scale shrinks the frame's children.
105
+ */
106
+ cellIsBox?: boolean;
107
+ /**
108
+ * Emit per-page running headers and footers.
109
+ *
110
+ * `slots` places a running slot name at a margin-box position, in the same
111
+ * `region -> side` shape a document profile uses. `markers` are the section
112
+ * headings, resolved from the rendered document by the caller.
113
+ *
114
+ * Absent means no running content, which is the state of every document that
115
+ * has not asked for one.
116
+ */
117
+ running?: {
118
+ slots: RunningSlotPlacement[];
119
+ markers: readonly SectionMarker[];
120
+ /** `' (continued)'` by default. */
121
+ continuedSuffix?: string;
122
+ /** Font and colour for the margin boxes, matching `buildPageStylesheet`. */
123
+ marginFont?: string;
124
+ marginColor?: string;
125
+ };
126
+ }
127
+ /** Where a running slot goes: which margin box, and which slot fills it. */
128
+ export interface RunningSlotPlacement {
129
+ edge: 'top' | 'bottom';
130
+ side: 'left' | 'center' | 'right';
131
+ /** `section-title` or `section-title-continued`. */
132
+ slot: string;
133
+ }
134
+ export interface ScaledRow {
135
+ /** The band id — member cell ids joined by `+`. */
136
+ id: string;
137
+ heightPx: number;
138
+ /** The factor applied. Always < 1. */
139
+ scale: number;
140
+ scaledHeightPx: number;
141
+ }
142
+ export interface RefusedRow {
143
+ id: string;
144
+ heightPx: number;
145
+ /** The scale it would have needed. Below the floor. */
146
+ requiredScale: number;
147
+ }
148
+ export interface PrintLayout {
149
+ /**
150
+ * The stylesheet to put in the document. Empty string when there is nothing
151
+ * to say.
152
+ *
153
+ * Contains both the print typesetting and a `@media screen` block that makes
154
+ * the same page boundaries VISIBLE on screen — see `screenSeparators`.
155
+ */
156
+ css: string;
157
+ /** Rows that will be broken to a fresh page, in page order. Excludes page 1. */
158
+ pageStartCellIds: string[];
159
+ scaled: ScaledRow[];
160
+ /** Too tall to scale within the floor — these need a print variant. */
161
+ refused: RefusedRow[];
162
+ /** Per-page running content, for a consumer that wants to show it. */
163
+ runningByPage: Array<{
164
+ page: number;
165
+ sectionTitle?: string;
166
+ continued: boolean;
167
+ }>;
168
+ /** Rows given the whole sheet as their box, in page order. */
169
+ fullPageCellIds: string[];
170
+ /** Rows that carry a deliberate blank page before them, in page order. */
171
+ blankPageBeforeCellIds: string[];
172
+ /**
173
+ * How far below such a row's box top its own content begins, on screen.
174
+ *
175
+ * Published rather than re-derived: the hover affordances are positioned
176
+ * against the box and would otherwise be drawn on top of the blank page —
177
+ * the owner's *"there are no options that show up on hover"*. They were
178
+ * there, a page above where anyone would look.
179
+ */
180
+ blankPageScreenOffsetPx: number;
181
+ /**
182
+ * How much taller `css` makes the document on SCREEN.
183
+ *
184
+ * Two contributions: the page separators, which are margin, border and
185
+ * padding and therefore real height; and the floor put under a full-page row,
186
+ * which grows its box to the sheet.
187
+ *
188
+ * A caller that re-measures the document after applying this stylesheet must
189
+ * add this back, or it will conclude the document changed when only the
190
+ * stylesheet did — see `map-staleness.ts` for what that cost.
191
+ */
192
+ screenExtraPx: number;
193
+ }
194
+ /** What one separator adds to the document's screen height. */
195
+ /**
196
+ * What the line BELOW a blank page adds on screen — margin plus border.
197
+ *
198
+ * No padding: `box-sizing: border-box` would absorb it into the blank page's
199
+ * own height instead of adding to it, so counting it would overstate the
200
+ * document by 16px per blank page and re-open the staleness oscillation.
201
+ */
202
+ export declare const BLANK_PAGE_SEPARATOR_PX: number;
203
+ export declare const SEPARATOR_TOTAL_PX: number;
204
+ /**
205
+ * Turn a page map into the CSS that makes it real.
206
+ *
207
+ * @param map From `paginate`, at the same geometry as `options.textBlockPx`.
208
+ * @param bands From `collectBlocks`, so a band id can be resolved to the cell
209
+ * whose box carries the rule. The map alone cannot do this: its
210
+ * block ids are joined band ids, and the selector needs one member.
211
+ */
212
+ export declare function buildPrintLayout(map: PageMap, bands: readonly Band[], { textBlockPx, scaleFloor, screenSeparators, running, blankPageNotice, cellIsBox }: PrintLayoutOptions): PrintLayout;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Pagination rule 08, as the check the specification asks it to be.
3
+ *
4
+ * `Document System.pdf` p11, rule 08: *"No element is positioned fixed, and no
5
+ * length is in viewport units"* — annotated `lint rule`, the only one of the
6
+ * eight the spec describes as a check rather than as a CSS property. Its
7
+ * reason: *"Fixed elements stamp every sheet and viewport units mis-size at
8
+ * print."*
9
+ *
10
+ * ── The two halves need two different sources, and that is the whole point ──
11
+ *
12
+ * `position: fixed` is visible in computed style. `fixed` is never what an
13
+ * element resolves to for free, so reading it back is sound.
14
+ *
15
+ * **Viewport units are NOT visible in computed style, and this is the same
16
+ * defect that hid `break-inside` for a fortnight**. `max-height:
17
+ * 100vh` computes to `806px` — a resolved value with no trace of how it was
18
+ * written. A check that read computed style would find zero violations on a
19
+ * document full of them and report a clean bill.
20
+ *
21
+ * It is not hypothetical here either. `max-height: 100vh` on the bundle's cover plate
22
+ * is exactly why the cover printed as a small card in the top third of an empty
23
+ * sheet, and `806` — the number I chased for an afternoon — was my
24
+ * own `innerHeight` resolved into the cascade.
25
+ *
26
+ * So viewport units are found by reading the DECLARED text of the stylesheets
27
+ * and the inline `style` attributes. Nothing else can see them.
28
+ *
29
+ * ── Why this reports rather than fixes ─────────────────────────────────────
30
+ *
31
+ * Most violations will be in a component repo's components, which are not this repo's to
32
+ * edit. The deliverable for a cross-repo defect is a precise list — selector,
33
+ * property, declared value — that the owner can act on. A host that silently
34
+ * rewrote another repo's CSS would be worse than the bug.
35
+ */
36
+ /** One rule-08 violation, named precisely enough for its owner to find it. */
37
+ export interface PrintLintViolation {
38
+ rule: 'position-fixed' | 'viewport-unit';
39
+ /** A CSS selector, or a `[data-cell-id]` for an element found in the DOM. */
40
+ where: string;
41
+ /** `position` for the first rule; the offending property for the second. */
42
+ property: string;
43
+ /** The DECLARED value — `100vh`, not the `806px` it resolved to. */
44
+ value: string;
45
+ }
46
+ /**
47
+ * Scan declared CSS text for viewport units.
48
+ *
49
+ * Exported for its own tests, and because the stylesheet walk that feeds it is
50
+ * inherently untestable without a browser — the parsing is where the mistakes
51
+ * live, so the parsing is what gets tested directly.
52
+ */
53
+ export declare function viewportUnitsIn(cssText: string, where: string): PrintLintViolation[];
54
+ /**
55
+ * Can a rule under this media condition reach PAPER?
56
+ *
57
+ * ── Why the question is about print, not about "the current media" ─────────
58
+ *
59
+ * A review found this check media-blind: it walked `CSSMediaRule` children
60
+ * happily, so a rule that cannot apply reported identically to one that does.
61
+ * That finding is right and this is the fix — but the fix is NOT to filter to
62
+ * the media the browser is currently in, which is what a first reading suggests.
63
+ *
64
+ * The print route renders in **screen** media. Filtering to the current media
65
+ * would discard everything inside `@media print` — and a viewport unit inside
66
+ * `@media print` is not the harmless case, it is **the dangerous one**. It is
67
+ * the declaration that is guaranteed to be live at exactly the moment rule 08
68
+ * is about it: *"viewport units mis-size at print."*
69
+ *
70
+ * On the specific case that prompted this: the bundle's
71
+ * `.cover-solo .cover-plate { max-height: 100vh }` was print-only. Under
72
+ * "current media" it is a false positive. Under "reaches paper" it is the single
73
+ * most reportable declaration in the sheet. FEC removed it for a better reason
74
+ * than either — the plate's 17/22 ratio resolves to 851px against a 946px page,
75
+ * so the backstop could never fire — but the lint rule matters beyond that one
76
+ * declaration.
77
+ *
78
+ * So: **screen-only is skipped; everything else is kept.** Conservative in the
79
+ * direction that matters, since a false positive is reviewable and a silent
80
+ * drop is not.
81
+ *
82
+ * Deliberately textual rather than `window.matchMedia`, which evaluates against
83
+ * the browser's CURRENT media and would answer the wrong question confidently.
84
+ */
85
+ export declare function mediaReachesPrint(condition: string | null | undefined): boolean;
86
+ export interface PrintLintOptions {
87
+ /** Resolve an element's computed style. Injected — jsdom has no layout. */
88
+ computedStyle: (el: Element) => {
89
+ position?: string;
90
+ };
91
+ /**
92
+ * The CSS rules that APPLY to this document, as `[label, declarations]`.
93
+ *
94
+ * Text rather than `CSSStyleSheet` objects because a cross-origin sheet
95
+ * throws on `.cssRules` with no way to ask first, and because flattening a
96
+ * whole sheet to text destroys the at-rule nesting a caller has to walk
97
+ * anyway.
98
+ *
99
+ * ── "APPLY" is load-bearing, and it cost a wrong number ────────────────────
100
+ *
101
+ * The first version took whole stylesheets. Run against the real document it
102
+ * reported **42** violations; **40** of them were Tailwind utility classes
103
+ * (`h-[85vh]`, `h-screen`, `100dvh` under an `@supports`) sitting unused in
104
+ * the host's global chunk. The document's actual count was **two**.
105
+ *
106
+ * Forty false positives do not read as noise — they read as a document in
107
+ * serious trouble, and they would have been handed to FEC as a list to fix.
108
+ * A wrongly-scoped measurement reads as PRECISE rather than as wrong, which
109
+ * is the fourth time that has happened on this project.
110
+ *
111
+ * So the caller filters to rules that match something under `root` — it has
112
+ * the DOM and can ask `root.querySelector(selectorText)`, which this pure
113
+ * function cannot. A rule that matches nothing cannot mis-size anything.
114
+ */
115
+ rules: ReadonlyArray<readonly [string, string]>;
116
+ }
117
+ /**
118
+ * Audit a rendered document against rule 08.
119
+ *
120
+ * Returns violations in the order found, most-specific label first. An empty
121
+ * array means the document conforms — and unlike a computed-style check, that
122
+ * emptiness is load-bearing rather than structural.
123
+ */
124
+ export declare function auditPrintLint(root: Element | null, { computedStyle, rules }: PrintLintOptions): PrintLintViolation[];
@@ -0,0 +1,210 @@
1
+ /**
2
+ * `@printPolicy` — parsing, composition, and the CSS it becomes.
3
+ *
4
+ * FEC declares a component's paper behaviour as a JSDoc tag; their
5
+ * `scripts/check-print-policy.mts` proves each key against the CSS the
6
+ * component actually emits, and the values reach us on
7
+ * `ComponentDeclaration.print_policy`. This is the host half: read them,
8
+ * compose them down a nesting chain, and emit the styles.
9
+ *
10
+ * Grammar and composition rules from the document-components contract (v1).
11
+ *
12
+ * ── Two classes of key, and only one of them composes ───────────────────────
13
+ *
14
+ * **Constraints** answer *"how may this break"*. They are monotonic: a child may
15
+ * tighten what its parent permits and never loosen it. `orphans`/`widows`
16
+ * compose as `max`, which is that same rule on a threshold instead of an enum.
17
+ *
18
+ * **With one exception, added 2026-09-03: a FORCED break.**
19
+ * `breakAfter: 'page'` is an instruction at a point, not a property of a
20
+ * subtree, and it is not monotonic — it does not tighten an ancestor's `avoid`,
21
+ * it contradicts it. CSS Fragmentation §3.2 settles the conflict in favour of
22
+ * the forced break, and `composeConstraints` follows CSS rather than inventing
23
+ * a rule, because CSS is what actually decides it on paper.
24
+ *
25
+ * It lives on the constraint keys anyway, because CSS models prohibition and
26
+ * instruction as one property with both values in its domain. A third class
27
+ * here would read cleaner and disagree with the enforcer.
28
+ *
29
+ * **Placements** answer *"where does this render"*. They do NOT compose, and
30
+ * applying the constraint rule to them returns a confident wrong answer —
31
+ * FEC's words. `region: page-footer` lifts an element out of its parent
32
+ * entirely, so the parent's atomicity has nothing to say about it.
33
+ * `display: none` is monotonic in the OPPOSITE direction: a hidden parent
34
+ * removes the subtree and a visible child cannot un-hide itself.
35
+ *
36
+ * That distinction is the whole reason this file has two functions instead of
37
+ * one, and it is a known failure in miniature — a sound rule applied to a set it was
38
+ * not written for.
39
+ *
40
+ * ── Values are strings on the wire, numerics included ───────────────────────
41
+ *
42
+ * `"3"`, not `3`. FEC offered to emit typed values; coercing here is one line,
43
+ * where changing their emitter is a shipped-contract change that would also
44
+ * strand every already-built bundle. So the coercion lives at this parser and
45
+ * `print_policy` stays `Record<string, string>` on the wire.
46
+ */
47
+ export type BreakInside = 'avoid' | 'auto';
48
+ /**
49
+ * A break edge is bivalent: a prohibition or an instruction.
50
+ *
51
+ * A review found that the grammar could only ever FORBID a break — every
52
+ * constraint key was `avoid`, `auto`, or a threshold — so `PageBreak`, the one
53
+ * component in the document bundle that exists solely to force a break, could not be
54
+ * described by the contract at all. Its whole implementation is
55
+ * `style={{ breakAfter: 'page' }}`, and this parser rejected that value as
56
+ * unrecognised.
57
+ *
58
+ * `page` is added here rather than deferred to the paginator because **CSS
59
+ * enforces it today**. `break-after: page` is honoured by Chromium in print
60
+ * with no host pagination whatsoever, so this reaches paper by the same path as
61
+ * `avoid` — `printPolicyToStyle` onto the cell wrapper. A key that only a
62
+ * future paginator could honour would be the declared-and-unread shape this
63
+ * whole effort exists to end.
64
+ */
65
+ export type BreakEdge = 'avoid' | 'page';
66
+ /** "How may this break." Monotonic — a child may only tighten. */
67
+ export interface PrintConstraints {
68
+ breakInside?: BreakInside;
69
+ /**
70
+ * `avoid` prohibits a break; `page` FORCES one. Both live on this one key
71
+ * because CSS models them as one property (`break-after`) with both values in
72
+ * its domain, and splitting them would fight the enforcer. See
73
+ * `composeConstraints` for the precedence rule when they conflict.
74
+ */
75
+ breakAfter?: BreakEdge;
76
+ breakBefore?: BreakEdge;
77
+ orphans?: number;
78
+ widows?: number;
79
+ /** Never split, at any depth. Implemented in FEC's checker, declared by nothing yet. */
80
+ atomic?: boolean;
81
+ }
82
+ /** "Where does this render." Does NOT compose like a constraint. */
83
+ export interface PrintPlacements {
84
+ /** Lift out of flow into the page's footer region, repeated per page. */
85
+ region?: 'page-footer';
86
+ /** Omit from paper entirely. */
87
+ display?: 'none';
88
+ }
89
+ export interface ParsedPrintPolicy {
90
+ constraints: PrintConstraints;
91
+ placements: PrintPlacements;
92
+ /**
93
+ * Keys or values this parser did not recognise, as `key=value` strings.
94
+ *
95
+ * **Reported, never silently dropped.** A tag FEC ships that the host quietly
96
+ * ignores is the exact failure `print_policy` spent a month in — declared on
97
+ * ten components and read by nothing. An unknown key means the vocabularies
98
+ * have diverged, and the only way that surfaces is if someone says so.
99
+ */
100
+ unrecognised: string[];
101
+ }
102
+ export declare function parsePrintPolicy(raw: Record<string, string> | null | undefined): ParsedPrintPolicy;
103
+ /**
104
+ * Compose a chain of CONSTRAINTS, outermost first.
105
+ *
106
+ * A child may tighten and never loosen:
107
+ * - `breakInside`: any `avoid` in the chain wins over every `auto`
108
+ * - `breakAfter` / `breakBefore`: set by anyone, cleared by no one — and a
109
+ * `page` (forced) beats an `avoid` in either order, per CSS Fragmentation
110
+ * §3.2, which is what decides it on paper
111
+ * - `orphans` / `widows`: `max`
112
+ * - `atomic`: any `true` wins, and it also forces `breakInside: avoid`,
113
+ * because "never split at any depth" is strictly stronger
114
+ *
115
+ * **Placements are deliberately absent from this function.** See the header —
116
+ * they answer a different question and composing them here would be wrong in
117
+ * two different directions at once.
118
+ */
119
+ export declare function composeConstraints(chain: ReadonlyArray<PrintConstraints | undefined | null>): PrintConstraints;
120
+ /**
121
+ * Resolve PLACEMENT down a chain, outermost first.
122
+ *
123
+ * Not a compose — a resolution, and the two keys resolve oppositely:
124
+ * - `display: none` on ANY ancestor hides the subtree; a child cannot
125
+ * un-hide itself, so the outermost `none` is absolute
126
+ * - `region` belongs to the element that declares it. An ancestor's region
127
+ * does not push a descendant into the footer, so the INNERMOST declaration
128
+ * is the one that applies
129
+ *
130
+ * **Innermost-wins for `region` is inferred, not specified.** FEC said region
131
+ * *"belongs to the element that declares it"*, which settles the ancestor case
132
+ * but not two competing declarations — and with `page-footer` as the only legal
133
+ * value that case cannot arise, so no real data can distinguish it. Pinned by a
134
+ * cast in the tests so the rule survives a refactor; flag to FEC if a second
135
+ * region value is ever added.
136
+ */
137
+ export declare function resolvePlacement(chain: ReadonlyArray<PrintPlacements | undefined | null>): PrintPlacements;
138
+ /**
139
+ * The effective policy as print CSS.
140
+ *
141
+ * Emitted only where it says something: `breakInside: auto` is the CSS default,
142
+ * so writing it changes nothing and would make an unspecified element
143
+ * indistinguishable from a deliberately-splittable one in the DOM. FEC checks
144
+ * `auto` as a positive assertion for that same reason.
145
+ *
146
+ * `region: page-footer` is NOT handled here. Lifting an element into a `@page`
147
+ * margin box is a paginator operation, not a style — and Chromium does not
148
+ * implement GCPM `string()`, so the host resolves footer content to literals at
149
+ * build time (`print-stylesheet.ts`). Returning a style for it would look like
150
+ * support for something that does not work.
151
+ */
152
+ export declare function printPolicyToStyle(constraints: PrintConstraints, placements?: PrintPlacements): Record<string, string | number>;
153
+ /**
154
+ * The attribute the renderers stamp the DECLARED policy onto.
155
+ *
156
+ * ── Why the DOM needs this when it already carries the CSS ─────────────────
157
+ *
158
+ * Because CSS is lossy in exactly the place the packer reads it. Three
159
+ * declarations that mean different things arrive at `getComputedStyle` as one
160
+ * value:
161
+ *
162
+ * a bundle declared `breakInside: 'auto'` -> computed `auto`
163
+ * a bundle declared nothing at all -> computed `auto` (initial value)
164
+ * nobody has ever considered the component -> computed `auto`
165
+ *
166
+ * `paginate`'s `mayNotSplit` treats an explicit `auto` as PERMISSION to split
167
+ * and absence as a prohibition — *"absence is not permission"*, its own header
168
+ * says — and the DOM had made that rule unachievable. Every band on a real
169
+ * document read back as splittable, including the 44 untagged components the
170
+ * conservative default exists for.
171
+ *
172
+ * `orphans`/`widows` are worse: their computed value is `2` in Chrome whether
173
+ * or not anyone said so, so `constraintsFromComputedStyle` had to drop them
174
+ * entirely — which is why the spec's pagination rule 05 (`orphans: 3;
175
+ * widows: 3`) could not be enforced no matter what a bundle declared.
176
+ *
177
+ * So the renderer, which HAS the declaration, writes it down. This is not a
178
+ * second resolution path: the same `parsePrintPolicy` output that becomes the
179
+ * style becomes the attribute, one line apart. The paginator reads the
180
+ * declaration where there is one and falls back to computed style where there
181
+ * is not, so a page rendered by anything else still paginates as before.
182
+ */
183
+ export declare const PRINT_POLICY_ATTR = "data-oa-print-policy";
184
+ /**
185
+ * Constraints as an attribute value: `breakAfter=avoid;orphans=3`.
186
+ *
187
+ * Key=value pairs rather than JSON because this is read in devtools far more
188
+ * often than by code, and a quoted-and-escaped object in an attribute is
189
+ * unreadable at exactly the moment someone is asking why a row broke.
190
+ *
191
+ * Deterministic key order, so a DOM diff shows a real change and not a
192
+ * reordering. Empty in, empty out — the caller omits the attribute rather than
193
+ * writing `""`, because an empty attribute is a declaration of nothing and
194
+ * would be indistinguishable from a declaration this parser failed to render.
195
+ */
196
+ export declare function serializeConstraints(c: PrintConstraints | undefined | null): string;
197
+ /**
198
+ * Read an attribute written by `serializeConstraints` back to constraints.
199
+ *
200
+ * **Routed through `parsePrintPolicy` on purpose.** A private parser here would
201
+ * be a second implementation of the grammar, free to drift from the one that
202
+ * validates FEC's tags — and the whole reason this attribute exists is that two
203
+ * representations of one fact with no shared reader diverge silently. Anything
204
+ * this function accepts, `parsePrintPolicy` accepted first.
205
+ *
206
+ * Returns `undefined` for absent, empty, or entirely-unrecognised input, which
207
+ * is what the packer needs: `undefined` means "no declaration", and that is a
208
+ * different state from "declared, and it permits nothing".
209
+ */
210
+ export declare function deserializeConstraints(value: string | null | undefined): PrintConstraints | undefined;