@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 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: "page"` starts the instance on a fresh page.
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.** `break: "page"` is `pageBreakBefore` on the instance's first
15
- * paragraph, skipped where the instance also declared `reset`, whose section has
16
- * already turned the page, and where it has nothing before it at all.
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 — SCHEMA.md, "Style declarations". A `false` in an inner layer is a
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, splitting, tbl } from "./table.js";
37
- import { describe, drawing, idOf, sizeOf } from "./picture.js";
38
- import { EMPTY_CTX } from "./furniture.js";
39
- import { looking, paragraph, runs } from "./text.js";
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. ADR 0014: a target supplies defaults where
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, size: 14 },
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
- // The heading a group instance's *first* header item is still owed, set when
88
- // the instance opens and taken by whichever item claims it. One string rather
89
- // than a depth and a flag: the two only ever moved together.
90
- let heading = "";
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
- * `furniture.js`'s `Ctx`, so the two writers describe a render one way.
95
- * @type {import("./furniture.js").Ctx} */
96
- let ctx = EMPTY_CTX;
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, and the depths of the instances that
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
- /** @type {number[]} */
135
- let columned = [];
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("./furniture.js").Ctx, event: any) => void} */
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) ? ROLES[event.role] : null),
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) && blocks.push({ props: "", inner: "" });
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
- heading = `<w:pStyle w:val="${headingAt(event.depth)}"/>`;
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
- /** @type {(event: any) => void} */
214
- let groupEnd = (event) => {
215
- if (columned.at(-1) !== event.depth) return;
216
- columned.pop();
217
- reopen({ continuous: true, columns: columns });
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
- * The paragraph properties an item's role earns: the heading its instance is
222
- * owed, on a group instance's *first* header item, and nothing anywhere else.
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 claimHeading = (role) => {
227
- if (role !== "group-header") return "";
228
- let owes = heading;
229
- heading = "";
230
- return owes;
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
- push(claimHeading(event.role), style, contentOf(event, style));
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 splitStart = (event) => {
267
- split = splitting(event.slots, columnWidth(), styleOf(event));
299
+ let splitOf = (event) => {
300
+ block(splitBlock(event, columnWidth(), styleOf(event), ctx));
268
301
  };
269
302
 
270
- let splitEnd = () => {
271
- // oxlint-disable-next-line no-unused-expressions
272
- split && block(split.close());
273
- split = null;
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
- "split-start": splitStart,
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
- blocks
332
- .map(
333
- (b) =>
334
- /** @type {any} */ (b).raw
335
- ? b.inner
336
- : paragraph(b.props + (b.closes ? sectPr(b.closes) : ""), b.inner),
337
- )
338
- .join("") + sectPr(sections.at(-1)),
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
+ };