@quario/layout 0.1.0 → 0.2.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 +50 -0
- package/README.md +1 -1
- package/lib/box.js +5 -1
- package/lib/canvas.js +105 -49
- package/lib/fonts.js +16 -31
- package/lib/index.d.ts +7 -1
- package/lib/index.js +2 -2
- package/lib/layout.js +297 -220
- package/lib/page.js +1 -7
- package/lib/paint.js +9 -4
- package/lib/style.js +25 -2
- package/lib/text.js +20 -26
- package/package.json +7 -6
package/lib/page.js
CHANGED
|
@@ -49,12 +49,6 @@ let marginOf = (margin = 54, width, height, path) => {
|
|
|
49
49
|
return margin;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
// This layout's baseline type size. Not a host option: a document's type size
|
|
53
|
-
// is the document's own, so it is `style.size` on the report and the number
|
|
54
|
-
// here is only what text renders at when nothing declares one. The XLSX target
|
|
55
|
-
// carries the same 10 for the same reason (docs/adr/0014, docs/adr/0033).
|
|
56
|
-
let BASE = 10;
|
|
57
|
-
|
|
58
52
|
// Size is settled at the factory; margin may come from the document's
|
|
59
53
|
// `report-start`, so a render peeks that event before opening a canvas.
|
|
60
54
|
/** @type {(page: any) => any} */
|
|
@@ -95,5 +89,5 @@ export let pageBox = (page, at = "page") => {
|
|
|
95
89
|
export let geometry = (page = {}, opening) => {
|
|
96
90
|
let { width, height } = pageBox(page, "options.page");
|
|
97
91
|
let chosen = marginChoice(opening, page, "options.page");
|
|
98
|
-
return frame(width, height, marginOf(chosen.value, width, height, chosen.path)
|
|
92
|
+
return frame(width, height, marginOf(chosen.value, width, height, chosen.path));
|
|
99
93
|
};
|
package/lib/paint.js
CHANGED
|
@@ -183,9 +183,6 @@ let paintText = (ctx, op) => {
|
|
|
183
183
|
}
|
|
184
184
|
};
|
|
185
185
|
|
|
186
|
-
/** @type {(ctx: CanvasRenderingContext2D, op: any, bitmap: ImageBitmap) => void} */
|
|
187
|
-
let paintImage = (ctx, op, bitmap) => ctx.drawImage(bitmap, op.x, op.y, op.w, op.h);
|
|
188
|
-
|
|
189
186
|
// The marking, as the PDF draws it: translucent grey along the page diagonal,
|
|
190
187
|
// turned counter-clockwise by the list's angle. The canvas reads down, so the
|
|
191
188
|
// turn is negative here.
|
|
@@ -207,7 +204,8 @@ let PAINTERS = { rect: paintRect, line: paintLine, text: paintText, mark: paintM
|
|
|
207
204
|
|
|
208
205
|
/** @type {(ctx: CanvasRenderingContext2D, op: Op, bitmaps: Map<Op, ImageBitmap>) => void} */
|
|
209
206
|
let paintOp = (ctx, op, bitmaps) => {
|
|
210
|
-
if (op.kind === "image")
|
|
207
|
+
if (op.kind === "image")
|
|
208
|
+
ctx.drawImage(/** @type {ImageBitmap} */ (bitmaps.get(op)), op.x, op.y, op.w, op.h);
|
|
211
209
|
else PAINTERS[op.kind](ctx, op);
|
|
212
210
|
};
|
|
213
211
|
|
|
@@ -225,6 +223,13 @@ let decode = async (page) => {
|
|
|
225
223
|
* the canvas, so it knows. `fonts` is the host's font mapping, the same
|
|
226
224
|
* record given to `layout()`, so a TrueType family draws in its own face.
|
|
227
225
|
*
|
|
226
|
+
* It awaits its faces and images before it draws, and it draws whatever
|
|
227
|
+
* happened in between: a canvas re-sized under a call still in flight is
|
|
228
|
+
* filled at that call's own `scale`, not the size it now has. A caller that
|
|
229
|
+
* repaints one canvas at changing scales owns that, by not letting a
|
|
230
|
+
* superseded call reach a canvas still on screen — which is what the viewer's
|
|
231
|
+
* stage retires a page for (docs/adr/0046).
|
|
232
|
+
*
|
|
228
233
|
* @param {CanvasRenderingContext2D} ctx The context to paint on.
|
|
229
234
|
* @param {Page} page One page of a `Layout`.
|
|
230
235
|
* @param {{ scale?: number, fonts?: any }} [options]
|
package/lib/style.js
CHANGED
|
@@ -44,6 +44,13 @@ let BLACK = { r: 0, g: 0, b: 0 };
|
|
|
44
44
|
let SHIFT = { right: 1, center: 0.5 };
|
|
45
45
|
/** @type {(align: any, extra: number) => number} */
|
|
46
46
|
let shift = (align, extra) => (Object.hasOwn(SHIFT, align) ? SHIFT[align] : 0) * extra;
|
|
47
|
+
// The same for the leftover height -- the slack a box has over its content,
|
|
48
|
+
// which is the only place `valign` is legal (CONTEXT.md "Box"). Top keeps it,
|
|
49
|
+
// bottom takes all of it, middle takes half; undeclared is top.
|
|
50
|
+
/** @type {Record<string, number>} */
|
|
51
|
+
let VSHIFT = { bottom: 1, middle: 0.5 };
|
|
52
|
+
/** @type {(valign: any, extra: number) => number} */
|
|
53
|
+
let vshift = (valign, extra) => (Object.hasOwn(VSHIFT, valign) ? VSHIFT[valign] : 0) * extra;
|
|
47
54
|
|
|
48
55
|
// A declared colour as a `Color`, or null when the value is not one.
|
|
49
56
|
/** @type {(value: any) => Color | null} */
|
|
@@ -62,7 +69,8 @@ let sizeOf = (style, base) => (Number.isFinite(style.size) && style.size > 0 ? s
|
|
|
62
69
|
// Style blocks layer outward-in: row under cell, split under slot. Those are
|
|
63
70
|
// the two innermost of the four layers; the outer two reach a node without
|
|
64
71
|
// being merged into it -- the band-role default through `roled` below, and the
|
|
65
|
-
// report default through the
|
|
72
|
+
// report default through the render's settings, as `canvas.js`'s
|
|
73
|
+
// `adoptSettings` explains.
|
|
66
74
|
/** @type {(under: any, over: any) => any} */
|
|
67
75
|
let merge = (under, over) => (under ? (over ? { ...under, ...over } : under) : over || {});
|
|
68
76
|
|
|
@@ -101,4 +109,19 @@ let dressed = (style) => !!(style && (style.underline || style.strikethrough));
|
|
|
101
109
|
/** @type {(style: any) => boolean} */
|
|
102
110
|
let upper = (style) => !!(style && style.uppercase);
|
|
103
111
|
|
|
104
|
-
export {
|
|
112
|
+
export {
|
|
113
|
+
BAND,
|
|
114
|
+
BLACK,
|
|
115
|
+
GUTTER,
|
|
116
|
+
LEAD,
|
|
117
|
+
PADX,
|
|
118
|
+
PADY,
|
|
119
|
+
col,
|
|
120
|
+
dressed,
|
|
121
|
+
merge,
|
|
122
|
+
roled,
|
|
123
|
+
shift,
|
|
124
|
+
sizeOf,
|
|
125
|
+
upper,
|
|
126
|
+
vshift,
|
|
127
|
+
};
|
package/lib/text.js
CHANGED
|
@@ -7,15 +7,12 @@ import { display, format } from "quario";
|
|
|
7
7
|
import { ascOf, face, printable, width } from "./fonts.js";
|
|
8
8
|
import { LEAD, col, dressed, sizeOf, upper } from "./style.js";
|
|
9
9
|
|
|
10
|
-
// What measuring needs and no more
|
|
11
|
-
// the base size and family a style falls back to
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {{ fonts: import('./fonts.js').Fonts, base: number,
|
|
17
|
-
* family: string | null, locale?: string, currency?: string, timeZone?: string }} Metrics
|
|
18
|
-
*/
|
|
10
|
+
// What measuring needs and no more, and it is exactly the render's settings:
|
|
11
|
+
// the faces to measure against, the base size and family a style falls back to,
|
|
12
|
+
// and the intl three a formatted value resolves in. Declared next door, in
|
|
13
|
+
// `canvas.js`, where it is built and settled — nothing here can touch a page,
|
|
14
|
+
// and a caller hands over `canvas.settings` rather than the canvas.
|
|
15
|
+
/** @typedef {import('./canvas.js').Settings} Settings */
|
|
19
16
|
|
|
20
17
|
/**
|
|
21
18
|
* @typedef {{ text: string, font: any, size: number, color: any,
|
|
@@ -29,10 +26,10 @@ import { LEAD, col, dressed, sizeOf, upper } from "./style.js";
|
|
|
29
26
|
|
|
30
27
|
/** @typedef {{ cur: Atom[], w: number, lines: Line[], base: number }} Wrap */
|
|
31
28
|
|
|
32
|
-
/** @type {(token: any, style: any,
|
|
33
|
-
let rawOf = (token, style,
|
|
29
|
+
/** @type {(token: any, style: any, settings: Settings) => string} */
|
|
30
|
+
let rawOf = (token, style, settings) => {
|
|
34
31
|
if ("literal" in token) return token.literal;
|
|
35
|
-
return format(token.value, style?.format,
|
|
32
|
+
return format(token.value, style?.format, settings) ?? display(token.value);
|
|
36
33
|
};
|
|
37
34
|
|
|
38
35
|
// This layout has no text-transform to defer to, so `uppercase` is applied to
|
|
@@ -43,12 +40,9 @@ let rawOf = (token, style, metrics) => {
|
|
|
43
40
|
/** @type {(text: string, style: any) => string} */
|
|
44
41
|
let cased = (text, style) => (upper(style) ? text.toUpperCase() : text);
|
|
45
42
|
|
|
46
|
-
/** @type {(font: any, line: string) => string[]} */
|
|
47
|
-
let partsOf = (font, line) => printable(font, line).split(/( +)/).filter(Boolean);
|
|
48
|
-
|
|
49
43
|
/** @type {(out: Atom[], font: any, size: number, color: any, line: string) => void} */
|
|
50
44
|
let pushParts = (out, font, size, color, line) => {
|
|
51
|
-
for (let part of
|
|
45
|
+
for (let part of printable(font, line).split(/( +)/).filter(Boolean))
|
|
52
46
|
out.push({ text: part, font, size, color, space: part[0] === " ", hard: false });
|
|
53
47
|
};
|
|
54
48
|
|
|
@@ -58,12 +52,12 @@ let pushLine = (out, font, size, color, i, line) => {
|
|
|
58
52
|
pushParts(out, font, size, color, line);
|
|
59
53
|
};
|
|
60
54
|
|
|
61
|
-
/** @type {(
|
|
62
|
-
let look = (
|
|
55
|
+
/** @type {(settings: Settings, style: any) => { font: any, size: number, color: any }} */
|
|
56
|
+
let look = (settings, style) => {
|
|
63
57
|
let resolved = style || {};
|
|
64
58
|
return {
|
|
65
|
-
font: face(
|
|
66
|
-
size: sizeOf(resolved,
|
|
59
|
+
font: face(settings.fonts, resolved, settings.family),
|
|
60
|
+
size: sizeOf(resolved, settings.base),
|
|
67
61
|
color: col(resolved.color),
|
|
68
62
|
};
|
|
69
63
|
};
|
|
@@ -71,12 +65,12 @@ let look = (metrics, style) => {
|
|
|
71
65
|
// Flatten a cell's tokens to word/space atoms carrying the cell's resolved
|
|
72
66
|
// typography — one face, size and colour for the whole cell. CR, LF, and
|
|
73
67
|
// CRLF are one hard break each (SCHEMA.md, Cell values).
|
|
74
|
-
/** @type {(
|
|
75
|
-
let atoms = (
|
|
68
|
+
/** @type {(settings: Settings, tokens: any[], style: any) => Atom[]} */
|
|
69
|
+
let atoms = (settings, tokens, style) => {
|
|
76
70
|
let out = /** @type {Atom[]} */ ([]);
|
|
77
|
-
let { font, size, color } = look(
|
|
71
|
+
let { font, size, color } = look(settings, style);
|
|
78
72
|
for (let token of tokens)
|
|
79
|
-
for (let [i, line] of cased(rawOf(token, style,
|
|
73
|
+
for (let [i, line] of cased(rawOf(token, style, settings), style)
|
|
80
74
|
.split(/\r\n|\r|\n/)
|
|
81
75
|
.entries())
|
|
82
76
|
pushLine(out, font, size, color, i, line);
|
|
@@ -228,8 +222,8 @@ let placeAtom = (state, atom, avail) => {
|
|
|
228
222
|
// breaks by character, hard breaks always break. A cell's atoms share one
|
|
229
223
|
// typography, so a line's atoms merge into a single draw piece. `size` is the
|
|
230
224
|
// empty-run height — a blank item, a hard-break hole.
|
|
231
|
-
/** @type {(
|
|
232
|
-
let wrap = (
|
|
225
|
+
/** @type {(list: Atom[], avail: number, size: number) => Line[]} */
|
|
226
|
+
let wrap = (list, avail, size) => {
|
|
233
227
|
/** @type {Wrap} */
|
|
234
228
|
let state = { cur: [], w: 0, lines: [], base: size };
|
|
235
229
|
for (let atom of list) placeAtom(state, atom, avail);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quario/layout",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The paged display list for quario — the layout the PDF target writes and the viewer paints — in the makings, not yet released",
|
|
5
5
|
"homepage": "https://getquario.com",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -40,18 +40,19 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@arethetypeswrong/cli": "^0.18.3",
|
|
43
|
-
"@pdf-lib/fontkit": "^1.1.1",
|
|
44
43
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
45
|
-
"
|
|
44
|
+
"@types/fontkit": "^2.0.9",
|
|
45
|
+
"fontkit": "^2.0.4",
|
|
46
|
+
"quario": "^0.5.0",
|
|
46
47
|
"size-limit": "^13.0.3",
|
|
47
48
|
"typescript": "^7.0.2"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
|
-
"
|
|
51
|
-
"quario": "^0.
|
|
51
|
+
"fontkit": "^2.0.4",
|
|
52
|
+
"quario": "^0.5.0"
|
|
52
53
|
},
|
|
53
54
|
"peerDependenciesMeta": {
|
|
54
|
-
"
|
|
55
|
+
"fontkit": {
|
|
55
56
|
"optional": true
|
|
56
57
|
}
|
|
57
58
|
},
|