@quario/docx 0.1.1 → 0.3.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 +39 -0
- package/README.md +7 -3
- package/lib/body.js +129 -95
- package/lib/content.js +51 -0
- package/lib/furniture.js +87 -95
- package/lib/index.d.ts +24 -0
- package/lib/index.js +91 -52
- package/lib/pack.js +2 -5
- package/lib/page.js +5 -8
- package/lib/picture.js +4 -6
- package/lib/rels.js +66 -0
- package/lib/style.js +12 -19
- package/lib/stylepart.js +8 -7
- package/lib/table.js +8 -7
- package/lib/text.js +65 -13
- package/package.json +15 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# @quario/docx
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **A `capabilities` descriptor.** The target exports what it makes of each declaration the spec's support matrix names — `resolved`, `approximated`, `withdrawn` or `unread` — beside its factory, so a host can ask before it renders rather than reading the prose. It is keyed by the schema name an author writes (`valign`, `break`, `page.margin`, and `column.width` / `slot.width` where one word carries two declarations), hand-written and gated against that matrix. It carries no version of its own: the package that ships it is the version.
|
|
8
|
+
- **A run that carries an `href` is a Word hyperlink.** A URL becomes a relationship of the part the run lands in, marked as leaving the package, and one relationship serves every run pointing at the same place. An `href` beginning `#` becomes a `w:anchor`, and the first instance of the group carrying that `label` writes the bookmark it names, beside the heading that item already takes. A run whose text is empty writes no link and relates to nothing. The support matrix recorded this target as withdrawing `href`, and it now resolves it.
|
|
9
|
+
|
|
10
|
+
Two things a page band's own part had wrong are fixed with it. A header or footer now carries its **own relationships part**, so a picture or a link inside a band resolves in the part that refers to it rather than only in `document.xml`'s. And `[Content_Types].xml` no longer writes an `Override` for a relationship that names no WordprocessingML part. That entry typed a picture's bytes as a document part, which said nothing true about them.
|
|
11
|
+
|
|
12
|
+
- **`negative` and `zero` reach the runs.** The target presents through the engine's `format()`, so a cell declaring either reads the same in Word as on the page.
|
|
13
|
+
- **A collapsed group does not draw its content.** An instance whose group declares `collapsed` renders as its header and footer, with the rows between them left out — a page has nowhere to put a row a reader could open. Aggregates are unchanged, so a footer still totals them.
|
|
14
|
+
- **The report header scales with the report default.** A report-header run is written at 1.4× the report default's size rather than a fixed 14, rounded to Word's half-points. A document declaring no size keeps its bytes, because 1.4× the base 10 is the same 14. A document declaring `"size": 20` now renders its report header at 28 where it rendered at 14, smaller than its own body.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- quario@0.11.0
|
|
20
|
+
|
|
21
|
+
## 0.2.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- **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.
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
**`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.
|
|
30
|
+
|
|
31
|
+
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`.
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- Updated dependencies
|
|
40
|
+
- quario@0.10.0
|
|
41
|
+
|
|
3
42
|
## 0.1.1
|
|
4
43
|
|
|
5
44
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ that depends on a style lookup is a look three readers may resolve three ways.
|
|
|
69
69
|
Page bands become a section's header and footer. A bare `{{ page.number }}` or `{{ page.total }}`
|
|
70
70
|
becomes a live `PAGE` / `NUMPAGES` field the reader's own application recomputes. Anything computed
|
|
71
71
|
from them freezes at the value the render saw. A group's `reset: "page"` opens a section that
|
|
72
|
-
restarts the numbering, and `break
|
|
72
|
+
restarts the numbering, and a `break` turns a page at every boundary its position names.
|
|
73
73
|
|
|
74
74
|
## Determinism
|
|
75
75
|
|
|
@@ -77,6 +77,12 @@ Two renders of one report are byte-identical, on **every supported runtime**. `f
|
|
|
77
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.
|
|
@@ -96,5 +102,3 @@ Pass your license key once, on the instance. quario verifies it offline:
|
|
|
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,27 +32,40 @@
|
|
|
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
|
-
import { headingAt } from "./stylepart.js";
|
|
37
|
+
import { BASE, headingAt } from "./stylepart.js";
|
|
35
38
|
import { paraProps, under } from "./style.js";
|
|
36
|
-
import { grid, record,
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
|
|
39
|
+
import { grid, record, tbl } from "./table.js";
|
|
40
|
+
import { content, splitBlock } from "./content.js";
|
|
41
|
+
import { bookmark, paragraph } from "./text.js";
|
|
42
|
+
|
|
43
|
+
// The page break a paragraph wears, spelled once: `push` folds it into a
|
|
44
|
+
// paragraph's own properties, and `carrier` gives it one where no paragraph is
|
|
45
|
+
// coming to take it.
|
|
46
|
+
const BREAK = "<w:pageBreakBefore/>";
|
|
40
47
|
|
|
41
48
|
// 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.
|
|
49
|
+
// on a report that declared nothing (ADR 0014: a document is final on open).
|
|
44
50
|
// `test/omakase-defaults.test.js` holds this copy and the other two in
|
|
45
51
|
// agreement; it is not shared code, and that is the ADR's point.
|
|
46
52
|
/** @type {Record<string, any>} */
|
|
47
53
|
const ROLES = {
|
|
48
|
-
"report-header": { bold: true,
|
|
54
|
+
"report-header": { bold: true, scale: 1.4 },
|
|
49
55
|
"group-header": { bold: true },
|
|
50
56
|
};
|
|
51
57
|
|
|
58
|
+
// A role's default as a style block: `scale` is a factor over the report
|
|
59
|
+
// default's size, or over the base the style part writes when the report
|
|
60
|
+
// declared none, so a larger document keeps its headline larger.
|
|
61
|
+
/** @type {(role: string, base: any) => any} */
|
|
62
|
+
const roleStyle = (role, base) => {
|
|
63
|
+
let { scale, ...rest } = ROLES[role];
|
|
64
|
+
// The engine admits only a positive number, so absent is the one other case.
|
|
65
|
+
let size = base?.size || BASE;
|
|
66
|
+
return scale ? { ...rest, size: Math.round(scale * size * 100) / 100 } : rest;
|
|
67
|
+
};
|
|
68
|
+
|
|
52
69
|
// The cell padding this target supplies on a side the author did not name: 6 pt
|
|
53
70
|
// across, 2 pt down, the layout's own numbers. A named `0` beats it, which is
|
|
54
71
|
// what makes the declaration a declaration (`docs/adr/0040`).
|
|
@@ -84,22 +101,29 @@ export let bodyOf = (section) => {
|
|
|
84
101
|
// paragraph carries one `spacing` element and the author may have declared
|
|
85
102
|
// it -- so the gap layers under their own `spaceBefore`, which then wins.
|
|
86
103
|
let owed = { broken: false, gapped: false };
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
104
|
+
// What a group instance's *first* header item is still owed, set when the
|
|
105
|
+
// instance opens and taken by whichever item claims it: the heading its depth
|
|
106
|
+
// earns, and the bookmark its `label` is the destination of. One record rather
|
|
107
|
+
// than a depth, a flag and a name: they only ever move together.
|
|
108
|
+
/** @type {{ props: string, mark: { id: number, name: string } | null }} */
|
|
109
|
+
let owes = { props: "", mark: null };
|
|
110
|
+
// The labels a destination has already been written for, and how many there
|
|
111
|
+
// are. A label two instances share is one destination and the first wins
|
|
112
|
+
// (SCHEMA.md, "Style declarations"), and Word's bookmark id is an integer the
|
|
113
|
+
// document uses once.
|
|
114
|
+
/** @type {Set<string>} */
|
|
115
|
+
let labelled = new Set();
|
|
91
116
|
/** What a render carries that the events do not: the report default, the
|
|
92
117
|
* instance's `format` configuration, the text column and the picture
|
|
93
118
|
* registry. Settled once, on the opening event, and read-only after —
|
|
94
|
-
* `
|
|
95
|
-
*
|
|
96
|
-
|
|
119
|
+
* `content.js`'s `Ctx`, so the two writers describe a render one way.
|
|
120
|
+
* Nothing reads it before then, which is why the picture registry the real
|
|
121
|
+
* one carries is absent here rather than stubbed.
|
|
122
|
+
* @type {import("./content.js").Ctx} */
|
|
123
|
+
let ctx = /** @type {any} */ ({ base: null, intl: null, width: 0 });
|
|
97
124
|
/** The table being filled, if any: its grid, and the rows written so far. */
|
|
98
125
|
/** @type {{ widths: number[], rows: string } | null} */
|
|
99
126
|
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
127
|
|
|
104
128
|
// `CT_PPr` is a sequence and not a bag: `pStyle` is its first child,
|
|
105
129
|
// `pageBreakBefore` its fourth, and everything `paraProps` writes comes after
|
|
@@ -107,17 +131,24 @@ export let bodyOf = (section) => {
|
|
|
107
131
|
/** @type {(props: string, style: any, inner: string) => void} */
|
|
108
132
|
let push = (props, style, inner) => {
|
|
109
133
|
let look = owed.gapped ? under({ spaceBefore: GAP }, style) : style;
|
|
110
|
-
blocks.push({
|
|
111
|
-
props: props + (owed.broken ? "<w:pageBreakBefore/>" : "") + paraProps(look),
|
|
112
|
-
inner,
|
|
113
|
-
});
|
|
134
|
+
blocks.push({ props: props + (owed.broken ? BREAK : "") + paraProps(look), inner });
|
|
114
135
|
owed = { broken: false, gapped: false };
|
|
115
136
|
};
|
|
116
137
|
|
|
138
|
+
/** An empty paragraph, written where something is owed and no paragraph is
|
|
139
|
+
* coming to take it: a section break whose last block is a table, and a page
|
|
140
|
+
* break a table would otherwise swallow.
|
|
141
|
+
* @type {(props?: string) => void} */
|
|
142
|
+
let carrier = (props = "") => void blocks.push({ props, inner: "" });
|
|
143
|
+
|
|
117
144
|
/** A block that is not a paragraph -- a table -- which carries no properties
|
|
118
|
-
* and so cannot take what the next paragraph is owed.
|
|
145
|
+
* and so cannot take what the next paragraph is owed. A page break takes a
|
|
146
|
+
* carrier of its own first, or the table would swallow it; the band gap
|
|
147
|
+
* does not, because the next paragraph still takes it.
|
|
119
148
|
* @type {(inner: string) => void} */
|
|
120
149
|
let block = (inner) => {
|
|
150
|
+
if (owed.broken) carrier(BREAK);
|
|
151
|
+
owed.broken = false;
|
|
121
152
|
blocks.push({ props: "", inner, raw: true });
|
|
122
153
|
};
|
|
123
154
|
|
|
@@ -128,15 +159,17 @@ export let bodyOf = (section) => {
|
|
|
128
159
|
sections.push(section(kind));
|
|
129
160
|
};
|
|
130
161
|
|
|
131
|
-
/** The report's own page-column count
|
|
132
|
-
* opened a columned section, so `group-end` knows whose to close. */
|
|
162
|
+
/** The report's own page-column count. */
|
|
133
163
|
let columns = 1;
|
|
134
|
-
/**
|
|
135
|
-
|
|
164
|
+
/** One frame per open instance, innermost last: the columned section this
|
|
165
|
+
* instance opened, if any, and its trailing page break. The brackets pair,
|
|
166
|
+
* so `group-end` pops the frame `group-start` pushed.
|
|
167
|
+
* @type {{ columns: number, trailing: boolean }[]} */
|
|
168
|
+
let instances = [];
|
|
136
169
|
/** @type {(value: any) => number} */
|
|
137
170
|
let counted = (value) => (Number.isInteger(value) && value > 1 ? value : 0);
|
|
138
171
|
|
|
139
|
-
/** @type {(own: import("./
|
|
172
|
+
/** @type {(own: import("./content.js").Ctx, event: any) => void} */
|
|
140
173
|
let start = (own, event) => {
|
|
141
174
|
ctx = own;
|
|
142
175
|
columns = counted(event.columns) || 1;
|
|
@@ -147,7 +180,7 @@ export let bodyOf = (section) => {
|
|
|
147
180
|
/** @type {(event: any) => any} */
|
|
148
181
|
let styleOf = (event) =>
|
|
149
182
|
under(
|
|
150
|
-
under(ctx.base, Object.hasOwn(ROLES, event.role) ?
|
|
183
|
+
under(ctx.base, Object.hasOwn(ROLES, event.role) ? roleStyle(event.role, ctx.base) : null),
|
|
151
184
|
event.style,
|
|
152
185
|
);
|
|
153
186
|
|
|
@@ -165,7 +198,7 @@ export let bodyOf = (section) => {
|
|
|
165
198
|
let closeSection = () => {
|
|
166
199
|
let last = blocks.at(-1);
|
|
167
200
|
// oxlint-disable-next-line no-unused-expressions
|
|
168
|
-
(blocks.length === sectionStart || last?.raw) &&
|
|
201
|
+
(blocks.length === sectionStart || last?.raw) && carrier();
|
|
169
202
|
/** @type {any} */ (blocks.at(-1)).closes = sections.at(-1);
|
|
170
203
|
};
|
|
171
204
|
|
|
@@ -178,18 +211,17 @@ export let bodyOf = (section) => {
|
|
|
178
211
|
* @type {(event: any) => void}
|
|
179
212
|
*/
|
|
180
213
|
let group = (event) => {
|
|
181
|
-
|
|
214
|
+
owes = { props: `<w:pStyle w:val="${headingAt(event.depth)}"/>`, mark: marking(event.label) };
|
|
182
215
|
// A structural half-line before every instance, dropped where the document
|
|
183
216
|
// has not started: groups read as blocks without authored margins.
|
|
184
217
|
// oxlint-disable-next-line no-unused-expressions
|
|
185
218
|
owed.gapped ||= blocks.length > 0;
|
|
219
|
+
// Its own count is what `group-end` closes; a section that merely inherits
|
|
220
|
+
// the report's is not one this instance opened.
|
|
186
221
|
let own = counted(event.columns);
|
|
222
|
+
instances.push({ columns: own, trailing: event.breakAfter === "page" });
|
|
187
223
|
let kind = sectionFor(event.reset === "page", own);
|
|
188
224
|
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
225
|
reopen(kind);
|
|
194
226
|
};
|
|
195
227
|
|
|
@@ -210,38 +242,42 @@ export let bodyOf = (section) => {
|
|
|
210
242
|
open(kind);
|
|
211
243
|
};
|
|
212
244
|
|
|
213
|
-
/**
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
245
|
+
/** What an instance's close owes, in the order the document takes them: the
|
|
246
|
+
* trailing page break the stream stated when it opened, then the columned
|
|
247
|
+
* section it opened.
|
|
248
|
+
* @type {(frame: { columns: number, trailing: boolean }) => void} */
|
|
249
|
+
let closeInstance = (frame) => {
|
|
250
|
+
if (frame.trailing) owed.broken ||= blocks.length > 0;
|
|
251
|
+
if (frame.columns) reopen({ continuous: true, columns });
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/** @type {() => void} */
|
|
255
|
+
let groupEnd = () => {
|
|
256
|
+
let frame = instances.pop();
|
|
257
|
+
if (frame) closeInstance(frame);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/** The destination an instance's label is, or none where it declared none or
|
|
261
|
+
* an earlier instance already carries it.
|
|
262
|
+
* @type {(label: any) => { id: number, name: string } | null} */
|
|
263
|
+
let marking = (label) => {
|
|
264
|
+
if (!label || labelled.has(label)) return null;
|
|
265
|
+
labelled.add(label);
|
|
266
|
+
return { id: labelled.size, name: label };
|
|
218
267
|
};
|
|
219
268
|
|
|
220
269
|
/**
|
|
221
|
-
*
|
|
222
|
-
* owed, on a group instance's *first* header item, and nothing anywhere
|
|
270
|
+
* What an item's role earns from its instance: the heading and the bookmark
|
|
271
|
+
* it is owed, on a group instance's *first* header item, and nothing anywhere
|
|
272
|
+
* else.
|
|
223
273
|
*
|
|
224
|
-
* @type {(role: string) => string}
|
|
274
|
+
* @type {(role: string) => { props: string, mark: { id: number, name: string } | null }}
|
|
225
275
|
*/
|
|
226
|
-
let
|
|
227
|
-
if (role !== "group-header") return "";
|
|
228
|
-
let
|
|
229
|
-
|
|
230
|
-
return
|
|
231
|
-
};
|
|
232
|
-
|
|
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));
|
|
276
|
+
let claim = (role) => {
|
|
277
|
+
if (role !== "group-header") return { props: "", mark: null };
|
|
278
|
+
let owed = owes;
|
|
279
|
+
owes = { props: "", mark: null };
|
|
280
|
+
return owed;
|
|
245
281
|
};
|
|
246
282
|
|
|
247
283
|
/** How wide the body is where it is being written: the text column, divided
|
|
@@ -254,29 +290,22 @@ export let bodyOf = (section) => {
|
|
|
254
290
|
|
|
255
291
|
/** @type {(event: any) => void} */
|
|
256
292
|
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
293
|
let style = styleOf(event);
|
|
262
|
-
|
|
294
|
+
let { props, mark } = claim(event.role);
|
|
295
|
+
push(props, style, bookmark(mark, content(event, style, columnWidth(), ctx)));
|
|
263
296
|
};
|
|
264
297
|
|
|
265
298
|
/** @type {(event: any) => void} */
|
|
266
|
-
let
|
|
267
|
-
|
|
299
|
+
let splitOf = (event) => {
|
|
300
|
+
block(splitBlock(event, columnWidth(), styleOf(event), ctx));
|
|
268
301
|
};
|
|
269
302
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
/** What a table's cells are written with. `intl` is settled on the opening
|
|
277
|
-
* event, so it is read here rather than captured at compile.
|
|
278
|
-
* @type {() => { pad: Record<string, number>, intl: any }} */
|
|
279
|
-
let ruled = () => ({ pad: PAD, intl: ctx.intl });
|
|
303
|
+
/** What a table's cells are written with. `intl` and the link registry are
|
|
304
|
+
* settled on the opening event, so they are read here rather than captured
|
|
305
|
+
* at compile.
|
|
306
|
+
* @type {() => { pad: Record<string, number>, intl: any,
|
|
307
|
+
* link: (url: string) => string }} */
|
|
308
|
+
let ruled = () => ({ pad: PAD, intl: ctx.intl, link: ctx.link });
|
|
280
309
|
|
|
281
310
|
/** @type {(event: any) => void} */
|
|
282
311
|
let tableStart = (event) => {
|
|
@@ -319,22 +348,27 @@ export let bodyOf = (section) => {
|
|
|
319
348
|
"group-end": groupEnd,
|
|
320
349
|
item: itemOf,
|
|
321
350
|
image: itemOf,
|
|
322
|
-
|
|
323
|
-
"split-end": splitEnd,
|
|
351
|
+
split: splitOf,
|
|
324
352
|
"table-start": tableStart,
|
|
325
353
|
row,
|
|
326
354
|
"total-row": row,
|
|
327
355
|
"table-end": tableEnd,
|
|
328
356
|
},
|
|
329
357
|
/** @type {(sectPr: (section: any) => string) => string} */
|
|
330
|
-
xml: (sectPr) =>
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
358
|
+
xml: (sectPr) => {
|
|
359
|
+
// A page break the document ends owing is dropped, and `owed` is simply
|
|
360
|
+
// left set: a break turns the page for what follows it, and nothing
|
|
361
|
+
// follows. Word, the PDF and a printed fragment agree on that.
|
|
362
|
+
return (
|
|
363
|
+
blocks
|
|
364
|
+
.map(
|
|
365
|
+
(b) =>
|
|
366
|
+
/** @type {any} */ (b).raw
|
|
367
|
+
? b.inner
|
|
368
|
+
: paragraph(b.props + (b.closes ? sectPr(b.closes) : ""), b.inner),
|
|
369
|
+
)
|
|
370
|
+
.join("") + sectPr(sections.at(-1))
|
|
371
|
+
);
|
|
372
|
+
},
|
|
339
373
|
};
|
|
340
374
|
};
|
package/lib/content.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
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 registries a picture's bytes and a link's
|
|
16
|
+
* URL become relationships in, and -- for a page band only -- which field gives
|
|
17
|
+
* the length of the sequence its section numbers. Both writers hand one of
|
|
18
|
+
* these down, so they describe a render one way.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {{ base: any, intl: any, width: number,
|
|
21
|
+
* picture: (event: any) => string, link: (url: string) => string,
|
|
22
|
+
* sequence?: string }} Ctx
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* One item's or picture's content: the runs a text item's tokens become, or
|
|
27
|
+
* the inline drawing a picture is. `column` is what caps a picture: the text
|
|
28
|
+
* column, or the slot holding it.
|
|
29
|
+
*
|
|
30
|
+
* @type {(event: any, style: any, column: number, ctx: Ctx) => string}
|
|
31
|
+
*/
|
|
32
|
+
export let content = (event, style, column, ctx) => {
|
|
33
|
+
if (event.type !== "image") return runs(event.tokens, looking(style, ctx));
|
|
34
|
+
let rId = ctx.picture(event);
|
|
35
|
+
return drawing(event, idOf(rId), rId, describe(event), sizeOf(event, column));
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One split, folded, as the borderless one-row table it becomes: a slot per
|
|
40
|
+
* cell, each slot's own style layered over the split's, which is `style`.
|
|
41
|
+
*
|
|
42
|
+
* @type {(event: any, width: number, style: any, ctx: Ctx) => string}
|
|
43
|
+
*/
|
|
44
|
+
export let splitBlock = (event, width, style, ctx) => {
|
|
45
|
+
let split = splitting(event.slots, width, style);
|
|
46
|
+
for (let slot of event.items) {
|
|
47
|
+
let own = under(split.style, slot.style);
|
|
48
|
+
split.slot(own, (column) => content(slot, own, column, ctx));
|
|
49
|
+
}
|
|
50
|
+
return split.close();
|
|
51
|
+
};
|