@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/CHANGELOG.md +84 -0
- package/LICENSE +219 -0
- package/README.md +94 -0
- package/lib/balance.js +90 -0
- package/lib/box.js +143 -0
- package/lib/canvas.js +335 -0
- package/lib/fonts.js +409 -0
- package/lib/image.js +58 -0
- package/lib/index.d.ts +220 -0
- package/lib/index.js +114 -0
- package/lib/layout.js +1913 -0
- package/lib/page.js +99 -0
- package/lib/paint.js +266 -0
- package/lib/style.js +104 -0
- package/lib/text.js +258 -0
- package/package.json +71 -0
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
|
+
}
|