@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/lib/furniture.js CHANGED
@@ -14,16 +14,16 @@
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
- * which is what a band holding nothing but fields and literal text is — means
19
- * one part and no `titlePg`; different means a first-page part, a default one,
20
- * and `titlePg` in the section. The probe assumes a total of two, so a band
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 { describe, drawing, idOf, sizeOf } from "./picture.js";
22
+ import { splits } from "quario";
23
+ import { content, splitBlock } from "./content.js";
24
24
  import { paraProps, under } from "./style.js";
25
- import { splitting } from "./table.js";
26
- import { looking, paragraph, run, runs } from "./text.js";
25
+ import { relationships } from "./rels.js";
26
+ import { paragraph, run } from "./text.js";
27
27
  import { R, W, XML } from "./xml.js";
28
28
 
29
29
  // The two pages every band is read at, in order: the first of two, then a
@@ -40,67 +40,32 @@ const MARKING_LOOK = '<w:color w:val="808080"/><w:sz w:val="14"/>';
40
40
 
41
41
  /**
42
42
  * 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, and the bracket is filled
44
- * exactly as the body fills one `splitting` is the shared rule, so the two
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.
43
+ * paragraph, a split is a borderless one-row table. `splitting` is the shared
44
+ * rule, so this and the body cannot drift on what a split is.
51
45
  *
52
46
  * @type {(events: any[], ctx: Band) => string}
53
47
  */
54
48
  let banded = (events, ctx) => {
55
- /** @type {{ out: string, split: ReturnType<typeof splitting> | null }} */
56
- let state = { out: "", split: null };
57
- for (let e of events) STEPS.get(e.type)?.(state, e, ctx);
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));
64
- };
65
-
66
- /** @type {(state: any) => void} */
67
- let close = (state) => {
68
- state.out += state.split ? state.split.close() : "";
69
- state.split = null;
49
+ let out = "";
50
+ for (let e of splits(events)) out += STEPS[e.type](e, ctx);
51
+ return out;
70
52
  };
71
53
 
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
- }
54
+ /** One item or picture, as the paragraph it becomes in the band.
55
+ * @type {(e: any, ctx: Band) => string} */
56
+ let draw = (e, ctx) => {
79
57
  let style = under(ctx.base, e.style);
80
- state.out += paragraph(paraProps(style), content(e, style, ctx.width, ctx));
58
+ return paragraph(paraProps(style), content(e, style, ctx.width, ctx));
81
59
  };
82
60
 
83
- // What each kind of band event does to the band being built. An event this does
84
- // not know contributes nothing rather than failing, which is what lets a
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
- ]);
61
+ /** @type {(e: any, ctx: Band) => string} */
62
+ let drawSplit = (e, ctx) => splitBlock(e, ctx.width, under(ctx.base, e.style), ctx);
93
63
 
94
- /**
95
- * One item's or picture's content.
96
- *
97
- * @type {(e: any, style: any, column: number, ctx: Band) => string}
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
- };
64
+ // What each kind of band event becomes. An event this does not know
65
+ // contributes nothing rather than failing, which is what lets a consumer
66
+ // written before an event kind existed go on working.
67
+ /** @type {Record<string, (e: any, ctx: Band) => string>} */
68
+ const STEPS = { split: drawSplit, item: draw, image: draw };
104
69
 
105
70
  /**
106
71
  * One reading of a band at one page, behind whatever the target leads the part
@@ -110,16 +75,7 @@ let content = (e, style, column, ctx) => {
110
75
  */
111
76
  let reading = (closure, at, lead, ctx) => lead + (closure ? banded(closure(at), ctx) : "");
112
77
 
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
- */
78
+ /** @typedef {import('./content.js').Ctx} Ctx */
123
79
 
124
80
  /**
125
81
  * A `Ctx` for one sequence: the same render, plus which field gives the length
@@ -130,18 +86,24 @@ let reading = (closure, at, lead, ctx) => lead + (closure ? banded(closure(at),
130
86
  */
131
87
 
