@beltype/cicero 0.1.11 → 0.1.12
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/dist/seam/client.d.ts +5 -1
- package/dist/seam/client.js +21 -3
- package/dist/seam/rim.d.ts +2 -1
- package/dist/seam/rim.js +3 -1
- package/dist/src/compose-dom.js +5 -2
- package/dist/src/data-beltype.d.ts +2 -0
- package/dist/src/data-beltype.js +4 -0
- package/dist/src/document-state.d.ts +13 -2
- package/dist/src/document-state.js +18 -5
- package/dist/src/driver.d.ts +25 -8
- package/dist/src/driver.js +141 -39
- package/dist/src/fragments.js +7 -0
- package/dist/src/gauge.js +22 -0
- package/dist/src/measure.d.ts +3 -1
- package/dist/src/measure.js +29 -3
- package/dist/src/reads.d.ts +14 -8
- package/dist/src/reads.js +45 -32
- package/dist/src/scope.d.ts +23 -0
- package/dist/src/scope.js +60 -0
- package/dist/src/stonehand.d.ts +1 -1
- package/dist/src/stonehand.js +20 -9
- package/dist/src/word-space.d.ts +11 -0
- package/dist/src/word-space.js +40 -0
- package/package.json +1 -1
package/dist/src/driver.js
CHANGED
|
@@ -23,12 +23,13 @@ import { readGeometryTable, effectivePageName, pageRulesOf, styleSheetsOf, regis
|
|
|
23
23
|
import { snapshotOfStyle } from './forcing-memo.js';
|
|
24
24
|
import { cloneFlowPiece, declineRepeats, mayRepeat, mutateToContinuation, truncateFrom } from './fragments.js';
|
|
25
25
|
import { cutAfter, divides, elAtIn, flattenResume, indexBlocks, inFlowIndex, inRemainders, normalizePath, pathOf, placeAt, remaindersOf } from './positions.js';
|
|
26
|
-
import { documentOpsOf, counterStateAt, pageNumberAt, withoutPageCounters, stringAssignmentsOf, runningAssignmentsOf, namedStringsForPage } from './document-state.js';
|
|
26
|
+
import { documentOpsOf, withRunning, counterStateAt, pageNumberAt, withoutPageCounters, stringAssignmentsOf, runningAssignmentsOf, namedStringsForPage } from './document-state.js';
|
|
27
27
|
import { RUNNING_PROP } from './gcpm-encoding.js';
|
|
28
28
|
import { ARTIFACT, HOST, hasWord } from './data-beltype.js';
|
|
29
|
-
import { blockChildrenOf, footerBeforeRows, inDisplayOrder, isFixedBox, SIDE_BY_SIDE, sourceOf } from './word-space.js';
|
|
29
|
+
import { blockChildrenOf, footerBeforeRows, inDisplayOrder, isFixedBox, isInlineLevel, SIDE_BY_SIDE, sourceOf } from './word-space.js';
|
|
30
30
|
import { changedPairs, settleReads } from './reads.js';
|
|
31
31
|
import { converge, holderPage, namesNoPlace, priorKeys, restartPage } from './resume.js';
|
|
32
|
+
import { readScope, foldTallColumns } from './scope.js';
|
|
32
33
|
import { answerHas, boxesWhoseWordsMoved } from './has-words.js';
|
|
33
34
|
const PX_PER = { px: 1, pt: 96 / 72, in: 96, cm: 96 / 2.54, mm: 96 / 25.4, q: 96 / 101.6 };
|
|
34
35
|
function toPx(len) {
|
|
@@ -93,10 +94,37 @@ export function gapAbove(box, report, win) {
|
|
|
93
94
|
}
|
|
94
95
|
return gap;
|
|
95
96
|
}
|
|
97
|
+
/** The displays besides a block container's whose children stand at class A break points: a table's row groups and a row group's rows (css-break-3 §4.1). */
|
|
98
|
+
const ROWS_HOLDER = /^(inline-)?table$|^table-(row-group|header-group|footer-group)$/;
|
|
99
|
+
/** Does a box of this display lay its children out at class A break points, a block container's in-flow block-level children or a table's rows (css-break-3 §4.1)? A flex or grid container does not: css-page-3 §8.1 gives `page` to no flex or grid item. */
|
|
100
|
+
const laysClassA = (display) => holdsAnonymousBlocks(display) || ROWS_HOLDER.test(display);
|
|
96
101
|
/**
|
|
97
|
-
*
|
|
102
|
+
* Does the `page` property apply to a box (css-page-3 §8.1, *"boxes that create class A break points"*)? An in-flow block-level box whose parent lays its children at class A break points (css-break-3 §4.1), and a table's row group or row.
|
|
98
103
|
*
|
|
99
|
-
* @remarks
|
|
104
|
+
* @remarks
|
|
105
|
+
* css-break-3 §4.1 counts *"a float and an immediately-adjacent in-flow or floated box"* among class A points too; the WPT tests `css-page/page-name-float-001` and `-002` have a name on a float force nothing, as Chromium prints them, and the engine follows the tests. A flex item, a grid item, a table cell and an inline-level box are not boxes it applies to (`page-name-flex-*`, `page-name-inline-block-003`).
|
|
106
|
+
*
|
|
107
|
+
* @param parent - the parent box's style, or null where the box stands in the body and nothing reads it.
|
|
108
|
+
*/
|
|
109
|
+
export function pageApplies(st, parent) {
|
|
110
|
+
if (isOutOfFlow(st))
|
|
111
|
+
return false;
|
|
112
|
+
const display = st.display ?? '';
|
|
113
|
+
if (isInlineLevel(display) || /^table-(cell|column|column-group|caption)$/.test(display))
|
|
114
|
+
return false;
|
|
115
|
+
return laysClassA(parent?.display ?? 'block');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The used page name of the box at `el` (css-page-3 §8.1): `effectivePageName` where the property applies to it, else its parent's. A box it does not apply to takes no name of its own; an inline box standing among blocks is in an anonymous block box, which the property applies to, named as its parent is.
|
|
119
|
+
*/
|
|
120
|
+
export function usedPageName(el, styleOf) {
|
|
121
|
+
const parent = el.parentElement;
|
|
122
|
+
return !parent || pageApplies(styleOf(el), styleOf(parent)) ? effectivePageName(el) : effectivePageName(parent);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The start page value of a box (css-page-3 §8.1): *"the value (if any) propagated from its first … child box, else the used value on the box itself"*, where *"a child propagates its own start … page value if and only if the page property applies to it"*. So down the first in-flow child while it is a box the property applies to ({@link pageApplies}) and no run of words stands before it, then {@link usedPageName} from the box found. The driver's `nameAt` names a page by it, and the forcing index compares it against the end page value before it.
|
|
126
|
+
*
|
|
127
|
+
* @remarks The driver's `nameAt` asks this of a cut that resumes at a box whole, with the position index's word counts for `runBefore`; a host with no index passes its own reading of the same question (*the engine knows no view*). Two reads are the host's: how a box's style is asked, and whether a run stands before a child. A run before the child is the box's first child box, an anonymous block box, which carries the box's own used value.
|
|
100
128
|
*
|
|
101
129
|
* @param el - The box the page begins in.
|
|
102
130
|
* @param reads.runBefore - Whether a line of words stands before `child` in `parent`, which is the page's first content and the container's.
|
|
@@ -104,12 +132,15 @@ export function gapAbove(box, report, win) {
|
|
|
104
132
|
*/
|
|
105
133
|
export function pageNameDownFrom(el, { styleOf, runBefore, blockChildren = blockChildrenOf }) {
|
|
106
134
|
for (;;) {
|
|
135
|
+
const st = styleOf(el);
|
|
136
|
+
if (isOutOfFlow(st) || !laysClassA(st.display ?? ''))
|
|
137
|
+
break; // a page a float begins is named by the float's place in the flow, never by its interior
|
|
107
138
|
const next = blockChildren(el).find((c) => !isOutOfFlow(styleOf(c)));
|
|
108
|
-
if (!next || runBefore(el, next))
|
|
139
|
+
if (!next || runBefore(el, next) || !pageApplies(styleOf(next), st))
|
|
109
140
|
break;
|
|
110
141
|
el = next;
|
|
111
142
|
}
|
|
112
|
-
return
|
|
143
|
+
return usedPageName(el, styleOf);
|
|
113
144
|
}
|
|
114
145
|
/**
|
|
115
146
|
* Read the document's @page geometry from the CSSOM — every sheet the document wears, adopted ones included, and the rules inside its layers and conditions (`capture.ts`'s `pageRulesOf`). Returns page and content box sizes in px.
|
|
@@ -216,7 +247,7 @@ export const combineParity = (earlier, later) => later ?? earlier;
|
|
|
216
247
|
*
|
|
217
248
|
* This used to be a loop over consecutive feed items, and a feed item is a top-level block — so a command declared on a descendant was invisible, which is every heading in every BelType document since the belt began wrapping them in sections. The law (*no forced break lies inside an interval*, C1) always required this reading; the mechanism was narrower than the law. Descending closes it at word granularity, and §3.1.1's child→parent propagation falls out as a corollary rather than as walks: a `break-before` on a first in-flow child sits at the same word position as its container's start edge, and a `break-after` on a last in-flow child at the same position as its container's end, so the descent finds both where a promotion would have put them. `forcedBreakAfter` — the third of the rule's four walks, and the only forced one ever built — is deleted for exactly that reason.
|
|
218
249
|
*
|
|
219
|
-
* What the walk refuses to enter, each for its own citation: an absolutely positioned box (§3.1's must not — it neither carries forcing nor receives it, and §4.3's name switch is forcing under another name), a float's interior (§2.1 — a forced break inside a parallel flow does not affect the content outside it), and a monolith's interior (*the line box is the atom*, I3, and §4.1 in as many words: a forced break inside monolithic content must be ignored). A float's and a monolith's own commands are still read
|
|
250
|
+
* What the walk refuses to enter, each for its own citation: an absolutely positioned box (§3.1's must not — it neither carries forcing nor receives it, and §4.3's name switch is forcing under another name), a float's interior (§2.1 — a forced break inside a parallel flow does not affect the content outside it), and a monolith's interior (*the line box is the atom*, I3, and §4.1 in as many words: a forced break inside monolithic content must be ignored). A float's and a monolith's own commands are still read: §3.1 says *"User agents should also apply these properties to floated boxes whose containing block is in the normal flow of the root fragmented element"*, so a float's forced `break-before` falls before its place in the flow and its forced `break-after` is pending into the box or run after it, never inside the float. A float the walk reaches has a containing block in that flow, since it never enters a float's or an absolutely positioned box's interior.
|
|
220
251
|
*
|
|
221
252
|
* `namesOnly` answers the composition region's version of the question. A name switch is the only forcing that changes the measure, so it is the only forcing a region must stop at: lines past a forced break are composed at the right width and merely unused.
|
|
222
253
|
*
|
|
@@ -240,41 +271,84 @@ export function forcingIndexOf(blocks, { styleOf, pageNameOf, cutOf, posOf, posO
|
|
|
240
271
|
/**
|
|
241
272
|
* @param [rowish]
|
|
242
273
|
*/
|
|
243
|
-
|
|
274
|
+
/**
|
|
275
|
+
* css-page-3 §8.1's two values ride the walk: `visit` answers a box's start and end page values where it stands among its siblings at class A break points — a box the property applies to, or an inline box standing among blocks, which is an anonymous block box named as its parent is — and null for any other. Each sibling whose start value differs from the end value before it is a name break.
|
|
276
|
+
*
|
|
277
|
+
* @param [rowish]
|
|
278
|
+
* @param parentStyle - the parent's style, null in the body where nothing reads it.
|
|
279
|
+
* @param parentName - the parent's used page name, which an anonymous block box in it takes.
|
|
280
|
+
*/
|
|
281
|
+
const visit = (el, b, sealedBy, rowish, parentStyle, parentName) => {
|
|
244
282
|
const st = styleOf(el);
|
|
245
283
|
if (isAbsolutelyPositioned(st))
|
|
246
|
-
return; // §3.1's must not: it neither carries forcing nor receives it, so it is not an event at all
|
|
284
|
+
return null; // §3.1's must not: it neither carries forcing nor receives it, so it is not an event at all
|
|
247
285
|
const parallel = isOutOfFlow(st);
|
|
248
|
-
const
|
|
286
|
+
const applies = pageApplies(st, parentStyle);
|
|
287
|
+
const anonymous = !applies && !parallel && isInlineLevel(st.display ?? '') && laysClassA(parentStyle?.display ?? 'block');
|
|
288
|
+
const used = applies ? pageNameOf(el) : parentName;
|
|
289
|
+
const e = { b, el, cut: cutOf(el, b), nameBreak: false, before: isForced(st.breakBefore), beforeParity: parityOf(st.breakBefore), after: false, afterParity: null, end: 0, pos: posOf(el, b), inFlow: !parallel, sealedBy, rowish };
|
|
249
290
|
const k = events.length;
|
|
250
291
|
events.push(e);
|
|
251
292
|
const inner = sealedBy >= 0 ? sealedBy : neverCutInside(el, st) ? k : -1;
|
|
293
|
+
let first = null;
|
|
294
|
+
let last = null;
|
|
252
295
|
if (!parallel && !monolith(el, st)) {
|
|
253
296
|
const kids = blockChildren(el);
|
|
254
|
-
|
|
297
|
+
// A run of lines among block children is an anonymous block box, or in a flex or grid container an anonymous item (CSS Flexbox §4, CSS Grid §6), which a forced break before it reaches the same way.
|
|
298
|
+
const runs = !!extentOf && (holdsAnonymousBlocks(st.display ?? '') || /^(inline-)?(flex|grid)$/.test(st.display ?? ''));
|
|
255
299
|
const row = rowish || SIDE_BY_SIDE.has(st.display ?? '');
|
|
300
|
+
const flow = laysClassA(st.display ?? '');
|
|
301
|
+
const own = pageNameOf(el);
|
|
302
|
+
// The siblings' sequence: each box's start against the end before it, a run of lines being an anonymous block box named as `el` is.
|
|
303
|
+
const meet = (at, start, end) => {
|
|
304
|
+
if (!flow)
|
|
305
|
+
return;
|
|
306
|
+
if (last !== null && last !== start)
|
|
307
|
+
events[at].nameBreak = true;
|
|
308
|
+
first ??= start;
|
|
309
|
+
last = end;
|
|
310
|
+
};
|
|
311
|
+
if (runs && kids.length > 0 && extentOf(kids[0]).before > extentOf(el).before)
|
|
312
|
+
first = last = own;
|
|
256
313
|
for (let i = 0; i < kids.length; i++) {
|
|
257
|
-
|
|
314
|
+
const at = events.length;
|
|
315
|
+
const met = visit(kids[i], b, inner, row, st, own);
|
|
316
|
+
if (met)
|
|
317
|
+
meet(at, met.start, met.end);
|
|
258
318
|
if (!runs)
|
|
259
319
|
continue;
|
|
260
320
|
// A run of lines after this child is an anonymous block box, and the box a forced `break-after` closing here is pending into: an event at its first word, in the word space of the box that holds it (*the anonymous box is in the procession*). Words standing between the child's end and the next child's start, or this box's end, are the run; none is no box.
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
const done =
|
|
321
|
+
const extent = extentOf;
|
|
322
|
+
const ext = extent(el);
|
|
323
|
+
const done = extent(kids[i]);
|
|
264
324
|
const word = done.before + done.count;
|
|
265
325
|
const next = kids[i + 1];
|
|
266
|
-
if ((next ?
|
|
326
|
+
if ((next ? extent(next).before : ext.before + ext.count) <= word)
|
|
267
327
|
continue;
|
|
268
|
-
events.push({ b, el, cut: cutOf(el, b, word -
|
|
328
|
+
events.push({ b, el, cut: cutOf(el, b, word - ext.before), nameBreak: false, before: false, beforeParity: null, after: false, afterParity: null, end: events.length + 1, pos: e.pos + word - ext.before, inFlow: true, sealedBy: inner, rowish: row, runAfter: kids[i] });
|
|
329
|
+
meet(events.length - 1, own, own);
|
|
269
330
|
}
|
|
270
331
|
}
|
|
271
|
-
e.after =
|
|
332
|
+
e.after = isForced(st.breakAfter); // a float's too (§3.1's should): pending into what follows its place in the flow
|
|
272
333
|
e.afterParity = parityOf(st.breakAfter);
|
|
273
334
|
e.end = events.length;
|
|
335
|
+
if (!applies && !anonymous)
|
|
336
|
+
return null;
|
|
337
|
+
return { start: applies ? first ?? used : used, end: applies ? last ?? used : used };
|
|
274
338
|
};
|
|
339
|
+
const root = blocks[0]?.parentElement ?? null;
|
|
340
|
+
const rootStyle = root ? styleOf(root) : null;
|
|
341
|
+
const rootName = root ? pageNameOf(root) : '';
|
|
342
|
+
let before = null;
|
|
275
343
|
for (let b = 0; b < blocks.length; b++) {
|
|
276
344
|
blockStart.push(events.length);
|
|
277
|
-
|
|
345
|
+
const at = events.length;
|
|
346
|
+
const met = visit(blocks[b], b, -1, false, rootStyle, rootName);
|
|
347
|
+
if (!met)
|
|
348
|
+
continue;
|
|
349
|
+
if (before !== null && before !== met.start)
|
|
350
|
+
events[at].nameBreak = true;
|
|
351
|
+
before = met.end;
|
|
278
352
|
}
|
|
279
353
|
const n = events.length;
|
|
280
354
|
// What the walk carries as `pending` into event k from the boxes that close just in front of it: those whose subtree ends at k, deepest first — a larger index is a deeper box — so the outermost's parity is the one that stands.
|
|
@@ -288,13 +362,13 @@ export function forcingIndexOf(blocks, { styleOf, pageNameOf, cutOf, posOf, posO
|
|
|
288
362
|
for (let k = n - 1; k >= 0; k--)
|
|
289
363
|
nextForced[k] = closing[k] || events[k].before ? k : nextForced[k + 1];
|
|
290
364
|
const nextName = new Array(n + 1).fill(n);
|
|
291
|
-
for (let k = n -
|
|
292
|
-
nextName[k] = events[k
|
|
293
|
-
const hit = (k, pending,
|
|
365
|
+
for (let k = n - 1; k >= 0; k--)
|
|
366
|
+
nextName[k] = events[k].nameBreak ? k : nextName[k + 1];
|
|
367
|
+
const hit = (k, pending, namesOnly) => {
|
|
294
368
|
const e = events[k];
|
|
295
369
|
const before = !namesOnly && e.before;
|
|
296
370
|
// §4.3's combination at the one break point where two forced values meet: the `break-after` pending into the box, and the box's own `break-before`, the later element in the flow, whose parity wins where it has one. The address is a copy, so no page can reach into the index through it.
|
|
297
|
-
return { b: e.b, namedSwitch: e.
|
|
371
|
+
return { b: e.b, namedSwitch: e.nameBreak, el: e.el, cut: { ...e.cut, path: [...e.cut.path] }, parity: combineParity(pending?.parity ?? null, before ? e.beforeParity : null), rowish: e.rowish, runAfter: e.runAfter };
|
|
298
372
|
};
|
|
299
373
|
/** The first event strictly after the cursor — a binary search, because a bare address never precedes the one before it in walk order — or `n` where there is none. */
|
|
300
374
|
const firstAfter = (cursorCut) => {
|
|
@@ -314,20 +388,24 @@ export function forcingIndexOf(blocks, { styleOf, pageNameOf, cutOf, posOf, posO
|
|
|
314
388
|
};
|
|
315
389
|
return {
|
|
316
390
|
size: n,
|
|
317
|
-
/**
|
|
318
|
-
|
|
391
|
+
/**
|
|
392
|
+
* The first forcing event strictly after `cursorCut`, or null.
|
|
393
|
+
*
|
|
394
|
+
* @param _name - the page's name, which a host still passes; a name break is the index's own, the end page value before a class A break point against the start page value after it (css-page-3 §8.1), and no longer a box's name against the page's.
|
|
395
|
+
*/
|
|
396
|
+
find(cursorCut, _name, { namesOnly = false } = {}) {
|
|
319
397
|
const s = firstAfter(cursorCut);
|
|
320
398
|
if (s >= n)
|
|
321
399
|
return null;
|
|
322
400
|
// Pending into s: the forced break-afters whose subtree closes exactly in front of it — never one that closed earlier, which was spent where it fell (*a box is not its first child*).
|
|
323
401
|
const pending = namesOnly ? null : closing[s];
|
|
324
|
-
if (pending || events[s].
|
|
325
|
-
return hit(s, pending,
|
|
402
|
+
if (pending || events[s].nameBreak || (!namesOnly && events[s].before))
|
|
403
|
+
return hit(s, pending, namesOnly);
|
|
326
404
|
const k = s + 1;
|
|
327
405
|
if (k >= n)
|
|
328
406
|
return null;
|
|
329
|
-
const f = Math.min(
|
|
330
|
-
return f < n ? hit(f, namesOnly ? null : closing[f],
|
|
407
|
+
const f = Math.min(nextName[k], namesOnly ? n : nextForced[k]);
|
|
408
|
+
return f < n ? hit(f, namesOnly ? null : closing[f], namesOnly) : null;
|
|
331
409
|
},
|
|
332
410
|
/**
|
|
333
411
|
* The Cast-off's place (*the Cast-off bounds the feed*, 2026-09-26): the first block boundary strictly after `cursorCut` whose word position is at least `pos`, where a feed may be cut without moving any box before it — or null, where no such place is left and the feed is the whole remainder.
|
|
@@ -531,6 +609,8 @@ export function paginate(doc, opts = {}) {
|
|
|
531
609
|
const boxes = opts.originals ?? topLevelBoxes(doc);
|
|
532
610
|
const hasMarks = answerHas(doc, boxes, keep);
|
|
533
611
|
performance.measure('cicero:has-words', { start: t0, end: performance.now(), detail: { boxes: boxes.length, marked: hasMarks.size, ...(memo ? { memo: memo.tally.has ?? null } : {}) } });
|
|
612
|
+
// Before the reads' counter walk, whose log the run takes: the reason is paginateOnce's, where the call stays for a caller that runs once.
|
|
613
|
+
registerGcpmProperties(doc.defaultView);
|
|
534
614
|
return settleReads(doc, { run: (laid) => paginateOnce(doc, opts, laid, { boxes, hasMarks, memo }), blocksOf: () => opts.blocks ?? topLevelBlocks(doc), originals: opts.originals ?? opts.blocks ?? null, warm: opts.prior && opts.dirtyAt ? opts.prior.delivered : null, warmFacts: opts.prior && opts.dirtyAt ? opts.prior.facts ?? null : null, hide: !!opts.pace, keep });
|
|
535
615
|
}
|
|
536
616
|
/**
|
|
@@ -577,6 +657,8 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
577
657
|
await awaitFonts(doc, table.furnitureFaces); // the faces page furniture names, requested now and not when the first margin box is presented (the table is read after the first wait, so its lengths are measured as they always were)
|
|
578
658
|
const win = doc.defaultView;
|
|
579
659
|
const blocks = intakeBlocks ?? topLevelBlocks(doc);
|
|
660
|
+
// What is out of scope is refused here, and a multi-column box too tall for a page becomes one column on its copies (`src/scope.ts`).
|
|
661
|
+
foldTallColumns(doc, readScope(win, blocks), table.pageFor({ first: true }));
|
|
580
662
|
// The running elements and where each was written, handed back with the blocks by a host that took them at intake, for the same reason: by now the body holds the root and nothing else.
|
|
581
663
|
const running = intakeRunning ?? runningBoxes(doc, memo?.keep);
|
|
582
664
|
/** Whether a box outside an interval may be hollow (*the clone is hollow outside the interval*, 2026-09-27; *one attribute has words*, 2026-10-10). It may, unless a stylesheet the document wears has a `:has(` that nothing answers for: the host rewrites every `:has()` it can into a union that a `has:` word on the copy satisfies (`has-words.js`), and only an argument reading user state (`:hover`, `:checked`, `:focus-within`, `:target`, …) is left alone, with a console line from the rewrite naming the rule. Then every feed is built deep, as before, and the console says so once per run. `root.js` asks the same question of the same sheets when it presents. */
|
|
@@ -643,11 +725,14 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
643
725
|
*
|
|
644
726
|
* The ancestors are `html` and `body` — every top-level block is a body child — and they are recorded before the document's first word, which is what puts a `body { counter-reset }` in force on page one.
|
|
645
727
|
*/
|
|
646
|
-
const
|
|
728
|
+
const ancestors = [doc.documentElement, doc.body].filter(Boolean);
|
|
729
|
+
const runningAt = running.map((r) => ({ name: r.name, pos: runningPosition(r, index) }));
|
|
730
|
+
// The reads' walk, where they took one, is this walk under their marks: a mark ends what an operation at its depth would end, and adds nothing else (`reads.ts`' `settleReads`).
|
|
731
|
+
const documentOps = laid.ops ? withRunning(laid.ops, runningAt) : documentOpsOf(blocks, index, {
|
|
647
732
|
styleOf: (el) => win.getComputedStyle(el),
|
|
648
733
|
pseudoStyleOf: sheetsCountInPseudos(doc) ? (el, pseudo) => win.getComputedStyle(el, pseudo) : null,
|
|
649
|
-
ancestors
|
|
650
|
-
running:
|
|
734
|
+
ancestors,
|
|
735
|
+
running: runningAt,
|
|
651
736
|
keep: memo?.keep
|
|
652
737
|
});
|
|
653
738
|
// The same walk's second reading (*the host rewrites what the parser drops*, 2026-09-14): every named-string assignment the document makes, with the position it sits at. Evaluated here, where the source is still in its real ancestor chain — `content()` is the element's own text and would come back right from an unassigned slottable, but `counter()` inside a content list would not, and neither would anything inherited.
|
|
@@ -672,7 +757,7 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
672
757
|
: forcingIndexOf(blocks, { styleOf: (el) => win.getComputedStyle(el), pageNameOf: effectivePageName, cutOf: (el, b, word = null) => cutTo(el, b, { word }), posOf: (el, b) => index.posOf(b, index.wordsBefore(el)), posOfCut, extentOf });
|
|
673
758
|
performance.measure('cicero:forcing-index', { start: t, end: (t = performance.now()), detail: { events: forcing.size, ...(memo ? { memoHits: memo.hits, memoMisses: memo.misses } : {}) } });
|
|
674
759
|
/**
|
|
675
|
-
* The
|
|
760
|
+
* The page name at an address: the start page value of the box that begins there (css-page-3 §8.1, `pageNameDownFrom`), or, where the page resumes inside a box, that box's used page name. A page may begin inside a block whose interior named a different page, so the cursor's block is not the thing to ask.
|
|
676
761
|
*
|
|
677
762
|
* A bare path resumes at a box whole, and a first in-flow block child begins exactly where its parent does (§3.1.1's own coincidence), so the deepest box that starts the page is the end of the first-child chain — which is where the old search for *the box containing word w* arrived, without the ambiguity a wordless first child gave it. A word or a pixel extent names a box the page resumes inside, and that box is itself the deepest; where the old search read the box after an end gap, it was naming the wrong side of a cut that resumes in the gap.
|
|
678
763
|
*/
|
|
@@ -681,9 +766,10 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
681
766
|
if (!el)
|
|
682
767
|
return '';
|
|
683
768
|
// A line before a child is the page's first content, and it is the container's (*the anonymous box is in the procession*).
|
|
769
|
+
const styleOf = (c) => win.getComputedStyle(c);
|
|
684
770
|
if (cut.word == null && cut.px == null)
|
|
685
|
-
return pageNameDownFrom(el, { styleOf
|
|
686
|
-
return
|
|
771
|
+
return pageNameDownFrom(el, { styleOf, runBefore: (parent, child) => index.wordsBefore(child) !== index.wordsBefore(parent) });
|
|
772
|
+
return usedPageName(el, styleOf);
|
|
687
773
|
};
|
|
688
774
|
/**
|
|
689
775
|
* The document's own first page — css-page-3 §3.3, and it is a parity offset rather than a blank entry.
|
|
@@ -787,6 +873,13 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
787
873
|
pageCounters.set(key, ops = table.pageFor({ name: c.name, first: i === 0, parity: c.parity, blank: c.blank }).pageCounter);
|
|
788
874
|
return ops;
|
|
789
875
|
};
|
|
876
|
+
/**
|
|
877
|
+
* The depth in the counter log's terms of the place a cut names: the element its path reaches, below the ancestors, or one level further in where the cut falls inside it, among its children (a `word` or a `px`).
|
|
878
|
+
*
|
|
879
|
+
* @remarks
|
|
880
|
+
* A place inside an element is taken at its children's depth, so an instance a grandchild opened before the cut is ended there even where the cut falls inside the child that holds it: a counter reset on an inline element nested in another inside a paragraph a page divides. No case claims it.
|
|
881
|
+
*/
|
|
882
|
+
const depthOfCut = (cut) => ancestors.length + cut.path.length - 1 + (cut.word != null || cut.px != null ? 1 : 0);
|
|
790
883
|
/** The conditions of page `pageIndex`, which begins at `cut` (projected to `pos`) — {@link conditionsFor} with the cursor made a parameter, so a resumed run can write the conditions of a page it keeps from the address that page began at (*the run resumes from an entry*, build two, 2026-10-08). */
|
|
791
884
|
const conditionsAt = (pageIndex, cut, pos, { blank = false, carried = null } = {}) => {
|
|
792
885
|
const name = nameAt(cut);
|
|
@@ -796,7 +889,7 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
796
889
|
// The run's count, an author's reset of the page counter counted on from the page it begins on, and each page's own `@page` counter rules (`document-state.js`'s `pageNumberAt`). Entry `i` closes page `i`, so page `i` begins where entry `i - 1` ended, under the conditions it closes.
|
|
797
890
|
const pageNumber = pageNumberAt(documentOps, pageIndex, pos, { pages: (i) => ({ start: i === 0 ? 0 : account[i - 1].pos ?? 0, blank: account[i].blank === true, counter: pageCounterOf(i, account[i].closes) }), blank, counter: geo.pageCounter, away });
|
|
798
891
|
// The document's counters as they stand at this page's first word — strictly before it, which is §6.1's *spans the page break* in the flat position space (`document-state.js`) — less what the flows carried onto this page have still to show, which stands before that word and is on this page or after it (beltype#43). The page's own two are not among them: they are the host's, and `pageNumber` carries the reset.
|
|
799
|
-
const counters = withoutPageCounters(counterStateAt(documentOps, pos, away));
|
|
892
|
+
const counters = withoutPageCounters(counterStateAt(documentOps, pos, away, depthOfCut(cut)));
|
|
800
893
|
return Object.freeze({ ...geo, name, pageNumber, parity, blank, counters, dpr: win.devicePixelRatio });
|
|
801
894
|
};
|
|
802
895
|
/**
|
|
@@ -1139,6 +1232,7 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
1139
1232
|
const conditions = conditionsFor(account.length);
|
|
1140
1233
|
const recreated = ctx === null || conditions.contentW !== ctxWidth;
|
|
1141
1234
|
if (recreated) {
|
|
1235
|
+
ctx?.dispose(); // each context seats a container of its own (since 2026-11-06), and one left standing is laid out in the document and printed
|
|
1142
1236
|
ctx = createMeasureContext(doc, conditions.contentW);
|
|
1143
1237
|
ctxWidth = conditions.contentW;
|
|
1144
1238
|
}
|
|
@@ -1218,10 +1312,15 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
1218
1312
|
let container;
|
|
1219
1313
|
let pageStart;
|
|
1220
1314
|
let spentLead;
|
|
1315
|
+
/** The place the last pour stood at when the check failed: a doubled bound that names it again is the same feed. */
|
|
1316
|
+
let short = null;
|
|
1221
1317
|
for (;;) {
|
|
1222
1318
|
pours++;
|
|
1223
1319
|
// The place first, then the feed cloned to it (*the clone is hollow outside the interval*, 2026-09-27): neither the Cast-off's place nor the forced event needs anything but the cursor and the index.
|
|
1224
|
-
|
|
1320
|
+
let place = castOff.bound(cursor, words);
|
|
1321
|
+
// A doubled bound whose place is the one the last pour stopped above the edge at, its words landing inside the same long paragraph or the same box the bound never cuts inside, would pour the same feed again: it is doubled on without pouring (critic2-long-paragraph-quadratic, 2026-11-09).
|
|
1322
|
+
while (place && place.el === short)
|
|
1323
|
+
place = castOff.bound(cursor, (words = words * 2));
|
|
1225
1324
|
const forcingCuts = !!forcedEvent && !rowForced && (!place || !cutAfter(forcedEvent.cut, place.cut, posOfCut));
|
|
1226
1325
|
bounded = !forcingCuts && !!place;
|
|
1227
1326
|
({ items, cloneOfSource } = buildFeed(forcingCuts ? forcedEvent : bounded ? place : null, forcingCuts ? 'the grab' : 'the Cast-off'));
|
|
@@ -1240,7 +1339,9 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
1240
1339
|
pageStart = (truncateLead ? report.contentTop : 0) + spentLead;
|
|
1241
1340
|
if (rowForced) {
|
|
1242
1341
|
/** A forced break inside a box that lays its children side by side is not the grab's to cut at (*a parallel flow is a fragmentainer*, 2026-10-08; *a forced break inside a cell*, P37c): the cut falls in the item's flow and only the row's height crosses the wall, so the feed is not cut at the event — a word position cannot say a cell's place, and cutting there put away the neighbour cell with everything after the paragraph in document order. Whether the event is inside a row item is a fact of the layout, asked here of the report: the box stands inside an item of a row that is not itself inside another item's, and after a sibling in the item's procession (a break before an item's first block is the item's own start). Where it is not, the grab cuts as it always did. An event past the feed the Cast-off bounded is not this page's, and a longer pour may reach it. */
|
|
1243
|
-
|
|
1342
|
+
// A run has no box: its event is asked through the block child it follows, after which it stands, so it is never its item's start.
|
|
1343
|
+
const ev = forcedEvent;
|
|
1344
|
+
const clone = cloneOfSource.get(ev.runAfter ?? ev.el);
|
|
1244
1345
|
const at = clone ? report.boxOf(clone) : null;
|
|
1245
1346
|
rowEventIn = !!clone;
|
|
1246
1347
|
if (clone) {
|
|
@@ -1254,7 +1355,7 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
1254
1355
|
item = a;
|
|
1255
1356
|
}
|
|
1256
1357
|
const sequence = at ? report.processionOf(at) : [];
|
|
1257
|
-
if (!at || !item || nested || sequence.indexOf(at) < 1 || at.rect.top <= item.rect.top + 0.25) {
|
|
1358
|
+
if (!at || !item || nested || (!ev.runAfter && (sequence.indexOf(at) < 1 || at.rect.top <= item.rect.top + 0.25))) {
|
|
1258
1359
|
rowForced = false; // not a row's: the grab's, as before
|
|
1259
1360
|
continue;
|
|
1260
1361
|
}
|
|
@@ -1262,6 +1363,7 @@ async function paginateOnce(doc, { composed = false, compositionCache = new Comp
|
|
|
1262
1363
|
}
|
|
1263
1364
|
if (!bounded || reachesBelowTheEdge({ report, sourceOf, pageStart, contentHeight: conditions.contentH }))
|
|
1264
1365
|
break;
|
|
1366
|
+
short = place.el;
|
|
1265
1367
|
// @ts-expect-error `words` is a number wherever the feed is bounded, and a feed that is not bounded broke out above
|
|
1266
1368
|
words *= 2;
|
|
1267
1369
|
}
|
package/dist/src/fragments.js
CHANGED
|
@@ -762,6 +762,10 @@ export function mutateToContinuation(clone, { cut = null, remainderPx = null, so
|
|
|
762
762
|
const mine = itemsOf(flows ?? flowsOf(items), path, source, cloneOf);
|
|
763
763
|
// The items of a divided row stand before the place this cut resumes at, and are not spent: each has a place of its own, applied below.
|
|
764
764
|
const keep = new Set(mine.map((m) => m.itemClone));
|
|
765
|
+
// A row a spanning cell's group reaches stands before the place too (the Gauge's rows: a `rowspan` makes the cells of every row it spans one row), and every cell in it is an item with a place of its own, so the row stays and its cells are cut below.
|
|
766
|
+
for (const m of mine)
|
|
767
|
+
if (m.itemClone.parentElement && m.itemClone.parentElement !== clone && /^TR$/i.test(m.itemClone.parentElement.tagName))
|
|
768
|
+
keep.add(m.itemClone.parentElement);
|
|
765
769
|
if (divides(cut)) {
|
|
766
770
|
const target = targetOf(cut, source, cloneOf);
|
|
767
771
|
if (!target)
|
|
@@ -914,6 +918,9 @@ function shellAt(clone, target, px, keep = new Set(), hollow = false) {
|
|
|
914
918
|
el.style.boxSizing = 'border-box';
|
|
915
919
|
el.style.height = `${Math.max(0, px.remaining)}px`;
|
|
916
920
|
el.style.minHeight = '0';
|
|
921
|
+
// Its block-end padding is spent with the rest (`block-end-padding-taller-than-page`, assessment 2026-11-07): the shell has no content, so its padding paints exactly as the content box it gives way to, and the border box is the remainder alone. A padding taller than the page area otherwise held every shell at its own height, the remainder never shrank, and the walk refused. A box that clones its decoration wears its padding on every fragment, so it keeps it.
|
|
922
|
+
if (!clonesDecoration(el))
|
|
923
|
+
el.style.setProperty('padding-bottom', '0', 'important');
|
|
917
924
|
return clone;
|
|
918
925
|
}
|
|
919
926
|
// The painted shell: a slice wrapper takes the box's place in flow at exactly the leftover height and clips there; the box keeps its natural size — an image must never be resized into the remainder — and a negative top margin pulls the already-shown part above the clip.
|
package/dist/src/gauge.js
CHANGED
|
@@ -193,6 +193,28 @@ export function takeReport(container, { contentHeight }) {
|
|
|
193
193
|
for (const item of row)
|
|
194
194
|
rowOfBox.set(item, row);
|
|
195
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* A cell spanning rows (`rowspan`) stands beside the cells of every row it spans, so the cells of those rows are one row of parallel flows, in document order: a cut through the second spanned row divides the spanning cell and that row's cells, each at its own verdict, and leaves the rows above it spent and the rows below it whole, as Chromium prints it (CSS 2.1 §17.5.3; css-break-3 §2.1; the essay's case 92, *the cell that spans the cut*). A span stays inside its row group, as §17.2 says.
|
|
198
|
+
*/
|
|
199
|
+
for (const box of boxes) {
|
|
200
|
+
const trs = box.blockChildren.filter((c) => !c.anonymous && win.getComputedStyle(c.el).display === 'table-row');
|
|
201
|
+
for (let i = 0; i < trs.length; i++) {
|
|
202
|
+
let end = i;
|
|
203
|
+
for (let j = i; j <= end; j++) {
|
|
204
|
+
for (const cell of trs[j].blockChildren) {
|
|
205
|
+
const span = cell.el.rowSpan ?? 1;
|
|
206
|
+
end = Math.min(trs.length - 1, Math.max(end, span === 0 ? trs.length - 1 : j + span - 1));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (end === i)
|
|
210
|
+
continue;
|
|
211
|
+
const row = trs.slice(i, end + 1).flatMap((tr) => tr.blockChildren.filter((c) => !['absolute', 'fixed'].includes(win.getComputedStyle(c.el).position)));
|
|
212
|
+
if (row.length > 1)
|
|
213
|
+
for (const item of row)
|
|
214
|
+
rowOfBox.set(item, row);
|
|
215
|
+
i = end;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
196
218
|
const topLevel = boxes.filter((b) => !b.parent);
|
|
197
219
|
const contentTop = topLevel.length ? topLevel[0].rect.top : 0;
|
|
198
220
|
let flowBottom = contentTop;
|
package/dist/src/measure.d.ts
CHANGED
|
@@ -14,7 +14,9 @@ export type MeasureContext = ReturnType<typeof createMeasureContext>;
|
|
|
14
14
|
*
|
|
15
15
|
* A vertical or sideways root is refused by name too (Codeberg issue 12): every read in this engine is horizontal, the lines by vertical overlap, the cut by a y, the page area by its height, so under `writing-mode: sideways-lr` on the root the text of a positioned paragraph fell below the clip and was never resumed, and the printer's tiling invariant refused what the engine should have. Vertical writing is outside the beta, and a refusal that says so at intake is cheaper than one three tools later.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* A right-to-left root or body (`direction: rtl`) is refused by name as well, as out of scope (SPEC II.1, item 6); a vertical box below the root is refused by `readScope` (`src/scope.ts`).
|
|
18
|
+
*
|
|
19
|
+
* @throws Error — bare and deliberate (`src/` may not import BelType's error types): a device pixel ratio other than 1 without `relaxDpr`, a document root whose line-height is not explicit (*render-identical conditions*, I5), a document root whose writing mode is not `horizontal-tb`, or a root or body that computes `direction: rtl`.
|
|
18
20
|
*/
|
|
19
21
|
export declare function assertRenderConditions(doc: Document, { relaxDpr }?: {
|
|
20
22
|
relaxDpr?: boolean;
|
package/dist/src/measure.js
CHANGED
|
@@ -17,7 +17,9 @@ const NEUTRALIZE_ID = '__beltype-neutralize';
|
|
|
17
17
|
*
|
|
18
18
|
* A vertical or sideways root is refused by name too (Codeberg issue 12): every read in this engine is horizontal, the lines by vertical overlap, the cut by a y, the page area by its height, so under `writing-mode: sideways-lr` on the root the text of a positioned paragraph fell below the clip and was never resumed, and the printer's tiling invariant refused what the engine should have. Vertical writing is outside the beta, and a refusal that says so at intake is cheaper than one three tools later.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
20
|
+
* A right-to-left root or body (`direction: rtl`) is refused by name as well, as out of scope (SPEC II.1, item 6); a vertical box below the root is refused by `readScope` (`src/scope.ts`).
|
|
21
|
+
*
|
|
22
|
+
* @throws Error — bare and deliberate (`src/` may not import BelType's error types): a device pixel ratio other than 1 without `relaxDpr`, a document root whose line-height is not explicit (*render-identical conditions*, I5), a document root whose writing mode is not `horizontal-tb`, or a root or body that computes `direction: rtl`.
|
|
21
23
|
*/
|
|
22
24
|
export function assertRenderConditions(doc, { relaxDpr = false } = {}) {
|
|
23
25
|
const win = doc.defaultView;
|
|
@@ -29,6 +31,10 @@ export function assertRenderConditions(doc, { relaxDpr = false } = {}) {
|
|
|
29
31
|
if (!/^-?[\d.]+px$/.test(lh)) {
|
|
30
32
|
throw new Error(`I5 (DPR ruling): explicit line-height required, document root computes '${lh}'`);
|
|
31
33
|
}
|
|
34
|
+
const rtl = [root, doc.body ? win.getComputedStyle(doc.body) : null].find((st) => st?.direction === 'rtl');
|
|
35
|
+
if (rtl) {
|
|
36
|
+
throw new Error(`right-to-left text is outside this engine's scope: the document computes direction 'rtl' on its ${rtl === root ? 'root' : 'body'}, and every read here is left to right (the line's start edge, the hanging of a margin, the page's left and right). Write the document ltr, or print it with another engine.`);
|
|
37
|
+
}
|
|
32
38
|
if (root.writingMode && root.writingMode !== 'horizontal-tb') {
|
|
33
39
|
throw new Error(`a vertical or sideways root is outside this engine's beta: the document root computes writing-mode '${root.writingMode}', and every read here is horizontal (the line by vertical overlap, the cut by a y). Write the document horizontal-tb, or print it with another engine.`);
|
|
34
40
|
}
|
|
@@ -114,12 +120,32 @@ export async function awaitCloneImages(container) {
|
|
|
114
120
|
unsettled = true;
|
|
115
121
|
break;
|
|
116
122
|
}
|
|
117
|
-
|
|
123
|
+
const objects = objectsFetching(container);
|
|
124
|
+
if (!unsettled && !objects.length)
|
|
118
125
|
return false;
|
|
119
|
-
await awaitImages(container);
|
|
126
|
+
await Promise.all([unsettled ? awaitImages(container) : null, ...objects]);
|
|
120
127
|
void container.offsetHeight;
|
|
121
128
|
return true;
|
|
122
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* The clones' `<object>` elements that fetch their `data`, each as a promise of its `load` or `error`. A copy of an `<object>` fetches its data again and is 0 by 0 until the fetch answers, and only then does it show its resource or its fallback content (measured 2026-11-09: a copy of an object whose data fails measured 0 px in the turn it was mounted, 150 px at its `error` and 700 px, its fallback's height, one task later). Measured early, an object whose fallback is taller than the page was cut as nothing and presented clipped on one page (`cicero-object-fallback-throws`, assessment 2026-11-07). An object has no `complete`, so every fetching copy is awaited, and one that never answers is given up after two seconds rather than holding the run.
|
|
132
|
+
*/
|
|
133
|
+
function objectsFetching(container) {
|
|
134
|
+
const win = container.ownerDocument.defaultView;
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const o of container.querySelectorAll('object[data]')) {
|
|
137
|
+
if (o.getAttribute('is') || win.getComputedStyle(o).display === 'none')
|
|
138
|
+
continue; // a presenter's element and a hidden one are measured by nobody's fetch
|
|
139
|
+
out.push(new Promise((resolve) => {
|
|
140
|
+
// The fallback is laid out a task after `error` fires (measured: 150 px, the default object size, at the event; 700 px a `setTimeout(0)` later), so the promise settles one task after the event.
|
|
141
|
+
const later = () => setTimeout(resolve, 0);
|
|
142
|
+
o.addEventListener('load', later, { once: true });
|
|
143
|
+
o.addEventListener('error', later, { once: true });
|
|
144
|
+
setTimeout(resolve, 2000); // ponytail: a fixed ceiling for a fetch that never answers; the rim hears nothing of an object, so a later answer is not re-cut
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
123
149
|
/** The declarations {@link publishOn} last wrote on a container, so the next call takes them off. */
|
|
124
150
|
const PUBLISHED = new WeakMap();
|
|
125
151
|
/** Write a page's published values on the measure container, and take the previous page's off. */
|
package/dist/src/reads.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @packageDocumentation
|
|
13
13
|
*/
|
|
14
|
+
import { documentOpsOf } from './document-state.ts';
|
|
14
15
|
import { indexBlocks } from './positions.ts';
|
|
15
16
|
import type { ReadFn } from './gcpm-encoding.ts';
|
|
16
17
|
import type { PagePublish } from './measure.ts';
|
|
@@ -47,6 +48,8 @@ export type Laid = {
|
|
|
47
48
|
sites: Site[] | null;
|
|
48
49
|
/** the position index the loop already built. */
|
|
49
50
|
index: ReturnType<typeof indexBlocks> | null;
|
|
51
|
+
/** the counter log the loop already walked, under the marks of its counter references and without running elements, or null where it walked none. */
|
|
52
|
+
ops?: ReturnType<typeof documentOpsOf> | null;
|
|
50
53
|
/** the references whose page number moved a line of their own in the layout before. */
|
|
51
54
|
because?: string[];
|
|
52
55
|
/** on a layout after the first: the layout before it, as the run returned it, and the addresses of the boxes whose lines the delivered values moved, which the run resumes from as from an edit (*delivery resumes from the moved page*, 2026-10-21). */
|
|
@@ -132,8 +135,8 @@ export declare function pageFactsOf(account: Array<{
|
|
|
132
135
|
* @returns null where the document declares no read, which is every document but a few.
|
|
133
136
|
*/
|
|
134
137
|
export declare function findSites(doc: Document, originals?: Element[] | null, keep?: Keep | null): Site[] | null;
|
|
135
|
-
/** Deliver a table of values: one constructed sheet, adopted once, its text replaced. */
|
|
136
|
-
export declare function deliver(doc: Document, values: Values): void;
|
|
138
|
+
/** Deliver a table of values: one constructed sheet per scope, adopted once, its text replaced. The rules still reach every element of `doc` that matches, scope or none: the scope keeps the sheets apart, not the elements. */
|
|
139
|
+
export declare function deliver(doc: Document, values: Values, scope?: object): void;
|
|
137
140
|
/** Is this adopted sheet one of the engine's own — the delivery sheet (the values a run delivered, rewritten every run) or the sheet that hides a reservation until its true number is delivered? */
|
|
138
141
|
export declare const isDeliverySheet: (doc: Document, sheet: CSSStyleSheet) => boolean;
|
|
139
142
|
/**
|
|
@@ -149,13 +152,14 @@ export declare function hideSheetText(sites: Pick<Site, 'key' | 'pseudos'>[], sc
|
|
|
149
152
|
/**
|
|
150
153
|
* Paint the reservations invisible: the page numbers the cuts so far do not know yet (*the pages are cut as far as they are asked*, Egildo's ruling 1 of 2026-10-12, on the note's recommendation). A document shown before it is complete must not show `0` where a page number will stand.
|
|
151
154
|
*
|
|
152
|
-
* @param
|
|
155
|
+
* @param selector - as {@link hideSheetText}'s `scope`.
|
|
156
|
+
* @param scope - the host's token for this view, as {@link deliver}'s.
|
|
153
157
|
*/
|
|
154
|
-
export declare function hideReservations(doc: Document, sites: Pick<Site, 'key' | 'pseudos'>[],
|
|
158
|
+
export declare function hideReservations(doc: Document, sites: Pick<Site, 'key' | 'pseudos'>[], selector?: string | undefined, scope?: object): void;
|
|
155
159
|
/** Take the hiding sheet off: the true numbers are delivered, or there is nothing left to hide. */
|
|
156
|
-
export declare function showReservations(doc: Document): void;
|
|
160
|
+
export declare function showReservations(doc: Document, scope?: object): void;
|
|
157
161
|
/** Take the delivery sheet off the document, where one was adopted — a second paginate of a document that no longer declares a read leaves nothing behind. */
|
|
158
|
-
export declare function withdraw(doc: Document): void;
|
|
162
|
+
export declare function withdraw(doc: Document, scope?: object): void;
|
|
159
163
|
/**
|
|
160
164
|
* The written and cascade facts, before any run: every `target-text()`, and every `target-counter()` and `target-counters()` of a counter other than `page`.
|
|
161
165
|
*
|
|
@@ -170,6 +174,7 @@ export declare function staticValues(sites: Site[], { doc, blocks, index, keep }
|
|
|
170
174
|
}): {
|
|
171
175
|
values: Values;
|
|
172
176
|
unread: Site[];
|
|
177
|
+
ops: ReturnType<typeof documentOpsOf> | null;
|
|
173
178
|
};
|
|
174
179
|
/**
|
|
175
180
|
* The layout facts, after a run: the page each page read's target lands on, as the ledger assigned it — the page whose interval holds the target's first word, over the intervals `root.pageOf` reads (`positions.js`'s `pageContaining`).
|
|
@@ -226,7 +231,7 @@ export declare function linesOfSites<S extends {
|
|
|
226
231
|
*
|
|
227
232
|
* **A layout sent round again resumes from the one before it** (*delivery resumes from the moved page*, 2026-10-21). A delivered value that moves a line is an edit at its site's page, so `run` is handed `resume`, the layout just checked and the addresses of the boxes whose lines moved, and the driver restarts one page before the first of them, re-cuts, and stops where it converges past the last, exactly as it resumes from a host's edit. The result's `resumed` and `why` stay the call's: two resumes compose into one from the prior the host handed (`resume.js`' `composeResumed`), a whole layout anywhere in the loop makes `resumed` null, and each resumed layout leaves a `cicero:reads-run` measure naming the pages it cut again.
|
|
228
233
|
*
|
|
229
|
-
* @param how - `run` lays the document out once, whole or resumed (`because`: the references whose page number moved a line of their own in the run before, when this is not the first; `facts`: the page values a flow site reads, per page, as the run before decided them, or null for the reservation; it returns `laidFacts` beside `account` when the page values it was laid out under are not the ones it was handed); `blocksOf` names the top-level blocks; `originals` the intake's top-level boxes, where the document has been presented (see {@link findSites}); `warm` and `warmFacts` the tables the previous run's layout was cut under, for a run that resumes; `hide` that pages are shown before the run is complete (*the pages are cut as far as they are asked*), so the reservations still standing are painted invisible until the true numbers are delivered, under `<beltype-root>` for `true` and under the selector a string names for a host that is not the root; `keep` the memo's door, through which the site search, the position index and the counter walk take the parts of the boxes no edit replaced (*a resumed run keeps its reads*, 2026-10-22). A `run` that answers `complete: false` laid out only part of the document (a view's chain that stopped short): the loop ends there, without checking or delivering page values, so the reservations stay hidden and the values it returns are the ones that layout was cut under; the driver's own run is always complete.
|
|
234
|
+
* @param how - `run` lays the document out once, whole or resumed (`because`: the references whose page number moved a line of their own in the run before, when this is not the first; `facts`: the page values a flow site reads, per page, as the run before decided them, or null for the reservation; it returns `laidFacts` beside `account` when the page values it was laid out under are not the ones it was handed); `blocksOf` names the top-level blocks; `originals` the intake's top-level boxes, where the document has been presented (see {@link findSites}); `warm` and `warmFacts` the tables the previous run's layout was cut under, for a run that resumes; `hide` that pages are shown before the run is complete (*the pages are cut as far as they are asked*), so the reservations still standing are painted invisible until the true numbers are delivered, under `<beltype-root>` for `true` and under the selector a string names for a host that is not the root; `scope` an object the host holds for this view (the document where absent), which keeps this view's delivery and hiding sheets apart from another view's in the same document; `keep` the memo's door, through which the site search, the position index and the counter walk take the parts of the boxes no edit replaced (*a resumed run keeps its reads*, 2026-10-22). A `run` that answers `complete: false` laid out only part of the document (a view's chain that stopped short): the loop ends there, without checking or delivering page values, so the reservations stay hidden and the values it returns are the ones that layout was cut under; the driver's own run is always complete.
|
|
230
235
|
*/
|
|
231
236
|
export declare function settleReads<R extends {
|
|
232
237
|
account: any[];
|
|
@@ -235,7 +240,7 @@ export declare function settleReads<R extends {
|
|
|
235
240
|
why?: string | null;
|
|
236
241
|
laidFacts?: PagePublish[] | null;
|
|
237
242
|
complete?: boolean;
|
|
238
|
-
}>(doc: Document, { run, blocksOf, originals, warm, warmFacts, hide, keep }: {
|
|
243
|
+
}>(doc: Document, { run, blocksOf, originals, warm, warmFacts, hide, keep, scope }: {
|
|
239
244
|
run: (laid: Laid) => Promise<R>;
|
|
240
245
|
blocksOf: () => Element[];
|
|
241
246
|
originals?: Element[] | null;
|
|
@@ -243,6 +248,7 @@ export declare function settleReads<R extends {
|
|
|
243
248
|
warmFacts?: PagePublish[] | null;
|
|
244
249
|
hide?: boolean | string;
|
|
245
250
|
keep?: Keep | null;
|
|
251
|
+
scope?: object;
|
|
246
252
|
}): Promise<R & {
|
|
247
253
|
resumed: Resumed | null;
|
|
248
254
|
why: string | null;
|