@quario/layout 0.5.0 → 0.6.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 CHANGED
@@ -1,5 +1,50 @@
1
1
  # @quario/layout
2
2
 
3
+ ## 0.6.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
+ - Updated dependencies
46
+ - quario@0.9.0
47
+
3
48
  ## 0.5.0
4
49
 
5
50
  ### Minor Changes
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  **The paged layout of a [quario](https://www.npmjs.com/package/quario) report, as a display
4
4
  list.** `layout(options)` is a render target whose output is pages of boxes, text runs, rules
5
- and images in points the layout the PDF target writes and the viewer and editor paint on screen,
6
- so the preview breaks its pages exactly where the document does.
5
+ and images in points. The PDF target writes that layout, and the viewer and editor paint it on
6
+ screen, so the preview breaks its pages exactly where the document does.
7
7
 
8
8
  You install this package directly only to consume the list yourself. `@quario/pdf`, `@quario/viewer`
9
9
  and `@quario/editor` depend on it and run it for you.
@@ -39,11 +39,16 @@ list.pages[0].boxes; // which schema node was drawn where
39
39
  ```
40
40
 
41
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
44
- it is set in, and — for a built-in family only — the per-character advances the measurer laid it
45
- out with) and, on an unlicensed render, one `mark` — and its `boxes`, one per schema node drawn,
46
- named by the node's path.
42
+ `list.height` are the paper every page is drawn on. A report has one paper, so a page does not
43
+ carry its own size.
44
+
45
+ A page carries:
46
+
47
+ - its `{ number, total }`,
48
+ - its ops: `rect`, `line`, `image`, and `text`. A `text` op holds the string to draw, the face it
49
+ uses, and, for a built-in family only, the per-character advances the measurer laid it out
50
+ with. An unlicensed render adds one `mark` op.
51
+ - its `boxes`, one per schema node drawn, named by the node's path.
47
52
 
48
53
  ## Options
49
54
 
@@ -52,7 +57,7 @@ named by the node's path.
52
57
  | `page` | `{ size: "A4" \| "letter" \| [w, h], margin }` points | A4, 54pt |
53
58
  | `fonts` | A font mapping: family name to TrueType bytes | none |
54
59
 
55
- They are the pdf target's own `page` and `fonts`: pass the same object to `layout()` and to
60
+ They are the pdf target's own `page` and `fonts`. Pass the same object to `layout()` and to
56
61
  `pdf()` and the two agree, because the pdf target runs this layout and paints its list.
57
62
 
58
63
  ## Painting on a canvas
@@ -70,26 +75,26 @@ hit(page, x, y); // -> { path, x, y, w, h } or null
70
75
  ```
71
76
 
72
77
  `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
75
- no `img-src` grant, no URLs. `hit` answers which schema node was drawn at a point: the smallest box
78
+ the ops are in the page's own coordinates, so a smaller canvas clips them, and `paint` fills the
79
+ white over the whole canvas. `paint` decodes images from the bytes on the list. No `img-src`
80
+ grant, no URLs. `hit` answers which schema node was drawn at a point: the smallest box
76
81
  containing it.
77
82
 
78
- Text is drawn in the face the document will use. A family you pass as `fonts` is registered as a
79
- `FontFace` from your own bytes, and its runs are drawn as the browser shapes them — the same
80
- shaping the PDF gets from the same file, so ligatures, joined scripts and accents look on screen
81
- the way they will on paper. Text in the built-in families is drawn character by character at the
82
- measured advances, because the typeface a browser has for Helvetica, Times or Courier only stands
83
- in for the one the PDF writes, and without the correction a line would drift as it ran.
83
+ Text is drawn in the face the document will use. `paint` registers a family you pass as `fonts` as
84
+ a `FontFace` from your own bytes, and the browser then shapes its runs. That is the same shaping
85
+ the PDF gets from the same file, so ligatures, joined scripts and accents look on screen the way
86
+ they will on paper. `paint` draws text in the built-in families character by character at the
87
+ measured advances. The typeface a browser has for Helvetica, Times or Courier only stands in for
88
+ the one the PDF writes, and without that correction a line would drift as it ran.
84
89
 
85
90
  Inside a single run of a family you supply, the preview is not promised to place glyphs exactly
86
- where the PDF does: both read a glyph's own advance and neither applies the font's GPOS
91
+ where the PDF does. Both read a glyph's own advance and neither applies the font's GPOS
87
92
  positioning, so a browser may kern by a fraction more. Where a line breaks, how wide a column is,
88
93
  and where a page ends are the layout's — one measurer, so preview and document agree.
89
94
 
90
- `PX_PER_POINT` is how big a point is on screen — 96 dpi over PostScript's 72 so a surface showing
91
- a page at 100% sizes its canvas `list.width * PX_PER_POINT` CSS pixels wide and paints at
92
- `PX_PER_POINT * devicePixelRatio`. Any zoom is a factor on top of that.
95
+ `PX_PER_POINT` is how big a point is on screen — 96 dpi over PostScript's 72. A surface that draws
96
+ a page at 100% therefore sizes its canvas `list.width * PX_PER_POINT` CSS pixels wide and paints
97
+ at `PX_PER_POINT * devicePixelRatio`. Any zoom is a factor on top of that.
93
98
 
94
99
  ## Documentation
95
100
 
@@ -100,5 +105,5 @@ specification of what a report may declare, and
100
105
 
101
106
  ## License
102
107
 
103
- quario is commercial software; evaluation is free and fully featured, with output marked as
104
- unlicensed. See [LICENSE](https://getquario.com/license).
108
+ quario is commercial software. Evaluation is free and fully featured, and quario marks its output
109
+ as unlicensed. See [LICENSE](https://getquario.com/license).
package/lib/fonts.js CHANGED
@@ -165,8 +165,8 @@ let useFontkit = async () => {
165
165
  let asFamily = (name, def, at) => {
166
166
  let path = at + "." + name;
167
167
  if (!def || typeof def !== "object")
168
- throw Error(path + ": expected { regular, bold?, italic?, boldItalic? }");
169
- if (def.regular == null) throw Error(path + ".regular: required");
168
+ throw TypeError(path + ": expected { regular, bold?, italic?, boldItalic? }");
169
+ if (def.regular == null) throw TypeError(path + ".regular: required");
170
170
  return path;
171
171
  };
172
172
 
@@ -279,6 +279,10 @@ export async function loadFonts(custom) {
279
279
  return { families };
280
280
  }
281
281
 
282
+ // An array is a `typeof "object"` whose families would be named 0, 1, 2.
283
+ /** @type {(custom: any) => boolean} */
284
+ let record = (custom) => typeof custom === "object" && !Array.isArray(custom);
285
+
282
286
  /**
283
287
  * Check the shape of a host's font mapping without loading a parser.
284
288
  *
@@ -295,7 +299,7 @@ export async function loadFonts(custom) {
295
299
  */
296
300
  export let checkFonts = (custom, at = "options.fonts") => {
297
301
  if (custom == null) return;
298
- if (typeof custom !== "object") throw Error(at + ": expected a record of families");
302
+ if (!record(custom)) throw TypeError(at + ": expected a record of families");
299
303
  for (let [name, def] of Object.entries(custom)) asFamily(name, def, at);
300
304
  };
301
305
 
package/lib/index.d.ts CHANGED
@@ -22,7 +22,11 @@ export interface LayoutFontFamily {
22
22
  /** The font mapping: a declared `family` name to the bytes it measures against. */
23
23
  export type LayoutFonts = Record<string, LayoutFontFamily>;
24
24
 
25
- /** Host controls, taken and validated at the factory call. */
25
+ /**
26
+ * Host controls, taken and validated at the factory call: an option this
27
+ * target does not understand, or one of the wrong type, throws a `TypeError`
28
+ * there rather than costing the host the option in silence.
29
+ */
26
30
  export interface LayoutOptions {
27
31
  page?: LayoutPage;
28
32
  /**
package/lib/index.js CHANGED
@@ -20,7 +20,7 @@
20
20
  * driver dispatches to is the band flow's, next door in `layout.js`; this
21
21
  * file adds none. The package publishes `lib/` verbatim.
22
22
  */
23
- import { breathe, walk } from "quario";
23
+ import { breathe, hostOptions, walk } from "quario";
24
24
  import { listing, stamp } from "./canvas.js";
25
25
  import { checkFonts, loadFonts } from "./fonts.js";
26
26
  import { flow, furniture } from "./layout.js";
@@ -58,6 +58,19 @@ let markPages = async (canvas, marking) => {
58
58
  await overPages(canvas, () => canvas.watermark(mark));
59
59
  };
60
60
 
61
+ // This target's whole option contract, checked at the factory call: the key
62
+ // sets, then the geometry and the font mapping's shape. The `page` key set
63
+ // closes here rather than in `pageBox`, which the two surface elements also
64
+ // call -- a host configures those by assigning DOM properties, and nothing can
65
+ // refuse an unknown one (`docs/adr/0072`).
66
+ /** @type {(options: any) => void} */
67
+ let checkOptions = (options) => {
68
+ hostOptions(options, ["page", "fonts"], "options");
69
+ hostOptions(options?.page, ["size", "margin"], "options.page");
70
+ geometry(options?.page);
71
+ checkFonts(options?.fonts);
72
+ };
73
+
61
74
  /**
62
75
  * The layout target:
63
76
  * `quario().report(schema).render(layout({ page }), data)` resolves the
@@ -70,8 +83,7 @@ let markPages = async (canvas, marking) => {
70
83
  * The target (see SCHEMA.md, "Instances and targets").
71
84
  */
72
85
  export function layout(options) {
73
- geometry(options?.page);
74
- checkFonts(options?.fonts);
86
+ checkOptions(options);
75
87
  let custom = options?.fonts;
76
88
  /** @type {(stream: any) => (data?: any) => Promise<Layout>} */
77
89
  let compile = (stream) => async (data) => {
package/lib/page.js CHANGED
@@ -23,7 +23,7 @@ let SIZES = /** @type {Record<string, [number, number]>} */ ({
23
23
 
24
24
  /** @type {(msg: string) => never} */
25
25
  let err = (msg) => {
26
- throw Error(msg);
26
+ throw TypeError(msg);
27
27
  };
28
28
 
29
29
  /** @type {(value: number) => boolean} */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quario/layout",
3
- "version": "0.5.0",
3
+ "version": "0.6.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.8.0",
46
+ "quario": "^0.9.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.8.0"
52
+ "quario": "^0.9.0"
53
53
  },
54
54
  "peerDependenciesMeta": {
55
55
  "fontkit": {