132
88
  /**
133
- * The part registry: every distinct part written once, whatever asks for it.
134
- * Sections opened by `reset: "page"` all number themselves the same way, so a
135
- * document of two hundred invoices carries one footer part, not two hundred.
89
+ * The part registry: every distinct part written once. Sections opened by
90
+ * `reset: "page"` all number themselves the same way, so a document of two
91
+ * hundred invoices carries one footer part.
136
92
  *
137
- * @param {any} [page] `report-start.page`, absent when no band is declared.
138
- * @param {string} [marking] The wording, on an unlicensed render.
139
- * @param {(kind: string, target: string) => string} [relate] Registers a part
140
- * and returns the relationship that reaches it; the caller owns the document's
141
- * one allocator, because the body draws pictures too.
142
- * @param {Ctx} [ctx] What a render carries that the events do not.
93
+ * A band's own picture or link is a relationship of *its* part rather than of
94
+ * `document.xml`, so each reading records what it allocated and the part is
95
+ * written beside its own `.rels`. A reader that cannot resolve an `r:id` in the
96
+ * part it reads it from has a broken reference, however well the document
97
+ * resolves the same id.
98
+ *
99
+ * @param {any} page `report-start.page`, absent when no band is declared.
100
+ * @param {string} marking The wording, on an unlicensed render.
101
+ * @param {{ rels: any[], relate: (kind: string, target: string) => string }}
102
+ * registry The document's one relationship registry; the caller owns it,
103
+ * because the body draws pictures and links too.
104
+ * @param {Ctx} ctx What a render carries that the events do not.
143
105
  */
