@beltype/cicero 0.1.10 → 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 +23 -5
- package/dist/seam/index.js +1 -1
- package/dist/seam/rewrite.js +7 -1
- package/dist/seam/rim.d.ts +2 -1
- package/dist/seam/rim.js +3 -1
- package/dist/src/capture.js +7 -20
- package/dist/src/compose-dom.d.ts +8 -0
- package/dist/src/compose-dom.js +65 -9
- package/dist/src/data-beltype.d.ts +8 -0
- package/dist/src/data-beltype.js +18 -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/fragmentation.d.ts +3 -3
- package/dist/src/fragmentation.js +3 -3
- package/dist/src/fragments.js +31 -6
- package/dist/src/gauge.js +22 -0
- package/dist/src/gcpm-encoding.d.ts +2 -0
- package/dist/src/gcpm-encoding.js +2 -0
- package/dist/src/measure.d.ts +3 -1
- package/dist/src/measure.js +29 -3
- package/dist/src/reads.d.ts +17 -9
- package/dist/src/reads.js +51 -32
- package/dist/src/root.js +2 -2
- 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/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;
|
|
@@ -23,6 +23,8 @@ export type ContentFn = {
|
|
|
23
23
|
export declare const STRING_SET_PROP = "--beltype-string-set";
|
|
24
24
|
/** `position: running(head)` survives as this custom property, holding the identifier alone. */
|
|
25
25
|
export declare const RUNNING_PROP = "--beltype-running";
|
|
26
|
+
/** `hyphenate-limit-lines` survives as this custom property, its value text carried verbatim (Chromium drops the property, so the Compositor reads the author's limit from here). It inherits, as the original does, and so is not among {@link CUSTOM_PROPS}. */
|
|
27
|
+
export declare const HYPHENATE_LIMIT_LINES_PROP = "--beltype-hyphenate-limit-lines";
|
|
26
28
|
/** Both, for a host that registers them. `syntax: '*'` because the values are the author's own text and this engine is not their grammar; `inherits: false` because neither original inherits. */
|
|
27
29
|
export declare const CUSTOM_PROPS: string[];
|
|
28
30
|
/**
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
export const STRING_SET_PROP = '--beltype-string-set';
|
|
14
14
|
/** `position: running(head)` survives as this custom property, holding the identifier alone. */
|
|
15
15
|
export const RUNNING_PROP = '--beltype-running';
|
|
16
|
+
/** `hyphenate-limit-lines` survives as this custom property, its value text carried verbatim (Chromium drops the property, so the Compositor reads the author's limit from here). It inherits, as the original does, and so is not among {@link CUSTOM_PROPS}. */
|
|
17
|
+
export const HYPHENATE_LIMIT_LINES_PROP = '--beltype-hyphenate-limit-lines';
|
|
16
18
|
/** Both, for a host that registers them. `syntax: '*'` because the values are the author's own text and this engine is not their grammar; `inherits: false` because neither original inherits. */
|
|
17
19
|
export const CUSTOM_PROPS = [STRING_SET_PROP, RUNNING_PROP];
|
|
18
20
|
/**
|
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). */
|
|
@@ -110,6 +113,8 @@ export type PageSite = {
|
|
|
110
113
|
* @returns null where no rule reads a page fact.
|
|
111
114
|
*/
|
|
112
115
|
export declare function findPageSites(doc: Document, originals?: Element[] | null, keep?: Keep | null): PageSite[] | null;
|
|
116
|
+
/** The flow sites that read the page total, as the rule that reads it names them: what {@link hideReservations} paints invisible while the total is unknown, since a flow site reads `0` until then (`chase.ts`'s `setCounters`). A `counter(page)` and a named string are true from the first page and are not among them. */
|
|
117
|
+
export declare function totalSites(doc: Document): Pick<Site, 'key' | 'pseudos'>[];
|
|
113
118
|
/** What every page of a run publishes, as the measure wears it: the page's number and the document's total, and its named strings' properties (`chase.js`'s `setCounters` and `setStrings`, the same values). */
|
|
114
119
|
export declare function pageFactsOf(account: Array<{
|
|
115
120
|
closes: {
|
|
@@ -130,8 +135,8 @@ export declare function pageFactsOf(account: Array<{
|
|
|
130
135
|
* @returns null where the document declares no read, which is every document but a few.
|
|
131
136
|
*/
|
|
132
137
|
export declare function findSites(doc: Document, originals?: Element[] | null, keep?: Keep | null): Site[] | null;
|
|
133
|
-
/** Deliver a table of values: one constructed sheet, adopted once, its text replaced. */
|
|
134
|
-
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;
|
|
135
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? */
|
|
136
141
|
export declare const isDeliverySheet: (doc: Document, sheet: CSSStyleSheet) => boolean;
|
|
137
142
|
/**
|
|
@@ -143,17 +148,18 @@ export declare const isDeliverySheet: (doc: Document, sheet: CSSStyleSheet) => b
|
|
|
143
148
|
* @param sites - the page reads that are still at the reservation.
|
|
144
149
|
* @param scope - the selector of the element the presented pages stand in.
|
|
145
150
|
*/
|
|
146
|
-
export declare function hideSheetText(sites: Site[], scope?: string): string;
|
|
151
|
+
export declare function hideSheetText(sites: Pick<Site, 'key' | 'pseudos'>[], scope?: string): string;
|
|
147
152
|
/**
|
|
148
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.
|
|
149
154
|
*
|
|
150
|
-
* @param
|
|
155
|
+
* @param selector - as {@link hideSheetText}'s `scope`.
|
|
156
|
+
* @param scope - the host's token for this view, as {@link deliver}'s.
|
|
151
157
|
*/
|
|
152
|
-
export declare function hideReservations(doc: Document, sites: Site[],
|
|
158
|
+
export declare function hideReservations(doc: Document, sites: Pick<Site, 'key' | 'pseudos'>[], selector?: string | undefined, scope?: object): void;
|
|
153
159
|
/** Take the hiding sheet off: the true numbers are delivered, or there is nothing left to hide. */
|
|
154
|
-
export declare function showReservations(doc: Document): void;
|
|
160
|
+
export declare function showReservations(doc: Document, scope?: object): void;
|
|
155
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. */
|
|
156
|
-
export declare function withdraw(doc: Document): void;
|
|
162
|
+
export declare function withdraw(doc: Document, scope?: object): void;
|
|
157
163
|
/**
|
|
158
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`.
|
|
159
165
|
*
|
|
@@ -168,6 +174,7 @@ export declare function staticValues(sites: Site[], { doc, blocks, index, keep }
|
|
|
168
174
|
}): {
|
|
169
175
|
values: Values;
|
|
170
176
|
unread: Site[];
|
|
177
|
+
ops: ReturnType<typeof documentOpsOf> | null;
|
|
171
178
|
};
|
|
172
179
|
/**
|
|
173
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`).
|
|
@@ -224,7 +231,7 @@ export declare function linesOfSites<S extends {
|
|
|
224
231
|
*
|
|
225
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.
|
|
226
233
|
*
|
|
227
|
-
* @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.
|
|
228
235
|
*/
|
|
229
236
|
export declare function settleReads<R extends {
|
|
230
237
|
account: any[];
|
|
@@ -233,7 +240,7 @@ export declare function settleReads<R extends {
|
|
|
233
240
|
why?: string | null;
|
|
234
241
|
laidFacts?: PagePublish[] | null;
|
|
235
242
|
complete?: boolean;
|
|
236
|
-
}>(doc: Document, { run, blocksOf, originals, warm, warmFacts, hide, keep }: {
|
|
243
|
+
}>(doc: Document, { run, blocksOf, originals, warm, warmFacts, hide, keep, scope }: {
|
|
237
244
|
run: (laid: Laid) => Promise<R>;
|
|
238
245
|
blocksOf: () => Element[];
|
|
239
246
|
originals?: Element[] | null;
|
|
@@ -241,6 +248,7 @@ export declare function settleReads<R extends {
|
|
|
241
248
|
warmFacts?: PagePublish[] | null;
|
|
242
249
|
hide?: boolean | string;
|
|
243
250
|
keep?: Keep | null;
|
|
251
|
+
scope?: object;
|
|
244
252
|
}): Promise<R & {
|
|
245
253
|
resumed: Resumed | null;
|
|
246
254
|
why: string | null;
|
package/dist/src/reads.js
CHANGED
|
@@ -237,6 +237,12 @@ export function findPageSites(doc, originals = null, keep = null) {
|
|
|
237
237
|
}
|
|
238
238
|
return sites.length ? sites : null;
|
|
239
239
|
}
|
|
240
|
+
/** The flow sites that read the page total, as the rule that reads it names them: what {@link hideReservations} paints invisible while the total is unknown, since a flow site reads `0` until then (`chase.ts`'s `setCounters`). A `counter(page)` and a named string are true from the first page and are not among them. */
|
|
241
|
+
export function totalSites(doc) {
|
|
242
|
+
return contentRules(doc)
|
|
243
|
+
.filter((r) => /counters?\(\s*pages\s*[,)]/.test(r.content))
|
|
244
|
+
.map((r) => ({ key: hostSelector(r.selector), pseudos: r.pseudos }));
|
|
245
|
+
}
|
|
240
246
|
/** What every page of a run publishes, as the measure wears it: the page's number and the document's total, and its named strings' properties (`chase.js`'s `setCounters` and `setStrings`, the same values). */
|
|
241
247
|
export function pageFactsOf(account) {
|
|
242
248
|
return account.map((entry) => {
|
|
@@ -306,23 +312,32 @@ function targetOf(doc, fragment, originals) {
|
|
|
306
312
|
}
|
|
307
313
|
return null;
|
|
308
314
|
}
|
|
309
|
-
/** The engine's one delivery sheet per
|
|
315
|
+
/** The engine's one delivery sheet per scope, replaced at every delivery and withdrawn when a paginate finds nothing to deliver. A scope is any object the host holds for one view of the document (the document itself where none is named), so two hosts in one document keep a sheet each (fix/scope-per-view, 2026-11-08). */
|
|
310
316
|
const SHEETS = new WeakMap();
|
|
311
|
-
/**
|
|
312
|
-
|
|
313
|
-
|
|
317
|
+
/** The text each engine sheet last carried: a sheet whose text is unchanged is not rewritten, since `replaceSync` restyles the document whether or not anything differs (*the reads loop writes only what changed*, CHANGELOG Unreleased). */
|
|
318
|
+
const SHEET_TEXT = new WeakMap();
|
|
319
|
+
const write = (sheet, text) => { if (SHEET_TEXT.get(sheet) !== text) {
|
|
320
|
+
sheet.replaceSync(text);
|
|
321
|
+
SHEET_TEXT.set(sheet, text);
|
|
322
|
+
} };
|
|
323
|
+
/** Every sheet the engine made, whatever its scope: what {@link isDeliverySheet} asks. */
|
|
324
|
+
const OWN = new WeakSet();
|
|
325
|
+
/** 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. */
|
|
326
|
+
export function deliver(doc, values, scope = doc) {
|
|
327
|
+
let sheet = SHEETS.get(scope);
|
|
314
328
|
if (!sheet) {
|
|
315
329
|
sheet = new (doc.defaultView.CSSStyleSheet)();
|
|
316
|
-
SHEETS.set(
|
|
330
|
+
SHEETS.set(scope, sheet);
|
|
331
|
+
OWN.add(sheet);
|
|
317
332
|
}
|
|
318
|
-
sheet
|
|
333
|
+
write(sheet, deliverySheetText(values));
|
|
319
334
|
if (!doc.adoptedStyleSheets.includes(sheet))
|
|
320
335
|
doc.adoptedStyleSheets = [...doc.adoptedStyleSheets, sheet];
|
|
321
336
|
}
|
|
322
|
-
/** The engine's one hiding sheet per
|
|
337
|
+
/** The engine's one hiding sheet per scope: the rule that paints a reservation invisible while the pages are still being cut (*the pages are cut as far as they are asked*, 2026-10-12). */
|
|
323
338
|
const HIDERS = new WeakMap();
|
|
324
339
|
/** 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? */
|
|
325
|
-
export const isDeliverySheet = (doc, sheet) =>
|
|
340
|
+
export const isDeliverySheet = (doc, sheet) => OWN.has(sheet);
|
|
326
341
|
/**
|
|
327
342
|
* The hiding sheet's text: one rule per distinct address the reserved page reads are keyed by, naming the pseudo-elements that read them, scoped to the presented document (`beltype-root`, or the scope a host that is not the root names). Pure, so the shape is pinned without a browser.
|
|
328
343
|
*
|
|
@@ -347,33 +362,35 @@ export function hideSheetText(sites, scope = 'beltype-root') {
|
|
|
347
362
|
/**
|
|
348
363
|
* 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.
|
|
349
364
|
*
|
|
350
|
-
* @param
|
|
365
|
+
* @param selector - as {@link hideSheetText}'s `scope`.
|
|
366
|
+
* @param scope - the host's token for this view, as {@link deliver}'s.
|
|
351
367
|
*/
|
|
352
|
-
export function hideReservations(doc, sites, scope =
|
|
353
|
-
const text = hideSheetText(sites,
|
|
368
|
+
export function hideReservations(doc, sites, selector = undefined, scope = doc) {
|
|
369
|
+
const text = hideSheetText(sites, selector);
|
|
354
370
|
if (!text)
|
|
355
371
|
return;
|
|
356
|
-
let sheet = HIDERS.get(
|
|
372
|
+
let sheet = HIDERS.get(scope);
|
|
357
373
|
if (!sheet) {
|
|
358
374
|
sheet = new (doc.defaultView.CSSStyleSheet)();
|
|
359
|
-
HIDERS.set(
|
|
375
|
+
HIDERS.set(scope, sheet);
|
|
376
|
+
OWN.add(sheet);
|
|
360
377
|
}
|
|
361
|
-
sheet
|
|
378
|
+
write(sheet, text);
|
|
362
379
|
if (!doc.adoptedStyleSheets.includes(sheet))
|
|
363
380
|
doc.adoptedStyleSheets = [...doc.adoptedStyleSheets, sheet];
|
|
364
381
|
}
|
|
365
382
|
/** Take the hiding sheet off: the true numbers are delivered, or there is nothing left to hide. */
|
|
366
|
-
export function showReservations(doc) {
|
|
367
|
-
const sheet = HIDERS.get(
|
|
383
|
+
export function showReservations(doc, scope = doc) {
|
|
384
|
+
const sheet = HIDERS.get(scope);
|
|
368
385
|
if (sheet && doc.adoptedStyleSheets.includes(sheet))
|
|
369
386
|
doc.adoptedStyleSheets = doc.adoptedStyleSheets.filter((s) => s !== sheet);
|
|
370
387
|
}
|
|
371
388
|
/** 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. */
|
|
372
|
-
export function withdraw(doc) {
|
|
373
|
-
const sheet = SHEETS.get(
|
|
389
|
+
export function withdraw(doc, scope = doc) {
|
|
390
|
+
const sheet = SHEETS.get(scope);
|
|
374
391
|
if (sheet && doc.adoptedStyleSheets.includes(sheet))
|
|
375
392
|
doc.adoptedStyleSheets = doc.adoptedStyleSheets.filter((s) => s !== sheet);
|
|
376
|
-
showReservations(doc);
|
|
393
|
+
showReservations(doc, scope);
|
|
377
394
|
}
|
|
378
395
|
/**
|
|
379
396
|
* The written and cascade facts, before any run: every `target-text()`, and every `target-counter()` and `target-counters()` of a counter other than `page`.
|
|
@@ -386,9 +403,10 @@ export function staticValues(sites, { doc, blocks, index = null, keep = null })
|
|
|
386
403
|
const unread = [];
|
|
387
404
|
const counted = sites.filter((s) => s.target && (s.read.fn === 'counter' || s.read.fn === 'counters') && !isPageRead(s.read));
|
|
388
405
|
let states = new Map();
|
|
406
|
+
let ops = null;
|
|
389
407
|
if (counted.length) {
|
|
390
408
|
const win = doc.defaultView;
|
|
391
|
-
|
|
409
|
+
ops = documentOpsOf(blocks, index ?? indexBlocks(blocks), {
|
|
392
410
|
styleOf: (el) => win.getComputedStyle(el),
|
|
393
411
|
pseudoStyleOf: sheetsCountInPseudos(doc) ? (el, pseudo) => win.getComputedStyle(el, pseudo) : null,
|
|
394
412
|
ancestors: [doc.documentElement, doc.body].filter(Boolean),
|
|
@@ -416,7 +434,7 @@ export function staticValues(sites, { doc, blocks, index = null, keep = null })
|
|
|
416
434
|
}
|
|
417
435
|
put(values, s.key, s.prop, cssString(formatCounter(counterValue(state, s.read.counter), s.read.style)));
|
|
418
436
|
}
|
|
419
|
-
return { values, unread };
|
|
437
|
+
return { values, unread, ops };
|
|
420
438
|
}
|
|
421
439
|
/**
|
|
422
440
|
* 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`).
|
|
@@ -528,14 +546,14 @@ function boxAddress(site, index, blocks) {
|
|
|
528
546
|
*
|
|
529
547
|
* **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.
|
|
530
548
|
*
|
|
531
|
-
* @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.
|
|
549
|
+
* @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.
|
|
532
550
|
*/
|
|
533
|
-
export async function settleReads(doc, { run, blocksOf, originals = null, warm = null, warmFacts = null, hide = false, keep = null }) {
|
|
551
|
+
export async function settleReads(doc, { run, blocksOf, originals = null, warm = null, warmFacts = null, hide = false, keep = null, scope = doc }) {
|
|
534
552
|
let t = performance.now(); // the site search is the reads' own cost and is inside their measure (*a resumed run keeps its reads*, 2026-10-22)
|
|
535
553
|
const found = findSites(doc, originals, keep);
|
|
536
554
|
const pageSites = findPageSites(doc, originals, keep);
|
|
537
555
|
if (!found && !pageSites) {
|
|
538
|
-
withdraw(doc);
|
|
556
|
+
withdraw(doc, scope);
|
|
539
557
|
const once = await run({ attempt: 0, delivered: null, facts: null, sites: null, index: null });
|
|
540
558
|
return { ...once, resumed: once.resumed ?? null, why: once.why ?? null, delivered: null, facts: null, reads: null };
|
|
541
559
|
}
|
|
@@ -547,17 +565,17 @@ export async function settleReads(doc, { run, blocksOf, originals = null, warm =
|
|
|
547
565
|
for (const s of sites)
|
|
548
566
|
if (s.target && s.read.fn === 'text' && !blocks.some((b) => b.contains(s.target)))
|
|
549
567
|
s.target = null;
|
|
550
|
-
// One walk of the words, shared with the run (*the run resumes from an entry*, build two: 60 ms of every manual run was this walk, done twice)
|
|
568
|
+
// One walk of the words, shared with the run (*the run resumes from an entry*, build two: 60 ms of every manual run was this walk, done twice), and one counter walk where a counter reference needs it: the run takes this log and lays its running elements in (`document-state.ts`' `withRunning`). It is read under the GCPM properties' registration, which `paginate` makes before the reads, since an unregistered `string-set` inherits and a log taken before it would assign every heading's string to its descendants too. Measured 2026-11-09 on a 190-section document with one `target-counter()` of a figure: the run's `cicero:document-ops` went from 79 to 82 ms to under 1 ms, the reads' measure unchanged at 163 to 177 ms.
|
|
551
569
|
const index = indexBlocks(blocks, undefined, undefined, keep);
|
|
552
570
|
const fixed = staticValues(sites, { doc, blocks, index, keep });
|
|
553
571
|
const paged = sites.filter((s) => s.target && isPageRead(s.read));
|
|
554
572
|
let delivered = reservations(sites, warm);
|
|
555
573
|
if (found)
|
|
556
|
-
deliver(doc, merged(fixed.values, delivered));
|
|
574
|
+
deliver(doc, merged(fixed.values, delivered), scope);
|
|
557
575
|
else
|
|
558
|
-
withdraw(doc);
|
|
576
|
+
withdraw(doc, scope);
|
|
559
577
|
if (hide)
|
|
560
|
-
hideReservations(doc, paged.filter((s) => delivered.get(s.key ?? '')?.get(s.prop) === cssString(RESERVATION)), hide === true ? undefined : hide);
|
|
578
|
+
hideReservations(doc, [...paged.filter((s) => delivered.get(s.key ?? '')?.get(s.prop) === cssString(RESERVATION)), ...(pageSites ? totalSites(doc) : [])], hide === true ? undefined : hide, scope);
|
|
561
579
|
performance.measure('cicero:reads', { start: t, end: (t = performance.now()), detail: { references: sites.length, pageSites: pageSites?.length ?? 0 } });
|
|
562
580
|
let runs = 0;
|
|
563
581
|
let moved = [];
|
|
@@ -577,7 +595,7 @@ export async function settleReads(doc, { run, blocksOf, originals = null, warm =
|
|
|
577
595
|
laidUnder = merged(fixed.values, delivered);
|
|
578
596
|
laidFacts = facts;
|
|
579
597
|
const started = performance.now();
|
|
580
|
-
result = await run({ attempt: runs, delivered: found ? laidUnder : null, facts: laidFacts, sites: found, index, because: because.map(name), resume });
|
|
598
|
+
result = await run({ attempt: runs, delivered: found ? laidUnder : null, facts: laidFacts, sites: found, index, ops: fixed.ops, because: because.map(name), resume });
|
|
581
599
|
laidFacts = result.laidFacts ?? laidFacts; // the page values the layout was cut under are the run's to say; a run that says none was cut under the ones it was handed
|
|
582
600
|
if (runs === 0) {
|
|
583
601
|
resumed = result.resumed ?? null;
|
|
@@ -595,7 +613,7 @@ export async function settleReads(doc, { run, blocksOf, originals = null, warm =
|
|
|
595
613
|
facts = laidFacts;
|
|
596
614
|
break;
|
|
597
615
|
}
|
|
598
|
-
showReservations(doc); // the run is complete: what was reserved is delivered true on the next lines, or was true already
|
|
616
|
+
showReservations(doc, scope); // the run is complete: what was reserved is delivered true on the next lines, or was true already
|
|
599
617
|
const truth = pageValues(paged, result);
|
|
600
618
|
unplaced = truth.unplaced;
|
|
601
619
|
const changed = new Set(changedPairs(delivered, truth.values));
|
|
@@ -612,7 +630,7 @@ export async function settleReads(doc, { run, blocksOf, originals = null, warm =
|
|
|
612
630
|
const before = linesOfSites(doc, touched, result, blocks);
|
|
613
631
|
const beforePage = linesOfSites(doc, shifted, result, blocks, (page) => factOf(laidFacts, page));
|
|
614
632
|
if (found)
|
|
615
|
-
deliver(doc, merged(fixed.values, truth.values));
|
|
633
|
+
deliver(doc, merged(fixed.values, truth.values), scope);
|
|
616
634
|
delivered = truth.values;
|
|
617
635
|
facts = truthFacts;
|
|
618
636
|
const after = linesOfSites(doc, touched, result, blocks);
|
|
@@ -638,6 +656,7 @@ export async function settleReads(doc, { run, blocksOf, originals = null, warm =
|
|
|
638
656
|
if (moved.length)
|
|
639
657
|
console.warn(`BelType/Cicero: after ${runs} runs the page numbers of ${named(moved)} still move their own lines — the last true values stand, on a layout measured with the values before them.`);
|
|
640
658
|
const pageReads = sites.filter((s) => isPageRead(s.read)).length;
|
|
641
|
-
|
|
659
|
+
if (moved.length)
|
|
660
|
+
console.info(`BelType/Cicero: ${sites.length} reference(s) read (${pageReads} page, ${sites.length - pageReads} counter or text) and ${pageSites?.length ?? 0} site(s) reading their own page; the pages settled in ${runs} run${runs === 1 ? '' : 's'}.`);
|
|
642
661
|
return { ...result, resumed, why, delivered: found ? (moved.length ? laidUnder : merged(fixed.values, delivered)) : null, facts: moved.length ? laidFacts : facts, reads: { references: sites.length, pageSites: pageSites?.length ?? 0, runs, unsettled: moved.map(name) } };
|
|
643
662
|
}
|
package/dist/src/root.js
CHANGED
|
@@ -19,7 +19,7 @@ import { pointShownBy, sourcePointOf } from './source-point.js';
|
|
|
19
19
|
import { copyOf, holdsFixedBox, sourceOf } from './word-space.js';
|
|
20
20
|
import { holdsPresented } from './presenters.js';
|
|
21
21
|
import { transliterateContent, neutralizeMarginBoxes, sheetsUseHas } from './capture.js';
|
|
22
|
-
import { ARTIFACT, addWord, FIXED, FRAGMENT, selectorOf, setValue, wordOf } from './data-beltype.js';
|
|
22
|
+
import { ARTIFACT, decodeFragment, addWord, FIXED, FRAGMENT, selectorOf, setValue, wordOf } from './data-beltype.js';
|
|
23
23
|
import { deliverValues, ensureContinuationDressing, ensureDressing, isWordsSheet } from './dressing.js';
|
|
24
24
|
import { carriedPast, divides, elAtIn, extensionPx, flattenResume, flowsOf, indexBlocks, pageIntervals, pageOfPos, widthKeyOf, wordsBeforeChar } from './positions.js';
|
|
25
25
|
const PRESS_STYLE_ID = '__beltype-press';
|
|
@@ -690,7 +690,7 @@ export class BeltypeRoot extends HTMLElement {
|
|
|
690
690
|
this.#listening = true;
|
|
691
691
|
const view = this.ownerDocument.defaultView;
|
|
692
692
|
const targetOf = (hash) => {
|
|
693
|
-
const id = hash.length > 1 ?
|
|
693
|
+
const id = hash.length > 1 ? decodeFragment(hash) : '';
|
|
694
694
|
const found = id ? this.ownerDocument.getElementById(id) ?? this.ownerDocument.getElementsByName(id)[0] : null;
|
|
695
695
|
return found && this.contains(found) ? found : null;
|
|
696
696
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the engine does not do yet, read from the author's boxes at intake: vertical writing below the root is refused by name, and a multi-column box too tall for any page is laid out as one column.
|
|
3
|
+
*
|
|
4
|
+
* @remarks The root's own direction and writing mode are refused by `assertRenderConditions` (`src/measure.ts`). Both refusals are out of scope for the beta (SPEC II.1, item 6). The one-column fallback is an interim: the computed fact (the box needs dividing) is known and only the presentation of its columns is not, so it says so on the console (SPEC Article I, item 11). The ceiling is the first page's geometry, and a box nested in another multi-column box is measured on its own.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Walk the blocks' boxes once: refuse a vertical writing mode, answer the multi-column boxes.
|
|
10
|
+
*
|
|
11
|
+
* @throws Error — bare and deliberate (`src/` may not import BelType's error types): a box below the root that computes a `writing-mode` other than `horizontal-tb`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readScope(win: Window & typeof globalThis, blocks: Iterable<Element>): Element[];
|
|
14
|
+
/**
|
|
15
|
+
* Give the word `one-column` to every multi-column box that is taller than a page area at the page's width, so its copies lay out as one column and divide like any block. A box that fits a page keeps its columns.
|
|
16
|
+
*
|
|
17
|
+
* @param columns - {@link readScope}'s answer.
|
|
18
|
+
* @param page - the geometry the boxes are measured against.
|
|
19
|
+
*/
|
|
20
|
+
export declare function foldTallColumns(doc: Document, columns: Element[], page: {
|
|
21
|
+
contentW: number;
|
|
22
|
+
contentH: number;
|
|
23
|
+
}): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the engine does not do yet, read from the author's boxes at intake: vertical writing below the root is refused by name, and a multi-column box too tall for any page is laid out as one column.
|
|
3
|
+
*
|
|
4
|
+
* @remarks The root's own direction and writing mode are refused by `assertRenderConditions` (`src/measure.ts`). Both refusals are out of scope for the beta (SPEC II.1, item 6). The one-column fallback is an interim: the computed fact (the box needs dividing) is known and only the presentation of its columns is not, so it says so on the console (SPEC Article I, item 11). The ceiling is the first page's geometry, and a box nested in another multi-column box is measured on its own.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import { createMeasureContext } from './measure.js';
|
|
9
|
+
import { ONE_COLUMN } from './data-beltype.js';
|
|
10
|
+
import { addSourceWord, measuringCopyOf, sourceHasWord } from './word-space.js';
|
|
11
|
+
const named = new WeakSet();
|
|
12
|
+
const describe = (el) => `<${el.localName}${el.className && typeof el.className === 'string' ? ` class="${el.className}"` : ''}>`;
|
|
13
|
+
/**
|
|
14
|
+
* Walk the blocks' boxes once: refuse a vertical writing mode, answer the multi-column boxes.
|
|
15
|
+
*
|
|
16
|
+
* @throws Error — bare and deliberate (`src/` may not import BelType's error types): a box below the root that computes a `writing-mode` other than `horizontal-tb`.
|
|
17
|
+
*/
|
|
18
|
+
export function readScope(win, blocks) {
|
|
19
|
+
const columns = [];
|
|
20
|
+
for (const block of blocks) {
|
|
21
|
+
for (const el of [block, ...block.querySelectorAll('*')]) {
|
|
22
|
+
const st = win.getComputedStyle(el);
|
|
23
|
+
if (st.writingMode && st.writingMode !== 'horizontal-tb') {
|
|
24
|
+
throw new Error(`vertical writing is outside this engine's scope: ${describe(el)} computes writing-mode '${st.writingMode}', and every read here is horizontal (the line by vertical overlap, the cut by a y). Write it horizontal-tb, or print the document with another engine.`);
|
|
25
|
+
}
|
|
26
|
+
if ((st.columnCount ?? 'auto') !== 'auto' || (st.columnWidth ?? 'auto') !== 'auto')
|
|
27
|
+
columns.push(el);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return columns;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Give the word `one-column` to every multi-column box that is taller than a page area at the page's width, so its copies lay out as one column and divide like any block. A box that fits a page keeps its columns.
|
|
34
|
+
*
|
|
35
|
+
* @param columns - {@link readScope}'s answer.
|
|
36
|
+
* @param page - the geometry the boxes are measured against.
|
|
37
|
+
*/
|
|
38
|
+
export function foldTallColumns(doc, columns, page) {
|
|
39
|
+
if (!columns.length)
|
|
40
|
+
return;
|
|
41
|
+
const ctx = createMeasureContext(doc, page.contentW);
|
|
42
|
+
try {
|
|
43
|
+
for (const el of columns) {
|
|
44
|
+
if (sourceHasWord(el, ONE_COLUMN))
|
|
45
|
+
continue; // settled earlier in this call; the next call asks again
|
|
46
|
+
const copy = measuringCopyOf(el);
|
|
47
|
+
ctx.measure([copy]);
|
|
48
|
+
if (copy.getBoundingClientRect().height <= page.contentH)
|
|
49
|
+
continue;
|
|
50
|
+
addSourceWord(el, ONE_COLUMN);
|
|
51
|
+
if (!named.has(el)) {
|
|
52
|
+
named.add(el);
|
|
53
|
+
doc.defaultView?.console?.warn(`cicero: ${describe(el)} is a multi-column box taller than a page, and columns are not divided across pages yet — it is laid out as one column`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
ctx.dispose();
|
|
59
|
+
}
|
|
60
|
+
}
|
package/dist/src/stonehand.d.ts
CHANGED
|
@@ -133,7 +133,7 @@ export declare function classBWindow(T: number, orphans: number, widows: number)
|
|
|
133
133
|
*/
|
|
134
134
|
export declare function isMonolith(el: Element, style: CSSStyleDeclaration, live?: Element): boolean;
|
|
135
135
|
/**
|
|
136
|
-
* **A fourth ground, and a temporary one: what the engine does not yet divide, where dividing it loses words** (*every branch gets a place*, 2026-09-21). Two arrangements lay content side by side in ways a row of sibling boxes does not describe, and each lost words at a cut until it was held whole (`packages/essay/probes/conservation.js`): a multi-column container, whose columns are fragmentainers of their own; and an inline-level block container, which is an atom of a line, with paragraphs inside it that the procession used to see. Held as a monolith each moves whole, or is sliced by the edge with its remainder painted — css-break-3's own remedy. **The remedy has a price, measured the same day, and it is why the list is this short**: a slice through text leaves the line the edge crosses half on each page, and the print's text layer reads it on both. That is better than words on no page and worse than anything else, so a grid with sized tracks or placed items — which divides without losing a word and merely opens its next page on the spent track's blank — was tried here and taken out again. Each entry leaves this list when it has a design of its own.
|
|
136
|
+
* **A fourth ground, and a temporary one: what the engine does not yet divide, where dividing it loses words** (*every branch gets a place*, 2026-09-21). Two arrangements lay content side by side in ways a row of sibling boxes does not describe, and each lost words at a cut until it was held whole (`packages/essay/probes/conservation.js`): a multi-column container, whose columns are fragmentainers of their own; and an inline-level block container, which is an atom of a line, with paragraphs inside it that the procession used to see. Held as a monolith each moves whole, or is sliced by the edge with its remainder painted — css-break-3's own remedy. **The remedy has a price, measured the same day, and it is why the list is this short**: a slice through text leaves the line the edge crosses half on each page, and the print's text layer reads it on both. A multi-column box taller than a page no longer reaches this list: it is laid out as one column (`src/scope.ts`), and the slice is left to a box that is a monolith for another reason. That is better than words on no page and worse than anything else, so a grid with sized tracks or placed items — which divides without losing a word and merely opens its next page on the spent track's blank — was tried here and taken out again. Each entry leaves this list when it has a design of its own.
|
|
137
137
|
*
|
|
138
138
|
* `live` is the element a measured clone stands for, kept in the signature for the day a ground needs to ask the element rather than its computed style — a grid's declared tracks are such a question: `grid-template-rows` resolves to the used tracks, implicit ones included, and only `computedStyleMap()` tells a declared list from none (measured in Chromium).
|
|
139
139
|
*/
|