@beyondwork/docx-react-component 1.0.36 → 1.0.38
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 +103 -13
- package/package.json +1 -1
- package/src/api/package-version.ts +13 -0
- package/src/api/public-types.ts +402 -1
- package/src/core/commands/index.ts +18 -1
- package/src/core/commands/section-layout-commands.ts +58 -0
- package/src/core/commands/table-grid.ts +431 -0
- package/src/core/commands/table-structure-commands.ts +815 -55
- package/src/core/selection/mapping.ts +6 -0
- package/src/io/docx-session.ts +24 -9
- package/src/io/export/build-app-properties-xml.ts +88 -0
- package/src/io/export/serialize-comments.ts +6 -1
- package/src/io/export/serialize-footnotes.ts +10 -9
- package/src/io/export/serialize-headers-footers.ts +11 -10
- package/src/io/export/serialize-main-document.ts +328 -50
- package/src/io/export/serialize-numbering.ts +114 -24
- package/src/io/export/serialize-tables.ts +87 -11
- package/src/io/export/table-properties-xml.ts +174 -20
- package/src/io/export/twip.ts +66 -0
- package/src/io/normalize/normalize-text.ts +20 -0
- package/src/io/ooxml/parse-footnotes.ts +62 -1
- package/src/io/ooxml/parse-headers-footers.ts +62 -1
- package/src/io/ooxml/parse-main-document.ts +158 -1
- package/src/io/ooxml/parse-tables.ts +249 -0
- package/src/legal/bookmarks.ts +78 -0
- package/src/model/canonical-document.ts +45 -0
- package/src/review/store/scope-tag-diff.ts +130 -0
- package/src/runtime/document-layout.ts +4 -2
- package/src/runtime/document-navigation.ts +2 -306
- package/src/runtime/document-runtime.ts +287 -11
- package/src/runtime/layout/default-page-format.ts +96 -0
- package/src/runtime/layout/docx-font-loader.ts +143 -0
- package/src/runtime/layout/index.ts +233 -0
- package/src/runtime/layout/inert-layout-facet.ts +59 -0
- package/src/runtime/layout/layout-engine-instance.ts +628 -0
- package/src/runtime/layout/layout-invalidation.ts +257 -0
- package/src/runtime/layout/layout-measurement-provider.ts +175 -0
- package/src/runtime/layout/margin-preset-catalog.ts +178 -0
- package/src/runtime/layout/measurement-backend-canvas.ts +307 -0
- package/src/runtime/layout/measurement-backend-empirical.ts +208 -0
- package/src/runtime/layout/page-format-catalog.ts +233 -0
- package/src/runtime/layout/page-fragment-mapper.ts +179 -0
- package/src/runtime/layout/page-graph.ts +452 -0
- package/src/runtime/layout/page-layout-snapshot-adapter.ts +70 -0
- package/src/runtime/layout/page-story-resolver.ts +195 -0
- package/src/runtime/layout/paginated-layout-engine.ts +921 -0
- package/src/runtime/layout/project-block-fragments.ts +91 -0
- package/src/runtime/layout/public-facet.ts +1398 -0
- package/src/runtime/layout/resolved-formatting-document.ts +317 -0
- package/src/runtime/layout/resolved-formatting-state.ts +430 -0
- package/src/runtime/layout/table-render-plan.ts +229 -0
- package/src/runtime/render/block-fragment-projection.ts +35 -0
- package/src/runtime/render/decoration-resolver.ts +189 -0
- package/src/runtime/render/index.ts +57 -0
- package/src/runtime/render/pending-op-delta-reader.ts +129 -0
- package/src/runtime/render/render-frame-types.ts +317 -0
- package/src/runtime/render/render-kernel.ts +755 -0
- package/src/runtime/scope-tag-registry.ts +95 -0
- package/src/runtime/surface-projection.ts +1 -0
- package/src/runtime/text-ack-range.ts +49 -0
- package/src/runtime/view-state.ts +67 -0
- package/src/runtime/workflow-markup.ts +1 -5
- package/src/runtime/workflow-rail-segments.ts +280 -0
- package/src/ui/WordReviewEditor.tsx +99 -15
- package/src/ui/editor-runtime-boundary.ts +10 -1
- package/src/ui/editor-shell-view.tsx +6 -0
- package/src/ui/editor-surface-controller.tsx +3 -0
- package/src/ui/headless/chrome-registry.ts +501 -0
- package/src/ui/headless/scoped-chrome-policy.ts +183 -0
- package/src/ui/headless/selection-tool-context.ts +2 -0
- package/src/ui/headless/selection-tool-resolver.ts +36 -17
- package/src/ui/headless/selection-tool-types.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
- package/src/ui-tailwind/chrome/role-action-sets.ts +74 -0
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +163 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +57 -92
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +274 -138
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +90 -0
- package/src/ui-tailwind/chrome-overlay/index.ts +22 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +86 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
- package/src/ui-tailwind/chrome-overlay/tw-workspace-view-switcher.tsx +95 -0
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +337 -0
- package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +100 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +27 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +20 -2
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +93 -23
- package/src/ui-tailwind/editor-surface/predicted-position-map.ts +78 -0
- package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +63 -0
- package/src/ui-tailwind/editor-surface/predicted-tx-gate.ts +39 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +176 -6
- package/src/ui-tailwind/index.ts +33 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
- package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
- package/src/ui-tailwind/theme/editor-theme.css +505 -144
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +559 -0
- package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +2 -2
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +304 -166
- package/src/ui-tailwind/tw-review-workspace.tsx +163 -2
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
isSyntheticDocxNullAbstractDefinition,
|
|
4
4
|
isSyntheticDocxNullNumberingInstance,
|
|
5
5
|
} from "../ooxml/numbering-sentinels.ts";
|
|
6
|
+
import { twip } from "./twip.ts";
|
|
6
7
|
|
|
7
8
|
export const WORD_NUMBERING_CONTENT_TYPE =
|
|
8
9
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml";
|
|
@@ -48,14 +49,18 @@ export function serializeParagraphNumberingProperties(
|
|
|
48
49
|
return "";
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
return `<w:numPr><w:ilvl w:val="${numbering.level}"/><w:numId w:val="${escapeAttribute(
|
|
52
|
-
|
|
52
|
+
return `<w:numPr><w:ilvl w:val="${clampIlvl(numbering.level)}"/><w:numId w:val="${escapeAttribute(
|
|
53
|
+
clampNonNegativeIdString(
|
|
54
|
+
stripCanonicalPrefix(numbering.numberingInstanceId, "num:"),
|
|
55
|
+
),
|
|
53
56
|
)}"/></w:numPr>`;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
function serializeAbstractDefinition(definition: NumberingCatalog["abstractDefinitions"][string]): string {
|
|
57
60
|
const abstractNumId = escapeAttribute(
|
|
58
|
-
|
|
61
|
+
clampNonNegativeIdString(
|
|
62
|
+
stripCanonicalPrefix(definition.abstractNumberingId, "abstract-num:"),
|
|
63
|
+
),
|
|
59
64
|
);
|
|
60
65
|
const levels = [...definition.levels]
|
|
61
66
|
.sort((left, right) => left.level - right.level)
|
|
@@ -69,7 +74,10 @@ function serializeLevel(
|
|
|
69
74
|
level: NumberingCatalog["abstractDefinitions"][string]["levels"][number],
|
|
70
75
|
serializedLevel = level.level,
|
|
71
76
|
): string {
|
|
72
|
-
const start =
|
|
77
|
+
const start =
|
|
78
|
+
level.startAt !== undefined
|
|
79
|
+
? `<w:start w:val="${clampStart(level.startAt)}"/>`
|
|
80
|
+
: "";
|
|
73
81
|
const paragraphStyle = level.paragraphStyleId
|
|
74
82
|
? `<w:pStyle w:val="${escapeAttribute(level.paragraphStyleId)}"/>`
|
|
75
83
|
: "";
|
|
@@ -80,7 +88,7 @@ function serializeLevel(
|
|
|
80
88
|
: "";
|
|
81
89
|
const paragraphProperties = serializeLevelParagraphGeometry(level.paragraphGeometry);
|
|
82
90
|
|
|
83
|
-
return `<w:lvl w:ilvl="${serializedLevel}">${start}<w:numFmt w:val="${escapeAttribute(
|
|
91
|
+
return `<w:lvl w:ilvl="${clampIlvl(serializedLevel)}">${start}<w:numFmt w:val="${escapeAttribute(
|
|
84
92
|
level.format,
|
|
85
93
|
)}"/><w:lvlText w:val="${escapeAttribute(level.text)}"/>${paragraphStyle}${isLegal}${suffix}${justification}${paragraphProperties}</w:lvl>`;
|
|
86
94
|
}
|
|
@@ -93,7 +101,10 @@ function serializeLevelOverride(
|
|
|
93
101
|
return "";
|
|
94
102
|
}
|
|
95
103
|
|
|
96
|
-
const start =
|
|
104
|
+
const start =
|
|
105
|
+
level.startAt !== undefined
|
|
106
|
+
? `<w:start w:val="${clampStart(level.startAt)}"/>`
|
|
107
|
+
: "";
|
|
97
108
|
const format = level.format ? `<w:numFmt w:val="${escapeAttribute(level.format)}"/>` : "";
|
|
98
109
|
const text = level.text !== undefined
|
|
99
110
|
? `<w:lvlText w:val="${escapeAttribute(level.text)}"/>`
|
|
@@ -101,12 +112,17 @@ function serializeLevelOverride(
|
|
|
101
112
|
const paragraphStyle = level.paragraphStyleId
|
|
102
113
|
? `<w:pStyle w:val="${escapeAttribute(level.paragraphStyleId)}"/>`
|
|
103
114
|
: "";
|
|
115
|
+
// ST_OnOff element (A.3):
|
|
116
|
+
// - true → <w:isLgl/>.
|
|
117
|
+
// - false → <w:isLgl w:val="false"/> (explicit override of a parent
|
|
118
|
+
// abstract level that declared legal numbering on).
|
|
119
|
+
// - undefined → omit.
|
|
104
120
|
const isLegal =
|
|
105
|
-
level.isLegalNumbering ===
|
|
106
|
-
? ""
|
|
107
|
-
: level.isLegalNumbering
|
|
108
|
-
?
|
|
109
|
-
:
|
|
121
|
+
level.isLegalNumbering === true
|
|
122
|
+
? "<w:isLgl/>"
|
|
123
|
+
: level.isLegalNumbering === false
|
|
124
|
+
? `<w:isLgl w:val="false"/>`
|
|
125
|
+
: "";
|
|
110
126
|
const suffix = level.suffix ? `<w:suff w:val="${escapeAttribute(level.suffix)}"/>` : "";
|
|
111
127
|
const justification = level.paragraphGeometry?.justification
|
|
112
128
|
? `<w:lvlJc w:val="${escapeAttribute(level.paragraphGeometry.justification)}"/>`
|
|
@@ -114,13 +130,19 @@ function serializeLevelOverride(
|
|
|
114
130
|
const paragraphProperties = serializeLevelParagraphGeometry(level.paragraphGeometry);
|
|
115
131
|
const body = `${start}${format}${text}${paragraphStyle}${isLegal}${suffix}${justification}${paragraphProperties}`;
|
|
116
132
|
|
|
117
|
-
return body.length > 0
|
|
133
|
+
return body.length > 0
|
|
134
|
+
? `<w:lvl w:ilvl="${clampIlvl(serializedLevel)}">${body}</w:lvl>`
|
|
135
|
+
: "";
|
|
118
136
|
}
|
|
119
137
|
|
|
120
138
|
function serializeInstance(instance: NumberingCatalog["instances"][string]): string {
|
|
121
|
-
const numId = escapeAttribute(
|
|
139
|
+
const numId = escapeAttribute(
|
|
140
|
+
clampNonNegativeIdString(stripCanonicalPrefix(instance.numberingInstanceId, "num:")),
|
|
141
|
+
);
|
|
122
142
|
const abstractNumId = escapeAttribute(
|
|
123
|
-
|
|
143
|
+
clampNonNegativeIdString(
|
|
144
|
+
stripCanonicalPrefix(instance.abstractNumberingId, "abstract-num:"),
|
|
145
|
+
),
|
|
124
146
|
);
|
|
125
147
|
const overrides = [...instance.overrides]
|
|
126
148
|
.sort((left, right) => left.level - right.level)
|
|
@@ -132,11 +154,13 @@ function serializeInstance(instance: NumberingCatalog["instances"][string]): str
|
|
|
132
154
|
|
|
133
155
|
function serializeOverride(override: NumberingCatalog["instances"][string]["overrides"][number]): string {
|
|
134
156
|
const startOverride =
|
|
135
|
-
override.startAt !== undefined
|
|
157
|
+
override.startAt !== undefined
|
|
158
|
+
? `<w:startOverride w:val="${clampStart(override.startAt)}"/>`
|
|
159
|
+
: "";
|
|
136
160
|
const levelDefinition = override.levelDefinition
|
|
137
161
|
? serializeLevelOverride(override.levelDefinition, override.level)
|
|
138
162
|
: "";
|
|
139
|
-
return `<w:lvlOverride w:ilvl="${override.level}">${startOverride}${levelDefinition}</w:lvlOverride>`;
|
|
163
|
+
return `<w:lvlOverride w:ilvl="${clampIlvl(override.level)}">${startOverride}${levelDefinition}</w:lvlOverride>`;
|
|
140
164
|
}
|
|
141
165
|
|
|
142
166
|
function serializeLevelParagraphGeometry(
|
|
@@ -150,13 +174,13 @@ function serializeLevelParagraphGeometry(
|
|
|
150
174
|
if (paragraphGeometry.spacing) {
|
|
151
175
|
const attrs: string[] = [];
|
|
152
176
|
if (paragraphGeometry.spacing.before !== undefined) {
|
|
153
|
-
attrs.push(`w:before="${paragraphGeometry.spacing.before}"`);
|
|
177
|
+
attrs.push(`w:before="${twip(paragraphGeometry.spacing.before)}"`);
|
|
154
178
|
}
|
|
155
179
|
if (paragraphGeometry.spacing.after !== undefined) {
|
|
156
|
-
attrs.push(`w:after="${paragraphGeometry.spacing.after}"`);
|
|
180
|
+
attrs.push(`w:after="${twip(paragraphGeometry.spacing.after)}"`);
|
|
157
181
|
}
|
|
158
182
|
if (paragraphGeometry.spacing.line !== undefined) {
|
|
159
|
-
attrs.push(`w:line="${paragraphGeometry.spacing.line}"`);
|
|
183
|
+
attrs.push(`w:line="${twip(paragraphGeometry.spacing.line)}"`);
|
|
160
184
|
}
|
|
161
185
|
if (paragraphGeometry.spacing.lineRule !== undefined) {
|
|
162
186
|
attrs.push(`w:lineRule="${paragraphGeometry.spacing.lineRule}"`);
|
|
@@ -169,16 +193,16 @@ function serializeLevelParagraphGeometry(
|
|
|
169
193
|
if (paragraphGeometry.indentation) {
|
|
170
194
|
const attrs: string[] = [];
|
|
171
195
|
if (paragraphGeometry.indentation.left !== undefined) {
|
|
172
|
-
attrs.push(`w:left="${paragraphGeometry.indentation.left}"`);
|
|
196
|
+
attrs.push(`w:left="${twip(paragraphGeometry.indentation.left)}"`);
|
|
173
197
|
}
|
|
174
198
|
if (paragraphGeometry.indentation.right !== undefined) {
|
|
175
|
-
attrs.push(`w:right="${paragraphGeometry.indentation.right}"`);
|
|
199
|
+
attrs.push(`w:right="${twip(paragraphGeometry.indentation.right)}"`);
|
|
176
200
|
}
|
|
177
201
|
if (paragraphGeometry.indentation.firstLine !== undefined) {
|
|
178
|
-
attrs.push(`w:firstLine="${paragraphGeometry.indentation.firstLine}"`);
|
|
202
|
+
attrs.push(`w:firstLine="${twip(paragraphGeometry.indentation.firstLine)}"`);
|
|
179
203
|
}
|
|
180
204
|
if (paragraphGeometry.indentation.hanging !== undefined) {
|
|
181
|
-
attrs.push(`w:hanging="${paragraphGeometry.indentation.hanging}"`);
|
|
205
|
+
attrs.push(`w:hanging="${twip(paragraphGeometry.indentation.hanging)}"`);
|
|
182
206
|
}
|
|
183
207
|
if (attrs.length > 0) {
|
|
184
208
|
children.push(`<w:ind ${attrs.join(" ")}/>`);
|
|
@@ -189,7 +213,7 @@ function serializeLevelParagraphGeometry(
|
|
|
189
213
|
const tabsXml = paragraphGeometry.tabStops.map((tab) => {
|
|
190
214
|
const leader = tab.leader === "middleDot" ? "middledot" : tab.leader;
|
|
191
215
|
const leaderAttr = leader ? ` w:leader="${leader}"` : "";
|
|
192
|
-
return `<w:tab w:val="${tab.align}" w:pos="${tab.position}"${leaderAttr}/>`;
|
|
216
|
+
return `<w:tab w:val="${tab.align}" w:pos="${twip(tab.position)}"${leaderAttr}/>`;
|
|
193
217
|
}).join("");
|
|
194
218
|
children.push(`<w:tabs>${tabsXml}</w:tabs>`);
|
|
195
219
|
}
|
|
@@ -197,6 +221,72 @@ function serializeLevelParagraphGeometry(
|
|
|
197
221
|
return children.length > 0 ? `<w:pPr>${children.join("")}</w:pPr>` : "";
|
|
198
222
|
}
|
|
199
223
|
|
|
224
|
+
/**
|
|
225
|
+
* ST_DecimalNumberOrPercent / ST_DecimalNumber based ilvl/numId/abstractNumId
|
|
226
|
+
* attributes are integer-typed. Ilvl is bounded 0-8 by the schema.
|
|
227
|
+
* Negative ids violate MinInclusive ≥ 0 and make the validator fail.
|
|
228
|
+
* These clampers normalise at the authoring edge (A.6 MinInclusive clamp).
|
|
229
|
+
*/
|
|
230
|
+
function clampIlvl(value: number | undefined): number {
|
|
231
|
+
if (value === undefined || !Number.isFinite(value)) return 0;
|
|
232
|
+
const rounded = Math.round(value);
|
|
233
|
+
if (rounded < 0) {
|
|
234
|
+
warnClamp("w:ilvl", rounded, 0);
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
if (rounded > 8) {
|
|
238
|
+
warnClamp("w:ilvl", rounded, 8);
|
|
239
|
+
return 8;
|
|
240
|
+
}
|
|
241
|
+
return rounded;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function clampStart(value: number | undefined): number {
|
|
245
|
+
if (value === undefined || !Number.isFinite(value)) return 1;
|
|
246
|
+
const rounded = Math.round(value);
|
|
247
|
+
// w:start MinInclusive per the schema is 0 (0 is legal for Word-created
|
|
248
|
+
// lists that begin with a 0-based custom format). Negative values fail.
|
|
249
|
+
if (rounded < 0) {
|
|
250
|
+
warnClamp("w:start", rounded, 0);
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
return rounded;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Clamp a stringified id attribute (w:numId / w:abstractNumId) to the
|
|
258
|
+
* schema's MinInclusive=0. Accepts the canonical id string (produced by
|
|
259
|
+
* `stripCanonicalPrefix`) and returns a string safe to embed as a value.
|
|
260
|
+
* If the string is non-numeric or positive it passes through unchanged.
|
|
261
|
+
*/
|
|
262
|
+
function clampNonNegativeIdString(value: string): string {
|
|
263
|
+
const parsed = Number.parseInt(value, 10);
|
|
264
|
+
if (!Number.isFinite(parsed)) return value;
|
|
265
|
+
if (parsed < 0) {
|
|
266
|
+
warnClamp("w:numId/w:abstractNumId", parsed, 0);
|
|
267
|
+
return "0";
|
|
268
|
+
}
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let warnedClamps = 0;
|
|
273
|
+
const WARNED_CLAMPS_MAX = 20;
|
|
274
|
+
function warnClamp(attr: string, from: number, to: number): void {
|
|
275
|
+
if (warnedClamps >= WARNED_CLAMPS_MAX) return;
|
|
276
|
+
warnedClamps += 1;
|
|
277
|
+
// Only warn outside of production builds. The test runner shows these
|
|
278
|
+
// via console.warn automatically; in production this is a silent no-op
|
|
279
|
+
// so the Buffer cost is zero.
|
|
280
|
+
const proc = (globalThis as unknown as {
|
|
281
|
+
process?: { env?: Record<string, string | undefined> };
|
|
282
|
+
}).process;
|
|
283
|
+
if (proc?.env?.NODE_ENV !== "production") {
|
|
284
|
+
console.warn(
|
|
285
|
+
`[numbering] clamped ${attr} ${from} -> ${to} at MinInclusive (A.6 guard)`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
200
290
|
function compareSerializedIds(left: string, right: string): number {
|
|
201
291
|
return stripKnownPrefix(left).localeCompare(stripKnownPrefix(right), "en", { numeric: true });
|
|
202
292
|
}
|
|
@@ -10,13 +10,14 @@ import type {
|
|
|
10
10
|
ParsedTableRow,
|
|
11
11
|
ParsedTableWidth,
|
|
12
12
|
} from "../ooxml/parse-tables.ts";
|
|
13
|
+
import { twip } from "./twip.ts";
|
|
13
14
|
|
|
14
15
|
export function serializeTable(table: ParsedTable): string {
|
|
15
16
|
const propertiesXml = table.propertiesXml ?? buildTablePropertiesXml(table);
|
|
16
17
|
const gridXml =
|
|
17
18
|
table.gridColumns.length > 0
|
|
18
19
|
? `<w:tblGrid>${table.gridColumns
|
|
19
|
-
.map((width) => `<w:gridCol w:w="${width}"/>`)
|
|
20
|
+
.map((width) => `<w:gridCol w:w="${twip(width)}"/>`)
|
|
20
21
|
.join("")}</w:tblGrid>`
|
|
21
22
|
: "";
|
|
22
23
|
const rowsXml = table.rows.map(serializeRow).join("");
|
|
@@ -46,6 +47,28 @@ function buildTablePropertiesXml(table: ParsedTable): string {
|
|
|
46
47
|
if (table.alignment) {
|
|
47
48
|
children.push(`<w:jc w:val="${table.alignment}"/>`);
|
|
48
49
|
}
|
|
50
|
+
if (table.indent) {
|
|
51
|
+
children.push(`<w:tblInd w:w="${table.indent.value}" w:type="${table.indent.type}"/>`);
|
|
52
|
+
}
|
|
53
|
+
if (table.layoutMode) {
|
|
54
|
+
children.push(`<w:tblLayout w:type="${table.layoutMode}"/>`);
|
|
55
|
+
}
|
|
56
|
+
if (table.cellSpacing) {
|
|
57
|
+
children.push(`<w:tblCellSpacing w:w="${table.cellSpacing.value}" w:type="${table.cellSpacing.type}"/>`);
|
|
58
|
+
}
|
|
59
|
+
if (table.bidiVisual !== undefined) {
|
|
60
|
+
children.push(table.bidiVisual ? `<w:bidiVisual/>` : `<w:bidiVisual w:val="0"/>`);
|
|
61
|
+
}
|
|
62
|
+
if (table.caption !== undefined) {
|
|
63
|
+
children.push(`<w:tblCaption w:val="${escapeAttribute(table.caption)}"/>`);
|
|
64
|
+
}
|
|
65
|
+
if (table.description !== undefined) {
|
|
66
|
+
children.push(`<w:tblDescription w:val="${escapeAttribute(table.description)}"/>`);
|
|
67
|
+
}
|
|
68
|
+
if (table.floating) {
|
|
69
|
+
const floatingXml = serializeTableFloating(table.floating);
|
|
70
|
+
if (floatingXml) children.push(floatingXml);
|
|
71
|
+
}
|
|
49
72
|
if (table.borders) {
|
|
50
73
|
const bordersXml = serializeTableBorders(table.borders);
|
|
51
74
|
if (bordersXml) children.push(`<w:tblBorders>${bordersXml}</w:tblBorders>`);
|
|
@@ -61,15 +84,43 @@ function buildTablePropertiesXml(table: ParsedTable): string {
|
|
|
61
84
|
return children.length > 0 ? `<w:tblPr>${children.join("")}</w:tblPr>` : "";
|
|
62
85
|
}
|
|
63
86
|
|
|
87
|
+
function serializeTableFloating(floating: NonNullable<ParsedTable["floating"]>): string {
|
|
88
|
+
const attrs: string[] = [];
|
|
89
|
+
if (floating.horizontalAnchor) attrs.push(`w:horzAnchor="${floating.horizontalAnchor}"`);
|
|
90
|
+
if (floating.verticalAnchor) attrs.push(`w:vertAnchor="${floating.verticalAnchor}"`);
|
|
91
|
+
if (floating.horizontalAlign) attrs.push(`w:tblpXSpec="${floating.horizontalAlign}"`);
|
|
92
|
+
if (floating.horizontalOffset !== undefined) attrs.push(`w:tblpX="${floating.horizontalOffset}"`);
|
|
93
|
+
if (floating.verticalAlign) attrs.push(`w:tblpYSpec="${floating.verticalAlign}"`);
|
|
94
|
+
if (floating.verticalOffset !== undefined) attrs.push(`w:tblpY="${floating.verticalOffset}"`);
|
|
95
|
+
if (floating.leftFromText !== undefined) attrs.push(`w:leftFromText="${floating.leftFromText}"`);
|
|
96
|
+
if (floating.rightFromText !== undefined) attrs.push(`w:rightFromText="${floating.rightFromText}"`);
|
|
97
|
+
if (floating.topFromText !== undefined) attrs.push(`w:topFromText="${floating.topFromText}"`);
|
|
98
|
+
if (floating.bottomFromText !== undefined) attrs.push(`w:bottomFromText="${floating.bottomFromText}"`);
|
|
99
|
+
const tblpPr = attrs.length > 0 ? `<w:tblpPr ${attrs.join(" ")}/>` : "";
|
|
100
|
+
const overlap = floating.overlap !== undefined
|
|
101
|
+
? `<w:tblOverlap w:val="${floating.overlap ? "overlap" : "never"}"/>`
|
|
102
|
+
: "";
|
|
103
|
+
return `${tblpPr}${overlap}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
64
106
|
function buildRowPropertiesXml(row: ParsedTableRow): string {
|
|
65
107
|
const children: string[] = [];
|
|
108
|
+
if (row.cnfStyle) {
|
|
109
|
+
children.push(`<w:cnfStyle w:val="${escapeAttribute(row.cnfStyle)}"/>`);
|
|
110
|
+
}
|
|
111
|
+
if (row.cantSplit !== undefined) {
|
|
112
|
+
children.push(row.cantSplit ? `<w:cantSplit/>` : `<w:cantSplit w:val="0"/>`);
|
|
113
|
+
}
|
|
66
114
|
if (row.height !== undefined) {
|
|
67
115
|
const hRuleAttr = row.heightRule ? ` w:hRule="${row.heightRule}"` : "";
|
|
68
|
-
children.push(`<w:trHeight w:val="${row.height}"${hRuleAttr}/>`);
|
|
116
|
+
children.push(`<w:trHeight w:val="${twip(row.height)}"${hRuleAttr}/>`);
|
|
69
117
|
}
|
|
70
118
|
if (row.isHeader) {
|
|
71
119
|
children.push(`<w:tblHeader/>`);
|
|
72
120
|
}
|
|
121
|
+
if (row.horizontalAlignment) {
|
|
122
|
+
children.push(`<w:jc w:val="${row.horizontalAlignment}"/>`);
|
|
123
|
+
}
|
|
73
124
|
return children.length > 0 ? `<w:trPr>${children.join("")}</w:trPr>` : "";
|
|
74
125
|
}
|
|
75
126
|
|
|
@@ -79,11 +130,14 @@ function ensureCellProperties(cell: ParsedTableCell): string {
|
|
|
79
130
|
}
|
|
80
131
|
|
|
81
132
|
const children: string[] = [];
|
|
133
|
+
if (cell.cnfStyle) {
|
|
134
|
+
children.push(`<w:cnfStyle w:val="${escapeAttribute(cell.cnfStyle)}"/>`);
|
|
135
|
+
}
|
|
82
136
|
if (cell.width) {
|
|
83
137
|
children.push(serializeWidth("tcW", cell.width));
|
|
84
138
|
}
|
|
85
139
|
if (cell.gridSpan && cell.gridSpan > 1) {
|
|
86
|
-
children.push(`<w:gridSpan w:val="${cell.gridSpan}"/>`);
|
|
140
|
+
children.push(`<w:gridSpan w:val="${twip(cell.gridSpan)}"/>`);
|
|
87
141
|
}
|
|
88
142
|
if (cell.verticalMerge) {
|
|
89
143
|
children.push(
|
|
@@ -99,6 +153,19 @@ function ensureCellProperties(cell: ParsedTableCell): string {
|
|
|
99
153
|
if (cell.shading) {
|
|
100
154
|
children.push(serializeCellShading(cell.shading));
|
|
101
155
|
}
|
|
156
|
+
if (cell.margins) {
|
|
157
|
+
const marginsXml = serializeTableCellMargins(cell.margins);
|
|
158
|
+
if (marginsXml) children.push(`<w:tcMar>${marginsXml}</w:tcMar>`);
|
|
159
|
+
}
|
|
160
|
+
if (cell.noWrap !== undefined) {
|
|
161
|
+
children.push(cell.noWrap ? `<w:noWrap/>` : `<w:noWrap w:val="0"/>`);
|
|
162
|
+
}
|
|
163
|
+
if (cell.fitText !== undefined) {
|
|
164
|
+
children.push(cell.fitText ? `<w:tcFitText/>` : `<w:tcFitText w:val="0"/>`);
|
|
165
|
+
}
|
|
166
|
+
if (cell.textDirection) {
|
|
167
|
+
children.push(`<w:textDirection w:val="${cell.textDirection}"/>`);
|
|
168
|
+
}
|
|
102
169
|
if (cell.verticalAlign) {
|
|
103
170
|
children.push(`<w:vAlign w:val="${cell.verticalAlign}"/>`);
|
|
104
171
|
}
|
|
@@ -107,14 +174,14 @@ function ensureCellProperties(cell: ParsedTableCell): string {
|
|
|
107
174
|
}
|
|
108
175
|
|
|
109
176
|
function serializeWidth(element: string, width: ParsedTableWidth): string {
|
|
110
|
-
return `<w:${element} w:w="${width.value}" w:type="${width.type}"/>`;
|
|
177
|
+
return `<w:${element} w:w="${twip(width.value)}" w:type="${width.type}"/>`;
|
|
111
178
|
}
|
|
112
179
|
|
|
113
180
|
function serializeBorderSpec(element: string, spec: ParsedBorderSpec): string {
|
|
114
181
|
const attrs: string[] = [];
|
|
115
182
|
if (spec.value) attrs.push(`w:val="${spec.value}"`);
|
|
116
|
-
if (spec.size !== undefined) attrs.push(`w:sz="${spec.size}"`);
|
|
117
|
-
if (spec.space !== undefined) attrs.push(`w:space="${spec.space}"`);
|
|
183
|
+
if (spec.size !== undefined) attrs.push(`w:sz="${twip(spec.size)}"`);
|
|
184
|
+
if (spec.space !== undefined) attrs.push(`w:space="${twip(spec.space)}"`);
|
|
118
185
|
if (spec.color) attrs.push(`w:color="${spec.color}"`);
|
|
119
186
|
const attrsStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
|
|
120
187
|
return `<w:${element}${attrsStr}/>`;
|
|
@@ -149,7 +216,8 @@ function serializeTableLook(tblLook: ParsedTableLook): string {
|
|
|
149
216
|
] as const) {
|
|
150
217
|
const value = tblLook[key];
|
|
151
218
|
if (value !== undefined) {
|
|
152
|
-
|
|
219
|
+
// ST_OnOff (A.3): emit "true"/"false", never "1"/"0".
|
|
220
|
+
attrs.push(`${attr}="${value ? "true" : "false"}"`);
|
|
153
221
|
}
|
|
154
222
|
}
|
|
155
223
|
return attrs.length > 0 ? `<w:tblLook ${attrs.join(" ")}/>` : "";
|
|
@@ -166,9 +234,17 @@ function serializeCellShading(shading: ParsedCellShading): string {
|
|
|
166
234
|
|
|
167
235
|
function serializeTableCellMargins(margins: ParsedCellMargins): string {
|
|
168
236
|
const parts: string[] = [];
|
|
169
|
-
if (margins.top !== undefined) parts.push(`<w:top w:w="${margins.top}" w:type="dxa"/>`);
|
|
170
|
-
if (margins.left !== undefined) parts.push(`<w:left w:w="${margins.left}" w:type="dxa"/>`);
|
|
171
|
-
if (margins.bottom !== undefined) parts.push(`<w:bottom w:w="${margins.bottom}" w:type="dxa"/>`);
|
|
172
|
-
if (margins.right !== undefined) parts.push(`<w:right w:w="${margins.right}" w:type="dxa"/>`);
|
|
237
|
+
if (margins.top !== undefined) parts.push(`<w:top w:w="${twip(margins.top)}" w:type="dxa"/>`);
|
|
238
|
+
if (margins.left !== undefined) parts.push(`<w:left w:w="${twip(margins.left)}" w:type="dxa"/>`);
|
|
239
|
+
if (margins.bottom !== undefined) parts.push(`<w:bottom w:w="${twip(margins.bottom)}" w:type="dxa"/>`);
|
|
240
|
+
if (margins.right !== undefined) parts.push(`<w:right w:w="${twip(margins.right)}" w:type="dxa"/>`);
|
|
173
241
|
return parts.join("");
|
|
174
242
|
}
|
|
243
|
+
|
|
244
|
+
function escapeAttribute(value: string): string {
|
|
245
|
+
return value
|
|
246
|
+
.replace(/&/gu, "&")
|
|
247
|
+
.replace(/"/gu, """)
|
|
248
|
+
.replace(/</gu, "<")
|
|
249
|
+
.replace(/>/gu, ">");
|
|
250
|
+
}
|