144
- export let furnish = (page, marking, relate = () => "", ctx = EMPTY_CTX) => {
106
+ export let furnish = (page, marking, registry, ctx) => {
145
107
  /** @type {Record<string, string>} */
146
108
  let parts = {};
147
109
  /** @type {Map<string, string>} */
@@ -149,17 +111,34 @@ export let furnish = (page, marking, relate = () => "", ctx = EMPTY_CTX) => {
149
111
  /** @type {Record<string, number>} */
150
112
  let counts = { header: 0, footer: 0 };
151
113
 
152
- // A part for this body, or the one already written for it.
153
- /** @type {(kind: string, body: string) => string} */
154
- let partFor = (kind, body) => {
114
+ // What the reading being taken has allocated: replaced per reading, and
115
+ // closed over by the wrappers below, so each part learns its own.
116
+ /** @type {Set<string>} */
117
+ let used = new Set();
118
+ /** @type {(allocate: any) => any} */
119
+ let recording = (allocate) => (/** @type {any} */ of) => {
120
+ let rId = allocate(of);
121
+ used.add(rId);
122
+ return rId;
123
+ };
124
+
125
+ // A part for this body, or the one already written for it. Identical markup
126
+ // refers to identical ids -- a picture is one part however often it is drawn
127
+ // -- so a part found by its markup is found with its relationships too.
128
+ /** @type {(kind: string, reading: { xml: string, used: Set<string> }) => string} */
129
+ let partFor = (kind, reading) => {
155
130
  let tag = kind === "header" ? "hdr" : "ftr";
156
- let xml = XML + `<w:${tag} ${W}>` + body + `</w:${tag}>`;
131
+ let xml = XML + `<w:${tag} ${W}>` + reading.xml + `</w:${tag}>`;
157
132
  let seen = byXml.get(xml);
158
133
  if (seen) return seen;
159
134
  let target = kind + ++counts[kind] + ".xml";
160
- let rId = relate(kind, target);
135
+ let rId = registry.relate(kind, target);
161
136
  byXml.set(xml, rId);
162
137
  parts["word/" + target] = xml;
138
+ if (reading.used.size)
139
+ parts["word/_rels/" + target + ".rels"] = relationships(
140
+ registry.rels.filter((rel) => reading.used.has(rel.rId)),
141
+ );
163
142
  return rId;
164
143
  };
165
144
 
@@ -167,13 +146,12 @@ export let furnish = (page, marking, relate = () => "", ctx = EMPTY_CTX) => {
167
146
  // reads it as "the first page takes the first-page parts" -- so a band with
168
147
  // no `first` reference shows *nothing* on page one once the other band has
169
148
  // turned it on. Both therefore name a first-page part whenever either needs
170
- // one; the steady one names the part it already wrote, which the registry
171
- // hands back rather than writing twice.
172
- /** @type {(kind: string, pair: string[], titlePg: boolean) => string} */
149
+ // one; the steady one names the part it already wrote.
150
+ /** @type {(kind: string, pair: any[], titlePg: boolean) => string} */
173
151
  let references = (kind, [first, later], titlePg) => {
174
- /** @type {(type: string, body: string) => string} */
175
- let ref = (type, body) =>
176
- `<w:${kind}Reference w:type="${type}" r:id="${partFor(kind, body)}"/>`;
152
+ /** @type {(type: string, reading: any) => string} */
153
+ let ref = (type, reading) =>
154
+ `<w:${kind}Reference w:type="${type}" r:id="${partFor(kind, reading)}"/>`;
177
155
  return (titlePg ? ref("first", first) : "") + ref("default", later);
178
156
  };
179
157
 
@@ -186,7 +164,12 @@ export let furnish = (page, marking, relate = () => "", ctx = EMPTY_CTX) => {
186
164
  * @type {(sequence: string) => { refs: string, titlePg: boolean }}
187
165
  */
188
166
  let refsFor = (sequence) => {
189
- let band = { ...ctx, sequence };
167
+ let band = {
168
+ ...ctx,
169
+ sequence,
170
+ picture: recording(ctx.picture),
171
+ link: recording(ctx.link),
172
+ };
190
173
  let lead = marking ? paragraph("", run(marking, MARKING_LOOK)) : "";
191
174
  let bands = [
192
175
  { kind: "header", closure: page?.header, lead: "" },
@@ -195,9 +178,12 @@ export let furnish = (page, marking, relate = () => "", ctx = EMPTY_CTX) => {
195
178
  .filter((band) => band.closure || band.lead)
196
179
  .map(({ kind, closure, lead: own }) => ({
197
180
  kind,
198
- pair: PROBE.map((at) => reading(closure, at, own, band)),
181
+ pair: PROBE.map((at) => {
182
+ used = new Set();
183
+ return { xml: reading(closure, at, own, band), used };
184
+ }),
199
185
  }));
200
- let titlePg = bands.some(({ pair }) => pair[0] !== pair[1]);
186
+ let titlePg = bands.some(({ pair }) => pair[0].xml !== pair[1].xml);
201
187
  return {
202
188
  refs: bands.map(({ kind, pair }) => references(kind, pair, titlePg)).join(""),
203
189
  titlePg,
@@ -207,8 +193,14 @@ export let furnish = (page, marking, relate = () => "", ctx = EMPTY_CTX) => {
207
193
  return { parts, refsFor };
208
194
  };
209
195
 
210
- /** A render that declares no bands and marks nothing needs none of it. */
211
- export const EMPTY_CTX = { base: null, intl: null, width: 0, picture: () => "" };
196
+ /** A document nothing has furnished yet: the value a render carries until its
197
+ * opening event says what its page bands are. A stream walked from part-way
198
+ * through never replaces it, and still describes a page. */
199
+ /** @type {{ parts: Record<string, string>, refsFor: (sequence: string) => { refs: string, titlePg: boolean } }} */
200
+ export const NO_FURNITURE = {
201
+ parts: {},
202
+ refsFor: () => ({ refs: "", titlePg: false }),
203
+ };
212
204
 
213
205
  /** The namespaces a document referencing furniture declares. */
214
206
  export const DOCUMENT_NS = W + " " + R;
package/lib/index.d.ts CHANGED
@@ -34,3 +34,27 @@ export interface DocxOptions {
34
34
  * bytes described in SCHEMA.md ("The DOCX target").
35
35
  */
36
36
  export function docx(options?: DocxOptions): Target<"docx", Promise<Uint8Array>>;
37
+
38
+ /** What a target makes of a declaration: CONTEXT.md's fate vocabulary. */
39
+ export type Fate = "resolved" | "approximated" | "withdrawn" | "unread";
40
+
41
+ /**
42
+ * What this target makes of each declaration SCHEMA.md's support matrix
43
+ * names. Hand-written and gated against that matrix rather than generated
44
+ * from it. It carries no version of its own: the package that ships it is the
45
+ * version.
46
+ *
47
+ * Keyed by the schema name an author writes — `valign`, `break`, `page.margin`
48
+ * — so a host, a `required` marking and a problem message share one
49
+ * vocabulary. Two matrix rows share the bare word `width`, so each is
50
+ * qualified by what carries it: `column.width` and `slot.width`.
51
+ *
52
+ * It covers the declarations every official target states one fate for. An
53
+ * absent key means the matrix states no single fate for that row — a row that
54
+ * summarises more than one declaration, or that describes a behaviour — never
55
+ * that this target has no answer.
56
+ */
57
+ export const capabilities: {
58
+ readonly target: "docx";
59
+ readonly declarations: Readonly<Record<string, Fate>>;
60
+ };
package/lib/index.js CHANGED
@@ -6,23 +6,19 @@
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 each event kind
10
- * needs live beside it: `body.js` for the paragraphs a report resolves to,
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 { hostMeta, hostOptions, walk } from "quario";
12
+ import { collapse, hostMeta, hostOptions, splits, walk } from "quario";
15
13
  import { bodyOf } from "./body.js";
16
- import { DOCUMENT_NS, furnish } from "./furniture.js";
14
+ import { DOCUMENT_NS, furnish, NO_FURNITURE } from "./furniture.js";
17
15
  import { STYLES } from "./stylepart.js";
18
16
  import { DOCUMENT_PAGES, SECTION_PAGES } from "./text.js";
19
17
  import { geometry, pageBox, twips } from "./page.js";
20
18
  import { pack } from "./pack.js";
19
+ import { overrides, packageRels, relationships } from "./rels.js";
21
20
  import { W, XML, esc } from "./xml.js";
22
21
 
23
- const RELS = 'xmlns="http://schemas.openxmlformats.org/package/2006/relationships"';
24
- const OFFICE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
25
- const PACKAGE = "http://schemas.openxmlformats.org/package/2006/relationships";
26
22
  const WML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
27
23
 
28
24
  // The distance from the paper's edge to a header or a footer, in twips: Word's
@@ -77,9 +73,9 @@ let core = (meta) =>
77
73
  * refs: string, titlePg: boolean }} Section */
78
74
 
79
75
  // The parts every document relates to, in relationship order. A furniture part
80
- // numbers from after them, and both the content types and the relationships
81
- // below are one map over the pair, so a part cannot reach the package through
82
- // one and not the other.
76
+ // numbers from after them, and `rels.js` maps the whole list to both the
77
+ // content types and the relationships, so a part cannot reach the package
78
+ // through one and not the other.
83
79
  // A picture's bytes are typed by extension rather than by part name, the way
84
80
  // `rels` and `xml` already are: one `Default` covers every image of that kind.
85
81
  const IMAGE_TYPES = [
@@ -88,20 +84,10 @@ const IMAGE_TYPES = [
88
84
  ];
89
85
 
90
86
  const FIXED = [
91
- { path: "word/settings.xml", target: "settings.xml", kind: "settings", rId: "rId1" },
92
- { path: "word/styles.xml", target: "styles.xml", kind: "styles", rId: "rId2" },
87
+ { target: "settings.xml", kind: "settings", rId: "rId1" },
88
+ { target: "styles.xml", kind: "styles", rId: "rId2" },
93
89
  ];
94
90
 
95
- // The content type of one furniture part, and the relationship that reaches
96
- // it. Both are keyed off the same `{ path, kind, rId }` the registry hands
97
- // back, so a part cannot reach the package through one and not the other.
98
- /** @type {(part: { path: string, kind: string }) => string} */
99
- let override = ({ path, kind }) =>
100
- `<Override PartName="/${path}" ContentType="${WML}.${kind}+xml"/>`;
101
- /** @type {(part: { target: string, kind: string, rId: string }) => string} */
102
- let related = ({ target, kind, rId }) =>
103
- `<Relationship Id="${rId}" Type="${OFFICE}/${kind}" Target="${target}"/>`;
104
-
105
91
  /**
106
92
  * The parts of the package. The floor is what Word opens: the content types,
107
93
  * the package relationships and `document.xml` — plus `settings.xml`, which is
@@ -118,7 +104,7 @@ let parts = (body, meta, furniture, rels) => ({
118
104
  '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
119
105
  '<Default Extension="xml" ContentType="application/xml"/>' +
120
106
  `<Override PartName="/word/document.xml" ContentType="${WML}.document.main+xml"/>` +
121
- rels.map(override).join("") +
107
+ overrides(rels) +
122
108
  IMAGE_TYPES.filter(([ext]) => rels.some((r) => r.target.endsWith("." + ext)))
123
109
  .map(([ext, type]) => `<Default Extension="${ext}" ContentType="image/${type}"/>`)
124
110
  .join("") +
@@ -126,17 +112,9 @@ let parts = (body, meta, furniture, rels) => ({
126
112
  ? '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>'
127
113
  : "") +
128
114
  "</Types>",
129
- "_rels/.rels":
130
- XML +
131
- `<Relationships ${RELS}>` +
132
- `<Relationship Id="rId1" Type="${OFFICE}/officeDocument" Target="word/document.xml"/>` +
133
- (meta
134
- ? `<Relationship Id="rId2" Type="${PACKAGE}/metadata/core-properties" Target="docProps/core.xml"/>`
135
- : "") +
136
- "</Relationships>",
115
+ "_rels/.rels": packageRels(meta),
137
116
  "word/document.xml": XML + `<w:document ${DOCUMENT_NS}><w:body>${body}</w:body></w:document>`,
138
- "word/_rels/document.xml.rels":
139
- XML + `<Relationships ${RELS}>` + rels.map(related).join("") + "</Relationships>",
117
+ "word/_rels/document.xml.rels": relationships(rels),
140
118
  "word/settings.xml":
141
119
  XML +
142
120
  `<w:settings ${W}><w:compat>` +
@@ -153,8 +131,6 @@ let parts = (body, meta, furniture, rels) => ({
153
131
  * bytes described in SCHEMA.md ("The DOCX target"). Options are taken and
154
132
  * validated at the factory call.
155
133
  *
156
- * The options are described once, in the hand-written public declarations,
157
- * and read back here -- a second copy in JSDoc is a copy that drifts.
158
134
  * @import { DocxOptions } from './index.d.ts'
159
135
  *
160
136
  * @param {DocxOptions} [options] Host controls (see SCHEMA.md, "The DOCX target").
@@ -176,33 +152,45 @@ export function docx(options) {
176
152
  // The sections are written after the walk, so the opening event is taken
177
153
  // in its handler rather than peeked ahead of the driver: the margin a
178
154
  // document may declare instead of the host is settled by the time any
179
- // second event is pulled, and the stream reaches `walk` unwrapped. The
180
- // walk is what spends the render's budget and breathes for the host
181
- // meanwhile.
155
+ // second event is pulled, and the stream reaches `walk` unwrapped.
182
156
  let geo = geometry(page);
183
157
  // Every relationship the document carries, in one place: the parts it
184
158
  // always has, then whatever the furniture and the body ask for. One
185
159
  // allocator, because a page band and the body both draw pictures and two
186
160
  // would hand out the same number twice.
187
- /** @type {{ path: string, target: string, kind: string, rId: string }[]} */
161
+ /** @type {import('./rels.js').Rel[]} */
188
162
  let rels = [...FIXED];
189
163
  /** @type {Record<string, Uint8Array>} */
190
164
  let media = {};
191
165
  /** @type {(kind: string, target: string) => string} */
192
166
  let relate = (kind, target) => {
193
167
  let rId = "rId" + (rels.length + 1);
194
- rels.push({ path: "word/" + target, target, kind, rId });
168
+ rels.push({ target, kind, rId });
195
169
  return rId;
196
170
  };
197
- // One part per distinct picture. Keyed on the bytes themselves: the
171
+ // The one registry, handed to whatever writes a part: the furniture needs
172
+ // the records and not only the ids, because a part's own relationships are
173
+ // the ones its markup refers to.
174
+ let registry = { rels, relate };
175
+ // One part per distinct picture, keyed on the bytes themselves: the
198
176
  // page-band probe reads each band twice and every section reads it again,
199
- // and a logo that arrived by reading a field is the same array each time,
200
- // so the part -- and the header XML naming it -- stays identical and the
201
- // furniture's own dedup still collapses the sections. A `source` that
202
- // *computes* fresh bytes per call writes a part per call, which is
203
- // wasteful and still correct.
177
+ // so the part stays identical and the furniture's dedup still collapses
178
+ // the sections. A `source` computing fresh bytes per call writes a part
179
+ // per call, which is wasteful and still correct.
204
180
  /** @type {Map<Uint8Array, string>} */
205
181
  let drawn = new Map();
182
+ // One relationship per distinct URL, for the same reason: a report linking
183
+ // one place from every row would otherwise relate to it once per row.
184
+ /** @type {Map<string, string>} */
185
+ let linked = new Map();
186
+ /** @type {(url: string) => string} */
187
+ let link = (url) => {
188
+ let seen = linked.get(url);
189
+ if (seen) return seen;
190
+ let rId = relate("hyperlink", url);
191
+ linked.set(url, rId);
192
+ return rId;
193
+ };
206
194
  /** @type {(event: any) => string} */
207
195
  let picture = (event) => {
208
196
  let seen = drawn.get(event.bytes);
@@ -213,7 +201,7 @@ export function docx(options) {
213
201
  drawn.set(event.bytes, rId);
214
202
  return rId;
215
203
  };
216
- let furniture = furnish();
204
+ let furniture = NO_FURNITURE;
217
205
 
218
206
  /** @type {(kind: any) => Section} */
219
207
  let section = (kind) => ({
@@ -221,20 +209,21 @@ export function docx(options) {
221
209
  ...furniture.refsFor(kind.reset ? SECTION_PAGES : DOCUMENT_PAGES),
222
210
  });
223
211
  let body = bodyOf(section);
224
- await walk(stream(data), {
212
+ await walk(collapse(splits(stream(data))), {
225
213
  ...body.handlers,
226
214
  "report-start": (event) => {
227
215
  geo = geometry(page, event);
228
216
  // What a render carries that the events do not, settled once and handed
229
217
  // to both writers: a fifth instance field must be read here and nowhere
230
- // else (`furniture.js`, `Ctx`).
218
+ // else (`content.js`, `Ctx`).
231
219
  let ctx = {
232
220
  base: event.style || null,
233
221
  intl: { locale: event.locale, currency: event.currency, timeZone: event.timeZone },
234
222
  width: geo.width - 2 * geo.margin,
235
223
  picture,
224
+ link,
236
225
  };
237
- furniture = furnish(event.page, event.marking, relate, ctx);
226
+ furniture = furnish(event.page, event.marking ?? "", registry, ctx);
238
227
  body.start(ctx, event);
239
228
  },
240
229
  });
@@ -254,3 +243,53 @@ export function docx(options) {
254
243
  };
255
244
  return { name: "docx", compile };
256
245
  }
246
+
247
+ /**
248
+ * What this target makes of each declaration the support matrix names: one of
249
+ * the [fates](../../../CONTEXT.md#declaration-and-resolution) — `resolved`,
250
+ * `approximated`, `withdrawn` or `unread` — so a host can ask before it
251
+ * renders rather than reading the prose.
252
+ *
253
+ * **Hand-written, and gated rather than generated.** SCHEMA.md's matrix is the
254
+ * description a person maintains and this is the data a host reads; the repo's
255
+ * `test/capabilities.test.js` holds the two equal, which is the same bargain
256
+ * ADR 0014 makes for the band-role table. It carries no version of its own:
257
+ * the package that ships it is the version, and a second copy could only drift
258
+ * from it.
259
+ *
260
+ * Keyed by the **schema name** an author writes, so a host, a `required`
261
+ * marking and a problem message all index by one vocabulary. Two rows share
262
+ * the bare word `width`, so each is qualified by what carries it.
263
+ *
264
+ * It covers the declarations every official target states one fate for. A
265
+ * matrix row that summarises more than one declaration, or that describes a
266
+ * behaviour rather than a declaration, states no single fate and is absent
267
+ * here — an absent key means the spec does not answer, never that this target
268
+ * has no answer.
269
+ */
270
+ export const capabilities = Object.freeze({
271
+ target: "docx",
272
+ declarations: Object.freeze({
273
+ "page.header": "resolved",
274
+ "page.footer": "resolved",
275
+ break: "resolved",
276
+ reset: "resolved",
277
+ "page.margin": "resolved",
278
+ "header.height": "withdrawn",
279
+ groups: "resolved",
280
+ label: "approximated",
281
+ collapsed: "resolved",
282
+ "column.width": "approximated",
283
+ "slot.width": "resolved",
284
+ span: "resolved",
285
+ uppercase: "resolved",
286
+ href: "resolved",
287
+ spaceBefore: "resolved",
288
+ spaceAfter: "resolved",
289
+ format: "resolved",
290
+ family: "resolved",
291
+ valign: "resolved",
292
+ runs: "resolved",
293
+ alt: "resolved",
294
+ }),
295
+ });
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. The asynchronous family would
7
- * reach for a worker built from a string, and the suites run under
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
- * ADR 0039 gates the page-size table to one home, and `test/page-sizes.test.js`
9
- * is that gate: a second table is a size that means one thing here and another
10
- * there. This target is the one consumer that cannot reach the layout's — a
11
- * flow target measures nothing, and taking `@quario/layout` would install the
12
- * pagination engine and its font metrics for a two-entry table (ADR 0014: a
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
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 so a
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 a counter per writer hands the
85
- * same number to a page band's picture and the body's and stable across the
86
- * two readings of a band, where a running count would move and make every band
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
  */