@docx4j/core-ts 0.3.2 → 0.5.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.
Files changed (72) hide show
  1. package/README.md +8 -3
  2. package/dist/html/convert.d.mts +116 -0
  3. package/dist/html/convert.mjs +565 -0
  4. package/dist/html/elements.d.mts +19 -0
  5. package/dist/html/elements.mjs +182 -0
  6. package/dist/html/index.d.mts +2 -0
  7. package/dist/html/index.mjs +4 -0
  8. package/dist/model/content/Body.d.mts +13 -1
  9. package/dist/model/content/Body.mjs +97 -19
  10. package/dist/model/content/Comment.mjs +7 -0
  11. package/dist/model/content/InlinePicture.d.mts +89 -10
  12. package/dist/model/content/InlinePicture.mjs +245 -35
  13. package/dist/model/content/List.mjs +10 -1
  14. package/dist/model/content/Paragraph.d.mts +28 -5
  15. package/dist/model/content/Paragraph.mjs +77 -37
  16. package/dist/model/content/Range.d.mts +18 -2
  17. package/dist/model/content/Range.mjs +194 -64
  18. package/dist/model/content/Table.d.mts +38 -0
  19. package/dist/model/content/Table.mjs +80 -2
  20. package/dist/model/content/TrackedChange.d.mts +129 -3
  21. package/dist/model/content/TrackedChange.mjs +577 -12
  22. package/dist/model/content/binder.d.mts +2 -1
  23. package/dist/model/content/binder.mjs +48 -1
  24. package/dist/model/content/index.d.mts +4 -3
  25. package/dist/model/content/index.mjs +3 -2
  26. package/dist/model/content/relationships.d.mts +30 -0
  27. package/dist/model/content/relationships.mjs +133 -0
  28. package/dist/model/content/stylesFacade.d.mts +4 -2
  29. package/dist/model/content/stylesFacade.mjs +20 -6
  30. package/dist/model/content/tableColumns.d.mts +48 -0
  31. package/dist/model/content/tableColumns.mjs +484 -0
  32. package/dist/model/content/tree.d.mts +52 -2
  33. package/dist/model/content/tree.mjs +48 -3
  34. package/dist/model/customxml/CustomXmlPartCollection.d.mts +6 -1
  35. package/dist/model/customxml/CustomXmlPartCollection.mjs +19 -3
  36. package/dist/model/customxml/bindings.d.mts +5 -2
  37. package/dist/model/customxml/bindings.mjs +21 -6
  38. package/dist/model/customxml/index.d.mts +3 -0
  39. package/dist/model/customxml/index.mjs +3 -0
  40. package/dist/model/customxml/opendope.d.mts +32 -0
  41. package/dist/model/customxml/opendope.mjs +56 -0
  42. package/dist/model/customxml/pictures.d.mts +57 -0
  43. package/dist/model/customxml/pictures.mjs +192 -0
  44. package/dist/model/customxml/xhtml.d.mts +42 -0
  45. package/dist/model/customxml/xhtml.mjs +162 -0
  46. package/dist/model/fonts/families.generated.mjs +2 -2
  47. package/dist/model/fonts/registry.mjs +7 -1
  48. package/dist/model/fonts/substitutions.generated.mjs +2 -2
  49. package/dist/model/fonts/themes.generated.mjs +1 -1
  50. package/dist/model/listnumbering/definitions.mjs +18 -0
  51. package/dist/model/properties/PropertyResolver.d.mts +27 -6
  52. package/dist/model/properties/PropertyResolver.mjs +83 -16
  53. package/dist/model/properties/catalogue.d.mts +15 -1
  54. package/dist/model/properties/catalogue.mjs +80 -27
  55. package/dist/model/properties/table.d.mts +16 -2
  56. package/dist/model/properties/table.mjs +13 -3
  57. package/dist/opc/Load.d.mts +9 -2
  58. package/dist/opc/index.d.mts +2 -2
  59. package/dist/opc/index.mjs +2 -2
  60. package/dist/opc/mce/preprocessor.d.mts +62 -6
  61. package/dist/opc/mce/preprocessor.mjs +142 -35
  62. package/dist/opc/mce/understood.d.mts +10 -0
  63. package/dist/opc/mce/understood.mjs +25 -3
  64. package/dist/packages/OpcPackage.d.mts +7 -0
  65. package/dist/packages/OpcPackage.mjs +15 -3
  66. package/dist/packages/WordprocessingMLPackage.d.mts +35 -2
  67. package/dist/packages/WordprocessingMLPackage.mjs +99 -2
  68. package/dist/parts/XmlPart.d.mts +26 -5
  69. package/dist/parts/XmlPart.mjs +67 -18
  70. package/dist/parts/wml/knownStyles.d.mts +1 -0
  71. package/dist/parts/wml/knownStyles.mjs +2233 -0
  72. package/package.json +7 -2
