@stll/folio-core 0.6.0 → 0.6.1
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/dist/controller/layoutPipeline.js +17 -6
- package/dist/docx/blockContentParser.js +2 -100
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +9 -0
- package/dist/docx/paragraphTextBoxEnrichment.js +104 -0
- package/dist/docx/tableParser.js +2 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +15 -13
- package/dist/layout-bridge/sectionColumns.js +6 -1
- package/dist/layout-engine/index.js +9 -7
- package/dist/layout-engine/measure/measureBlocks.js +3 -2
- package/dist/layout-engine/paginator.d.ts +2 -0
- package/dist/layout-engine/paginator.js +17 -13
- package/dist/layout-engine/tableRowBreak.js +3 -0
- package/dist/layout-engine/types.d.ts +5 -2
- package/dist/layout-painter/index.js +1 -1
- package/dist/layout-painter/renderPage.js +7 -2
- package/dist/layout-painter/renderParagraph.js +5 -4
- package/dist/layout-painter/renderTable.js +88 -10
- package/dist/paged-layout/sectionBlockWidths.js +11 -3
- package/dist/prosemirror/conversion/fromProseDoc.js +11 -2
- package/dist/prosemirror/conversion/toProseDoc.js +26 -20
- package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import "../layout-engine/types.js";
|
|
2
2
|
import { renderPages } from "../layout-painter/renderPage.js";
|
|
3
3
|
import { templatePreviewValuesKey } from "../prosemirror/plugins/templatePreviewValues.js";
|
|
4
|
+
import { getMargins, getPageSize, twipsToPixels } from "../paged-layout/sectionGeometry.js";
|
|
5
|
+
import { getColumns } from "../layout-bridge/sectionColumns.js";
|
|
4
6
|
import { toFlowBlocks } from "../layout-bridge/convert/toFlowBlocks.js";
|
|
5
7
|
import { recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart } from "../layout-engine/layoutInstrumentation.js";
|
|
6
8
|
import { buildBookmarkPageMap } from "../fields/bookmarkPages.js";
|
|
@@ -15,7 +17,6 @@ import { layoutDocument } from "../layout-engine/index.js";
|
|
|
15
17
|
import { measureBlocks, measureSingleBlockWithoutFloatingZones } from "../layout-engine/measure/measureBlocks.js";
|
|
16
18
|
import { tryBuildIncrementalMeasures } from "../paged-layout/incrementalMeasure.js";
|
|
17
19
|
import { computePerBlockMeasureInputs } from "../paged-layout/sectionBlockWidths.js";
|
|
18
|
-
import { getMargins, getPageSize, twipsToPixels } from "../paged-layout/sectionGeometry.js";
|
|
19
20
|
import { getDocumentWatermark } from "../watermark/index.js";
|
|
20
21
|
//#region src/controller/layoutPipeline.ts
|
|
21
22
|
function runLayoutPipeline(deps, state, options = {}) {
|
|
@@ -98,10 +99,17 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
98
99
|
margins
|
|
99
100
|
};
|
|
100
101
|
if (columns !== void 0) bodyLayoutConfig.columns = columns;
|
|
102
|
+
const finalSectionProperties = document?.package.document.sections?.at(-1)?.properties;
|
|
103
|
+
const finalLayoutConfig = finalSectionProperties ? {
|
|
104
|
+
pageSize: getPageSize(finalSectionProperties),
|
|
105
|
+
margins: getMargins(finalSectionProperties)
|
|
106
|
+
} : bodyLayoutConfig;
|
|
107
|
+
const finalColumns = getColumns(finalSectionProperties);
|
|
108
|
+
if (finalColumns !== void 0) finalLayoutConfig.columns = finalColumns;
|
|
101
109
|
const blockMeasureInputs = computePerBlockMeasureInputs({
|
|
102
110
|
blocks: newBlocks,
|
|
103
111
|
bodyConfig: bodyLayoutConfig,
|
|
104
|
-
finalConfig:
|
|
112
|
+
finalConfig: finalLayoutConfig
|
|
105
113
|
});
|
|
106
114
|
const blockWidths = blockMeasureInputs.widths;
|
|
107
115
|
const previousArtifacts = session.artifacts;
|
|
@@ -144,10 +152,13 @@ function runLayoutPipeline(deps, state, options = {}) {
|
|
|
144
152
|
top: headerBottom
|
|
145
153
|
};
|
|
146
154
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
nextLayoutOpts.
|
|
150
|
-
nextLayoutOpts.
|
|
155
|
+
if (finalSectionProperties) {
|
|
156
|
+
nextLayoutOpts.finalPageSize = finalLayoutConfig.pageSize;
|
|
157
|
+
nextLayoutOpts.finalMargins = finalLayoutConfig.margins;
|
|
158
|
+
nextLayoutOpts.finalColumns = finalLayoutConfig.columns ?? {
|
|
159
|
+
count: 1,
|
|
160
|
+
gap: 0
|
|
161
|
+
};
|
|
151
162
|
}
|
|
152
163
|
if (columns !== void 0) nextLayoutOpts.columns = columns;
|
|
153
164
|
if (bodyBreakType !== void 0) nextLayoutOpts.bodyBreakType = bodyBreakType;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { elementToXml, findChild,
|
|
1
|
+
import { elementToXml, findChild, getChildElements, getLocalName, mergeXmlnsDeclarations } from "./xmlParser.js";
|
|
2
2
|
import { parseBookmarkEnd, parseBookmarkStart } from "./bookmarkParser.js";
|
|
3
|
-
import { getTextBoxContentElement, isTextBoxDrawing, parseTextBox, parseTextBoxContent } from "./textBoxParser.js";
|
|
4
3
|
import { parseSdtProperties } from "./sdtProperties.js";
|
|
5
4
|
import { parseParagraph } from "./paragraphParser.js";
|
|
6
5
|
import { appendBookmarkMarkerToLastParagraphInBlocks, prependBookmarkMarkersToFirstParagraphInBlocks } from "./bookmarkPlacement.js";
|
|
6
|
+
import { enrichParagraphTextBoxes } from "./paragraphTextBoxEnrichment.js";
|
|
7
7
|
import { parseTable } from "./tableParser.js";
|
|
8
8
|
import { padDecimal } from "./numberingParser.js";
|
|
9
9
|
import { convertBulletToUnicode } from "./bulletMarkers.js";
|
|
@@ -100,104 +100,6 @@ const computeListMarker = (paragraph, numbering, listCounters, abstractCounters)
|
|
|
100
100
|
}
|
|
101
101
|
listRendering.marker = computedMarker;
|
|
102
102
|
};
|
|
103
|
-
const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering, rels, media) => {
|
|
104
|
-
const xmlChildren = getChildElements(paraXml);
|
|
105
|
-
let parsedIndex = 0;
|
|
106
|
-
let lastConsumedRun;
|
|
107
|
-
for (const xmlChild of xmlChildren) {
|
|
108
|
-
if (getLocalName(xmlChild.name ?? "") !== "r") {
|
|
109
|
-
if (parsedIndex < paragraph.content.length && paragraph.content[parsedIndex]?.type !== "run") parsedIndex += 1;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
const { textBoxDrawings, hasNonTextBoxContent } = scanRunForTextBoxDrawings(xmlChild);
|
|
113
|
-
const parsedContent = paragraph.content[parsedIndex];
|
|
114
|
-
const parsedRun = parsedContent?.type === "run" ? parsedContent : void 0;
|
|
115
|
-
const targetRun = parsedRun ?? (hasNonTextBoxContent ? lastConsumedRun : void 0);
|
|
116
|
-
for (const runEl of textBoxDrawings) {
|
|
117
|
-
const textBox = parseTextBox(runEl);
|
|
118
|
-
if (!textBox) continue;
|
|
119
|
-
const wsp = findDeep(runEl, "wps", "wsp");
|
|
120
|
-
if (wsp) {
|
|
121
|
-
const txbxContentEl = getTextBoxContentElement(wsp);
|
|
122
|
-
if (txbxContentEl) textBox.content = parseTextBoxContent(txbxContentEl, parseParagraph, null, styles, theme, numbering, rels ?? void 0, media ?? void 0);
|
|
123
|
-
}
|
|
124
|
-
const shape = {
|
|
125
|
-
type: "shape",
|
|
126
|
-
shapeType: "textBox",
|
|
127
|
-
size: textBox.size,
|
|
128
|
-
...textBox.position !== void 0 ? { position: textBox.position } : {},
|
|
129
|
-
...textBox.wrap !== void 0 ? { wrap: textBox.wrap } : {},
|
|
130
|
-
...textBox.fill !== void 0 ? { fill: textBox.fill } : {},
|
|
131
|
-
...textBox.outline !== void 0 ? { outline: textBox.outline } : {},
|
|
132
|
-
textBody: {
|
|
133
|
-
content: textBox.content,
|
|
134
|
-
...textBox.margins !== void 0 ? { margins: textBox.margins } : {}
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
if (textBox.id) shape.id = textBox.id;
|
|
138
|
-
const shapeContent = {
|
|
139
|
-
type: "shape",
|
|
140
|
-
shape
|
|
141
|
-
};
|
|
142
|
-
if (targetRun && hasNonTextBoxContent) targetRun.content.push(shapeContent);
|
|
143
|
-
else {
|
|
144
|
-
const newRun = {
|
|
145
|
-
type: "run",
|
|
146
|
-
content: [shapeContent]
|
|
147
|
-
};
|
|
148
|
-
paragraph.content.splice(parsedIndex, 0, newRun);
|
|
149
|
-
lastConsumedRun = newRun;
|
|
150
|
-
parsedIndex += 1;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (hasNonTextBoxContent && parsedRun) {
|
|
154
|
-
lastConsumedRun = parsedRun;
|
|
155
|
-
parsedIndex += 1;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
const scanRunForTextBoxDrawings = (xmlRun) => {
|
|
160
|
-
const textBoxDrawings = [];
|
|
161
|
-
let hasNonTextBoxContent = false;
|
|
162
|
-
const visitDrawing = (drawingEl) => {
|
|
163
|
-
if (isTextBoxDrawing(drawingEl)) {
|
|
164
|
-
textBoxDrawings.push(drawingEl);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
hasNonTextBoxContent = true;
|
|
168
|
-
};
|
|
169
|
-
for (const el of getChildElements(xmlRun)) {
|
|
170
|
-
const name = getLocalName(el.name ?? "");
|
|
171
|
-
if (name === "rPr") continue;
|
|
172
|
-
if (name === "drawing") {
|
|
173
|
-
visitDrawing(el);
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
if (name === "AlternateContent") {
|
|
177
|
-
const branches = getChildElements(el);
|
|
178
|
-
const choice = branches.find((branch) => getLocalName(branch.name ?? "") === "Choice");
|
|
179
|
-
const fallback = branches.find((branch) => getLocalName(branch.name ?? "") === "Fallback");
|
|
180
|
-
const tryBranch = (branch) => {
|
|
181
|
-
if (!branch) return false;
|
|
182
|
-
let found = false;
|
|
183
|
-
for (const innerEl of getChildElements(branch)) if (getLocalName(innerEl.name ?? "") === "drawing") {
|
|
184
|
-
visitDrawing(innerEl);
|
|
185
|
-
found = true;
|
|
186
|
-
}
|
|
187
|
-
return found;
|
|
188
|
-
};
|
|
189
|
-
let foundInBranch = tryBranch(choice);
|
|
190
|
-
if (!foundInBranch) foundInBranch = tryBranch(fallback);
|
|
191
|
-
if (!foundInBranch) hasNonTextBoxContent = true;
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
hasNonTextBoxContent = true;
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
textBoxDrawings,
|
|
198
|
-
hasNonTextBoxContent
|
|
199
|
-
};
|
|
200
|
-
};
|
|
201
103
|
const parseBlockContent = (parent, styles, theme, numbering, rels, media, options) => parseBlockContentWithState(parent, styles, theme, numbering, rels, media, {
|
|
202
104
|
listCounters: /* @__PURE__ */ new Map(),
|
|
203
105
|
abstractCounters: /* @__PURE__ */ new Map(),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { NumberingMap } from "./numberingParser.js";
|
|
3
|
+
import { StyleMap } from "./styleParser.js";
|
|
4
|
+
import { XmlElement } from "./xmlParser.js";
|
|
5
|
+
|
|
6
|
+
//#region src/docx/paragraphTextBoxEnrichment.d.ts
|
|
7
|
+
declare const enrichParagraphTextBoxes: (paragraph: document_d_exports.Paragraph, paraXml: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null) => void;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { enrichParagraphTextBoxes };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { findDeep, getChildElements, getLocalName } from "./xmlParser.js";
|
|
2
|
+
import { getTextBoxContentElement, isTextBoxDrawing, parseTextBox, parseTextBoxContent } from "./textBoxParser.js";
|
|
3
|
+
import { parseParagraph } from "./paragraphParser.js";
|
|
4
|
+
//#region src/docx/paragraphTextBoxEnrichment.ts
|
|
5
|
+
const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering, rels, media) => {
|
|
6
|
+
const xmlChildren = getChildElements(paraXml);
|
|
7
|
+
let parsedIndex = 0;
|
|
8
|
+
let lastConsumedRun;
|
|
9
|
+
for (const xmlChild of xmlChildren) {
|
|
10
|
+
if (getLocalName(xmlChild.name ?? "") !== "r") {
|
|
11
|
+
if (parsedIndex < paragraph.content.length && paragraph.content[parsedIndex]?.type !== "run") parsedIndex += 1;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const { textBoxDrawings, hasNonTextBoxContent } = scanRunForTextBoxDrawings(xmlChild);
|
|
15
|
+
const parsedContent = paragraph.content[parsedIndex];
|
|
16
|
+
const parsedRun = parsedContent?.type === "run" ? parsedContent : void 0;
|
|
17
|
+
const targetRun = parsedRun ?? (hasNonTextBoxContent ? lastConsumedRun : void 0);
|
|
18
|
+
for (const runEl of textBoxDrawings) {
|
|
19
|
+
const textBox = parseTextBox(runEl);
|
|
20
|
+
if (!textBox) continue;
|
|
21
|
+
const wsp = findDeep(runEl, "wps", "wsp");
|
|
22
|
+
if (wsp) {
|
|
23
|
+
const txbxContentEl = getTextBoxContentElement(wsp);
|
|
24
|
+
if (txbxContentEl) textBox.content = parseTextBoxContent(txbxContentEl, parseParagraph, null, styles, theme, numbering, rels ?? void 0, media ?? void 0);
|
|
25
|
+
}
|
|
26
|
+
const shape = {
|
|
27
|
+
type: "shape",
|
|
28
|
+
shapeType: "textBox",
|
|
29
|
+
size: textBox.size,
|
|
30
|
+
...textBox.position !== void 0 ? { position: textBox.position } : {},
|
|
31
|
+
...textBox.wrap !== void 0 ? { wrap: textBox.wrap } : {},
|
|
32
|
+
...textBox.fill !== void 0 ? { fill: textBox.fill } : {},
|
|
33
|
+
...textBox.outline !== void 0 ? { outline: textBox.outline } : {},
|
|
34
|
+
textBody: {
|
|
35
|
+
content: textBox.content,
|
|
36
|
+
...textBox.margins !== void 0 ? { margins: textBox.margins } : {}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
if (textBox.id) shape.id = textBox.id;
|
|
40
|
+
const shapeContent = {
|
|
41
|
+
type: "shape",
|
|
42
|
+
shape
|
|
43
|
+
};
|
|
44
|
+
if (targetRun && hasNonTextBoxContent) targetRun.content.push(shapeContent);
|
|
45
|
+
else {
|
|
46
|
+
const newRun = {
|
|
47
|
+
type: "run",
|
|
48
|
+
content: [shapeContent]
|
|
49
|
+
};
|
|
50
|
+
paragraph.content.splice(parsedIndex, 0, newRun);
|
|
51
|
+
lastConsumedRun = newRun;
|
|
52
|
+
parsedIndex += 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (hasNonTextBoxContent && parsedRun) {
|
|
56
|
+
lastConsumedRun = parsedRun;
|
|
57
|
+
parsedIndex += 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const scanRunForTextBoxDrawings = (xmlRun) => {
|
|
62
|
+
const textBoxDrawings = [];
|
|
63
|
+
let hasNonTextBoxContent = false;
|
|
64
|
+
const visitDrawing = (drawingEl) => {
|
|
65
|
+
if (isTextBoxDrawing(drawingEl)) {
|
|
66
|
+
textBoxDrawings.push(drawingEl);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
hasNonTextBoxContent = true;
|
|
70
|
+
};
|
|
71
|
+
for (const el of getChildElements(xmlRun)) {
|
|
72
|
+
const name = getLocalName(el.name ?? "");
|
|
73
|
+
if (name === "rPr") continue;
|
|
74
|
+
if (name === "drawing") {
|
|
75
|
+
visitDrawing(el);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (name === "AlternateContent") {
|
|
79
|
+
const branches = getChildElements(el);
|
|
80
|
+
const choice = branches.find((branch) => getLocalName(branch.name ?? "") === "Choice");
|
|
81
|
+
const fallback = branches.find((branch) => getLocalName(branch.name ?? "") === "Fallback");
|
|
82
|
+
const tryBranch = (branch) => {
|
|
83
|
+
if (!branch) return false;
|
|
84
|
+
let found = false;
|
|
85
|
+
for (const innerEl of getChildElements(branch)) if (getLocalName(innerEl.name ?? "") === "drawing") {
|
|
86
|
+
visitDrawing(innerEl);
|
|
87
|
+
found = true;
|
|
88
|
+
}
|
|
89
|
+
return found;
|
|
90
|
+
};
|
|
91
|
+
let foundInBranch = tryBranch(choice);
|
|
92
|
+
if (!foundInBranch) foundInBranch = tryBranch(fallback);
|
|
93
|
+
if (!foundInBranch) hasNonTextBoxContent = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
hasNonTextBoxContent = true;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
textBoxDrawings,
|
|
100
|
+
hasNonTextBoxContent
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
export { enrichParagraphTextBoxes };
|
package/dist/docx/tableParser.js
CHANGED
|
@@ -3,6 +3,7 @@ import { parseBookmarkEnd, parseBookmarkStart } from "./bookmarkParser.js";
|
|
|
3
3
|
import { BorderStyleSchema, FloatingTableXSpecSchema, FloatingTableYSpecSchema, ShadingPatternSchema, TableCellTextDirectionSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
4
4
|
import { parseParagraph } from "./paragraphParser.js";
|
|
5
5
|
import { appendBookmarkMarkerToLastParagraphInBlocks, appendBookmarkMarkerToLastParagraphInCells, prependBookmarkMarkersToFirstParagraphInBlocks, prependBookmarkMarkersToFirstParagraphInCell } from "./bookmarkPlacement.js";
|
|
6
|
+
import { enrichParagraphTextBoxes } from "./paragraphTextBoxEnrichment.js";
|
|
6
7
|
//#region src/docx/tableParser.ts
|
|
7
8
|
/**
|
|
8
9
|
* Parse a table measurement (width, height, etc.)
|
|
@@ -489,6 +490,7 @@ function parseCellContent(tcElement, styles, theme, numbering, rels, media, opti
|
|
|
489
490
|
const localName = getLocalName(child.name);
|
|
490
491
|
if (localName === "p") {
|
|
491
492
|
const para = parseParagraph(child, styles, theme, numbering, rels, media, options);
|
|
493
|
+
enrichParagraphTextBoxes(para, child, styles, theme, numbering, rels, media);
|
|
492
494
|
prependPendingBookmarkMarkers(para, pendingBookmarkMarkers);
|
|
493
495
|
content.push(para);
|
|
494
496
|
} else if (localName === "tbl") {
|
|
@@ -6,8 +6,9 @@ import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.j
|
|
|
6
6
|
import { resolveShadingFill } from "../../utils/formatToStyle.js";
|
|
7
7
|
import { convertBulletToUnicode } from "../../docx/bulletMarkers.js";
|
|
8
8
|
import { setTextBoxGroupId } from "../../layout-engine/textBoxGroup.js";
|
|
9
|
+
import { getColumns } from "../sectionColumns.js";
|
|
9
10
|
import { directionIsRtl } from "../../prosemirror/paragraphDirection.js";
|
|
10
|
-
import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunShadingMarkAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../../prosemirror/attrs/index.js";
|
|
11
|
+
import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunShadingMarkAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../../prosemirror/attrs/index.js";
|
|
11
12
|
import { autospacingMatchesBase } from "../../prosemirror/autospacingBase.js";
|
|
12
13
|
import { runShadingAttrsToShading } from "../../prosemirror/conversion/runShadingMark.js";
|
|
13
14
|
import { assertValidProseMirrorDocument } from "../../prosemirror/validation.js";
|
|
@@ -919,6 +920,7 @@ function convertTableCell(node, startPos, options, tableCellMargins) {
|
|
|
919
920
|
const block = convertParagraph(child, offset, options);
|
|
920
921
|
blocks.push(block);
|
|
921
922
|
} else if (child.type.name === "table") blocks.push(convertTable(child, offset, options));
|
|
923
|
+
else if (child.type.name === "textBox") blocks.push(convertTextBoxNode(child, offset, options));
|
|
922
924
|
offset += child.nodeSize;
|
|
923
925
|
});
|
|
924
926
|
const trailingBlock = blocks.at(-1);
|
|
@@ -1213,9 +1215,17 @@ function toFlowBlocks(doc, options = {}) {
|
|
|
1213
1215
|
}
|
|
1214
1216
|
switch (node.type.name) {
|
|
1215
1217
|
case "paragraph": {
|
|
1216
|
-
const block = convertParagraph(node, pos, opts);
|
|
1217
1218
|
const pmAttrs = expectParagraphAttrs(node);
|
|
1218
|
-
|
|
1219
|
+
const onlyChild = node.childCount === 1 ? node.firstChild : null;
|
|
1220
|
+
if (onlyChild?.type.name === "hardBreak" && expectHardBreakAttrs(onlyChild).breakType === "column") {
|
|
1221
|
+
const columnBreak = {
|
|
1222
|
+
kind: "columnBreak",
|
|
1223
|
+
id: nextBlockId(),
|
|
1224
|
+
pmStart: pos,
|
|
1225
|
+
pmEnd: pos + node.nodeSize
|
|
1226
|
+
};
|
|
1227
|
+
trackedPush(columnBreak);
|
|
1228
|
+
} else trackedPush(convertParagraph(node, pos, opts));
|
|
1219
1229
|
const secProps = pmAttrs._sectionProperties;
|
|
1220
1230
|
if (secProps || pmAttrs.sectionBreakType) {
|
|
1221
1231
|
const sectionBreak = {
|
|
@@ -1245,16 +1255,8 @@ function toFlowBlocks(doc, options = {}) {
|
|
|
1245
1255
|
if (secProps.headerDistance !== void 0) sectionBreak.margins.header = twipsToPixels(secProps.headerDistance);
|
|
1246
1256
|
if (secProps.footerDistance !== void 0) sectionBreak.margins.footer = twipsToPixels(secProps.footerDistance);
|
|
1247
1257
|
}
|
|
1248
|
-
const
|
|
1249
|
-
if (
|
|
1250
|
-
const cols = {
|
|
1251
|
-
count: colCount,
|
|
1252
|
-
gap: twipsToPixels(secProps.columnSpace ?? 720),
|
|
1253
|
-
equalWidth: secProps.equalWidth ?? true
|
|
1254
|
-
};
|
|
1255
|
-
if (secProps.separator !== void 0) cols.separator = secProps.separator;
|
|
1256
|
-
sectionBreak.columns = cols;
|
|
1257
|
-
}
|
|
1258
|
+
const columns = getColumns(secProps);
|
|
1259
|
+
if (columns) sectionBreak.columns = columns;
|
|
1258
1260
|
}
|
|
1259
1261
|
trackedPush(sectionBreak);
|
|
1260
1262
|
}
|
|
@@ -7,13 +7,18 @@ const DEFAULT_COLUMN_SPACE_TWIPS = 720;
|
|
|
7
7
|
* Returns undefined for single-column (default) to avoid unnecessary paginator overhead.
|
|
8
8
|
*/
|
|
9
9
|
function getColumns(sectionProps) {
|
|
10
|
-
const
|
|
10
|
+
const authoredColumns = sectionProps?.columns;
|
|
11
|
+
const count = sectionProps?.columnCount ?? authoredColumns?.length ?? 1;
|
|
11
12
|
if (count <= 1) return void 0;
|
|
12
13
|
const columns = {
|
|
13
14
|
count,
|
|
14
15
|
gap: twipsToPixels(sectionProps?.columnSpace ?? DEFAULT_COLUMN_SPACE_TWIPS),
|
|
15
16
|
equalWidth: sectionProps?.equalWidth ?? true
|
|
16
17
|
};
|
|
18
|
+
if (sectionProps?.equalWidth === false && authoredColumns?.length === count && authoredColumns.every(({ width }) => width !== void 0)) {
|
|
19
|
+
columns.widths = authoredColumns.map(({ width }) => twipsToPixels(width ?? 0));
|
|
20
|
+
columns.gaps = authoredColumns.slice(0, -1).map(({ space }) => twipsToPixels(space ?? sectionProps.columnSpace ?? DEFAULT_COLUMN_SPACE_TWIPS));
|
|
21
|
+
}
|
|
17
22
|
if (sectionProps?.separator !== void 0) columns.separator = sectionProps.separator;
|
|
18
23
|
return columns;
|
|
19
24
|
}
|
|
@@ -156,7 +156,8 @@ function layoutDocument(blocks, measures, options) {
|
|
|
156
156
|
pageSize: finalPageSize,
|
|
157
157
|
margins: finalMargins
|
|
158
158
|
};
|
|
159
|
-
|
|
159
|
+
const finalColumns = options.finalColumns ?? options.columns;
|
|
160
|
+
if (finalColumns !== void 0) finalConfig.columns = finalColumns;
|
|
160
161
|
const { configs: sectionConfigs, breakIndices } = collectSectionConfigs(blocks, bodyConfig, finalConfig);
|
|
161
162
|
const sectionBreakTypes = [...breakIndices.map((index) => blocks[index].type), options.bodyBreakType];
|
|
162
163
|
const initialConfig = sectionConfigs.at(0) ?? bodyConfig;
|
|
@@ -197,7 +198,7 @@ function layoutDocument(blocks, measures, options) {
|
|
|
197
198
|
const pageBeforeBlockLayout = paginator.getCurrentState().page.number;
|
|
198
199
|
switch (block.kind) {
|
|
199
200
|
case "paragraph":
|
|
200
|
-
layoutParagraph(block, measure, paginator, paginator.
|
|
201
|
+
layoutParagraph(block, measure, paginator, paginator.columnWidth, options.footnoteHeightById);
|
|
201
202
|
break;
|
|
202
203
|
case "table":
|
|
203
204
|
if (block.floating) layoutFloatingTable(block, measure, paginator, paginator.getContentWidth());
|
|
@@ -428,9 +429,9 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
428
429
|
let x = paginator.getColumnX(columnIndex);
|
|
429
430
|
if (block.justification === "center") x += (paginator.columnWidth - measure.totalWidth) / 2;
|
|
430
431
|
else if (block.justification === "right") x = x + paginator.columnWidth - measure.totalWidth;
|
|
431
|
-
else
|
|
432
|
-
const leadingCellMargin = block.rows
|
|
433
|
-
x += block.indent - leadingCellMargin;
|
|
432
|
+
else {
|
|
433
|
+
const leadingCellMargin = block.rows.at(0)?.cells.at(0)?.padding?.left ?? 0;
|
|
434
|
+
x += (block.indent ?? 0) - leadingCellMargin;
|
|
434
435
|
}
|
|
435
436
|
return x;
|
|
436
437
|
};
|
|
@@ -637,8 +638,9 @@ function layoutFloatingTable(block, measure, paginator, contentWidth) {
|
|
|
637
638
|
else if (spec === "center") y = baseY + (contentHeight - tableHeight) / 2;
|
|
638
639
|
}
|
|
639
640
|
if (!usedExplicitY) y = paginator.ensureFits(tableHeight).cursorY;
|
|
640
|
-
const
|
|
641
|
-
const
|
|
641
|
+
const pageAnchored = floating?.horzAnchor === "page";
|
|
642
|
+
const minX = pageAnchored ? 0 : margins.left;
|
|
643
|
+
const maxX = pageAnchored ? page.size.w - tableWidth : margins.left + contentWidth - tableWidth;
|
|
642
644
|
if (Number.isFinite(maxX)) x = Math.max(minX, Math.min(x, maxX));
|
|
643
645
|
const fragment = {
|
|
644
646
|
kind: "table",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, tableColumnsArePinned } from "../types.js";
|
|
1
|
+
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, isFloatingTextBoxBlock, tableColumnsArePinned } from "../types.js";
|
|
2
2
|
import { findClearLineY, measureParagraph } from "./measureParagraph.js";
|
|
3
3
|
import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
|
|
4
4
|
import { getTextBoxGroupId } from "../textBoxGroup.js";
|
|
@@ -56,6 +56,7 @@ function isInlineFlowImageRun(run) {
|
|
|
56
56
|
return true;
|
|
57
57
|
}
|
|
58
58
|
function measureTableCellBlockVisualHeight(block, blockMeasure) {
|
|
59
|
+
if (block.kind === "textBox" && isFloatingTextBoxBlock(block)) return 0;
|
|
59
60
|
if (block.kind !== "paragraph" || blockMeasure.kind !== "paragraph") {
|
|
60
61
|
if ("totalHeight" in blockMeasure) return blockMeasure.totalHeight;
|
|
61
62
|
if ("height" in blockMeasure) return blockMeasure.height;
|
|
@@ -95,7 +96,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
95
96
|
columnWidths = Array.from({ length: colCount }, () => equalWidth);
|
|
96
97
|
} else if (columnWidths.length > 0 && explicitWidthPx) {
|
|
97
98
|
const totalWidth = columnWidths.reduce((sum, w) => sum + w, 0);
|
|
98
|
-
if (totalWidth > 0 && Math.abs(totalWidth - explicitWidthPx) > 1) {
|
|
99
|
+
if (!(tableBlock.layout !== "fixed" && (tableBlock.widthType === void 0 || tableBlock.widthType === "dxa") && totalWidth - explicitWidthPx > 1) && totalWidth > 0 && Math.abs(totalWidth - explicitWidthPx) > 1) {
|
|
99
100
|
const scale = explicitWidthPx / totalWidth;
|
|
100
101
|
columnWidths = columnWidths.map((w) => w * scale);
|
|
101
102
|
}
|
|
@@ -55,6 +55,8 @@ declare function createPaginator(options: PaginatorOptions): {
|
|
|
55
55
|
count: number;
|
|
56
56
|
gap: number;
|
|
57
57
|
equalWidth?: boolean;
|
|
58
|
+
widths?: number[];
|
|
59
|
+
gaps?: number[];
|
|
58
60
|
separator?: boolean;
|
|
59
61
|
}; /** Get current state. */
|
|
60
62
|
getCurrentState: () => PageState; /** Get available height in current column. */
|
|
@@ -7,12 +7,13 @@ import { panic } from "better-result";
|
|
|
7
7
|
* Tracks the current page, cursor position, and available space.
|
|
8
8
|
* Creates new pages when content doesn't fit.
|
|
9
9
|
*/
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return (
|
|
10
|
+
/** Calculate active column widths, preferring authored unequal widths. */
|
|
11
|
+
function calculateColumnWidths(pageWidth, leftMargin, rightMargin, columns) {
|
|
12
|
+
if (columns.widths?.length === columns.count && columns.widths.every((width) => Number.isFinite(width) && width > 0)) return [...columns.widths];
|
|
13
|
+
const equalWidth = (pageWidth - leftMargin - rightMargin - (columns.count - 1) * columns.gap) / columns.count;
|
|
14
|
+
return Array.from({ length: columns.count }, () => equalWidth);
|
|
15
15
|
}
|
|
16
|
+
const gapAfterColumn = (columns, columnIndex) => columns.gaps?.[columnIndex] ?? columns.gap;
|
|
16
17
|
function arePageSizesEqual(left, right) {
|
|
17
18
|
return left.w === right.w && left.h === right.h;
|
|
18
19
|
}
|
|
@@ -49,9 +50,9 @@ function createPaginator(options) {
|
|
|
49
50
|
return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins, getPageMargins(state.page.number));
|
|
50
51
|
}
|
|
51
52
|
if (getContentHeight() <= 0) panic("Paginator: page size and margins yield no content area");
|
|
52
|
-
let
|
|
53
|
-
function
|
|
54
|
-
|
|
53
|
+
let columnWidths = calculateColumnWidths(pageSize.w, margins.left, margins.right, columns);
|
|
54
|
+
function recalculateColumnWidths() {
|
|
55
|
+
columnWidths = calculateColumnWidths(pageSize.w, margins.left, margins.right, columns);
|
|
55
56
|
}
|
|
56
57
|
function applyPendingLayout() {
|
|
57
58
|
if (pendingPageSize) pageSize = pendingPageSize;
|
|
@@ -59,7 +60,7 @@ function createPaginator(options) {
|
|
|
59
60
|
pendingPageSize = void 0;
|
|
60
61
|
pendingMargins = void 0;
|
|
61
62
|
if (getContentHeight() <= 0) panic("Paginator: section page size and margins yield no content area");
|
|
62
|
-
|
|
63
|
+
recalculateColumnWidths();
|
|
63
64
|
}
|
|
64
65
|
function getPageMargins(pageNumber) {
|
|
65
66
|
const pageMargins = pageNumber === 1 && options.firstPageMargins ? { ...options.firstPageMargins } : { ...margins };
|
|
@@ -75,7 +76,9 @@ function createPaginator(options) {
|
|
|
75
76
|
* Get X position for a given column index.
|
|
76
77
|
*/
|
|
77
78
|
function getColumnX(columnIndex) {
|
|
78
|
-
|
|
79
|
+
let x = states.at(-1)?.page.margins.left ?? getPageMargins(1).left;
|
|
80
|
+
for (let index = 0; index < columnIndex; index++) x += (columnWidths[index] ?? columnWidths[0] ?? 0) + gapAfterColumn(columns, index);
|
|
81
|
+
return x;
|
|
79
82
|
}
|
|
80
83
|
/**
|
|
81
84
|
* Create a new page and add it to the list.
|
|
@@ -274,7 +277,7 @@ function createPaginator(options) {
|
|
|
274
277
|
*/
|
|
275
278
|
function updateColumns(newColumns) {
|
|
276
279
|
columns = newColumns;
|
|
277
|
-
|
|
280
|
+
recalculateColumnWidths();
|
|
278
281
|
const state = getCurrentState();
|
|
279
282
|
if (columns.count > 1) state.page.columns = { ...columns };
|
|
280
283
|
else delete state.page.columns;
|
|
@@ -290,7 +293,7 @@ function createPaginator(options) {
|
|
|
290
293
|
if (newPageSize) pageSize = { ...newPageSize };
|
|
291
294
|
if (newMargins) margins = { ...newMargins };
|
|
292
295
|
if (getContentHeight() <= 0) panic("Paginator: section page size and margins yield no content area");
|
|
293
|
-
|
|
296
|
+
recalculateColumnWidths();
|
|
294
297
|
pendingPageSize = void 0;
|
|
295
298
|
pendingMargins = void 0;
|
|
296
299
|
}
|
|
@@ -312,7 +315,8 @@ function createPaginator(options) {
|
|
|
312
315
|
states,
|
|
313
316
|
/** Column width in pixels (use getColumnWidth() for current value after updates). */
|
|
314
317
|
get columnWidth() {
|
|
315
|
-
|
|
318
|
+
const columnIndex = states.at(-1)?.columnIndex ?? 0;
|
|
319
|
+
return columnWidths[columnIndex] ?? columnWidths[0] ?? getContentWidth();
|
|
316
320
|
},
|
|
317
321
|
/** Get current column layout (returns copy to prevent external mutation). */
|
|
318
322
|
get columns() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isFloatingTextBoxBlock } from "./types.js";
|
|
1
2
|
import { measureParagraph } from "./measure/measureParagraph.js";
|
|
2
3
|
import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "./measure/tableCellFloating.js";
|
|
3
4
|
//#region src/layout-engine/tableRowBreak.ts
|
|
@@ -74,6 +75,8 @@ function cellBreakGeometry(cell, measure) {
|
|
|
74
75
|
y = blockTop + blockMeasure.totalHeight;
|
|
75
76
|
paragraphY += blockMeasure.totalHeight;
|
|
76
77
|
} else if (blockMeasure) {
|
|
78
|
+
const block = cellBlocks?.[i];
|
|
79
|
+
if (block?.kind === "textBox" && isFloatingTextBoxBlock(block)) continue;
|
|
77
80
|
const blockHeight = getAtomicBlockHeight(blockMeasure);
|
|
78
81
|
if (blockHeight > 0) {
|
|
79
82
|
const top = y;
|
|
@@ -846,7 +846,9 @@ type Page = {
|
|
|
846
846
|
type ColumnLayout = {
|
|
847
847
|
count: number;
|
|
848
848
|
gap: number;
|
|
849
|
-
equalWidth?: boolean; /**
|
|
849
|
+
equalWidth?: boolean; /** Authored widths for unequal-width section columns. */
|
|
850
|
+
widths?: number[]; /** Authored space after each column except the last. */
|
|
851
|
+
gaps?: number[]; /** Draw vertical separator line between columns (w:sep). */
|
|
850
852
|
separator?: boolean;
|
|
851
853
|
};
|
|
852
854
|
/**
|
|
@@ -904,7 +906,8 @@ type LayoutOptions = {
|
|
|
904
906
|
w: number;
|
|
905
907
|
h: number;
|
|
906
908
|
}; /** Body-level final section margins. */
|
|
907
|
-
finalMargins?: PageMargins; /**
|
|
909
|
+
finalMargins?: PageMargins; /** Body-level final section column configuration. */
|
|
910
|
+
finalColumns?: ColumnLayout; /** Column configuration. */
|
|
908
911
|
columns?: ColumnLayout; /** Gap between rendered pages (for UI). */
|
|
909
912
|
pageGap?: number; /** Default line height multiplier. */
|
|
910
913
|
defaultLineHeight?: number; /** Header content heights by variant. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { FRAGMENT_CLASS_NAMES, renderFragment } from "./renderFragment.js";
|
|
2
2
|
import { IMAGE_CLASS_NAMES, renderImageFragment } from "./renderImage.js";
|
|
3
3
|
import { renderLine, renderParagraphFragment, sliceRunsForLine } from "./renderParagraph.js";
|
|
4
|
-
import { TABLE_CLASS_NAMES, renderTableFragment } from "./renderTable.js";
|
|
5
4
|
import { TEXTBOX_CLASS_NAMES, renderTextBoxFragment } from "./renderTextBox.js";
|
|
5
|
+
import { TABLE_CLASS_NAMES, renderTableFragment } from "./renderTable.js";
|
|
6
6
|
import { renderPage, renderPages } from "./renderPage.js";
|
|
7
7
|
import { prefersReducedMotionBehavior } from "../paged-layout/scrollNavigation.js";
|
|
8
8
|
import { createFeatureRegistry } from "./registry/registry.js";
|
|
@@ -8,8 +8,8 @@ import { renderFragment } from "./renderFragment.js";
|
|
|
8
8
|
import { applyImageVisualAttrs, hasImageVisualAttrs, renderImageFragment } from "./renderImage.js";
|
|
9
9
|
import { emuToPixels } from "./renderUtils.js";
|
|
10
10
|
import { renderParagraphFragment } from "./renderParagraph.js";
|
|
11
|
-
import { renderTableFragment } from "./renderTable.js";
|
|
12
11
|
import { renderTextBoxFragment } from "./renderTextBox.js";
|
|
12
|
+
import { renderTableFragment } from "./renderTable.js";
|
|
13
13
|
import { renderWatermarkLayer } from "./renderWatermark.js";
|
|
14
14
|
import { panic } from "better-result";
|
|
15
15
|
//#region src/layout-painter/renderPage.ts
|
|
@@ -560,7 +560,12 @@ function renderHeaderFooterContent(content, context, options, layout) {
|
|
|
560
560
|
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
561
561
|
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
|
|
562
562
|
}, block, measure, hfContext, { document: doc });
|
|
563
|
-
|
|
563
|
+
const textBoxTop = block.position ? resolveHeaderFooterFloatTop({
|
|
564
|
+
height: measure.height,
|
|
565
|
+
paragraphY: cursorY,
|
|
566
|
+
position: block.position
|
|
567
|
+
}, layout) : cursorY;
|
|
568
|
+
fragEl.style.top = `${textBoxTop}px`;
|
|
564
569
|
fragEl.style.left = resolveHeaderFooterFloatLeft(measure.width, block.position?.horizontal, layout);
|
|
565
570
|
if (block.wrapType === "behind") fragEl.style.zIndex = "-1";
|
|
566
571
|
containerEl.append(fragEl);
|
|
@@ -910,7 +910,9 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
910
910
|
if (!options.isLastLine || options.paragraphEndsWithLineBreak) {
|
|
911
911
|
const firstLineIndentPx = options.isFirstLine ? options.firstLineIndentPx ?? 0 : 0;
|
|
912
912
|
const firstLineHangingPx = Math.max(0, -firstLineIndentPx);
|
|
913
|
-
const
|
|
913
|
+
const hasVisibleListMarker = options.isFirstLine && block.attrs?.listMarker && !block.attrs.listMarkerHidden;
|
|
914
|
+
const firstLineHangingExpansionPx = hasVisibleListMarker ? Math.min(firstLineHangingPx, Math.max(0, options.leftIndentPx ?? 0)) : firstLineHangingPx;
|
|
915
|
+
const justifyCapacityPx = options.availableWidth + firstLineHangingExpansionPx;
|
|
914
916
|
const overfullPx = line.width - justifyCapacityPx;
|
|
915
917
|
const shrinkableSpaces = countShrinkableSpaces(runsForLine);
|
|
916
918
|
if (overfullPx > RIGHT_EDGE_EPSILON_PX && shrinkableSpaces > 0) {
|
|
@@ -925,7 +927,7 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
925
927
|
lineEl.style.textAlign = "justify";
|
|
926
928
|
lineEl.style.textAlignLast = "justify";
|
|
927
929
|
}
|
|
928
|
-
const listFirstLineOffset =
|
|
930
|
+
const listFirstLineOffset = hasVisibleListMarker ? Math.max(firstLineIndentPx, -firstLineHangingExpansionPx) : 0;
|
|
929
931
|
lineEl.style.width = `${options.availableWidth - listFirstLineOffset}px`;
|
|
930
932
|
}
|
|
931
933
|
}
|
|
@@ -1249,7 +1251,6 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1249
1251
|
} else if (indentLeft > 0) lineEl.style.paddingLeft = `${indentLeft}px`;
|
|
1250
1252
|
else if (hasFirstLine && !isFlexLine) lineEl.style.textIndent = `${indent.firstLine ?? 0}px`;
|
|
1251
1253
|
} else if (indentLeft > 0) lineEl.style.paddingLeft = `${indentLeft}px`;
|
|
1252
|
-
else if (hasHanging && indentLeft === 0) lineEl.style.paddingLeft = `${indent.hanging ?? 0}px`;
|
|
1253
1254
|
if (indentRight > 0) lineEl.style.paddingRight = `${indentRight}px`;
|
|
1254
1255
|
if (isFirstLine && block.attrs?.listMarker && !block.attrs.listMarkerHidden) {
|
|
1255
1256
|
const hanging = indent?.hanging ?? 0;
|
|
@@ -1269,7 +1270,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1269
1270
|
const markerFontSize = block.attrs.listMarkerFontSize ?? firstTextRun?.fontSize ?? block.attrs.defaultFontSize;
|
|
1270
1271
|
const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), doc, markerFontFamily, markerFontSize, block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
|
|
1271
1272
|
const markerMarginLeft = markerStart - Math.min(indentLeft, 0);
|
|
1272
|
-
if (markerMarginLeft < 0
|
|
1273
|
+
if (markerMarginLeft < 0) marker.style.marginLeft = `${markerMarginLeft}px`;
|
|
1273
1274
|
lineEl.prepend(marker);
|
|
1274
1275
|
}
|
|
1275
1276
|
fragmentEl.append(lineEl);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { emuToPixels } from "../utils/units.js";
|
|
2
|
+
import { isFloatingImageRun, isFloatingTextBoxBlock, tableColumnsArePinned } from "../layout-engine/types.js";
|
|
2
3
|
import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
|
|
3
4
|
import { getAutomaticTextColorForBackground } from "./documentColors.js";
|
|
4
5
|
import { renderParagraphFragment } from "./renderParagraph.js";
|
|
5
6
|
import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "../layout-engine/measure/tableCellFloating.js";
|
|
7
|
+
import { renderTextBoxFragment } from "./renderTextBox.js";
|
|
6
8
|
//#region src/layout-painter/renderTable.ts
|
|
7
9
|
/**
|
|
8
10
|
* Table Renderer
|
|
@@ -26,9 +28,6 @@ const TABLE_CLASS_NAMES = {
|
|
|
26
28
|
tableEdgeHandleBottom: "layout-table-edge-handle-bottom",
|
|
27
29
|
tableEdgeHandleRight: "layout-table-edge-handle-right"
|
|
28
30
|
};
|
|
29
|
-
/**
|
|
30
|
-
* Render cell content (paragraphs and nested tables)
|
|
31
|
-
*/
|
|
32
31
|
function renderCellContent(cell, cellMeasure, context, doc) {
|
|
33
32
|
const contentEl = doc.createElement("div");
|
|
34
33
|
contentEl.className = TABLE_CLASS_NAMES.cellContent;
|
|
@@ -37,6 +36,7 @@ function renderCellContent(cell, cellMeasure, context, doc) {
|
|
|
37
36
|
contentEl.style.width = `${contentWidth}px`;
|
|
38
37
|
const cellFloatingImages = getTableCellFloatingImages(cell, cellMeasure, contentWidth);
|
|
39
38
|
const floatingZones = buildTableCellFloatingZones(cellFloatingImages, contentWidth);
|
|
39
|
+
const floatingLayers = [];
|
|
40
40
|
if (cellFloatingImages.length > 0) {
|
|
41
41
|
const floatingLayer = doc.createElement("div");
|
|
42
42
|
floatingLayer.className = "layout-cell-floating-images-layer";
|
|
@@ -47,7 +47,7 @@ function renderCellContent(cell, cellMeasure, context, doc) {
|
|
|
47
47
|
floatingLayer.style.height = "100%";
|
|
48
48
|
floatingLayer.style.pointerEvents = "none";
|
|
49
49
|
floatingLayer.style.zIndex = "10";
|
|
50
|
-
floatingLayer.style.overflow = "
|
|
50
|
+
floatingLayer.style.overflow = "visible";
|
|
51
51
|
for (const img of cellFloatingImages) {
|
|
52
52
|
const imgContainer = doc.createElement("div");
|
|
53
53
|
imgContainer.className = "layout-cell-floating-image";
|
|
@@ -67,9 +67,11 @@ function renderCellContent(cell, cellMeasure, context, doc) {
|
|
|
67
67
|
imgContainer.append(imgEl);
|
|
68
68
|
floatingLayer.append(imgContainer);
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
floatingLayers.push(floatingLayer);
|
|
71
71
|
}
|
|
72
72
|
let cumulativeY = 0;
|
|
73
|
+
let anchorParagraphY = 0;
|
|
74
|
+
let floatingTextBoxesLayer;
|
|
73
75
|
for (let i = 0; i < cell.blocks.length; i++) {
|
|
74
76
|
const block = cell.blocks[i];
|
|
75
77
|
const measure = cellMeasure.blocks[i];
|
|
@@ -103,15 +105,81 @@ function renderCellContent(cell, cellMeasure, context, doc) {
|
|
|
103
105
|
const spaceBefore = paragraphBlock.attrs?.spacing?.before ?? 0;
|
|
104
106
|
if (spaceBefore > 0) fragEl.style.paddingTop = `${spaceBefore}px`;
|
|
105
107
|
contentEl.append(fragEl);
|
|
108
|
+
anchorParagraphY = cumulativeY;
|
|
106
109
|
cumulativeY += paragraphMeasure.totalHeight;
|
|
107
110
|
} else if (block?.kind === "table" && measure?.kind === "table") {
|
|
108
111
|
const nestedTableEl = renderNestedTable(block, measure, context, doc);
|
|
109
112
|
nestedTableEl.style.position = "relative";
|
|
110
113
|
contentEl.append(nestedTableEl);
|
|
111
114
|
cumulativeY += measure.totalHeight;
|
|
115
|
+
anchorParagraphY = cumulativeY;
|
|
116
|
+
} else if (block?.kind === "textBox" && measure?.kind === "textBox") {
|
|
117
|
+
const textBoxBlock = block;
|
|
118
|
+
const textBoxMeasure = measure;
|
|
119
|
+
const textBoxEl = renderTextBoxFragment({
|
|
120
|
+
kind: "textBox",
|
|
121
|
+
blockId: textBoxBlock.id,
|
|
122
|
+
x: 0,
|
|
123
|
+
y: 0,
|
|
124
|
+
width: textBoxMeasure.width,
|
|
125
|
+
height: textBoxMeasure.height,
|
|
126
|
+
...textBoxBlock.pmStart !== void 0 ? { pmStart: textBoxBlock.pmStart } : {},
|
|
127
|
+
...textBoxBlock.pmEnd !== void 0 ? { pmEnd: textBoxBlock.pmEnd } : {}
|
|
128
|
+
}, textBoxBlock, textBoxMeasure, {
|
|
129
|
+
...context,
|
|
130
|
+
insideTableCell: true
|
|
131
|
+
}, { document: doc });
|
|
132
|
+
if (isFloatingTextBoxBlock(textBoxBlock)) {
|
|
133
|
+
floatingTextBoxesLayer ??= createCellFloatingTextBoxesLayer(doc);
|
|
134
|
+
textBoxEl.style.left = `${resolveCellTextBoxX(textBoxBlock, contentWidth)}px`;
|
|
135
|
+
textBoxEl.style.top = `${anchorParagraphY + resolveCellTextBoxY(textBoxBlock)}px`;
|
|
136
|
+
textBoxEl.style.pointerEvents = "auto";
|
|
137
|
+
floatingTextBoxesLayer.append(textBoxEl);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
textBoxEl.style.position = "relative";
|
|
141
|
+
textBoxEl.style.left = "0";
|
|
142
|
+
textBoxEl.style.top = "0";
|
|
143
|
+
contentEl.append(textBoxEl);
|
|
144
|
+
cumulativeY += textBoxMeasure.height;
|
|
145
|
+
anchorParagraphY = cumulativeY;
|
|
112
146
|
}
|
|
113
147
|
}
|
|
114
|
-
|
|
148
|
+
if (floatingTextBoxesLayer) floatingLayers.push(floatingTextBoxesLayer);
|
|
149
|
+
return {
|
|
150
|
+
content: contentEl,
|
|
151
|
+
floatingLayers
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function createCellFloatingTextBoxesLayer(doc) {
|
|
155
|
+
const layer = doc.createElement("div");
|
|
156
|
+
layer.className = "layout-cell-floating-text-boxes-layer";
|
|
157
|
+
layer.style.position = "absolute";
|
|
158
|
+
layer.style.inset = "0";
|
|
159
|
+
layer.style.pointerEvents = "none";
|
|
160
|
+
layer.style.zIndex = "10";
|
|
161
|
+
layer.style.overflow = "visible";
|
|
162
|
+
return layer;
|
|
163
|
+
}
|
|
164
|
+
function resolveCellTextBoxX(block, contentWidth) {
|
|
165
|
+
const horizontal = block.position?.horizontal;
|
|
166
|
+
if (horizontal?.posOffset !== void 0) return emuToPixels(horizontal.posOffset);
|
|
167
|
+
if (horizontal?.align === "center") return (contentWidth - block.width) / 2;
|
|
168
|
+
if (horizontal?.align === "right" || horizontal?.align === "outside") return contentWidth - block.width;
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
function resolveCellTextBoxY(block) {
|
|
172
|
+
const vertical = block.position?.vertical;
|
|
173
|
+
if (vertical?.posOffset !== void 0) return emuToPixels(vertical.posOffset);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
function tableHasFloatingCellContent(block) {
|
|
177
|
+
for (const row of block.rows) for (const cell of row.cells) for (const cellBlock of cell.blocks) {
|
|
178
|
+
if (cellBlock.kind === "textBox" && isFloatingTextBoxBlock(cellBlock)) return true;
|
|
179
|
+
if (cellBlock.kind === "paragraph" && cellBlock.runs.some((run) => run.kind === "image" && isFloatingImageRun(run))) return true;
|
|
180
|
+
if (cellBlock.kind === "table" && tableHasFloatingCellContent(cellBlock)) return true;
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
115
183
|
}
|
|
116
184
|
/**
|
|
117
185
|
* Render a nested table (within a cell)
|
|
@@ -214,8 +282,18 @@ function renderTableCell(cell, cellMeasure, x, rowHeight, borderFlags, columnsPi
|
|
|
214
282
|
default: break;
|
|
215
283
|
}
|
|
216
284
|
}
|
|
217
|
-
const
|
|
218
|
-
|
|
285
|
+
const renderedContent = renderCellContent(cell, cellMeasure, context, doc);
|
|
286
|
+
if (renderedContent.floatingLayers.length > 0) {
|
|
287
|
+
renderedContent.content.style.height = "100%";
|
|
288
|
+
renderedContent.content.style.overflow = "hidden";
|
|
289
|
+
cellEl.style.overflow = "visible";
|
|
290
|
+
}
|
|
291
|
+
cellEl.append(renderedContent.content);
|
|
292
|
+
for (const floatingLayer of renderedContent.floatingLayers) {
|
|
293
|
+
floatingLayer.style.left = `${padLeft}px`;
|
|
294
|
+
floatingLayer.style.top = `${padTop}px`;
|
|
295
|
+
cellEl.append(floatingLayer);
|
|
296
|
+
}
|
|
219
297
|
if (cell.blocks.length > 0) {
|
|
220
298
|
const firstBlock = cell.blocks.at(0);
|
|
221
299
|
const lastBlock = cell.blocks.at(-1);
|
|
@@ -316,7 +394,7 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
|
|
|
316
394
|
tableEl.style.position = "absolute";
|
|
317
395
|
tableEl.style.width = `${fragment.width}px`;
|
|
318
396
|
tableEl.style.height = `${fragment.height}px`;
|
|
319
|
-
tableEl.style.overflow = "hidden";
|
|
397
|
+
tableEl.style.overflow = tableHasFloatingCellContent(block) ? "visible" : "hidden";
|
|
320
398
|
tableEl.dataset["blockId"] = String(fragment.blockId);
|
|
321
399
|
tableEl.dataset["fromRow"] = String(fragment.fromRow);
|
|
322
400
|
tableEl.dataset["toRow"] = String(fragment.toRow);
|
|
@@ -20,8 +20,10 @@ function computePerBlockWidths({ blocks, bodyConfig, finalConfig }) {
|
|
|
20
20
|
}).widths;
|
|
21
21
|
}
|
|
22
22
|
function computePerBlockMeasureInputs({ blocks, bodyConfig, finalConfig }) {
|
|
23
|
-
function colWidth(cw, cols) {
|
|
23
|
+
function colWidth(cw, cols, columnIndex) {
|
|
24
24
|
if (cols.count <= 1) return cw;
|
|
25
|
+
const authoredWidth = cols.widths?.[columnIndex];
|
|
26
|
+
if (authoredWidth !== void 0) return authoredWidth;
|
|
25
27
|
return Math.floor((cw - (cols.count - 1) * cols.gap) / cols.count);
|
|
26
28
|
}
|
|
27
29
|
function contentWidth(config) {
|
|
@@ -29,17 +31,23 @@ function computePerBlockMeasureInputs({ blocks, bodyConfig, finalConfig }) {
|
|
|
29
31
|
}
|
|
30
32
|
const { configs: sectionConfigs, breakIndices } = collectSectionConfigs(blocks, bodyConfig, finalConfig);
|
|
31
33
|
let sectionIdx = 0;
|
|
34
|
+
let columnIndex = 0;
|
|
32
35
|
const widths = [];
|
|
33
36
|
const marginTops = [];
|
|
34
37
|
const pageHeights = [];
|
|
35
38
|
const marginBottoms = [];
|
|
36
39
|
for (let i = 0; i < blocks.length; i++) {
|
|
37
40
|
const config = sectionConfigs[sectionIdx] ?? finalConfig;
|
|
38
|
-
|
|
41
|
+
const columns = config.columns ?? SINGLE_COLUMN_LAYOUT;
|
|
42
|
+
widths.push(colWidth(contentWidth(config), columns, columnIndex));
|
|
39
43
|
marginTops.push(config.margins.top);
|
|
40
44
|
pageHeights.push(config.pageSize.h);
|
|
41
45
|
marginBottoms.push(config.margins.bottom);
|
|
42
|
-
if (sectionIdx < breakIndices.length && i === breakIndices[sectionIdx])
|
|
46
|
+
if (sectionIdx < breakIndices.length && i === breakIndices[sectionIdx]) {
|
|
47
|
+
sectionIdx++;
|
|
48
|
+
columnIndex = 0;
|
|
49
|
+
} else if (blocks[i]?.kind === "pageBreak") columnIndex = 0;
|
|
50
|
+
else if (blocks[i]?.kind === "columnBreak") columnIndex = (columnIndex + 1) % columns.count;
|
|
43
51
|
}
|
|
44
52
|
return {
|
|
45
53
|
widths,
|
|
@@ -1430,9 +1430,18 @@ function tableRowAttrsToFormatting(attrs) {
|
|
|
1430
1430
|
function convertPMTableCell(node, documentCounts) {
|
|
1431
1431
|
const attrs = expectTableCellAttrs(node);
|
|
1432
1432
|
const content = [];
|
|
1433
|
+
let previousStandaloneTextBox = null;
|
|
1433
1434
|
node.forEach((contentNode) => {
|
|
1434
|
-
if (contentNode.type.name === "paragraph")
|
|
1435
|
-
|
|
1435
|
+
if (contentNode.type.name === "paragraph") {
|
|
1436
|
+
content.push(convertPMParagraph(contentNode, documentCounts));
|
|
1437
|
+
previousStandaloneTextBox = null;
|
|
1438
|
+
} else if (contentNode.type.name === "table") {
|
|
1439
|
+
content.push(convertPMTable(contentNode, documentCounts));
|
|
1440
|
+
previousStandaloneTextBox = null;
|
|
1441
|
+
} else if (contentNode.type.name === "textBox") previousStandaloneTextBox = appendTextBoxBlock(content, contentNode, {
|
|
1442
|
+
pendingPageBreaks: 0,
|
|
1443
|
+
previousStandaloneTextBox
|
|
1444
|
+
});
|
|
1436
1445
|
});
|
|
1437
1446
|
const cell = {
|
|
1438
1447
|
type: "tableCell",
|
|
@@ -24,15 +24,18 @@ function toProseDoc(document, options) {
|
|
|
24
24
|
const styleResolver = createStyleEngine(options?.styles ?? document.package.styles);
|
|
25
25
|
const theme = options?.theme ?? document.package.theme ?? null;
|
|
26
26
|
let textBoxGroupIndex = 0;
|
|
27
|
+
const nextTextBoxGroupId = () => String(textBoxGroupIndex++);
|
|
27
28
|
const convertBodyBlocks = (blocks) => {
|
|
28
29
|
const out = [];
|
|
29
30
|
for (const block of blocks) if (block.type === "paragraph") {
|
|
30
31
|
const pbPos = paragraphPageBreakPosition(block);
|
|
31
32
|
if (pbPos === "before") out.push(schema.node("pageBreak"));
|
|
32
|
-
out.push(...convertParagraphWithTextBoxes(block, styleResolver,
|
|
33
|
-
textBoxGroupIndex += 1;
|
|
33
|
+
out.push(...convertParagraphWithTextBoxes(block, styleResolver, { textBoxGroupId: nextTextBoxGroupId() }));
|
|
34
34
|
if (pbPos === "after") out.push(schema.node("pageBreak"));
|
|
35
|
-
} else if (block.type === "table") out.push(convertTable(block, styleResolver,
|
|
35
|
+
} else if (block.type === "table") out.push(convertTable(block, styleResolver, {
|
|
36
|
+
theme,
|
|
37
|
+
nextTextBoxGroupId
|
|
38
|
+
}));
|
|
36
39
|
else out.push(convertBlockSdt(block, convertBodyBlocks));
|
|
37
40
|
return out;
|
|
38
41
|
};
|
|
@@ -535,7 +538,7 @@ function paragraphContentHasMeaningfulContent(content) {
|
|
|
535
538
|
if (content.type === "insertion" || content.type === "deletion" || content.type === "moveFrom" || content.type === "moveTo") return content.content.some(paragraphContentHasMeaningfulContent);
|
|
536
539
|
return true;
|
|
537
540
|
}
|
|
538
|
-
function convertTable(table, styleResolver,
|
|
541
|
+
function convertTable(table, styleResolver, context) {
|
|
539
542
|
const rowSpanMap = calculateRowSpans(table);
|
|
540
543
|
const columnWidths = table.columnWidths;
|
|
541
544
|
const totalWidth = columnWidths?.reduce((sum, w) => sum + w, 0) ?? 0;
|
|
@@ -604,7 +607,7 @@ function convertTable(table, styleResolver, theme) {
|
|
|
604
607
|
})();
|
|
605
608
|
})();
|
|
606
609
|
if (bandingEnabledH && !isFirstRowStyled && !isLastRow) dataRowIndex++;
|
|
607
|
-
return convertTableRow(row, styleResolver, isFirstRowStyled, columnWidths, totalWidth, conditionalStyles, rowBandStyle, bandingEnabledV, look, resolvedTableBorders, rowIndex, totalRows, totalColumns, rowSpanMap, cellMarginsAttr
|
|
610
|
+
return convertTableRow(row, styleResolver, context, isFirstRowStyled, columnWidths, totalWidth, conditionalStyles, rowBandStyle, bandingEnabledV, look, resolvedTableBorders, rowIndex, totalRows, totalColumns, rowSpanMap, cellMarginsAttr);
|
|
608
611
|
});
|
|
609
612
|
return schema.node("table", attrs, rows);
|
|
610
613
|
}
|
|
@@ -620,7 +623,7 @@ function countTableColumns(rows) {
|
|
|
620
623
|
/**
|
|
621
624
|
* Convert a TableRow to a ProseMirror table row node
|
|
622
625
|
*/
|
|
623
|
-
function convertTableRow(row, styleResolver, isHeaderRow, columnWidths, totalWidth, conditionalStyles, rowBandStyle, bandingEnabledV, tableLook, tableBorders, rowIndex, totalRows, totalColumns, rowSpanMap, defaultCellMargins
|
|
626
|
+
function convertTableRow(row, styleResolver, context, isHeaderRow, columnWidths, totalWidth, conditionalStyles, rowBandStyle, bandingEnabledV, tableLook, tableBorders, rowIndex, totalRows, totalColumns, rowSpanMap, defaultCellMargins) {
|
|
624
627
|
const attrs = { isHeader: !!row.formatting?.header };
|
|
625
628
|
if (row.formatting?.height?.value !== void 0) attrs.height = row.formatting.height.value;
|
|
626
629
|
if (row.formatting?.heightRule) attrs.heightRule = row.formatting.heightRule;
|
|
@@ -695,7 +698,7 @@ function convertTableRow(row, styleResolver, isHeaderRow, columnWidths, totalWid
|
|
|
695
698
|
if (cellIsFirstRow && cellIsLastCol && (tableLook?.firstRow || rowCnf?.firstRow || cellCnf?.firstRow) && (tableLook?.lastColumn || rowCnf?.lastColumn || cellCnf?.lastColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.neCell);
|
|
696
699
|
if (cellIsLastRow && cellIsFirstCol && (tableLook?.lastRow || rowCnf?.lastRow || cellCnf?.lastRow) && (tableLook?.firstColumn || rowCnf?.firstColumn || cellCnf?.firstColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.swCell);
|
|
697
700
|
if (cellIsLastRow && cellIsLastCol && (tableLook?.lastRow || rowCnf?.lastRow || cellCnf?.lastRow) && (tableLook?.lastColumn || rowCnf?.lastColumn || cellCnf?.lastColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.seCell);
|
|
698
|
-
cells.push(convertTableCell(cell, styleResolver, isHeaderRow, gridWidth, cellConditionalStyle, tableBorders, isFirstRow, isLastRow, isFirstCol, isLastCol, calculatedRowSpan, preserveVMergeRestart, rowSpanInfo?.continuationCells, defaultCellMargins
|
|
701
|
+
cells.push(convertTableCell(cell, styleResolver, context, isHeaderRow, gridWidth, cellConditionalStyle, tableBorders, isFirstRow, isLastRow, isFirstCol, isLastCol, calculatedRowSpan, preserveVMergeRestart, rowSpanInfo?.continuationCells, defaultCellMargins));
|
|
699
702
|
}
|
|
700
703
|
return schema.node("tableRow", attrs, cells);
|
|
701
704
|
}
|
|
@@ -724,7 +727,8 @@ function resolveThemedBorderColors(borders, theme) {
|
|
|
724
727
|
/**
|
|
725
728
|
* Convert a TableCell to a ProseMirror table cell node
|
|
726
729
|
*/
|
|
727
|
-
function convertTableCell(cell, styleResolver, isHeader, gridWidthPercent, conditionalStyle, tableBorders, isFirstRow, isLastRow, isFirstCol, isLastCol, calculatedRowSpan, preserveVMergeRestart, vMergeContinuationCells, defaultCellMargins
|
|
730
|
+
function convertTableCell(cell, styleResolver, context, isHeader, gridWidthPercent, conditionalStyle, tableBorders, isFirstRow, isLastRow, isFirstCol, isLastCol, calculatedRowSpan, preserveVMergeRestart, vMergeContinuationCells, defaultCellMargins) {
|
|
731
|
+
const { theme } = context;
|
|
728
732
|
const formatting = cell.formatting;
|
|
729
733
|
const rowspan = calculatedRowSpan ?? 1;
|
|
730
734
|
let width = formatting?.width?.value;
|
|
@@ -776,8 +780,12 @@ function convertTableCell(cell, styleResolver, isHeader, gridWidthPercent, condi
|
|
|
776
780
|
if (preserveVMergeRestart) attrs._preserveVMergeRestart = true;
|
|
777
781
|
if (vMergeContinuationCells && vMergeContinuationCells.length > 0) attrs._docxVMergeContinuationCells = vMergeContinuationCells;
|
|
778
782
|
const contentNodes = [];
|
|
779
|
-
for (const content of cell.content) if (content.type === "paragraph") contentNodes.push(
|
|
780
|
-
|
|
783
|
+
for (const content of cell.content) if (content.type === "paragraph") contentNodes.push(...convertParagraphWithTextBoxes(content, styleResolver, {
|
|
784
|
+
textBoxGroupId: context.nextTextBoxGroupId(),
|
|
785
|
+
...conditionalStyle?.rPr !== void 0 ? { extraRunFormatting: conditionalStyle.rPr } : {},
|
|
786
|
+
...conditionalStyle?.pPr !== void 0 ? { tableParagraphOverlay: conditionalStyle.pPr } : {}
|
|
787
|
+
}));
|
|
788
|
+
else contentNodes.push(convertTable(content, styleResolver, context));
|
|
781
789
|
if (contentNodes.length === 0) contentNodes.push(schema.node("paragraph", {}, []));
|
|
782
790
|
const nodeType = isHeader ? "tableHeader" : "tableCell";
|
|
783
791
|
return schema.node(nodeType, attrs, contentNodes);
|
|
@@ -1214,13 +1222,9 @@ function convertShape(shape) {
|
|
|
1214
1222
|
position
|
|
1215
1223
|
});
|
|
1216
1224
|
}
|
|
1217
|
-
|
|
1218
|
-
* Convert a paragraph block to PM nodes, extracting text boxes as sibling nodes.
|
|
1219
|
-
* Skips ghost empty paragraphs that only contained text box drawings.
|
|
1220
|
-
*/
|
|
1221
|
-
function convertParagraphWithTextBoxes(block, styleResolver, textBoxGroupId) {
|
|
1225
|
+
function convertParagraphWithTextBoxes(block, styleResolver, { textBoxGroupId, extraRunFormatting, tableParagraphOverlay }) {
|
|
1222
1226
|
const textBoxes = extractTextBoxesFromParagraph(block);
|
|
1223
|
-
const pmParagraph = convertParagraph(block, styleResolver);
|
|
1227
|
+
const pmParagraph = convertParagraph(block, styleResolver, void 0, extraRunFormatting, tableParagraphOverlay);
|
|
1224
1228
|
const nodes = [];
|
|
1225
1229
|
const isEmptyAfterExtraction = textBoxes.length > 0 && pmParagraph.content.size === 0;
|
|
1226
1230
|
const keepWrapperParagraph = isEmptyAfterExtraction && hasParagraphBoundaryPayload(block, pmParagraph);
|
|
@@ -1361,12 +1365,14 @@ function headerFooterToProseDoc(content, options) {
|
|
|
1361
1365
|
const styleResolver = options?.styles ? createStyleEngine(options.styles) : null;
|
|
1362
1366
|
const theme = options?.theme ?? null;
|
|
1363
1367
|
let textBoxGroupIndex = 0;
|
|
1368
|
+
const nextTextBoxGroupId = () => String(textBoxGroupIndex++);
|
|
1364
1369
|
const convertBlocks = (blocks) => {
|
|
1365
1370
|
const out = [];
|
|
1366
|
-
for (const block of blocks) if (block.type === "paragraph") {
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1371
|
+
for (const block of blocks) if (block.type === "paragraph") out.push(...convertParagraphWithTextBoxes(block, styleResolver, { textBoxGroupId: nextTextBoxGroupId() }));
|
|
1372
|
+
else if (block.type === "table") out.push(convertTable(block, styleResolver, {
|
|
1373
|
+
theme,
|
|
1374
|
+
nextTextBoxGroupId
|
|
1375
|
+
}));
|
|
1370
1376
|
else out.push(convertBlockSdt(block, convertBlocks));
|
|
1371
1377
|
return out;
|
|
1372
1378
|
};
|
|
@@ -298,7 +298,7 @@ function buildCellWidthStyles(attrs) {
|
|
|
298
298
|
return styles;
|
|
299
299
|
}
|
|
300
300
|
const tableCellSpec = {
|
|
301
|
-
content: "(paragraph | table)+",
|
|
301
|
+
content: "(paragraph | table | textBox)+",
|
|
302
302
|
tableRole: "cell",
|
|
303
303
|
isolating: true,
|
|
304
304
|
attrs: {
|
|
@@ -351,7 +351,7 @@ const tableCellSpec = {
|
|
|
351
351
|
}
|
|
352
352
|
};
|
|
353
353
|
const tableHeaderSpec = {
|
|
354
|
-
content: "(paragraph | table)+",
|
|
354
|
+
content: "(paragraph | table | textBox)+",
|
|
355
355
|
tableRole: "header_cell",
|
|
356
356
|
isolating: true,
|
|
357
357
|
attrs: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|