@beltype/cicero 0.1.8 → 0.1.9
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/src/chase.js
CHANGED
|
@@ -312,9 +312,11 @@ export class BeltypePage extends HTMLElement {
|
|
|
312
312
|
const keys = names.map((n, i) => `${this.#dressText} ${n} ${generated[i]} ${(this.#reads[n] ?? []).map((r) => `${r}=${this.#published.get(r) ?? ''}`).join(',')} ${cellKey(cells[i])}`);
|
|
313
313
|
let pairs = keys.map((k) => INTRINSICS.get(k));
|
|
314
314
|
if (pairs.some((p) => !p)) {
|
|
315
|
+
// A rect is drawn under every transform above the page, and a host that zooms with `transform: scale(s)` would hand §5.3.2 widths in its pixels, not the page's, and the cache would keep them for every scale after (assessment 2026-11-07). The band spans exactly `#bandWidth` of the page's own pixels, so its rect says the scale.
|
|
316
|
+
const scale = el.getBoundingClientRect().width / this.#bandWidth || 1;
|
|
315
317
|
const read = (track) => {
|
|
316
318
|
el.style.gridTemplateColumns = `${track} ${track} ${track}`;
|
|
317
|
-
return cells.map((c) => c.getBoundingClientRect().width);
|
|
319
|
+
return cells.map((c) => c.getBoundingClientRect().width / scale);
|
|
318
320
|
};
|
|
319
321
|
const max = read('max-content');
|
|
320
322
|
const min = read('min-content');
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* **A position is the flat word index** (`positions.js`), and an operation applies when its position is strictly before the one asked about — and not inside a remainder a carried flow has still to show, which stands before the page's first word in document order and is on that page or after it (beltype#43; `positions.js`' `remaindersOf`). That is §6.1's *"the deepest element in the normal flow that spans the page break"* said in this engine's own space: an element whose first word is the page's first word begins on the new page and does not span the break, so its own operation is not yet in force, while an element that spans the break started before that word and is included. The ancestors above the top-level blocks sit at position **−1** — they open before the document's first word and span every break there is — which is what makes the first page's answer fall out of the same comparison rather than needing a case of its own.
|
|
15
15
|
*
|
|
16
|
-
* **What is not read, each a real gap and each showing as a counter reading low.** A list item's implicit increment, so an
|
|
16
|
+
* **What is not read, each a real gap and each showing as a counter reading low.** A list item's implicit increment, so a `counter(list-item)` an author's own content reads, a margin box's among them, reads low on a page a list continues onto; the list's markers are right since 2026-11-07, because each divided `<ol>`'s copy carries a `start` of HTML's own numbering (`fragments.js`'s `numberDividedLists`); css-lists-3's `reversed()` counters; and a top-level `position: fixed` box, which is not among the flow blocks the position index is built over. `display: none` elements are skipped, which is CSS 2.1's own rule rather than an omission. A `::before`'s and an `::after`'s counters are read since 2026-11-07, where a sheet sets one (`pseudoStyleOf`). **And `content(before)`, `content(after)` and `content(first-letter)` are read as `content()`** — a stated limit rather than a discovered one: a pseudo-element's own string is generated content this walk does not see, and answering the element's text is the nearest honest answer there is.
|
|
17
17
|
*
|
|
18
18
|
* @packageDocumentation
|
|
19
19
|
*/
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* **A position is the flat word index** (`positions.js`), and an operation applies when its position is strictly before the one asked about — and not inside a remainder a carried flow has still to show, which stands before the page's first word in document order and is on that page or after it (beltype#43; `positions.js`' `remaindersOf`). That is §6.1's *"the deepest element in the normal flow that spans the page break"* said in this engine's own space: an element whose first word is the page's first word begins on the new page and does not span the break, so its own operation is not yet in force, while an element that spans the break started before that word and is included. The ancestors above the top-level blocks sit at position **−1** — they open before the document's first word and span every break there is — which is what makes the first page's answer fall out of the same comparison rather than needing a case of its own.
|
|
15
15
|
*
|
|
16
|
-
* **What is not read, each a real gap and each showing as a counter reading low.** A list item's implicit increment, so an
|
|
16
|
+
* **What is not read, each a real gap and each showing as a counter reading low.** A list item's implicit increment, so a `counter(list-item)` an author's own content reads, a margin box's among them, reads low on a page a list continues onto; the list's markers are right since 2026-11-07, because each divided `<ol>`'s copy carries a `start` of HTML's own numbering (`fragments.js`'s `numberDividedLists`); css-lists-3's `reversed()` counters; and a top-level `position: fixed` box, which is not among the flow blocks the position index is built over. `display: none` elements are skipped, which is CSS 2.1's own rule rather than an omission. A `::before`'s and an `::after`'s counters are read since 2026-11-07, where a sheet sets one (`pseudoStyleOf`). **And `content(before)`, `content(after)` and `content(first-letter)` are read as `content()`** — a stated limit rather than a discovered one: a pseudo-element's own string is generated content this walk does not see, and answering the element's text is the nearest honest answer there is.
|
|
17
17
|
*
|
|
18
18
|
* @packageDocumentation
|
|
19
19
|
*/
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
* The fragmentation face: the engine's members a view adapter builds on, `@beltype/cicero/fragmentation`.
|
|
3
3
|
*
|
|
4
4
|
* @remarks
|
|
5
|
-
* Exactly the names the first view adapter imported by deep path on 2026-10-29 (68 of them, from 16 modules), re-exported unchanged
|
|
5
|
+
* Exactly the names the first view adapter imported by deep path on 2026-10-29 (68 of them, from 16 modules), re-exported unchanged, and the names a view has asked for since (the leading-decoration door, `WRAPPED`, `clonesDecoration` and `truncateLeadingDecoration`, 2026-11-07); nothing is un-exported. Which of them stay public, merge or leave is the shape round's question. The face loads in node: `chase.ts`, which defines the `<beltype-page>` element and so needs a DOM at load, is not here; a view imports it for that effect as `@beltype/cicero/internal/chase`.
|
|
6
6
|
*
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
9
|
export { effectivePageName, readGeometryTable, registerGcpmProperties, topLevelReach, transliterateContent } from './capture.ts';
|
|
10
|
-
export { ADJOIN, ARTIFACT, AWAY, END, FRAGMENT, SHELL, SPLIT, START, addWord, hasWord, removeWord } from './data-beltype.ts';
|
|
10
|
+
export { ADJOIN, ARTIFACT, AWAY, END, FRAGMENT, SHELL, SPLIT, START, WRAPPED, addWord, hasWord, removeWord } from './data-beltype.ts';
|
|
11
11
|
export { counterStateAt, documentOpsOf, namedStringsForPage, runningAssignmentsOf, stringAssignmentsOf } from './document-state.ts';
|
|
12
12
|
export { ensureContinuationDressing, ensureDressing } from './dressing.ts';
|
|
13
13
|
export { forcingIndexOf, gapAbove, pageNameDownFrom, runningBoxes, runningPosition, topLevelBlocks } from './driver.ts';
|
|
14
14
|
export { ForcingMemo } from './forcing-memo.ts';
|
|
15
15
|
export type { Keep } from './forcing-memo.ts';
|
|
16
|
-
export { joinedWords, mutateToContinuation, mutateToFirstFragment, pinTrack } from './fragments.ts';
|
|
16
|
+
export { joinedWords, mutateToContinuation, mutateToFirstFragment, pinTrack, truncateLeadingDecoration } from './fragments.ts';
|
|
17
17
|
export { takeReport } from './gauge.ts';
|
|
18
18
|
export { STRING_KEYWORDS, encodeStringProp } from './gcpm-encoding.ts';
|
|
19
19
|
export { assertRenderConditions, awaitCloneImages, awaitFonts, createMeasureContext } from './measure.ts';
|
|
@@ -21,4 +21,4 @@ export { divides, elAt, extensionPx, indexBlocks, normalizePath, pageContaining,
|
|
|
21
21
|
export { present, presentation } from './presenters.ts';
|
|
22
22
|
export { cssString, findPageSites, settleReads } from './reads.ts';
|
|
23
23
|
export { adjudicate, firstBelowTheEdge, fitsPage, isOutOfFlow, neverCutInside, processionBottoms, reachesBelowTheEdge } from './stonehand.ts';
|
|
24
|
-
export { BREAKING_SPACE, copyOf, isFixedBox, isInlineBox, isLineBreak, noteSource } from './word-space.ts';
|
|
24
|
+
export { BREAKING_SPACE, clonesDecoration, copyOf, isFixedBox, isInlineBox, isLineBreak, noteSource } from './word-space.ts';
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
* The fragmentation face: the engine's members a view adapter builds on, `@beltype/cicero/fragmentation`.
|
|
3
3
|
*
|
|
4
4
|
* @remarks
|
|
5
|
-
* Exactly the names the first view adapter imported by deep path on 2026-10-29 (68 of them, from 16 modules), re-exported unchanged
|
|
5
|
+
* Exactly the names the first view adapter imported by deep path on 2026-10-29 (68 of them, from 16 modules), re-exported unchanged, and the names a view has asked for since (the leading-decoration door, `WRAPPED`, `clonesDecoration` and `truncateLeadingDecoration`, 2026-11-07); nothing is un-exported. Which of them stay public, merge or leave is the shape round's question. The face loads in node: `chase.ts`, which defines the `<beltype-page>` element and so needs a DOM at load, is not here; a view imports it for that effect as `@beltype/cicero/internal/chase`.
|
|
6
6
|
*
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
9
|
export { effectivePageName, readGeometryTable, registerGcpmProperties, topLevelReach, transliterateContent } from './capture.js';
|
|
10
|
-
export { ADJOIN, ARTIFACT, AWAY, END, FRAGMENT, SHELL, SPLIT, START, addWord, hasWord, removeWord } from './data-beltype.js';
|
|
10
|
+
export { ADJOIN, ARTIFACT, AWAY, END, FRAGMENT, SHELL, SPLIT, START, WRAPPED, addWord, hasWord, removeWord } from './data-beltype.js';
|
|
11
11
|
export { counterStateAt, documentOpsOf, namedStringsForPage, runningAssignmentsOf, stringAssignmentsOf } from './document-state.js';
|
|
12
12
|
export { ensureContinuationDressing, ensureDressing } from './dressing.js';
|
|
13
13
|
export { forcingIndexOf, gapAbove, pageNameDownFrom, runningBoxes, runningPosition, topLevelBlocks } from './driver.js';
|
|
14
14
|
export { ForcingMemo } from './forcing-memo.js';
|
|
15
|
-
export { joinedWords, mutateToContinuation, mutateToFirstFragment, pinTrack } from './fragments.js';
|
|
15
|
+
export { joinedWords, mutateToContinuation, mutateToFirstFragment, pinTrack, truncateLeadingDecoration } from './fragments.js';
|
|
16
16
|
export { takeReport } from './gauge.js';
|
|
17
17
|
export { STRING_KEYWORDS, encodeStringProp } from './gcpm-encoding.js';
|
|
18
18
|
export { assertRenderConditions, awaitCloneImages, awaitFonts, createMeasureContext } from './measure.js';
|
|
@@ -20,4 +20,4 @@ export { divides, elAt, extensionPx, indexBlocks, normalizePath, pageContaining,
|
|
|
20
20
|
export { present, presentation } from './presenters.js';
|
|
21
21
|
export { cssString, findPageSites, settleReads } from './reads.js';
|
|
22
22
|
export { adjudicate, firstBelowTheEdge, fitsPage, isOutOfFlow, neverCutInside, processionBottoms, reachesBelowTheEdge } from './stonehand.js';
|
|
23
|
-
export { BREAKING_SPACE, copyOf, isFixedBox, isInlineBox, isLineBreak, noteSource } from './word-space.js';
|
|
23
|
+
export { BREAKING_SPACE, clonesDecoration, copyOf, isFixedBox, isInlineBox, isLineBreak, noteSource } from './word-space.js';
|
package/dist/src/fragments.d.ts
CHANGED
|
@@ -141,6 +141,12 @@ export declare function mutateToContinuation(clone: Element, { cut, remainderPx,
|
|
|
141
141
|
hollow?: boolean;
|
|
142
142
|
forced?: boolean;
|
|
143
143
|
}): Element;
|
|
144
|
+
/**
|
|
145
|
+
* The ordinal value of each item an `<ol>` owns, as HTML numbers them (§4.4.8) and css-lists-3 states it as the `list-item` counter: the list resets the counter one step before its `start`, which under `reversed` defaults to the number of items; each item adds its step, 1 or −1 under `reversed` unless a `counter-increment` names `list-item`, and a `counter-set` of `list-item`, or else a `value` attribute, sets it.
|
|
146
|
+
*
|
|
147
|
+
* @remarks A `counter-reset` of `list-item` on an item, which opens a list of its own inside the list, is not modelled.
|
|
148
|
+
*/
|
|
149
|
+
export declare function listOrdinals(ol: Element): Map<Element, number>;
|
|
144
150
|
/**
|
|
145
151
|
* A clone of a top-level block for one interval, hollow outside it (*the clone is hollow outside the interval*, 2026-09-27) — the one construction behind the measure feed and every presented fragment, and the one place the clone-to-source map is filled.
|
|
146
152
|
*
|
package/dist/src/fragments.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { applyRecordsUnder, hyphenCharOf } from './compose-dom.js';
|
|
21
21
|
import { addWord, ADJOIN, ARTIFACT, AWAY, END, ENGINE_MARKS, hasAnyWord, hasWord, removeWord, SHELL, SPLIT, START, WRAPPED } from './data-beltype.js';
|
|
22
|
+
import { pairsOf } from './document-state.js';
|
|
22
23
|
import { divides, elAtIn, flowsOf } from './positions.js';
|
|
23
24
|
import { BREAKING_SPACE, clonesDecoration, cutRanges, emptyText, leaveOut, replaceChars, isEngineText, isFixedBox, isInlineBox, isLineBreak, copyOf, shellOf, noteText, READS_ITS_CHILDREN, sourceOf, sourceStyle } from './word-space.js';
|
|
24
25
|
/** The word space, clone side: words are maximal non-whitespace runs of the subtree's joined text — a run crossing a text-node or inline-element seam is one word, exactly as the Gauge reads, and exactly as the position index counts over sources (src/positions.js — the two tokenizations must never drift; counting per node instead shifts every offset by the seam-spanning words before it, which the test-document's inline markup caught). **A non-inline boundary ends the run** (word-space.js), which is where this stopped agreeing with the page for two months. This walk survives on the clone side only: consumption must find the resume word's actual text nodes in the mutated copy, which no source-side index can answer. */
|
|
@@ -790,8 +791,72 @@ export function mutateToContinuation(clone, { cut = null, remainderPx = null, so
|
|
|
790
791
|
applyCarriedRemainder(clone, source, remainderPx);
|
|
791
792
|
for (const m of mine)
|
|
792
793
|
mutateToContinuation(m.itemClone, { cut: m.cut, remainderPx: m.e.remainingPx ?? null, source: m.itemSource, cloneOf, flows: m.nested, rootPath: m.e.item, hollow });
|
|
794
|
+
numberDividedLists(root, cut, source, cloneOf);
|
|
793
795
|
return root;
|
|
794
796
|
}
|
|
797
|
+
/** The list items an `<ol>` owns, in tree order: HTML's list owner is the nearest `ol`, `ul` or `menu` ancestor. An item that is not displayed as a list item has no ordinal, where a style can be read. */
|
|
798
|
+
function ownedItems(ol) {
|
|
799
|
+
const out = [];
|
|
800
|
+
(function walk(el) {
|
|
801
|
+
for (const c of el.children) {
|
|
802
|
+
if (/^(?:OL|UL|MENU)$/i.test(c.tagName))
|
|
803
|
+
continue;
|
|
804
|
+
if (/^LI$/i.test(c.tagName)) {
|
|
805
|
+
const display = sourceStyle(c)?.display;
|
|
806
|
+
if (display == null || display.includes('list-item'))
|
|
807
|
+
out.push(c);
|
|
808
|
+
}
|
|
809
|
+
walk(c);
|
|
810
|
+
}
|
|
811
|
+
})(ol);
|
|
812
|
+
return out;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* The ordinal value of each item an `<ol>` owns, as HTML numbers them (§4.4.8) and css-lists-3 states it as the `list-item` counter: the list resets the counter one step before its `start`, which under `reversed` defaults to the number of items; each item adds its step, 1 or −1 under `reversed` unless a `counter-increment` names `list-item`, and a `counter-set` of `list-item`, or else a `value` attribute, sets it.
|
|
816
|
+
*
|
|
817
|
+
* @remarks A `counter-reset` of `list-item` on an item, which opens a list of its own inside the list, is not modelled.
|
|
818
|
+
*/
|
|
819
|
+
export function listOrdinals(ol) {
|
|
820
|
+
const items = ownedItems(ol);
|
|
821
|
+
const reversed = ol.hasAttribute('reversed');
|
|
822
|
+
const dflt = reversed ? -1 : 1;
|
|
823
|
+
const start = parseInt(ol.getAttribute('start') ?? '', 10);
|
|
824
|
+
let n = (Number.isFinite(start) ? start : reversed ? items.length : 1) - dflt;
|
|
825
|
+
const out = new Map();
|
|
826
|
+
for (const li of items) {
|
|
827
|
+
const style = sourceStyle(li);
|
|
828
|
+
const named = (value, fallback) => pairsOf(value, fallback).find(([name]) => name === 'list-item')?.[1];
|
|
829
|
+
n += named(style?.counterIncrement, 1) ?? dflt;
|
|
830
|
+
const value = parseInt(li.getAttribute('value') ?? '', 10);
|
|
831
|
+
const set = named(style?.counterSet, 0) ?? (Number.isFinite(value) ? value : null);
|
|
832
|
+
if (set != null)
|
|
833
|
+
n = set;
|
|
834
|
+
out.set(li, n);
|
|
835
|
+
}
|
|
836
|
+
return out;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Every `<ol>` a cut divides is numbered from the first item its piece keeps: the piece's copy carries a `start` attribute of that item's ordinal, so the browser's own marker counts on from the source's numbering. Without it a continued list numbered from 1 again on every page, and the first piece of a `reversed` list counted down from the items it kept (measured 2026-11-07 against Chromium's print, `test/browser/list-continuation.browser.ts`). Read from the source, so it holds whichever cut ran first.
|
|
840
|
+
*/
|
|
841
|
+
function numberDividedLists(root, cut, source, cloneOf) {
|
|
842
|
+
if (!divides(cut) || !source || !cloneOf)
|
|
843
|
+
return;
|
|
844
|
+
const target = elAtIn(source, cut.path);
|
|
845
|
+
for (let a = target?.parentElement ?? null; a; a = a === source ? null : a.parentElement) {
|
|
846
|
+
if (!/^OL$/i.test(a.tagName))
|
|
847
|
+
continue;
|
|
848
|
+
const copy = cloneOf.get(a);
|
|
849
|
+
if (!copy)
|
|
850
|
+
continue;
|
|
851
|
+
for (const [li, n] of listOrdinals(a)) {
|
|
852
|
+
const c = cloneOf.get(li);
|
|
853
|
+
if (!c || !root.contains(c) || hasWord(c, AWAY))
|
|
854
|
+
continue;
|
|
855
|
+
copy.setAttribute('start', String(n));
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
795
860
|
/** The shell branch of the start cut: the box the path names continues at exactly the extent it has left, blank or painted. */
|
|
796
861
|
function shellAt(clone, target, px, keep = new Set(), hollow = false) {
|
|
797
862
|
const painted = px.paint != null;
|
|
@@ -1004,6 +1069,7 @@ export function mutateToFirstFragment(clone, { cut = null, extPx = 0, source = n
|
|
|
1004
1069
|
/** And the cut line stays justified. css-text-3 §7.2 says a line before a forced or fragmentation break is not the last line of its block, so `text-align: justify` binds it — but in a clone cut AT the break it is the last line and the browser would set it ragged. The alignment is restated rather than inherited, and it is read from the source through the word space's map, because a detached clone has no computed style to ask. The Compositor makes the same move for the same reason (`compose-dom.js`), which is what makes this the engine's one answer written twice for two different line providers rather than two answers. */
|
|
1005
1070
|
if (sourceStyle(inner)?.textAlign === 'justify')
|
|
1006
1071
|
inner.style.textAlignLast = 'justify';
|
|
1072
|
+
numberDividedLists(clone, cut, source, cloneOf);
|
|
1007
1073
|
return clone;
|
|
1008
1074
|
}
|
|
1009
1075
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beltype/cicero",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Cicero, a paged-media engine for the live browser: it cuts a document into pages under CSS Paged Media and presents them as real frames. Beta.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|