@quario/xlsx 0.6.0 → 0.7.1
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 +41 -45
- package/README.md +22 -8
- package/lib/cell.js +55 -6
- package/lib/index.js +17 -17
- package/lib/style.js +2 -1
- package/package.json +3 -3
- package/lib/image.js +0 -45
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @quario/xlsx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 0.7.1
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
### Patch Changes
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
- **Every published README says where the documentation is.** Each package now
|
|
8
|
+
carries a Documentation section pointing at the reference, at the report schema
|
|
9
|
+
that normatively specifies what a report may declare, and at the package's own
|
|
10
|
+
API. The paragraphs that used to end on an unstated contract — the event
|
|
11
|
+
stream's field semantics, the style vocabulary, page columns, the Content
|
|
12
|
+
Security Policy a fragment with images needs, the formula mangling, and each
|
|
13
|
+
target's own contract — link the page that states it. Every link is an absolute
|
|
14
|
+
URL, so it resolves from the npm package page as readily as from an installed
|
|
15
|
+
copy.
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
- quario@0.8.0
|
|
9
18
|
|
|
10
|
-
##
|
|
19
|
+
## 0.7.0
|
|
11
20
|
|
|
12
|
-
###
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- **Styled runs become rich text.** A cell of more than one styled run is written
|
|
24
|
+
as rich text, each run's font merged over the cell's — so bold, italic,
|
|
25
|
+
underline, strikethrough, size, face and colour survive into the grid. A
|
|
26
|
+
run-level `background` is not read: a spreadsheet's rich-text runs carry fonts
|
|
27
|
+
only, and filling the cell would colour text nobody asked to colour. A cell of
|
|
28
|
+
exactly one run stays a typed cell, so a single styled amount is still a
|
|
29
|
+
number.
|
|
30
|
+
- **A run inside a sentence presents its value.** A run holding one
|
|
31
|
+
interpolation under a `format` shows the same decimals here as on the page,
|
|
32
|
+
where a mixed cell used to show the bare value.
|
|
33
|
+
|
|
34
|
+
## 0.6.0
|
|
35
|
+
|
|
36
|
+
### Minor Changes
|
|
13
37
|
|
|
14
38
|
- **A date cell's number format follows the `form` its report declares.** The
|
|
15
39
|
four forms map to `dd/mm/yy`, `dd mmm yyyy`, `dd mmmm yyyy` and
|
|
@@ -17,26 +41,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
17
41
|
weekday name is spelled in stays the reader's, because that is what a
|
|
18
42
|
spreadsheet application supplies and pinning it would stop the file reading
|
|
19
43
|
naturally for whoever opens it.
|
|
20
|
-
|
|
21
44
|
- **A declared digit count builds the pattern.** A `number` cell asking for
|
|
22
45
|
three digits writes `#,##0.000`, and a `currency` cell asking for none writes
|
|
23
46
|
`"EUR"#,##0` — the same count the page presents, taken off the declaration
|
|
24
47
|
the engine resolved rather than computed here.
|
|
25
|
-
|
|
26
48
|
- **A cell's own `currency` code picks the number format**, ahead of the
|
|
27
49
|
instance's default: `"JPY"#,##0` where the row says `JPY`, since the code
|
|
28
50
|
decides the digits too. The cell stays a number either way. A code the engine
|
|
29
51
|
could not accept writes no number format at all, rather than labelling the
|
|
30
52
|
cell in the instance's currency.
|
|
31
|
-
|
|
32
|
-
### Changed
|
|
33
|
-
|
|
34
53
|
- **A `date` cell with no declared form now writes `dd mmm yyyy`, not
|
|
35
54
|
`yyyy-mm-dd`.** A bare `date` means the `medium` form throughout, so the grid
|
|
36
55
|
agrees with the page. A report that wants the ISO shape has no form for it;
|
|
37
56
|
the nearest is `{ "kind": "date", "form": "short" }`, which writes
|
|
38
57
|
`dd/mm/yy`. The cell is still a real typed date either way.
|
|
39
|
-
|
|
40
58
|
- **A cell's number format now comes from the engine's fraction-digit count
|
|
41
59
|
rather than from four patterns this target held of its own.** The visible
|
|
42
60
|
change is `currency`: the pattern followed the currency's own minor units,
|
|
@@ -45,7 +63,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
45
63
|
(`#,##0.00`), `percent` (`0.00%`) and `date` (`yyyy-mm-dd`) are unchanged in
|
|
46
64
|
a worksheet; what changed for those is that HTML and PDF now show the same
|
|
47
65
|
decimals the grid always did, instead of their own.
|
|
48
|
-
|
|
49
66
|
- A `currency` code that is not a readable currency no longer produces a number
|
|
50
67
|
format at all. The cell keeps its value; before, the code was pasted straight
|
|
51
68
|
into a pattern while every other target fell back to the unformatted value.
|
|
@@ -54,39 +71,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
54
71
|
position still come from the application that opens the file, which is what
|
|
55
72
|
lets one workbook read naturally wherever it is opened.
|
|
56
73
|
|
|
57
|
-
### Fixed
|
|
58
|
-
|
|
59
74
|
- A `format` kind reaching this target from an `=` expression is now looked up
|
|
60
75
|
as an own key. Before, a kind resolving to the name of a built-in object
|
|
61
76
|
member threw out of the render (`valueOf`, `hasOwnProperty`) or wrote
|
|
62
77
|
`[object Undefined]` into the cell's number format (`toString`). A literal
|
|
63
78
|
was never affected: it is checked against the four kinds.
|
|
64
|
-
|
|
65
|
-
### Fixed
|
|
66
|
-
|
|
67
79
|
- **An image whose size cannot be read now names the item that asked for it.**
|
|
68
80
|
The failure said only that the size could not be read from the bytes, so a
|
|
69
81
|
report with two pictures gave no way to tell which one was bad. The message
|
|
70
82
|
now begins with the item's `source` path, as every other render error does
|
|
71
83
|
and as it does from every other target.
|
|
72
84
|
|
|
73
|
-
##
|
|
85
|
+
## 0.5.0
|
|
74
86
|
|
|
75
|
-
###
|
|
87
|
+
### Minor Changes
|
|
76
88
|
|
|
77
89
|
- **Chrome merges, data does not.** A cell spanning several columns in the
|
|
78
90
|
header row or a total row is written into the first column it covers, the
|
|
79
91
|
rest of the range is written empty, and the range is merged. Nothing in the
|
|
80
92
|
data region merges, so sort, filter and column selection keep working over
|
|
81
93
|
it.
|
|
82
|
-
|
|
83
94
|
- **`valign`** writes the cell's vertical alignment for table cells and split
|
|
84
95
|
slots. Undeclared writes nothing, so the spreadsheet application keeps its
|
|
85
96
|
own default.
|
|
86
97
|
|
|
87
|
-
##
|
|
98
|
+
## 0.4.0
|
|
88
99
|
|
|
89
|
-
###
|
|
100
|
+
### Minor Changes
|
|
90
101
|
|
|
91
102
|
- **A date string under `format: "date"` now reaches the grid as a date.**
|
|
92
103
|
The cell carries a real date value under the `yyyy-mm-dd` number format
|
|
@@ -94,83 +105,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
94
105
|
cell declaring the kind is affected; a string in a form the engine does not
|
|
95
106
|
read stays text. See the `quario` changelog for the forms.
|
|
96
107
|
|
|
97
|
-
##
|
|
108
|
+
## 0.3.0
|
|
98
109
|
|
|
99
|
-
###
|
|
110
|
+
### Minor Changes
|
|
100
111
|
|
|
101
112
|
- **`format` maps to a number format; the cell stays typed.** `number` is
|
|
102
113
|
`#,##0.00`, `percent` `0.00%`, `date` `yyyy-mm-dd`, `currency` the
|
|
103
114
|
instance currency code as `"USD"#,##0.00`. A kind on the wrong type
|
|
104
115
|
contributes nothing.
|
|
105
|
-
|
|
106
116
|
- **Cell borders map; padding and flow spacing do not.** `solid` is exceljs
|
|
107
117
|
`thin`; `dashed` and `dotted` keep their names. A header-row, row, or
|
|
108
118
|
total-row box fans onto that row's cells; a cell that named any of a
|
|
109
119
|
side's three keys owns that side whole. Padding is unread: a worksheet
|
|
110
120
|
cell has no inset. Flow spacing is unread: a grid has no flow.
|
|
111
|
-
|
|
112
121
|
- **`page.margin` and report-header `height` are unread.** A grid has no
|
|
113
122
|
page top to pin from.
|
|
114
|
-
|
|
115
|
-
### Changed
|
|
116
|
-
|
|
117
123
|
- **A table total emits N rows.** Each `total-row` is one worksheet row, as
|
|
118
124
|
each data row is.
|
|
119
|
-
|
|
120
125
|
- **A cell whose display contains a newline wraps.** `wrapText` is this
|
|
121
126
|
target's mapping of a literal newline as a line break, so `"one\ntwo"` is
|
|
122
127
|
two lines in the grid. Single-line cells are unchanged.
|
|
123
128
|
|
|
124
|
-
##
|
|
129
|
+
## 0.2.0
|
|
125
130
|
|
|
126
|
-
###
|
|
131
|
+
### Minor Changes
|
|
127
132
|
|
|
128
133
|
- **The report default reaches every cell.** A report's top-level `style`
|
|
129
134
|
replaces this target's baseline, so a document declaring a face and size gets
|
|
130
135
|
them in cells that declare nothing of their own. It is the layer under the
|
|
131
136
|
band-role defaults — a report declaring `size: 12` still writes its report
|
|
132
137
|
header at 14 — and under each cell's own style.
|
|
133
|
-
|
|
134
138
|
- **`uppercase` is accepted and deliberately not read.** A spreadsheet font
|
|
135
139
|
has no text-transform, and writing capitals into the cell instead would turn
|
|
136
140
|
presentation into data — the cell would stop round-tripping and would sort
|
|
137
141
|
differently. The cell keeps the text you wrote; every other declaration on
|
|
138
142
|
it still applies. Same posture as the column widths this target withdrew.
|
|
139
|
-
|
|
140
143
|
- **A split is one row, its slots the cells across it.** The grid's own
|
|
141
144
|
reading of values placed beside each other. Slot `width` shares go unread,
|
|
142
145
|
on exactly the ground the table's column widths do — a worksheet's columns
|
|
143
146
|
are global to the sheet. The split's own style is what its slots sit under,
|
|
144
147
|
layering the way a table row's does, and a slot that renders nothing writes
|
|
145
148
|
an empty cell so the cells either side keep their columns.
|
|
146
|
-
|
|
147
|
-
### Changed
|
|
148
|
-
|
|
149
149
|
- **Every cell is written at 10 points unless something declares otherwise.**
|
|
150
150
|
The size was previously left to the writer's own default of 11. Now that a
|
|
151
151
|
report can declare its own size, this target and the PDF carrying different
|
|
152
152
|
numbers for one declaration is exactly what the targets' agreement rule
|
|
153
153
|
forbids. A worksheet's row height follows its font size, so existing unstyled
|
|
154
154
|
sheets come out slightly tighter.
|
|
155
|
-
|
|
156
155
|
- **Display-text `Date`s join as ISO 8601 UTC.** A `Date` inside a mixed
|
|
157
156
|
cell (literal text plus interpolation) joined through `String(date)`, which
|
|
158
157
|
bakes the host's timezone and locale into the worksheet. It now joins
|
|
159
158
|
through the engine's shared display rule as `toISOString()` text. Typed
|
|
160
159
|
cells are untouched: a cell that is one bare `Date` interpolation still
|
|
161
160
|
writes a native date.
|
|
162
|
-
|
|
163
|
-
### Fixed
|
|
164
|
-
|
|
165
161
|
- **Cells carry their font face explicitly.** Text declaring no `family` used
|
|
166
162
|
to inherit whatever the workbook writer defaulted to, which agreed with the
|
|
167
163
|
other targets only by coincidence. It now writes the same face that
|
|
168
164
|
`family: "sans"` resolves to, so the baseline is a rule rather than a
|
|
169
165
|
property of the library underneath.
|
|
170
166
|
|
|
171
|
-
##
|
|
167
|
+
## 0.1.0
|
|
172
168
|
|
|
173
|
-
###
|
|
169
|
+
### Minor Changes
|
|
174
170
|
|
|
175
171
|
- **A workbook from the same compiled report.** Bands become rows; a bare
|
|
176
172
|
interpolation that is a number, boolean, or Date stays that type. The first
|
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ as that order alone. Group boundaries add no rows of their own, and `break: "pag
|
|
|
79
79
|
`reset: "page"` are ignored, as are `page.margin` and a report header's `height`.
|
|
80
80
|
|
|
81
81
|
Each item takes one row with its cell in the first column, unmerged; a split takes one row with its
|
|
82
|
-
slots as the cells across it (slot widths
|
|
82
|
+
slots as the cells across it (slot widths withdrawn, a slot rendering nothing an empty cell), and a
|
|
83
83
|
spanning header or total cell is a merged range. Two band roles carry an
|
|
84
84
|
omakase default that the author's own style always overrides: report-header items render bold at
|
|
85
85
|
size 14, group-header items render bold.
|
|
@@ -109,9 +109,11 @@ on the wrong type, or a code the engine cannot read, writes no format.
|
|
|
109
109
|
### Images
|
|
110
110
|
|
|
111
111
|
An image item is not a cell: it takes one worksheet row as its anchor and floats over the sheet as a
|
|
112
|
-
drawing at its natural pixel size. Placement here is best-effort. `fit`, `alt`, and `style`
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
drawing at its natural pixel size. Placement here is best-effort. `fit`, `alt`, and `style` are
|
|
113
|
+
unused, and all of it may change without a breaking change (see the
|
|
114
|
+
[support matrix](https://getquario.com/docs/reference/support-matrix/#output)). Bytes too short to
|
|
115
|
+
carry a size never reach this target: the engine reads every image's size and rejects them for
|
|
116
|
+
every target alike.
|
|
115
117
|
|
|
116
118
|
### Styles
|
|
117
119
|
|
|
@@ -136,17 +138,18 @@ The first table's header row anchors the frozen view. Everything from that row u
|
|
|
136
138
|
while the body scrolls, the spreadsheet analogue of the PDF target's repeated headers. A report
|
|
137
139
|
without a table freezes nothing. This is normative.
|
|
138
140
|
|
|
139
|
-
### Widths are
|
|
141
|
+
### Widths are withdrawn
|
|
140
142
|
|
|
141
143
|
Column `width` percentages size nothing here, and every worksheet column keeps the application
|
|
142
144
|
default. A width is a share of the _table_ width, and a worksheet's columns are global to the sheet.
|
|
143
145
|
Column A carries the flattened band item rows and the table's first column alike, so there is no
|
|
144
146
|
table for a share to be a share of.
|
|
145
147
|
|
|
146
|
-
### Page columns are
|
|
148
|
+
### Page columns are withdrawn
|
|
147
149
|
|
|
148
|
-
A page column count
|
|
149
|
-
|
|
150
|
+
A [page column](https://getquario.com/docs/reference/support-matrix/#page-and-pagination) count
|
|
151
|
+
sizes and splits nothing here, and will not change, as `break: "page"` is ignored. Page columns
|
|
152
|
+
are strips of a page, and this target has no page to strip.
|
|
150
153
|
|
|
151
154
|
## Unlicensed marking
|
|
152
155
|
|
|
@@ -161,6 +164,17 @@ The document model is deterministic: the workbook's dates are pinned rather than
|
|
|
161
164
|
`meta` is opt-in. The **bytes** are not. The writer stamps archive entries with the packing time.
|
|
162
165
|
Compare extracted content, not digests.
|
|
163
166
|
|
|
167
|
+
The full contract is [The XLSX target](https://getquario.com/docs/diving-deeper/xlsx-target/),
|
|
168
|
+
with each declaration's fate in the
|
|
169
|
+
[support matrix](https://getquario.com/docs/reference/support-matrix/).
|
|
170
|
+
|
|
171
|
+
## Documentation
|
|
172
|
+
|
|
173
|
+
[The quario documentation](https://getquario.com/docs/) is the reference.
|
|
174
|
+
The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
|
|
175
|
+
specification of what a report may declare, and
|
|
176
|
+
[`@quario/xlsx`](https://getquario.com/docs/reference/xlsx/) is this package's own API.
|
|
177
|
+
|
|
164
178
|
## License
|
|
165
179
|
|
|
166
180
|
Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
|
package/lib/cell.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* From an event cell to a plain cell descriptor `{ value, format }`, built on
|
|
3
3
|
* the engine's `typed` stream rule. `value` is a native number, boolean, or
|
|
4
4
|
* Date when the cell is one bare interpolation (so numbers stay computable in
|
|
5
|
-
* the sheet),
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* the sheet), the worksheet's rich-text shape when the cell holds more than
|
|
6
|
+
* one styled run, and the display-text join otherwise. `format` is the
|
|
7
|
+
* resolved cell-level formatting from the style module. Display text that
|
|
8
|
+
* contains a newline gains `wrapText` so the break is visible in the grid.
|
|
8
9
|
*/
|
|
9
|
-
import { currencyOf, text, typed } from "quario";
|
|
10
|
-
import { format as look, merge } from "./style.js";
|
|
10
|
+
import { currencyOf, display, format as present, styledRuns, text, typed } from "quario";
|
|
11
|
+
import { font, format as look, merge } from "./style.js";
|
|
11
12
|
|
|
12
13
|
// The digit count is the engine's, never this target's: it rides on the
|
|
13
14
|
// resolved declaration the stream carries, the same one HTML and PDF
|
|
@@ -87,14 +88,62 @@ let paintWrap = (out, shown) => {
|
|
|
87
88
|
out.alignment = { ...out.alignment, wrapText: true };
|
|
88
89
|
};
|
|
89
90
|
|
|
91
|
+
// One run's presented text: literals verbatim, every value through the
|
|
92
|
+
// declaration that run resolved. A run holding a single value token presents
|
|
93
|
+
// it, so a formatted amount inside a sentence reads the same here as it does
|
|
94
|
+
// on the page -- SCHEMA.md pins that the fraction digits are the same in every
|
|
95
|
+
// target that presents, and this target presents (ADR 0061). A run the engine
|
|
96
|
+
// withdrew the declaration from has nothing to present with and falls to
|
|
97
|
+
// display text, which is what the whole cell used to do.
|
|
98
|
+
/** @type {(tokens: any[], style: any, intl: any) => string} */
|
|
99
|
+
let shownText = (tokens, style, intl) => {
|
|
100
|
+
let out = "";
|
|
101
|
+
for (let token of tokens)
|
|
102
|
+
out +=
|
|
103
|
+
"literal" in token
|
|
104
|
+
? token.literal
|
|
105
|
+
: (present(token.value, style, intl) ?? display(token.value));
|
|
106
|
+
return out;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// A cell of more than one styled run takes the worksheet's rich-text shape:
|
|
110
|
+
// each run's **font half** merged over the cell font, and nothing else. A
|
|
111
|
+
// run-level `background` is inert -- spreadsheet rich-text runs carry fonts
|
|
112
|
+
// only, and filling the cell would colour text the author did not name -- and
|
|
113
|
+
// so is a run-level number format, since a cell has one and this cell is text.
|
|
114
|
+
// No typed cell is lost: a cell of more than one run holds more than one token
|
|
115
|
+
// and was display text already.
|
|
116
|
+
/** @type {(cell: any, styled: any[], under: any, intl: any) => any} */
|
|
117
|
+
let rich = (cell, styled, under, intl) => {
|
|
118
|
+
let base = merge(under, cell.style);
|
|
119
|
+
let out = look(base);
|
|
120
|
+
let parts = styled.map((one) => {
|
|
121
|
+
let style = one.style ? merge(base, one.style) : base;
|
|
122
|
+
return { font: font(style), text: shownText(one.tokens, style, intl) };
|
|
123
|
+
});
|
|
124
|
+
paintWrap(out, parts.map((part) => part.text).join(""));
|
|
125
|
+
return { value: { richText: parts }, format: out };
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// The style a cell of one styled run wears: that run's resolved answer, which
|
|
129
|
+
// already carries the cell's own declarations, or the cell's where it has no
|
|
130
|
+
// run at all.
|
|
131
|
+
/** @type {(cell: any, styled: any[]) => any} */
|
|
132
|
+
let soleStyle = (cell, styled) => styled[0]?.style ?? cell.style;
|
|
133
|
+
|
|
90
134
|
/**
|
|
91
135
|
* Resolve one event cell against its enclosing style (a row's style, or an
|
|
92
136
|
* item's band-role default — the layer under the cell's own).
|
|
93
137
|
*
|
|
138
|
+
* A cell of exactly one styled run wears that run's answer whole, so a single
|
|
139
|
+
* styled amount is still a number in the grid.
|
|
140
|
+
*
|
|
94
141
|
* @type {(cell: { tokens: any[], style?: any }, under: any, intl?: any) => { value: any, format: any }}
|
|
95
142
|
*/
|
|
96
143
|
let field = (cell, under, intl) => {
|
|
97
|
-
let
|
|
144
|
+
let styled = styledRuns(cell.tokens);
|
|
145
|
+
if (styled.length > 1) return rich(cell, styled, under, intl);
|
|
146
|
+
let resolved = merge(under, soleStyle(cell, styled));
|
|
98
147
|
let shown = shownOf(cell, resolved?.format);
|
|
99
148
|
let out = look(resolved);
|
|
100
149
|
paintFmt(out, resolved, shown, intl);
|
package/lib/index.js
CHANGED
|
@@ -15,10 +15,9 @@
|
|
|
15
15
|
* the packing time. Byte-for-byte output is a goal for the writer that will
|
|
16
16
|
* replace `workbook.js`.
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
18
|
+
import { walk } from "quario";
|
|
19
19
|
import { field } from "./cell.js";
|
|
20
20
|
import { merge as under } from "./style.js";
|
|
21
|
-
import { pixels } from "./image.js";
|
|
22
21
|
import { append, create, embed, freeze, mark, place, save, sheet, span } from "./workbook.js";
|
|
23
22
|
|
|
24
23
|
// The options are described once, in the hand-written public declarations, and
|
|
@@ -27,17 +26,19 @@ import { append, create, embed, freeze, mark, place, save, sheet, span } from ".
|
|
|
27
26
|
|
|
28
27
|
// Band-role omakase defaults, applied under the author's own style so a
|
|
29
28
|
// declared style always wins. Only the headline roles carry one; every other
|
|
30
|
-
// band writes plain cells.
|
|
31
|
-
//
|
|
32
|
-
// together, and a target imports only public engine helpers so there is nowhere
|
|
33
|
-
// to share them from.
|
|
29
|
+
// band writes plain cells. SCHEMA.md states the pair, and a target imports only
|
|
30
|
+
// public engine helpers so there is nowhere to share them from.
|
|
34
31
|
//
|
|
35
32
|
// `@quario/html` deliberately carries none of them: its consumer has a
|
|
36
33
|
// stylesheet and the `q-*` classes are the seam, where a worksheet has neither.
|
|
37
34
|
// The rule is docs/adr/0014-a-target-supplies-defaults-only-where-its-consumer-has-no-seam.md —
|
|
38
35
|
// a target supplies defaults only where its consumer has no seam to supply
|
|
39
|
-
// them.
|
|
40
|
-
//
|
|
36
|
+
// them.
|
|
37
|
+
//
|
|
38
|
+
// `test/omakase-defaults.test.js` is what holds the copies in agreement. This
|
|
39
|
+
// comment names the gate rather than listing the files, because the list is
|
|
40
|
+
// what rotted: it used to name `packages/viewer/lib/style.js` and a `style.js`
|
|
41
|
+
// of the PDF target's own, and neither file exists.
|
|
41
42
|
/** @type {Record<string, any>} */
|
|
42
43
|
let ROLES = {
|
|
43
44
|
"report-header": { bold: true, size: 14 },
|
|
@@ -195,15 +196,14 @@ export function xlsx(options) {
|
|
|
195
196
|
image: (event) => {
|
|
196
197
|
let known = images.get(event.bytes);
|
|
197
198
|
if (!known) {
|
|
198
|
-
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
known = { ...size, id: embed(workbook, event.bytes, event.format) };
|
|
199
|
+
// The event states the size in pixels, which is the unit a floating
|
|
200
|
+
// drawing is placed in, so this target has nothing to work out: the
|
|
201
|
+
// engine read the header once for every target (`docs/adr/0067`).
|
|
202
|
+
known = {
|
|
203
|
+
width: event.width,
|
|
204
|
+
height: event.height,
|
|
205
|
+
id: embed(workbook, event.bytes, event.format),
|
|
206
|
+
};
|
|
207
207
|
images.set(event.bytes, known);
|
|
208
208
|
}
|
|
209
209
|
if (split) {
|
package/lib/style.js
CHANGED
|
@@ -22,6 +22,7 @@ let FAMILY = { sans: "Calibri", serif: "Times New Roman", mono: "Courier New" };
|
|
|
22
22
|
|
|
23
23
|
// A declared colour as an ARGB string, or null when the value is not one.
|
|
24
24
|
/** @type {(value: any) => string | null} */
|
|
25
|
+
// fallow-ignore-next-line code-duplication -- each target reads a declared hex into its own type; neither may import a sibling, and the engine's stream is all they share
|
|
25
26
|
let argb = (value) => {
|
|
26
27
|
let match = typeof value === "string" && HEX.exec(value);
|
|
27
28
|
if (!match) return null;
|
|
@@ -138,4 +139,4 @@ let format = (style) => {
|
|
|
138
139
|
return put(out, "alignment", alignment(style));
|
|
139
140
|
};
|
|
140
141
|
|
|
141
|
-
export { format, merge };
|
|
142
|
+
export { font, format, merge };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quario/xlsx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "The spreadsheet render target for quario — in the makings, not yet released",
|
|
5
5
|
"homepage": "https://getquario.com",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"@arethetypeswrong/cli": "^0.18.3",
|
|
43
43
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
44
44
|
"@types/node": "^22.20.1",
|
|
45
|
-
"quario": "^0.
|
|
45
|
+
"quario": "^0.8.0",
|
|
46
46
|
"size-limit": "^13.0.3",
|
|
47
47
|
"typescript": "^7.0.2"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"quario": "^0.
|
|
50
|
+
"quario": "^0.8.0"
|
|
51
51
|
},
|
|
52
52
|
"size-limit": [
|
|
53
53
|
{
|
package/lib/image.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* An image's own size, read from its header. A floating drawing is placed in
|
|
3
|
-
* pixels, and the event carries the bytes and the format the engine sniffed
|
|
4
|
-
* but no dimensions, so the one thing this target needs to place a picture is
|
|
5
|
-
* worked out here — from the header alone, never by decoding the image.
|
|
6
|
-
*
|
|
7
|
-
* The PDF target reads the same two headers for its own placement, in points.
|
|
8
|
-
* Restated per target rather than shared, like every other coercion at a
|
|
9
|
-
* target's edge: neither package imports the other, and the engine's stream is
|
|
10
|
-
* all they have in common.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/** @type {(bytes: Uint8Array, at: number) => number} */
|
|
14
|
-
let word = (bytes, at) => (bytes[at] << 8) | bytes[at + 1];
|
|
15
|
-
|
|
16
|
-
// The dimensions live in the frame header, the first SOFn marker: every
|
|
17
|
-
// code in C0..CF except the three in that range that are not frames --
|
|
18
|
-
// DHT, the JPG extension, and DAC.
|
|
19
|
-
/** @type {Record<number, number>} */
|
|
20
|
-
let SKIP = { 0xc4: 1, 0xc8: 1, 0xcc: 1 };
|
|
21
|
-
/** @type {(code: number) => any} */
|
|
22
|
-
let sof = (code) => code >= 0xc0 && code <= 0xcf && !SKIP[code];
|
|
23
|
-
|
|
24
|
-
/** @type {(bytes: Uint8Array) => { width: number, height: number }} */
|
|
25
|
-
let jpeg = (bytes) => {
|
|
26
|
-
for (let at = 2; at + 9 < bytes.length; at += 2 + word(bytes, at + 2)) {
|
|
27
|
-
if (bytes[at] !== 0xff) break;
|
|
28
|
-
if (sof(bytes[at + 1])) return { width: word(bytes, at + 7), height: word(bytes, at + 5) };
|
|
29
|
-
}
|
|
30
|
-
return { width: 0, height: 0 };
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* The image's size in pixels, or null when the header does not carry one —
|
|
35
|
-
* bytes the engine vouched the magic numbers of, truncated before the size.
|
|
36
|
-
*
|
|
37
|
-
* @param {Uint8Array} bytes The image file.
|
|
38
|
-
* @param {string} format The engine's sniff: `"png"` or `"jpeg"`.
|
|
39
|
-
* @returns {{ width: number, height: number } | null} The size, if readable.
|
|
40
|
-
*/
|
|
41
|
-
export let pixels = (bytes, format) => {
|
|
42
|
-
// A PNG carries the two numbers in its IHDR at a fixed offset.
|
|
43
|
-
let size = format === "png" ? { width: word(bytes, 18), height: word(bytes, 22) } : jpeg(bytes);
|
|
44
|
-
return size.width > 0 && size.height > 0 ? size : null;
|
|
45
|
-
};
|