@quario/layout 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js ADDED
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @quario/layout — the paged layout of a report, as a display list. Not a
3
+ * document format: `layout(options)` is a target whose output is pages of
4
+ * boxes, text runs, rules and images in points, and two painters consume
5
+ * that list — `@quario/pdf` writes it through pdf-lib, and `paint` here draws
6
+ * it on a Canvas 2D context for the viewer and the editor
7
+ * (docs/adr/0039-preview-pages-from-a-shared-layout-list.md).
8
+ *
9
+ * The algorithm is the PDF target's typesetter — wrapped paragraphs,
10
+ * keep-together, repeated table headers, page bands, `break: "page"`,
11
+ * `reset: "page"` — with its home moved so pdf-lib does not follow the
12
+ * preview. Node-safe: nothing here reaches pdf-lib or a browser canvas, and
13
+ * measurement is this package's own (`fonts.js`), so a preview and a PDF
14
+ * given the same `page` and the same font bytes break their lines in the same
15
+ * places.
16
+ *
17
+ * This file is the target's options, the walk itself, and the passes that
18
+ * follow it over the finished pages — page furniture and the marking, neither
19
+ * of which can be drawn until the page count is known. Every handler the
20
+ * driver dispatches to is the band flow's, next door in `layout.js`; this
21
+ * file adds none. The package publishes `lib/` verbatim.
22
+ */
23
+ import { breathe, walk } from "quario";
24
+ import { listing, stamp } from "./canvas.js";
25
+ import { checkFonts, loadFonts } from "./fonts.js";
26
+ import { flow, furniture } from "./layout.js";
27
+ import { geometry } from "./page.js";
28
+
29
+ export { PX_PER_POINT, hit, paint } from "./paint.js";
30
+ export { pageBox } from "./page.js";
31
+
32
+ // The options are described once, in the hand-written public declarations, and
33
+ // read back here — a second copy in JSDoc is a copy that drifts.
34
+ /** @import { Layout, LayoutOptions } from './index.d.ts' */
35
+
36
+ /** @type {(canvas: import('./canvas.js').Listing, draw: (i: number) => void) => Promise<void>} */
37
+ let overPages = async (canvas, draw) => {
38
+ for (let i = 0; i < canvas.count; i++) {
39
+ canvas.select(i);
40
+ draw(i);
41
+ if (i % 50 === 49) await breathe();
42
+ }
43
+ };
44
+
45
+ /** @type {(canvas: import('./canvas.js').Listing, bands: any,
46
+ * pages: { number: number, total: number }[]) => Promise<void>} */
47
+ let furnish = async (canvas, bands, pages) => {
48
+ if (!bands) return;
49
+ await overPages(canvas, (i) => furniture(canvas, bands, pages[i]));
50
+ };
51
+
52
+ /** @type {(canvas: import('./canvas.js').Listing, marking: any) => Promise<void>} */
53
+ let markPages = async (canvas, marking) => {
54
+ if (!marking) return;
55
+ let mark = stamp(canvas, marking);
56
+ await overPages(canvas, () => canvas.watermark(mark));
57
+ };
58
+
59
+ /**
60
+ * The layout target:
61
+ * `quario().report(schema).render(layout({ page }), data)` resolves the
62
+ * display list — every page of the document, with its `{ number, total }`,
63
+ * its ops and its hit boxes. Geometry and the font mapping's shape are
64
+ * validated here, at the factory call.
65
+ *
66
+ * @param {LayoutOptions} [options] Host controls (see SCHEMA.md, "The PDF target").
67
+ * @returns {{name: "layout", compile: (stream: any) => (data?: any) => Promise<Layout>}}
68
+ * The target (see SCHEMA.md, "Instances and targets").
69
+ */
70
+ export function layout(options) {
71
+ geometry(options?.page);
72
+ checkFonts(options?.fonts);
73
+ let custom = options?.fonts;
74
+ /** @type {(stream: any) => (data?: any) => Promise<Layout>} */
75
+ let compile = (stream) => async (data) => {
76
+ // Every face is loaded before the walk, so resolving a style to a face
77
+ // during layout stays synchronous.
78
+ let fonts = await loadFonts(custom);
79
+ let gen = stream(data);
80
+ let first = gen.next();
81
+ let geo = geometry(options?.page, first.done ? null : first.value);
82
+ let canvas = listing(geo, fonts);
83
+ // The band flow owns the placement state and every handler over it, and
84
+ // opens the first page as it is built; this file only hands it the stream.
85
+ let { handlers, finish } = flow(canvas);
86
+ function* events() {
87
+ if (!first.done) yield first.value;
88
+ yield* gen;
89
+ }
90
+ await walk(events(), handlers);
91
+ // The flow settled the opening event on its way past — it reserves the
92
+ // page bands off it — and hands it back with the marks. What is left on it
93
+ // is what the passes below want: the band closures to render per page, and
94
+ // the marking's wording to stamp.
95
+ // Null when nothing settled one, which reads as a document owing neither.
96
+ let { marks, opening, pages } = finish();
97
+ opening = opening || {};
98
+ // The passes below run over the finished pages, not the stream — no walk
99
+ // at all — so they open each page themselves and breathe on their own
100
+ // rather than through the driver.
101
+ await furnish(canvas, opening.page, pages);
102
+ // The unlicensed marking goes on last, over content and page furniture
103
+ // alike, once per page (LICENSE section 6). Its wording rode in on
104
+ // `report-start`; only the placement is this layout's.
105
+ await markPages(canvas, opening.marking);
106
+ return /** @type {Layout} */ ({
107
+ width: geo.width,
108
+ height: geo.height,
109
+ pages: canvas.pages.map((page, i) => ({ ...page, ...pages[i] })),
110
+ marks,
111
+ });
112
+ };
113
+ return { name: "layout", compile };
114
+ }