@@ -0,0 +1,182 @@
1
+ import { highlightNameOf } from './convert.mjs';
2
+ /** Word's character style for a link's text, as `Range.hyperlink` gives it. */
3
+ export const HYPERLINK_STYLE = 'Hyperlink';
4
+ function walkBlocks(blocks, onParagraph) {
5
+ for (const b of blocks) {
6
+ if (b.kind === 'paragraph')
7
+ onParagraph(b);
8
+ else
9
+ for (const row of b.rows)
10
+ for (const c of row)
11
+ walkBlocks(c.blocks, onParagraph);
12
+ }
13
+ }
14
+ export function needsOf(blocks) {
15
+ const lists = new Map();
16
+ const links = new Set();
17
+ walkBlocks(blocks, (p) => {
18
+ if (p.list && !lists.has(p.list.key))
19
+ lists.set(p.list.key, p.list.bullet);
20
+ for (const i of p.inlines)
21
+ if ('text' in i && i.link !== undefined)
22
+ links.add(i.link);
23
+ });
24
+ return { lists: [...lists].map(([key, bullet]) => ({ key, bullet })), links: [...links] };
25
+ }
26
+ const esc = (text) => text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
27
+ const attr = (value) => String(value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
28
+ function rPrXml(run, linked) {
29
+ const out = [];
30
+ // In the schema's order: rStyle, rFonts, b, i, strike, color, sz, szCs, highlight, u, vertAlign.
31
+ if (linked)
32
+ out.push(`<w:rStyle w:val="${HYPERLINK_STYLE}"/>`);
33
+ else if (run.style)
34
+ out.push(`<w:rStyle w:val="${attr(run.style)}"/>`);
35
+ if (run.fontName)
36
+ out.push(`<w:rFonts w:ascii="${attr(run.fontName)}" w:hAnsi="${attr(run.fontName)}" w:cs="${attr(run.fontName)}"/>`);
37
+ if (run.bold)
38
+ out.push('<w:b/>');
39
+ if (run.italic)
40
+ out.push('<w:i/>');
41
+ if (run.strike)
42
+ out.push('<w:strike/>');
43
+ // A link's colour is its style's, as `Range.hyperlink` leaves no direct colour on a link.
44
+ if (run.color && !linked)
45
+ out.push(`<w:color w:val="${attr(run.color === 'auto' ? 'auto' : run.color.replace(/^#/, ''))}"/>`);
46
+ if (run.fontSize !== undefined) {
47
+ const half = Math.round(run.fontSize * 2);
48
+ out.push(`<w:sz w:val="${half}"/><w:szCs w:val="${half}"/>`);
49
+ }
50
+ if (run.highlight) {
51
+ const name = highlightNameOf(run.highlight);
52
+ if (name)
53
+ out.push(`<w:highlight w:val="${name}"/>`);
54
+ }
55
+ if (run.underline)
56
+ out.push('<w:u w:val="single"/>');
57
+ if (run.vertAlign)
58
+ out.push(`<w:vertAlign w:val="${run.vertAlign}"/>`);
59
+ return out.length > 0 ? `<w:rPr>${out.join('')}</w:rPr>` : '';
60
+ }
61
+ function runXml(run, linked) {
62
+ if (run.text === '')
63
+ return '';
64
+ const space = /^\s|\s$|\s\s/.test(run.text) ? ' xml:space="preserve"' : '';
65
+ return `<w:r>${rPrXml(run, linked)}<w:t${space}>${esc(run.text)}</w:t></w:r>`;
66
+ }
67
+ function inlinesXml(inlines, resolve) {
68
+ var _a;
69
+ const out = [];
70
+ let i = 0;
71
+ while (i < inlines.length) {
72
+ const inline = inlines[i];
73
+ if ('br' in inline) {
74
+ out.push('<w:r><w:br/></w:r>');
75
+ i++;
76
+ continue;
77
+ }
78
+ if ('tab' in inline) {
79
+ out.push('<w:r><w:tab/></w:r>');
80
+ i++;
81
+ continue;
82
+ }
83
+ const rId = inline.link !== undefined ? (_a = resolve.relId) === null || _a === void 0 ? void 0 : _a.call(resolve, inline.link) : undefined;
84
+ if (rId === undefined) {
85
+ out.push(runXml(inline, false));
86
+ i++;
87
+ continue;
88
+ }
89
+ // The runs of one address under one w:hyperlink, in Word's look.
90
+ const runs = [];
91
+ while (i < inlines.length) {
92
+ const next = inlines[i];
93
+ if (!('text' in next) || next.link !== inline.link)
94
+ break;
95
+ runs.push(runXml(next, true));
96
+ i++;
97
+ }
98
+ out.push(`<w:hyperlink r:id="${attr(rId)}">${runs.join('')}</w:hyperlink>`);
99
+ }
100
+ return out.join('');
101
+ }
102
+ function pPrXml(p, resolve) {
103
+ var _a;
104
+ const out = [];
105
+ // In the schema's order: pStyle, numPr, spacing, ind, jc.
106
+ if (p.style)
107
+ out.push(`<w:pStyle w:val="${attr(p.style)}"/>`);
108
+ const numId = p.list ? (_a = resolve.numId) === null || _a === void 0 ? void 0 : _a.call(resolve, p.list.key) : undefined;
109
+ if (p.list && numId !== undefined)
110
+ out.push(`<w:numPr><w:ilvl w:val="${p.list.level}"/><w:numId w:val="${attr(numId)}"/></w:numPr>`);
111
+ if (p.spacing)
112
+ out.push(`<w:spacing w:before="${p.spacing.before}"/>`);
113
+ if (p.ind) {
114
+ const a = [];
115
+ if (p.ind.start !== undefined)
116
+ a.push(`w:left="${p.ind.start}"`);
117
+ if (p.ind.end !== undefined)
118
+ a.push(`w:right="${p.ind.end}"`);
119
+ if (p.ind.hanging !== undefined)
120
+ a.push(`w:hanging="${p.ind.hanging}"`);
121
+ else if (p.ind.firstLine !== undefined)
122
+ a.push(`w:firstLine="${p.ind.firstLine}"`);
123
+ if (a.length > 0)
124
+ out.push(`<w:ind ${a.join(' ')}/>`);
125
+ }
126
+ if (p.jc)
127
+ out.push(`<w:jc w:val="${attr(p.jc)}"/>`);
128
+ return out.length > 0 ? `<w:pPr>${out.join('')}</w:pPr>` : '';
129
+ }
130
+ function paragraphXml(p, resolve) {
131
+ return `<w:p>${pPrXml(p, resolve)}${inlinesXml(p.inlines, resolve)}</w:p>`;
132
+ }
133
+ /** Word's default text width shared equally where the table does not say (A4 or Letter with Word's margins: 9,026 twips). */
134
+ const TEXT_WIDTH = 9026;
135
+ function tableXml(t, resolve) {
136
+ const columns = Math.max(1, ...t.rows.map((r) => r.reduce((n, c) => n + c.colspan, 0)));
137
+ const widths = Array.from({ length: columns }, () => Math.floor(TEXT_WIDTH / columns));
138
+ const first = t.rows[0];
139
+ if (first && first.every((c) => c.width !== undefined && c.colspan === 1) && first.length === columns)
140
+ first.forEach((c, i) => { widths[i] = c.width; });
141
+ const widthOf = (col, span) => { var _a; let w = 0; for (let k = 0; k < span; k++)
142
+ w += (_a = widths[col + k]) !== null && _a !== void 0 ? _a : widths[0]; return w; };
143
+ const tcPr = (col, span, merge) => `<w:tcPr><w:tcW w:w="${widthOf(col, span)}" w:type="dxa"/>${span > 1 ? `<w:gridSpan w:val="${span}"/>` : ''}${merge === 'restart' ? '<w:vMerge w:val="restart"/>' : merge === 'continue' ? '<w:vMerge/>' : ''}</w:tcPr>`;
144
+ const cellXml = (c, col) => {
145
+ const blocks = c.blocks.length > 0 ? blocksToXml(c.blocks, resolve) : '<w:p/>';
146
+ return `<w:tc>${tcPr(col, c.colspan, c.rowspan > 1 ? 'restart' : undefined)}${blocks}</w:tc>`;
147
+ };
148
+ // A cell spanning rows holds its place in the rows below with a continuation cell, as WordprocessingML writes a vertical merge.
149
+ const pending = new Map();
150
+ const rows = t.rows.map((cells, r) => {
151
+ const out = [];
152
+ let col = 0;
153
+ const continuation = () => {
154
+ const p = pending.get(col);
155
+ if (!p)
156
+ return;
157
+ out.push(`<w:tc>${tcPr(col, p.span, 'continue')}<w:p/></w:tc>`);
158
+ if (--p.rows <= 0)
159
+ pending.delete(col);
160
+ col += p.span;
161
+ };
162
+ for (const c of cells) {
163
+ while (pending.has(col))
164
+ continuation();
165
+ out.push(cellXml(c, col));
166
+ if (c.rowspan > 1)
167
+ pending.set(col, { rows: c.rowspan - 1, span: c.colspan });
168
+ col += c.colspan;
169
+ }
170
+ while (col < columns && pending.has(col))
171
+ continuation();
172
+ const trPr = r === 0 && t.headerRow ? '<w:trPr><w:tblHeader/></w:trPr>' : '';
173
+ return `<w:tr>${trPr}${out.join('')}</w:tr>`;
174
+ });
175
+ const tblPr = `<w:tblPr>${t.style ? `<w:tblStyle w:val="${attr(t.style)}"/>` : ''}<w:tblW w:w="0" w:type="auto"/></w:tblPr>`;
176
+ const grid = `<w:tblGrid>${widths.map((w) => `<w:gridCol w:w="${w}"/>`).join('')}</w:tblGrid>`;
177
+ return `<w:tbl>${tblPr}${grid}${rows.join('')}</w:tbl>`;
178
+ }
179
+ /** The blocks as a WordprocessingML fragment: `w:p` and `w:tbl` elements, the prefixes undeclared (`insertXml` declares them). */
180
+ export function blocksToXml(blocks, resolve = {}) {
181
+ return blocks.map((b) => (b.kind === 'table' ? tableXml(b, resolve) : paragraphXml(b, resolve))).join('');
182
+ }
@@ -0,0 +1,2 @@
1
+ export * from './convert.mjs';
2
+ export * from './elements.mjs';
@@ -0,0 +1,4 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // `@docx4j/core-ts/html`: HTML to an intermediate form, and that form to WordprocessingML (CR-005 section 8).
3
+ export * from './convert.mjs';
4
+ export * from './elements.mjs';
@@ -192,7 +192,8 @@ export declare class Body {
192
192
  replaceText(find: string, replace: string, options?: SearchOptions): number;
193
193
  /**
194
194
  * Removes everything (section properties stay). While the package tracks changes, every
195
- * paragraph is marked deleted and every row's `w:trPr` takes a `w:del` instead.
195
+ * paragraph is marked deleted and every row's `w:trPr` takes a `w:del` instead. Removed outright,
196
+ * the relationships the content held (its pictures' images) are settled on save (CR-002 section 47).
196
197
  */
197
198
  clear(): void;
198
199
  /**
@@ -256,3 +257,14 @@ export declare class Body {
256
257
  */
257
258
  insertContentControl(kind?: ContentControlType): ContentControl;
258
259
  }
260
+ /**
261
+ * A story's tracked changes resolved (`Body.acceptAll` or `rejectAll`), then those of each text box in
262
+ * it - a `w:txbxContent`, in a DrawingML shape or a VML one - which Word counts as a story of its own
263
+ * (CR-002 section 49). A text box inside a text box is reached through its own story. Returns how many
264
+ * changes were listed.
265
+ *
266
+ * In a kept `mc:AlternateContent` the text box of every branch is resolved, so that each says the same
267
+ * afterwards (docx4j's `McMode.ALL`), and the changes are counted in the selected branch alone, so a
268
+ * text box held in both counts once, as a resolved load's does (CR-010 phase 3).
269
+ */
270
+ export declare function resolveStory(body: Body, what: 'accept' | 'reject'): number;
@@ -1,5 +1,5 @@
1
1
  import { Docx4JException, PropertyResolverNotCreatedException } from '../../opc/exceptions.mjs';
2
- import { typeNameOf, childrenOf, linkParents, BLOCK_LEVEL_TYPES, textOf, textOfView, rowsOf, cellsOf } from './tree.mjs';
2
+ import { typeNameOf, childrenOf, linkParents, BLOCK_LEVEL_TYPES, textOf, textOfView, rowsOf, cellsOf, selectedBranchOf, ALTERNATE_CONTENT } from './tree.mjs';
3
3
  import { r as textRun, br as breakItem, tbl as tableOf } from '@docx4j/generated-objects-ts/builders/wml';
4
4
  import { runOf, paragraphOf, runsOf } from './tree.mjs';
5
5
  import { Paragraph } from './Paragraph.mjs';
@@ -9,10 +9,11 @@ import { sdt as sdtOf, nextSdtId } from '@docx4j/generated-objects-ts/builders/w
9
9
  import { controlIdScope, sdtKindFor, declareSdtExtensions } from '../customxml/insert.mjs';
10
10
  import { InlinePicture, addImage, writableWidthEmu } from './InlinePicture.mjs';
11
11
  import { contentOf } from './ooxml.mjs';
12
+ import { relationshipIdsIn, queueRelationshipRemovals } from './relationships.mjs';
12
13
  import { expandReplacement } from './search.mjs';
13
14
  import { commentApi } from './comments.mjs';
14
15
  import { trackerOf, trackInsertedBlock, markHolderInserted } from './tracking.mjs';
15
- import { trackedChangesOfRow, trackedChangeTokensOfParagraph, groupTouching, BREAK, tablePropertiesChangeOf, cellChangeOf, bodySectionChangeOf, resolveUnlisted, holderInsertionAt } from './TrackedChange.mjs';
16
+ import { trackedChangesOfRow, trackedChangeTokensOfParagraph, groupTouching, BREAK, tablePropertiesChangeOf, cellChangeOf, bodySectionChangeOf, resolveUnlisted, holderInsertionAt, resolving } from './TrackedChange.mjs';
16
17
  import { listsOf, listLabelsOf } from './List.mjs';
17
18
  /**
18
19
  * A subset of Office JS `Word.Body` over any container with block-level content: the main
@@ -239,11 +240,18 @@ export class Body {
239
240
  * `values` fills it row by row. The columns are equal over the section's text width.
240
241
  */
241
242
  insertTable(rowCount, columnCount, location, values) {
243
+ var _a;
244
+ var _b;
242
245
  if (rowCount < 1 || columnCount < 1)
243
246
  throw new Docx4JException(`A table needs at least one row and one column; got ${rowCount} by ${columnCount}`);
244
247
  const rows = Array.from({ length: rowCount }, (_, r) => Array.from({ length: columnCount }, (_, c) => { var _a, _b; return (_b = (_a = values === null || values === void 0 ? void 0 : values[r]) === null || _a === void 0 ? void 0 : _a[c]) !== null && _b !== void 0 ? _b : ''; }));
245
248
  const width = writableWidthTwips(this.container);
246
249
  const element = tableOf(rows, width ? { width } : {});
250
+ // Word's own default (Insert Table, fixed column width Auto; the editor's check 42, Word 365): an
251
+ // automatic table width, no w:tblLayout, the cells in twips sharing the text width - the grid and
252
+ // cells the builder wrote, the table's width made automatic (CR-002 section 42.5 item 7).
253
+ const tblPr = ((_a = (_b = element.value).tblPr) !== null && _a !== void 0 ? _a : (_b.tblPr = {}));
254
+ tblPr.tblW = { w: 0, type: 'auto' };
247
255
  this.insertElement(element, location);
248
256
  return new Table(element, this.content, this);
249
257
  }
@@ -257,8 +265,11 @@ export class Body {
257
265
  throw new Docx4JException('This body has no part, so an image cannot be related to it');
258
266
  const { run, drawing } = addImage(this.part, base64, this.container, writableWidthEmu(this.container), options);
259
267
  const paragraph = this.insertParagraph('', location);
260
- paragraph.p.content.push(run);
261
- linkParents(run, paragraph.p);
268
+ // under tracking the picture is an insertion, as its paragraph's mark is (insertBreak's form)
269
+ const tracker = paragraph.changeTracker;
270
+ const item = tracker ? tracker.ins([run]) : run;
271
+ paragraph.p.content.push(item);
272
+ linkParents(item, paragraph.p);
262
273
  return new InlinePicture(drawing, run, paragraph);
263
274
  }
264
275
  insertBreak(type, location) {
@@ -363,11 +374,16 @@ export class Body {
363
374
  }
364
375
  /**
365
376
  * Removes everything (section properties stay). While the package tracks changes, every
366
- * paragraph is marked deleted and every row's `w:trPr` takes a `w:del` instead.
377
+ * paragraph is marked deleted and every row's `w:trPr` takes a `w:del` instead. Removed outright,
378
+ * the relationships the content held (its pictures' images) are settled on save (CR-002 section 47).
367
379
  */
368
380
  clear() {
381
+ var _a;
369
382
  const tracker = this.changeTracker;
370
383
  if (!tracker) {
384
+ const ids = new Set();
385
+ relationshipIdsIn(this.content, ids);
386
+ queueRelationshipRemovals((_a = this.package_) === null || _a === void 0 ? void 0 : _a.pendingRelationshipRemovals, this.part, ids);
371
387
  this.content.length = 0;
372
388
  return;
373
389
  }
@@ -420,14 +436,14 @@ export class Body {
420
436
  // belongs to that change, which accepts or rejects it along with the row.
421
437
  const tokens = [];
422
438
  for (const cell of cellsOf(row.element.value)) {
423
- const cellChange = cellChangeOf(cell.element, cell.container, row.element.value);
439
+ const cellChange = cellChangeOf(cell.element, cell.container, row.element.value, this);
424
440
  if (cellChange)
425
441
  tokens.push(cellChange, BREAK);
426
442
  visit((_b = childrenOf(cell.element.value)) !== null && _b !== void 0 ? _b : [], tokens);
427
443
  tokens.push(BREAK);
428
444
  }
429
445
  const inner = groupTouching(tokens);
430
- const rowChanges = trackedChangesOfRow(row.element, row.container, inner, { element: el, container: items });
446
+ const rowChanges = trackedChangesOfRow(row.element, row.container, inner, { element: el, container: items }, this);
431
447
  if (rowChanges.length > 0)
432
448
  into.push(...rowChanges);
433
449
  else {
@@ -474,18 +490,22 @@ export class Body {
474
490
  * pass that resolves nothing ends it.
475
491
  */
476
492
  resolveAll(what) {
477
- let changes = this.getTrackedChanges();
478
- const count = changes.length;
479
- while (changes.length > 0) {
480
- for (let i = changes.length - 1; i >= 0; i--)
481
- changes[i][what]();
482
- const left = this.getTrackedChanges();
483
- if (left.length >= changes.length)
484
- break;
485
- changes = left;
486
- }
487
- // and what is never listed: a cell merge, a numbering change, records that change nothing
488
- resolveUnlisted(this.content, what);
493
+ let count = 0;
494
+ // one resolution for the lot, so the bookmarks are read before the first and after the last
495
+ resolving(this.package_, this, () => {
496
+ let changes = this.getTrackedChanges();
497
+ count = changes.length;
498
+ while (changes.length > 0) {
499
+ for (let i = changes.length - 1; i >= 0; i--)
500
+ changes[i][what]();
501
+ const left = this.getTrackedChanges();
502
+ if (left.length >= changes.length)
503
+ break;
504
+ changes = left;
505
+ }
506
+ // and what is never listed: a cell merge, a numbering change, records that change nothing
507
+ resolveUnlisted(this.content, what);
508
+ });
489
509
  return count;
490
510
  }
491
511
  /** Every `w:tr` in this body's tables, in document order (rows of nested tables included). */
@@ -770,3 +790,61 @@ function describe(el) {
770
790
  const tn = typeNameOf(el);
771
791
  return `${(_b = (_a = el.name) === null || _a === void 0 ? void 0 : _a.localPart) !== null && _b !== void 0 ? _b : '?'} (${tn !== null && tn !== void 0 ? tn : 'no TYPE_NAME'})`;
772
792
  }
793
+ /**
794
+ * A story's tracked changes resolved (`Body.acceptAll` or `rejectAll`), then those of each text box in
795
+ * it - a `w:txbxContent`, in a DrawingML shape or a VML one - which Word counts as a story of its own
796
+ * (CR-002 section 49). A text box inside a text box is reached through its own story. Returns how many
797
+ * changes were listed.
798
+ *
799
+ * In a kept `mc:AlternateContent` the text box of every branch is resolved, so that each says the same
800
+ * afterwards (docx4j's `McMode.ALL`), and the changes are counted in the selected branch alone, so a
801
+ * text box held in both counts once, as a resolved load's does (CR-010 phase 3).
802
+ */
803
+ export function resolveStory(body, what) {
804
+ let count = what === 'accept' ? body.acceptAll() : body.rejectAll();
805
+ for (const { box, selected } of textBoxesIn(body.container)) {
806
+ const resolved = resolveStory(body.sub(box), what);
807
+ if (selected)
808
+ count += resolved;
809
+ }
810
+ return count;
811
+ }
812
+ /**
813
+ * The `w:txbxContent` of every text box in a story's content, not descending into one found, each with
814
+ * whether it is in a branch a reader takes: false inside a branch of a kept `mc:AlternateContent` other
815
+ * than its selected one (`selectedBranchOf`).
816
+ */
817
+ function textBoxesIn(container) {
818
+ const out = [];
819
+ const seen = new Set();
820
+ const visit = (o, selected) => {
821
+ var _a;
822
+ if (typeof o !== 'object' || o === null || seen.has(o))
823
+ return;
824
+ if (typeof o.nodeType === 'number')
825
+ return; // DOM the model does not type
826
+ seen.add(o);
827
+ if (Array.isArray(o)) {
828
+ for (const item of o)
829
+ visit(item, selected);
830
+ return;
831
+ }
832
+ const typeName = o.TYPE_NAME;
833
+ if (typeName === 'org_docx4j_wml.CTTxbxContent') {
834
+ out.push({ box: o, selected });
835
+ return;
836
+ }
837
+ if (typeName === ALTERNATE_CONTENT) {
838
+ const ac = o;
839
+ const read = selectedBranchOf(ac);
840
+ for (const branch of [...((_a = ac.choice) !== null && _a !== void 0 ? _a : []), ...(ac.fallback ? [ac.fallback] : [])])
841
+ visit(branch.any, selected && branch.any === read);
842
+ return;
843
+ }
844
+ for (const key in o)
845
+ if (key !== 'PARENT')
846
+ visit(o[key], selected);
847
+ };
848
+ visit(container.content, true);
849
+ return out;
850
+ }
@@ -9,6 +9,7 @@ import { Paragraph } from './Paragraph.mjs';
9
9
  import { Range } from './Range.mjs';
10
10
  import { runOf, paragraphOf, typeNameOf } from './tree.mjs';
11
11
  import { revisionDateOf, wordCalendarOf, copyRPr } from './tracking.mjs';
12
+ import { relationshipIdsIn, queueRelationshipRemovals } from './relationships.mjs';
12
13
  import { COMMENT_REFERENCE_STYLE, COMMENT_TEXT_STYLE, commentExOf, commentIdsOf, commentPartsAccess, commentParaId, initialsOf, isDone, markersOf, markersOfParagraph, nextCommentId, paraIdTaken, randomHexId, removeMarkers, setCommentApi, } from './comments.mjs';
13
14
  /**
14
15
  * A subset of Office JS `Word.Comment` over a `w:comment` and the markers that anchor it. A view:
@@ -191,6 +192,12 @@ export class Comment {
191
192
  if (i >= 0)
192
193
  comments.splice(i, 1);
193
194
  }
195
+ // what the comment's own content related to - a picture's image - goes on save when nothing else
196
+ // uses it, as for a picture deleted outright (CR-002 section 50; docx4j's Removals)
197
+ const released = new Set();
198
+ relationshipIdsIn(this.element.content, released);
199
+ const pkg = this.body.package_;
200
+ queueRelationshipRemovals(pkg === null || pkg === void 0 ? void 0 : pkg.pendingRelationshipRemovals, this.parts.commentsPart, released);
194
201
  const paraId = this.paraId;
195
202
  if (paraId === undefined)
196
203
  return;
@@ -1,4 +1,5 @@
1
1
  import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ import type * as dml from '@docx4j/generated-objects-ts/modules/org_docx4j_dml';
2
3
  import type * as wp from '@docx4j/generated-objects-ts/modules/org_docx4j_dml_wordprocessingDrawing';
3
4
  import { IMAGE_CONTENT_TYPES_BY_EXTENSION } from '../../opc/ContentTypes.mjs';
4
5
  import { ImagePart } from '../../parts/BinaryPart.mjs';
@@ -22,7 +23,9 @@ export interface ImageInfo {
22
23
  * The size and kind of an image, from its header: PNG (IHDR and pHYs), JPEG (SOFn and the JFIF
23
24
  * APP0 densities), GIF (the logical screen descriptor) and BMP (the DIB header, with its pixels
24
25
  * per metre). docx4j reads these through XML Graphics Commons' ImageInfo; this is the same
25
- * information, and the same 96 dpi default when a file declares none.
26
+ * information, and the same 96 dpi default when a file declares none. An SVG document is told by
27
+ * its root element and has no pixel size (both 0): it can be the part behind an existing picture
28
+ * (CR-005 section 9, a binding that keeps the drawing) but not be placed as a new one.
26
29
  */
27
30
  export declare function imageInfoOf(bytes: Uint8Array): ImageInfo;
28
31
  /** The natural size of an image in EMU: pixels over the resolution it declares. */
@@ -49,17 +52,56 @@ export interface NewPicture {
49
52
  imagePart: ImagePart;
50
53
  relId: string;
51
54
  }
55
+ /** A new image part, its relationship from the part it was added to, and what its header says. */
56
+ export interface NewImagePart {
57
+ imagePart: ImagePart;
58
+ relId: string;
59
+ info: ImageInfo;
60
+ }
52
61
  /**
53
62
  * Adds the image as a part of `source` (the main document part, or the header or footer the body
54
- * belongs to) with a relationship, and builds the run that shows it. docx4j:
55
- * `BinaryPartAbstractImage.createImagePart` then `createImageInline`.
63
+ * belongs to) with a relationship, and nothing else: docx4j `BinaryPartAbstractImage.createImagePart`
64
+ * (`BindingTraverserXSLT.createImagePartReturnRelId`, where a picture binding replaces an `r:embed`
65
+ * and keeps the drawing, CR-005 section 9). Throws, and adds nothing, when the bytes are not an
66
+ * image this package reads.
67
+ */
68
+ export declare function addImagePart(source: Part, base64: string): NewImagePart;
69
+ /**
70
+ * Adds the image as a part of `source` with a relationship (`addImagePart`), and builds the run
71
+ * that shows it. docx4j: `BinaryPartAbstractImage.createImagePart` then `createImageInline`.
56
72
  */
57
73
  export declare function addImage(source: Part, base64: string, scope: object, maxCx: number | undefined, options?: InlinePictureOptions): NewPicture;
58
74
  /** The writable width of a section in EMU (page width less the margins), for scaling. */
59
75
  export declare function writableWidthEmu(container: object): number | undefined;
76
+ /** The `asvg:svgBlip` in a blip's `a:extLst`, if any: an SVG picture's SVG (CR-002 section 52). */
77
+ export declare function svgBlipOf(blip: dml.CTBlip | undefined): {
78
+ embed?: string;
79
+ } | undefined;
80
+ /**
81
+ * Points a blip at a new image the way Word's own picture binding does (test/README.md checks 46 and
82
+ * 47; CR-002 section 52). The blip's effects and other extensions stay.
83
+ * - A raster image goes in `r:embed`, and an `asvg:svgBlip` is taken away. Left in place, Word would go
84
+ * on drawing its old SVG, and would replace the new image with a PNG of that SVG when it saves.
85
+ * - An SVG goes under the `asvg:svgBlip`, and `r:embed` is taken away. Word puts a PNG of the SVG it
86
+ * makes itself in `r:embed`, which this package cannot make, and Word 365 draws the SVG without one.
87
+ * `r:link` is left as it was.
88
+ */
89
+ export declare function setBlipImage(blip: dml.CTBlip, relId: string, svg: boolean): void;
90
+ /**
91
+ * The inline pictures a run's item holds: the item itself when it is a `w:drawing` whose `wp:inline`
92
+ * holds a `pic:pic`, and those in a kept `mc:AlternateContent`'s selected branch (`selectedBranchOf`,
93
+ * CR-010 phase 3). A drawing of anything else, such as a chart, a text box, a group or SmartArt, is no
94
+ * inline picture: Office JS lists none for an inline text box (CR-010 section 12.6, test/README.md
95
+ * check 42).
96
+ */
97
+ export declare function picturesIn(item: Element): Element<wml.Drawing>[];
60
98
  /**
61
- * A subset of Office JS `Word.InlinePicture` over a `w:drawing` holding a `wp:inline`. A view:
62
- * the drawing, the run it is in and the paragraph. Sizes are points, as Office JS reports them.
99
+ * A subset of Office JS `Word.InlinePicture` over a `w:drawing` holding a `wp:inline` with a `pic:pic`.
100
+ * A view: the drawing, the run it is in and the paragraph. Sizes are points, as Office JS reports them.
101
+ *
102
+ * A picture in a kept `mc:AlternateContent`'s selected branch has the element as its `holder`. Reads and
103
+ * the setters are the selected branch's drawing; `delete` removes the element whole, every branch with
104
+ * it (CR-010 phase 3).
63
105
  */
64
106
  export declare class InlinePicture {
65
107
  /** The `w:drawing` element pair. */
@@ -67,11 +109,15 @@ export declare class InlinePicture {
67
109
  /** The run holding the drawing, and the array holding the run. */
68
110
  readonly run: Element<wml.R>;
69
111
  readonly paragraph: Paragraph;
112
+ /** The run's item the drawing stands in, where that is not the drawing: a kept `mc:AlternateContent`. */
113
+ readonly holder?: Element | undefined;
70
114
  constructor(
71
115
  /** The `w:drawing` element pair. */
72
116
  element: Element<wml.Drawing>,
73
117
  /** The run holding the drawing, and the array holding the run. */
74
- run: Element<wml.R>, paragraph: Paragraph);
118
+ run: Element<wml.R>, paragraph: Paragraph,
119
+ /** The run's item the drawing stands in, where that is not the drawing: a kept `mc:AlternateContent`. */
120
+ holder?: Element | undefined);
75
121
  /** The wp:inline; a floating picture (wp:anchor) is not an InlinePicture, and throws. */
76
122
  get inline(): wp.Inline;
77
123
  /** Width in points, as Office JS. */
@@ -85,18 +131,51 @@ export declare class InlinePicture {
85
131
  /** wp:docPr/@title (typed since objects 0.1.3; before that the attribute was not marshalled, CR-002 section 8). */
86
132
  get altTextTitle(): string;
87
133
  set altTextTitle(text: string);
88
- /** The kind of image, from the content type of the part the picture embeds. */
134
+ /**
135
+ * The kind of image, from the content type of the part `getBase64` reads: `Svg` for an SVG picture
136
+ * (`svgImagePart`), else the `a:blip`'s part's. Office JS gives `null` for an SVG picture, though its
137
+ * typings declare the property non-null and `Word.ImageFormat` has `Svg` (test/README.md check 45);
138
+ * this package gives `Svg`, a departure (CR-002 section 52).
139
+ */
89
140
  get imageFormat(): ImageFormat;
90
141
  /** The relationship id of the embedded image (r:embed), if any (extension). */
91
142
  get relId(): string | undefined;
92
- /** The image's bytes as base64 (Office JS getBase64ImageSrc). */
143
+ /**
144
+ * The relationship id of the SVG the picture embeds beside its image, if any (extension): the
145
+ * `r:embed` of an `asvg:svgBlip` in the `a:blip`'s `a:extLst`, as Office 2016 and later write an SVG
146
+ * picture, with the `a:blip`'s own image a PNG of it for readers that don't understand SVG.
147
+ */
148
+ get svgRelId(): string | undefined;
149
+ /**
150
+ * The image's bytes as base64 (Office JS getBase64ImageSrc). For an SVG picture, the SVG
151
+ * (`svgImagePart`), as Office JS gives it (test/README.md check 43); else the `a:blip`'s image.
152
+ */
93
153
  getBase64(): Promise<string>;
94
154
  /** Office JS's name for `getBase64()`. */
95
155
  getBase64ImageSrc(): Promise<string>;
96
- /** The image part this picture embeds (extension). */
156
+ /** The image part this picture embeds: the `a:blip`'s, which is an SVG picture's PNG (extension). */
97
157
  imagePart(): ImagePart | undefined;
98
- /** Removes the picture; the run goes too when the picture was all it held. */
158
+ /** The SVG part an SVG picture embeds beside its image (`svgRelId`), if any (extension). */
159
+ svgImagePart(): ImagePart | undefined;
160
+ private partFor;
161
+ /**
162
+ * Removes the picture; the run goes too when the picture was all it held. A picture in a kept
163
+ * `mc:AlternateContent` takes the element with it, every branch (`holder`).
164
+ *
165
+ * While the package tracks changes it is marked deleted instead: its run goes into a `w:del`, and
166
+ * when the run held more than the picture, the picture is first given a run of its own with the
167
+ * same properties, so the deletion holds it alone. Accepting the deletion then takes it, and its
168
+ * image part with it (CR-002 section 46). A picture its author inserted under tracking is taken
169
+ * back, as `Paragraph.delete` takes back its own text, and one inside a deletion is refused.
170
+ *
171
+ * A picture removed outright (tracking off, or taken back) queues its image relationship on the
172
+ * package. On save the relationship goes if nothing then references it, and the image part with it
173
+ * when nothing else targets it. Waiting for the save lets a picture cut and pasted back keep its
174
+ * image.
175
+ */
99
176
  delete(): void;
177
+ /** The picture's run in a `w:del`, the picture split into a run of its own when the run held more. */
178
+ private markDeleted;
100
179
  private pic;
101
180
  private resize;
102
181
  }