@quario/layout 0.1.0 → 0.3.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 +95 -0
- package/README.md +1 -1
- package/lib/box.js +5 -1
- package/lib/canvas.js +38 -63
- package/lib/fonts.js +16 -31
- package/lib/image.js +1 -1
- package/lib/index.d.ts +17 -3
- package/lib/index.js +17 -11
- package/lib/layout.js +536 -299
- package/lib/page.js +41 -9
- package/lib/paint.js +37 -9
- package/lib/settings.js +88 -0
- package/lib/style.js +24 -2
- package/lib/text.js +20 -26
- package/package.json +7 -6
package/lib/page.js
CHANGED
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
* it is written. Owned here because every consumer of the list — the PDF
|
|
4
4
|
* target, the viewer, the editor — lays out on the same page, and a size that
|
|
5
5
|
* meant one thing on screen and another on paper would be a silent lie.
|
|
6
|
+
*
|
|
7
|
+
* The derivation belongs with the validation: `frame` below turns a validated
|
|
8
|
+
* page and margin into the geometry everything downstream lays out in, so a
|
|
9
|
+
* page becomes a content box here and `canvas.js` only takes the answer,
|
|
10
|
+
* rather than the drawing surface defining the page it draws on. Deriving a
|
|
11
|
+
* frame is not owning its lifetime, and this module claims only the first:
|
|
12
|
+
* `adopt` (`canvas.js`) narrows `top`/`bottom` once for the page bands, and
|
|
13
|
+
* `reserve` (`layout.js`) mints the frame it narrows from.
|
|
6
14
|
*/
|
|
7
|
-
import { frame } from "./canvas.js";
|
|
8
15
|
|
|
9
16
|
// The named sizes. `@quario/pdf` used to own this table, and the two element
|
|
10
17
|
// packages restated it; the layout package is where all three now read it.
|
|
@@ -49,12 +56,6 @@ let marginOf = (margin = 54, width, height, path) => {
|
|
|
49
56
|
return margin;
|
|
50
57
|
};
|
|
51
58
|
|
|
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
59
|
// Size is settled at the factory; margin may come from the document's
|
|
59
60
|
// `report-start`, so a render peeks that event before opening a canvas.
|
|
60
61
|
/** @type {(page: any) => any} */
|
|
@@ -86,14 +87,45 @@ export let pageBox = (page, at = "page") => {
|
|
|
86
87
|
return { width, height, margin: marginOf(page?.margin, width, height, at + ".margin") };
|
|
87
88
|
};
|
|
88
89
|
|
|
90
|
+
// The page box and the content box, and nothing whatever else: geometry, all
|
|
91
|
+
// of it derived below from a page and a margin. Fixed for the document — the
|
|
92
|
+
// one exception is `top`/`bottom`, which the page bands narrow once through
|
|
93
|
+
// `adopt` (`canvas.js`), while the first page is still untouched. Everything
|
|
94
|
+
// else that holds for a whole render is that module's `Settings`; a frame
|
|
95
|
+
// carrying either half of that was a type with two lifetimes, and the copy that
|
|
96
|
+
// kept a probe reading the same locale as the draw had to be written out by
|
|
97
|
+
// hand.
|
|
98
|
+
/**
|
|
99
|
+
* @typedef {{ width: number, height: number, margin: number,
|
|
100
|
+
* content: number, top: number, bottom: number }} Frame
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
// A frame from the page box: how `content`/`top`/`bottom` fall out of a page
|
|
104
|
+
// and a margin is derived here, once, so no caller and no suite has to restate
|
|
105
|
+
// it and drift from what a real render uses. A render reaches a frame through
|
|
106
|
+
// `geometry` below and never through this; the package entry carries neither,
|
|
107
|
+
// so no host reaches one at all. Exported for `layout.js`, which re-derives the
|
|
108
|
+
// page frame a canvas presents, and for the suites that build a fixture frame
|
|
109
|
+
// — the day that re-derivation goes, the export stands for the suites alone,
|
|
110
|
+
// and they should take `geometry` rather than keep it standing.
|
|
111
|
+
/** @type {(width: number, height: number, margin: number) => Frame} */
|
|
112
|
+
export let frame = (width, height, margin) => ({
|
|
113
|
+
width,
|
|
114
|
+
height,
|
|
115
|
+
margin,
|
|
116
|
+
content: width - 2 * margin,
|
|
117
|
+
top: height - margin,
|
|
118
|
+
bottom: margin,
|
|
119
|
+
});
|
|
120
|
+
|
|
89
121
|
/**
|
|
90
122
|
* The page box and the content box in one, for a render: the host's size,
|
|
91
123
|
* and whichever margin the host and the opening event agreed on.
|
|
92
124
|
*
|
|
93
|
-
* @type {(page: any, opening?: any) =>
|
|
125
|
+
* @type {(page: any, opening?: any) => Frame}
|
|
94
126
|
*/
|
|
95
127
|
export let geometry = (page = {}, opening) => {
|
|
96
128
|
let { width, height } = pageBox(page, "options.page");
|
|
97
129
|
let chosen = marginChoice(opening, page, "options.page");
|
|
98
|
-
return frame(width, height, marginOf(chosen.value, width, height, chosen.path)
|
|
130
|
+
return frame(width, height, marginOf(chosen.value, width, height, chosen.path));
|
|
99
131
|
};
|
package/lib/paint.js
CHANGED
|
@@ -123,7 +123,9 @@ let loadFaces = async (fonts) => {
|
|
|
123
123
|
};
|
|
124
124
|
|
|
125
125
|
// Every bitmap decoded so far, by the bytes it was decoded from: one logo on
|
|
126
|
-
// every page is decoded once.
|
|
126
|
+
// every page is decoded once. A rejection is remembered too, so a file the
|
|
127
|
+
// browser refused is refused from memory ever after — a caller repainting the
|
|
128
|
+
// same page never pays for a second decode of bytes that will not decode.
|
|
127
129
|
/** @type {WeakMap<Uint8Array, Promise<ImageBitmap>>} */
|
|
128
130
|
let BITMAPS = new WeakMap();
|
|
129
131
|
|
|
@@ -183,9 +185,6 @@ let paintText = (ctx, op) => {
|
|
|
183
185
|
}
|
|
184
186
|
};
|
|
185
187
|
|
|
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
188
|
// The marking, as the PDF draws it: translucent grey along the page diagonal,
|
|
190
189
|
// turned counter-clockwise by the list's angle. The canvas reads down, so the
|
|
191
190
|
// turn is negative here.
|
|
@@ -207,16 +206,30 @@ let PAINTERS = { rect: paintRect, line: paintLine, text: paintText, mark: paintM
|
|
|
207
206
|
|
|
208
207
|
/** @type {(ctx: CanvasRenderingContext2D, op: Op, bitmaps: Map<Op, ImageBitmap>) => void} */
|
|
209
208
|
let paintOp = (ctx, op, bitmaps) => {
|
|
210
|
-
|
|
211
|
-
|
|
209
|
+
// An image with no bitmap did not decode, and is drawn as nothing. The map
|
|
210
|
+
// is the one place that is known: `decode` keeps no entry for a file the
|
|
211
|
+
// browser refused, so there is no second flag to read and no way to ask
|
|
212
|
+
// this question twice.
|
|
213
|
+
if (op.kind === "image") {
|
|
214
|
+
let bitmap = bitmaps.get(op);
|
|
215
|
+
if (bitmap) ctx.drawImage(bitmap, op.x, op.y, op.w, op.h);
|
|
216
|
+
} else PAINTERS[op.kind](ctx, op);
|
|
212
217
|
};
|
|
213
218
|
|
|
214
|
-
// Every image on the page, decoded together rather than one after another
|
|
219
|
+
// Every image on the page, decoded together rather than one after another,
|
|
220
|
+
// and each settling on its own: gathering with `Promise.all` made one file the
|
|
221
|
+
// browser refused the whole page's failure, thrown before the first draw op.
|
|
222
|
+
// `paint`'s own doc says why that is the wrong price. An image that did not
|
|
223
|
+
// decode has no entry here, which is how `paintOp` knows.
|
|
215
224
|
/** @type {(page: Page) => Promise<Map<Op, ImageBitmap>>} */
|
|
216
225
|
let decode = async (page) => {
|
|
217
226
|
let images = page.ops.filter((op) => op.kind === "image");
|
|
218
|
-
let bitmaps = await Promise.all(images.map(bitmapOf));
|
|
219
|
-
return new Map(
|
|
227
|
+
let bitmaps = await Promise.all(images.map((op) => bitmapOf(op).catch(() => null)));
|
|
228
|
+
return new Map(
|
|
229
|
+
/** @type {[Op, ImageBitmap][]} */ (
|
|
230
|
+
images.map((op, i) => [op, bitmaps[i]]).filter(([, bitmap]) => bitmap)
|
|
231
|
+
),
|
|
232
|
+
);
|
|
220
233
|
};
|
|
221
234
|
|
|
222
235
|
/**
|
|
@@ -225,6 +238,21 @@ let decode = async (page) => {
|
|
|
225
238
|
* the canvas, so it knows. `fonts` is the host's font mapping, the same
|
|
226
239
|
* record given to `layout()`, so a TrueType family draws in its own face.
|
|
227
240
|
*
|
|
241
|
+
* It awaits its faces and images before it draws, and it draws whatever
|
|
242
|
+
* happened in between: a canvas re-sized under a call still in flight is
|
|
243
|
+
* filled at that call's own `scale`, not the size it now has. A caller that
|
|
244
|
+
* repaints one canvas at changing scales owns that, by not letting a
|
|
245
|
+
* superseded call reach a canvas still on screen — which is what the viewer's
|
|
246
|
+
* stage retires a page for (docs/adr/0046).
|
|
247
|
+
*
|
|
248
|
+
* **An image the browser will not decode is drawn as nothing, and the page is
|
|
249
|
+
* drawn around it.** The engine vouched for the magic numbers and the layout
|
|
250
|
+
* read the size out of the header, so a file corrupt past that point is not
|
|
251
|
+
* known to be bad until here; costing the whole page for it — every other op
|
|
252
|
+
* and the marking with it — is a worse answer than costing the image. What is
|
|
253
|
+
* lost is what could not be drawn. A caller wanting the failure instead should
|
|
254
|
+
* decode before it paints.
|
|
255
|
+
*
|
|
228
256
|
* @param {CanvasRenderingContext2D} ctx The context to paint on.
|
|
229
257
|
* @param {Page} page One page of a `Layout`.
|
|
230
258
|
* @param {{ scale?: number, fonts?: any }} [options]
|
package/lib/settings.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything that holds for a whole render and is not geometry — the render's
|
|
3
|
+
* `Settings`, and the one reading of what a report default amounts to.
|
|
4
|
+
*
|
|
5
|
+
* A `Frame` (canvas.js) is a page's geometry; a `Settings` is the other half:
|
|
6
|
+
* the faces to measure against, the report default narrowed to `base` and
|
|
7
|
+
* `family`, and the three intl facts a formatted value resolves in. A canvas
|
|
8
|
+
* holds one by reference, so a measuring canvas built for a probe reads exactly
|
|
9
|
+
* what the listing canvas reads and a band cannot be reserved against one
|
|
10
|
+
* locale and drawn in another. This module owns what a settings *is* and how a
|
|
11
|
+
* report's opening event becomes one; canvas.js draws with it, text.js measures
|
|
12
|
+
* against it, and neither has to know what `style.size` or `style.family`
|
|
13
|
+
* amount to.
|
|
14
|
+
*/
|
|
15
|
+
import { familyName } from "./fonts.js";
|
|
16
|
+
import { sizeOf } from "./style.js";
|
|
17
|
+
|
|
18
|
+
// This layout's baseline type size. Not a host option: a document's type size
|
|
19
|
+
// is the document's own, so it is `style.size` on the report and the number
|
|
20
|
+
// here is only what text renders at when nothing declares one. The XLSX target
|
|
21
|
+
// carries the same 10 for the same reason (docs/adr/0014, docs/adr/0033).
|
|
22
|
+
let BASE = 10;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Everything that holds for a whole render and is not geometry: the faces to
|
|
26
|
+
* measure against, the report default narrowed to `family` and `size` (landing
|
|
27
|
+
* in `family` and `base` here), and the three intl facts a formatted value
|
|
28
|
+
* resolves in.
|
|
29
|
+
*
|
|
30
|
+
* A canvas holds one of these by reference, never a copy, which is the whole
|
|
31
|
+
* reason it is an object. A measuring canvas built off the same settings reads
|
|
32
|
+
* exactly what the listing canvas reads, so a page band cannot be reserved
|
|
33
|
+
* against one locale and drawn in another — an agreement that used to rest on
|
|
34
|
+
* a hand-written copy staying in step.
|
|
35
|
+
*
|
|
36
|
+
* `family` is null when the report declares none, and each of the intl three is
|
|
37
|
+
* undefined when the engine settled none.
|
|
38
|
+
*
|
|
39
|
+
* @typedef {{ fonts: import('./fonts.js').Fonts, base: number,
|
|
40
|
+
* family: string | null, locale?: string, currency?: string,
|
|
41
|
+
* timeZone?: string }} Settings
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The render's settings, complete by construction from the opening event the
|
|
46
|
+
* target already peeked. The report default is narrowed to `family` and `size`,
|
|
47
|
+
* and each replaces this target's own baseline outright: row heights and band
|
|
48
|
+
* gaps scale with the document's type rather than staying at a size nothing is
|
|
49
|
+
* set in, and text declaring no family is set in the document's. Reading the
|
|
50
|
+
* pair here is the whole of this target's reading of docs/adr/0033 — the default
|
|
51
|
+
* reaches a node as a fallback the settings carry, never as a layer merged into
|
|
52
|
+
* its style, so a document-wide fact costs no allocation however many cells a
|
|
53
|
+
* report has.
|
|
54
|
+
*
|
|
55
|
+
* `sizeOf` and `familyName` are this target's one reading each of what a
|
|
56
|
+
* declared size and family amount to, so they read the default here too — both
|
|
57
|
+
* reach this unchecked from a computed style, and two spellings of that
|
|
58
|
+
* leniency would drift. Each falls back to this target's own baseline, so a
|
|
59
|
+
* default declaring one of the pair leaves the other at the baseline, and one
|
|
60
|
+
* whose value is unusable leaves the baseline standing.
|
|
61
|
+
*
|
|
62
|
+
* Built once and never settled again: these hold for a whole render, so there
|
|
63
|
+
* is no event that could set them a second time — a second settling is not
|
|
64
|
+
* refused, it is unexpressible. `opening` is null when the stream is empty (no
|
|
65
|
+
* report to read a default from), which yields the bare baseline: the target's
|
|
66
|
+
* own size, no family, and the intl three unset. Those three are read straight
|
|
67
|
+
* off the event whether or not it declares them — a formatted value is the only
|
|
68
|
+
* thing that reads them, and an empty stream produces none, so an unset one
|
|
69
|
+
* never reaches `Intl`.
|
|
70
|
+
*
|
|
71
|
+
* @param {import('./fonts.js').Fonts} fonts The loaded faces.
|
|
72
|
+
* @param {any} [opening] The peeked `report-start` event, or null.
|
|
73
|
+
* @returns {Settings}
|
|
74
|
+
*/
|
|
75
|
+
let settings = (fonts, opening) => {
|
|
76
|
+
let event = opening || {};
|
|
77
|
+
let style = event.style || {};
|
|
78
|
+
return {
|
|
79
|
+
fonts,
|
|
80
|
+
base: sizeOf(style, BASE),
|
|
81
|
+
family: familyName(style) || null,
|
|
82
|
+
locale: event.locale,
|
|
83
|
+
currency: event.currency,
|
|
84
|
+
timeZone: event.timeZone,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export { settings };
|
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,7 @@ 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 `settings.js` explains.
|
|
66
73
|
/** @type {(under: any, over: any) => any} */
|
|
67
74
|
let merge = (under, over) => (under ? (over ? { ...under, ...over } : under) : over || {});
|
|
68
75
|
|
|
@@ -101,4 +108,19 @@ let dressed = (style) => !!(style && (style.underline || style.strikethrough));
|
|
|
101
108
|
/** @type {(style: any) => boolean} */
|
|
102
109
|
let upper = (style) => !!(style && style.uppercase);
|
|
103
110
|
|
|
104
|
-
export {
|
|
111
|
+
export {
|
|
112
|
+
BAND,
|
|
113
|
+
BLACK,
|
|
114
|
+
GUTTER,
|
|
115
|
+
LEAD,
|
|
116
|
+
PADX,
|
|
117
|
+
PADY,
|
|
118
|
+
col,
|
|
119
|
+
dressed,
|
|
120
|
+
merge,
|
|
121
|
+
roled,
|
|
122
|
+
shift,
|
|
123
|
+
sizeOf,
|
|
124
|
+
upper,
|
|
125
|
+
vshift,
|
|
126
|
+
};
|
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
|
-
// touch a page.
|
|
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 in `settings.js`,
|
|
13
|
+
// the module that owns them; a caller hands over `canvas.settings` rather than
|
|
14
|
+
// the canvas, so nothing here can touch a page.
|
|
15
|
+
/** @typedef {import('./settings.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
|
|
32
|
+
return format(token.value, style, 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.3.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.6.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.6.0"
|
|
52
53
|
},
|
|
53
54
|
"peerDependenciesMeta": {
|
|
54
|
-
"
|
|
55
|
+
"fontkit": {
|
|
55
56
|
"optional": true
|
|
56
57
|
}
|
|
57
58
|
},
|