@quillmark/wasm 0.115.0 → 0.116.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 +205 -0
- package/README.md +46 -47
- package/core/wasm.d.ts +57 -77
- package/core/wasm.js +102 -102
- package/core/wasm_bg.wasm +0 -0
- package/core/wasm_bg.wasm.d.ts +4 -5
- package/package.json +1 -1
- package/render/wasm.d.ts +83 -144
- package/render/wasm.js +126 -144
- package/render/wasm_bg.wasm +0 -0
- package/render/wasm_bg.wasm.d.ts +6 -7
- package/runtime/runtime.d.ts +49 -62
- package/runtime/runtime.js +15 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,210 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.116.0 - 2026-09-26
|
|
4
|
+
|
|
5
|
+
Upgrade path: [0.115 → 0.116](docs/migrations/0.115-to-0.116.md).
|
|
6
|
+
|
|
7
|
+
### What blocks a render
|
|
8
|
+
|
|
9
|
+
- feat(core)!: **a card no kind claims, and a body under `body.enabled: false`,
|
|
10
|
+
render and warn.** A render fails only where the engine would have to invent
|
|
11
|
+
what the author wrote: a value that will not read as its declared cell's
|
|
12
|
+
type, or markup the grammar cannot read. Input no declaration claims
|
|
13
|
+
displaces nothing, so it renders and warns; SCHEMAS.md § "What blocks a
|
|
14
|
+
render" sorts every validation code into incomplete, malformed and
|
|
15
|
+
unclaimed. `validation::unknown_card` and `validation::body_disabled` are
|
|
16
|
+
`Severity::Warning` from `Quill::validate`, `unknown_card` carries
|
|
17
|
+
`allowed`, and an unclaimed card keeps its place in `$cards` with its
|
|
18
|
+
fields verbatim. `ValidationError::UnknownCard` and `BodyDisabled` are
|
|
19
|
+
deleted. This is the release's silent break: a pipeline that relied on the
|
|
20
|
+
render failing catches nothing. (#1877)
|
|
21
|
+
- feat(core)!: **an undeclared key warns `validation::unknown_field`**, at
|
|
22
|
+
every depth of a claimed card: a card field, a typed dictionary's property,
|
|
23
|
+
an array element's, a matrix member's column, and a cell inside a variant
|
|
24
|
+
container no world declares. `args` carry `suggestion`, the closest declared
|
|
25
|
+
field the document leaves unwritten, or `container` and `variant` for a
|
|
26
|
+
variant cell written beside its discriminant rather than under it. (#1871)
|
|
27
|
+
- feat(quillmark,cli)!: **a one-shot render carries validate's warnings.**
|
|
28
|
+
`Quillmark::render`, WASM `engine.render` and Python `render` put every
|
|
29
|
+
`Quill::validate` warning on the result's `warnings` ahead of the compile's,
|
|
30
|
+
so the unclaimed input the page left out is on the result a pipeline reads.
|
|
31
|
+
A session's warnings stay its current compile's. (#1931)
|
|
32
|
+
- feat(core)!: **a card-yaml block with no `$kind` fails the parse** as
|
|
33
|
+
`parse::missing_kind`, located at its opener, with a hint naming both fixes:
|
|
34
|
+
a `$kind:` line, or a backtick fence. Such a block was a card missing its
|
|
35
|
+
kind line or tilde-fenced code, and reading it as either was a guess. Every
|
|
36
|
+
card now carries `$kind`: `ResolvedCard.kind` is `String` (`string` in
|
|
37
|
+
TypeScript), and a stored card with no `$kind` folds into the body above it
|
|
38
|
+
on load, its payload as a code block, then its body. (#1905)
|
|
39
|
+
- feat(core)!: **a card-yaml payload that is not a mapping fails at its
|
|
40
|
+
fence** as `parse::payload_not_mapping`, located at the opener, with `actual`
|
|
41
|
+
and `info` args and a hint naming the backtick fence, where it was an
|
|
42
|
+
unlocated `parse::invalid_structure` on a card and `parse::missing_quill` on
|
|
43
|
+
the root. (#1873)
|
|
44
|
+
|
|
45
|
+
### `!must_fill` is removed
|
|
46
|
+
|
|
47
|
+
- refactor(core)!: **`!must_fill` and `validation::must_fill` are gone.** The
|
|
48
|
+
marker said a cell held a placeholder rather than an answer, and the schema
|
|
49
|
+
half of the warning read `default:`'s absence as an obligation. `default:`
|
|
50
|
+
is the value an unanswered cell renders, `?` moves the render floor, and
|
|
51
|
+
null ≡ absent holds on every surface. A value under the marker keeps its
|
|
52
|
+
value: in markdown the tag drops as any unknown tag does, warned
|
|
53
|
+
`parse::unsupported_yaml_tag` at the tagged node's rooted path (which that
|
|
54
|
+
code now carries), and a stored row of any earlier tag is read past its
|
|
55
|
+
`fill` keys. Deleted: `store_fill`,
|
|
56
|
+
`is_fill`, the `QuillValue` fill set, `FieldSchema::{must_fill,
|
|
57
|
+
example_content}`, `edit::fill_on_mapping`,
|
|
58
|
+
`parse::fill_marker_unsupported_position`, WASM `storeFill` / `isFill`, and
|
|
59
|
+
the card wire's `fill` / `nestedFills`. (#1906, #1933)
|
|
60
|
+
- refactor(core)!: **the storage tag is `quillmark/document@0.116.0`.** A
|
|
61
|
+
field item carries no `fill` or `nested_fills`, and a row carrying either,
|
|
62
|
+
or any key its type does not name at the document, card, payload or item
|
|
63
|
+
level, fails the load, as does a nested comment spanning lines, which
|
|
64
|
+
`to_markdown` would have written as bare YAML. Every earlier tag still
|
|
65
|
+
reads. A row's bytes move on its next save, so content hashes
|
|
66
|
+
recompute and a 0.115 build refuses the row from then on.
|
|
67
|
+
- feat(core,wasm,python)!: **a card-wire payload item refuses an unknown
|
|
68
|
+
key.** A 0.115 integration still sending `{value, fill: true}` committed the
|
|
69
|
+
placeholder as an answer, and an item typo vanished; `insertCard` throws and
|
|
70
|
+
`insert_card` raises `ValueError` instead. (#1933)
|
|
71
|
+
- feat(core): **a `?` on a cell's type renders it `none` when unanswered**,
|
|
72
|
+
so a plate can tell "0 votes against" from "nobody entered a tally". `?` and
|
|
73
|
+
`default:` are exclusive (`quill::optional_default`), a namespace takes no
|
|
74
|
+
`?` (`quill::optional_namespace`), and the transform schema projects
|
|
75
|
+
`type: [t, "null"]`. (#1880)
|
|
76
|
+
|
|
77
|
+
### The blueprint and the seed
|
|
78
|
+
|
|
79
|
+
- feat(core)!: **seeding commits no `example:`.** An example documents a
|
|
80
|
+
cell's shape rather than answering it, and `body.example` is guide text
|
|
81
|
+
quills write as instructions. `seed_document`, `seed_main` and `seed_card`
|
|
82
|
+
commit the `$seed` overlay alone; every other body is empty and every other
|
|
83
|
+
field absent. (#1923)
|
|
84
|
+
- feat(core,wasm,python)!: **the blueprint answers nothing on the author's
|
|
85
|
+
behalf.** A defaultless cell is empty (`title: # string`) with its
|
|
86
|
+
`example:` on a `# e.g.` line above it; every body is empty, a
|
|
87
|
+
`body.example` riding a `# body e.g.` line that closes the payload and a
|
|
88
|
+
kind under `body.enabled: false` closing with `# no body`; a card's
|
|
89
|
+
description rides its `$kind` line; a `default: []` table is led by the
|
|
90
|
+
whole field commented out holding its row; and a matrix names its columns
|
|
91
|
+
in an `# e.g.` line, a column with no example showing its
|
|
92
|
+
`<type>[<format>]`. (#1874, #1942)
|
|
93
|
+
- feat(core,wasm,python)!: **`FORMAT_RULES` is `MARKDOWN_RULES`**, headed
|
|
94
|
+
`Quillmark Markdown rules:` (`markdownRules` / `markdown_rules`), and keeps
|
|
95
|
+
only what departs from plain Markdown and YAML. `blueprint_instruction` is
|
|
96
|
+
deleted in every binding: a prompt is the consumer's to write. (#1942)
|
|
97
|
+
- feat(core)!: **`to_markdown` writes a multi-line string as a `|` literal
|
|
98
|
+
block** (`|-` without a trailing newline), at every depth, where it wrote
|
|
99
|
+
one double-quoted line. A string a block would not hold verbatim stays
|
|
100
|
+
quoted. The value round-trips unchanged; the markdown bytes move. (#1883)
|
|
101
|
+
|
|
102
|
+
### The quill authoring contract
|
|
103
|
+
|
|
104
|
+
- feat(core)!: **a field's and a card kind's label is a top-level `title`**,
|
|
105
|
+
beside `description`, which `schema()` emits and the blueprint prints as
|
|
106
|
+
`# <title> — <description>`. `ui.title` fails the load, a `{field}` token
|
|
107
|
+
in a `title` is `quill::title_template`, and a `title` on an array's `items`
|
|
108
|
+
is `quill::title_on_items`. (#1922)
|
|
109
|
+
- refactor(core,wasm)!: **`body.unsupported` is removed**, with
|
|
110
|
+
`plate::unsupported_construct`, `Quill::unsupported_constructs` and the WASM
|
|
111
|
+
`QuillBlockConstruct`. Nothing verified a declaration against the plate and
|
|
112
|
+
no editor read it; a `Quill.yaml` still declaring it fails
|
|
113
|
+
`quill::invalid_body`.
|
|
114
|
+
- fix(core)!: **an unquoted `quill.version` reads as written**: `version:
|
|
115
|
+
1.10` is `1.10`, where it loaded as `1.1`, and `1.1e1` fails
|
|
116
|
+
`quill::invalid_version`.
|
|
117
|
+
- feat(core)!: **a `plaintext(inline)` value ending in a newline names `|-`
|
|
118
|
+
as the fix.** `validation::not_inline`, `edit::field_not_inline` and
|
|
119
|
+
`conform::field_not_inline` carry `trailingNewline: true`, and
|
|
120
|
+
`ValidationError::NotInline` / `EditError::FieldNotInline` gain
|
|
121
|
+
`trailing_newline`. (#1918)
|
|
122
|
+
- fix(core): `quill::invalid_name` suggests a name the loader accepts.
|
|
123
|
+
|
|
124
|
+
### The render date
|
|
125
|
+
|
|
126
|
+
- feat(core)!: **the host supplies the render date; a `date` field may hold
|
|
127
|
+
`today`.** The engine reads no clock. `Quillmark::open` / `render`,
|
|
128
|
+
`Quill::compile_data` / `compile_checked` / `resolve`,
|
|
129
|
+
`QuillConfig::compile_data` / `compile_checked`, `TypedReader::resolve` and
|
|
130
|
+
`Backend::open` take a required `today: CalendarDate`; a plate's
|
|
131
|
+
`datetime.today()` returns it whatever its `offset:`. The CLI (`--today`)
|
|
132
|
+
and the WASM and Python bindings default to the local date. A `date` value
|
|
133
|
+
is `YYYY-MM-DD` or `today`, and the blueprint annotates `date<YYYY-MM-DD |
|
|
134
|
+
today>`. `CalendarDate`'s parse fails with `ParseDateError`, which tells a
|
|
135
|
+
day the calendar lacks from a string that is no date. (#1900, #1930)
|
|
136
|
+
- feat(fixtures)!: `usaf_memo`'s blank `date` renders a fill-in field where it
|
|
137
|
+
rendered today's date.
|
|
138
|
+
|
|
139
|
+
### Plates and the Typst backend
|
|
140
|
+
|
|
141
|
+
- feat(typst)!: **a dictionary carries the `$ink` twin its fields print
|
|
142
|
+
through.** A scalar printed off `data` kept its click target only as a
|
|
143
|
+
direct read in the plate. `data`, each card, and each typed dictionary or
|
|
144
|
+
table row carry `$ink` and `$path` wherever a field has ink: compute with the
|
|
145
|
+
dictionary, print its ink (`ink(row).org`). A date's ink prints its default
|
|
146
|
+
display, and `display(row, "due", "[year]")` formats one by dictionary and
|
|
147
|
+
key. The keys are visible to `keys()`, `pairs()`, `len()`, `==` and a
|
|
148
|
+
spread. (#1868)
|
|
149
|
+
- feat(typst)!: **a plate imports the quill's `.typ` files by Typst's path
|
|
150
|
+
rules.** The plate loads at its `plate_file` path and every `.typ` file
|
|
151
|
+
outside `packages/` beside it, so a bare path resolves from the importing
|
|
152
|
+
file and a `/`-rooted one from the quill root. Plate diagnostics name the
|
|
153
|
+
declared `plate_file`, where they named `main.typ`, and a vendored package's
|
|
154
|
+
file under its spec (`@local/p:0.1.0/lib.typ`), apart from a quill module
|
|
155
|
+
of the same path. (#1869)
|
|
156
|
+
- feat(typst): **a `typst:` key the backend does not read warns
|
|
157
|
+
`typst::unknown_key`**, `typst.packages` included; a package is vendored
|
|
158
|
+
under `packages/`, never fetched. A package manifest without `version` or
|
|
159
|
+
`entrypoint` is skipped at load.
|
|
160
|
+
- refactor(typst): the `when` / `value-or` plate helpers are dropped; a plate
|
|
161
|
+
reads an optional cell with `!= none`.
|
|
162
|
+
- fix(typst): a negative scalar's ink lexes, and region queries stay linear.
|
|
163
|
+
|
|
164
|
+
### Rendering, the CLI and the bindings
|
|
165
|
+
|
|
166
|
+
- feat(core)!: **one raster ceiling, 16384 px a side**, `MAX_RASTER_SIDE`,
|
|
167
|
+
owned by core, where core refused past 16384² px of area and the WASM
|
|
168
|
+
painter clamped by side on its own. A PNG export long on one axis that fit
|
|
169
|
+
by area now fails `backend::invalid_raster_scale`.
|
|
170
|
+
- feat(wasm)!: **`paint(ctx, page, scale)` takes one scale and returns
|
|
171
|
+
nothing**; `PaintOptions` and `PaintResult` are deleted, and a missing or
|
|
172
|
+
non-finite scale throws `backend::invalid_raster_scale`.
|
|
173
|
+
- feat(cli): **`quillmark workspace` exports a plate for Typst's own
|
|
174
|
+
tooling**: the generated helper for one document, every vendored package
|
|
175
|
+
and the backend's fonts, with the `typst watch` command over them. A rerun
|
|
176
|
+
replaces the `packages/` and `fonts/` an earlier export wrote. The facade
|
|
177
|
+
re-exports it as `quillmark::typst_workspace`. (#1869)
|
|
178
|
+
- feat(cli): **`quillmark check` prints a document's every diagnostic**, and
|
|
179
|
+
`--strict` exits `1` on a warning, for a CI gate over a repository of
|
|
180
|
+
documents; `render` prints the warnings for what the page leaves out.
|
|
181
|
+
(#1872)
|
|
182
|
+
- fix(cli)!: `render` takes its format from an `-o` extension naming one, and
|
|
183
|
+
a `-f` the extension contradicts exits `1` before writing.
|
|
184
|
+
- feat(core,wasm,python): `Quill.emptyDocument()` / `Quill.empty_document()`
|
|
185
|
+
reach the bindings, and WASM `isInline` / `isPlain` export the content
|
|
186
|
+
predicates. (#1919)
|
|
187
|
+
|
|
188
|
+
### Fixes
|
|
189
|
+
|
|
190
|
+
- fix(core): the YAML prescan keeps comments, tags and anchors with the value
|
|
191
|
+
they sit on across continuation lines, dash lines, block scalars, multi-line
|
|
192
|
+
flow collections, a value below its key, nested keys with an inner `:` or a
|
|
193
|
+
leading `-`/`?`, and a removed item's trailing comment; a `$seed` overlay edit keeps the comment on
|
|
194
|
+
its `$seed` line.
|
|
195
|
+
- fix(core): `to_markdown` projects a content object at every depth of a
|
|
196
|
+
field's value, and a container under a sequence item's first key
|
|
197
|
+
round-trips.
|
|
198
|
+
- fix(core): a folded island past `isl-{u64::MAX}` mints a fresh id, and a
|
|
199
|
+
folded card's `$ext` stays out of the body.
|
|
200
|
+
- fix(core): the unknown-key walk stays linear in a mapping's keys.
|
|
201
|
+
- fix(core): a `$seed.<kind>.$body` for a kind under `body.enabled: false`
|
|
202
|
+
warns `validation::seed_unknown_field`, where the seed dropped it silently.
|
|
203
|
+
- fix(cli): `validate -v` reports a backend load warning once, where each of
|
|
204
|
+
the three canonical renders repeated it.
|
|
205
|
+
- fix(quillmark): the facade re-exports `ParseDateError`.
|
|
206
|
+
|
|
207
|
+
|
|
3
208
|
## v0.115.0 - 2026-09-22
|
|
4
209
|
|
|
5
210
|
Upgrade path: [0.114 → 0.115](docs/migrations/0.114-to-0.115.md).
|
package/README.md
CHANGED
|
@@ -150,8 +150,7 @@ document was built. Parse warnings and the `conform::*` warnings both ride
|
|
|
150
150
|
other way (`fromStored`, a stored row), returning the `conform::*` `Diagnostic[]`
|
|
151
151
|
(`[]` when everything rested). It is idempotent and a byte no-op on an
|
|
152
152
|
already-canonical document, YAML comments included, so calling it on every load
|
|
153
|
-
is safe. A
|
|
154
|
-
value the strict write refuses stays as authored under a warning. Both throw
|
|
153
|
+
is safe. A value the strict write refuses stays as authored under a warning. Both throw
|
|
155
154
|
when the document declares a `$quill` this quill does not answer to, before any
|
|
156
155
|
mutation.
|
|
157
156
|
|
|
@@ -175,9 +174,9 @@ const doc = Document.storageVersionOf(content)
|
|
|
175
174
|
: Document.fromMarkdown(content);
|
|
176
175
|
```
|
|
177
176
|
|
|
178
|
-
The `schema` value (`quillmark/document@0.
|
|
177
|
+
The `schema` value (`quillmark/document@0.116.0`) is the **model version**,
|
|
179
178
|
not the running crate version. It is a hand-set constant, bumped only when
|
|
180
|
-
the `Document` model itself changes, so every `0.
|
|
179
|
+
the `Document` model itself changes, so every `0.116.x` patch release reads
|
|
181
180
|
and writes that same value.
|
|
182
181
|
|
|
183
182
|
- **Upgrading is safe.** A newer build reads documents an older build's
|
|
@@ -220,8 +219,9 @@ To render a form editor, read field definitions from `quill.schema` (walk
|
|
|
220
219
|
values from the `Document` payload: there is no separate form-view projection.
|
|
221
220
|
`quill.validate(doc)` scores it without invoking the backend.
|
|
222
221
|
|
|
223
|
-
`quill.seedDocument()` returns a starter document
|
|
224
|
-
|
|
222
|
+
`quill.seedDocument()` returns a starter document: one card per kind, each body
|
|
223
|
+
empty, every field absent (no `example:` is committed);
|
|
224
|
+
`quill.seedMain()` and `quill.seedCard(kind)` seed one card. All
|
|
225
225
|
return the read `Card` shape of `doc.main` / `doc.cards`, which `doc.insertCard`
|
|
226
226
|
accepts directly:
|
|
227
227
|
|
|
@@ -248,7 +248,7 @@ card is an object literal: one `{ type: "field", key, value }` per field in
|
|
|
248
248
|
`{ card?, field? }`, absent `card` = main, absent `field` = body, and a bare
|
|
249
249
|
string is shorthand for `{ field }`. So `doc.storeField("qty", 3)` targets the
|
|
250
250
|
main card's `qty`, `doc.storeField({ card: 2, field: "qty" }, 3)` a composable
|
|
251
|
-
card's. Reads are total over the field axis (`getStored` → `undefined
|
|
251
|
+
card's. Reads are total over the field axis (`getStored` → `undefined` for
|
|
252
252
|
an absent field; only an out-of-range card throws); field writes throw on a body
|
|
253
253
|
address. `getStored` is the verbatim transport read, distinct from the interpreted
|
|
254
254
|
`quill.reader(doc).get`; `bodyMarkdown` is the body markdown read (a `CardAddr`; a field's
|
|
@@ -289,7 +289,7 @@ on `Document` itself (**store** = verbatim, **set** = typed):
|
|
|
289
289
|
`_reviseField` ABI the writer delegates to, hidden from the `.d.ts`.)
|
|
290
290
|
|
|
291
291
|
- **`store*`: the deliberate quill-free primitive.** `doc.storeField(addr, value)`
|
|
292
|
-
/ `doc.storeFields(cardAddr, {...})`
|
|
292
|
+
/ `doc.storeFields(cardAddr, {...})` validate only the field
|
|
293
293
|
name/depth/kind and store the value verbatim, no quill required. Reach for it
|
|
294
294
|
on purpose when you *want* the opaque store: quill-agnostic storage/migration
|
|
295
295
|
infra that has no bundle and must write regardless of a drifted schema;
|
|
@@ -354,43 +354,47 @@ tells you which pages to repaint (`dirty ∩ visible`). `update` is transactiona
|
|
|
354
354
|
on throw, every read keeps serving the last-good compile. Don't open a session
|
|
355
355
|
per export, and don't re-open per edit: `update` instead.
|
|
356
356
|
|
|
357
|
+
Both take the render date as an optional last argument (`YYYY-MM-DD`, default
|
|
358
|
+
the local date): what a `today` date field and a plate's `datetime.today()`
|
|
359
|
+
render as. A session keeps the date it opened with, so a preview left open past
|
|
360
|
+
midnight renders yesterday's until it is reopened.
|
|
361
|
+
|
|
357
362
|
A document that compiles to zero pages still produces a valid session
|
|
358
|
-
(`pageCount === 0`); `paint(ctx, 0)` and `pageSize(0)` then throw. Branch on
|
|
363
|
+
(`pageCount === 0`); `paint(ctx, 0, scale)` and `pageSize(0)` then throw. Branch on
|
|
359
364
|
`pageCount === 0` to render a "no pages to preview" UI rather than relying on
|
|
360
365
|
the throw.
|
|
361
366
|
|
|
362
367
|
Their `warnings` differ in reach. `engine.render` returns one list for the
|
|
363
|
-
whole pipeline: `doc.warnings` (parse
|
|
364
|
-
`
|
|
368
|
+
whole pipeline: `doc.warnings` (parse and `conform::*`), then every
|
|
369
|
+
`quill.validate(doc)` warning, then the compile's own. A session outlives
|
|
365
370
|
the document it opened from, so `session.render` and `session.warnings` carry
|
|
366
|
-
the compile
|
|
371
|
+
the compile's alone — read `doc.warnings` and `quill.validate(doc)` beside them.
|
|
367
372
|
|
|
368
373
|
### Canvas Preview
|
|
369
374
|
|
|
370
|
-
`session.paint(ctx, page,
|
|
375
|
+
`session.paint(ctx, page, scale)` rasterizes a page directly into a
|
|
371
376
|
`CanvasRenderingContext2D` (main thread) or
|
|
372
377
|
`OffscreenCanvasRenderingContext2D` (Worker), skipping PNG/SVG byte
|
|
373
378
|
round-trips.
|
|
374
379
|
|
|
375
|
-
The painter owns `canvas.width` /
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
380
|
+
`scale` is backing-store pixels per point. The painter owns `canvas.width` /
|
|
381
|
+
`canvas.height`, reducing `scale` where it must so neither exceeds 16384 px;
|
|
382
|
+
consumers own `canvas.style.*`. A canvas styled to fill its page box
|
|
383
|
+
needs nothing back from the paint:
|
|
379
384
|
|
|
380
385
|
```ts
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
canvas.style.width = `${result.layoutWidth}px`;
|
|
387
|
-
canvas.style.height = `${result.layoutHeight}px`;
|
|
386
|
+
canvas.style.width = "100%"; // the box sets the display size
|
|
387
|
+
const cssPxPerPt = canvas.clientWidth / session.pageSize(0).widthPt;
|
|
388
|
+
if (cssPxPerPt > 0) { // 0 while the canvas has no layout box
|
|
389
|
+
session.paint(canvas.getContext("2d")!, 0, cssPxPerPt * window.devicePixelRatio);
|
|
390
|
+
}
|
|
388
391
|
```
|
|
389
392
|
|
|
390
|
-
- `
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
393
|
+
- Fold `devicePixelRatio`, in-app zoom, and `visualViewport.scale` into `scale`.
|
|
394
|
+
- A canvas with no layout box (`display: none` on it or an ancestor, detached,
|
|
395
|
+
in a zero-width container) has `clientWidth` 0, and a 0 scale throws
|
|
396
|
+
`backend::invalid_raster_scale`: skip the paint, and paint when a
|
|
397
|
+
`ResizeObserver` reports a width.
|
|
394
398
|
- `paint` writes the whole backing store with `putImageData`, which ignores the
|
|
395
399
|
2D context transform, `globalAlpha`, and clip. Give each visible page its own
|
|
396
400
|
`<canvas>`: no compositing, sub-rect, or transform reaches through `paint`.
|
|
@@ -402,33 +406,28 @@ canvas.style.height = `${result.layoutHeight}px`;
|
|
|
402
406
|
cache them between committed `update`s only. After one, the count is
|
|
403
407
|
`ChangeSet.pageCount`, and every page in `ChangeSet.dirtyPages` needs its
|
|
404
408
|
`pageSize` re-read.
|
|
405
|
-
- In a Worker, pass an `OffscreenCanvasRenderingContext2D
|
|
406
|
-
|
|
407
|
-
is the host's
|
|
409
|
+
- In a Worker, pass an `OffscreenCanvasRenderingContext2D`. An
|
|
410
|
+
`OffscreenCanvas` has no layout box to measure, so the main thread posts the
|
|
411
|
+
scale. Loading the WASM module inside the Worker is the host's
|
|
412
|
+
responsibility.
|
|
408
413
|
- `paint` / `pageSize` throw on a page the compile does not have, a zero-page
|
|
409
414
|
compile included, naming the index and the `pageCount` that excludes it. That
|
|
410
415
|
throw is the whole contract: open the session and handle it.
|
|
411
416
|
|
|
412
417
|
### Schema model
|
|
413
418
|
|
|
414
|
-
A field
|
|
419
|
+
A field declares no `required` key: nothing is required.
|
|
415
420
|
|
|
416
|
-
|
|
417
|
-
that value under a type-only `# <type>` annotation and the render path
|
|
418
|
-
when the document omits the field. Without one,
|
|
419
|
-
|
|
421
|
+
**`default:`** — what an unanswered field renders. With one, `quill.blueprint`
|
|
422
|
+
renders that value under a type-only `# <type>` annotation and the render path
|
|
423
|
+
uses it when the document omits the field. Without one, the blueprint leaves the
|
|
424
|
+
cell empty and an absent field blank-fills.
|
|
420
425
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
`quill.validate(doc)` emits the non-fatal `validation::must_fill` warning while
|
|
425
|
-
the document leaves it unauthored — from either of two triggers, named by the
|
|
426
|
-
diagnostic's `trigger` arg: `marker` for a marker the document still carries,
|
|
427
|
-
`unauthored` for a cell the schema obliges and the document never filled.
|
|
428
|
-
Authoring the field's blank discharges the obligation; clearing the key does
|
|
429
|
-
not.
|
|
426
|
+
**`example:`** — the schema's illustration of the field's shape.
|
|
427
|
+
`quill.blueprint` shows it on a `# e.g.` line above the field; it never takes a
|
|
428
|
+
cell and never renders.
|
|
430
429
|
|
|
431
|
-
|
|
430
|
+
An unanswered field draws no diagnostic. Partial documents are accepted, and
|
|
432
431
|
`engine.render(quill, doc)` throws only for malformed input.
|
|
433
432
|
|
|
434
433
|
### Errors
|
|
@@ -516,7 +515,7 @@ in a browser that hasn't shipped it:
|
|
|
516
515
|
const session = await engine.open(quill, doc);
|
|
517
516
|
try {
|
|
518
517
|
for (let p = 0; p < session.pageCount; p++) {
|
|
519
|
-
session.paint(
|
|
518
|
+
session.paint(canvases[p].getContext("2d")!, p, scale);
|
|
520
519
|
}
|
|
521
520
|
} finally {
|
|
522
521
|
session.free();
|