@quario/docx 0.1.0 → 0.2.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 +64 -0
- package/README.md +21 -17
- package/lib/body.js +74 -73
- package/lib/content.js +50 -0
- package/lib/furniture.js +28 -74
- package/lib/index.d.ts +5 -1
- package/lib/index.js +18 -59
- package/lib/pack.js +2 -5
- package/lib/page.js +6 -9
- package/lib/picture.js +4 -6
- package/lib/style.js +12 -19
- package/lib/stylepart.js +2 -3
- package/package.json +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# @quario/docx
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **A group's `break` names a position, and `break: "page"` is retired.** Its values are `"before"`, `"between"`, `"after"` and `"around"`. Write `break: "before"` where you wrote `break: "page"`. That is the whole of the migration, and it changes nothing the report produces.
|
|
8
|
+
|
|
9
|
+
The boundary between two consecutive instances always turns a page. `before` adds the leading edge of the run, so the first instance opens a page of its own. `between` adds neither edge, which keeps the first instance on the page the report header opened — the case the old vocabulary could not express, and the reason for the change. `after` adds the trailing edge, and `around` adds both. A trailing edge turns the page for what follows the run, so a report whose last band is that group reads `after` as `between` and `around` as `before`. A nested group takes its two edges from each instance of the group above it, rather than from the document. `"page"` said which unit a break used, where the four say where it falls; one set cannot say both and still read at a glance.
|
|
10
|
+
|
|
11
|
+
**`reset: "page"` turns no page of its own, and now needs a `break` beside it.** It says only that a new `page.number` / `page.total` sequence starts at this instance. A sequence owns whole pages, so `reset` requires `break` to be `"before"` or `"around"`. Any other `break`, and `reset` with no `break` at all, is a definition error the compile reports. Add `break: "before"` to a group that declares `reset` alone today.
|
|
12
|
+
|
|
13
|
+
The HTML target adds `q-break` to an instance whose leading edge turns, as before, and the new `q-break-after` to one whose trailing edge turns. `@quario/html/style.css` gains `.q-break-after { break-after: page }` beside the rule it already shipped for `.q-break`.
|
|
14
|
+
|
|
15
|
+
The Word target also stops losing a page break a table would swallow. A table carries no paragraph properties, so a break owed where one starts had nowhere to sit and reached the next paragraph instead, on the wrong page or on none. It now gets a paragraph of its own, the same carrier a section break already took.
|
|
16
|
+
|
|
17
|
+
The render-event stream states the two edges rather than the four positions. `group-start` carries `break` where a page turns before the instance, and the new `breakAfter` where one turns after it, so a target reads boundaries and never the position that asked for them.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
- quario@0.10.0
|
|
23
|
+
|
|
24
|
+
## 0.1.1
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- **Every target factory now refuses an option it does not understand.** An unknown key, a key with
|
|
29
|
+
a value of the wrong type, or an `options` that is not an object throws a `TypeError` at the
|
|
30
|
+
factory call, naming the option path — `options.meta.title`, `options.page.size`. `@quario/docx`
|
|
31
|
+
already behaved this way. `@quario/pdf`, `@quario/html`, `@quario/xlsx` and `@quario/layout` read
|
|
32
|
+
the keys they knew and ignored the rest, so a typo cost you the option you meant to set with no
|
|
33
|
+
signal at any point. `csv()` takes no options, and it refuses an argument rather than discarding
|
|
34
|
+
one.
|
|
35
|
+
|
|
36
|
+
The check itself is one thing, so the engine now exports it: `hostOptions` closes a key set and
|
|
37
|
+
`hostMeta` validates the `{ title, author, subject }` contract the document targets share. A
|
|
38
|
+
fourth document property is one edit rather than three.
|
|
39
|
+
|
|
40
|
+
**What this changes for you.** One options object spread across several targets stops working if
|
|
41
|
+
any target does not know one of its keys — `const o = { page, fonts, meta }` passed to `pdf(o)`,
|
|
42
|
+
`html(o)` and `xlsx(o)` is three different key sets. An object holding only what its consumers
|
|
43
|
+
share is unaffected, so `{ page, fonts }` into both `pdf()` and `layout()` still works.
|
|
44
|
+
TypeScript does not warn about this: excess-property checking fires on an object literal and not
|
|
45
|
+
on a variable, so a bag held in a `const` compiles clean and throws when you call the factory.
|
|
46
|
+
|
|
47
|
+
Nullish is absence everywhere, at both levels, so `{ meta: config.meta ?? null }` and
|
|
48
|
+
`{ meta: { title: config.title } }` over a config that carries neither are both fine.
|
|
49
|
+
`html({ paths })` now takes `true` or `false` rather than anything truthy, so `paths: "no"` throws
|
|
50
|
+
instead of turning path stamping on. A `fonts` mapping given as an array is refused by
|
|
51
|
+
`@quario/html` and `@quario/layout` rather than read as families named `0`, `1`, `2`.
|
|
52
|
+
|
|
53
|
+
Every host-option failure is now a `TypeError` rather than a plain `Error`. A `catch` that tests
|
|
54
|
+
`instanceof Error` is unaffected. One that compares the constructor is not.
|
|
55
|
+
|
|
56
|
+
`@quario/docx` is relaxed in three places, each of them now accepting what it refused: `meta: null`
|
|
57
|
+
and `page: null` are absence rather than errors, a property written as `meta: { title: undefined }`
|
|
58
|
+
is a property you did not write rather than one of the wrong type, and an inherited enumerable key
|
|
59
|
+
is no longer reported as an option you wrote.
|
|
60
|
+
|
|
61
|
+
`@quario/pdf`, `@quario/xlsx` and `@quario/docx` now copy `meta` at the factory call. Mutating the
|
|
62
|
+
object you passed no longer changes what a configured target writes.
|
|
63
|
+
|
|
64
|
+
- Updated dependencies
|
|
65
|
+
- quario@0.9.0
|
|
66
|
+
|
|
3
67
|
## 0.1.0
|
|
4
68
|
|
|
5
69
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install quario @quario/docx
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
The engine is a peer dependency, installed beside the target. ESM-only, Node 22+, and
|
|
14
|
-
browser-ready through any standards-based ESM bundler. The renderer resolves a `Uint8Array
|
|
14
|
+
browser-ready through any standards-based ESM bundler. The renderer resolves a `Uint8Array`. You
|
|
15
15
|
write the file.
|
|
16
16
|
|
|
17
17
|
## Quick start
|
|
@@ -39,8 +39,8 @@ writeFileSync("orders.docx", bytes);
|
|
|
39
39
|
|
|
40
40
|
## Options
|
|
41
41
|
|
|
42
|
-
Both are optional
|
|
43
|
-
|
|
42
|
+
Both are optional. The factory call validates both: an unknown option, or one of the wrong type,
|
|
43
|
+
throws there rather than at the first render.
|
|
44
44
|
|
|
45
45
|
```js
|
|
46
46
|
docx({
|
|
@@ -56,27 +56,33 @@ docx({
|
|
|
56
56
|
|
|
57
57
|
## What it renders
|
|
58
58
|
|
|
59
|
-
A report's bands become paragraphs
|
|
59
|
+
A report's bands become paragraphs. A table detail becomes a **real Word table** carrying the
|
|
60
60
|
author's column shares as a fixed grid, with `span` as `gridSpan` and a header row that repeats
|
|
61
|
-
after every page break. A split is a borderless one-row table
|
|
62
|
-
column, carrying its `alt` as the drawing's description.
|
|
61
|
+
after every page break. A split is a borderless one-row table. An image is an inline picture at
|
|
62
|
+
its natural size, capped at the text column, carrying its `alt` as the drawing's description.
|
|
63
63
|
|
|
64
64
|
The grouping becomes the **navigation pane**: a group header's first item takes `Heading{depth+1}`
|
|
65
65
|
from this package's own `styles.xml`, so no theme typography leaks in. Everything a report declares
|
|
66
|
-
is direct formatting on the runs and paragraphs that wear it
|
|
66
|
+
is direct formatting on the runs and paragraphs that wear it. `Normal` stays empty, because a look
|
|
67
67
|
that depends on a style lookup is a look three readers may resolve three ways.
|
|
68
68
|
|
|
69
69
|
Page bands become a section's header and footer. A bare `{{ page.number }}` or `{{ page.total }}`
|
|
70
|
-
becomes a live `PAGE` / `NUMPAGES` field the reader's own application recomputes
|
|
71
|
-
from them
|
|
72
|
-
restarts the numbering, and `break
|
|
70
|
+
becomes a live `PAGE` / `NUMPAGES` field the reader's own application recomputes. Anything computed
|
|
71
|
+
from them freezes at the value the render saw. A group's `reset: "page"` opens a section that
|
|
72
|
+
restarts the numbering, and a `break` turns a page at every boundary its position names.
|
|
73
73
|
|
|
74
74
|
## Determinism
|
|
75
75
|
|
|
76
|
-
Two renders of one report are byte-identical, on **every supported runtime
|
|
77
|
-
JavaScript, so its deflate output does not vary by engine.
|
|
76
|
+
Two renders of one report are byte-identical, on **every supported runtime**. `fflate` is pure
|
|
77
|
+
JavaScript, so its deflate output does not vary by engine. The package stamps every entry
|
|
78
78
|
1980-01-01 and nothing in the document carries a clock, so a digest over the bytes is a fair test.
|
|
79
79
|
|
|
80
|
+
## Unlicensed marking
|
|
81
|
+
|
|
82
|
+
An unlicensed render writes the wording from `report-start.marking` as the first paragraph of
|
|
83
|
+
every footer. A licensed render writes none. The per-footer presence is normative. The wording's
|
|
84
|
+
look is best-effort.
|
|
85
|
+
|
|
80
86
|
## Documentation
|
|
81
87
|
|
|
82
88
|
[The quario documentation](https://getquario.com/docs/) is the reference.
|
|
@@ -87,14 +93,12 @@ of a declaration.
|
|
|
87
93
|
|
|
88
94
|
## License
|
|
89
95
|
|
|
90
|
-
Commercial software with readable source.
|
|
91
|
-
licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
96
|
+
Commercial software with readable source. Evaluation is free, unlimited, and watermarked.
|
|
97
|
+
Per-developer licenses at [getquario.com](https://getquario.com). See the bundled LICENSE.
|
|
92
98
|
|
|
93
|
-
Pass your license key once, on the instance
|
|
99
|
+
Pass your license key once, on the instance. quario verifies it offline:
|
|
94
100
|
|
|
95
101
|
```js
|
|
96
102
|
const q = quario({ license: "quario_..." });
|
|
97
103
|
await q.license; // { licensed: true, licensee: "Acme BV", id: "1-ACME" }
|
|
98
104
|
```
|
|
99
|
-
|
|
100
|
-
An unlicensed render carries the marking as the first paragraph of every footer.
|
package/lib/body.js
CHANGED
|
@@ -11,9 +11,13 @@
|
|
|
11
11
|
* SCHEMA.md's "an instance already at the top of a page does not force an empty
|
|
12
12
|
* page".
|
|
13
13
|
*
|
|
14
|
-
* **Page breaks.**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* **Page breaks.** The stream resolved a group's `break` position into the two
|
|
15
|
+
* edges of each instance (ADR 0077). A leading edge is `pageBreakBefore` on the
|
|
16
|
+
* instance's first paragraph, skipped where the instance also declared `reset`,
|
|
17
|
+
* whose section has already turned the page, and where it has nothing before it
|
|
18
|
+
* at all. A trailing edge is the same property on whatever paragraph follows the
|
|
19
|
+
* instance, and a document that ends owing one drops it: a break turns the page
|
|
20
|
+
* for what follows, and nothing follows.
|
|
17
21
|
*
|
|
18
22
|
* **Page columns.** A count on the report columns the body's own sections; a
|
|
19
23
|
* count on a group wraps each instance in *continuous* section breaks, so the
|
|
@@ -28,19 +32,22 @@
|
|
|
28
32
|
*
|
|
29
33
|
* **Layers.** Outward-in: this target's baseline (`docDefaults`, so nothing is
|
|
30
34
|
* written per run), the report default, the band-role default, then the node's
|
|
31
|
-
* own
|
|
32
|
-
* declaration and wins, which a plain overwrite already gives.
|
|
35
|
+
* own. A `false` in an inner layer is a declaration and wins.
|
|
33
36
|
*/
|
|
34
37
|
import { headingAt } from "./stylepart.js";
|
|
35
38
|
import { paraProps, under } from "./style.js";
|
|
36
|
-
import { grid, record,
|
|
37
|
-
import {
|
|
39
|
+
import { grid, record, tbl } from "./table.js";
|
|
40
|
+
import { content, splitBlock } from "./content.js";
|
|
38
41
|
import { EMPTY_CTX } from "./furniture.js";
|
|
39
|
-
import {
|
|
42
|
+
import { paragraph } from "./text.js";
|
|
43
|
+
|
|
44
|
+
// The page break a paragraph wears, spelled once: `push` folds it into a
|
|
45
|
+
// paragraph's own properties, and `carrier` gives it one where no paragraph is
|
|
46
|
+
// coming to take it.
|
|
47
|
+
const BREAK = "<w:pageBreakBefore/>";
|
|
40
48
|
|
|
41
49
|
// The band roles this target supplies a default for, and the only look it puts
|
|
42
|
-
// on a report that declared nothing
|
|
43
|
-
// its consumer has no seam to supply them, and a document is final on open.
|
|
50
|
+
// on a report that declared nothing (ADR 0014: a document is final on open).
|
|
44
51
|
// `test/omakase-defaults.test.js` holds this copy and the other two in
|
|
45
52
|
// agreement; it is not shared code, and that is the ADR's point.
|
|
46
53
|
/** @type {Record<string, any>} */
|
|
@@ -91,15 +98,12 @@ export let bodyOf = (section) => {
|
|
|
91
98
|
/** What a render carries that the events do not: the report default, the
|
|
92
99
|
* instance's `format` configuration, the text column and the picture
|
|
93
100
|
* registry. Settled once, on the opening event, and read-only after —
|
|
94
|
-
* `
|
|
95
|
-
* @type {import("./
|
|
101
|
+
* `content.js`'s `Ctx`, so the two writers describe a render one way.
|
|
102
|
+
* @type {import("./content.js").Ctx} */
|
|
96
103
|
let ctx = EMPTY_CTX;
|
|
97
104
|
/** The table being filled, if any: its grid, and the rows written so far. */
|
|
98
105
|
/** @type {{ widths: number[], rows: string } | null} */
|
|
99
106
|
let table = null;
|
|
100
|
-
/** The split being filled, if any. Splits never nest, so one is enough. */
|
|
101
|
-
/** @type {ReturnType<typeof splitting> | null} */
|
|
102
|
-
let split = null;
|
|
103
107
|
|
|
104
108
|
// `CT_PPr` is a sequence and not a bag: `pStyle` is its first child,
|
|
105
109
|
// `pageBreakBefore` its fourth, and everything `paraProps` writes comes after
|
|
@@ -107,17 +111,24 @@ export let bodyOf = (section) => {
|
|
|
107
111
|
/** @type {(props: string, style: any, inner: string) => void} */
|
|
108
112
|
let push = (props, style, inner) => {
|
|
109
113
|
let look = owed.gapped ? under({ spaceBefore: GAP }, style) : style;
|
|
110
|
-
blocks.push({
|
|
111
|
-
props: props + (owed.broken ? "<w:pageBreakBefore/>" : "") + paraProps(look),
|
|
112
|
-
inner,
|
|
113
|
-
});
|
|
114
|
+
blocks.push({ props: props + (owed.broken ? BREAK : "") + paraProps(look), inner });
|
|
114
115
|
owed = { broken: false, gapped: false };
|
|
115
116
|
};
|
|
116
117
|
|
|
118
|
+
/** An empty paragraph, written where something is owed and no paragraph is
|
|
119
|
+
* coming to take it: a section break whose last block is a table, and a page
|
|
120
|
+
* break a table would otherwise swallow.
|
|
121
|
+
* @type {(props?: string) => void} */
|
|
122
|
+
let carrier = (props = "") => void blocks.push({ props, inner: "" });
|
|
123
|
+
|
|
117
124
|
/** A block that is not a paragraph -- a table -- which carries no properties
|
|
118
|
-
* and so cannot take what the next paragraph is owed.
|
|
125
|
+
* and so cannot take what the next paragraph is owed. A page break takes a
|
|
126
|
+
* carrier of its own first, or the table would swallow it; the band gap
|
|
127
|
+
* does not, because the next paragraph still takes it.
|
|
119
128
|
* @type {(inner: string) => void} */
|
|
120
129
|
let block = (inner) => {
|
|
130
|
+
if (owed.broken) carrier(BREAK);
|
|
131
|
+
owed.broken = false;
|
|
121
132
|
blocks.push({ props: "", inner, raw: true });
|
|
122
133
|
};
|
|
123
134
|
|
|
@@ -128,15 +139,17 @@ export let bodyOf = (section) => {
|
|
|
128
139
|
sections.push(section(kind));
|
|
129
140
|
};
|
|
130
141
|
|
|
131
|
-
/** The report's own page-column count
|
|
132
|
-
* opened a columned section, so `group-end` knows whose to close. */
|
|
142
|
+
/** The report's own page-column count. */
|
|
133
143
|
let columns = 1;
|
|
134
|
-
/**
|
|
135
|
-
|
|
144
|
+
/** One frame per open instance, innermost last: the columned section this
|
|
145
|
+
* instance opened, if any, and its trailing page break. The brackets pair,
|
|
146
|
+
* so `group-end` pops the frame `group-start` pushed.
|
|
147
|
+
* @type {{ columns: number, trailing: boolean }[]} */
|
|
148
|
+
let instances = [];
|
|
136
149
|
/** @type {(value: any) => number} */
|
|
137
150
|
let counted = (value) => (Number.isInteger(value) && value > 1 ? value : 0);
|
|
138
151
|
|
|
139
|
-
/** @type {(own: import("./
|
|
152
|
+
/** @type {(own: import("./content.js").Ctx, event: any) => void} */
|
|
140
153
|
let start = (own, event) => {
|
|
141
154
|
ctx = own;
|
|
142
155
|
columns = counted(event.columns) || 1;
|
|
@@ -165,7 +178,7 @@ export let bodyOf = (section) => {
|
|
|
165
178
|
let closeSection = () => {
|
|
166
179
|
let last = blocks.at(-1);
|
|
167
180
|
// oxlint-disable-next-line no-unused-expressions
|
|
168
|
-
(blocks.length === sectionStart || last?.raw) &&
|
|
181
|
+
(blocks.length === sectionStart || last?.raw) && carrier();
|
|
169
182
|
/** @type {any} */ (blocks.at(-1)).closes = sections.at(-1);
|
|
170
183
|
};
|
|
171
184
|
|
|
@@ -183,13 +196,12 @@ export let bodyOf = (section) => {
|
|
|
183
196
|
// has not started: groups read as blocks without authored margins.
|
|
184
197
|
// oxlint-disable-next-line no-unused-expressions
|
|
185
198
|
owed.gapped ||= blocks.length > 0;
|
|
199
|
+
// Its own count is what `group-end` closes; a section that merely inherits
|
|
200
|
+
// the report's is not one this instance opened.
|
|
186
201
|
let own = counted(event.columns);
|
|
202
|
+
instances.push({ columns: own, trailing: event.breakAfter === "page" });
|
|
187
203
|
let kind = sectionFor(event.reset === "page", own);
|
|
188
204
|
if (!kind) return breakBefore(event);
|
|
189
|
-
// Its own count is what `group-end` closes; a section that merely inherits
|
|
190
|
-
// the report's is not one this instance opened.
|
|
191
|
-
// oxlint-disable-next-line no-unused-expressions
|
|
192
|
-
own && columned.push(event.depth);
|
|
193
205
|
reopen(kind);
|
|
194
206
|
};
|
|
195
207
|
|
|
@@ -210,11 +222,19 @@ export let bodyOf = (section) => {
|
|
|
210
222
|
open(kind);
|
|
211
223
|
};
|
|
212
224
|
|
|
213
|
-
/**
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
225
|
+
/** What an instance's close owes, in the order the document takes them: the
|
|
226
|
+
* trailing page break the stream stated when it opened, then the columned
|
|
227
|
+
* section it opened.
|
|
228
|
+
* @type {(frame: { columns: number, trailing: boolean }) => void} */
|
|
229
|
+
let closeInstance = (frame) => {
|
|
230
|
+
if (frame.trailing) owed.broken ||= blocks.length > 0;
|
|
231
|
+
if (frame.columns) reopen({ continuous: true, columns });
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/** @type {() => void} */
|
|
235
|
+
let groupEnd = () => {
|
|
236
|
+
let frame = instances.pop();
|
|
237
|
+
if (frame) closeInstance(frame);
|
|
218
238
|
};
|
|
219
239
|
|
|
220
240
|
/**
|
|
@@ -230,20 +250,6 @@ export let bodyOf = (section) => {
|
|
|
230
250
|
return owes;
|
|
231
251
|
};
|
|
232
252
|
|
|
233
|
-
/** One item or picture, as the content of the paragraph it becomes. `column`
|
|
234
|
-
* is what caps a picture: the text column, or the slot holding it.
|
|
235
|
-
* @type {(event: any, style: any, column?: number) => string} */
|
|
236
|
-
let contentOf = (event, style, column = columnWidth()) =>
|
|
237
|
-
event.type === "image"
|
|
238
|
-
? pictureRun(event, column)
|
|
239
|
-
: runs(event.tokens, looking(style, ctx.intl));
|
|
240
|
-
|
|
241
|
-
/** @type {(event: any, column: number) => string} */
|
|
242
|
-
let pictureRun = (event, column) => {
|
|
243
|
-
let rId = ctx.picture(event);
|
|
244
|
-
return drawing(event, idOf(rId), rId, describe(event), sizeOf(event, column));
|
|
245
|
-
};
|
|
246
|
-
|
|
247
253
|
/** How wide the body is where it is being written: the text column, divided
|
|
248
254
|
* between the [page columns](../../../SCHEMA.md#page-columns) of the section
|
|
249
255
|
* it lands in, less the gutter between them. `708` twips is Word's own. */
|
|
@@ -254,23 +260,13 @@ export let bodyOf = (section) => {
|
|
|
254
260
|
|
|
255
261
|
/** @type {(event: any) => void} */
|
|
256
262
|
let itemOf = (event) => {
|
|
257
|
-
if (split) {
|
|
258
|
-
let own = under(split.style, event.style);
|
|
259
|
-
return split.slot(own, (column) => contentOf(event, own, column));
|
|
260
|
-
}
|
|
261
263
|
let style = styleOf(event);
|
|
262
|
-
push(claimHeading(event.role), style,
|
|
264
|
+
push(claimHeading(event.role), style, content(event, style, columnWidth(), ctx));
|
|
263
265
|
};
|
|
264
266
|
|
|
265
267
|
/** @type {(event: any) => void} */
|
|
266
|
-
let
|
|
267
|
-
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
let splitEnd = () => {
|
|
271
|
-
// oxlint-disable-next-line no-unused-expressions
|
|
272
|
-
split && block(split.close());
|
|
273
|
-
split = null;
|
|
268
|
+
let splitOf = (event) => {
|
|
269
|
+
block(splitBlock(event, columnWidth(), styleOf(event), ctx));
|
|
274
270
|
};
|
|
275
271
|
|
|
276
272
|
/** What a table's cells are written with. `intl` is settled on the opening
|
|
@@ -319,22 +315,27 @@ export let bodyOf = (section) => {
|
|
|
319
315
|
"group-end": groupEnd,
|
|
320
316
|
item: itemOf,
|
|
321
317
|
image: itemOf,
|
|
322
|
-
|
|
323
|
-
"split-end": splitEnd,
|
|
318
|
+
split: splitOf,
|
|
324
319
|
"table-start": tableStart,
|
|
325
320
|
row,
|
|
326
321
|
"total-row": row,
|
|
327
322
|
"table-end": tableEnd,
|
|
328
323
|
},
|
|
329
324
|
/** @type {(sectPr: (section: any) => string) => string} */
|
|
330
|
-
xml: (sectPr) =>
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
325
|
+
xml: (sectPr) => {
|
|
326
|
+
// A page break the document ends owing is dropped, and `owed` is simply
|
|
327
|
+
// left set: a break turns the page for what follows it, and nothing
|
|
328
|
+
// follows. Word, the PDF and a printed fragment agree on that.
|
|
329
|
+
return (
|
|
330
|
+
blocks
|
|
331
|
+
.map(
|
|
332
|
+
(b) =>
|
|
333
|
+
/** @type {any} */ (b).raw
|
|
334
|
+
? b.inner
|
|
335
|
+
: paragraph(b.props + (b.closes ? sectPr(b.closes) : ""), b.inner),
|
|
336
|
+
)
|
|
337
|
+
.join("") + sectPr(sections.at(-1))
|
|
338
|
+
);
|
|
339
|
+
},
|
|
339
340
|
};
|
|
340
341
|
};
|
package/lib/content.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an item, a picture or a split becomes, one way for both writers: the
|
|
3
|
+
* body and a page band fill the same paragraphs and the same one-row table,
|
|
4
|
+
* and differ on nothing but the layer under an event's style and which field
|
|
5
|
+
* numbers the pages. Both arrive in `ctx`, so neither writer keeps a copy.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, drawing, idOf, sizeOf } from "./picture.js";
|
|
8
|
+
import { under } from "./style.js";
|
|
9
|
+
import { splitting } from "./table.js";
|
|
10
|
+
import { looking, runs } from "./text.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* What a render carries that the events do not: the report default every band
|
|
14
|
+
* item wears under its own, the instance's `format` configuration, the text
|
|
15
|
+
* column a picture is capped at, the registry a picture's bytes become a part
|
|
16
|
+
* in, and -- for a page band only -- which field gives the length of the
|
|
17
|
+
* sequence its section numbers. Both writers hand one of these down, so they
|
|
18
|
+
* describe a render one way.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {{ base: any, intl: any, width: number,
|
|
21
|
+
* picture: (event: any) => string, sequence?: string }} Ctx
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* One item's or picture's content: the runs a text item's tokens become, or
|
|
26
|
+
* the inline drawing a picture is. `column` is what caps a picture: the text
|
|
27
|
+
* column, or the slot holding it.
|
|
28
|
+
*
|
|
29
|
+
* @type {(event: any, style: any, column: number, ctx: Ctx) => string}
|
|
30
|
+
*/
|
|
31
|
+
export let content = (event, style, column, ctx) => {
|
|
32
|
+
if (event.type !== "image") return runs(event.tokens, looking(style, ctx.intl, ctx.sequence));
|
|
33
|
+
let rId = ctx.picture(event);
|
|
34
|
+
return drawing(event, idOf(rId), rId, describe(event), sizeOf(event, column));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* One split, folded, as the borderless one-row table it becomes: a slot per
|
|
39
|
+
* cell, each slot's own style layered over the split's, which is `style`.
|
|
40
|
+
*
|
|
41
|
+
* @type {(event: any, width: number, style: any, ctx: Ctx) => string}
|
|
42
|
+
*/
|
|
43
|
+
export let splitBlock = (event, width, style, ctx) => {
|
|
44
|
+
let split = splitting(event.slots, width, style);
|
|
45
|
+
for (let slot of event.items) {
|
|
46
|
+
let own = under(split.style, slot.style);
|
|
47
|
+
split.slot(own, (column) => content(slot, own, column, ctx));
|
|
48
|
+
}
|
|
49
|
+
return split.close();
|
|
50
|
+
};
|
package/lib/furniture.js
CHANGED
|
@@ -14,16 +14,15 @@
|
|
|
14
14
|
* what the probe below saw.
|
|
15
15
|
*
|
|
16
16
|
* *Where* on the page comes from the **probe**: the band closure is evaluated
|
|
17
|
-
* at page 1 of 2 and at page 2 of 2, and the two results are compared. Equal
|
|
18
|
-
*
|
|
19
|
-
* one
|
|
20
|
-
*
|
|
21
|
-
* gated on `page.total` sees 2, exactly as the PDF target's height probe does.
|
|
17
|
+
* at page 1 of 2 and at page 2 of 2, and the two results are compared. Equal
|
|
18
|
+
* means one part and no `titlePg`; different means a first-page part, a
|
|
19
|
+
* default one, and `titlePg` in the section. The probe assumes a total of two,
|
|
20
|
+
* so a band gated on `page.total` sees 2.
|
|
22
21
|
*/
|
|
23
|
-
import {
|
|
22
|
+
import { splits } from "quario";
|
|
23
|
+
import { content, splitBlock } from "./content.js";
|
|
24
24
|
import { paraProps, under } from "./style.js";
|
|
25
|
-
import {
|
|
26
|
-
import { looking, paragraph, run, runs } from "./text.js";
|
|
25
|
+
import { paragraph, run } from "./text.js";
|
|
27
26
|
import { R, W, XML } from "./xml.js";
|
|
28
27
|
|
|
29
28
|
// The two pages every band is read at, in order: the first of two, then a
|
|
@@ -40,67 +39,32 @@ const MARKING_LOOK = '<w:color w:val="808080"/><w:sz w:val="14"/>';
|
|
|
40
39
|
|
|
41
40
|
/**
|
|
42
41
|
* One band's events as the blocks they become: an item or a picture is a
|
|
43
|
-
* paragraph, a split is a borderless one-row table
|
|
44
|
-
*
|
|
45
|
-
* cannot drift on what a split is.
|
|
46
|
-
*
|
|
47
|
-
* `ctx` is what a render carries that the events do not: the report default
|
|
48
|
-
* every band item wears under its own, the instance's `format` configuration,
|
|
49
|
-
* the text column a picture is capped at, and the registry a picture's bytes
|
|
50
|
-
* become a part in.
|
|
42
|
+
* paragraph, a split is a borderless one-row table. `splitting` is the shared
|
|
43
|
+
* rule, so this and the body cannot drift on what a split is.
|
|
51
44
|
*
|
|
52
45
|
* @type {(events: any[], ctx: Band) => string}
|
|
53
46
|
*/
|
|
54
47
|
let banded = (events, ctx) => {
|
|
55
|
-
|
|
56
|
-
let
|
|
57
|
-
|
|
58
|
-
return state.out;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/** @type {(state: any, e: any, ctx: Band) => void} */
|
|
62
|
-
let open = (state, e, ctx) => {
|
|
63
|
-
state.split = splitting(e.slots, ctx.width, under(ctx.base, e.style));
|
|
48
|
+
let out = "";
|
|
49
|
+
for (let e of splits(events)) out += STEPS[e.type]?.(e, ctx) ?? "";
|
|
50
|
+
return out;
|
|
64
51
|
};
|
|
65
52
|
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
state.split = null;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/** One item or picture, into the split being filled or into the band itself.
|
|
73
|
-
* @type {(state: any, e: any, ctx: Band) => void} */
|
|
74
|
-
let draw = (state, e, ctx) => {
|
|
75
|
-
if (state.split) {
|
|
76
|
-
let own = under(state.split.style, e.style);
|
|
77
|
-
return state.split.slot(own, (/** @type {number} */ column) => content(e, own, column, ctx));
|
|
78
|
-
}
|
|
53
|
+
/** One item or picture, as the paragraph it becomes in the band.
|
|
54
|
+
* @type {(e: any, ctx: Band) => string} */
|
|
55
|
+
let draw = (e, ctx) => {
|
|
79
56
|
let style = under(ctx.base, e.style);
|
|
80
|
-
|
|
57
|
+
return paragraph(paraProps(style), content(e, style, ctx.width, ctx));
|
|
81
58
|
};
|
|
82
59
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// consumer written before an event kind existed go on working.
|
|
86
|
-
/** @type {Map<string, (state: any, e: any, ctx: Band) => void>} */
|
|
87
|
-
const STEPS = new Map([
|
|
88
|
-
["split-start", open],
|
|
89
|
-
["split-end", close],
|
|
90
|
-
["item", draw],
|
|
91
|
-
["image", draw],
|
|
92
|
-
]);
|
|
60
|
+
/** @type {(e: any, ctx: Band) => string} */
|
|
61
|
+
let drawSplit = (e, ctx) => splitBlock(e, ctx.width, under(ctx.base, e.style), ctx);
|
|
93
62
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
let content = (e, style, column, ctx) => {
|
|
100
|
-
if (e.type !== "image") return runs(e.tokens, looking(style, ctx.intl, ctx.sequence));
|
|
101
|
-
let rId = ctx.picture(e);
|
|
102
|
-
return drawing(e, idOf(rId), rId, describe(e), sizeOf(e, column));
|
|
103
|
-
};
|
|
63
|
+
// What each kind of band event becomes. An event this does not know
|
|
64
|
+
// contributes nothing rather than failing, which is what lets a consumer
|
|
65
|
+
// written before an event kind existed go on working.
|
|
66
|
+
/** @type {Record<string, (e: any, ctx: Band) => string>} */
|
|
67
|
+
const STEPS = { split: drawSplit, item: draw, image: draw };
|
|
104
68
|
|
|
105
69
|
/**
|
|
106
70
|
* One reading of a band at one page, behind whatever the target leads the part
|
|
@@ -110,16 +74,7 @@ let content = (e, style, column, ctx) => {
|
|
|
110
74
|
*/
|
|
111
75
|
let reading = (closure, at, lead, ctx) => lead + (closure ? banded(closure(at), ctx) : "");
|
|
112
76
|
|
|
113
|
-
/**
|
|
114
|
-
* What a render carries that the events do not: the report default every band
|
|
115
|
-
* item wears under its own, the instance's `format` configuration, the text
|
|
116
|
-
* column a picture is capped at, and the registry a picture's bytes become a
|
|
117
|
-
* part in. `body.js` holds the same shape, so the two writers describe a render
|
|
118
|
-
* one way.
|
|
119
|
-
*
|
|
120
|
-
* @typedef {{ base: any, intl: any, width: number,
|
|
121
|
-
* picture: (event: any) => string }} Ctx
|
|
122
|
-
*/
|
|
77
|
+
/** @typedef {import('./content.js').Ctx} Ctx */
|
|
123
78
|
|
|
124
79
|
/**
|
|
125
80
|
* A `Ctx` for one sequence: the same render, plus which field gives the length
|
|
@@ -130,9 +85,9 @@ let reading = (closure, at, lead, ctx) => lead + (closure ? banded(closure(at),
|
|
|
130
85
|
*/
|
|
131
86
|
|
|
132
87
|
/**
|
|
133
|
-
* The part registry: every distinct part written once
|
|
134
|
-
*
|
|
135
|
-
*
|
|
88
|
+
* The part registry: every distinct part written once. Sections opened by
|
|
89
|
+
* `reset: "page"` all number themselves the same way, so a document of two
|
|
90
|
+
* hundred invoices carries one footer part.
|
|
136
91
|
*
|
|
137
92
|
* @param {any} [page] `report-start.page`, absent when no band is declared.
|
|
138
93
|
* @param {string} [marking] The wording, on an unlicensed render.
|
|
@@ -167,8 +122,7 @@ export let furnish = (page, marking, relate = () => "", ctx = EMPTY_CTX) => {
|
|
|
167
122
|
// reads it as "the first page takes the first-page parts" -- so a band with
|
|
168
123
|
// no `first` reference shows *nothing* on page one once the other band has
|
|
169
124
|
// turned it on. Both therefore name a first-page part whenever either needs
|
|
170
|
-
// one; the steady one names the part it already wrote
|
|
171
|
-
// hands back rather than writing twice.
|
|
125
|
+
// one; the steady one names the part it already wrote.
|
|
172
126
|
/** @type {(kind: string, pair: string[], titlePg: boolean) => string} */
|
|
173
127
|
let references = (kind, [first, later], titlePg) => {
|
|
174
128
|
/** @type {(type: string, body: string) => string} */
|
package/lib/index.d.ts
CHANGED
|
@@ -18,7 +18,11 @@ export interface DocxMeta {
|
|
|
18
18
|
subject?: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Host controls, taken and validated at the factory call: an option this
|
|
23
|
+
* target does not understand, or one of the wrong type, throws a `TypeError`
|
|
24
|
+
* there rather than costing the host the option in silence.
|
|
25
|
+
*/
|
|
22
26
|
export interface DocxOptions {
|
|
23
27
|
page?: DocxPage;
|
|
24
28
|
meta?: DocxMeta;
|
package/lib/index.js
CHANGED
|
@@ -6,12 +6,10 @@
|
|
|
6
6
|
* one is quario's.
|
|
7
7
|
*
|
|
8
8
|
* This module is the entry — the options a host writes, the walk over the
|
|
9
|
-
* stream, and the parts a package is made of. The writers
|
|
10
|
-
*
|
|
11
|
-
* `furniture.js` for the parts a page band becomes, `styles.js` for the base
|
|
12
|
-
* every part inherits, and `text.js` for the runs all of them are made of.
|
|
9
|
+
* stream, and the parts a package is made of. The writers live beside it:
|
|
10
|
+
* `body.js`, `furniture.js`, `styles.js` and `text.js`.
|
|
13
11
|
*/
|
|
14
|
-
import { walk } from "quario";
|
|
12
|
+
import { hostMeta, hostOptions, splits, walk } from "quario";
|
|
15
13
|
import { bodyOf } from "./body.js";
|
|
16
14
|
import { DOCUMENT_NS, furnish } from "./furniture.js";
|
|
17
15
|
import { STYLES } from "./stylepart.js";
|
|
@@ -29,44 +27,6 @@ const WML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
|
29
27
|
// own default, and what the page bands will be laid against.
|
|
30
28
|
const FURNITURE = 720;
|
|
31
29
|
|
|
32
|
-
/** @type {(msg: string) => never} */
|
|
33
|
-
let err = (msg) => {
|
|
34
|
-
throw Error(msg);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Split from `object` below rather than inlined: the three terms together
|
|
38
|
-
// breach the complexity budget `npm run fallow` holds this package to.
|
|
39
|
-
/** @type {(value: any) => boolean} */
|
|
40
|
-
let plain = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41
|
-
|
|
42
|
-
/** @type {(value: any, at: string) => any} */
|
|
43
|
-
let object = (value, at) => {
|
|
44
|
-
// oxlint-disable-next-line no-unused-expressions
|
|
45
|
-
value === undefined || plain(value) || err(at + ": expected an object");
|
|
46
|
-
return value;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/** @type {(value: any, keys: string[], at: string) => void} */
|
|
50
|
-
let only = (value, keys, at) => {
|
|
51
|
-
for (let key in value) if (!keys.includes(key)) err(at + ': unknown option "' + key + '"');
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// The three document properties a host may write.
|
|
55
|
-
let PROPERTIES = ["title", "author", "subject"];
|
|
56
|
-
|
|
57
|
-
// The document properties, validated: strings, and never a date. What a host
|
|
58
|
-
// cannot write is what keeps a render deterministic -- `core.xml` is the one
|
|
59
|
-
// part with a slot for a clock, and it has none of them.
|
|
60
|
-
/** @type {(meta: any) => any} */
|
|
61
|
-
let metaOf = (meta) => {
|
|
62
|
-
object(meta, "options.meta");
|
|
63
|
-
only(meta, PROPERTIES, "options.meta");
|
|
64
|
-
for (let key in meta)
|
|
65
|
-
// oxlint-disable-next-line no-unused-expressions
|
|
66
|
-
typeof meta[key] === "string" || err("options.meta." + key + ": expected a string");
|
|
67
|
-
return meta;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
30
|
/**
|
|
71
31
|
* One section. The child order is the schema's and Word is strict about it:
|
|
72
32
|
* the part references, then `type`, the geometry, `pgNumType`, `cols`, and
|
|
@@ -191,15 +151,18 @@ let parts = (body, meta, furniture, rels) => ({
|
|
|
191
151
|
* bytes described in SCHEMA.md ("The DOCX target"). Options are taken and
|
|
192
152
|
* validated at the factory call.
|
|
193
153
|
*
|
|
194
|
-
* @
|
|
154
|
+
* @import { DocxOptions } from './index.d.ts'
|
|
155
|
+
*
|
|
156
|
+
* @param {DocxOptions} [options] Host controls (see SCHEMA.md, "The DOCX target").
|
|
195
157
|
* @returns {{name: "docx", compile: (stream: any) => (data?: any) => Promise<Uint8Array>}}
|
|
196
158
|
* The target (see SCHEMA.md, "Instances and targets").
|
|
197
159
|
*/
|
|
198
160
|
export function docx(options) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
let
|
|
161
|
+
hostOptions(options, ["page", "meta"], "options");
|
|
162
|
+
// Closed here rather than in `pageBox`, for the reason the layout package
|
|
163
|
+
// gives at the same call: the surface elements share that function.
|
|
164
|
+
let page = hostOptions(options?.page, ["size", "margin"], "options.page");
|
|
165
|
+
let meta = hostMeta(options?.meta, "options.meta");
|
|
203
166
|
// The size fails here rather than at the first render: a host wrote it, so
|
|
204
167
|
// a host hears about it where it was written. The margin is validated again
|
|
205
168
|
// per render, because the document may be the one declaring it.
|
|
@@ -209,9 +172,7 @@ export function docx(options) {
|
|
|
209
172
|
// The sections are written after the walk, so the opening event is taken
|
|
210
173
|
// in its handler rather than peeked ahead of the driver: the margin a
|
|
211
174
|
// document may declare instead of the host is settled by the time any
|
|
212
|
-
// second event is pulled, and the stream reaches `walk` unwrapped.
|
|
213
|
-
// walk is what spends the render's budget and breathes for the host
|
|
214
|
-
// meanwhile.
|
|
175
|
+
// second event is pulled, and the stream reaches `walk` unwrapped.
|
|
215
176
|
let geo = geometry(page);
|
|
216
177
|
// Every relationship the document carries, in one place: the parts it
|
|
217
178
|
// always has, then whatever the furniture and the body ask for. One
|
|
@@ -227,13 +188,11 @@ export function docx(options) {
|
|
|
227
188
|
rels.push({ path: "word/" + target, target, kind, rId });
|
|
228
189
|
return rId;
|
|
229
190
|
};
|
|
230
|
-
// One part per distinct picture
|
|
191
|
+
// One part per distinct picture, keyed on the bytes themselves: the
|
|
231
192
|
// page-band probe reads each band twice and every section reads it again,
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
// *computes* fresh bytes per call writes a part per call, which is
|
|
236
|
-
// wasteful and still correct.
|
|
193
|
+
// so the part stays identical and the furniture's dedup still collapses
|
|
194
|
+
// the sections. A `source` computing fresh bytes per call writes a part
|
|
195
|
+
// per call, which is wasteful and still correct.
|
|
237
196
|
/** @type {Map<Uint8Array, string>} */
|
|
238
197
|
let drawn = new Map();
|
|
239
198
|
/** @type {(event: any) => string} */
|
|
@@ -254,13 +213,13 @@ export function docx(options) {
|
|
|
254
213
|
...furniture.refsFor(kind.reset ? SECTION_PAGES : DOCUMENT_PAGES),
|
|
255
214
|
});
|
|
256
215
|
let body = bodyOf(section);
|
|
257
|
-
await walk(stream(data), {
|
|
216
|
+
await walk(splits(stream(data)), {
|
|
258
217
|
...body.handlers,
|
|
259
218
|
"report-start": (event) => {
|
|
260
219
|
geo = geometry(page, event);
|
|
261
220
|
// What a render carries that the events do not, settled once and handed
|
|
262
221
|
// to both writers: a fifth instance field must be read here and nowhere
|
|
263
|
-
// else (`
|
|
222
|
+
// else (`content.js`, `Ctx`).
|
|
264
223
|
let ctx = {
|
|
265
224
|
base: event.style || null,
|
|
266
225
|
intl: { locale: event.locale, currency: event.currency, timeZone: event.timeZone },
|
package/lib/pack.js
CHANGED
|
@@ -3,11 +3,8 @@
|
|
|
3
3
|
* fflate, which is what makes the writer a seam — a later one (a sibling OOXML
|
|
4
4
|
* writer, `CompressionStream`) replaces this file and nothing else.
|
|
5
5
|
*
|
|
6
|
-
* Only the synchronous names are ever called
|
|
7
|
-
* reach for a worker built from a string
|
|
8
|
-
* `--disallow-code-generation-from-strings` with the browser page under a
|
|
9
|
-
* `default-src 'none'` policy, so the path this target takes is proved rather
|
|
10
|
-
* than promised.
|
|
6
|
+
* Only the synchronous names are ever called: the asynchronous family would
|
|
7
|
+
* reach for a worker built from a string.
|
|
11
8
|
*/
|
|
12
9
|
import { strToU8, zipSync } from "fflate";
|
|
13
10
|
|
package/lib/page.js
CHANGED
|
@@ -5,14 +5,11 @@
|
|
|
5
5
|
* — the rules and their wording, not the frame derivation this target has no
|
|
6
6
|
* use for, because Word does the paginating.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* target imports only public engine helpers). So the copy is hand-synced under
|
|
14
|
-
* that same gate, which asserts this table and these wordings against the
|
|
15
|
-
* layout's: change one and change the other, or the gate fails.
|
|
8
|
+
* This target cannot reach the layout's: taking `@quario/layout` would install
|
|
9
|
+
* the pagination engine and its font metrics for a two-entry table (ADR 0014).
|
|
10
|
+
* So the copy is hand-synced under `test/page-sizes.test.js`, which asserts
|
|
11
|
+
* this table and these wordings against the layout's: change one and change
|
|
12
|
+
* the other, or the gate fails (ADR 0039).
|
|
16
13
|
*/
|
|
17
14
|
|
|
18
15
|
// The named sizes, in points, exactly as `@quario/layout` states them; the
|
|
@@ -25,7 +22,7 @@ let SIZES = /** @type {Record<string, [number, number]>} */ ({
|
|
|
25
22
|
|
|
26
23
|
/** @type {(msg: string) => never} */
|
|
27
24
|
let err = (msg) => {
|
|
28
|
-
throw
|
|
25
|
+
throw TypeError(msg);
|
|
29
26
|
};
|
|
30
27
|
|
|
31
28
|
/** @type {(value: number) => boolean} */
|
package/lib/picture.js
CHANGED
|
@@ -71,8 +71,7 @@ export let drawing = (event, id, rId, alt, size) => {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* A picture's description, joined through the engine's own display rule
|
|
75
|
-
* `Date` in an `alt` reads as ISO 8601 UTC here as it does everywhere else, and
|
|
74
|
+
* A picture's description, joined through the engine's own display rule, so
|
|
76
75
|
* the bytes do not move with the host's zone.
|
|
77
76
|
*
|
|
78
77
|
* @type {(event: any) => string}
|
|
@@ -81,10 +80,9 @@ export let describe = (event) => text(event.alt || []);
|
|
|
81
80
|
|
|
82
81
|
/**
|
|
83
82
|
* The number that names a drawing: its relationship's. One id space for the
|
|
84
|
-
* whole document, which is what `docPr` wants
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* holding a logo look like one that differs on the first page.
|
|
83
|
+
* whole document, which is what `docPr` wants, and stable across the two
|
|
84
|
+
* readings of a band — a running count would move and make every band holding
|
|
85
|
+
* a logo look like one that differs on the first page.
|
|
88
86
|
*
|
|
89
87
|
* @type {(rId: string) => number}
|
|
90
88
|
*/
|
package/lib/style.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* This target's reading of the closed style vocabulary
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* value of the wrong shape contributes nothing rather than reaching the
|
|
6
|
-
* document.
|
|
2
|
+
* This target's reading of the closed style vocabulary. Declarations are
|
|
3
|
+
* lenient at render: a value of the wrong shape contributes nothing rather
|
|
4
|
+
* than reaching the document.
|
|
7
5
|
*
|
|
8
6
|
* Everything here is a property *string*, in the order the schema fixes.
|
|
9
7
|
* `CT_RPr`, `CT_PPrBase`, `CT_TcPr` and the border containers are sequences,
|
|
@@ -22,10 +20,9 @@ let HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
|
22
20
|
|
|
23
21
|
// A border container's sides, in the order `CT_PBdr` and `CT_TcBorders` both
|
|
24
22
|
// declare them -- which is *not* the clockwise order the authoring surface
|
|
25
|
-
// names them in.
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// module makes.
|
|
23
|
+
// names them in. Built once: `borders` runs per paragraph and per cell, and
|
|
24
|
+
// rebuilding four key strings per side per call is most of this module's
|
|
25
|
+
// garbage.
|
|
29
26
|
let SIDES = ["Top", "Left", "Bottom", "Right"].map((side) => ({
|
|
30
27
|
tag: side.toLowerCase(),
|
|
31
28
|
width: "border" + side + "Width",
|
|
@@ -74,9 +71,8 @@ let underline = (value) =>
|
|
|
74
71
|
/**
|
|
75
72
|
* The face a declared family names. A generic maps to the name all three
|
|
76
73
|
* readers ship with; any other is the author's own string, written for the
|
|
77
|
-
* reader to substitute and **escaped** —
|
|
78
|
-
*
|
|
79
|
-
* reaches an attribute as the author wrote it (root CLAUDE.md, constraint 4).
|
|
74
|
+
* reader to substitute and **escaped** — the one value in this module that is
|
|
75
|
+
* not a number, a hex colour or a closed name.
|
|
80
76
|
*
|
|
81
77
|
* @type {(family: any) => string}
|
|
82
78
|
*/
|
|
@@ -123,13 +119,10 @@ let tint = (value) => {
|
|
|
123
119
|
* Unicode default case mapping, never the host's locale, exactly as the PDF
|
|
124
120
|
* target does for its own reason (`text.js`).
|
|
125
121
|
*
|
|
126
|
-
* `boxed` is the same question `paraProps` asks below
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* here is `false` and there is `true`. Only a
|
|
131
|
-
* [styled run](../../../SCHEMA.md#cell-values) has nothing but the run to draw
|
|
132
|
-
* its own on.
|
|
122
|
+
* `boxed` is the same question `paraProps` asks below: *is this element the
|
|
123
|
+
* one that draws the box?* The box belongs to the innermost container that can
|
|
124
|
+
* hold one, which is why the default here is `false` and there `true`. Only a
|
|
125
|
+
* [styled run](../../../SCHEMA.md#cell-values) has nothing but the run.
|
|
133
126
|
*
|
|
134
127
|
* @type {(style: any, boxed?: boolean) => string}
|
|
135
128
|
*/
|
package/lib/stylepart.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `styles.xml` part, written on every render and carrying two things.
|
|
3
|
-
* Named for the part rather than
|
|
4
|
-
*
|
|
5
|
-
* together often enough that one letter between them would be a trap.
|
|
3
|
+
* Named for the part rather than the vocabulary: `style.js` beside it is this
|
|
4
|
+
* target's reading of what an author declares.
|
|
6
5
|
*
|
|
7
6
|
* `docDefaults` is the base every run and paragraph inherits: the sans
|
|
8
7
|
* generic's own name, the report default's size, and the line spacing the
|
package/package.json
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quario/docx",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Tiny, flow-based Word render target for quario. Real Word tables, a navigable outline, live page-number fields.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"csp",
|
|
7
|
+
"docx",
|
|
8
|
+
"ooxml",
|
|
9
|
+
"quario",
|
|
10
|
+
"word"
|
|
11
|
+
],
|
|
5
12
|
"homepage": "https://getquario.com",
|
|
6
13
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
14
|
"repository": {
|
|
@@ -41,12 +48,12 @@
|
|
|
41
48
|
"devDependencies": {
|
|
42
49
|
"@arethetypeswrong/cli": "^0.18.3",
|
|
43
50
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
44
|
-
"quario": "^0.
|
|
51
|
+
"quario": "^0.10.0",
|
|
45
52
|
"size-limit": "^13.0.3",
|
|
46
53
|
"typescript": "^7.0.2"
|
|
47
54
|
},
|
|
48
55
|
"peerDependencies": {
|
|
49
|
-
"quario": "^0.
|
|
56
|
+
"quario": "^0.10.0"
|
|
50
57
|
},
|
|
51
58
|
"size-limit": [
|
|
52
59
|
{
|