@quario/layout 0.4.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 +72 -49
- package/README.md +16 -6
- package/lib/canvas.js +4 -1
- package/lib/index.d.ts +28 -9
- package/lib/layout.js +25 -22
- package/lib/page.js +1 -0
- package/lib/paint.js +18 -9
- package/lib/style.js +1 -0
- package/package.json +3 -3
- package/lib/image.js +0 -58
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,69 @@
|
|
|
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
|
|
13
67
|
|
|
14
68
|
- **Per-run typography, decoration and highlights.** A styled run resolves its
|
|
15
69
|
own face, size and colour, and a line takes the largest size among its runs.
|
|
@@ -17,31 +71,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
17
71
|
that run's colour — where they used to stroke the whole line in the first
|
|
18
72
|
piece's colour. A run's `background` paints a highlight rectangle behind its
|
|
19
73
|
text; a cell's own background is still painted once, at cell scope.
|
|
20
|
-
|
|
21
74
|
- **`checkFonts` is public.** It checks the shape of a `fonts` mapping without
|
|
22
75
|
loading a parser, and takes an optional name to prefix a failure with, the
|
|
23
76
|
way `pageBox` does — so a surface validating its own `fonts` property can
|
|
24
77
|
report the mistake against that property rather than against
|
|
25
78
|
`options.fonts`.
|
|
26
|
-
|
|
27
|
-
### Fixed
|
|
28
|
-
|
|
29
79
|
- **An outline `Mark` carries only what it declares.** Every mark shipped an
|
|
30
80
|
extra `titled` boolean — bookkeeping for whether a header had claimed the
|
|
31
81
|
entry — which was never part of the `Mark` interface and which nothing
|
|
32
82
|
reads. It is the open group instance's own state now, and no longer travels
|
|
33
83
|
on the object a consumer receives.
|
|
34
84
|
|
|
35
|
-
##
|
|
85
|
+
## 0.3.0
|
|
36
86
|
|
|
37
|
-
###
|
|
87
|
+
### Minor Changes
|
|
38
88
|
|
|
39
89
|
- The text join reads a cell's own `currency` code, ahead of the instance's
|
|
40
90
|
default, so every target built on this package presents a per-cell
|
|
41
91
|
denomination.
|
|
42
|
-
|
|
43
|
-
### Changed
|
|
44
|
-
|
|
45
92
|
- **A group instance that renders nothing no longer takes up space.** A group
|
|
46
93
|
whose header and footer items all resolve `visible: false`, with nothing
|
|
47
94
|
visible under it either, used to open the same half-line gap as any other
|
|
@@ -52,7 +99,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
52
99
|
its rows stay in the aggregates. A group that declares `break: "page"` or
|
|
53
100
|
`reset: "page"` still starts its page either way. Every document with such a
|
|
54
101
|
group renders slightly shorter than it did.
|
|
55
|
-
|
|
56
102
|
- **Numbers presented through `format` now show a fixed two fraction digits,
|
|
57
103
|
matching every other target** — `1,000.00` where `1,000` was rendered,
|
|
58
104
|
`21.00%` where `21%` was, and a currency's own minor units in place of a
|
|
@@ -60,15 +106,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
60
106
|
a formatted cell is now up to three characters wider than it was: a line
|
|
61
107
|
that just fitted can wrap, which can move a page break in a PDF, the viewer,
|
|
62
108
|
or an editor preview. Nothing else about wrapping changed.
|
|
63
|
-
|
|
64
|
-
### Fixed
|
|
65
|
-
|
|
66
109
|
- **An image failure now names the item that asked for the bytes.** A file too
|
|
67
110
|
short to carry a size failed saying only that the size could not be read,
|
|
68
111
|
naming no item, so a report with two pictures gave no way to tell which one
|
|
69
112
|
was bad. The message is now prefixed with the item's `source` path, as every
|
|
70
113
|
other render error is.
|
|
71
|
-
|
|
72
114
|
- **An image the browser cannot decode no longer costs the whole page.** A
|
|
73
115
|
PNG or JPEG whose pixel data is corrupt past the size in its header lays out
|
|
74
116
|
like any other — the size is all that is read of it — and used to throw out
|
|
@@ -77,9 +119,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
77
119
|
drawn as nothing and the page is drawn around it, so what a bad image costs
|
|
78
120
|
is the image.
|
|
79
121
|
|
|
80
|
-
##
|
|
122
|
+
## 0.2.0
|
|
81
123
|
|
|
82
|
-
###
|
|
124
|
+
### Minor Changes
|
|
83
125
|
|
|
84
126
|
- **A spanning cell is drawn as one box** across the columns it covers,
|
|
85
127
|
starting where the first of them starts. It takes no part in allocating their
|
|
@@ -87,21 +129,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
87
129
|
empty table still shows its geometry. A spanning row too tall for the page it
|
|
88
130
|
is on slices like any other, each slice following the geometry of the page or
|
|
89
131
|
page-column strip it lands in.
|
|
90
|
-
|
|
91
132
|
- **`valign`** places a cell's lines, or a slot's lines or picture, in the slack
|
|
92
133
|
its row or split leaves over its own content: middle halves it, bottom takes
|
|
93
134
|
it. The box does not move, and a picture is not scaled. A row too tall for
|
|
94
135
|
any page, sliced across pages, paints from the top.
|
|
95
|
-
|
|
96
|
-
### Changed
|
|
97
|
-
|
|
98
136
|
- **A row's box is drawn by the row's cells.** A box declared on a table row
|
|
99
137
|
used to be one rect across the summed column widths; it is now each covered
|
|
100
138
|
cell's own, so a row's `borderBottom` still reads as one continuous edge
|
|
101
139
|
while a row's `borderLeft` becomes an edge on every cell rather than one at
|
|
102
140
|
the row's outer left. A row's border also occupies height now, as a cell's
|
|
103
141
|
always has, so a bordered row is taller by its border's width.
|
|
104
|
-
|
|
105
142
|
- **Measuring TrueType families now needs `fontkit`, not `@pdf-lib/fontkit`.**
|
|
106
143
|
Install `fontkit` instead; nothing else about `options.fonts` changes. The
|
|
107
144
|
old package's bundle crashed with a bare `ReferenceError` on any OpenType
|
|
@@ -110,9 +147,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
110
147
|
besides. Those faces now measure where they used to throw. Which scripts a
|
|
111
148
|
face supports remains the font's and the parser's to answer, not this
|
|
112
149
|
package's.
|
|
113
|
-
|
|
114
|
-
### Fixed
|
|
115
|
-
|
|
116
150
|
- **A group header no longer strands above a split.** A split cannot be broken
|
|
117
151
|
across a page, so a header that introduces one has to keep the whole of it
|
|
118
152
|
company — but the keep-together test measured a split by its first two lines
|
|
@@ -127,9 +161,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
127
161
|
always has before content no page can hold: it degrades to plain paginated
|
|
128
162
|
flow and repeats nothing.
|
|
129
163
|
|
|
130
|
-
##
|
|
164
|
+
## 0.1.0
|
|
131
165
|
|
|
132
|
-
###
|
|
166
|
+
### Minor Changes
|
|
133
167
|
|
|
134
168
|
- **The paged display list.** `layout({ page, fonts })` is a render target
|
|
135
169
|
resolving every page of the report — ops in points from the top-left, one
|
|
@@ -137,12 +171,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
137
171
|
marks, and on an unlicensed render the marking. The algorithm is the PDF
|
|
138
172
|
target's typesetter, with its home moved here so a preview can paint the
|
|
139
173
|
same pages the document has.
|
|
140
|
-
|
|
141
174
|
- **One measurer.** Base-14 families measure against `@pdf-lib/standard-fonts`'
|
|
142
175
|
AFM metrics over WinAnsi, TrueType families against `@pdf-lib/fontkit`'s
|
|
143
176
|
shaping of the same bytes the PDF embeds, so a preview breaks its lines
|
|
144
177
|
where the document breaks them.
|
|
145
|
-
|
|
146
178
|
- **A box survives a page break.** An item or table row no page can hold
|
|
147
179
|
whole is drawn as slices, and a slice carries the box sides the break left
|
|
148
180
|
it: the top belongs to the first slice, the bottom to the last, and left and
|
|
@@ -151,7 +183,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
151
183
|
short one draws. Each slice reserves the bottom padding it may yet owe, so
|
|
152
184
|
that edge stays above the bottom margin, and each carries a hit box, so an
|
|
153
185
|
item that breaks is selectable on every page it reaches.
|
|
154
|
-
|
|
155
186
|
- **`paint` and `hit`.** A Canvas 2D painter for one page, and the hit-test
|
|
156
187
|
that maps a point on a page back to the schema node drawn there.
|
|
157
188
|
|
|
@@ -170,13 +201,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
170
201
|
|
|
171
202
|
- **`pageBox`.** The page-size table and its validation, in one home for the
|
|
172
203
|
PDF target, the viewer and the editor.
|
|
173
|
-
|
|
174
204
|
- **`PX_PER_POINT`.** How big a point is on screen — 96 dpi over PostScript's
|
|
175
205
|
72 — so every surface that shows a page shows it at the same size, and a
|
|
176
206
|
zoom is a factor on top of it.
|
|
177
|
-
|
|
178
|
-
### Changed
|
|
179
|
-
|
|
180
207
|
- **A split slot's box now fills the split's height.** Before, a slot's
|
|
181
208
|
background and border were exactly as tall as that slot's own content and
|
|
182
209
|
padding asked for, so any slot shorter than the tallest one drew a box that
|
|
@@ -189,13 +216,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
189
216
|
does, and a slot's hit box follows its painted one. A report that relied on
|
|
190
217
|
the short box — a slot background used as a chip beside taller content —
|
|
191
218
|
now draws it full height; give that content a narrower slot of its own.
|
|
192
|
-
|
|
193
219
|
- **A date string under `format: "date"` now presents as a date.** The text
|
|
194
220
|
join presents the kind through the engine's `format()` helper, which now
|
|
195
221
|
revives the two read forms. See the `quario` changelog for the forms.
|
|
196
|
-
|
|
197
|
-
### Fixed
|
|
198
|
-
|
|
199
222
|
- **A nested group's footer keeps the page columns.** Inside a page-column
|
|
200
223
|
region, the first footer of a group nested in it ended the region: the
|
|
201
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
|
@@ -140,7 +140,10 @@ let listing = (box, render) => {
|
|
|
140
140
|
|
|
141
141
|
// Open a fresh page and put the cursor at its top.
|
|
142
142
|
let newPage = () => {
|
|
143
|
-
|
|
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: [] };
|
|
144
147
|
pages.push(page);
|
|
145
148
|
canvas.y = canvas.top;
|
|
146
149
|
canvas.fresh = true;
|
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,
|
package/lib/layout.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* document's bands claim before anything is placed, and where they are drawn
|
|
22
22
|
* on each finished page once the count is known.
|
|
23
23
|
*/
|
|
24
|
-
import { display,
|
|
24
|
+
import { display, isReportBand, text } from "quario";
|
|
25
25
|
import { balance } from "./balance.js";
|
|
26
26
|
/** `Measured` is the measured table's own type, and the flow passes one
|
|
27
27
|
* through: the region buffer measures early and the replay hands it back.
|
|
@@ -35,7 +35,6 @@ import { balance } from "./balance.js";
|
|
|
35
35
|
import { gridOf, measureTable, sum, tableOf } from "./measure.js";
|
|
36
36
|
import { adopt, measuring } from "./canvas.js";
|
|
37
37
|
import { frame } from "./page.js";
|
|
38
|
-
import { intrinsic } from "./image.js";
|
|
39
38
|
import { CELL_PAD, NO_PAD, WHOLE, insetOf, isWhole, paintBox, sliceInset, unbox } from "./box.js";
|
|
40
39
|
import {
|
|
41
40
|
BAND,
|
|
@@ -122,19 +121,22 @@ let unsliceable = (block) => Boolean(block.picture || block.parts);
|
|
|
122
121
|
// image's own size in points, never wider than the content box; `width`
|
|
123
122
|
// scales to the content box either way. The ratio is kept in both, so a
|
|
124
123
|
// height is never anything but the width's consequence.
|
|
124
|
+
// An image pixel as a page point, at the conventional 96 dpi the schema names
|
|
125
|
+
// for `fit: "natural"`. Deliberately not `PX_PER_POINT`, which is the same
|
|
126
|
+
// number answering a different question -- how a point becomes a length on
|
|
127
|
+
// screen, where a zoom is a factor on top of it. An image's resolution and a
|
|
128
|
+
// viewer's scale share a convention today and are free to stop.
|
|
129
|
+
let PER_PX = 72 / 96;
|
|
130
|
+
|
|
125
131
|
/** @type {(event: any, avail: number) => Picture} */
|
|
126
132
|
let pictureOf = (event, avail) => {
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
} catch (cause) {
|
|
135
|
-
throw imageError(event.path, /** @type {Error} */ (cause).message, cause);
|
|
136
|
-
}
|
|
137
|
-
let { w, h } = sized;
|
|
133
|
+
// The event states the size in pixels; a page is measured in points. The
|
|
134
|
+
// conversion is the first step of the same calculation the rest of this
|
|
135
|
+
// function is, at the conventional 96 dpi the schema names, and there is no
|
|
136
|
+
// header to read: the engine read it once, for every target, and a file too
|
|
137
|
+
// short to state a size never reached a target at all (`docs/adr/0067`).
|
|
138
|
+
let w = event.width * PER_PX;
|
|
139
|
+
let h = event.height * PER_PX;
|
|
138
140
|
let width = event.fit === "width" ? avail : Math.min(w, avail);
|
|
139
141
|
return {
|
|
140
142
|
bytes: event.bytes,
|
|
@@ -195,23 +197,24 @@ let shrink = (block, room) => {
|
|
|
195
197
|
block.h = inner + block.inset.t + block.inset.b;
|
|
196
198
|
};
|
|
197
199
|
|
|
198
|
-
// Draw one block whole at the cursor.
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
200
|
+
// Draw one block whole at the cursor. The box is the width the block was
|
|
201
|
+
// given, exactly as `drawLines` paints one: an image's background and border
|
|
202
|
+
// span the content width, or the slot's share inside a split, and never the
|
|
203
|
+
// picture's own width. `fit` sizes the picture and `align` places it in the
|
|
204
|
+
// inner box; neither moves an edge. Text is placed line by line as each is
|
|
205
|
+
// drawn; a picture is one box, so where it sits is worked out once.
|
|
202
206
|
/** @type {(canvas: Canvas, block: Block, x: number, avail: number) => void} */
|
|
203
207
|
let drawPicture = (canvas, block, x, avail) => {
|
|
204
208
|
let inset = block.inset;
|
|
205
209
|
let { bytes, format, w, h } = /** @type {Picture} */ (block.picture);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
canvas.box(block.path, at, canvas.y, outer, block.h);
|
|
210
|
+
paintBox(canvas, x, canvas.y, avail, block.h, block.style, block.bg);
|
|
211
|
+
canvas.box(block.path, x, canvas.y, avail, block.h);
|
|
212
|
+
let inner = Math.max(avail - inset.l - inset.r, 1);
|
|
210
213
|
canvas.picture(
|
|
211
214
|
block.path,
|
|
212
215
|
bytes,
|
|
213
216
|
format,
|
|
214
|
-
|
|
217
|
+
x + inset.l + shift(block.align, inner - w),
|
|
215
218
|
canvas.y - inset.t - block.drop - h,
|
|
216
219
|
w,
|
|
217
220
|
h,
|
package/lib/page.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
// The named sizes. `@quario/pdf` used to own this table, and the two element
|
|
17
17
|
// packages restated it; the layout package is where all three now read it.
|
|
18
|
+
// fallow-ignore-next-line code-duplication -- @quario/docx keeps a deliberate copy of these rules: a flow target runs no layout, and taking @quario/layout for a two-entry table would install the pagination engine with it. test/page-sizes.test.js holds the two in sync (ADR 0039).
|
|
18
19
|
let SIZES = /** @type {Record<string, [number, number]>} */ ({
|
|
19
20
|
A4: [595.28, 841.89],
|
|
20
21
|
letter: [612, 792],
|
package/lib/paint.js
CHANGED
|
@@ -235,18 +235,20 @@ let decode = async (page) => {
|
|
|
235
235
|
/**
|
|
236
236
|
* Paint one page of the list onto a Canvas 2D context: a white page, then
|
|
237
237
|
* every op in order. `scale` is device pixels per point — the caller sized
|
|
238
|
-
* the canvas, so it knows
|
|
238
|
+
* the canvas, so it knows, and the canvas it sized is the page's own size,
|
|
239
|
+
* which is why the paper is filled over the whole store and no page geometry
|
|
240
|
+
* is asked for (ADR 0069). `fonts` is the host's font mapping, the same
|
|
239
241
|
* record given to `layout()`, so a TrueType family draws in its own face.
|
|
240
242
|
*
|
|
241
243
|
* 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
|
|
243
|
-
*
|
|
244
|
-
* repaints one canvas at changing scales owns that, by not letting
|
|
245
|
-
* superseded call reach a canvas still on screen — which is what the
|
|
246
|
-
* stage retires a page for (docs/adr/0046).
|
|
244
|
+
* happened in between: a canvas re-sized under a call still in flight has its
|
|
245
|
+
* ops placed at that call's own `scale` on a store of the size it now has. A
|
|
246
|
+
* caller that repaints one canvas at changing scales owns that, by not letting
|
|
247
|
+
* a superseded call reach a canvas still on screen — which is what the
|
|
248
|
+
* viewer's stage retires a page for (docs/adr/0046).
|
|
247
249
|
*
|
|
248
250
|
* **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
|
|
251
|
+
* drawn around it.** The engine vouched for the magic numbers and
|
|
250
252
|
* read the size out of the header, so a file corrupt past that point is not
|
|
251
253
|
* known to be bad until here; costing the whole page for it — every other op
|
|
252
254
|
* and the marking with it — is a worse answer than costing the image. What is
|
|
@@ -263,9 +265,16 @@ export async function paint(ctx, page, { scale = 1, fonts } = {}) {
|
|
|
263
265
|
await loadFaces(fonts);
|
|
264
266
|
let bitmaps = await decode(page);
|
|
265
267
|
ctx.save();
|
|
266
|
-
|
|
268
|
+
// The paper, over the whole store rather than over the page's own box: a
|
|
269
|
+
// page carries no geometry to fill to (ADR 0069), and the caller has already
|
|
270
|
+
// sized the canvas to the page — the obligation the ops themselves put on it,
|
|
271
|
+
// since a short store clips them. In device pixels, before the scale goes on,
|
|
272
|
+
// so a store rounded up to a whole pixel is covered to its edge rather than
|
|
273
|
+
// left a sliver of whatever it held.
|
|
274
|
+
ctx.resetTransform();
|
|
267
275
|
ctx.fillStyle = "#fff";
|
|
268
|
-
ctx.fillRect(0, 0,
|
|
276
|
+
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
277
|
+
ctx.setTransform(scale, 0, 0, scale, 0, 0);
|
|
269
278
|
for (let op of page.ops) paintOp(ctx, op, bitmaps);
|
|
270
279
|
ctx.restore();
|
|
271
280
|
}
|
package/lib/style.js
CHANGED
|
@@ -54,6 +54,7 @@ let vshift = (valign, extra) => (Object.hasOwn(VSHIFT, valign) ? VSHIFT[valign]
|
|
|
54
54
|
|
|
55
55
|
// A declared colour as a `Color`, or null when the value is not one.
|
|
56
56
|
/** @type {(value: any) => Color | null} */
|
|
57
|
+
// fallow-ignore-next-line code-duplication -- each target reads a declared hex into its own type; neither may import a sibling, and the engine's stream is all they share
|
|
57
58
|
let col = (value) => {
|
|
58
59
|
let match = typeof value === "string" && HEX.exec(value);
|
|
59
60
|
if (!match) return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quario/layout",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
44
44
|
"@types/fontkit": "^2.0.9",
|
|
45
45
|
"fontkit": "^2.0.4",
|
|
46
|
-
"quario": "^0.
|
|
46
|
+
"quario": "^0.8.0",
|
|
47
47
|
"size-limit": "^13.0.3",
|
|
48
48
|
"typescript": "^7.0.2"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"fontkit": "^2.0.4",
|
|
52
|
-
"quario": "^0.
|
|
52
|
+
"quario": "^0.8.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"fontkit": {
|
package/lib/image.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* An image's own size, read from its header. Sizing is a fact about the bytes
|
|
3
|
-
* rather than anything a page can answer, so it sits here and not on the
|
|
4
|
-
* drawing surface: the band flow asks for it while measuring, where no
|
|
5
|
-
* document is in reach at all, and again while drawing, where one is.
|
|
6
|
-
*
|
|
7
|
-
* Read, never decoded — a PNG's IHDR and a JPEG's frame header carry the two
|
|
8
|
-
* numbers the layout needs, and nothing else here looks at a pixel.
|
|
9
|
-
*
|
|
10
|
-
* The XLSX target reads the same two headers for its own placement, in pixels.
|
|
11
|
-
* Whether that fact belongs on the engine's event instead — it already reads
|
|
12
|
-
* the magic numbers to name the format — is bead `quario-b0h`.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// Points, at the conventional 96 dpi the schema names: 72/96 of a pixel.
|
|
16
|
-
let PER_PX = 72 / 96;
|
|
17
|
-
|
|
18
|
-
/** @type {(bytes: Uint8Array, at: number) => number} */
|
|
19
|
-
let word = (bytes, at) => (bytes[at] << 8) | bytes[at + 1];
|
|
20
|
-
|
|
21
|
-
/** @type {(code: number) => boolean} */
|
|
22
|
-
let inSof = (code) => code >= 0xc0 && code <= 0xcf;
|
|
23
|
-
|
|
24
|
-
/** @type {(code: number) => boolean} */
|
|
25
|
-
let notTable = (code) => code !== 0xc4 && code !== 0xc8 && code !== 0xcc;
|
|
26
|
-
|
|
27
|
-
// The frame header carries the dimensions, and it is the first SOFn marker:
|
|
28
|
-
// every code in C0..CF except the three in that range that are not frames --
|
|
29
|
-
// DHT, the JPG extension, and DAC.
|
|
30
|
-
/** @type {(code: number) => boolean} */
|
|
31
|
-
let isFrame = (code) => inSof(code) && notTable(code);
|
|
32
|
-
|
|
33
|
-
/** @type {(bytes: Uint8Array) => { w: number, h: number }} */
|
|
34
|
-
let jpegSize = (bytes) => {
|
|
35
|
-
for (let at = 2; at + 9 < bytes.length; at += 2 + word(bytes, at + 2)) {
|
|
36
|
-
if (bytes[at] !== 0xff) break;
|
|
37
|
-
if (isFrame(bytes[at + 1])) return { w: word(bytes, at + 7), h: word(bytes, at + 5) };
|
|
38
|
-
}
|
|
39
|
-
return { w: 0, h: 0 };
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* The image's intrinsic size in points. The format is the engine's sniff,
|
|
44
|
-
* riding on the event, so nothing here decides it a second time.
|
|
45
|
-
*
|
|
46
|
-
* @param {Uint8Array} bytes The image file.
|
|
47
|
-
* @param {string} format The engine's sniff: `"png"` or `"jpeg"`.
|
|
48
|
-
* @returns {{ w: number, h: number }} The size, in points.
|
|
49
|
-
*/
|
|
50
|
-
export let intrinsic = (bytes, format) => {
|
|
51
|
-
// A PNG carries the two numbers in its IHDR at a fixed offset.
|
|
52
|
-
let { w, h } = format === "png" ? { w: word(bytes, 18), h: word(bytes, 22) } : jpegSize(bytes);
|
|
53
|
-
// The engine vouched for the magic numbers, not for the rest of the file:
|
|
54
|
-
// a truncated header reaches here as a zero, and failing loudly beats
|
|
55
|
-
// drawing an image with no size (SCHEMA.md, "Image item").
|
|
56
|
-
if (!(w > 0 && h > 0)) throw Error("could not read the image's size from its bytes");
|
|
57
|
-
return { w: w * PER_PX, h: h * PER_PX };
|
|
58
|
-
};
|