@docx4j/core-ts 0.3.2 → 0.4.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 (34) hide show
  1. package/README.md +3 -1
  2. package/dist/html/convert.d.mts +116 -0
  3. package/dist/html/convert.mjs +541 -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.mjs +7 -0
  9. package/dist/model/content/InlinePicture.d.mts +18 -3
  10. package/dist/model/content/InlinePicture.mjs +44 -7
  11. package/dist/model/content/Table.d.mts +38 -0
  12. package/dist/model/content/Table.mjs +80 -2
  13. package/dist/model/content/index.d.mts +2 -1
  14. package/dist/model/content/index.mjs +2 -1
  15. package/dist/model/content/stylesFacade.d.mts +4 -2
  16. package/dist/model/content/stylesFacade.mjs +20 -6
  17. package/dist/model/content/tableColumns.d.mts +48 -0
  18. package/dist/model/content/tableColumns.mjs +484 -0
  19. package/dist/model/customxml/CustomXmlPartCollection.d.mts +6 -1
  20. package/dist/model/customxml/CustomXmlPartCollection.mjs +19 -3
  21. package/dist/model/customxml/bindings.d.mts +5 -2
  22. package/dist/model/customxml/bindings.mjs +21 -6
  23. package/dist/model/customxml/index.d.mts +3 -0
  24. package/dist/model/customxml/index.mjs +3 -0
  25. package/dist/model/customxml/opendope.d.mts +32 -0
  26. package/dist/model/customxml/opendope.mjs +56 -0
  27. package/dist/model/customxml/pictures.d.mts +54 -0
  28. package/dist/model/customxml/pictures.mjs +186 -0
  29. package/dist/model/customxml/xhtml.d.mts +42 -0
  30. package/dist/model/customxml/xhtml.mjs +162 -0
  31. package/dist/packages/WordprocessingMLPackage.mjs +1 -0
  32. package/dist/parts/wml/knownStyles.d.mts +1 -0
  33. package/dist/parts/wml/knownStyles.mjs +2233 -0
  34. package/package.json +6 -1
