@beltype/cicero 0.1.4 → 0.1.5

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/README.md CHANGED
@@ -9,6 +9,8 @@ Cicero is a paged-media engine for the live browser. It cuts a document into pag
9
9
  | Import | What |
10
10
  | --- | --- |
11
11
  | `@beltype/cicero` | The host face: `ciceroEngine` (BelType's engine seam) and `rewriteDocumentSheets`. |
12
+ | `@beltype/cicero/engine` | `ciceroEngine` alone, without the sheet rewriter and its css-tree: a host that only serves a page (BelType's CLI) loads this. |
13
+ | `@beltype/cicero/flags` | `ciceroFlags` alone, the run-wide flags in common's shape. |
12
14
  | `@beltype/cicero/rewrite` | `rewriteDocumentSheets` alone, for a page: the host face also carries `ciceroEngine`, which imports node's `url` and so cannot load in a browser. |
13
15
  | `@beltype/cicero/client` | The page side: loading it sets `window.__beltypeCicero`, typed in `seam/handle.d.ts`. |
14
16
  | `@beltype/cicero/facsimile` | The embedded PDF page (`<object type="application/pdf">`). |
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Cicero's run-wide flags, in `@beltype/common`'s shape.
3
+ *
4
+ * @remarks
5
+ * One today: `composed`. The other options of a `run()` call — `until`, `resumeAt` and `layer` — belong to a single call, not to a run, and are not flags; nor is a constant someone may want to tune later, until someone asks.
6
+ *
7
+ * This is the node face's table. The client runs in a tab with no way to resolve `@beltype/common`, so `client.ts` keeps the literal `composed = false` as `run()`'s default, and `test/flags.test.ts` pins that literal to this table's default.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ export declare const ciceroFlags: {
12
+ readonly owner: string;
13
+ readonly table: {
14
+ /** Default `false`: the browser sets the lines and Cicero reads what it produced. `true` gives them to the Compositor (*the composed mode reaches the operator*, 2026-09-10). */
15
+ composed: {
16
+ values: boolean[];
17
+ default: boolean;
18
+ };
19
+ };
20
+ readonly defaults: import("@beltype/common/flags").FlagValues<{
21
+ /** Default `false`: the browser sets the lines and Cicero reads what it produced. `true` gives them to the Compositor (*the composed mode reaches the operator*, 2026-09-10). */
22
+ composed: {
23
+ values: boolean[];
24
+ default: boolean;
25
+ };
26
+ }>;
27
+ resolve(given?: Partial<import("@beltype/common/flags").FlagValues<{
28
+ /** Default `false`: the browser sets the lines and Cicero reads what it produced. `true` gives them to the Compositor (*the composed mode reaches the operator*, 2026-09-10). */
29
+ composed: {
30
+ values: boolean[];
31
+ default: boolean;
32
+ };
33
+ }>> | undefined): import("@beltype/common/flags").FlagValues<{
34
+ /** Default `false`: the browser sets the lines and Cicero reads what it produced. `true` gives them to the Compositor (*the composed mode reaches the operator*, 2026-09-10). */
35
+ composed: {
36
+ values: boolean[];
37
+ default: boolean;
38
+ };
39
+ }>;
40
+ parse(name: string, raw: string): unknown;
41
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Cicero's run-wide flags, in `@beltype/common`'s shape.
3
+ *
4
+ * @remarks
5
+ * One today: `composed`. The other options of a `run()` call — `until`, `resumeAt` and `layer` — belong to a single call, not to a run, and are not flags; nor is a constant someone may want to tune later, until someone asks.
6
+ *
7
+ * This is the node face's table. The client runs in a tab with no way to resolve `@beltype/common`, so `client.ts` keeps the literal `composed = false` as `run()`'s default, and `test/flags.test.ts` pins that literal to this table's default.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ import { defineFlags } from '@beltype/common/flags';
12
+ export const ciceroFlags = defineFlags('cicero', {
13
+ /** Default `false`: the browser sets the lines and Cicero reads what it produced. `true` gives them to the Compositor (*the composed mode reaches the operator*, 2026-09-10). */
14
+ composed: { values: [false, true], default: false }
15
+ });
@@ -2,9 +2,10 @@
2
2
  * The host face, `@beltype/cicero`: what a host that is not BelType's CLI needs to paginate a document with Cicero.
3
3
  *
4
4
  * @remarks
5
- * `ciceroEngine` is the engine's side of BelType's engine seam (its head, its bootstrap, its vendored assets); `rewriteDocumentSheets` makes a document's sheets Cicero's (the `@page` world renamed, optionally under a layer the host names). The page-side API is the client handle, `window.__beltypeCicero`, typed in `seam/handle.d.ts`.
5
+ * `ciceroEngine` is the engine's side of BelType's engine seam (its head, its bootstrap, its vendored assets); `rewriteDocumentSheets` makes a document's sheets Cicero's (the `@page` world renamed, optionally under a layer the host names). A host that needs one of them alone imports the subpath (`@beltype/cicero/engine`, `@beltype/cicero/flags`, `@beltype/cicero/rewrite`), since this face loads all three and the sheet rewriter brings css-tree with it (about 24 ms in node). The page-side API is the client handle, `window.__beltypeCicero`, typed in `seam/handle.d.ts`.
6
6
  *
7
7
  * @packageDocumentation
8
8
  */
9
9
  export { ciceroEngine } from './index.ts';
10
10
  export { rewriteDocumentSheets } from './rewrite-document.ts';
11
+ export { ciceroFlags } from './flags.ts';
package/dist/seam/host.js CHANGED
@@ -2,9 +2,10 @@
2
2
  * The host face, `@beltype/cicero`: what a host that is not BelType's CLI needs to paginate a document with Cicero.
3
3
  *
4
4
  * @remarks
5
- * `ciceroEngine` is the engine's side of BelType's engine seam (its head, its bootstrap, its vendored assets); `rewriteDocumentSheets` makes a document's sheets Cicero's (the `@page` world renamed, optionally under a layer the host names). The page-side API is the client handle, `window.__beltypeCicero`, typed in `seam/handle.d.ts`.
5
+ * `ciceroEngine` is the engine's side of BelType's engine seam (its head, its bootstrap, its vendored assets); `rewriteDocumentSheets` makes a document's sheets Cicero's (the `@page` world renamed, optionally under a layer the host names). A host that needs one of them alone imports the subpath (`@beltype/cicero/engine`, `@beltype/cicero/flags`, `@beltype/cicero/rewrite`), since this face loads all three and the sheet rewriter brings css-tree with it (about 24 ms in node). The page-side API is the client handle, `window.__beltypeCicero`, typed in `seam/handle.d.ts`.
6
6
  *
7
7
  * @packageDocumentation
8
8
  */
9
9
  export { ciceroEngine } from './index.js';
10
10
  export { rewriteDocumentSheets } from './rewrite-document.js';
11
+ export { ciceroFlags } from './flags.js';
@@ -9,6 +9,8 @@
9
9
  *
10
10
  * **With a layer named, every sheet of the document goes into it** (*the author's sheets in a layer the host names*, 2026-10-17): a `<style>` holds its text wrapped (`wrapInLayer`), and every `<link>` becomes a `<style>` of its wrapped text at the same position. **The address is not kept, on a measurement**: `<style>@import url(<the href>) layer(<name>)</style>` would keep it, and Chromium fetches an imported sheet once for every frame that imports it, but it parses it again in each (measured 2026-10-17 in the studio experiment, an 11 MB sheet in four same-origin frames: +9 MB a further frame linked, +61–74 MB imported), so an import buys a host no shared copy a `<style>` does not, and it hides the sheet from every walk that reads `document.styleSheets`' top level (`@page`, `@font-face` for a printer, `position: fixed`). A host that shared a linked sheet's parsed copy across frames loses that sharing under a layer, and the loss is the host's to weigh. **And the engine's root line-height provision moves into the engine's own layer**, the first one: unlayered, it would outrank every rule of the author's now-layered sheets, the author's own root line-height among them, where it was written to lose every tie. **And the order of the two layers is stated first in the head**, `@layer <the engine's>, <the host's>;` in the engine's own layers element (made where it is not there yet): a layer's place is where it is first named, and a host that seats the author's sheets before the engine's head (the HTML lane, the studio, its print frame) would otherwise name the author's layer before the engine's, putting the engine's `!important` trimming below an author's and its provision above the author's normal rules. Without a layer nothing here changes.
11
11
  *
12
+ * **A replaced `<link>`'s relative addresses are written against its href** (`rebaseUrls`), because a `<style>` resolves them against the document.
13
+ *
12
14
  * **The `<link>` is replaced, not merely disabled.** A disabled link leaves the browser's own copy of the rules out of the CSSOM but leaves the element in the document, and the next thing to walk `document.styleSheets` would meet two sheets where the author wrote one.
13
15
  *
14
16
  * @packageDocumentation
@@ -9,11 +9,13 @@
9
9
  *
10
10
  * **With a layer named, every sheet of the document goes into it** (*the author's sheets in a layer the host names*, 2026-10-17): a `<style>` holds its text wrapped (`wrapInLayer`), and every `<link>` becomes a `<style>` of its wrapped text at the same position. **The address is not kept, on a measurement**: `<style>@import url(<the href>) layer(<name>)</style>` would keep it, and Chromium fetches an imported sheet once for every frame that imports it, but it parses it again in each (measured 2026-10-17 in the studio experiment, an 11 MB sheet in four same-origin frames: +9 MB a further frame linked, +61–74 MB imported), so an import buys a host no shared copy a `<style>` does not, and it hides the sheet from every walk that reads `document.styleSheets`' top level (`@page`, `@font-face` for a printer, `position: fixed`). A host that shared a linked sheet's parsed copy across frames loses that sharing under a layer, and the loss is the host's to weigh. **And the engine's root line-height provision moves into the engine's own layer**, the first one: unlayered, it would outrank every rule of the author's now-layered sheets, the author's own root line-height among them, where it was written to lose every tie. **And the order of the two layers is stated first in the head**, `@layer <the engine's>, <the host's>;` in the engine's own layers element (made where it is not there yet): a layer's place is where it is first named, and a host that seats the author's sheets before the engine's head (the HTML lane, the studio, its print frame) would otherwise name the author's layer before the engine's, putting the engine's `!important` trimming below an author's and its provision above the author's normal rules. Without a layer nothing here changes.
11
11
  *
12
+ * **A replaced `<link>`'s relative addresses are written against its href** (`rebaseUrls`), because a `<style>` resolves them against the document.
13
+ *
12
14
  * **The `<link>` is replaced, not merely disabled.** A disabled link leaves the browser's own copy of the rules out of the CSSOM but leaves the element in the document, and the next thing to walk `document.styleSheets` would meet two sheets where the author wrote one.
13
15
  *
14
16
  * @packageDocumentation
15
17
  */
16
- import { rewriteSheet, rewriteNote, isLayerName } from './rewrite.js';
18
+ import { rewriteSheet, rewriteNote, isLayerName, rebaseUrls } from './rewrite.js';
17
19
  import { CICERO_INTERFACE, CICERO_PROVISION, LAYER as ENGINE_LAYER, WORDS_RULES, hasWord } from '../src/data-beltype.js';
18
20
  import { LAYERS_ID } from '../src/dressing.js';
19
21
  /** Is this URL on the document's own origin? A relative href resolves against the document, so the constructor answers both cases at once. */
@@ -112,10 +114,12 @@ export async function rewriteDocumentSheets(doc, judged = new WeakMap(), { layer
112
114
  judged.set(node, out.text); // the text the next run will find there
113
115
  return;
114
116
  }
117
+ // A `<style>` resolves its addresses against the document, so the sheet's own are written out first.
118
+ const text = rebaseUrls(out.text, new URL(node.getAttribute('href'), doc.baseURI).href);
115
119
  const style = doc.createElement('style');
116
- style.textContent = out.text;
120
+ style.textContent = text;
117
121
  node.replaceWith(style); // in place, so the sheet keeps its seat in the cascade
118
- judged.set(style, out.text);
122
+ judged.set(style, text);
119
123
  });
120
124
  const note = rewriteNote(counts);
121
125
  if (note)
@@ -74,5 +74,15 @@ export declare function wrapInLayer(source: string, layer: string): {
74
74
  text: string;
75
75
  wrapped: boolean;
76
76
  };
77
+ /**
78
+ * A linked sheet's relative addresses, resolved against its own href, for the `<style>` that takes its place (`rewrite-document.js`).
79
+ *
80
+ * @remarks A `<style>` resolves `url()` against the document, a `<link>` against the sheet: once a sheet in `css/` is moved into a `<style>`, `url("./font.ttf")` asks for the document's `./font.ttf` (the tester's case, 2026-11-03: a font inside `@media print`, whose opening made the sheet a `<style>`). Paged.js resolves a sheet it inlines the same way. Every `url()` and every `@import "…"` string is written absolute, in place, so no line moves; a sheet css-tree cannot parse is returned as it is.
81
+ *
82
+ * ponytail: `url()` and the `@import` string only. A url in a custom property's value (kept raw) or a string in `image-set()` still resolves against the document; add them when a document needs them.
83
+ *
84
+ * @param base - the absolute href the sheet was fetched from.
85
+ */
86
+ export declare function rebaseUrls(source: string, base: string): string;
77
87
  /** The console line a host says once per document it rewrote — what moved and why a reader of the sheet will meet two strange forms. */
78
88
  export declare function rewriteNote(counts: Partial<Record<string, number>>): string | null;
@@ -522,6 +522,38 @@ function splitPageSelectorLists(source) {
522
522
  });
523
523
  return { text: applyEdits(source, edits), split: edits.length };
524
524
  }
525
+ /** An address the sheet's own href is the base of: not one with a scheme (`data:`, `https:`), and not a fragment, which names the document (`clip-path: url(#c)`). */
526
+ const isRelative = (url) => url !== '' && !url.startsWith('#') && !/^[a-z][a-z\d+.-]*:/i.test(url);
527
+ /**
528
+ * A linked sheet's relative addresses, resolved against its own href, for the `<style>` that takes its place (`rewrite-document.js`).
529
+ *
530
+ * @remarks A `<style>` resolves `url()` against the document, a `<link>` against the sheet: once a sheet in `css/` is moved into a `<style>`, `url("./font.ttf")` asks for the document's `./font.ttf` (the tester's case, 2026-11-03: a font inside `@media print`, whose opening made the sheet a `<style>`). Paged.js resolves a sheet it inlines the same way. Every `url()` and every `@import "…"` string is written absolute, in place, so no line moves; a sheet css-tree cannot parse is returned as it is.
531
+ *
532
+ * ponytail: `url()` and the `@import` string only. A url in a custom property's value (kept raw) or a string in `image-set()` still resolves against the document; add them when a document needs them.
533
+ *
534
+ * @param base - the absolute href the sheet was fetched from.
535
+ */
536
+ export function rebaseUrls(source, base) {
537
+ let ast;
538
+ try {
539
+ ast = parse(source, SHEET_OPTIONS);
540
+ }
541
+ catch {
542
+ return source;
543
+ }
544
+ const edits = [];
545
+ const absolute = (url) => `"${new URL(url, base).href}"`;
546
+ walkStyled(ast, source, SHEET_OPTIONS, {
547
+ enter(node) {
548
+ if (node.type === 'Url' && node.loc && isRelative(node.value))
549
+ edits.push({ start: node.loc.start.offset, end: node.loc.end.offset, text: `url(${absolute(node.value)})` });
550
+ const first = node.type === 'Atrule' && String(node.name).toLowerCase() === 'import' ? node.prelude?.children?.first : null;
551
+ if (first?.type === 'String' && first.loc && isRelative(first.value))
552
+ edits.push({ start: first.loc.start.offset, end: first.loc.end.offset, text: absolute(first.value) });
553
+ }
554
+ });
555
+ return applyEdits(source, edits);
556
+ }
525
557
  /** The console line a host says once per document it rewrote — what moved and why a reader of the sheet will meet two strange forms. */
526
558
  export function rewriteNote(counts) {
527
559
  const parts = [];
package/dist/src/chase.js CHANGED
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * @packageDocumentation
17
17
  */
18
- import { MARGIN_CELLS, ALIGN_X, ALIGN_Y, HORIZONTAL_BANDS, bandWidths, translateAlignment } from './margin-boxes.js';
18
+ import { MARGIN_CELLS, ALIGN_X, ALIGN_Y, HORIZONTAL_BANDS, bandTracks, bandWidths, translateAlignment } from './margin-boxes.js';
19
19
  import { counterLevels } from './document-state.js';
20
20
  import { encodeStringProp, STRING_KEYWORDS } from './gcpm-encoding.js';
21
21
  import { cssString } from './reads.js';
@@ -186,9 +186,9 @@ export class BeltypePage extends HTMLElement {
186
186
  this.style.containIntrinsicSize = `${pageW}px ${pageH - 1}px`;
187
187
  this.#bandWidth = pageW - margins.left - margins.right;
188
188
  this.#pageH = pageH;
189
- const cw3 = this.#bandWidth / 3;
189
+ const [wa, wb, wc] = bandTracks(this.#bandWidth);
190
190
  const ch3 = (pageH - margins.top - margins.bottom) / 3;
191
- this.style.gridTemplateColumns = `${margins.left}px ${cw3}px ${cw3}px ${cw3}px ${margins.right}px`;
191
+ this.style.gridTemplateColumns = `${margins.left}px ${wa}px ${wb}px ${wc}px ${margins.right}px`;
192
192
  this.style.gridTemplateRows = `${margins.top}px ${ch3}px ${ch3}px ${ch3}px ${Math.max(0, margins.bottom - 1)}px`;
193
193
  }
194
194
  /**
@@ -35,6 +35,13 @@ export declare const MARGIN_BOXES: string[];
35
35
  */
36
36
  export declare const ALIGN_X: Record<string, string>;
37
37
  export declare const ALIGN_Y: Record<string, string>;
38
+ /**
39
+ * The three tracks the page grid gives the band between its margins, in CSS px.
40
+ *
41
+ * @remarks
42
+ * Chromium lays a length out in 1/64 px and drops the remainder of every track on its own, so three thirds of 423.307 px are three tracks of 141.09375 and sum to 423.28125 px, while the measure container, one box of that width, floors the whole to 423.296875. A line that fits the one and not the other is measured on one line and shown on two (specimen `trattato`, page 111). The tracks are therefore whole 1/64 px units that sum to the floor of the band, the last one taking the remainder: no third of a unit is left for the grid to drop, and the content area is as wide as the container it was measured in.
43
+ */
44
+ export declare function bandTracks(band: number): [number, number, number];
38
45
  /** The three boxes of each horizontal band, in band order — what `bandWidths` sizes, and the only membership test `chase.js` needs to know which cells leave the outer grid. */
39
46
  export declare const HORIZONTAL_BANDS: {
40
47
  top: string[];
@@ -41,6 +41,17 @@ export const MARGIN_BOXES = MARGIN_CELLS.map(([name]) => name);
41
41
  */
42
42
  export const ALIGN_X = { left: 'flex-start', start: 'flex-start', right: 'flex-end', end: 'flex-end', center: 'center', justify: 'flex-start' };
43
43
  export const ALIGN_Y = { top: 'flex-start', 'text-top': 'flex-start', middle: 'center', bottom: 'flex-end', 'text-bottom': 'flex-end' };
44
+ /**
45
+ * The three tracks the page grid gives the band between its margins, in CSS px.
46
+ *
47
+ * @remarks
48
+ * Chromium lays a length out in 1/64 px and drops the remainder of every track on its own, so three thirds of 423.307 px are three tracks of 141.09375 and sum to 423.28125 px, while the measure container, one box of that width, floors the whole to 423.296875. A line that fits the one and not the other is measured on one line and shown on two (specimen `trattato`, page 111). The tracks are therefore whole 1/64 px units that sum to the floor of the band, the last one taking the remainder: no third of a unit is left for the grid to drop, and the content area is as wide as the container it was measured in.
49
+ */
50
+ export function bandTracks(band) {
51
+ const units = Math.floor(band * 64);
52
+ const third = Math.floor(units / 3);
53
+ return [third / 64, third / 64, (units - 2 * third) / 64];
54
+ }
44
55
  /** The three boxes of each horizontal band, in band order — what `bandWidths` sizes, and the only membership test `chase.js` needs to know which cells leave the outer grid. */
45
56
  export const HORIZONTAL_BANDS = {
46
57
  top: ['top-left', 'top-center', 'top-right'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beltype/cicero",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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",
@@ -20,6 +20,14 @@
20
20
  "types": "./dist/seam/host.d.ts",
21
21
  "import": "./dist/seam/host.js"
22
22
  },
23
+ "./engine": {
24
+ "types": "./dist/seam/index.d.ts",
25
+ "import": "./dist/seam/index.js"
26
+ },
27
+ "./flags": {
28
+ "types": "./dist/seam/flags.d.ts",
29
+ "import": "./dist/seam/flags.js"
30
+ },
23
31
  "./rewrite": {
24
32
  "types": "./dist/seam/rewrite-document.d.ts",
25
33
  "import": "./dist/seam/rewrite-document.js"
@@ -53,7 +61,7 @@
53
61
  "prepare": "npm run build"
54
62
  },
55
63
  "dependencies": {
56
- "@beltype/common": "0.1.1",
64
+ "@beltype/common": "0.1.2",
57
65
  "css-tree": "^3.2.1",
58
66
  "pdfjs-dist": "6.2.108"
59
67
  },