@quario/pdf 0.8.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 +55 -0
- package/README.md +36 -35
- package/lib/index.d.ts +5 -1
- package/lib/index.js +9 -3
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# @quario/pdf
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
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.
|
|
14
|
+
|
|
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.
|
|
18
|
+
|
|
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
|
+
|
|
3
58
|
## 0.8.0
|
|
4
59
|
|
|
5
60
|
### Minor Changes
|
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,19 +184,19 @@ 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
|
|
|
201
202
|
The full contract is [The PDF target](https://getquario.com/docs/diving-deeper/pdf-target/),
|
|
@@ -211,10 +212,10 @@ specification of what a report may declare, and
|
|
|
211
212
|
|
|
212
213
|
## License
|
|
213
214
|
|
|
214
|
-
Commercial software with readable source.
|
|
215
|
-
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.
|
|
216
217
|
|
|
217
|
-
Pass your license key once, on the instance
|
|
218
|
+
Pass your license key once, on the instance. quario verifies it offline:
|
|
218
219
|
|
|
219
220
|
```js
|
|
220
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/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": {
|