@@ -0,0 +1,48 @@
1
+ import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ import { type Element } from './tree.mjs';
3
+ import { type ChangeTracker } from './tracking.mjs';
4
+ /** The default text width in twips (A4 with 2.54 cm margins), as the objects package's `tbl` builder uses. */
5
+ export declare const DEFAULT_TEXT_WIDTH = 9026;
6
+ export type TableLayoutMode = 'fixed' | 'window' | 'contents';
7
+ /** Which of Word's three layout modes a table is in, read as Word's Table Properties reads the file. */
8
+ export declare function layoutModeOf(tbl: wml.Tbl): TableLayoutMode;
9
+ /** The section's text width in twips: the page less its margins and gutter, else the default. */
10
+ export declare function textWidthTwips(container: {
11
+ sectPr?: wml.SectPr;
12
+ } | undefined): number;
13
+ /** The grid's column widths in twips; a table without a `w:tblGrid` gets one from its first row (equal columns over the text width when the cells state none). */
14
+ export declare function gridWidthsOf(tbl: wml.Tbl, textWidth: number): number[];
15
+ /** The form a cell's width takes: its own `w:tcW` type, else the table's `w:tblW` type, else twips (section 42.2 item 4). */
16
+ export declare function widthTypeOf(tc: wml.Tc | undefined, tbl: wml.Tbl): string;
17
+ export interface ColumnInsertion {
18
+ /** The new cells per row, in row order; a row whose spanning cell was widened, or whose skipped columns grew, has none. */
19
+ cells: Element<wml.Tc>[][];
20
+ }
21
+ /**
22
+ * Inserts `count` grid columns at `gridIndex` (0 to the column count). The new column copies the
23
+ * width of the column to the right of the boundary when there is one, else the left (check 40:
24
+ * Insert Right in a narrow cell beside a wide one took the wide width; Insert Right in a spanning
25
+ * cell took the column after it), and each row's new cell takes the properties of the cell on the
26
+ * same side, less its span and merges. A cell spanning the boundary keeps its span and the new
27
+ * cell goes before it (check 40b1); a row's `w:gridBefore` or `w:gridAfter` covering the boundary
28
+ * grows instead. `values` is by row, then by new column. While changes are tracked the table
29
+ * change is recorded as Word's Office JS records one (check 40c): the old properties, every
30
+ * cell's properties - the new cells' as made - the grid as it is after, and the new cells' text in
31
+ * a `w:ins`, with no mark insertion.
32
+ */
33
+ export declare function insertGridColumns(tbl: wml.Tbl, textWidth: number, gridIndex: number, count: number, values: string[][] | undefined, tracker: ChangeTracker | undefined): ColumnInsertion;
34
+ /**
35
+ * Removes `count` grid columns from `columnIndex`: a cell covering only removed columns goes, one
36
+ * spanning further shrinks, a row's skipped columns shrink; the grid loses the columns, a `w:tblW`
37
+ * in twips loses their width, and in window mode the grid is rescaled to the width it had and the
38
+ * cells' percentages written again. While changes are tracked the cells still go, untracked, as
39
+ * Word's own Delete Columns and Office JS's `deleteColumns` leave them (known issues, entry 2;
40
+ * check 40c), and the table change is recorded as Office JS records it: the old properties, the
41
+ * remaining cells' properties, the grid as it is after.
42
+ */
43
+ export declare function deleteGridColumns(tbl: wml.Tbl, textWidth: number, columnIndex: number, count: number, tracker: ChangeTracker | undefined): void;
44
+ /** The grid columns a cell covers: its start and the one past its end. */
45
+ export declare function gridRangeOf(tr: wml.Tr, tc: wml.Tc): {
46
+ start: number;
47
+ end: number;
48
+ } | undefined;
@@ -0,0 +1,484 @@
1
+ import { deepCopy } from '@docx4j/generated-objects-ts';
2
+ import { tc as cellElementOf } from '@docx4j/generated-objects-ts/builders/wml';
3
+ import { Docx4JException } from '../../opc/exceptions.mjs';
4
+ import { rowsOf, cellsOf, childrenOf, linkParents, paragraphOf } from './tree.mjs';
5
+ import { wrapInsertedRuns } from './tracking.mjs';
6
+ /** The default text width in twips (A4 with 2.54 cm margins), as the objects package's `tbl` builder uses. */
7
+ export const DEFAULT_TEXT_WIDTH = 9026;
8
+ /** A table's width in fiftieths of a percent: `w:tblW w:type="pct" w:w="5000"` is the text width. */
9
+ const FULL_PCT = 5000;
10
+ /** Which of Word's three layout modes a table is in, read as Word's Table Properties reads the file. */
11
+ export function layoutModeOf(tbl) {
12
+ var _a, _b, _c, _d;
13
+ if (((_b = (_a = tbl.tblPr) === null || _a === void 0 ? void 0 : _a.tblLayout) === null || _b === void 0 ? void 0 : _b.type) === 'fixed')
14
+ return 'fixed';
15
+ if (((_d = (_c = tbl.tblPr) === null || _c === void 0 ? void 0 : _c.tblW) === null || _d === void 0 ? void 0 : _d.type) === 'pct')
16
+ return 'window';
17
+ return 'contents';
18
+ }
19
+ /** The section's text width in twips: the page less its margins and gutter, else the default. */
20
+ export function textWidthTwips(container) {
21
+ var _a, _b, _c, _d, _e, _f;
22
+ const sectPr = container === null || container === void 0 ? void 0 : container.sectPr;
23
+ const pgSz = sectPr === null || sectPr === void 0 ? void 0 : sectPr.pgSz;
24
+ if (!(pgSz === null || pgSz === void 0 ? void 0 : pgSz.w))
25
+ return DEFAULT_TEXT_WIDTH;
26
+ const width = pgSz.w - ((_b = (_a = sectPr === null || sectPr === void 0 ? void 0 : sectPr.pgMar) === null || _a === void 0 ? void 0 : _a.left) !== null && _b !== void 0 ? _b : 0) - ((_d = (_c = sectPr === null || sectPr === void 0 ? void 0 : sectPr.pgMar) === null || _c === void 0 ? void 0 : _c.right) !== null && _d !== void 0 ? _d : 0) - ((_f = (_e = sectPr === null || sectPr === void 0 ? void 0 : sectPr.pgMar) === null || _e === void 0 ? void 0 : _e.gutter) !== null && _f !== void 0 ? _f : 0);
27
+ return width > 0 ? width : DEFAULT_TEXT_WIDTH;
28
+ }
29
+ function rowMember(tr, name) {
30
+ var _a, _b;
31
+ return ((_b = (_a = tr.trPr) === null || _a === void 0 ? void 0 : _a.cnfStyleOrDivIdOrGridBefore) !== null && _b !== void 0 ? _b : []).find((item) => { var _a; return ((_a = item.name) === null || _a === void 0 ? void 0 : _a.localPart) === name; });
32
+ }
33
+ function spanOf(tc) {
34
+ var _a, _b, _c;
35
+ return Number((_c = (_b = (_a = tc.tcPr) === null || _a === void 0 ? void 0 : _a.gridSpan) === null || _b === void 0 ? void 0 : _b.val) !== null && _c !== void 0 ? _c : 1) || 1;
36
+ }
37
+ function setSpan(tc, count) {
38
+ var _a, _b;
39
+ const tcPr = ((_a = tc.tcPr) !== null && _a !== void 0 ? _a : (tc.tcPr = {}));
40
+ if (count > 1)
41
+ tcPr.gridSpan = { ...((_b = tcPr.gridSpan) !== null && _b !== void 0 ? _b : {}), val: count };
42
+ else
43
+ delete tcPr.gridSpan;
44
+ }
45
+ /** The grid's column widths in twips; a table without a `w:tblGrid` gets one from its first row (equal columns over the text width when the cells state none). */
46
+ export function gridWidthsOf(tbl, textWidth) {
47
+ var _a, _b, _c, _d;
48
+ const grid = (_a = tbl.tblGrid) === null || _a === void 0 ? void 0 : _a.gridCol;
49
+ if (grid && grid.length > 0)
50
+ return grid.map((col) => { var _a; return Number((_a = col.w) !== null && _a !== void 0 ? _a : 0); });
51
+ const first = rowsOf(tbl)[0];
52
+ const cells = first ? cellsOf(first.element.value) : [];
53
+ const columns = Math.max(1, cells.reduce((n, cell) => n + spanOf(cell.element.value), 0));
54
+ const each = Math.floor(textWidth / columns);
55
+ const widths = [];
56
+ for (const cell of cells) {
57
+ const tc = cell.element.value;
58
+ const span = spanOf(tc);
59
+ const stated = ((_c = (_b = tc.tcPr) === null || _b === void 0 ? void 0 : _b.tcW) === null || _c === void 0 ? void 0 : _c.type) === 'dxa' ? Number((_d = tc.tcPr.tcW.w) !== null && _d !== void 0 ? _d : 0) : 0;
60
+ for (let i = 0; i < span; i++)
61
+ widths.push(stated > 0 ? Math.floor(stated / span) : each);
62
+ }
63
+ return widths.length > 0 ? widths : [each];
64
+ }
65
+ function writeGrid(tbl, widths) {
66
+ var _a;
67
+ const grid = ((_a = tbl.tblGrid) !== null && _a !== void 0 ? _a : (tbl.tblGrid = {}));
68
+ grid.gridCol = widths.map((w) => ({ TYPE_NAME: 'org_docx4j_wml.TblGridCol', w }));
69
+ linkParents(grid, tbl);
70
+ }
71
+ /**
72
+ * Widths rescaled to a total: each floored, the remainder given to the columns with the largest
73
+ * fractions, the earlier first on a tie, so that equal columns inserted and then removed come
74
+ * back equal (Word's own grid, 3005/3006/3006, is its layout's measurement and not reproduced).
75
+ */
76
+ function rescaled(widths, total) {
77
+ const sum = widths.reduce((a, b) => a + b, 0);
78
+ if (sum <= 0 || total <= 0)
79
+ return widths.slice();
80
+ const exact = widths.map((w) => (w * total) / sum);
81
+ const out = exact.map((w) => Math.floor(w));
82
+ let remainder = total - out.reduce((a, b) => a + b, 0);
83
+ const order = exact.map((w, i) => ({ i, fraction: w - Math.floor(w) })).sort((a, b) => b.fraction - a.fraction || a.i - b.i);
84
+ for (const { i } of order) {
85
+ if (remainder <= 0)
86
+ break;
87
+ out[i] += 1;
88
+ remainder--;
89
+ }
90
+ return out;
91
+ }
92
+ /** A cell's share of the table in fiftieths of a percent over grid columns [start, end): the cumulative shares floored, as Word writes them. */
93
+ function pctShare(widths, start, end) {
94
+ const total = widths.reduce((a, b) => a + b, 0);
95
+ if (total <= 0)
96
+ return 0;
97
+ const cumulative = (to) => Math.floor((FULL_PCT * widths.slice(0, to).reduce((a, b) => a + b, 0)) / total);
98
+ return cumulative(end) - cumulative(start);
99
+ }
100
+ /** The form a cell's width takes: its own `w:tcW` type, else the table's `w:tblW` type, else twips (section 42.2 item 4). */
101
+ export function widthTypeOf(tc, tbl) {
102
+ var _a, _b, _c, _d, _e, _f;
103
+ return (_f = (_c = (_b = (_a = tc === null || tc === void 0 ? void 0 : tc.tcPr) === null || _a === void 0 ? void 0 : _a.tcW) === null || _b === void 0 ? void 0 : _b.type) !== null && _c !== void 0 ? _c : (_e = (_d = tbl.tblPr) === null || _d === void 0 ? void 0 : _d.tblW) === null || _e === void 0 ? void 0 : _e.type) !== null && _f !== void 0 ? _f : 'dxa';
104
+ }
105
+ /** Writes a cell's `w:tcW` for the grid columns it covers, in the form it takes; `auto` and `nil` widths are left as they are. */
106
+ function writeCellWidth(tc, tbl, widths, start, end) {
107
+ var _a, _b, _c;
108
+ const type = widthTypeOf(tc, tbl);
109
+ const tcPr = ((_a = tc.tcPr) !== null && _a !== void 0 ? _a : (tc.tcPr = {}));
110
+ if (type === 'pct')
111
+ tcPr.tcW = { ...((_b = tcPr.tcW) !== null && _b !== void 0 ? _b : {}), w: pctShare(widths, start, end), type: 'pct' };
112
+ else if (type === 'dxa')
113
+ tcPr.tcW = { ...((_c = tcPr.tcW) !== null && _c !== void 0 ? _c : {}), w: widths.slice(start, end).reduce((a, b) => a + b, 0), type: 'dxa' };
114
+ else if (!tcPr.tcW)
115
+ tcPr.tcW = { w: 0, type };
116
+ }
117
+ /** The typed object a row's cell array belongs to: the row, or a cell-level control's content. */
118
+ function ownerOfCells(tr, container) {
119
+ var _a, _b;
120
+ if (tr.content === container)
121
+ return tr;
122
+ for (const item of ((_a = tr.content) !== null && _a !== void 0 ? _a : [])) {
123
+ const v = item.value;
124
+ if (v && typeof v === 'object' && ((_b = v.sdtContent) === null || _b === void 0 ? void 0 : _b.content) === container)
125
+ return v.sdtContent;
126
+ }
127
+ return tr;
128
+ }
129
+ /**
130
+ * The cell properties a new cell takes from the cell to the right of the boundary: its width (the
131
+ * form; the value is written from the grid) and its shading, and nothing else - check 41 saw Word
132
+ * copy the shading and leave the borders and the vertical alignment behind.
133
+ */
134
+ const COPIED = ['tcW', 'shd'];
135
+ function cellPropertiesFrom(template) {
136
+ if (!(template === null || template === void 0 ? void 0 : template.tcPr))
137
+ return undefined;
138
+ const out = {};
139
+ for (const [key, value] of Object.entries(template.tcPr))
140
+ if (COPIED.includes(key))
141
+ out[key] = deepCopy(value);
142
+ return Object.keys(out).length > 0 ? out : undefined;
143
+ }
144
+ /** The paragraph properties a new cell's paragraph takes: the template's first paragraph's, its mark's run properties included, less any revision (check 41: centred bold, right-aligned italic, as the neighbour's). */
145
+ function paragraphPropertiesFrom(template) {
146
+ var _a, _b;
147
+ const first = ((_a = childrenOf(template !== null && template !== void 0 ? template : {})) !== null && _a !== void 0 ? _a : []).find((item) => { var _a; return ((_a = item.value) === null || _a === void 0 ? void 0 : _a.TYPE_NAME) === 'org_docx4j_wml.P'; });
148
+ const pPr = (_b = first === null || first === void 0 ? void 0 : first.value) === null || _b === void 0 ? void 0 : _b.pPr;
149
+ if (!pPr)
150
+ return undefined;
151
+ const out = {};
152
+ for (const [key, value] of Object.entries(pPr))
153
+ if (!['pPrChange', 'sectPr', 'PARENT'].includes(key))
154
+ out[key] = deepCopy(value);
155
+ const rPr = out.rPr;
156
+ if (rPr)
157
+ for (const key of ['ins', 'del', 'rPrChange', 'moveFrom', 'moveTo'])
158
+ delete rPr[key];
159
+ return Object.keys(out).length > 0 ? out : undefined;
160
+ }
161
+ /** The new cells of one row, each a `w:tc` of one paragraph with the template's shading and paragraph properties. */
162
+ function newCells(count, values, template) {
163
+ var _a, _b;
164
+ const out = [];
165
+ for (let i = 0; i < count; i++) {
166
+ // an empty cell holds an empty `w:p`, as Word writes one; a value its text
167
+ const text = (_a = values === null || values === void 0 ? void 0 : values[i]) !== null && _a !== void 0 ? _a : '';
168
+ const cell = cellElementOf(text === '' ? [paragraphOf([])] : text);
169
+ const tcPr = cellPropertiesFrom(template);
170
+ if (tcPr)
171
+ cell.value.tcPr = tcPr;
172
+ else
173
+ delete cell.value.tcPr;
174
+ const pPr = paragraphPropertiesFrom(template);
175
+ const paragraph = ((_b = childrenOf(cell.value)) !== null && _b !== void 0 ? _b : []).find((item) => item.value.TYPE_NAME === 'org_docx4j_wml.P');
176
+ if (pPr && paragraph) {
177
+ paragraph.value.pPr = pPr;
178
+ linkParents(pPr, paragraph.value);
179
+ }
180
+ out.push(cell);
181
+ }
182
+ return out;
183
+ }
184
+ /**
185
+ * Inserts `count` grid columns at `gridIndex` (0 to the column count). The new column copies the
186
+ * width of the column to the right of the boundary when there is one, else the left (check 40:
187
+ * Insert Right in a narrow cell beside a wide one took the wide width; Insert Right in a spanning
188
+ * cell took the column after it), and each row's new cell takes the properties of the cell on the
189
+ * same side, less its span and merges. A cell spanning the boundary keeps its span and the new
190
+ * cell goes before it (check 40b1); a row's `w:gridBefore` or `w:gridAfter` covering the boundary
191
+ * grows instead. `values` is by row, then by new column. While changes are tracked the table
192
+ * change is recorded as Word's Office JS records one (check 40c): the old properties, every
193
+ * cell's properties - the new cells' as made - the grid as it is after, and the new cells' text in
194
+ * a `w:ins`, with no mark insertion.
195
+ */
196
+ export function insertGridColumns(tbl, textWidth, gridIndex, count, values, tracker) {
197
+ var _a, _b;
198
+ if (!Number.isInteger(count) || count < 1)
199
+ throw new Docx4JException(`columnCount must be a positive integer, not ${count}`);
200
+ const old = gridWidthsOf(tbl, textWidth);
201
+ if (gridIndex < 0 || gridIndex > old.length)
202
+ throw new Docx4JException(`No column boundary ${gridIndex} in a grid of ${old.length} columns`);
203
+ const mode = layoutModeOf(tbl);
204
+ const copied = old[gridIndex < old.length ? gridIndex : old.length - 1];
205
+ const unscaled = [...old.slice(0, gridIndex), ...Array.from({ length: count }, () => copied), ...old.slice(gridIndex)];
206
+ const widths = mode === 'window' ? rescaled(unscaled, old.reduce((a, b) => a + b, 0)) : unscaled;
207
+ const rows = rowsOf(tbl).map((row) => row.element.value);
208
+ const plans = rows.map((tr) => planRow(tr, gridIndex));
209
+ // an AutoFit-to-contents add changes no width, and Office JS records nothing for it (check 41c)
210
+ const record = tracker && mode !== 'contents' ? recordTable(tbl, rows, tracker) : undefined;
211
+ const result = { cells: [] };
212
+ plans.forEach((plan, rowIndex) => {
213
+ var _a, _b;
214
+ const tr = plan.tr;
215
+ if (plan.skipped) {
216
+ plan.skipped.value.val = Number((_a = plan.skipped.value.val) !== null && _a !== void 0 ? _a : 0) + count;
217
+ result.cells.push([]);
218
+ return;
219
+ }
220
+ const cells = newCells(count, values === null || values === void 0 ? void 0 : values[rowIndex], plan.template);
221
+ plan.container.splice(plan.at, 0, ...cells);
222
+ const owner = ownerOfCells(tr, plan.container);
223
+ for (const cell of cells)
224
+ linkParents(cell, owner);
225
+ if (tracker)
226
+ for (const cell of cells)
227
+ for (const block of (_b = childrenOf(cell.value)) !== null && _b !== void 0 ? _b : [])
228
+ wrapInsertedRuns(tracker, childrenOf(block.value), block.value);
229
+ result.cells.push(cells);
230
+ });
231
+ // the grid, the table's width, and the cells' widths in their forms
232
+ writeGrid(tbl, widths);
233
+ const tblW = (_a = tbl.tblPr) === null || _a === void 0 ? void 0 : _a.tblW;
234
+ if (mode === 'fixed' && (tblW === null || tblW === void 0 ? void 0 : tblW.type) === 'dxa')
235
+ tblW.w = Number((_b = tblW.w) !== null && _b !== void 0 ? _b : 0) + copied * count;
236
+ writeCellWidths(tbl, widths, mode === 'window' ? 'all' : result.cells.flat().map((c) => c.value));
237
+ if (record)
238
+ record(result.cells.flat().map((c) => c.value), { unscaled, scaled: widths });
239
+ return result;
240
+ }
241
+ /** Where the boundary falls in a row: at or inside a cell (the new cells go before it), in its skipped columns, or after its last cell. */
242
+ function planRow(tr, gridIndex) {
243
+ var _a, _b, _c;
244
+ const before = rowMember(tr, 'gridBefore');
245
+ const after = rowMember(tr, 'gridAfter');
246
+ const skippedBefore = Number((_a = before === null || before === void 0 ? void 0 : before.value.val) !== null && _a !== void 0 ? _a : 0);
247
+ if (before && gridIndex < skippedBefore)
248
+ return { tr, skipped: before };
249
+ const cells = cellsOf(tr);
250
+ let at = skippedBefore;
251
+ for (const cell of cells) {
252
+ const tc = cell.element.value;
253
+ const end = at + spanOf(tc);
254
+ // at the cell's start, or inside its span (check 40b1: the span kept, the new cell before it)
255
+ if (gridIndex >= at && gridIndex < end)
256
+ return { tr, container: cell.container, at: cell.container.indexOf(cell.element), template: tc };
257
+ at = end;
258
+ }
259
+ const skippedAfter = Number((_b = after === null || after === void 0 ? void 0 : after.value.val) !== null && _b !== void 0 ? _b : 0);
260
+ if (after && gridIndex < at + skippedAfter)
261
+ return { tr, skipped: after };
262
+ const last = cells[cells.length - 1];
263
+ if (!last)
264
+ return { tr, container: ((_c = tr.content) !== null && _c !== void 0 ? _c : (tr.content = [])), at: tr.content.length, template: undefined };
265
+ return { tr, container: last.container, at: last.container.indexOf(last.element) + 1, template: last.element.value };
266
+ }
267
+ /** Every cell's `w:tcW` from the grid (`all`), or only the given cells', each in the form it takes. */
268
+ function writeCellWidths(tbl, widths, which) {
269
+ var _a, _b;
270
+ for (const row of rowsOf(tbl)) {
271
+ const tr = row.element.value;
272
+ let at = Number((_b = (_a = rowMember(tr, 'gridBefore')) === null || _a === void 0 ? void 0 : _a.value.val) !== null && _b !== void 0 ? _b : 0);
273
+ for (const cell of cellsOf(tr)) {
274
+ const tc = cell.element.value;
275
+ const end = at + spanOf(tc);
276
+ if (which === 'all' || which.includes(tc))
277
+ writeCellWidth(tc, tbl, widths, at, end);
278
+ at = end;
279
+ }
280
+ }
281
+ }
282
+ /**
283
+ * What Word records for a column added or removed through Office JS with tracking on (checks 40c
284
+ * and 41c, the form of section 35's table change): a `w:tblPrChange` with the properties as they
285
+ * were, a `w:tcPrChange` on every cell with its properties as they were - a new cell's as made -
286
+ * and a `w:tblGridChange` holding the grid **as it is after the change**. Where the change
287
+ * rescaled the columns (AutoFit to window), the recorded grid is instead the old and new column
288
+ * edges together - the unscaled grid's edges and the scaled grid's, merged - with every cell's
289
+ * record spanning the merged columns its unscaled extent covers, and a new cell's recorded width a
290
+ * placeholder (`1 pct`; check 27's form, as Word wrote it in check 41c), so that rejecting puts
291
+ * the old widths back over a grid that `collapseGrid` then folds. Taken before the change and
292
+ * written after it, so that `TrackedChange` lists the whole as one table change. A record already
293
+ * there (an earlier pending change) is kept, so the original state stays the recorded one.
294
+ */
295
+ function recordTable(tbl, rows, tracker) {
296
+ var _a, _b;
297
+ const tblPr = tbl.tblPr;
298
+ const oldTblPr = tblPr && !tblPr.tblPrChange ? without(tblPr, ['tblPrChange']) : undefined;
299
+ const recordGrid = !((_a = tbl.tblGrid) === null || _a === void 0 ? void 0 : _a.tblGridChange);
300
+ const oldCells = [];
301
+ for (const tr of rows)
302
+ for (const cell of cellsOf(tr)) {
303
+ const tc = cell.element.value;
304
+ if (!((_b = tc.tcPr) === null || _b === void 0 ? void 0 : _b.tcPrChange))
305
+ oldCells.push([tc, recordedCellProperties(tc)]);
306
+ }
307
+ return (added = [], rescale) => {
308
+ var _a, _b, _c;
309
+ if (oldTblPr && tbl.tblPr) {
310
+ tbl.tblPr.tblPrChange = { TYPE_NAME: 'org_docx4j_wml.CTTblPrChange', ...tracker.markup(), tblPr: { TYPE_NAME: 'org_docx4j_wml.CTTblPrBase', ...oldTblPr } };
311
+ linkParents(tbl.tblPr.tblPrChange, tbl.tblPr);
312
+ }
313
+ const merged = rescale && rescale.unscaled.some((w, i) => w !== rescale.scaled[i]) ? mergedEdges(rescale.unscaled, rescale.scaled) : undefined;
314
+ if (recordGrid && tbl.tblGrid) {
315
+ const gridCol = merged ? merged.widths.map((w) => ({ TYPE_NAME: 'org_docx4j_wml.TblGridCol', w })) : ((_a = tbl.tblGrid.gridCol) !== null && _a !== void 0 ? _a : []).map((col) => deepCopy(col));
316
+ tbl.tblGrid.tblGridChange = { TYPE_NAME: 'org_docx4j_wml.CTTblGridChange', id: tracker.nextId(), tblGrid: { TYPE_NAME: 'org_docx4j_wml.TblGridBase', gridCol } };
317
+ linkParents(tbl.tblGrid.tblGridChange, tbl.tblGrid);
318
+ }
319
+ const extents = merged ? cellExtents(tbl) : undefined;
320
+ const cells = [...oldCells.filter(([tc]) => isInTable(tc, tbl)), ...added.map((tc) => [tc, recordedCellProperties(tc)])];
321
+ for (const [tc, old] of cells) {
322
+ const tcPr = ((_b = tc.tcPr) !== null && _b !== void 0 ? _b : (tc.tcPr = {}));
323
+ const recorded = { ...old };
324
+ if (merged && extents) {
325
+ // over the merged grid: the columns the cell's unscaled extent covers
326
+ const extent = extents.get(tc);
327
+ if (extent) {
328
+ const span = merged.index(unscaledEdge(rescale.unscaled, extent.end)) - merged.index(unscaledEdge(rescale.unscaled, extent.start));
329
+ if (span > 1)
330
+ recorded.gridSpan = { val: span };
331
+ else
332
+ delete recorded.gridSpan;
333
+ }
334
+ if (added.includes(tc) && ((_c = recorded.tcW) === null || _c === void 0 ? void 0 : _c.type) === 'pct')
335
+ recorded.tcW = { w: 1, type: 'pct' };
336
+ }
337
+ tcPr.tcPrChange = { TYPE_NAME: 'org_docx4j_wml.CTTcPrChange', ...tracker.markup(), tcPr: { TYPE_NAME: 'org_docx4j_wml.TcPrInner', ...recorded } };
338
+ linkParents(tcPr.tcPrChange, tcPr);
339
+ }
340
+ };
341
+ }
342
+ /** The cumulative edge, in twips, of a column index over a grid. */
343
+ function unscaledEdge(widths, column) {
344
+ return widths.slice(0, column).reduce((a, b) => a + b, 0);
345
+ }
346
+ /** The old and new grids' edges together, as merged columns, and where an edge falls among them. */
347
+ function mergedEdges(unscaled, scaled) {
348
+ const edges = new Set([0]);
349
+ let at = 0;
350
+ for (const w of unscaled) {
351
+ at += w;
352
+ edges.add(at);
353
+ }
354
+ at = 0;
355
+ for (const w of scaled) {
356
+ at += w;
357
+ edges.add(at);
358
+ }
359
+ const sorted = [...edges].sort((a, b) => a - b);
360
+ return { widths: sorted.slice(1).map((edge, i) => edge - sorted[i]), index: (edge) => sorted.indexOf(edge) };
361
+ }
362
+ /** Each cell's column extent in the grid as it is now, through the rows' spans and skipped columns. */
363
+ function cellExtents(tbl) {
364
+ var _a, _b;
365
+ const out = new Map();
366
+ for (const row of rowsOf(tbl)) {
367
+ const tr = row.element.value;
368
+ let at = Number((_b = (_a = rowMember(tr, 'gridBefore')) === null || _a === void 0 ? void 0 : _a.value.val) !== null && _b !== void 0 ? _b : 0);
369
+ for (const cell of cellsOf(tr)) {
370
+ const tc = cell.element.value;
371
+ const end = at + spanOf(tc);
372
+ out.set(tc, { start: at, end });
373
+ at = end;
374
+ }
375
+ }
376
+ return out;
377
+ }
378
+ /** A cell's properties as a record holds them: everything but the markers and an earlier record. */
379
+ function recordedCellProperties(tc) {
380
+ return without(tc.tcPr, ['tcPrChange', 'cellIns', 'cellDel', 'cellMerge']);
381
+ }
382
+ /** Whether a cell is still in the table (a deleted column's cells are not). */
383
+ function isInTable(tc, tbl) {
384
+ return rowsOf(tbl).some((row) => cellsOf(row.element.value).some((cell) => cell.element.value === tc));
385
+ }
386
+ function without(value, keys) {
387
+ const out = {};
388
+ for (const [key, v] of Object.entries(value !== null && value !== void 0 ? value : {}))
389
+ if (!keys.includes(key) && key !== 'PARENT')
390
+ out[key] = deepCopy(v);
391
+ return out;
392
+ }
393
+ /**
394
+ * Removes `count` grid columns from `columnIndex`: a cell covering only removed columns goes, one
395
+ * spanning further shrinks, a row's skipped columns shrink; the grid loses the columns, a `w:tblW`
396
+ * in twips loses their width, and in window mode the grid is rescaled to the width it had and the
397
+ * cells' percentages written again. While changes are tracked the cells still go, untracked, as
398
+ * Word's own Delete Columns and Office JS's `deleteColumns` leave them (known issues, entry 2;
399
+ * check 40c), and the table change is recorded as Office JS records it: the old properties, the
400
+ * remaining cells' properties, the grid as it is after.
401
+ */
402
+ export function deleteGridColumns(tbl, textWidth, columnIndex, count, tracker) {
403
+ var _a, _b, _c, _d;
404
+ if (!Number.isInteger(count) || count < 1)
405
+ throw new Docx4JException(`columnCount must be a positive integer, not ${count}`);
406
+ const old = gridWidthsOf(tbl, textWidth);
407
+ if (columnIndex < 0 || columnIndex + count > old.length)
408
+ throw new Docx4JException(`No columns ${columnIndex} to ${columnIndex + count - 1} in a grid of ${old.length} columns`);
409
+ const mode = layoutModeOf(tbl);
410
+ const record = tracker && mode !== 'contents' ? recordTable(tbl, rowsOf(tbl).map((row) => row.element.value), tracker) : undefined;
411
+ const from = columnIndex;
412
+ const to = columnIndex + count;
413
+ const overlap = (start, end) => Math.max(0, Math.min(end, to) - Math.max(start, from));
414
+ for (const row of rowsOf(tbl)) {
415
+ const tr = row.element.value;
416
+ const before = rowMember(tr, 'gridBefore');
417
+ const skippedBefore = Number((_a = before === null || before === void 0 ? void 0 : before.value.val) !== null && _a !== void 0 ? _a : 0);
418
+ if (before)
419
+ before.value.val = skippedBefore - overlap(0, skippedBefore);
420
+ let at = skippedBefore;
421
+ for (const cell of cellsOf(tr)) {
422
+ const tc = cell.element.value;
423
+ const span = spanOf(tc);
424
+ const end = at + span;
425
+ const covered = overlap(at, end);
426
+ if (covered === span) {
427
+ const i = cell.container.indexOf(cell.element);
428
+ if (i >= 0)
429
+ cell.container.splice(i, 1);
430
+ }
431
+ else if (covered > 0) {
432
+ setSpan(tc, span - covered);
433
+ }
434
+ at = end;
435
+ }
436
+ const after = rowMember(tr, 'gridAfter');
437
+ if (after) {
438
+ const skippedAfter = Number((_b = after.value.val) !== null && _b !== void 0 ? _b : 0);
439
+ after.value.val = skippedAfter - overlap(at, at + skippedAfter);
440
+ }
441
+ }
442
+ const removed = old.slice(from, to).reduce((a, b) => a + b, 0);
443
+ let widths = [...old.slice(0, from), ...old.slice(to)];
444
+ if (mode === 'window')
445
+ widths = rescaled(widths, old.reduce((a, b) => a + b, 0));
446
+ writeGrid(tbl, widths);
447
+ const tblW = (_c = tbl.tblPr) === null || _c === void 0 ? void 0 : _c.tblW;
448
+ if ((tblW === null || tblW === void 0 ? void 0 : tblW.type) === 'dxa')
449
+ tblW.w = Math.max(0, Number((_d = tblW.w) !== null && _d !== void 0 ? _d : 0) - removed);
450
+ writeCellWidths(tbl, widths, mode === 'window' ? 'all' : shrunkCells(tbl));
451
+ if (record)
452
+ record();
453
+ }
454
+ /** The cells whose span no longer matches their stated twips width: those that shrank. */
455
+ function shrunkCells(tbl) {
456
+ var _a, _b, _c, _d, _e, _f, _g;
457
+ const out = [];
458
+ const widths = (_c = (_b = (_a = tbl.tblGrid) === null || _a === void 0 ? void 0 : _a.gridCol) === null || _b === void 0 ? void 0 : _b.map((col) => { var _a; return Number((_a = col.w) !== null && _a !== void 0 ? _a : 0); })) !== null && _c !== void 0 ? _c : [];
459
+ for (const row of rowsOf(tbl)) {
460
+ const tr = row.element.value;
461
+ let at = Number((_e = (_d = rowMember(tr, 'gridBefore')) === null || _d === void 0 ? void 0 : _d.value.val) !== null && _e !== void 0 ? _e : 0);
462
+ for (const cell of cellsOf(tr)) {
463
+ const tc = cell.element.value;
464
+ const end = at + spanOf(tc);
465
+ const tcW = (_f = tc.tcPr) === null || _f === void 0 ? void 0 : _f.tcW;
466
+ if ((tcW === null || tcW === void 0 ? void 0 : tcW.type) === 'dxa' && Number((_g = tcW.w) !== null && _g !== void 0 ? _g : 0) !== widths.slice(at, end).reduce((a, b) => a + b, 0))
467
+ out.push(tc);
468
+ at = end;
469
+ }
470
+ }
471
+ return out;
472
+ }
473
+ /** The grid columns a cell covers: its start and the one past its end. */
474
+ export function gridRangeOf(tr, tc) {
475
+ var _a, _b;
476
+ let at = Number((_b = (_a = rowMember(tr, 'gridBefore')) === null || _a === void 0 ? void 0 : _a.value.val) !== null && _b !== void 0 ? _b : 0);
477
+ for (const cell of cellsOf(tr)) {
478
+ const end = at + spanOf(cell.element.value);
479
+ if (cell.element.value === tc)
480
+ return { start: at, end };
481
+ at = end;
482
+ }
483
+ return undefined;
484
+ }
@@ -5,6 +5,7 @@ import { CustomXmlPart, type CustomXmlPartOwner } from './CustomXmlPart.mjs';
5
5
  import type { CustomXmlPartLookup } from './XmlMapping.mjs';
