@quario/pdf 0.7.0 → 0.9.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 +92 -61
- package/README.md +47 -35
- package/lib/index.d.ts +5 -1
- package/lib/index.js +9 -3
- package/lib/painter.js +3 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,39 +1,106 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @quario/pdf
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 0.9.0
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
### Minor Changes
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
- **Every target factory now refuses an option it does not understand.** An unknown key, a key with
|
|
8
|
+
a value of the wrong type, or an `options` that is not an object throws a `TypeError` at the
|
|
9
|
+
factory call, naming the option path — `options.meta.title`, `options.page.size`. `@quario/docx`
|
|
10
|
+
already behaved this way. `@quario/pdf`, `@quario/html`, `@quario/xlsx` and `@quario/layout` read
|
|
11
|
+
the keys they knew and ignored the rest, so a typo cost you the option you meant to set with no
|
|
12
|
+
signal at any point. `csv()` takes no options, and it refuses an argument rather than discarding
|
|
13
|
+
one.
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
The check itself is one thing, so the engine now exports it: `hostOptions` closes a key set and
|
|
16
|
+
`hostMeta` validates the `{ title, author, subject }` contract the document targets share. A
|
|
17
|
+
fourth document property is one edit rather than three.
|
|
11
18
|
|
|
12
|
-
|
|
19
|
+
**What this changes for you.** One options object spread across several targets stops working if
|
|
20
|
+
any target does not know one of its keys — `const o = { page, fonts, meta }` passed to `pdf(o)`,
|
|
21
|
+
`html(o)` and `xlsx(o)` is three different key sets. An object holding only what its consumers
|
|
22
|
+
share is unaffected, so `{ page, fonts }` into both `pdf()` and `layout()` still works.
|
|
23
|
+
TypeScript does not warn about this: excess-property checking fires on an object literal and not
|
|
24
|
+
on a variable, so a bag held in a `const` compiles clean and throws when you call the factory.
|
|
25
|
+
|
|
26
|
+
Nullish is absence everywhere, at both levels, so `{ meta: config.meta ?? null }` and
|
|
27
|
+
`{ meta: { title: config.title } }` over a config that carries neither are both fine.
|
|
28
|
+
`html({ paths })` now takes `true` or `false` rather than anything truthy, so `paths: "no"` throws
|
|
29
|
+
instead of turning path stamping on. A `fonts` mapping given as an array is refused by
|
|
30
|
+
`@quario/html` and `@quario/layout` rather than read as families named `0`, `1`, `2`.
|
|
31
|
+
|
|
32
|
+
Every host-option failure is now a `TypeError` rather than a plain `Error`. A `catch` that tests
|
|
33
|
+
`instanceof Error` is unaffected. One that compares the constructor is not.
|
|
34
|
+
|
|
35
|
+
`@quario/docx` is relaxed in three places, each of them now accepting what it refused: `meta: null`
|
|
36
|
+
and `page: null` are absence rather than errors, a property written as `meta: { title: undefined }`
|
|
37
|
+
is a property you did not write rather than one of the wrong type, and an inherited enumerable key
|
|
38
|
+
is no longer reported as an option you wrote.
|
|
39
|
+
|
|
40
|
+
`@quario/pdf`, `@quario/xlsx` and `@quario/docx` now copy `meta` at the factory call. Mutating the
|
|
41
|
+
object you passed no longer changes what a configured target writes.
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- The package now accepts only a version of `@cantoo/pdf-lib` it can import. The declared range is
|
|
46
|
+
`~2.9.1` rather than `^2.9.1`.
|
|
47
|
+
|
|
48
|
+
Earlier versions accepted `@cantoo/pdf-lib` 2.11.0, whose ESM build imports its font metric JSON
|
|
49
|
+
without an import attribute. A fresh install could resolve to it, and importing `@quario/pdf` then
|
|
50
|
+
failed on Node before rendering anything, with `TypeError [ERR_IMPORT_ATTRIBUTE_MISSING]` naming a
|
|
51
|
+
`.compressed.json` file inside that package. Node 22 and Node 24 both report it. If you saw that
|
|
52
|
+
error, reinstall — nothing in your own code has to change.
|
|
53
|
+
|
|
54
|
+
- Updated dependencies
|
|
55
|
+
- quario@0.9.0
|
|
56
|
+
- @quario/layout@0.6.0
|
|
57
|
+
|
|
58
|
+
## 0.8.0
|
|
59
|
+
|
|
60
|
+
### Minor Changes
|
|
61
|
+
|
|
62
|
+
- **An image's box is now as wide as the container it sits in.** A
|
|
63
|
+
`background` or `border*` on an image item hugged the picture and now spans
|
|
64
|
+
the content width, or the slot's share inside a split; the picture itself
|
|
65
|
+
does not move. See the `@quario/layout` changelog for the rule and for what
|
|
66
|
+
it costs a report that relied on a border hugging a logo.
|
|
67
|
+
|
|
68
|
+
### Patch Changes
|
|
69
|
+
|
|
70
|
+
- **Every published README says where the documentation is.** Each package now
|
|
71
|
+
carries a Documentation section pointing at the reference, at the report schema
|
|
72
|
+
that normatively specifies what a report may declare, and at the package's own
|
|
73
|
+
API. The paragraphs that used to end on an unstated contract — the event
|
|
74
|
+
stream's field semantics, the style vocabulary, page columns, the Content
|
|
75
|
+
Security Policy a fragment with images needs, the formula mangling, and each
|
|
76
|
+
target's own contract — link the page that states it. Every link is an absolute
|
|
77
|
+
URL, so it resolves from the npm package page as readily as from an installed
|
|
78
|
+
copy.
|
|
79
|
+
- Updated dependencies
|
|
80
|
+
- @quario/layout@0.5.0
|
|
81
|
+
- quario@0.8.0
|
|
82
|
+
|
|
83
|
+
## 0.7.0
|
|
84
|
+
|
|
85
|
+
### Minor Changes
|
|
13
86
|
|
|
14
87
|
- **Styled runs reach the page.** A cell value written as a list of styled runs
|
|
15
88
|
draws each run in its own face, size and colour, underlines and strikes
|
|
16
89
|
through per run rather than across the line, and paints a run's `background`
|
|
17
90
|
as a highlight behind its text.
|
|
18
|
-
|
|
19
|
-
### Changed
|
|
20
|
-
|
|
21
91
|
- **A `format` inside a sentence now presents where the engine says it does.** A
|
|
22
92
|
cell mixing literal text and one interpolation under a `format` draws the
|
|
23
93
|
value plainly, as the spreadsheet and CSV targets already did; splitting the
|
|
24
94
|
value into styled runs is how a value inside a sentence is formatted.
|
|
25
95
|
|
|
26
|
-
##
|
|
96
|
+
## 0.6.0
|
|
27
97
|
|
|
28
|
-
###
|
|
98
|
+
### Minor Changes
|
|
29
99
|
|
|
30
100
|
- **A cell's own `currency` code is honoured**, ahead of the instance's
|
|
31
101
|
default, so a listing whose rows arrive in different currencies presents each
|
|
32
102
|
in its own. A code the engine could not accept leaves the cell as plain
|
|
33
103
|
display text rather than presenting it in the instance's currency.
|
|
34
|
-
|
|
35
|
-
### Changed
|
|
36
|
-
|
|
37
104
|
- **Numbers presented through `format` now show a fixed two fraction digits,
|
|
38
105
|
matching every other target.** `format: "number"` on `1000` renders
|
|
39
106
|
`1,000.00` where it rendered `1,000`, and `0.12345` renders `0.12` where it
|
|
@@ -42,9 +109,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
42
109
|
so a JPY amount loses the two decimals it never had. `date` is unchanged, as
|
|
43
110
|
are grouping separators and symbol placement. The digits come from the
|
|
44
111
|
engine, so a cell reads the same here as it does in a workbook.
|
|
45
|
-
|
|
46
|
-
### Fixed
|
|
47
|
-
|
|
48
112
|
- **An image these bytes cannot embed now fails with the item's name on it.**
|
|
49
113
|
A PNG whose pixel data is corrupt past the size in its header used to reject
|
|
50
114
|
the render with `Invalid typed array length: 0` — the PDF writer's own words,
|
|
@@ -54,9 +118,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
54
118
|
account after it and on `cause`. A JPEG is unchanged: its header is read but
|
|
55
119
|
its pixel data never is, so a corrupt one still embeds without complaint.
|
|
56
120
|
|
|
57
|
-
##
|
|
121
|
+
## 0.5.0
|
|
58
122
|
|
|
59
|
-
###
|
|
123
|
+
### Minor Changes
|
|
60
124
|
|
|
61
125
|
- **A spanning cell draws as one box** across the columns it covers, through
|
|
62
126
|
`@quario/layout`. It takes no part in measuring them, and a spanning row that
|
|
@@ -64,16 +128,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
64
128
|
each slice lands in.
|
|
65
129
|
- **`valign`** on table cells and split slots, through `@quario/layout`: middle
|
|
66
130
|
and bottom place the content in the height its row or split leaves over it.
|
|
67
|
-
|
|
68
|
-
### Changed
|
|
69
|
-
|
|
70
131
|
- **A row's box is drawn by the row's cells**, through `@quario/layout`. A box
|
|
71
132
|
declared on a table row used to be one rect across the columns; each covered
|
|
72
133
|
cell now draws its own. A row's `borderBottom` still reads as one continuous
|
|
73
134
|
edge, a row's `borderLeft` becomes an edge on every cell rather than one at
|
|
74
135
|
the row's outer left, and a bordered row is taller by its border's width,
|
|
75
136
|
since a border occupies height as a cell's always has.
|
|
76
|
-
|
|
77
137
|
- **Embedding TrueType fonts now needs `fontkit`, not `@pdf-lib/fontkit`.**
|
|
78
138
|
Install `fontkit` instead; nothing else about `options.fonts` changes. The
|
|
79
139
|
old package's bundle crashed with a bare `ReferenceError` on any OpenType
|
|
@@ -81,28 +141,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
81
141
|
Tamil, Khmer or Myanmar webfont in practice, and some fifty further scripts
|
|
82
142
|
besides. Those faces now measure, embed and draw. Which scripts a face
|
|
83
143
|
supports remains the font's and the parser's to answer, not this package's.
|
|
84
|
-
|
|
85
144
|
- **`pdf-lib` is now `@cantoo/pdf-lib`.** A maintained fork, and what
|
|
86
145
|
`fontkit`'s subsetting requires. Rendered documents are unchanged in what
|
|
87
146
|
they draw: every drawn string is identical and every filled area lands in
|
|
88
147
|
the same place, but the file's bytes differ, so a host comparing digests
|
|
89
148
|
against stored output will see them move once.
|
|
90
149
|
|
|
91
|
-
##
|
|
150
|
+
## 0.4.0
|
|
92
151
|
|
|
93
|
-
###
|
|
152
|
+
### Minor Changes
|
|
94
153
|
|
|
95
154
|
- **A split slot's box now fills the split's height.** A slot's background and
|
|
96
155
|
border stopped at that slot's own content and now take the split's height,
|
|
97
156
|
the way a table cell's take their row's. See the `@quario/layout` changelog
|
|
98
157
|
for the rule and for what it costs a report that relied on the short box.
|
|
99
|
-
|
|
100
158
|
- **A date string under `format: "date"` now presents as a date.** The
|
|
101
159
|
drawn text comes from the engine's `format()` helper, which revives the two
|
|
102
160
|
read forms. See the `quario` changelog for the forms and the timezone rule.
|
|
103
|
-
|
|
104
|
-
### Changed
|
|
105
|
-
|
|
106
161
|
- **The layout moved to `@quario/layout`; this target paints its list.**
|
|
107
162
|
Measurement, wrapping, pagination, page furniture, the marking's geometry
|
|
108
163
|
and the page-size table now live in the layout package, which this target
|
|
@@ -112,16 +167,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
112
167
|
options are the layout's own, so the same object configures `pdf()`, the
|
|
113
168
|
viewer and the editor. The font mapping's shape is now refused at the
|
|
114
169
|
factory call rather than at render.
|
|
115
|
-
|
|
116
|
-
### Fixed
|
|
117
|
-
|
|
118
170
|
- **A nested group's footer keeps the page columns.** Inside a page-column
|
|
119
171
|
region, the first footer of a group nested in it ended the region: the
|
|
120
172
|
strips collapsed and every band after it was laid out across the page, as
|
|
121
173
|
if `columns` were absent. Only the node that declared `columns` closes its
|
|
122
174
|
own region now — a nested group's header and footer are region content,
|
|
123
175
|
laid out in the strips.
|
|
124
|
-
|
|
125
176
|
- **A padded or bordered item or row that breaks across a page keeps its
|
|
126
177
|
box.** Content too tall for one page is drawn as slices, and a slice now
|
|
127
178
|
carries the box sides the break left it — the top on the first, the bottom
|
|
@@ -133,60 +184,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
133
184
|
the horizontal padding they were wrapped for, rather than flush to the
|
|
134
185
|
column. **This changes output** for reports where such content breaks.
|
|
135
186
|
|
|
136
|
-
##
|
|
187
|
+
## 0.3.0
|
|
137
188
|
|
|
138
|
-
###
|
|
189
|
+
### Minor Changes
|
|
139
190
|
|
|
140
191
|
- **`format` stringifies at the edge from the instance locale.** A PDF
|
|
141
192
|
without a host locale still uses `en-US` / UTC, so the bytes stay
|
|
142
193
|
reproducible.
|
|
143
|
-
|
|
144
194
|
- **A height-declared report header pins from the page top.** Leftover sits
|
|
145
195
|
under the items; the next band starts at the pin. The half-line group gap
|
|
146
196
|
drops after it. `page.margin` on the document is the inset when the host
|
|
147
197
|
omits it; both is a render error.
|
|
148
|
-
|
|
149
198
|
- **`spaceBefore` / `spaceAfter` skip the cursor.** Adjacent gaps add.
|
|
150
199
|
`spaceBefore` drops at a fresh body page or strip top; page-band items
|
|
151
200
|
keep it.
|
|
152
|
-
|
|
153
201
|
- **The box is honoured in layout.** Padding and border inset wrap width; a
|
|
154
202
|
named padding `0` beats the cell omakase (`PADX` 6 / `PADY` 2) on that
|
|
155
203
|
side. Incomplete sides draw nothing.
|
|
156
|
-
|
|
157
|
-
### Changed
|
|
158
|
-
|
|
159
204
|
- **The last data row keeps the whole emitted total block.** A stack taller
|
|
160
205
|
than a page degrades like a tall row.
|
|
161
|
-
|
|
162
206
|
- **An unstyled table has no rules.** This target used to draw two 0.5 pt
|
|
163
207
|
rules, under the header and above the total, that no declaration could
|
|
164
208
|
ask for or refuse. Those strokes are gone. The strokes an author wants
|
|
165
209
|
are the box they declared. Cell padding omakase stays on undeclared cell
|
|
166
210
|
sides.
|
|
167
|
-
|
|
168
211
|
- **A visible text item occupies a line at its own `size`, empty or not.** An
|
|
169
212
|
empty or whitespace-only value used to sit at the report's base leading
|
|
170
213
|
(~14 pt at the 10 pt baseline). It now occupies `1.4 ×` the item's size, the
|
|
171
214
|
same as a glyph line. A literal newline is a line break; the blank line
|
|
172
215
|
among `"a\n\nb"` is that size too. A table cell that is empty or only
|
|
173
216
|
horizontal whitespace still has no content height.
|
|
174
|
-
|
|
175
|
-
### Fixed
|
|
176
|
-
|
|
177
217
|
- **A missing TrueType glyph draws as `?`, not `.notdef`.** Characters
|
|
178
218
|
outside WinAnsi already substituted `?`; an embedded face's cmap holes
|
|
179
219
|
drew a box instead, and text extraction hid it. Same rule for every
|
|
180
220
|
face: a character the face cannot draw becomes `?`.
|
|
181
|
-
|
|
182
221
|
- **CR, LF, and CRLF are one hard line break.** Cell text already broke
|
|
183
222
|
on LF; a CRLF left a CR on the previous line, and a lone CR did not
|
|
184
223
|
break. All three are now one break, matching `SCHEMA.md` Cell values.
|
|
185
224
|
Wrapping still applies within each line.
|
|
186
225
|
|
|
187
|
-
##
|
|
226
|
+
## 0.2.0
|
|
188
227
|
|
|
189
|
-
###
|
|
228
|
+
### Minor Changes
|
|
190
229
|
|
|
191
230
|
- **The report default sets the document's face and base size.** A report's
|
|
192
231
|
top-level `style` replaces this target's own baseline, so its `size` scales
|
|
@@ -194,12 +233,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
194
233
|
nothing is set in. It is the layer _under_ the band-role defaults: a report
|
|
195
234
|
declaring `size: 12` still renders its report header at 14, and an item's own
|
|
196
235
|
style wins over both.
|
|
197
|
-
|
|
198
236
|
- **`uppercase` draws capitals.** With no text-transform to defer to, this
|
|
199
237
|
target capitalises the string before measuring it, so wrapping and column
|
|
200
238
|
widths are those of the text actually drawn. The mapping is Unicode default
|
|
201
239
|
case, never the host's locale, so output stays byte-reproducible.
|
|
202
|
-
|
|
203
240
|
- **Splits lay out across the content width.** Each slot is measured and
|
|
204
241
|
wrapped inside its own share, all slots draw from a common top, and the
|
|
205
242
|
split takes the height of its tallest slot so the band below clears them
|
|
@@ -208,27 +245,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
208
245
|
any page renders in full past the bottom margin. The split's own style is
|
|
209
246
|
the layer under each slot's, and its `background` fills the whole split
|
|
210
247
|
behind them.
|
|
211
|
-
|
|
212
|
-
### Changed
|
|
213
|
-
|
|
214
248
|
- **A bare `Date` draws as ISO 8601 UTC, the same on every machine.** Cell
|
|
215
249
|
text and outline bookmark titles for `Date` values used `String(date)`,
|
|
216
250
|
which bakes the host's timezone and locale into the document — at odds with
|
|
217
251
|
this target's byte-reproducibility guarantee. Both now render through the
|
|
218
252
|
engine's shared display rule, so a `Date` group key titles its bookmark
|
|
219
253
|
with the same ISO text its cells draw.
|
|
220
|
-
|
|
221
|
-
### Removed
|
|
222
|
-
|
|
223
254
|
- **`options.baseSize`.** A document's type size is the document's own, so it
|
|
224
255
|
is the report's `style.size` — portable, travelling with the definition to
|
|
225
256
|
every target — rather than a host option one target honoured. Text with
|
|
226
257
|
nothing declared still renders at 10 points. Replace `pdf({ baseSize: 11 })`
|
|
227
258
|
with `"style": { "size": 11 }` on the report.
|
|
228
259
|
|
|
229
|
-
##
|
|
260
|
+
## 0.1.0
|
|
230
261
|
|
|
231
|
-
###
|
|
262
|
+
### Minor Changes
|
|
232
263
|
|
|
233
264
|
- **Paginated PDFs from the same compiled report.** The band flow places
|
|
234
265
|
headers, groups, and tables; pdf-lib writes the file. Page furniture and an
|
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
definition straight to a paginated PDF. No headless browser, no print CSS, deterministic bytes.
|
|
5
5
|
|
|
6
6
|
quario does the typesetting — pagination, keep-together, tables, fonts, the document outline —
|
|
7
|
-
through `@quario/layout
|
|
8
|
-
document break their pages in the same places. [`@cantoo/pdf-lib`](https://github.com/Cantoo-Scribe/pdf-lib) writes the file. You get typesetting plus pdf-lib: no Chromium
|
|
7
|
+
through `@quario/layout`. That is the same layout the viewer paints on screen, so the preview and
|
|
8
|
+
the document break their pages in the same places. [`@cantoo/pdf-lib`](https://github.com/Cantoo-Scribe/pdf-lib) writes the file. You get typesetting plus pdf-lib: no Chromium
|
|
9
9
|
in your container, no page-load race, no fonts-not-ready flake.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
@@ -14,8 +14,8 @@ in your container, no page-load race, no fonts-not-ready flake.
|
|
|
14
14
|
npm install quario @quario/pdf
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
`@cantoo/pdf-lib`
|
|
18
|
-
any standards-based ESM bundler. The renderer returns bytes, so the host decides where they go.
|
|
17
|
+
`@cantoo/pdf-lib` arrives as a dependency. The engine is a peer, installed beside it. ESM-only,
|
|
18
|
+
Node 22+, and browser-ready through any standards-based ESM bundler. The renderer returns bytes, so the host decides where they go.
|
|
19
19
|
|
|
20
20
|
Embedding TrueType fonts also needs the optional peer:
|
|
21
21
|
|
|
@@ -58,8 +58,9 @@ const paged = {
|
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
`visible: "=page.number > 1"` on a band is the cover-page recipe. Gate band _visibility_ on
|
|
61
|
-
`page.number` only.
|
|
62
|
-
two-page document
|
|
61
|
+
`page.number` only. The layout reserves band heights before it knows the real page count, probing as a
|
|
62
|
+
two-page document. `=page.total > 10` therefore reserves only the 8 pt gap a declared band always
|
|
63
|
+
costs.
|
|
63
64
|
`page.total` is fine in band _text_.
|
|
64
65
|
|
|
65
66
|
## API
|
|
@@ -67,7 +68,7 @@ two-page document, so `=page.total > 10` reserves only the 8 pt gap a declared b
|
|
|
67
68
|
### `pdf(options?)`
|
|
68
69
|
|
|
69
70
|
The target factory takes this target's host options, validates them at the call, and returns
|
|
70
|
-
the target you pass to `render`. `report()` compiles once and `report.render(pdf(options), data)`
|
|
71
|
+
the target you pass to `render`. The factory refuses an option it does not know. It throws a `TypeError` at the call for an unknown key. It throws one also for a key with a value of the wrong type. `report()` compiles once and `report.render(pdf(options), data)`
|
|
71
72
|
resolves the document bytes. One compile serves any number of configurations (A4 and letter from
|
|
72
73
|
the same report). Compile at startup and render per request. Definition problems throw at
|
|
73
74
|
`report()`, at compile time.
|
|
@@ -81,14 +82,14 @@ The compiled report carries `stream` (the raw event generator), `names`, `functi
|
|
|
81
82
|
`paths`, like every quario report. Engine-level options (`query` budgets, the license key)
|
|
82
83
|
live on the instance, and `q.license` settles with the verification result.
|
|
83
84
|
|
|
84
|
-
Rendering is asynchronous and
|
|
85
|
+
Rendering is asynchronous and returns the loop between batches, so a large report never blocks the
|
|
85
86
|
host. Render-time failures reject with located errors.
|
|
86
87
|
|
|
87
88
|
### Options
|
|
88
89
|
|
|
89
90
|
Page size and fonts are target configuration. The margin is the target's unless the document
|
|
90
|
-
declares `page.margin`, in which case the document's
|
|
91
|
-
declare one reject the render.
|
|
91
|
+
declares `page.margin`, in which case the document's margin wins. A document and a target that
|
|
92
|
+
both declare one reject the render.
|
|
92
93
|
|
|
93
94
|
```js
|
|
94
95
|
{
|
|
@@ -98,7 +99,7 @@ declare one reject the render.
|
|
|
98
99
|
}
|
|
99
100
|
```
|
|
100
101
|
|
|
101
|
-
`size` defaults to `A4` (595.28 × 841.89 pt)
|
|
102
|
+
`size` defaults to `A4` (595.28 × 841.89 pt). An array is a custom `[width, height]` in points. An
|
|
102
103
|
unknown size name is an error the factory throws. `margin` applies to all four sides and defaults to 54
|
|
103
104
|
(0.75 in). Text renders at 10pt when nothing declares a size: a document's type size is the
|
|
104
105
|
document's own, so it is the report's `style.size`, not a host option. Line leading is 1.4× a line's
|
|
@@ -108,8 +109,8 @@ largest font size.
|
|
|
108
109
|
|
|
109
110
|
**Guaranteed** normative behavior a conforming target must produce:
|
|
110
111
|
|
|
111
|
-
- Table header rows repeat after every page break. An unstyled table draws no rules
|
|
112
|
-
authored border on a header row or a total row
|
|
112
|
+
- Table header rows repeat after every page break. An unstyled table draws no rules. The target draws an
|
|
113
|
+
authored border on a header row or a total row at the width written.
|
|
113
114
|
- A group header always travels with its first content unit: the first detail lines, or a table's
|
|
114
115
|
header plus its first row. An instance's headers also repeat at the top of every page it
|
|
115
116
|
continues onto (outermost first, then content) and stop when the instance ends.
|
|
@@ -117,25 +118,25 @@ largest font size.
|
|
|
117
118
|
- A row that fits on a page is never split across one.
|
|
118
119
|
- `break: "page"` on a group opens a fresh page per instance.
|
|
119
120
|
- `reset: "page"` on a group does the same and restarts `page.number` / `page.total` for that instance's sequence.
|
|
120
|
-
-
|
|
121
|
-
content and
|
|
121
|
+
- The target honours authored column `width` percentages. It measures the remaining columns from
|
|
122
|
+
content and scales them to fill the rest. A table whose authored shares leave the width-less columns
|
|
122
123
|
nothing is a definition error, so there is no over-commitment case.
|
|
123
124
|
- Hidden cells keep their column slot.
|
|
124
125
|
- Page bands render on every page, their heights reserved out of the body area.
|
|
125
|
-
- A report header declaring `height` pins a box of that many points from the page top
|
|
126
|
+
- A report header declaring `height` pins a box of that many points from the page top. Its items
|
|
126
127
|
pack from the top and the next band starts where the box ends.
|
|
127
|
-
- Every group instance opens with a half-line gap, dropped at a page top
|
|
128
|
+
- Every group instance opens with a half-line gap, dropped at a page top. Authored `spaceBefore`
|
|
128
129
|
drops at a fresh page or strip top the same way.
|
|
129
130
|
- An item or row a page cannot hold whole still carries its box: each slice draws the sides the
|
|
130
131
|
break left it, the top on the first and the bottom on the last.
|
|
131
|
-
- A split lays out as one block at the height of its tallest slot
|
|
132
|
+
- A split lays out as one block at the height of its tallest slot. Every slot's box is the split's
|
|
132
133
|
height, and a slot's `valign` places its content in that slack, as a table cell's does in its
|
|
133
134
|
row's.
|
|
134
135
|
- Page columns are strips: the flow fills one to its foot, moves to the next, and turns the page
|
|
135
|
-
once the last is spent
|
|
136
|
+
once the last is spent. A table restates its headings at a strip head, and a group's headers only
|
|
136
137
|
at a page head.
|
|
137
|
-
- The band-role defaults (a bold, larger report header
|
|
138
|
-
author's own style
|
|
138
|
+
- The band-role defaults (a bold, larger report header, and bold group headers) sit under the
|
|
139
|
+
author's own style. `uppercase` capitalises the drawn string before the target measures it.
|
|
139
140
|
- An image draws at the size its `fit` dictates (`natural` at 96 dpi capped at the content
|
|
140
141
|
width, `width` scaled to it, aspect ratio preserved) and is never split across a page break:
|
|
141
142
|
one that does not fit the remaining height moves whole to a fresh page.
|
|
@@ -154,9 +155,9 @@ without authored margins.
|
|
|
154
155
|
|
|
155
156
|
The base-14 Helvetica, Times, and Courier families carry the default output (`family: "sans"`,
|
|
156
157
|
`"serif"`, `"mono"`, each with regular, bold, italic, and bold-italic faces), using WinAnsi
|
|
157
|
-
encoding. Characters a face cannot draw render as `?` rather than failing the report, because
|
|
158
|
-
|
|
159
|
-
base-14 limit
|
|
158
|
+
encoding. Characters a face cannot draw render as `?` rather than failing the report, because
|
|
159
|
+
quario does not trust cell text, and one stray character must not take a document down. WinAnsi
|
|
160
|
+
is the base-14 limit. A TrueType face's cmap is its own.
|
|
160
161
|
|
|
161
162
|
For full Unicode, supply TrueType families and select them by name:
|
|
162
163
|
|
|
@@ -169,10 +170,10 @@ const target = pdf({
|
|
|
169
170
|
// then, in the schema: style: { family: "Inter" }
|
|
170
171
|
```
|
|
171
172
|
|
|
172
|
-
Missing variants fall back to the family's regular. Embedded text uses the font's own metrics
|
|
173
|
-
|
|
174
|
-
copy see the original text.
|
|
175
|
-
without a `regular`
|
|
173
|
+
Missing variants fall back to the family's regular. Embedded text uses the font's own metrics.
|
|
174
|
+
The target subsets it to the glyphs the document uses and carries a ToUnicode map, so extraction
|
|
175
|
+
and copy see the original text. quario trusts fonts as host assets, like registered functions. The
|
|
176
|
+
factory refuses a mapping without a `regular` (`options.fonts.<name>.regular: required`). Bytes
|
|
176
177
|
the parser cannot read reject the render, located at the same option path.
|
|
177
178
|
|
|
178
179
|
## Outline
|
|
@@ -183,27 +184,38 @@ when there is none. Ungrouped reports have no outline.
|
|
|
183
184
|
|
|
184
185
|
## Determinism
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
data, and options produce **byte-identical** output. That makes report bytes cacheable, diffable,
|
|
187
|
+
This target pins the document's dates rather than stamping them, and `meta` is opt-in, so the
|
|
188
|
+
same schema, data, and options produce **byte-identical** output. That makes report bytes cacheable, diffable,
|
|
188
189
|
and safe to compare in tests.
|
|
189
190
|
|
|
190
191
|
## Unlicensed marking
|
|
191
192
|
|
|
192
193
|
An unlicensed render draws the wording from `report-start.marking` once per page, after
|
|
193
194
|
everything else: a translucent grey line corner-to-corner over content and page furniture. The
|
|
194
|
-
per-page presence is normative
|
|
195
|
+
per-page presence is normative. Exact geometry is best-effort. A licensed render draws nothing.
|
|
195
196
|
|
|
196
197
|
## Page furniture
|
|
197
198
|
|
|
198
|
-
Paper size and fonts are options, not schema
|
|
199
|
+
Paper size and fonts are options, not schema. The margin is an option a document may declare in
|
|
199
200
|
its stead. Authored watermarks and finer break control stay out of scope. Keep-together and continuation headers are by construction, above.
|
|
200
201
|
|
|
202
|
+
The full contract is [The PDF target](https://getquario.com/docs/diving-deeper/pdf-target/),
|
|
203
|
+
with each declaration's fate in the
|
|
204
|
+
[support matrix](https://getquario.com/docs/reference/support-matrix/).
|
|
205
|
+
|
|
206
|
+
## Documentation
|
|
207
|
+
|
|
208
|
+
[The quario documentation](https://getquario.com/docs/) is the reference.
|
|
209
|
+
The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
|
|
210
|
+
specification of what a report may declare, and
|
|
211
|
+
[`@quario/pdf`](https://getquario.com/docs/reference/pdf/) is this package's own API.
|
|
212
|
+
|
|
201
213
|
## License
|
|
202
214
|
|
|
203
|
-
Commercial software with readable source.
|
|
204
|
-
licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
215
|
+
Commercial software with readable source. Evaluation is free, unlimited, and watermarked.
|
|
216
|
+
Per-developer licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
205
217
|
|
|
206
|
-
Pass your license key once, on the instance
|
|
218
|
+
Pass your license key once, on the instance. quario verifies it offline:
|
|
207
219
|
|
|
208
220
|
```js
|
|
209
221
|
const q = quario({ license: "quario_..." });
|
package/lib/index.d.ts
CHANGED
|
@@ -14,7 +14,11 @@ export interface PdfMeta {
|
|
|
14
14
|
/** One embeddable TrueType family: the layout's own, measured and embedded from the same bytes. */
|
|
15
15
|
export type PdfFontFamily = LayoutFontFamily;
|
|
16
16
|
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Host controls, taken and validated at the factory call: an option this
|
|
19
|
+
* target does not understand, or one of the wrong type, throws a `TypeError`
|
|
20
|
+
* there rather than costing the host the option in silence.
|
|
21
|
+
*/
|
|
18
22
|
export interface PdfOptions {
|
|
19
23
|
page?: PdfPage;
|
|
20
24
|
meta?: PdfMeta;
|
package/lib/index.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { PDFDocument } from "@cantoo/pdf-lib";
|
|
17
17
|
import { layout } from "@quario/layout";
|
|
18
|
+
import { hostMeta, hostOptions } from "quario";
|
|
18
19
|
import { embedFonts } from "./embed.js";
|
|
19
20
|
import { outline } from "./outline.js";
|
|
20
21
|
import { paint } from "./painter.js";
|
|
@@ -26,13 +27,16 @@ import { paint } from "./painter.js";
|
|
|
26
27
|
// Optional document information. Never a date: pdf-lib stamps the current time
|
|
27
28
|
// unless told otherwise, and a timestamp would make the same input render
|
|
28
29
|
// different bytes on every run. Each key names the pdf-lib setter it feeds,
|
|
29
|
-
// here rather than in a table, and
|
|
30
|
+
// here rather than in a table, and that mapping is all this target owns of
|
|
31
|
+
// `meta` -- the contract itself is the engine's `hostMeta`, checked at the
|
|
32
|
+
// factory call, so a present property is a string and a nullish one is a
|
|
33
|
+
// property the host did not write.
|
|
30
34
|
/** @type {(doc: any, meta: any) => void} */
|
|
31
35
|
let describe = (doc, meta) => {
|
|
32
36
|
doc.setCreationDate(new Date(0));
|
|
33
37
|
doc.setModificationDate(new Date(0));
|
|
34
38
|
let field = (/** @type {string} */ method, /** @type {any} */ value) => {
|
|
35
|
-
if (
|
|
39
|
+
if (value != null) doc[method](value);
|
|
36
40
|
};
|
|
37
41
|
if (meta) {
|
|
38
42
|
field("setTitle", meta.title);
|
|
@@ -61,7 +65,9 @@ let open = async (meta, custom) => {
|
|
|
61
65
|
* The target (see SCHEMA.md, "Instances and targets").
|
|
62
66
|
*/
|
|
63
67
|
export function pdf(options) {
|
|
64
|
-
|
|
68
|
+
hostOptions(options, ["page", "meta", "fonts"], "options");
|
|
69
|
+
let { page, fonts: custom, meta: written } = options ?? {};
|
|
70
|
+
let meta = hostMeta(written, "options.meta");
|
|
65
71
|
let paged = layout({ page, fonts: custom });
|
|
66
72
|
/** @type {(stream: any) => (data?: any) => Promise<Uint8Array>} */
|
|
67
73
|
let compile = (stream) => {
|
package/lib/painter.js
CHANGED
|
@@ -80,6 +80,7 @@ let paintLine = ({ page, height }, op) =>
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
/** @type {(painter: Painter, op: any) => void} */
|
|
83
|
+
// fallow-ignore-next-line code-duplication -- one operator path, so a face is embedded once per page; ink, baseline, rotation and opacity all differ, and the marking's draw stays its own
|
|
83
84
|
let paintText = ({ page, height, fonts, faceKey }, op) => {
|
|
84
85
|
let font = fontFor(fonts, op.font);
|
|
85
86
|
page.pushOperators(
|
|
@@ -100,6 +101,7 @@ let paintText = ({ page, height, fonts, faceKey }, op) => {
|
|
|
100
101
|
// face reuses the page's `faceKey` resource instead of growing the dictionary
|
|
101
102
|
// per draw.
|
|
102
103
|
/** @type {(painter: Painter, op: any) => void} */
|
|
104
|
+
// fallow-ignore-next-line code-duplication -- one operator path, so a face is embedded once per page; ink, baseline, rotation and opacity all differ, and the marking's draw stays its own
|
|
103
105
|
let paintMark = ({ page, height, fonts, faceKey }, op) => {
|
|
104
106
|
let font = fontFor(fonts, op.font);
|
|
105
107
|
page.pushOperators(
|
|
@@ -126,7 +128,7 @@ let paintMark = ({ page, height, fonts, faceKey }, op) => {
|
|
|
126
128
|
// The embedders parse the bytes, and what they throw when those bytes are bad
|
|
127
129
|
// says nothing an author could act on: a corrupt PNG used to reach a host as a
|
|
128
130
|
// bare `RangeError: Invalid typed array length: 0`, naming no image and no
|
|
129
|
-
// item (quario-e0bz). The engine vouched for the magic numbers and
|
|
131
|
+
// item (quario-e0bz). The engine vouched for the magic numbers and
|
|
130
132
|
// read the size out of the header, so a file corrupt past that point arrives
|
|
131
133
|
// here looking like any other and this is the first place that knows. The
|
|
132
134
|
// embedder's own class and words survive behind the path, which is the shape
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quario/pdf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "The browserless, paginated PDF render target for quario — in the makings, not yet released",
|
|
5
5
|
"homepage": "https://getquario.com",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -36,21 +36,21 @@
|
|
|
36
36
|
"postpack": "node -e \"require('fs').rmSync('LICENSE',{force:true})\""
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cantoo/pdf-lib": "
|
|
40
|
-
"@quario/layout": "^0.
|
|
39
|
+
"@cantoo/pdf-lib": "~2.9.1",
|
|
40
|
+
"@quario/layout": "^0.6.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@arethetypeswrong/cli": "^0.18.3",
|
|
44
44
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
45
45
|
"@types/fontkit": "^2.0.9",
|
|
46
46
|
"fontkit": "^2.0.4",
|
|
47
|
-
"quario": "^0.
|
|
47
|
+
"quario": "^0.9.0",
|
|
48
48
|
"size-limit": "^13.0.3",
|
|
49
49
|
"typescript": "^7.0.2"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"fontkit": "^2.0.4",
|
|
53
|
-
"quario": "^0.
|
|
53
|
+
"quario": "^0.9.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"fontkit": {
|