quario 0.7.0 → 0.8.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 +70 -77
- package/README.md +19 -2
- package/lib/image.js +92 -0
- package/lib/index.d.ts +22 -2
- package/lib/license.js +5 -5
- package/lib/plan.js +41 -15
- package/lib/stream.js +0 -23
- package/lib/style.js +2 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,61 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
# quario
|
|
2
|
+
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **BREAKING: an image whose header does not state its size now fails on every
|
|
8
|
+
target.** A PNG or JPEG truncated before its dimensions used to render on the
|
|
9
|
+
HTML and CSV targets — the browser coped, and CSV has no picture to place —
|
|
10
|
+
while failing on the PDF and XLSX targets, which have to know how big a
|
|
11
|
+
picture is before they can put it anywhere. One file, two verdicts, depending
|
|
12
|
+
on where the report was sent.
|
|
13
|
+
|
|
14
|
+
It is now a single render error, raised where every other verdict on image
|
|
15
|
+
bytes is raised and naming the item's `source` the same way — for example
|
|
16
|
+
`header[0].source [=$.input.logo]: could not read the image's size from its bytes`.
|
|
17
|
+
A report that renders as a PDF today is unaffected: those bytes already
|
|
18
|
+
failed there. What changes is a report that renders as HTML today on bytes no other target
|
|
19
|
+
would accept: it now fails, and the file it names is one no target could ever
|
|
20
|
+
have drawn correctly.
|
|
21
|
+
|
|
22
|
+
Nothing further has changed about what the engine promises: a file whose size
|
|
23
|
+
reads but whose pixel data is corrupt is still nobody's guarantee, and each
|
|
24
|
+
target does whatever its own machinery does with it.
|
|
25
|
+
|
|
26
|
+
- **The image event carries the picture's size.** `width` and `height`, in
|
|
27
|
+
pixels, read from the same header the format is sniffed from. A target — the
|
|
28
|
+
built-in ones, or your own — places a picture from two fields instead of
|
|
29
|
+
parsing a PNG's IHDR and a JPEG's frame header for itself.
|
|
30
|
+
- **A page-number token says which page value it is.** A value token whose
|
|
31
|
+
interpolation is exactly `{{ page.number }}` or `{{ page.total }}` now carries
|
|
32
|
+
`field` on the event stream, holding that same string. The `value` is
|
|
33
|
+
unchanged — the number the page band was called with — so nothing renders
|
|
34
|
+
differently; `field` tells a target whose own document format numbers pages
|
|
35
|
+
that it may write its own live field there instead of the number this render
|
|
36
|
+
saw. Anything computed from them (`{{ page.number + 1 }}`, `{{ pad(page.total)
|
|
37
|
+
}}`) is an ordinary value and carries no `field`, so write the bare token
|
|
38
|
+
wherever a live number matters.
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- **A PNG wider or taller than 65535 pixels is no longer misplaced.** Its
|
|
43
|
+
dimensions were read from the low half of each of IHDR's four-byte fields, so
|
|
44
|
+
a 70000-pixel panorama measured 4464 pixels and was laid out at that size,
|
|
45
|
+
confidently and silently.
|
|
46
|
+
- **Every published README says where the documentation is.** Each package now
|
|
47
|
+
carries a Documentation section pointing at the reference, at the report schema
|
|
48
|
+
that normatively specifies what a report may declare, and at the package's own
|
|
49
|
+
API. The paragraphs that used to end on an unstated contract — the event
|
|
50
|
+
stream's field semantics, the style vocabulary, page columns, the Content
|
|
51
|
+
Security Policy a fragment with images needs, the formula mangling, and each
|
|
52
|
+
target's own contract — link the page that states it. Every link is an absolute
|
|
53
|
+
URL, so it resolves from the npm package page as readily as from an installed
|
|
54
|
+
copy.
|
|
55
|
+
|
|
56
|
+
## 0.7.0
|
|
57
|
+
|
|
58
|
+
### Minor Changes
|
|
13
59
|
|
|
14
60
|
- **Styled runs.** Bold a word, colour a phrase, or format one value inside a
|
|
15
61
|
sentence, and have it survive into the PDF and the spreadsheet — not only the
|
|
@@ -47,8 +93,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
47
93
|
seen in the definition alone, without data, so a quiet `warnings` list is not
|
|
48
94
|
a promise that every declaration will be read.
|
|
49
95
|
|
|
50
|
-
### Changed
|
|
51
|
-
|
|
52
96
|
- **A `format` declaration now needs one value to speak about, and a cell can
|
|
53
97
|
give it one.** Previously a `format` on a cell reached every interpolation in
|
|
54
98
|
it and skipped the literal text between them, which meant
|
|
@@ -78,8 +122,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
78
122
|
twice. `plan()` reports the cells this affects wherever it can see them
|
|
79
123
|
without data.
|
|
80
124
|
|
|
81
|
-
### Fixed
|
|
82
|
-
|
|
83
125
|
- **`round`, `floor` and `ceil` reuse their formatters instead of rebuilding
|
|
84
126
|
one per call.** The three reach the decimal you wrote through `Intl`, and
|
|
85
127
|
each call built a fresh formatter to do it — so a detail column of
|
|
@@ -88,7 +130,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
88
130
|
Every answer is the value it was before: the rounding mode and the digit
|
|
89
131
|
count are both part of what a reused formatter is found by, so no call can
|
|
90
132
|
be handed one built for another.
|
|
91
|
-
|
|
92
133
|
- **`round`, `floor` and `ceil` name themselves when they refuse an `n`.** All
|
|
93
134
|
three threw `n must be a number from 0 to 100`, so a cell calling more than
|
|
94
135
|
one was told how it failed and never which call; the message now opens with
|
|
@@ -112,7 +153,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
112
153
|
the same and are not diagnostics, so a host following that example rethrew
|
|
113
154
|
report faults as its own. Nothing about the guard changes; it never behaved
|
|
114
155
|
the way the page described.
|
|
115
|
-
|
|
116
156
|
- **A misused inline reducer says what it is, instead of how it folds.**
|
|
117
157
|
`{{ min(1, 2) }}` — the two-argument scalar `min` quario does not have —
|
|
118
158
|
failed at the cell with `(rows || []).map is not a function`, the fold's own
|
|
@@ -130,9 +170,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
130
170
|
mistake: `sum(@.lines)` on a row carrying no lines is `0`, exactly as an
|
|
131
171
|
empty array gives.
|
|
132
172
|
|
|
133
|
-
##
|
|
173
|
+
## 0.6.0
|
|
134
174
|
|
|
135
|
-
###
|
|
175
|
+
### Minor Changes
|
|
136
176
|
|
|
137
177
|
- **`imageError(path, said, cause)`**, the mint a render target raises an image
|
|
138
178
|
failure through. The engine vouches for an image's magic numbers and no
|
|
@@ -141,7 +181,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
141
181
|
that asked for the bytes and keeps the failing class and message behind it,
|
|
142
182
|
so one report reads the same however it is rendered. It is not a diagnostic:
|
|
143
183
|
`isDiagnostic` does not answer for one.
|
|
144
|
-
|
|
145
184
|
- **Formatted cells render dramatically faster.** The engine built a fresh
|
|
146
185
|
`Intl` formatter for every presented cell and now keeps them, keyed on
|
|
147
186
|
everything a formatter is built from — the kind, the locale, the digit count,
|
|
@@ -197,14 +236,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
197
236
|
|
|
198
237
|
- **`sort` is stable**: rows whose keys compare equal keep the order they
|
|
199
238
|
arrived in. The engine has always sorted this way; the promise is new.
|
|
200
|
-
|
|
201
239
|
- `currencyOf(style, options)` joins the package's exports beside `format()`.
|
|
202
240
|
It answers which currency code a cell wears — its own when it declares one,
|
|
203
241
|
else the instance default — for a target that needs the code itself rather
|
|
204
242
|
than presented text. A cell that declared a code the engine could not accept
|
|
205
243
|
carries `style.currency` as `null`, so `style.currency ?? yourDefault` is the
|
|
206
244
|
wrong spelling and this helper is the right one.
|
|
207
|
-
|
|
208
245
|
- **A cell may name its own currency.** `currency` is a new style declaration
|
|
209
246
|
beside `format` — a three-letter ISO 4217 code saying what a money cell is
|
|
210
247
|
denominated in.
|
|
@@ -257,14 +294,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
257
294
|
- `split-start` events carry `path`, the split definition's schema path, as
|
|
258
295
|
`item` and `image` events already do. A consumer can now name the definition
|
|
259
296
|
behind a split without tracking position.
|
|
260
|
-
|
|
261
297
|
- `fractionDigits(kind, options)` is exported beside `format()`: how many
|
|
262
298
|
fraction digits a kind presents, or nothing for a kind that carries no
|
|
263
299
|
count. It is the table the official targets read, and it is public so a
|
|
264
300
|
consumer writing its own target presents the same digits they do.
|
|
265
|
-
|
|
266
|
-
### Changed
|
|
267
|
-
|
|
268
301
|
- **A `date` cell with no `form` now presents `medium`, not the runtime's own
|
|
269
302
|
default.** A `date` that names no form used to render whatever the platform's
|
|
270
303
|
default date format was — under `en-US`, `8/14/2026` — which matched none of
|
|
@@ -285,18 +318,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
285
318
|
to write no number format. Read `style.format.kind` where you read
|
|
286
319
|
`style.format`, and take the digit count off the declaration rather than
|
|
287
320
|
computing one.
|
|
288
|
-
|
|
289
321
|
- **`fractionDigits` takes the whole style.** Its first argument was the kind
|
|
290
322
|
and is now the style, matching `format()` and `currencyOf()`.
|
|
291
323
|
`fractionDigits("number")` becomes
|
|
292
324
|
`fractionDigits({ format: "number" })`. Targets no longer need it at all —
|
|
293
325
|
the count rides on the declaration the stream carries.
|
|
294
|
-
|
|
295
326
|
- **`typed()`'s second argument is the resolved declaration.** Pass
|
|
296
327
|
`style.format` as it arrives on the stream; it reads the kind from the
|
|
297
328
|
object. The shorthand string is still accepted, and omitting the argument is
|
|
298
329
|
unchanged.
|
|
299
|
-
|
|
300
330
|
- **A rejected box value is now dropped from the resolved style rather than
|
|
301
331
|
carried on it.** A `border*` or `padding*` declaration written as an `=`
|
|
302
332
|
expression used to cross the render stream with whatever the expression
|
|
@@ -435,23 +465,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
435
465
|
- A `currency` code that is not a readable currency now presents nothing in
|
|
436
466
|
every target, rather than the worksheet inventing a number format from it
|
|
437
467
|
while the other targets fell back to the unformatted value.
|
|
438
|
-
|
|
439
|
-
### Fixed
|
|
440
|
-
|
|
441
468
|
- **A split slot is typed with the declarations the traversal accepts.**
|
|
442
469
|
`SplitSlot` reused the band items' style types, so TypeScript refused
|
|
443
470
|
`valign` on an image slot — legal there, and only there — and admitted
|
|
444
471
|
`spaceBefore`/`spaceAfter` on a slot, which the engine rejects. Two new
|
|
445
472
|
exported types, `SlotStyleDeclarations` and `SlotImageStyleDeclarations`,
|
|
446
473
|
now say what a slot may declare.
|
|
447
|
-
|
|
448
474
|
- **A page band closure is typed as what it returns.** `PageBandRenderers`
|
|
449
475
|
declared `header`/`footer` as returning item and image events only, while a
|
|
450
476
|
split in a page band has always come back as its bracket — `split-start`,
|
|
451
477
|
one event per slot, `split-end`. The closures now return `PageBandEvent[]`,
|
|
452
478
|
a new exported union naming all four, so a TypeScript host reading a page
|
|
453
479
|
band no longer types `role` onto a `split-end` that carries none.
|
|
454
|
-
|
|
455
480
|
- **A computed style value the schema does not accept now leaves the layer
|
|
456
481
|
below in place**, which is what the schema has always said it does —
|
|
457
482
|
"contributes nothing, same as omit". The name is dropped from the resolved
|
|
@@ -486,7 +511,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
486
511
|
the cell falls back to display text, as the documented rule says any
|
|
487
512
|
unrecognised kind does. Before, such a cell could render `[object Undefined]`.
|
|
488
513
|
A literal was never affected: it is checked against the four kinds.
|
|
489
|
-
|
|
490
514
|
- **A pinned report header now refuses a lead on every band that can sit under
|
|
491
515
|
its box, and only where the document settles which one that is.** When
|
|
492
516
|
`header` declares a `height`, an authored `spaceBefore` on the first item of
|
|
@@ -516,9 +540,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
516
540
|
than reporting against the report header, which is a band this rule never
|
|
517
541
|
covers.
|
|
518
542
|
|
|
519
|
-
##
|
|
543
|
+
## 0.5.0
|
|
520
544
|
|
|
521
|
-
###
|
|
545
|
+
### Minor Changes
|
|
522
546
|
|
|
523
547
|
- **`STYLE_NAMES`**, every name in the style vocabulary as a read-only list,
|
|
524
548
|
in the order the specification's table lists them. A tool that offers the
|
|
@@ -540,9 +564,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
540
564
|
on a band image, whose boxes have no such slack. A row's or a split's layers
|
|
541
565
|
under its cells' or slots' own. Undeclared is not a declaration: each target
|
|
542
566
|
keeps its own default.
|
|
543
|
-
|
|
544
|
-
### Changed
|
|
545
|
-
|
|
546
567
|
- **A table row's `style` now resolves onto that row's cells**, the box
|
|
547
568
|
included, instead of meaning something different on every output. Before,
|
|
548
569
|
padding and border on `detail.header`, `detail.row` or a total row's `style`
|
|
@@ -565,9 +586,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
565
586
|
carries only what layers by ordinary means, and a row whose whole block was
|
|
566
587
|
box carries no `style` at all.
|
|
567
588
|
|
|
568
|
-
##
|
|
589
|
+
## 0.4.0
|
|
569
590
|
|
|
570
|
-
###
|
|
591
|
+
### Minor Changes
|
|
571
592
|
|
|
572
593
|
- **`format: "date"` now reads a date string, not only a `Date`.** A JSON
|
|
573
594
|
document has no date type, so the kind could not be reached from parsed
|
|
@@ -591,9 +612,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
591
612
|
- **`typed(tokens, kind?)` takes the cell's `format` kind.** Passing `"date"`
|
|
592
613
|
opts into the same revival, for consumers that write typed cells. The
|
|
593
614
|
one-argument call is unchanged.
|
|
594
|
-
|
|
595
|
-
### Fixed
|
|
596
|
-
|
|
597
615
|
- **The TypeScript declarations accept the box.** The per-side `padding*` and
|
|
598
616
|
`border*` names, and the table's `detail.header`, were validated by the
|
|
599
617
|
engine from 0.3.0 but were missing from the shipped declarations, so a
|
|
@@ -608,9 +626,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
608
626
|
about rendering changes: a report that compiled through a cast produces the
|
|
609
627
|
same output without one.
|
|
610
628
|
|
|
611
|
-
##
|
|
629
|
+
## 0.3.0
|
|
612
630
|
|
|
613
|
-
###
|
|
631
|
+
### Minor Changes
|
|
614
632
|
|
|
615
633
|
- **`format` is a closed style name.** `number` / `currency` / `percent` /
|
|
616
634
|
`date` on text items, column cells, headers, and totals. Not images, not
|
|
@@ -618,19 +636,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
618
636
|
live on `quario({ locale, currency, timeZone })`. The public `format()`
|
|
619
637
|
helper is how targets present a kind; a kind on the wrong type contributes
|
|
620
638
|
nothing.
|
|
621
|
-
|
|
622
639
|
- **The report header may pin a `height` from the page top.** Dual-shaped
|
|
623
640
|
like `detail`: an item array, or `{ height, items }`. `page.margin` is a
|
|
624
641
|
document field (one number, all four sides), required with `height` and
|
|
625
642
|
legal without. Authored `spaceBefore` on the first occupying item of the
|
|
626
643
|
next band is refused.
|
|
627
|
-
|
|
628
644
|
- **`spaceBefore` / `spaceAfter` return as item flow spacing.** Blank space
|
|
629
645
|
before or after a band item, in points, including band images and splits as
|
|
630
646
|
band items. Adjacent gaps add. Table cells, `row.style`, headers, totals,
|
|
631
647
|
and split slots refuse the names. `spaceBefore` drops at a fresh body page
|
|
632
648
|
or strip top; page-band items keep it. Leading and inset stay cut.
|
|
633
|
-
|
|
634
649
|
- **Per-side padding and border on the closed style vocabulary.**
|
|
635
650
|
`paddingTop` / `Right` / `Bottom` / `Left` (points, ≥ 0) and, per side,
|
|
636
651
|
`border*Width`, `border*Style` (`solid` | `dashed` | `dotted`),
|
|
@@ -640,18 +655,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
640
655
|
than a solid black stroke. The names are legal wherever `background` is,
|
|
641
656
|
including images, plus `row.style`. The report default still takes only
|
|
642
657
|
`family` and `size`. Column `%` widths are border-box.
|
|
643
|
-
|
|
644
658
|
- **`detail.header` is the header-row box.** `{ style }` only, a different
|
|
645
659
|
path from `columns[i].header`. It crosses the seam as `table-start.style`.
|
|
646
|
-
|
|
647
|
-
### Changed
|
|
648
|
-
|
|
649
660
|
- **A table total is N rows.** Before, `total` was one row: a flat cell
|
|
650
661
|
array or `{ style, cells }`. After, it is absent or a non-empty array of
|
|
651
662
|
`{ cells, style?, visible? }`. A one-row total is `[{ cells: [...] }]`.
|
|
652
663
|
`total: []` is a definition error. Paths are `detail.total[r].cells[i]`.
|
|
653
664
|
The stream yields one `total-row` per emitted row.
|
|
654
|
-
|
|
655
665
|
- **A visible text item occupies a line at its own `size`.** Empty display
|
|
656
666
|
used to take the report default's leading in PDF and collapse in HTML;
|
|
657
667
|
`"a\n\nb"` broke in PDF and collapsed to a space in HTML. A visible item
|
|
@@ -660,9 +670,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
660
670
|
table cells stay contentless for height. This is not a spacing primitive;
|
|
661
671
|
`visible: false` is still how an item leaves the layout.
|
|
662
672
|
|
|
663
|
-
##
|
|
673
|
+
## 0.2.0
|
|
664
674
|
|
|
665
|
-
###
|
|
675
|
+
### Minor Changes
|
|
666
676
|
|
|
667
677
|
- **A report default: one `style` block for the whole document.** A top-level
|
|
668
678
|
`style` beside `header`/`detail`/`footer` states the typeface a report is set
|
|
@@ -672,21 +682,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
672
682
|
a text declaration on an image item is. Values are literals or `=`
|
|
673
683
|
expressions like any other style block's, resolved once per render in report
|
|
674
684
|
scope.
|
|
675
|
-
|
|
676
685
|
- **`report-start` carries the resolved report default as `style`.** A
|
|
677
686
|
report-level fact, never merged into an item's own `style`: an event's
|
|
678
687
|
`style` stays what the author wrote on that node, and a consumer composes the
|
|
679
688
|
default itself, once — under its own band-role defaults and under every
|
|
680
689
|
event's own style. Absent when the report declares none, so a consumer
|
|
681
690
|
written before this field renders in its own baseline exactly as it did.
|
|
682
|
-
|
|
683
691
|
- **An `uppercase` style declaration.** A boolean beside `bold` and `italic`,
|
|
684
692
|
literal or an `=` expression, for the capitalised column labels business
|
|
685
693
|
forms are usually set with. It is capitals, not small caps — real small caps
|
|
686
694
|
need a font feature the PDF target's built-in faces cannot supply, so the
|
|
687
695
|
declaration promises only what every target can draw. Image items keep
|
|
688
696
|
refusing it, as they refuse every text declaration.
|
|
689
|
-
|
|
690
697
|
- **A `split` item places values across the line instead of down the band.**
|
|
691
698
|
The invoice header's "seller left, customer right", which a band could not
|
|
692
699
|
say before. `{ "type": "split", "slots": [...] }` takes two or more slots,
|
|
@@ -697,13 +704,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
697
704
|
a slot that renders nothing keeps its width, so a line's geometry never
|
|
698
705
|
moves with the data. Splits may appear in every item array except table and
|
|
699
706
|
total cells.
|
|
700
|
-
|
|
701
707
|
- **`split-start` / `split-end` bracket a split's slots on the event stream.**
|
|
702
708
|
`split-start` carries the slot geometry, then one ordinary `item` or `image`
|
|
703
709
|
event per slot in order, then `split-end`. Existing consumers need no
|
|
704
710
|
change: the walk driver's missing-handler rule means a target that ignores
|
|
705
711
|
the bracket still receives the slot items and renders them stacked.
|
|
706
|
-
|
|
707
712
|
- **`quario().plan(schema, funcs?)` hands the whole traversal over at once.**
|
|
708
713
|
Returns `{ report, problems, anchors }` from one descent: the compiled
|
|
709
714
|
report (`null` while the document has problems), every problem as
|
|
@@ -727,12 +732,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
727
732
|
display rule behind the token join, re-exported beside `text()` so a stream
|
|
728
733
|
consumer that stringifies token values itself renders exactly what the
|
|
729
734
|
official targets render — Dates included.
|
|
730
|
-
|
|
731
735
|
- **`maxDepth: Infinity` opts a query budget out.** The data query's traversal
|
|
732
736
|
budgets accept an explicit `Infinity` per key for "this budget, unbounded".
|
|
733
737
|
The 500-deep default is unchanged — it is now padvinder's own, applied for
|
|
734
738
|
every consumer rather than added by quario at the seam.
|
|
735
|
-
|
|
736
739
|
- **Located data-query errors carry padvinder's code and span.** A `data`
|
|
737
740
|
query that does not parse now surfaces with padvinder's `code` and
|
|
738
741
|
`start`/`end` offsets into the query you wrote — filter faults included —
|
|
@@ -742,23 +745,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
742
745
|
`PADVINDER_UNKNOWN_FUNCTION`, or `PADVINDER_SYNTAX` for a path character or
|
|
743
746
|
filter body that is open-endedly not a query. Traversal budgets exceeded at
|
|
744
747
|
render time keep `limit`/`actual` and carry no span.
|
|
745
|
-
|
|
746
|
-
### Changed
|
|
747
|
-
|
|
748
748
|
- **Cells render straight over the engine scope chain.** A text cell no longer
|
|
749
749
|
allocates a wrapper scope and an anchor pair per cell per row — quario's
|
|
750
750
|
chain already binds `$` at the render base and `@` on the detail row, and
|
|
751
751
|
sjabloon now renders over it as-is. A 4-column stream over a million rows
|
|
752
752
|
went from 1.7s to 0.5s. No report changes what it renders. Requires
|
|
753
753
|
sjabloon 0.11.
|
|
754
|
-
|
|
755
754
|
- **A compiled report's `functions` carry signatures.** Each entry is now
|
|
756
755
|
`{ name, arity, doc? }` instead of a bare name — `arity` from the function's
|
|
757
756
|
declared parameter count (or its own numeric `arity` where rest parameters
|
|
758
757
|
mislead `length`), `doc` from an own `doc` string when it carries one — in
|
|
759
758
|
the same call-first-seen order. `names` is unchanged. Requires xprsn 0.11
|
|
760
759
|
and sjabloon 0.11.
|
|
761
|
-
|
|
762
760
|
- **A bare `Date` renders as ISO 8601 UTC, the same on every machine.**
|
|
763
761
|
Display text for a `Date` value was `String(date)`, which bakes the
|
|
764
762
|
rendering host's timezone and locale into the output — so one report
|
|
@@ -767,7 +765,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
767
765
|
rule; an invalid `Date` keeps its deterministic `Invalid Date` text.
|
|
768
766
|
Reports that want a formatted date keep using a registered function,
|
|
769
767
|
exactly as before.
|
|
770
|
-
|
|
771
768
|
- **A bad literal pattern in the data query is a definition error.** A typo'd
|
|
772
769
|
I-Regexp written as a string literal in `match()`/`search()` used to
|
|
773
770
|
produce a plausible empty report with no signal; `report()` and
|
|
@@ -779,7 +776,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
779
776
|
fault in an expression: the engine that decided the fault names it. A
|
|
780
777
|
pattern that arrives from render data keeps RFC 9535 semantics and still
|
|
781
778
|
matches nothing at render time. Requires padvinder 0.8.
|
|
782
|
-
|
|
783
779
|
- **Each engine relocates its own diagnostic.** A located error is now a copy
|
|
784
780
|
made by the engine that raised it (xprsn, sjabloon, or padvinder), so it
|
|
785
781
|
carries every field that engine puts on a diagnostic — nothing is lost in
|
|
@@ -787,9 +783,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
787
783
|
passes that engine's own `isDiagnostic`. Host errors are wrapped as plain
|
|
788
784
|
errors with no diagnostic metadata, exactly as before. Requires xprsn 0.10,
|
|
789
785
|
sjabloon 0.9, and padvinder 0.5. No report changes what it renders.
|
|
790
|
-
|
|
791
|
-
### Fixed
|
|
792
|
-
|
|
793
786
|
- **A host error cannot pose as the report's diagnostic.** `report()` rethrows
|
|
794
787
|
the first definition problem's located engine error; it chose that error by
|
|
795
788
|
probing for a `code` property, so a host error class that stamps `code` on
|
|
@@ -798,9 +791,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
798
791
|
`isDiagnostic`; everything else falls back to a plain `SyntaxError` naming
|
|
799
792
|
the first problem, as before.
|
|
800
793
|
|
|
801
|
-
##
|
|
794
|
+
## 0.1.0
|
|
802
795
|
|
|
803
|
-
###
|
|
796
|
+
### Minor Changes
|
|
804
797
|
|
|
805
798
|
- **A compile-once report engine.** You hand it a JSON report definition and
|
|
806
799
|
data; it plans once and streams render events that every target consumes.
|
package/README.md
CHANGED
|
@@ -98,19 +98,28 @@ row, one `total-row` per emitted total row, and `table-end`.
|
|
|
98
98
|
| `table-end` | - |
|
|
99
99
|
| `report-end` | - |
|
|
100
100
|
|
|
101
|
+
The full field semantics are in the [event stream reference](https://getquario.com/docs/reference/events/).
|
|
102
|
+
|
|
101
103
|
**Cells carry tokens.** A cell is `{ tokens, style? }`. Each token is either `{ literal }`
|
|
102
104
|
(static template text, verbatim) or `{ value }` (one interpolation's _pre-format_ value, the
|
|
103
105
|
expression result before any stringification). This is the typed seam: a cell whose template is `{{ @.amount }}` holds one value token with the
|
|
104
106
|
number itself, so a spreadsheet consumer writes a real numeric cell. `Total: {{ @.amount }}` mixes
|
|
105
107
|
a literal and a value, so the cell is text only.
|
|
106
108
|
|
|
109
|
+
**A page-number token names itself.** A value token whose interpolation is exactly
|
|
110
|
+
`{{ page.number }}` or `{{ page.total }}` also carries `field`, holding that same string. The
|
|
111
|
+
value is the number this page was rendered with; `field` says which page value the token stands
|
|
112
|
+
for, so a target whose own document format numbers pages can write its own live field there
|
|
113
|
+
instead. Anything computed from them carries no `field`.
|
|
114
|
+
|
|
107
115
|
**Escaping is the consumer's job.** A target that embeds values in markup must escape them at its
|
|
108
116
|
own edge.
|
|
109
117
|
|
|
110
118
|
**`report-start.marking`** carries the evaluation wording when the render is unlicensed, or while
|
|
111
119
|
verification is still settling. Licensed streams omit it. Targets place the marking; they do not
|
|
112
120
|
author its wording. **`columns`** on `report-start` / `group-start` is the declared
|
|
113
|
-
page column
|
|
121
|
+
[page column](https://getquario.com/docs/diving-deeper/bands/#flowing-in-columns) count when
|
|
122
|
+
present. `@quario/pdf` and `@quario/html` lay page columns out;
|
|
114
123
|
xlsx never will.
|
|
115
124
|
|
|
116
125
|
### `text(tokens)`
|
|
@@ -256,7 +265,8 @@ Markdown target lives in the repository at `example/markdown.js` in about 70 lin
|
|
|
256
265
|
targets are written against the same public API.
|
|
257
266
|
|
|
258
267
|
Two rules a target owes its users: escape or neutralize every `value` token at your own edge, and
|
|
259
|
-
map the style vocabulary to your own
|
|
268
|
+
map the [style vocabulary](https://getquario.com/docs/reference/style-declarations/) to your own
|
|
269
|
+
formatting model rather than expecting CSS.
|
|
260
270
|
|
|
261
271
|
The stream is additive. The walk driver skips any event you register no handler for, so a target
|
|
262
272
|
that ignores a newer event (for example, `example/markdown.js` has none for `image`) keeps rendering
|
|
@@ -272,6 +282,13 @@ package, so it runs under a script policy that omits `unsafe-eval`. The test sui
|
|
|
272
282
|
under Node's `--disallow-code-generation-from-strings` flag, a source scan, and a Playwright
|
|
273
283
|
harness that loads the published files under a strict CSP.
|
|
274
284
|
|
|
285
|
+
## Documentation
|
|
286
|
+
|
|
287
|
+
[The quario documentation](https://getquario.com/docs/) is the reference.
|
|
288
|
+
The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
|
|
289
|
+
specification of what a report may declare, and
|
|
290
|
+
the [engine reference](https://getquario.com/docs/reference/quario/) is this package's own API.
|
|
291
|
+
|
|
275
292
|
## License
|
|
276
293
|
|
|
277
294
|
Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
|
package/lib/image.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the engine reads out of an image item's bytes: which format they are,
|
|
3
|
+
* and how big the picture is. Read, never decoded -- a magic number, a PNG's
|
|
4
|
+
* IHDR and a JPEG's frame header, and nothing here looks at a pixel.
|
|
5
|
+
*
|
|
6
|
+
* Both answers ride on the image event, so no target reads these headers
|
|
7
|
+
* again. That is the whole of `docs/adr/0067`: two targets used to keep a
|
|
8
|
+
* parser each, in different units and with the same bug, and the engine was
|
|
9
|
+
* already in these bytes to name the format.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// The two raster formats an image item may carry, as the bytes announce
|
|
13
|
+
// themselves: PNG's 8-byte signature and JPEG's start-of-image marker. Read,
|
|
14
|
+
// never decoded -- the engine answers which format the bytes are, not what
|
|
15
|
+
// they depict.
|
|
16
|
+
let PNG = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
17
|
+
let JPEG = [0xff, 0xd8, 0xff];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The image seam: name the format a `source` expression's bytes hold, or null
|
|
21
|
+
* when they are not image bytes at all -- including when they are not bytes.
|
|
22
|
+
* The traversal sniffs once per image event and the answer rides on the event,
|
|
23
|
+
* so no consumer repeats it.
|
|
24
|
+
*
|
|
25
|
+
* @param {any} bytes The value a `source` expression yielded.
|
|
26
|
+
* @returns {"png" | "jpeg" | null} The format, or null when unreadable.
|
|
27
|
+
*/
|
|
28
|
+
export function sniff(bytes) {
|
|
29
|
+
if (!(bytes instanceof Uint8Array)) return null;
|
|
30
|
+
if (PNG.every((byte, i) => bytes[i] === byte)) return "png";
|
|
31
|
+
if (JPEG.every((byte, i) => bytes[i] === byte)) return "jpeg";
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** @type {(bytes: Uint8Array, at: number) => number} */
|
|
36
|
+
let word = (bytes, at) => (bytes[at] << 8) | bytes[at + 1];
|
|
37
|
+
|
|
38
|
+
// A PNG states each dimension in four bytes, so two words make one number.
|
|
39
|
+
/** @type {(bytes: Uint8Array, at: number) => number} */
|
|
40
|
+
let long = (bytes, at) => word(bytes, at) * 0x10000 + word(bytes, at + 2);
|
|
41
|
+
|
|
42
|
+
// The three codes in C0..CF that are not frame headers: DHT, the JPG
|
|
43
|
+
// extension, and DAC. Named as a set so the frame test is one range and one
|
|
44
|
+
// lookup rather than a chain of exclusions.
|
|
45
|
+
let NOT_FRAME = new Set([0xc4, 0xc8, 0xcc]);
|
|
46
|
+
|
|
47
|
+
// The dimensions live in the frame header, which is the first SOFn marker.
|
|
48
|
+
// SOF2 is a frame like SOF0, so a progressive file is read like a baseline one.
|
|
49
|
+
/** @type {(code: number) => boolean} */
|
|
50
|
+
let isFrame = (code) => code >= 0xc0 && code <= 0xcf && !NOT_FRAME.has(code);
|
|
51
|
+
|
|
52
|
+
// A PNG carries the two numbers in its IHDR at a fixed offset.
|
|
53
|
+
/** @type {(bytes: Uint8Array) => { width: number, height: number } } */
|
|
54
|
+
let pngSize = (bytes) => ({ width: long(bytes, 16), height: long(bytes, 20) });
|
|
55
|
+
|
|
56
|
+
/** @type {(bytes: Uint8Array) => { width: number, height: number } } */
|
|
57
|
+
let jpegSize = (bytes) => {
|
|
58
|
+
for (let at = 2; at + 9 < bytes.length; at += 2 + word(bytes, at + 2)) {
|
|
59
|
+
if (bytes[at] !== 0xff) break;
|
|
60
|
+
if (isFrame(bytes[at + 1])) return { width: word(bytes, at + 7), height: word(bytes, at + 5) };
|
|
61
|
+
}
|
|
62
|
+
return { width: 0, height: 0 };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// One reader per format `sniff` names.
|
|
66
|
+
/** @type {Record<string, (bytes: Uint8Array) => { width: number, height: number }>} */
|
|
67
|
+
let READERS = { png: pngSize, jpeg: jpegSize };
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The image's own size in pixels, or null when the header does not carry one
|
|
71
|
+
* -- bytes whose magic numbers `sniff` vouched for, truncated before the
|
|
72
|
+
* dimensions. Read, never decoded: a PNG's IHDR and a JPEG's frame header
|
|
73
|
+
* carry the two numbers, and nothing here looks at a pixel.
|
|
74
|
+
*
|
|
75
|
+
* The size rides on the image event beside the format, so every target places
|
|
76
|
+
* a picture from a field rather than parsing the same two headers again, and a
|
|
77
|
+
* file too short to state its size fails once, in the engine, for every target
|
|
78
|
+
* alike (`docs/adr/0067`).
|
|
79
|
+
*
|
|
80
|
+
* @param {Uint8Array} bytes The image file.
|
|
81
|
+
* @param {"png" | "jpeg"} format The format `sniff` named.
|
|
82
|
+
* @returns {{ width: number, height: number } | null} The size, if readable.
|
|
83
|
+
*/
|
|
84
|
+
export function dimensions(bytes, format) {
|
|
85
|
+
// Keyed rather than a ternary so the format list lives in one place: a
|
|
86
|
+
// format `sniff` learns later and this table does not would throw here
|
|
87
|
+
// rather than be read silently as the one on the ternary's else branch.
|
|
88
|
+
let size = READERS[format](bytes);
|
|
89
|
+
// A zero is how either reader says "the header stopped before this" -- an
|
|
90
|
+
// absent byte reads as zero, and no image is zero wide.
|
|
91
|
+
return size.width > 0 && size.height > 0 ? size : null;
|
|
92
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -506,8 +506,20 @@ export interface TokenStyle {
|
|
|
506
506
|
/** One static text run of a cell's template, verbatim (sjabloon's literal token). */
|
|
507
507
|
export type LiteralToken = SjabloonLiteralToken & TokenStyle;
|
|
508
508
|
|
|
509
|
-
/**
|
|
510
|
-
export type
|
|
509
|
+
/** Which page value a token *is*, on the two whose interpolation says so. */
|
|
510
|
+
export type PageField = "page.number" | "page.total";
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* One interpolation's pre-stringify value (sjabloon's value token).
|
|
514
|
+
*
|
|
515
|
+
* `field` is present where the interpolation is exactly `{{ page.number }}` or
|
|
516
|
+
* `{{ page.total }}`. The value is unchanged -- the number this render saw --
|
|
517
|
+
* and `field` says which page value the token stands for, so a target whose
|
|
518
|
+
* own document format numbers pages writes a live field there instead
|
|
519
|
+
* (SCHEMA.md, "Page bands"). Anything computed from a page value carries no
|
|
520
|
+
* `field`, and a target that paginates itself, or not at all, ignores the key.
|
|
521
|
+
*/
|
|
522
|
+
export type ValueToken = SjabloonValueToken & TokenStyle & { field?: PageField };
|
|
511
523
|
|
|
512
524
|
export type Token = LiteralToken | ValueToken;
|
|
513
525
|
|
|
@@ -629,6 +641,14 @@ export interface ImageEvent {
|
|
|
629
641
|
bytes: Uint8Array;
|
|
630
642
|
/** Sniffed from the bytes' magic numbers, so no consumer repeats it. */
|
|
631
643
|
format: ImageFormat;
|
|
644
|
+
/**
|
|
645
|
+
* The image's own width in pixels, read from the same header. Bytes too
|
|
646
|
+
* short to state a size never reach a target: that is a render error on
|
|
647
|
+
* every target alike.
|
|
648
|
+
*/
|
|
649
|
+
width: number;
|
|
650
|
+
/** The image's own height in pixels, read from the same header. */
|
|
651
|
+
height: number;
|
|
632
652
|
fit: ImageFit;
|
|
633
653
|
/** The rendered `alt` template, present when the item declares one. */
|
|
634
654
|
alt?: Token[];
|
package/lib/license.js
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
// Verification is entirely offline, and asynchronous only because WebCrypto
|
|
6
6
|
// is.
|
|
7
7
|
|
|
8
|
-
// Release date of this version, written into the
|
|
9
|
-
// release-
|
|
10
|
-
// spelling that `scripts/license/keygen.mjs` matches on,
|
|
11
|
-
// reassigns them. A key is valid for every version released inside its window
|
|
8
|
+
// Release date of this version, written into the Version PR's commit by
|
|
9
|
+
// `scripts/stamp-release-date.mjs` (the `release-date` mark). Both constants
|
|
10
|
+
// keep the `let` spelling that `scripts/license/keygen.mjs` matches on,
|
|
11
|
+
// though nothing reassigns them. A key is valid for every version released inside its window
|
|
12
12
|
// (LICENSE section 7), so validity compares ISO date strings and never reads
|
|
13
13
|
// a clock — accepted output stays accepted.
|
|
14
|
-
let RELEASE = "2026-09-
|
|
14
|
+
let RELEASE = "2026-09-14"; // release-date
|
|
15
15
|
// The verifying half of the signing pair: the 65-byte uncompressed P-256
|
|
16
16
|
// point, base64url. The private half never enters the repo;
|
|
17
17
|
// scripts/license/sign.mjs mints keys against it.
|
package/lib/plan.js
CHANGED
|
@@ -51,7 +51,8 @@ import { ANCHORS, BLOCKED, NAME, RESERVED, record } from "./names.js";
|
|
|
51
51
|
import { MATH } from "./math.js";
|
|
52
52
|
import { REDUCERS, reducerFor } from "./reducers.js";
|
|
53
53
|
import { aggregateValue, withRow } from "./scope.js";
|
|
54
|
-
import {
|
|
54
|
+
import { dimensions, sniff } from "./image.js";
|
|
55
|
+
import { oneValue, opt, sameStyle } from "./stream.js";
|
|
55
56
|
import {
|
|
56
57
|
checkBorderSides,
|
|
57
58
|
checkCellStyle,
|
|
@@ -482,10 +483,28 @@ let runProp = (expression, path, source) => (scope) => {
|
|
|
482
483
|
locate(path, source, error, expression.isDiagnostic(error) && relocateXprsn, 1);
|
|
483
484
|
}
|
|
484
485
|
};
|
|
486
|
+
// The [live field](../../../CONTEXT.md#live-field): which page value a token
|
|
487
|
+
// *is*, for a target whose own format numbers pages. sjabloon offers each
|
|
488
|
+
// interpolation's expression source once, while compiling, which is the only
|
|
489
|
+
// place the words the author wrote still exist -- a rendered token carries the
|
|
490
|
+
// number and nothing about where it came from. Reading the source here instead
|
|
491
|
+
// is what hard constraint 2 forbids, and a sentinel bound to `page.number` was
|
|
492
|
+
// ruled out because xprsn's `==` is strict: it would break `=page.number == 1`.
|
|
493
|
+
//
|
|
494
|
+
// One table for the whole compile: the answer depends on the expression alone,
|
|
495
|
+
// never on where the cell sits. A Map rather than a lookup object, because the
|
|
496
|
+
// key is author source and nothing reached through `Object.prototype` is an
|
|
497
|
+
// answer to this question; each name is written once and is its own answer,
|
|
498
|
+
// frozen because every cell that says it shares the one object.
|
|
499
|
+
const PAGE_FIELDS = /** @type {Map<string, { field: import("./index.js").PageField }>} */ (
|
|
500
|
+
new Map(["page.number", "page.total"].map((field) => [field, Object.freeze({ field })]))
|
|
501
|
+
);
|
|
502
|
+
/** @type {(expr: string) => { field: import("./index.js").PageField } | undefined} */
|
|
503
|
+
let pageField = (expr) => PAGE_FIELDS.get(expr);
|
|
485
504
|
/** @type {(src: string, path: string, source: any, FNS: any, BND: Set<string>) => any} */
|
|
486
505
|
let compileCell = (src, path, source, FNS, BND) => {
|
|
487
506
|
try {
|
|
488
|
-
return template(src, FNS, { bound: BND });
|
|
507
|
+
return template(src, FNS, { bound: BND, tag: pageField });
|
|
489
508
|
} catch (error) {
|
|
490
509
|
locate(path, source, error, isTemplateDiagnostic(error) && relocateTemplate);
|
|
491
510
|
}
|
|
@@ -1326,28 +1345,35 @@ let nodes = (
|
|
|
1326
1345
|
let altOf = (def, path) => (def.alt != null ? textValue(def.alt, path + ".alt") : null);
|
|
1327
1346
|
/** @type {(alt: any, scope: Scope) => any} */
|
|
1328
1347
|
let imageAlt = (alt, scope) => alt && alt(scope);
|
|
1348
|
+
// The two places `locate` originates an error instead of re-throwing a
|
|
1349
|
+
// caught one: each fault is quario's own verdict on the bytes, so it carries
|
|
1350
|
+
// the location every other render error does and, having no engine
|
|
1351
|
+
// diagnostic behind it, stays out of `isDiagnostic`.
|
|
1352
|
+
/** @type {(fields: any, message: string) => never} */
|
|
1353
|
+
let badImage = (fields, message) =>
|
|
1354
|
+
locate(fields.path + ".source", fields.sourceSpec, TypeError(message));
|
|
1329
1355
|
/** @type {(fields: any, scope: Scope, bytes: any) => any} */
|
|
1330
1356
|
let imageEvent = (fields, scope, bytes) => {
|
|
1331
|
-
// The bytes stay bytes.
|
|
1332
|
-
//
|
|
1333
|
-
//
|
|
1357
|
+
// The bytes stay bytes. Reading them touches the magic numbers and the
|
|
1358
|
+
// header the size lives in -- never a pixel, and nothing here turns them
|
|
1359
|
+
// into a string -- and both answers ride on the event so no target reads
|
|
1360
|
+
// the same header a second time.
|
|
1334
1361
|
let format = sniff(bytes);
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
//
|
|
1338
|
-
//
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
fields.sourceSpec,
|
|
1343
|
-
TypeError("expected a Uint8Array of PNG or JPEG bytes"),
|
|
1344
|
-
);
|
|
1362
|
+
if (!format) badImage(fields, "expected a Uint8Array of PNG or JPEG bytes");
|
|
1363
|
+
let size = dimensions(bytes, format);
|
|
1364
|
+
// The one thing the engine reads past the magic numbers, and so the one
|
|
1365
|
+
// header failure every target shares: a file that cannot state its size
|
|
1366
|
+
// can be placed by none of them (`docs/adr/0067`). Past the size the
|
|
1367
|
+
// engine still vouches for nothing.
|
|
1368
|
+
if (!size) badImage(fields, "could not read the image's size from its bytes");
|
|
1345
1369
|
return opt(
|
|
1346
1370
|
{
|
|
1347
1371
|
type: "image",
|
|
1348
1372
|
role: fields.role,
|
|
1349
1373
|
bytes,
|
|
1350
1374
|
format,
|
|
1375
|
+
width: size.width,
|
|
1376
|
+
height: size.height,
|
|
1351
1377
|
fit: fields.fit,
|
|
1352
1378
|
path: fields.path,
|
|
1353
1379
|
},
|
package/lib/stream.js
CHANGED
|
@@ -207,29 +207,6 @@ export function isReportBand(role) {
|
|
|
207
207
|
return REPORT_BANDS.has(role);
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
// The two raster formats an image item may carry, as the bytes announce
|
|
211
|
-
// themselves: PNG's 8-byte signature and JPEG's start-of-image marker. Read,
|
|
212
|
-
// never decoded -- the engine answers which format the bytes are, not what
|
|
213
|
-
// they depict.
|
|
214
|
-
let PNG = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
215
|
-
let JPEG = [0xff, 0xd8, 0xff];
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* The image seam: name the format a `source` expression's bytes hold, or null
|
|
219
|
-
* when they are not image bytes at all -- including when they are not bytes.
|
|
220
|
-
* The traversal sniffs once per image event and the answer rides on the event,
|
|
221
|
-
* so no consumer repeats it.
|
|
222
|
-
*
|
|
223
|
-
* @param {any} bytes The value a `source` expression yielded.
|
|
224
|
-
* @returns {"png" | "jpeg" | null} The format, or null when unreadable.
|
|
225
|
-
*/
|
|
226
|
-
export function sniff(bytes) {
|
|
227
|
-
if (!(bytes instanceof Uint8Array)) return null;
|
|
228
|
-
if (PNG.every((byte, i) => bytes[i] === byte)) return "png";
|
|
229
|
-
if (JPEG.every((byte, i) => bytes[i] === byte)) return "jpeg";
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
210
|
// The cooperative hand-back's plumbing. A timer is the slowest way a runtime will
|
|
234
211
|
// hand the loop back and, until this landed, the only one quario used: measured
|
|
235
212
|
// on an M-series Mac it costs ~1.20ms per hand-back against ~0.015ms for
|
package/lib/style.js
CHANGED
|
@@ -375,7 +375,8 @@ export let checkRunStyle = narrowed(RUN_STYLES, "a styled run");
|
|
|
375
375
|
export let checkSlotStyle = withoutFlow(checkStyle, "a split slot");
|
|
376
376
|
// `valign` is legal exactly where a box has height it did not ask for
|
|
377
377
|
// (CONTEXT.md "Box": slack). A slot's box is the split's height, so a slot --
|
|
378
|
-
// text or image -- reads it; a band image's box is its
|
|
378
|
+
// text or image -- reads it; a band image's box is exactly its picture's
|
|
379
|
+
// height, so it does not.
|
|
379
380
|
// That is the one name an image accepts in a slot and nowhere else, the same
|
|
380
381
|
// shape of reason `width` reaches an item only there (docs/adr/0029).
|
|
381
382
|
let SLOT_IMAGE_STYLES = [...IMAGE_STYLES, "valign"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quario",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A tiny, runtime-neutral report engine — in the makings, not yet released",
|
|
5
5
|
"homepage": "https://getquario.com",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"padvinder": "^0.9.0",
|
|
37
|
-
"sjabloon": "^0.
|
|
37
|
+
"sjabloon": "^0.13.0",
|
|
38
38
|
"xprsn": "^0.11.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|