6
6
  import type { XPathEngine } from './xpath.mjs';
7
7
  import { type BindingResult } from './bindings.mjs';
8
+ import { type XhtmlHost, type XhtmlBindingOptions } from './xhtml.mjs';
8
9
  /**
9
10
  * What the collection needs of its package. A structural interface, so that this module does not
10
11
  * import `WordprocessingMLPackage` (which imports it): the package hands itself in.
@@ -18,6 +19,8 @@ export interface CustomXmlHost {
18
19
  boundBodies(): Body[];
19
20
  /** Where a new custom XML part's relationship goes: the main document part (Word drops parts related from the package alone). */
20
21
  customXmlRelationshipSource(): Part;
22
+ /** The package, for the XHTML pass (list definitions, styles, relationships); absent, XHTML controls are left and noted. */
23
+ readonly xhtmlHost?: XhtmlHost;
21
24
  }
22
25
  /** A subset of Office JS `Word.CustomXmlPartCollection`. */
23
26
  export declare class CustomXmlPartCollection implements CustomXmlPartOwner, CustomXmlPartLookup {
@@ -62,7 +65,9 @@ export declare class CustomXmlPartCollection implements CustomXmlPartOwner, Cust
62
65
  * which is what Word does when it opens the document. Unmarshals the main document part (and the
63
66
  * headers and footers it has) and parses the custom XML parts.
64
67
  */
65
- applyBindings(): Promise<BindingResult>;
68
+ applyBindings(options?: {
69
+ html?: XhtmlBindingOptions;
70
+ }): Promise<BindingResult>;
66
71
  /**
67
72
  * docx4j `UpdateXmlFromDocumentSurface.updateCustomXmlParts`: writes what the controls now show
68
73
  * back into the custom XML. The reverse of `applyBindings`, and what makes an edit to a bound
@@ -14,6 +14,8 @@ import { parseXml, decodeXmlText } from '../../xml/dom.mjs';
14
14
  import { createDatastoreItem } from '@docx4j/generated-objects-ts/factory/org_docx4j_customXmlProperties';
15
15
  import { CustomXmlPart } from './CustomXmlPart.mjs';
16
16
  import { applyBindingsTo, updateFromControls } from './bindings.mjs';
17
+ import { applyXhtmlBindingsTo, isXhtmlBound } from './xhtml.mjs';
18
+ import { applyPictureHandlersTo, updateFromPictureControls } from './pictures.mjs';
17
19
  /** A subset of Office JS `Word.CustomXmlPartCollection`. */
18
20
  export class CustomXmlPartCollection {
19
21
  constructor(host) {
@@ -133,9 +135,19 @@ export class CustomXmlPartCollection {
133
135
  * which is what Word does when it opens the document. Unmarshals the main document part (and the
134
136
  * headers and footers it has) and parses the custom XML parts.
135
137
  */
136
- async applyBindings() {
138
+ async applyBindings(options = {}) {
139
+ var _a, _b;
137
140
  await this.load();
138
- return applyBindingsTo(this.controlsOf(await this.host.getBoundBodies()));
141
+ const controls = this.controlsOf(await this.host.getBoundBodies());
142
+ const result = applyBindingsTo(controls);
143
+ // od:Handler=picture through the tag's od:xpath entry (CR-005 section 9): after the text bindings, which took the picture controls.
144
+ applyPictureHandlersTo(controls, this, result);
145
+ // Escaped XHTML through the tag's od:xpath entry (CR-005 section 8.2): after the text bindings, asynchronous.
146
+ if (this.host.xhtmlHost)
147
+ await applyXhtmlBindingsTo(controls, this, this.host.xhtmlHost, result, (_a = options.html) !== null && _a !== void 0 ? _a : {});
148
+ else if (controls.some(isXhtmlBound))
149
+ ((_b = result.notes) !== null && _b !== void 0 ? _b : (result.notes = [])).push('XHTML-bound controls left: the collection has no package to make their lists, styles and links');
150
+ return result;
139
151
  }
140
152
  /**
141
153
  * docx4j `UpdateXmlFromDocumentSurface.updateCustomXmlParts`: writes what the controls now show
@@ -144,7 +156,11 @@ export class CustomXmlPartCollection {
144
156
  */
145
157
  async updateFromContentControls() {
146
158
  await this.load();
147
- return updateFromControls(this.controlsOf(await this.host.getBoundBodies()));
159
+ const controls = this.controlsOf(await this.host.getBoundBodies());
160
+ const result = updateFromControls(controls);
161
+ // a mapped picture control's image back to its node, as Word 15 writes it (CR-005 section 9, check 38)
162
+ await updateFromPictureControls(controls, result);
163
+ return result;
148
164
  }
149
165
  controlsOf(bodies) {
150
166
  return bodies.flatMap((body) => body.contentControls);
@@ -12,6 +12,8 @@ export interface BindingResult {
12
12
  updated: number;
13
13
  /** Controls left alone: the part is missing, the XPath selects nothing, or the kind is not handled. */
14
14
  skipped: number;
15
+ /** What the XHTML and picture passes left or dropped, one line each (CR-005 sections 8.2 and 9; REQ-076). */
16
+ notes?: string[];
15
17
  }
16
18
  /**
17
19
  * Whether a node's value is a Flat OPC package: what Word writes into the node of a mapped rich
@@ -32,9 +34,10 @@ export declare function formatDate(pattern: string, date: Date, locale?: string)
32
34
  /**
33
35
  * Pushes the custom XML value into one control (docx4j BindingHandler's per-sdt work). Returns
34
36
  * false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
35
- * this phase does not handle (pictures, explicit rich text), or a node holding a Flat OPC package.
37
+ * this phase does not handle (explicit rich text), a node holding a Flat OPC package, or a
38
+ * picture control whose node holds no image (`notes` says which; CR-005 section 9).
36
39
  */
37
- export declare function applyBindingTo(control: ContentControl): boolean;
40
+ export declare function applyBindingTo(control: ContentControl, notes?: string[]): boolean;
38
41
  /** `applyBindings` over a list of controls. */
39
42
  export declare function applyBindingsTo(controls: ContentControl[]): BindingResult;
40
43
  /**