@quario/layout 0.3.0 → 0.5.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 +91 -43
- package/README.md +16 -6
- package/lib/canvas.js +49 -17
- package/lib/fonts.js +19 -9
- package/lib/index.d.ts +38 -9
- package/lib/index.js +1 -0
- package/lib/layout.js +81 -286
- package/lib/measure.js +263 -0
- package/lib/page.js +1 -0
- package/lib/paint.js +18 -9
- package/lib/style.js +29 -11
- package/lib/text.js +85 -46
- package/package.json +3 -3
- package/lib/image.js +0 -58
package/CHANGELOG.md
CHANGED
|
@@ -1,22 +1,94 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
# @quario/layout
|
|
2
|
+
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **An image's box is now as wide as the container it sits in.** A
|
|
8
|
+
`background` or `border*` on an image item hugged the picture and now spans
|
|
9
|
+
the content width — or the slot's share inside a split — with `fit` sizing
|
|
10
|
+
the picture and `align` placing it inside, the way a text item's box has
|
|
11
|
+
always been drawn. The picture itself does not move. Breaking: a report that
|
|
12
|
+
relied on a border hugging a logo now draws that border across the full
|
|
13
|
+
width, and no declaration asks for the old shape.
|
|
14
|
+
- **A page of the display list no longer carries `width` and `height`.** Read
|
|
15
|
+
the list's own `width` and `height` instead: they are the paper every page of
|
|
16
|
+
the report is drawn on, and every page's pair was a copy of them. A report has
|
|
17
|
+
one page size for the whole render — that has always been so, and page size is
|
|
18
|
+
a target option rather than something a document declares — so a page is
|
|
19
|
+
identified and positioned, and asked nothing about its size. A page now
|
|
20
|
+
carries `{ number, total, ops, boxes }`.
|
|
21
|
+
|
|
22
|
+
If you consume the list yourself, the fix is one substitution:
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
canvas.width = list.width * 2; // was list.pages[i].width * 2
|
|
26
|
+
canvas.height = list.height * 2; // was list.pages[i].height * 2
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`paint()` is unchanged in shape — it still takes one page — and it now fills
|
|
30
|
+
the white over the whole canvas rather than over the page's own rectangle.
|
|
31
|
+
**Size the canvas to the page before you call it**, as the sizing example above
|
|
32
|
+
does: the ops are in the page's own coordinates, so a canvas short of the paper
|
|
33
|
+
clips them whatever the fill covers. That was always so; it is now written down
|
|
34
|
+
in the README and on `paint()` itself.
|
|
35
|
+
|
|
36
|
+
- **`Layout.pages` is typed as a non-empty list.** A render always lays out at
|
|
37
|
+
least one page — the first page opens whether or not anything renders on it —
|
|
38
|
+
so `pages[0]` no longer needs a check for a list that cannot exist. Runtime
|
|
39
|
+
behaviour is unchanged; this only states in the type what was already true.
|
|
40
|
+
- **`Paper`** — the page box a report is drawn on, `{ width, height }` in points,
|
|
41
|
+
and what a display list's own `width` and `height` are. `Layout` extends it, so
|
|
42
|
+
`list.width` is unchanged and the pair now has a name to refer to.
|
|
43
|
+
|
|
44
|
+
### Patch Changes
|
|
45
|
+
|
|
46
|
+
- **A page's right and bottom edge could be left unpainted.** Where a caller
|
|
47
|
+
rounds the backing store up to a whole device pixel — A4 at 100% asks for
|
|
48
|
+
793.71 and takes 794 — `paint()` filled its white over the page's exact
|
|
49
|
+
rectangle and left the fraction past it transparent. On a canvas with white
|
|
50
|
+
behind it that is invisible; on one without, a hairline of whatever is behind
|
|
51
|
+
the canvas showed along two edges. The fill now covers the whole canvas.
|
|
52
|
+
- **Every published README says where the documentation is.** Each package now
|
|
53
|
+
carries a Documentation section pointing at the reference, at the report schema
|
|
54
|
+
that normatively specifies what a report may declare, and at the package's own
|
|
55
|
+
API. The paragraphs that used to end on an unstated contract — the event
|
|
56
|
+
stream's field semantics, the style vocabulary, page columns, the Content
|
|
57
|
+
Security Policy a fragment with images needs, the formula mangling, and each
|
|
58
|
+
target's own contract — link the page that states it. Every link is an absolute
|
|
59
|
+
URL, so it resolves from the npm package page as readily as from an installed
|
|
60
|
+
copy.
|
|
61
|
+
- Updated dependencies
|
|
62
|
+
- quario@0.8.0
|
|
63
|
+
|
|
64
|
+
## 0.4.0
|
|
65
|
+
|
|
66
|
+
### Minor Changes
|
|
67
|
+
|
|
68
|
+
- **Per-run typography, decoration and highlights.** A styled run resolves its
|
|
69
|
+
own face, size and colour, and a line takes the largest size among its runs.
|
|
70
|
+
`underline` and `strikethrough` are drawn per run — over that run's width, in
|
|
71
|
+
that run's colour — where they used to stroke the whole line in the first
|
|
72
|
+
piece's colour. A run's `background` paints a highlight rectangle behind its
|
|
73
|
+
text; a cell's own background is still painted once, at cell scope.
|
|
74
|
+
- **`checkFonts` is public.** It checks the shape of a `fonts` mapping without
|
|
75
|
+
loading a parser, and takes an optional name to prefix a failure with, the
|
|
76
|
+
way `pageBox` does — so a surface validating its own `fonts` property can
|
|
77
|
+
report the mistake against that property rather than against
|
|
78
|
+
`options.fonts`.
|
|
79
|
+
- **An outline `Mark` carries only what it declares.** Every mark shipped an
|
|
80
|
+
extra `titled` boolean — bookkeeping for whether a header had claimed the
|
|
81
|
+
entry — which was never part of the `Mark` interface and which nothing
|
|
82
|
+
reads. It is the open group instance's own state now, and no longer travels
|
|
83
|
+
on the object a consumer receives.
|
|
84
|
+
|
|
85
|
+
## 0.3.0
|
|
86
|
+
|
|
87
|
+
### Minor Changes
|
|
13
88
|
|
|
14
89
|
- The text join reads a cell's own `currency` code, ahead of the instance's
|
|
15
90
|
default, so every target built on this package presents a per-cell
|
|
16
91
|
denomination.
|
|
17
|
-
|
|
18
|
-
### Changed
|
|
19
|
-
|
|
20
92
|
- **A group instance that renders nothing no longer takes up space.** A group
|
|
21
93
|
whose header and footer items all resolve `visible: false`, with nothing
|
|
22
94
|
visible under it either, used to open the same half-line gap as any other
|
|
@@ -27,7 +99,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
27
99
|
its rows stay in the aggregates. A group that declares `break: "page"` or
|
|
28
100
|
`reset: "page"` still starts its page either way. Every document with such a
|
|
29
101
|
group renders slightly shorter than it did.
|
|
30
|
-
|
|
31
102
|
- **Numbers presented through `format` now show a fixed two fraction digits,
|
|
32
103
|
matching every other target** — `1,000.00` where `1,000` was rendered,
|
|
33
104
|
`21.00%` where `21%` was, and a currency's own minor units in place of a
|
|
@@ -35,15 +106,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
35
106
|
a formatted cell is now up to three characters wider than it was: a line
|
|
36
107
|
that just fitted can wrap, which can move a page break in a PDF, the viewer,
|
|
37
108
|
or an editor preview. Nothing else about wrapping changed.
|
|
38
|
-
|
|
39
|
-
### Fixed
|
|
40
|
-
|
|
41
109
|
- **An image failure now names the item that asked for the bytes.** A file too
|
|
42
110
|
short to carry a size failed saying only that the size could not be read,
|
|
43
111
|
naming no item, so a report with two pictures gave no way to tell which one
|
|
44
112
|
was bad. The message is now prefixed with the item's `source` path, as every
|
|
45
113
|
other render error is.
|
|
46
|
-
|
|
47
114
|
- **An image the browser cannot decode no longer costs the whole page.** A
|
|
48
115
|
PNG or JPEG whose pixel data is corrupt past the size in its header lays out
|
|
49
116
|
like any other — the size is all that is read of it — and used to throw out
|
|
@@ -52,9 +119,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
52
119
|
drawn as nothing and the page is drawn around it, so what a bad image costs
|
|
53
120
|
is the image.
|
|
54
121
|
|
|
55
|
-
##
|
|
122
|
+
## 0.2.0
|
|
56
123
|
|
|
57
|
-
###
|
|
124
|
+
### Minor Changes
|
|
58
125
|
|
|
59
126
|
- **A spanning cell is drawn as one box** across the columns it covers,
|
|
60
127
|
starting where the first of them starts. It takes no part in allocating their
|
|
@@ -62,21 +129,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
62
129
|
empty table still shows its geometry. A spanning row too tall for the page it
|
|
63
130
|
is on slices like any other, each slice following the geometry of the page or
|
|
64
131
|
page-column strip it lands in.
|
|
65
|
-
|
|
66
132
|
- **`valign`** places a cell's lines, or a slot's lines or picture, in the slack
|
|
67
133
|
its row or split leaves over its own content: middle halves it, bottom takes
|
|
68
134
|
it. The box does not move, and a picture is not scaled. A row too tall for
|
|
69
135
|
any page, sliced across pages, paints from the top.
|
|
70
|
-
|
|
71
|
-
### Changed
|
|
72
|
-
|
|
73
136
|
- **A row's box is drawn by the row's cells.** A box declared on a table row
|
|
74
137
|
used to be one rect across the summed column widths; it is now each covered
|
|
75
138
|
cell's own, so a row's `borderBottom` still reads as one continuous edge
|
|
76
139
|
while a row's `borderLeft` becomes an edge on every cell rather than one at
|
|
77
140
|
the row's outer left. A row's border also occupies height now, as a cell's
|
|
78
141
|
always has, so a bordered row is taller by its border's width.
|
|
79
|
-
|
|
80
142
|
- **Measuring TrueType families now needs `fontkit`, not `@pdf-lib/fontkit`.**
|
|
81
143
|
Install `fontkit` instead; nothing else about `options.fonts` changes. The
|
|
82
144
|
old package's bundle crashed with a bare `ReferenceError` on any OpenType
|
|
@@ -85,9 +147,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
85
147
|
besides. Those faces now measure where they used to throw. Which scripts a
|
|
86
148
|
face supports remains the font's and the parser's to answer, not this
|
|
87
149
|
package's.
|
|
88
|
-
|
|
89
|
-
### Fixed
|
|
90
|
-
|
|
91
150
|
- **A group header no longer strands above a split.** A split cannot be broken
|
|
92
151
|
across a page, so a header that introduces one has to keep the whole of it
|
|
93
152
|
company — but the keep-together test measured a split by its first two lines
|
|
@@ -102,9 +161,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
102
161
|
always has before content no page can hold: it degrades to plain paginated
|
|
103
162
|
flow and repeats nothing.
|
|
104
163
|
|
|
105
|
-
##
|
|
164
|
+
## 0.1.0
|
|
106
165
|
|
|
107
|
-
###
|
|
166
|
+
### Minor Changes
|
|
108
167
|
|
|
109
168
|
- **The paged display list.** `layout({ page, fonts })` is a render target
|
|
110
169
|
resolving every page of the report — ops in points from the top-left, one
|
|
@@ -112,12 +171,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
112
171
|
marks, and on an unlicensed render the marking. The algorithm is the PDF
|
|
113
172
|
target's typesetter, with its home moved here so a preview can paint the
|
|
114
173
|
same pages the document has.
|
|
115
|
-
|
|
116
174
|
- **One measurer.** Base-14 families measure against `@pdf-lib/standard-fonts`'
|
|
117
175
|
AFM metrics over WinAnsi, TrueType families against `@pdf-lib/fontkit`'s
|
|
118
176
|
shaping of the same bytes the PDF embeds, so a preview breaks its lines
|
|
119
177
|
where the document breaks them.
|
|
120
|
-
|
|
121
178
|
- **A box survives a page break.** An item or table row no page can hold
|
|
122
179
|
whole is drawn as slices, and a slice carries the box sides the break left
|
|
123
180
|
it: the top belongs to the first slice, the bottom to the last, and left and
|
|
@@ -126,7 +183,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
126
183
|
short one draws. Each slice reserves the bottom padding it may yet owe, so
|
|
127
184
|
that edge stays above the bottom margin, and each carries a hit box, so an
|
|
128
185
|
item that breaks is selectable on every page it reaches.
|
|
129
|
-
|
|
130
186
|
- **`paint` and `hit`.** A Canvas 2D painter for one page, and the hit-test
|
|
131
187
|
that maps a point on a page back to the schema node drawn there.
|
|
132
188
|
|
|
@@ -145,13 +201,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
145
201
|
|
|
146
202
|
- **`pageBox`.** The page-size table and its validation, in one home for the
|
|
147
203
|
PDF target, the viewer and the editor.
|
|
148
|
-
|
|
149
204
|
- **`PX_PER_POINT`.** How big a point is on screen — 96 dpi over PostScript's
|
|
150
205
|
72 — so every surface that shows a page shows it at the same size, and a
|
|
151
206
|
zoom is a factor on top of it.
|
|
152
|
-
|
|
153
|
-
### Changed
|
|
154
|
-
|
|
155
207
|
- **A split slot's box now fills the split's height.** Before, a slot's
|
|
156
208
|
background and border were exactly as tall as that slot's own content and
|
|
157
209
|
padding asked for, so any slot shorter than the tallest one drew a box that
|
|
@@ -164,13 +216,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
164
216
|
does, and a slot's hit box follows its painted one. A report that relied on
|
|
165
217
|
the short box — a slot background used as a chip beside taller content —
|
|
166
218
|
now draws it full height; give that content a narrower slot of its own.
|
|
167
|
-
|
|
168
219
|
- **A date string under `format: "date"` now presents as a date.** The text
|
|
169
220
|
join presents the kind through the engine's `format()` helper, which now
|
|
170
221
|
revives the two read forms. See the `quario` changelog for the forms.
|
|
171
|
-
|
|
172
|
-
### Fixed
|
|
173
|
-
|
|
174
222
|
- **A nested group's footer keeps the page columns.** Inside a page-column
|
|
175
223
|
region, the first footer of a group nested in it ended the region: the
|
|
176
224
|
strips collapsed and every band after it was laid out across the page, as
|
package/README.md
CHANGED
|
@@ -38,8 +38,9 @@ list.pages[0].ops; // what to draw on the first page, in order
|
|
|
38
38
|
list.pages[0].boxes; // which schema node was drawn where
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Every coordinate is in PostScript points from the page's top-left corner.
|
|
42
|
-
`
|
|
41
|
+
Every coordinate is in PostScript points from the page's top-left corner. `list.width` and
|
|
42
|
+
`list.height` are the paper every page is drawn on — a report has one, so a page does not carry its
|
|
43
|
+
own size. A page carries its `{ number, total }`, its ops — `rect`, `line`, `image`, `text` (the string to draw, the face
|
|
43
44
|
it is set in, and — for a built-in family only — the per-character advances the measurer laid it
|
|
44
45
|
out with) and, on an unlicensed render, one `mark` — and its `boxes`, one per schema node drawn,
|
|
45
46
|
named by the node's path.
|
|
@@ -61,14 +62,16 @@ import { hit, paint } from "@quario/layout";
|
|
|
61
62
|
|
|
62
63
|
const canvas = document.querySelector("canvas");
|
|
63
64
|
const page = list.pages[0];
|
|
64
|
-
canvas.width =
|
|
65
|
-
canvas.height =
|
|
65
|
+
canvas.width = list.width * 2; // 2 device pixels per point
|
|
66
|
+
canvas.height = list.height * 2;
|
|
66
67
|
await paint(canvas.getContext("2d"), page, { scale: 2, fonts });
|
|
67
68
|
|
|
68
69
|
hit(page, x, y); // -> { path, x, y, w, h } or null
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
`paint` draws a white page and every op in order.
|
|
72
|
+
`paint` draws a white page and every op in order. Size the canvas to the page before you call it:
|
|
73
|
+
the ops are in the page's own coordinates, so a smaller canvas clips them, and the white is filled
|
|
74
|
+
over the whole canvas. Images are decoded from the bytes on the list —
|
|
72
75
|
no `img-src` grant, no URLs. `hit` answers which schema node was drawn at a point: the smallest box
|
|
73
76
|
containing it.
|
|
74
77
|
|
|
@@ -85,9 +88,16 @@ positioning, so a browser may kern by a fraction more. Where a line breaks, how
|
|
|
85
88
|
and where a page ends are the layout's — one measurer, so preview and document agree.
|
|
86
89
|
|
|
87
90
|
`PX_PER_POINT` is how big a point is on screen — 96 dpi over PostScript's 72 — so a surface showing
|
|
88
|
-
a page at 100% sizes its canvas `
|
|
91
|
+
a page at 100% sizes its canvas `list.width * PX_PER_POINT` CSS pixels wide and paints at
|
|
89
92
|
`PX_PER_POINT * devicePixelRatio`. Any zoom is a factor on top of that.
|
|
90
93
|
|
|
94
|
+
## Documentation
|
|
95
|
+
|
|
96
|
+
[The quario documentation](https://getquario.com/docs/) is the reference.
|
|
97
|
+
The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
|
|
98
|
+
specification of what a report may declare, and
|
|
99
|
+
[`@quario/layout`](https://getquario.com/docs/reference/layout/) is this package's own API.
|
|
100
|
+
|
|
91
101
|
## License
|
|
92
102
|
|
|
93
103
|
quario is commercial software; evaluation is free and fully featured, with output marked as
|
package/lib/canvas.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* `y` descending, the way a screen reads. Points throughout.
|
|
25
25
|
*/
|
|
26
26
|
import { baseSans } from "./fonts.js";
|
|
27
|
-
import { BLACK,
|
|
27
|
+
import { BLACK, shift } from "./style.js";
|
|
28
28
|
|
|
29
29
|
/** @typedef {import('./page.js').Frame} Frame */
|
|
30
30
|
/** @typedef {import('./text.js').Line} Line */
|
|
@@ -71,27 +71,55 @@ import { BLACK, dressed, shift } from "./style.js";
|
|
|
71
71
|
*/
|
|
72
72
|
let blank = (box, render) => ({ ...box, settings: render, y: 0, fresh: true });
|
|
73
73
|
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
// A styled run's `background`, painted behind its text as a highlight over
|
|
75
|
+
// that run's width and the line's full height -- the same rectangle a cell's
|
|
76
|
+
// own background is, one stretch narrower. All of a line's highlights are
|
|
77
|
+
// painted before any of its glyphs, so a rectangle behind one run can never
|
|
78
|
+
// land on top of the run before it. Shared with the layout suite's recorder
|
|
79
|
+
// for the reason `decorateLine` is.
|
|
80
|
+
/**
|
|
81
|
+
* @type {(rect: (color: Color, x: number, y: number, w: number, h: number) => void,
|
|
82
|
+
* line: Line, left: number, yTop: number) => void}
|
|
83
|
+
*/
|
|
84
|
+
let highlightLine = (rect, line, left, yTop) => {
|
|
85
|
+
let cursor = left;
|
|
86
|
+
for (let piece of line.pieces) {
|
|
87
|
+
if (piece.bg) rect(piece.bg, cursor, yTop - line.h, piece.w, line.h);
|
|
88
|
+
cursor += piece.w;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
// Text decoration, drawn per piece: over that piece's width, in that piece's
|
|
93
|
+
// colour. A rule across the whole line in the first piece's colour is what
|
|
94
|
+
// this used to be, and it is exactly what "underline a word" cannot mean
|
|
95
|
+
// (ADR 0061). Thickness and offset stay the line's, from the ascender already
|
|
96
|
+
// measured for baseline placement, so one line's rules sit at one height
|
|
97
|
+
// whatever sizes it mixes. Empty lines (no width) draw nothing. Shared by the
|
|
98
|
+
// listing adapter and the layout suite's recorder so both exercise the same
|
|
99
|
+
// path.
|
|
100
|
+
/**
|
|
101
|
+
* @type {(stroke: (x1: number, x2: number, y: number, thickness: number, color: Color) => void,
|
|
102
|
+
* piece: Line['pieces'][number], line: Line, left: number, baseline: number) => void}
|
|
103
|
+
*/
|
|
104
|
+
let decoratePiece = (stroke, piece, line, left, baseline) => {
|
|
105
|
+
let thickness = Math.max(line.asc / 12, 0.5);
|
|
106
|
+
let right = left + piece.w;
|
|
107
|
+
let color = piece.color || BLACK;
|
|
108
|
+
if (piece.underline) stroke(left, right, baseline - line.asc * 0.12, thickness, color);
|
|
109
|
+
if (piece.strikethrough) stroke(left, right, baseline + line.asc * 0.35, thickness, color);
|
|
110
|
+
};
|
|
83
111
|
|
|
84
112
|
/**
|
|
85
113
|
* @type {(stroke: (x1: number, x2: number, y: number, thickness: number, color: Color) => void,
|
|
86
114
|
* line: Line, left: number, baseline: number) => void}
|
|
87
115
|
*/
|
|
88
116
|
let decorateLine = (stroke, line, left, baseline) => {
|
|
89
|
-
if (!
|
|
90
|
-
let
|
|
91
|
-
let
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
117
|
+
if (!line.w) return;
|
|
118
|
+
let cursor = left;
|
|
119
|
+
for (let piece of line.pieces) {
|
|
120
|
+
decoratePiece(stroke, piece, line, cursor, baseline);
|
|
121
|
+
cursor += piece.w;
|
|
122
|
+
}
|
|
95
123
|
};
|
|
96
124
|
|
|
97
125
|
/**
|
|
@@ -112,7 +140,10 @@ let listing = (box, render) => {
|
|
|
112
140
|
|
|
113
141
|
// Open a fresh page and put the cursor at its top.
|
|
114
142
|
let newPage = () => {
|
|
115
|
-
|
|
143
|
+
// No geometry: the paper is the list's, written once from this canvas's
|
|
144
|
+
// own frame, because the page box is fixed for the whole document (ADR
|
|
145
|
+
// 0069). A page carries what is per-page.
|
|
146
|
+
page = { ops: [], boxes: [] };
|
|
116
147
|
pages.push(page);
|
|
117
148
|
canvas.y = canvas.top;
|
|
118
149
|
canvas.fresh = true;
|
|
@@ -172,6 +203,7 @@ let listing = (box, render) => {
|
|
|
172
203
|
/** @type {Canvas['drawLine']} */
|
|
173
204
|
let drawLine = (line, x, yTop, avail, align) => {
|
|
174
205
|
let left = x + shift(align, avail - line.w);
|
|
206
|
+
highlightLine(rect, line, left, yTop);
|
|
175
207
|
let cursor = left;
|
|
176
208
|
for (let piece of line.pieces) {
|
|
177
209
|
writePiece(piece, line, cursor, yTop);
|
|
@@ -307,4 +339,4 @@ let stamp = (canvas, text) => {
|
|
|
307
339
|
};
|
|
308
340
|
};
|
|
309
341
|
|
|
310
|
-
export { adopt, decorateLine, listing, measuring, stamp };
|
|
342
|
+
export { adopt, decorateLine, highlightLine, listing, measuring, stamp };
|
package/lib/fonts.js
CHANGED
|
@@ -161,9 +161,9 @@ let useFontkit = async () => {
|
|
|
161
161
|
}
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
-
/** @type {(name: string, def: any) => string} */
|
|
165
|
-
let asFamily = (name, def) => {
|
|
166
|
-
let path = "
|
|
164
|
+
/** @type {(name: string, def: any, at: string) => string} */
|
|
165
|
+
let asFamily = (name, def, at) => {
|
|
166
|
+
let path = at + "." + name;
|
|
167
167
|
if (!def || typeof def !== "object")
|
|
168
168
|
throw Error(path + ": expected { regular, bold?, italic?, boldItalic? }");
|
|
169
169
|
if (def.regular == null) throw Error(path + ".regular: required");
|
|
@@ -241,7 +241,9 @@ let remembered = (source, key, make) => {
|
|
|
241
241
|
// resolvable once the family is named.
|
|
242
242
|
/** @type {(fontkit: any, name: string, def: any) => Face[]} */
|
|
243
243
|
let familyOf = (fontkit, name, def) => {
|
|
244
|
-
|
|
244
|
+
// Always the factory's own option here: loading happens behind `layout()`,
|
|
245
|
+
// and a surface that named it something else has already validated it.
|
|
246
|
+
let path = asFamily(name, def, "options.fonts");
|
|
245
247
|
let family = name.toLowerCase();
|
|
246
248
|
/** @type {Face[]} */
|
|
247
249
|
let faces = [];
|
|
@@ -278,15 +280,23 @@ export async function loadFonts(custom) {
|
|
|
278
280
|
}
|
|
279
281
|
|
|
280
282
|
/**
|
|
281
|
-
* Check the shape of a host's font mapping without loading a parser
|
|
282
|
-
*
|
|
283
|
+
* Check the shape of a host's font mapping without loading a parser.
|
|
284
|
+
*
|
|
285
|
+
* `at` names the property in the message, the way `pageBox` takes one: the
|
|
286
|
+
* factory call reports `options.fonts`, but a surface element validating its
|
|
287
|
+
* own `fonts` property before any render has a different name for the same
|
|
288
|
+
* value, and a host mistake is named on the property that carries it.
|
|
289
|
+
*
|
|
290
|
+
* Only the shape. A face that will not parse, or a missing parser, is found
|
|
291
|
+
* while the report is measured and is a render failure there.
|
|
283
292
|
*
|
|
284
293
|
* @param {any} custom `options.fonts`, or null.
|
|
294
|
+
* @param {string} [at] What to call it in the message.
|
|
285
295
|
*/
|
|
286
|
-
export let checkFonts = (custom) => {
|
|
296
|
+
export let checkFonts = (custom, at = "options.fonts") => {
|
|
287
297
|
if (custom == null) return;
|
|
288
|
-
if (typeof custom !== "object") throw Error("
|
|
289
|
-
for (let [name, def] of Object.entries(custom)) asFamily(name, def);
|
|
298
|
+
if (typeof custom !== "object") throw Error(at + ": expected a record of families");
|
|
299
|
+
for (let [name, def] of Object.entries(custom)) asFamily(name, def, at);
|
|
290
300
|
};
|
|
291
301
|
|
|
292
302
|
// What a declared `family` normalises to before it is looked up: lower case,
|
package/lib/index.d.ts
CHANGED
|
@@ -159,12 +159,13 @@ export interface Box {
|
|
|
159
159
|
* One page of the list. Coordinates are points from the page's top-left
|
|
160
160
|
* corner, `y` descending; `number` and `total` are what `page.number` and
|
|
161
161
|
* `page.total` read on it.
|
|
162
|
+
*
|
|
163
|
+
* A page carries what is per-page and nothing else: the `Paper` it is drawn on
|
|
164
|
+
* is the list's (ADR 0069).
|
|
162
165
|
*/
|
|
163
166
|
export interface Page {
|
|
164
167
|
number: number;
|
|
165
168
|
total: number;
|
|
166
|
-
width: number;
|
|
167
|
-
height: number;
|
|
168
169
|
ops: Op[];
|
|
169
170
|
boxes: Box[];
|
|
170
171
|
}
|
|
@@ -178,11 +179,23 @@ export interface Mark {
|
|
|
178
179
|
y: number;
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
/**
|
|
182
|
-
|
|
182
|
+
/**
|
|
183
|
+
* The [paper](../../../CONTEXT.md#paper) a report is drawn on, in points: one
|
|
184
|
+
* width and one height for a whole render. The page box is fixed once the
|
|
185
|
+
* render opens, so this is the list's and a page carries none of its own (ADR
|
|
186
|
+
* 0069) — a consumer sizing a page reads it here.
|
|
187
|
+
*/
|
|
188
|
+
export interface Paper {
|
|
183
189
|
width: number;
|
|
184
190
|
height: number;
|
|
185
|
-
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The display list: every page of the document, laid out, on one `Paper`. */
|
|
194
|
+
export interface Layout extends Paper {
|
|
195
|
+
/** Every page, and never none: the band flow opens the body's first page
|
|
196
|
+
* whether or not anything renders on it. Typed as a non-empty tuple so a
|
|
197
|
+
* consumer reading `pages[0]` needs no check for a list that cannot exist. */
|
|
198
|
+
pages: [Page, ...Page[]];
|
|
186
199
|
/** The group tree, in document order — the PDF target's bookmarks. */
|
|
187
200
|
marks: Mark[];
|
|
188
201
|
}
|
|
@@ -206,11 +219,17 @@ export const PX_PER_POINT: number;
|
|
|
206
219
|
* order. `scale` is device pixels per point. `fonts` is the same record
|
|
207
220
|
* given to `layout()`, so a TrueType family draws in its own face.
|
|
208
221
|
*
|
|
222
|
+
* **Size the canvas to the page first** — `Layout.width` and `Layout.height`
|
|
223
|
+
* times `scale`, the paper every page of the list is drawn on. The ops are in
|
|
224
|
+
* the page's own coordinates, so a canvas short of that clips them, and the
|
|
225
|
+
* white is filled over the whole canvas rather than over a rectangle a page
|
|
226
|
+
* carries (ADR 0069).
|
|
227
|
+
*
|
|
209
228
|
* It awaits its faces and images before it draws, and it draws whatever
|
|
210
|
-
* happened in between: a canvas re-sized under a call still in flight
|
|
211
|
-
*
|
|
212
|
-
* repaints one canvas at changing scales owns that, by not letting
|
|
213
|
-
* superseded call reach a canvas still on screen.
|
|
229
|
+
* happened in between: a canvas re-sized under a call still in flight has its
|
|
230
|
+
* ops placed at that call's own `scale` on a canvas of the size it now has. A
|
|
231
|
+
* caller that repaints one canvas at changing scales owns that, by not letting
|
|
232
|
+
* a superseded call reach a canvas still on screen.
|
|
214
233
|
*/
|
|
215
234
|
export function paint(
|
|
216
235
|
ctx: CanvasRenderingContext2D,
|
|
@@ -232,3 +251,13 @@ export function pageBox(
|
|
|
232
251
|
page?: LayoutPage,
|
|
233
252
|
at?: string,
|
|
234
253
|
): { width: number; height: number; margin: number };
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Check the shape of a `fonts` mapping, throwing where it is malformed. `at`
|
|
257
|
+
* prefixes a failure with the option's name, so a surface validating its own
|
|
258
|
+
* `fonts` property names that rather than `options.fonts`.
|
|
259
|
+
*
|
|
260
|
+
* Only the shape: a face that will not parse, or a missing parser, is found
|
|
261
|
+
* while the report is measured and is a render failure there.
|
|
262
|
+
*/
|
|
263
|
+
export function checkFonts(fonts?: LayoutFonts | null, at?: string): void;
|
package/lib/index.js
CHANGED
|
@@ -29,6 +29,7 @@ import { settings } from "./settings.js";
|
|
|
29
29
|
|
|
30
30
|
export { PX_PER_POINT, hit, paint } from "./paint.js";
|
|
31
31
|
export { pageBox } from "./page.js";
|
|
32
|
+
export { checkFonts } from "./fonts.js";
|
|
32
33
|
|
|
33
34
|
// The options are described once, in the hand-written public declarations, and
|
|
34
35
|
// read back here — a second copy in JSDoc is a copy that drifts.
|