@docx4j/core-ts 0.3.1 → 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.
- package/README.md +3 -1
- package/dist/html/convert.d.mts +116 -0
- package/dist/html/convert.mjs +541 -0
- package/dist/html/elements.d.mts +19 -0
- package/dist/html/elements.mjs +182 -0
- package/dist/html/index.d.mts +2 -0
- package/dist/html/index.mjs +4 -0
- package/dist/model/content/Body.mjs +9 -1
- package/dist/model/content/ContentControl.d.mts +5 -1
- package/dist/model/content/ContentControl.mjs +18 -5
- package/dist/model/content/InlinePicture.d.mts +18 -3
- package/dist/model/content/InlinePicture.mjs +44 -7
- package/dist/model/content/Paragraph.d.mts +11 -11
- package/dist/model/content/Paragraph.mjs +30 -43
- package/dist/model/content/Range.mjs +3 -2
- package/dist/model/content/Table.d.mts +38 -0
- package/dist/model/content/Table.mjs +80 -2
- package/dist/model/content/index.d.mts +2 -1
- package/dist/model/content/index.mjs +2 -1
- package/dist/model/content/stylesFacade.d.mts +4 -2
- package/dist/model/content/stylesFacade.mjs +23 -6
- package/dist/model/content/tableColumns.d.mts +48 -0
- package/dist/model/content/tableColumns.mjs +484 -0
- package/dist/model/content/tracking.d.mts +14 -0
- package/dist/model/content/tracking.mjs +19 -10
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +6 -1
- package/dist/model/customxml/CustomXmlPartCollection.mjs +19 -3
- package/dist/model/customxml/XmlMapping.d.mts +20 -4
- package/dist/model/customxml/XmlMapping.mjs +44 -6
- package/dist/model/customxml/bindings.d.mts +13 -3
- package/dist/model/customxml/bindings.mjs +38 -7
- package/dist/model/customxml/index.d.mts +4 -1
- package/dist/model/customxml/index.mjs +4 -1
- package/dist/model/customxml/insert.d.mts +10 -0
- package/dist/model/customxml/insert.mjs +13 -0
- package/dist/model/customxml/opendope.d.mts +32 -0
- package/dist/model/customxml/opendope.mjs +56 -0
- package/dist/model/customxml/pictures.d.mts +54 -0
- package/dist/model/customxml/pictures.mjs +186 -0
- package/dist/model/customxml/xhtml.d.mts +42 -0
- package/dist/model/customxml/xhtml.mjs +162 -0
- package/dist/model/fonts/RunFontSelector.d.mts +8 -1
- package/dist/model/fonts/RunFontSelector.mjs +6 -5
- package/dist/model/properties/PropertyResolver.d.mts +110 -10
- package/dist/model/properties/PropertyResolver.mjs +300 -31
- package/dist/model/properties/index.d.mts +3 -0
- package/dist/model/properties/index.mjs +2 -0
- package/dist/model/properties/table.d.mts +155 -0
- package/dist/model/properties/table.mjs +315 -0
- package/dist/model/properties/tableStyleConditions.d.mts +101 -0
- package/dist/model/properties/tableStyleConditions.mjs +296 -0
- package/dist/packages/WordprocessingMLPackage.mjs +1 -0
- package/dist/parts/wml/knownStyles.d.mts +1 -0
- package/dist/parts/wml/knownStyles.mjs +2233 -0
- package/dist/parts/wml/placeholderStyle.d.mts +3 -0
- package/dist/parts/wml/placeholderStyle.mjs +13 -0
- package/package.json +7 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PasteBlock } from './convert.mjs';
|
|
2
|
+
/** Word's character style for a link's text, as `Range.hyperlink` gives it. */
|
|
3
|
+
export declare const HYPERLINK_STYLE = "Hyperlink";
|
|
4
|
+
/** What the blocks need from the document before they are built: a definition per list key, a relationship per link address. */
|
|
5
|
+
export interface BuildNeeds {
|
|
6
|
+
lists: {
|
|
7
|
+
key: string;
|
|
8
|
+
bullet: boolean;
|
|
9
|
+
}[];
|
|
10
|
+
links: string[];
|
|
11
|
+
}
|
|
12
|
+
/** The document's answers: `w:numId` per list key, `r:id` per address; undefined drops the list or the link (its text kept). */
|
|
13
|
+
export interface BuildResolvers {
|
|
14
|
+
numId?: (key: string) => string | undefined;
|
|
15
|
+
relId?: (address: string) => string | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare function needsOf(blocks: readonly PasteBlock[]): BuildNeeds;
|
|
18
|
+
/** The blocks as a WordprocessingML fragment: `w:p` and `w:tbl` elements, the prefixes undeclared (`insertXml` declares them). */
|
|
19
|
+
export declare function blocksToXml(blocks: readonly PasteBlock[], resolve?: BuildResolvers): string;
|
|
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
27
|
+
const attr = (value) => String(value).replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
|
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
|
+
}
|
|
@@ -6,7 +6,7 @@ import { Paragraph } from './Paragraph.mjs';
|
|
|
6
6
|
import { Table } from './Table.mjs';
|
|
7
7
|
import { ContentControl, collectControls } from './ContentControl.mjs';
|
|
8
8
|
import { sdt as sdtOf, nextSdtId } from '@docx4j/generated-objects-ts/builders/wml';
|
|
9
|
-
import { controlIdScope, sdtKindFor } from '../customxml/insert.mjs';
|
|
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
12
|
import { expandReplacement } from './search.mjs';
|
|
@@ -239,11 +239,18 @@ export class Body {
|
|
|
239
239
|
* `values` fills it row by row. The columns are equal over the section's text width.
|
|
240
240
|
*/
|
|
241
241
|
insertTable(rowCount, columnCount, location, values) {
|
|
242
|
+
var _a;
|
|
243
|
+
var _b;
|
|
242
244
|
if (rowCount < 1 || columnCount < 1)
|
|
243
245
|
throw new Docx4JException(`A table needs at least one row and one column; got ${rowCount} by ${columnCount}`);
|
|
244
246
|
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
247
|
const width = writableWidthTwips(this.container);
|
|
246
248
|
const element = tableOf(rows, width ? { width } : {});
|
|
249
|
+
// Word's own default (Insert Table, fixed column width Auto; the editor's check 42, Word 365): an
|
|
250
|
+
// automatic table width, no w:tblLayout, the cells in twips sharing the text width - the grid and
|
|
251
|
+
// cells the builder wrote, the table's width made automatic (CR-002 section 42.5 item 7).
|
|
252
|
+
const tblPr = ((_a = (_b = element.value).tblPr) !== null && _a !== void 0 ? _a : (_b.tblPr = {}));
|
|
253
|
+
tblPr.tblW = { w: 0, type: 'auto' };
|
|
247
254
|
this.insertElement(element, location);
|
|
248
255
|
return new Table(element, this.content, this);
|
|
249
256
|
}
|
|
@@ -748,6 +755,7 @@ export class Body {
|
|
|
748
755
|
this.content.length = 0;
|
|
749
756
|
this.content.push(sdt);
|
|
750
757
|
linkParents(sdt, this.container);
|
|
758
|
+
declareSdtExtensions(this, sdt);
|
|
751
759
|
// under tracking the control itself is the insertion, its content what it was (check 30)
|
|
752
760
|
const tracker = this.changeTracker;
|
|
753
761
|
if (tracker)
|
|
@@ -140,7 +140,11 @@ export declare class ContentControl {
|
|
|
140
140
|
get repeatingSectionContentControl(): RepeatingSectionContentControl | undefined;
|
|
141
141
|
/** The group view, for a `w:group` control (Office JS: an object with no members of its own). */
|
|
142
142
|
get groupContentControl(): GroupContentControl | undefined;
|
|
143
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Adds or replaces a `w:sdtPr` child (extension). A `w15` element (`w15:appearance`, `w15:color`,
|
|
145
|
+
* `w15:dataBinding`, the repeating section) puts `w15` in the part's `mc:Ignorable`, as Word's own
|
|
146
|
+
* files have it: Word 2010 opens a part holding one only then (CR-002 section 40).
|
|
147
|
+
*/
|
|
144
148
|
putProperty(element: Element): void;
|
|
145
149
|
/** Removes a `w:sdtPr` child (extension). */
|
|
146
150
|
removeProperty(localPart: string, namespaceURI?: string): void;
|
|
@@ -6,7 +6,8 @@ import { marshalString, deepCopy } from '@docx4j/generated-objects-ts';
|
|
|
6
6
|
// nothing from here at run time (types only), so this direction is the only one.
|
|
7
7
|
import { XmlMapping } from '../customxml/XmlMapping.mjs';
|
|
8
8
|
import { CheckboxContentControl, DatePickerContentControl, ListContentControl, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKBOX_FONT, } from '../customxml/kinds.mjs';
|
|
9
|
-
import { runsForValue, updateFromControl, PLACEHOLDER_TEXT } from '../customxml/bindings.mjs';
|
|
9
|
+
import { runsForValue, updateFromControl, PLACEHOLDER_TEXT, PLACEHOLDER_STYLE } from '../customxml/bindings.mjs';
|
|
10
|
+
import { declareIgnorable } from './tracking.mjs';
|
|
10
11
|
import { sdtProperty, sdtKindOf, t as textItem } from '@docx4j/generated-objects-ts/builders/wml';
|
|
11
12
|
import { typeNameOf, childrenOf, textOf, runItemsOf, segmentsOf, linkParents, runOf, SDT_TYPES } from './tree.mjs';
|
|
12
13
|
import { Range } from './Range.mjs';
|
|
@@ -429,7 +430,11 @@ export class ContentControl {
|
|
|
429
430
|
get groupContentControl() {
|
|
430
431
|
return sdtProperty(this.sdt.sdtPr, 'group') ? new GroupContentControl() : undefined;
|
|
431
432
|
}
|
|
432
|
-
/**
|
|
433
|
+
/**
|
|
434
|
+
* Adds or replaces a `w:sdtPr` child (extension). A `w15` element (`w15:appearance`, `w15:color`,
|
|
435
|
+
* `w15:dataBinding`, the repeating section) puts `w15` in the part's `mc:Ignorable`, as Word's own
|
|
436
|
+
* files have it: Word 2010 opens a part holding one only then (CR-002 section 40).
|
|
437
|
+
*/
|
|
433
438
|
putProperty(element) {
|
|
434
439
|
var _a, _b;
|
|
435
440
|
var _c;
|
|
@@ -441,6 +446,8 @@ export class ContentControl {
|
|
|
441
446
|
items[at] = element;
|
|
442
447
|
else
|
|
443
448
|
items.push(element);
|
|
449
|
+
if (element.name.namespaceURI === W15_NS)
|
|
450
|
+
declareIgnorable(this.parentBody.part, 'w15');
|
|
444
451
|
}
|
|
445
452
|
/** Removes a `w:sdtPr` child (extension). */
|
|
446
453
|
removeProperty(localPart, namespaceURI) {
|
|
@@ -481,11 +488,11 @@ export class ContentControl {
|
|
|
481
488
|
* the runs of a run-level one.
|
|
482
489
|
*/
|
|
483
490
|
setBoundContent(runs, showingPlaceholder = false) {
|
|
484
|
-
var _a, _b;
|
|
485
|
-
var
|
|
491
|
+
var _a, _b, _c, _d;
|
|
492
|
+
var _e;
|
|
486
493
|
const sdt = this.sdt;
|
|
487
494
|
(_a = sdt.sdtContent) !== null && _a !== void 0 ? _a : (sdt.sdtContent = {});
|
|
488
|
-
const content = ((_b = (
|
|
495
|
+
const content = ((_b = (_e = sdt.sdtContent).content) !== null && _b !== void 0 ? _b : (_e.content = []));
|
|
489
496
|
const first = content.find((e) => typeNameOf(e) !== undefined);
|
|
490
497
|
if (this.form === 'Run' || (first && typeNameOf(first) === 'org_docx4j_wml.R')) {
|
|
491
498
|
content.length = 0;
|
|
@@ -505,6 +512,12 @@ export class ContentControl {
|
|
|
505
512
|
}
|
|
506
513
|
}
|
|
507
514
|
this.isShowingPlaceholder = showingPlaceholder;
|
|
515
|
+
// The placeholder run names the PlaceholderText style, which a document made here lacks: the
|
|
516
|
+
// package splices the definition in when it is saved, as it does a new hyperlink's style. Word
|
|
517
|
+
// 2010 drops a w:rStyle naming a style the part does not define (CR-002 section 40).
|
|
518
|
+
if (showingPlaceholder) {
|
|
519
|
+
(_d = (_c = this.parentBody.package_) === null || _c === void 0 ? void 0 : _c.requireStyles) === null || _d === void 0 ? void 0 : _d.call(_c, [PLACEHOLDER_STYLE]);
|
|
520
|
+
}
|
|
508
521
|
}
|
|
509
522
|
/** Shows a checkbox's glyph, as Word writes it: one run in the checkbox font (docx4j checkboxRun). */
|
|
510
523
|
setCheckboxGlyph(symbol) {
|
|
@@ -22,7 +22,9 @@ export interface ImageInfo {
|
|
|
22
22
|
* The size and kind of an image, from its header: PNG (IHDR and pHYs), JPEG (SOFn and the JFIF
|
|
23
23
|
* APP0 densities), GIF (the logical screen descriptor) and BMP (the DIB header, with its pixels
|
|
24
24
|
* 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.
|
|
25
|
+
* information, and the same 96 dpi default when a file declares none. An SVG document is told by
|
|
26
|
+
* its root element and has no pixel size (both 0): it can be the part behind an existing picture
|
|
27
|
+
* (CR-005 section 9, a binding that keeps the drawing) but not be placed as a new one.
|
|
26
28
|
*/
|
|
27
29
|
export declare function imageInfoOf(bytes: Uint8Array): ImageInfo;
|
|
28
30
|
/** The natural size of an image in EMU: pixels over the resolution it declares. */
|
|
@@ -49,10 +51,23 @@ export interface NewPicture {
|
|
|
49
51
|
imagePart: ImagePart;
|
|
50
52
|
relId: string;
|
|
51
53
|
}
|
|
54
|
+
/** A new image part, its relationship from the part it was added to, and what its header says. */
|
|
55
|
+
export interface NewImagePart {
|
|
56
|
+
imagePart: ImagePart;
|
|
57
|
+
relId: string;
|
|
58
|
+
info: ImageInfo;
|
|
59
|
+
}
|
|
52
60
|
/**
|
|
53
61
|
* 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
|
|
55
|
-
* `
|
|
62
|
+
* belongs to) with a relationship, and nothing else: docx4j `BinaryPartAbstractImage.createImagePart`
|
|
63
|
+
* (`BindingTraverserXSLT.createImagePartReturnRelId`, where a picture binding replaces an `r:embed`
|
|
64
|
+
* and keeps the drawing, CR-005 section 9). Throws, and adds nothing, when the bytes are not an
|
|
65
|
+
* image this package reads.
|
|
66
|
+
*/
|
|
67
|
+
export declare function addImagePart(source: Part, base64: string): NewImagePart;
|
|
68
|
+
/**
|
|
69
|
+
* Adds the image as a part of `source` with a relationship (`addImagePart`), and builds the run
|
|
70
|
+
* that shows it. docx4j: `BinaryPartAbstractImage.createImagePart` then `createImageInline`.
|
|
56
71
|
*/
|
|
57
72
|
export declare function addImage(source: Part, base64: string, scope: object, maxCx: number | undefined, options?: InlinePictureOptions): NewPicture;
|
|
58
73
|
/** The writable width of a section in EMU (page width less the margins), for scaling. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { inlinePicture } from '@docx4j/generated-objects-ts/builders/wml';
|
|
2
2
|
import { Docx4JException } from '../../opc/exceptions.mjs';
|
|
3
3
|
import { ContentTypes, IMAGE_CONTENT_TYPES_BY_EXTENSION } from '../../opc/ContentTypes.mjs';
|
|
4
|
-
import { base64Decode, base64Encode } from '../../xml/dom.mjs';
|
|
4
|
+
import { base64Decode, base64Encode, decodeXmlText } from '../../xml/dom.mjs';
|
|
5
5
|
import { ImagePart } from '../../parts/BinaryPart.mjs';
|
|
6
6
|
import { PartName } from '../../opc/PartName.mjs';
|
|
7
7
|
import { find, runOf } from './tree.mjs';
|
|
@@ -15,7 +15,9 @@ const EMU_PER_POINT = 12700;
|
|
|
15
15
|
* The size and kind of an image, from its header: PNG (IHDR and pHYs), JPEG (SOFn and the JFIF
|
|
16
16
|
* APP0 densities), GIF (the logical screen descriptor) and BMP (the DIB header, with its pixels
|
|
17
17
|
* per metre). docx4j reads these through XML Graphics Commons' ImageInfo; this is the same
|
|
18
|
-
* information, and the same 96 dpi default when a file declares none.
|
|
18
|
+
* information, and the same 96 dpi default when a file declares none. An SVG document is told by
|
|
19
|
+
* its root element and has no pixel size (both 0): it can be the part behind an existing picture
|
|
20
|
+
* (CR-005 section 9, a binding that keeps the drawing) but not be placed as a new one.
|
|
19
21
|
*/
|
|
20
22
|
export function imageInfoOf(bytes) {
|
|
21
23
|
const png = pngInfo(bytes);
|
|
@@ -30,8 +32,27 @@ export function imageInfoOf(bytes) {
|
|
|
30
32
|
const bmp = bmpInfo(bytes);
|
|
31
33
|
if (bmp)
|
|
32
34
|
return bmp;
|
|
35
|
+
const svg = svgInfo(bytes);
|
|
36
|
+
if (svg)
|
|
37
|
+
return svg;
|
|
33
38
|
const head = Array.from(bytes.subarray(0, 8)).map((b) => b.toString(16).padStart(2, '0')).join(' ');
|
|
34
|
-
throw new Docx4JException(`Unsupported image format (first bytes ${head}); PNG, JPEG, GIF and
|
|
39
|
+
throw new Docx4JException(`Unsupported image format (first bytes ${head}); PNG, JPEG, GIF, BMP and SVG are read here`);
|
|
40
|
+
}
|
|
41
|
+
/** An SVG document: an XML declaration, comments and a doctype allowed before the `svg` root. */
|
|
42
|
+
function svgInfo(b) {
|
|
43
|
+
if (b.length < 5)
|
|
44
|
+
return undefined;
|
|
45
|
+
let head;
|
|
46
|
+
try {
|
|
47
|
+
head = decodeXmlText(b.subarray(0, Math.min(b.length, 4096)));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
const body = head.replace(/^\uFEFF/, '').replace(/<\?xml[^>]*\?>/, '').replace(/<!--[\s\S]*?-->/g, '').replace(/<!DOCTYPE[^>]*>/i, '').trimStart();
|
|
53
|
+
if (!/^<(\w+:)?svg[\s>]/.test(body))
|
|
54
|
+
return undefined;
|
|
55
|
+
return { contentType: ContentTypes.IMAGE_SVG, extension: 'svg', widthPx: 0, heightPx: 0, dpiX: DEFAULT_DPI, dpiY: DEFAULT_DPI };
|
|
35
56
|
}
|
|
36
57
|
function be16(b, at) { return (b[at] << 8) | b[at + 1]; }
|
|
37
58
|
function be32(b, at) { return ((b[at] << 24) >>> 0) + (b[at + 1] << 16) + (b[at + 2] << 8) + b[at + 3]; }
|
|
@@ -144,11 +165,12 @@ export function naturalSizeEmu(info) {
|
|
|
144
165
|
}
|
|
145
166
|
/**
|
|
146
167
|
* Adds the image as a part of `source` (the main document part, or the header or footer the body
|
|
147
|
-
* belongs to) with a relationship, and
|
|
148
|
-
* `
|
|
168
|
+
* belongs to) with a relationship, and nothing else: docx4j `BinaryPartAbstractImage.createImagePart`
|
|
169
|
+
* (`BindingTraverserXSLT.createImagePartReturnRelId`, where a picture binding replaces an `r:embed`
|
|
170
|
+
* and keeps the drawing, CR-005 section 9). Throws, and adds nothing, when the bytes are not an
|
|
171
|
+
* image this package reads.
|
|
149
172
|
*/
|
|
150
|
-
export function
|
|
151
|
-
var _a, _b;
|
|
173
|
+
export function addImagePart(source, base64) {
|
|
152
174
|
const pkg = source.package;
|
|
153
175
|
if (!pkg)
|
|
154
176
|
throw new Docx4JException('This part is not in a package yet; add it before adding an image');
|
|
@@ -157,6 +179,21 @@ export function addImage(source, base64, scope, maxCx, options = {}) {
|
|
|
157
179
|
const imagePart = new ImagePart(freeImageName(pkg, info.extension), info.contentType);
|
|
158
180
|
imagePart.setBytes(bytes);
|
|
159
181
|
const rel = source.addTargetPart(imagePart);
|
|
182
|
+
return { imagePart, relId: rel.id, info };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Adds the image as a part of `source` with a relationship (`addImagePart`), and builds the run
|
|
186
|
+
* that shows it. docx4j: `BinaryPartAbstractImage.createImagePart` then `createImageInline`.
|
|
187
|
+
*/
|
|
188
|
+
export function addImage(source, base64, scope, maxCx, options = {}) {
|
|
189
|
+
var _a, _b;
|
|
190
|
+
const bytes = base64Decode(base64);
|
|
191
|
+
const info = imageInfoOf(bytes);
|
|
192
|
+
if (info.widthPx === 0 || info.heightPx === 0) {
|
|
193
|
+
throw new Docx4JException(`A ${info.extension.toUpperCase()} image has no pixel size to place it at; PNG, JPEG, GIF and BMP can be placed`);
|
|
194
|
+
}
|
|
195
|
+
const { imagePart, relId } = addImagePart(source, base64);
|
|
196
|
+
const rel = { id: relId };
|
|
160
197
|
let { cx, cy } = naturalSizeEmu(info);
|
|
161
198
|
if (options.width !== undefined) {
|
|
162
199
|
const wanted = Math.round(options.width * EMU_PER_POINT);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
|
|
2
|
+
import type { CellContext } from '../properties/table.mjs';
|
|
2
3
|
import { type Element, W_NS, type TextSegment, type TextViewOptions } from './tree.mjs';
|
|
3
4
|
import { Font, type FontTracking } from './Font.mjs';
|
|
4
5
|
import { Range } from './Range.mjs';
|
|
@@ -95,6 +96,16 @@ export declare class Paragraph {
|
|
|
95
96
|
get effectivePPr(): wml.PPr;
|
|
96
97
|
/** The paragraph mark's effective run properties (docx4j `getEffectiveParagraphMarkRPr`). */
|
|
97
98
|
get effectiveParagraphMarkRPr(): wml.RPr;
|
|
99
|
+
/**
|
|
100
|
+
* Where this paragraph sits in a table, as far as its formatting goes (docx4j
|
|
101
|
+
* `PropertyResolver.cellContextOf`; CR-007): the table style its table resolves to and the
|
|
102
|
+
* conditional formats it is under. Undefined outside a table, and in a text box, note or comment
|
|
103
|
+
* reached from a cell. Every effective read of the paragraph and of its runs passes it, so a run
|
|
104
|
+
* in a header row the table style makes bold reads bold. Each read walks the table's rows; code
|
|
105
|
+
* resolving every paragraph of a large table should walk with a `TableContextTracker` instead.
|
|
106
|
+
* Needs the package's resolver, as the effective reads do.
|
|
107
|
+
*/
|
|
108
|
+
get cellContext(): CellContext | undefined;
|
|
98
109
|
/** The `w:pPr` a formatting read works from: the resolved one, or this paragraph's own. */
|
|
99
110
|
private readPPr;
|
|
100
111
|
/**
|
|
@@ -195,17 +206,6 @@ export declare class Paragraph {
|
|
|
195
206
|
*/
|
|
196
207
|
delete(): void;
|
|
197
208
|
private trackedDelete;
|
|
198
|
-
/**
|
|
199
|
-
* A paragraph mark deleted under tracking gives the next paragraph this paragraph's properties,
|
|
200
|
-
* as a `w:pPrChange` on it recording its own: Word, at Delete at the end of a Heading 2 with a
|
|
201
|
-
* Normal paragraph after it, wrote the next paragraph `w:pStyle Heading2` with
|
|
202
|
-
* `<w:pPrChange><w:pPr/></w:pPrChange>`, and No Markup reads the joined line as a heading
|
|
203
|
-
* throughout (the editor's `measure-word15.docx`, 2026-09-30; CR-002 section 38). The Delete
|
|
204
|
-
* counterpart of check 29's Enter. Nothing where the two paragraphs' properties are the same
|
|
205
|
-
* (check 22's deleted marks). Accepting the mark then joins into a paragraph with these
|
|
206
|
-
* properties, and rejecting puts the next paragraph's back.
|
|
207
|
-
*/
|
|
208
|
-
private giveMarkPropertiesToNext;
|
|
209
209
|
/**
|
|
210
210
|
* The paragraph's content and its mark marked deleted, the paragraph element itself kept:
|
|
211
211
|
* what the cells of a deleted table row need (a `w:tc` must keep a block-level child, and Word
|
|
@@ -15,11 +15,11 @@ import { builtInOf, idOfBuiltIn, styleNameOf, styleIdOf } from './styles.mjs';
|
|
|
15
15
|
import { cellOf } from './Table.mjs';
|
|
16
16
|
import { ContentControl, collectRunControls } from './ContentControl.mjs';
|
|
17
17
|
import { sdt as sdtOf, nextSdtId } from '@docx4j/generated-objects-ts/builders/wml';
|
|
18
|
-
import { controlIdScope, sdtKindFor } from '../customxml/insert.mjs';
|
|
18
|
+
import { controlIdScope, sdtKindFor, declareSdtExtensions } from '../customxml/insert.mjs';
|
|
19
19
|
import { InlinePicture, addImage, writableWidthEmu } from './InlinePicture.mjs';
|
|
20
20
|
import { contentOf } from './ooxml.mjs';
|
|
21
21
|
import { commentApi } from './comments.mjs';
|
|
22
|
-
import { ChangeTracker, copyRPr, markDeleted, toDeletedText, highestAnnotationId, canonical, restorePPr, wrapInsertedRuns, markHolderInserted } from './tracking.mjs';
|
|
22
|
+
import { ChangeTracker, copyRPr, markDeleted, toDeletedText, highestAnnotationId, canonical, restorePPr, wrapInsertedRuns, markHolderInserted, pruneParagraphProperties } from './tracking.mjs';
|
|
23
23
|
import { trackedChangesOfParagraph } from './TrackedChange.mjs';
|
|
24
24
|
import { isListItem, listOf, listOrNullObjectOf, listItemOf, listItemOrNullObjectOf, startNewList, attachToList, detachFromList, } from './List.mjs';
|
|
25
25
|
const TWIPS_PER_POINT = 20;
|
|
@@ -125,11 +125,23 @@ export class Paragraph {
|
|
|
125
125
|
* changing it. Needs the package's resolver; see `Body.propertyResolver`.
|
|
126
126
|
*/
|
|
127
127
|
get effectivePPr() {
|
|
128
|
-
return this.parentBody.propertyResolver.getEffectivePPr(this.p.pPr);
|
|
128
|
+
return this.parentBody.propertyResolver.getEffectivePPr(this.p.pPr, this.cellContext);
|
|
129
129
|
}
|
|
130
130
|
/** The paragraph mark's effective run properties (docx4j `getEffectiveParagraphMarkRPr`). */
|
|
131
131
|
get effectiveParagraphMarkRPr() {
|
|
132
|
-
return this.parentBody.propertyResolver.getEffectiveParagraphMarkRPr(this.p.pPr);
|
|
132
|
+
return this.parentBody.propertyResolver.getEffectiveParagraphMarkRPr(this.p.pPr, this.cellContext);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Where this paragraph sits in a table, as far as its formatting goes (docx4j
|
|
136
|
+
* `PropertyResolver.cellContextOf`; CR-007): the table style its table resolves to and the
|
|
137
|
+
* conditional formats it is under. Undefined outside a table, and in a text box, note or comment
|
|
138
|
+
* reached from a cell. Every effective read of the paragraph and of its runs passes it, so a run
|
|
139
|
+
* in a header row the table style makes bold reads bold. Each read walks the table's rows; code
|
|
140
|
+
* resolving every paragraph of a large table should walk with a `TableContextTracker` instead.
|
|
141
|
+
* Needs the package's resolver, as the effective reads do.
|
|
142
|
+
*/
|
|
143
|
+
get cellContext() {
|
|
144
|
+
return this.parentBody.propertyResolver.cellContextOf(this.p);
|
|
133
145
|
}
|
|
134
146
|
/** The `w:pPr` a formatting read works from: the resolved one, or this paragraph's own. */
|
|
135
147
|
readPPr(options) {
|
|
@@ -157,6 +169,7 @@ export class Paragraph {
|
|
|
157
169
|
else
|
|
158
170
|
pPr.jc = { val };
|
|
159
171
|
this.settleFormatting();
|
|
172
|
+
pruneParagraphProperties(this.p); // no empty w:pPr left (the editor's finding on 0.3.1)
|
|
160
173
|
}
|
|
161
174
|
/**
|
|
162
175
|
* The alignment the paragraph resolves to with no `w:jc` of its own: its style's, else the
|
|
@@ -172,7 +185,8 @@ export class Paragraph {
|
|
|
172
185
|
if (!resolver)
|
|
173
186
|
return undefined;
|
|
174
187
|
const { jc: _jc, pPrChange: _change, PARENT: _parent, ...rest } = this.p.pPr;
|
|
175
|
-
|
|
188
|
+
// in a table cell what is inherited includes the table style's (CR-007)
|
|
189
|
+
return alignmentOf(resolver.getEffectivePPr(rest, resolver.cellContextOf(this.p)), true);
|
|
176
190
|
}
|
|
177
191
|
/** Indents and spacing in points, as Office JS; the effective values (CR-001 Phase B step 2). */
|
|
178
192
|
get leftIndent() { var _a, _b; return ((_b = (_a = this.effectivePPr.ind) === null || _a === void 0 ? void 0 : _a.left) !== null && _b !== void 0 ? _b : 0) / TWIPS_PER_POINT; }
|
|
@@ -222,7 +236,7 @@ export class Paragraph {
|
|
|
222
236
|
const holders = () => runsOf(this.p).map((r) => r.value);
|
|
223
237
|
if ((options === null || options === void 0 ? void 0 : options.direct) === true)
|
|
224
238
|
return new Font(holders, () => this.fontTracking());
|
|
225
|
-
return new Font(holders, () => this.fontTracking(), (rPr) => this.parentBody.propertyResolver.getEffectiveRPr(rPr, this.p.pPr), (rPr) => { var _a; return (_a = runFontSelectorOf(this.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
|
|
239
|
+
return new Font(holders, () => this.fontTracking(), (rPr) => this.parentBody.propertyResolver.getEffectiveRPr(rPr, this.p.pPr, this.cellContext), (rPr) => { var _a; return (_a = runFontSelectorOf(this.parentBody.package_)) === null || _a === void 0 ? void 0 : _a.asciiFontName(rPr); });
|
|
226
240
|
}
|
|
227
241
|
/** w14:paraId, the stable address Word gives paragraphs. */
|
|
228
242
|
get paraId() {
|
|
@@ -426,42 +440,13 @@ export class Paragraph {
|
|
|
426
440
|
return;
|
|
427
441
|
}
|
|
428
442
|
tracker.markParagraphDeleted(this.p);
|
|
429
|
-
this
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
* throughout (the editor's `measure-word15.docx`, 2026-09-30; CR-002 section 38). The Delete
|
|
437
|
-
* counterpart of check 29's Enter. Nothing where the two paragraphs' properties are the same
|
|
438
|
-
* (check 22's deleted marks). Accepting the mark then joins into a paragraph with these
|
|
439
|
-
* properties, and rejecting puts the next paragraph's back.
|
|
440
|
-
*/
|
|
441
|
-
giveMarkPropertiesToNext(tracker) {
|
|
442
|
-
const i = this.index;
|
|
443
|
-
const next = i >= 0 ? this.container[i + 1] : undefined;
|
|
444
|
-
if (!next || typeNameOf(next) !== 'org_docx4j_wml.P')
|
|
445
|
-
return;
|
|
446
|
-
const base = (pPr) => {
|
|
447
|
-
const out = {};
|
|
448
|
-
for (const [key, value] of Object.entries(pPr !== null && pPr !== void 0 ? pPr : {})) {
|
|
449
|
-
if (key !== 'TYPE_NAME' && key !== 'PARENT' && key !== 'rPr' && key !== 'sectPr' && key !== 'pPrChange' && value !== undefined)
|
|
450
|
-
out[key] = value;
|
|
451
|
-
}
|
|
452
|
-
return out;
|
|
453
|
-
};
|
|
454
|
-
const source = base(this.p.pPr);
|
|
455
|
-
const nextP = next.value;
|
|
456
|
-
if (canonical(source) === canonical(base(nextP.pPr)))
|
|
457
|
-
return;
|
|
458
|
-
const target = new Paragraph(next, this.container, this.parentBody).pPr();
|
|
459
|
-
tracker.recordPPrChange(target);
|
|
460
|
-
for (const key of Object.keys(base(target)))
|
|
461
|
-
delete target[key];
|
|
462
|
-
for (const [key, value] of Object.entries(source))
|
|
463
|
-
target[key] = deepCopy(value);
|
|
464
|
-
linkParents(target, nextP);
|
|
443
|
+
// Word, deleting a paragraph mark ALONE under tracking, gives the next paragraph this one's
|
|
444
|
+
// properties as a w:pPrChange (CR-002 section 38 item 2, the editor's measure-word15.docx), so
|
|
445
|
+
// that the joined line reads in them. This method deletes the whole paragraph, text and mark - the
|
|
446
|
+
// engine's only mark deletion, a Range being within one paragraph - and what Word writes for that
|
|
447
|
+
// is not measured (test/README.md check 33). 0.3.1 carried the properties here too, which turned
|
|
448
|
+
// an agent's new heading, put in after a paragraph it then deleted, into body text once accepted
|
|
449
|
+
// (the editor's finding on 0.3.1); until the check, nothing is carried.
|
|
465
450
|
}
|
|
466
451
|
/**
|
|
467
452
|
* The paragraph's content and its mark marked deleted, the paragraph element itself kept:
|
|
@@ -533,8 +518,9 @@ export class Paragraph {
|
|
|
533
518
|
if (!same) {
|
|
534
519
|
const rest = (props) => canonical(Object.fromEntries(Object.entries(props).filter(([key]) => !FORMATTING_MEMBERS.has(key))));
|
|
535
520
|
const resolver = (_b = this.parentBody.package_) === null || _b === void 0 ? void 0 : _b.propertyResolverOrUndefined;
|
|
521
|
+
const cell = resolver === null || resolver === void 0 ? void 0 : resolver.cellContextOf(this.p);
|
|
536
522
|
same = resolver !== undefined && rest(current) === rest(before)
|
|
537
|
-
&& canonical(formattingOf(resolver.getEffectivePPr(current), true)) === canonical(formattingOf(resolver.getEffectivePPr(before), true));
|
|
523
|
+
&& canonical(formattingOf(resolver.getEffectivePPr(current, cell), true)) === canonical(formattingOf(resolver.getEffectivePPr(before, cell), true));
|
|
538
524
|
}
|
|
539
525
|
if (same)
|
|
540
526
|
restorePPr(this.p, recorded);
|
|
@@ -826,6 +812,7 @@ export class Paragraph {
|
|
|
826
812
|
const sdt = sdtOf([this.element], { kind: sdtKindFor(kind), id: nextSdtId(controlIdScope(this.parentBody)), form: 'block' });
|
|
827
813
|
this.container.splice(at, 1, sdt);
|
|
828
814
|
linkParents(sdt, (_a = this.p.PARENT) !== null && _a !== void 0 ? _a : this.parentBody.container);
|
|
815
|
+
declareSdtExtensions(this.parentBody, sdt);
|
|
829
816
|
// under tracking the control itself is the insertion, the paragraph what it was (check 30)
|
|
830
817
|
const tracker = this.changeTracker;
|
|
831
818
|
if (tracker)
|