@stll/folio-core 0.6.1 → 0.8.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/dist/ai-edits/blockRange.d.ts +13 -2
- package/dist/ai-edits/blockRange.js +23 -2
- package/dist/ai-edits/index.d.ts +4 -2
- package/dist/ai-edits/index.js +3 -1
- package/dist/ai-edits/scoped-reading.d.ts +9 -0
- package/dist/ai-edits/scoped-reading.js +60 -0
- package/dist/ai-edits/snapshot.js +13 -4
- package/dist/ai-edits/types.d.ts +44 -2
- package/dist/compat/eigenpal.d.ts +5 -2
- package/dist/compat/eigenpal.js +4 -1
- package/dist/controller/fontReadiness.d.ts +29 -0
- package/dist/controller/fontReadiness.js +139 -0
- package/dist/controller/layoutPipeline.js +86 -11
- package/dist/docx/blockContentParser.js +51 -8
- package/dist/docx/capabilities.d.ts +41 -0
- package/dist/docx/capabilities.js +322 -0
- package/dist/docx/compatibility.d.ts +34 -2
- package/dist/docx/compatibility.js +128 -38
- package/dist/docx/conformance.d.ts +6 -0
- package/dist/docx/conformance.js +18 -0
- package/dist/docx/ensureParaIds.d.ts +29 -0
- package/dist/docx/ensureParaIds.js +423 -0
- package/dist/docx/groupDrawingParser.d.ts +1 -1
- package/dist/docx/groupDrawingParser.js +49 -8
- package/dist/docx/numberingParser.js +1 -0
- package/dist/docx/paragraphParser.js +9 -0
- package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
- package/dist/docx/parser.js +2 -0
- package/dist/docx/runParser.js +11 -2
- package/dist/docx/selectiveXmlPatch.d.ts +10 -1
- package/dist/docx/selectiveXmlPatch.js +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +4 -0
- package/dist/docx/serializer/runSerializer.js +7 -2
- package/dist/docx/serializer/tableSerializer.js +4 -0
- package/dist/docx/server/boundedArchive.d.ts +24 -0
- package/dist/docx/server/boundedArchive.js +106 -0
- package/dist/docx/server/extractDocxText.d.ts +23 -0
- package/dist/docx/server/extractDocxText.js +154 -0
- package/dist/docx/tableParser.js +8 -0
- package/dist/docx/textBoxParser.js +6 -1
- package/dist/docx/vmlImageParser.js +145 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/layout-bridge/convert/headerFooterLayout.js +16 -2
- package/dist/layout-bridge/convert/paragraphFrames.d.ts +7 -0
- package/dist/layout-bridge/convert/paragraphFrames.js +136 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +106 -13
- package/dist/layout-bridge/engine/hitTest.js +2 -1
- package/dist/layout-bridge/engine/selectionRects.js +2 -1
- package/dist/layout-engine/headerFooterRefs.d.ts +7 -0
- package/dist/layout-engine/headerFooterRefs.js +40 -0
- package/dist/layout-engine/index.d.ts +3 -2
- package/dist/layout-engine/index.js +125 -72
- package/dist/layout-engine/keep-together.d.ts +7 -5
- package/dist/layout-engine/keep-together.js +20 -4
- package/dist/layout-engine/measure/cache.js +2 -0
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
- package/dist/layout-engine/measure/listMarkerWidth.js +23 -4
- package/dist/layout-engine/measure/measureBlocks.d.ts +3 -0
- package/dist/layout-engine/measure/measureBlocks.js +133 -51
- package/dist/layout-engine/measure/measureParagraph.js +42 -15
- package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -0
- package/dist/layout-engine/measure/tableCellGrid.js +62 -0
- package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +18 -0
- package/dist/layout-engine/measure/textBoxParagraphLayout.js +32 -0
- package/dist/layout-engine/paginator.d.ts +2 -1
- package/dist/layout-engine/paginator.js +17 -8
- package/dist/layout-engine/paragraphFrame.d.ts +22 -0
- package/dist/layout-engine/paragraphFrame.js +17 -0
- package/dist/layout-engine/paragraphSequence.d.ts +7 -0
- package/dist/layout-engine/paragraphSequence.js +25 -0
- package/dist/layout-engine/paragraphSpacing.d.ts +17 -0
- package/dist/layout-engine/paragraphSpacing.js +30 -0
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +58 -0
- package/dist/layout-engine/renderedBreakReconciliation.js +63 -0
- package/dist/layout-engine/types.d.ts +31 -10
- package/dist/layout-engine/types.js +15 -2
- package/dist/layout-painter/renderPage.js +2 -2
- package/dist/layout-painter/renderParagraph.js +94 -10
- package/dist/layout-painter/renderTable.js +42 -12
- package/dist/layout-painter/renderTextBox.js +17 -7
- package/dist/managers/DocumentLoaderManager.js +1 -1
- package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
- package/dist/paged-layout/sectionBlockWidths.js +9 -0
- package/dist/paged-layout/sectionGeometry.js +1 -1
- package/dist/prosemirror/attrs/index.js +11 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +2 -0
- package/dist/prosemirror/conversion/toProseDoc.js +29 -23
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -0
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
- package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/nodes.d.ts +6 -3
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/utils/tabCalculator.js +1 -1
- package/dist/server.d.ts +6 -2
- package/dist/server.js +5 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/utils/createDocument.js +26 -18
- package/dist/utils/formatToStyle.js +3 -3
- package/dist/utils/hexId.d.ts +8 -1
- package/dist/utils/hexId.js +11 -3
- package/dist/utils/units.d.ts +6 -6
- package/dist/utils/units.js +8 -8
- package/package.json +1 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { findAllDeep, findChild, findDeep, getAttributeAnyPrefix, getLocalName, getTextContent, parseXml } from "../xmlParser.js";
|
|
2
|
+
import { loadDocxArchive } from "./boundedArchive.js";
|
|
3
|
+
//#region src/docx/server/extractDocxText.ts
|
|
4
|
+
const HEADER_FOOTER_PATH = /^word\/(?:header|footer)\d+\.xml$/u;
|
|
5
|
+
const childElements = (element) => element.elements?.filter((child) => child.type === "element") ?? [];
|
|
6
|
+
const collectText = (element) => {
|
|
7
|
+
let text = "";
|
|
8
|
+
const walk = (node) => {
|
|
9
|
+
const localName = getLocalName(node.name ?? "");
|
|
10
|
+
if (localName === "t") {
|
|
11
|
+
text += getTextContent(node);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (localName === "br") {
|
|
15
|
+
text += "\n";
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (localName === "tab") {
|
|
19
|
+
text += " ";
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (localName === "del" || localName === "delText" || localName === "moveFrom") return;
|
|
23
|
+
for (const child of childElements(node)) walk(child);
|
|
24
|
+
};
|
|
25
|
+
walk(element);
|
|
26
|
+
return text;
|
|
27
|
+
};
|
|
28
|
+
const readParagraphProperties = (paragraph) => {
|
|
29
|
+
const properties = findChild(paragraph, "w", "pPr");
|
|
30
|
+
if (!properties) return {};
|
|
31
|
+
const result = {};
|
|
32
|
+
const styleValue = getAttributeAnyPrefix(findChild(properties, "w", "pStyle"), "val");
|
|
33
|
+
if (styleValue !== null) result.style = styleValue;
|
|
34
|
+
const alignment = getAttributeAnyPrefix(findChild(properties, "w", "jc"), "val");
|
|
35
|
+
if (alignment === "left" || alignment === "center" || alignment === "right" || alignment === "both") result.alignment = alignment;
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
const readRunMetrics = (paragraph) => {
|
|
39
|
+
const metrics = [];
|
|
40
|
+
for (const run of childElements(paragraph)) {
|
|
41
|
+
if (getLocalName(run.name ?? "") !== "r") continue;
|
|
42
|
+
const properties = findChild(run, "w", "rPr");
|
|
43
|
+
const boldProperty = findChild(properties, "w", "b");
|
|
44
|
+
const boldValue = getAttributeAnyPrefix(boldProperty, "val");
|
|
45
|
+
const bold = boldProperty !== null && boldValue !== "0" && boldValue !== "false";
|
|
46
|
+
const sizeValue = getAttributeAnyPrefix(findChild(properties, "w", "sz"), "val");
|
|
47
|
+
const parsedSize = sizeValue === null ? NaN : Number.parseInt(sizeValue, 10);
|
|
48
|
+
const fontSize = Number.isFinite(parsedSize) && parsedSize > 0 ? parsedSize : void 0;
|
|
49
|
+
let chars = 0;
|
|
50
|
+
for (const textNode of findAllDeep(run, "w", "t")) chars += getTextContent(textNode).length;
|
|
51
|
+
if (chars === 0) continue;
|
|
52
|
+
const entry = {
|
|
53
|
+
bold,
|
|
54
|
+
chars
|
|
55
|
+
};
|
|
56
|
+
if (fontSize !== void 0) entry.fontSize = fontSize;
|
|
57
|
+
metrics.push(entry);
|
|
58
|
+
}
|
|
59
|
+
return metrics;
|
|
60
|
+
};
|
|
61
|
+
const extractContainer = ({ container, source, startIndex }) => {
|
|
62
|
+
const paragraphs = [];
|
|
63
|
+
let charCount = 0;
|
|
64
|
+
for (const [offset, paragraph] of findAllDeep(container, "w", "p").entries()) {
|
|
65
|
+
const text = collectText(paragraph);
|
|
66
|
+
const entry = {
|
|
67
|
+
index: startIndex + offset,
|
|
68
|
+
text,
|
|
69
|
+
source
|
|
70
|
+
};
|
|
71
|
+
const { style, alignment } = readParagraphProperties(paragraph);
|
|
72
|
+
if (style !== void 0) entry.style = style;
|
|
73
|
+
if (alignment !== void 0) entry.alignment = alignment;
|
|
74
|
+
const runs = readRunMetrics(paragraph);
|
|
75
|
+
if (runs.length > 0) {
|
|
76
|
+
const totalChars = runs.reduce((sum, run) => sum + run.chars, 0);
|
|
77
|
+
if (runs.reduce((sum, run) => sum + (run.bold ? run.chars : 0), 0) > totalChars / 2) entry.bold = true;
|
|
78
|
+
const firstFontSize = runs.find((run) => run.fontSize !== void 0)?.fontSize;
|
|
79
|
+
if (firstFontSize !== void 0) entry.fontSize = firstFontSize;
|
|
80
|
+
}
|
|
81
|
+
paragraphs.push(entry);
|
|
82
|
+
charCount += text.length;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
paragraphs,
|
|
86
|
+
charCount
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const extractParts = async ({ archive, source, rootName, startIndex }) => {
|
|
90
|
+
const paragraphs = [];
|
|
91
|
+
let charCount = 0;
|
|
92
|
+
let nextIndex = startIndex;
|
|
93
|
+
const prefix = `word/${source}`;
|
|
94
|
+
const paths = archive.entries.filter((path) => HEADER_FOOTER_PATH.test(path) && path.startsWith(prefix)).toSorted();
|
|
95
|
+
for (const path of paths) {
|
|
96
|
+
const xml = await archive.readEntryString(path);
|
|
97
|
+
if (xml === null) continue;
|
|
98
|
+
const container = findDeep(parseXml(xml), "w", rootName);
|
|
99
|
+
if (!container) continue;
|
|
100
|
+
const result = extractContainer({
|
|
101
|
+
container,
|
|
102
|
+
source,
|
|
103
|
+
startIndex: nextIndex
|
|
104
|
+
});
|
|
105
|
+
paragraphs.push(...result.paragraphs);
|
|
106
|
+
charCount += result.charCount;
|
|
107
|
+
nextIndex += result.paragraphs.length;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
paragraphs,
|
|
111
|
+
charCount
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
const createEmptyResult = () => ({
|
|
115
|
+
paragraphs: [],
|
|
116
|
+
charCount: 0,
|
|
117
|
+
view: "accepted"
|
|
118
|
+
});
|
|
119
|
+
/** Extract paragraph text and formatting metadata from a DOCX archive. */
|
|
120
|
+
const extractDocxText = async (bytes) => {
|
|
121
|
+
const archive = await loadDocxArchive(bytes);
|
|
122
|
+
const documentXml = await archive.readEntryString("word/document.xml");
|
|
123
|
+
if (documentXml === null) return createEmptyResult();
|
|
124
|
+
const body = findDeep(parseXml(documentXml), "w", "body");
|
|
125
|
+
if (!body) return createEmptyResult();
|
|
126
|
+
const headers = await extractParts({
|
|
127
|
+
archive,
|
|
128
|
+
source: "header",
|
|
129
|
+
rootName: "hdr",
|
|
130
|
+
startIndex: 0
|
|
131
|
+
});
|
|
132
|
+
const bodyResult = extractContainer({
|
|
133
|
+
container: body,
|
|
134
|
+
source: "body",
|
|
135
|
+
startIndex: headers.paragraphs.length
|
|
136
|
+
});
|
|
137
|
+
const footers = await extractParts({
|
|
138
|
+
archive,
|
|
139
|
+
source: "footer",
|
|
140
|
+
rootName: "ftr",
|
|
141
|
+
startIndex: headers.paragraphs.length + bodyResult.paragraphs.length
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
paragraphs: [
|
|
145
|
+
...headers.paragraphs,
|
|
146
|
+
...bodyResult.paragraphs,
|
|
147
|
+
...footers.paragraphs
|
|
148
|
+
],
|
|
149
|
+
charCount: headers.charCount + bodyResult.charCount + footers.charCount,
|
|
150
|
+
view: "accepted"
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
//#endregion
|
|
154
|
+
export { extractDocxText };
|
package/dist/docx/tableParser.js
CHANGED
|
@@ -353,6 +353,14 @@ function parseTableCellStructuralChange(tcPrElement) {
|
|
|
353
353
|
function parseTableRowProperties(trPrElement) {
|
|
354
354
|
if (!trPrElement) return;
|
|
355
355
|
const formatting = {};
|
|
356
|
+
const gridBefore = parseNumericAttribute(findChild(trPrElement, "w", "gridBefore"), "w", "val");
|
|
357
|
+
if (gridBefore !== void 0 && gridBefore > 0) formatting.gridBefore = gridBefore;
|
|
358
|
+
const widthBefore = parseTableMeasurement(findChild(trPrElement, "w", "wBefore"));
|
|
359
|
+
if (widthBefore) formatting.widthBefore = widthBefore;
|
|
360
|
+
const gridAfter = parseNumericAttribute(findChild(trPrElement, "w", "gridAfter"), "w", "val");
|
|
361
|
+
if (gridAfter !== void 0 && gridAfter > 0) formatting.gridAfter = gridAfter;
|
|
362
|
+
const widthAfter = parseTableMeasurement(findChild(trPrElement, "w", "wAfter"));
|
|
363
|
+
if (widthAfter) formatting.widthAfter = widthAfter;
|
|
356
364
|
const heightElement = findChild(trPrElement, "w", "trHeight");
|
|
357
365
|
if (heightElement) {
|
|
358
366
|
const heightVal = parseNumericAttribute(heightElement, "w", "val");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findByFullName, findChildrenByLocalName, getAttribute, getChildElements, parseNumericAttribute } from "./xmlParser.js";
|
|
1
|
+
import { findByFullName, findChildByLocalName, findChildrenByLocalName, getAttribute, getChildElements, parseNumericAttribute } from "./xmlParser.js";
|
|
2
2
|
import { emuToPixels } from "../utils/units.js";
|
|
3
3
|
import { parseAnchorPosition, parseAnchorWrap, parseFill, parseOutline, resolveColorValueToHex } from "./drawingUtils.js";
|
|
4
4
|
//#region src/docx/textBoxParser.ts
|
|
@@ -11,6 +11,9 @@ const DEFAULT_MARGIN_EMU = 91440;
|
|
|
11
11
|
function parseBodyProperties(bodyPr) {
|
|
12
12
|
if (!bodyPr) return {};
|
|
13
13
|
const result = {};
|
|
14
|
+
if (findChildByLocalName(bodyPr, "spAutoFit")) result.autoFit = "shape";
|
|
15
|
+
else if (findChildByLocalName(bodyPr, "normAutofit")) result.autoFit = "normal";
|
|
16
|
+
else if (findChildByLocalName(bodyPr, "noAutofit")) result.autoFit = "none";
|
|
14
17
|
const lIns = parseNumericAttribute(bodyPr, null, "lIns");
|
|
15
18
|
const rIns = parseNumericAttribute(bodyPr, null, "rIns");
|
|
16
19
|
const tIns = parseNumericAttribute(bodyPr, null, "tIns");
|
|
@@ -121,6 +124,7 @@ function parseTextBox(drawingEl) {
|
|
|
121
124
|
if (fill) textBox.fill = fill;
|
|
122
125
|
if (outline) textBox.outline = outline;
|
|
123
126
|
if (bodyProps.margins) textBox.margins = bodyProps.margins;
|
|
127
|
+
if (bodyProps.autoFit) textBox.autoFit = bodyProps.autoFit;
|
|
124
128
|
if (isAnchor) {
|
|
125
129
|
const position = parseAnchorPosition(container);
|
|
126
130
|
if (position) textBox.position = position;
|
|
@@ -162,6 +166,7 @@ function parseTextBoxFromShape(wsp, size, position, wrap) {
|
|
|
162
166
|
if (fill) textBox.fill = fill;
|
|
163
167
|
if (outline) textBox.outline = outline;
|
|
164
168
|
if (bodyProps.margins) textBox.margins = bodyProps.margins;
|
|
169
|
+
if (bodyProps.autoFit) textBox.autoFit = bodyProps.autoFit;
|
|
165
170
|
if (position) textBox.position = position;
|
|
166
171
|
if (wrap) textBox.wrap = wrap;
|
|
167
172
|
return textBox;
|
|
@@ -1,8 +1,30 @@
|
|
|
1
|
-
import { cloneWithXmlnsDeclarations, elementToXml, findAllDeep, findChild, getAttribute } from "./xmlParser.js";
|
|
1
|
+
import { cloneWithXmlnsDeclarations, elementToXml, findAllDeep, findChild, getAttribute, getChildElements, getLocalName } from "./xmlParser.js";
|
|
2
2
|
import { pixelsToEmu } from "../utils/units.js";
|
|
3
3
|
import { resolveImageData } from "./imageParser.js";
|
|
4
4
|
import { isWatermarkShape } from "./watermarkParser.js";
|
|
5
5
|
//#region src/docx/vmlImageParser.ts
|
|
6
|
+
const MAX_VML_PREVIEW_SHAPES = 256;
|
|
7
|
+
const MAX_VML_PREVIEW_DIMENSION_PX = 2e4;
|
|
8
|
+
const MAX_VML_SVG_CHARACTERS = 1e6;
|
|
9
|
+
const SAFE_VML_COLORS = /* @__PURE__ */ new Set([
|
|
10
|
+
"black",
|
|
11
|
+
"white",
|
|
12
|
+
"red",
|
|
13
|
+
"green",
|
|
14
|
+
"blue",
|
|
15
|
+
"yellow",
|
|
16
|
+
"gray",
|
|
17
|
+
"grey",
|
|
18
|
+
"silver",
|
|
19
|
+
"maroon",
|
|
20
|
+
"purple",
|
|
21
|
+
"fuchsia",
|
|
22
|
+
"lime",
|
|
23
|
+
"olive",
|
|
24
|
+
"navy",
|
|
25
|
+
"teal",
|
|
26
|
+
"aqua"
|
|
27
|
+
]);
|
|
6
28
|
/**
|
|
7
29
|
* Convert a CSS length (pt/in/px/cm/mm/pc, default px) to pixels. Units are
|
|
8
30
|
* matched case-insensitively (`2IN`, `2Pt` are valid VML). The numeric part
|
|
@@ -40,6 +62,116 @@ function parseStyleAttr(style) {
|
|
|
40
62
|
}
|
|
41
63
|
return out;
|
|
42
64
|
}
|
|
65
|
+
const finiteNumber = (raw) => {
|
|
66
|
+
if (!raw || !/^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(raw.trim())) return;
|
|
67
|
+
const value = Number.parseFloat(raw);
|
|
68
|
+
return Number.isFinite(value) ? value : void 0;
|
|
69
|
+
};
|
|
70
|
+
const coordinatePair = (raw) => {
|
|
71
|
+
const [firstRaw, secondRaw, extra] = raw?.split(",").map((part) => part.trim()) ?? [];
|
|
72
|
+
if (extra !== void 0) return;
|
|
73
|
+
const first = finiteNumber(firstRaw);
|
|
74
|
+
const second = finiteNumber(secondRaw);
|
|
75
|
+
return first === void 0 || second === void 0 ? void 0 : {
|
|
76
|
+
first,
|
|
77
|
+
second
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
const safeColor = (raw, fallback) => {
|
|
81
|
+
const normalized = raw?.trim().toLowerCase();
|
|
82
|
+
if (!normalized) return fallback;
|
|
83
|
+
if (SAFE_VML_COLORS.has(normalized)) return normalized;
|
|
84
|
+
if (/^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/u.test(normalized)) return normalized;
|
|
85
|
+
return /^[0-9a-f]{6}$/u.test(normalized) ? `#${normalized}` : fallback;
|
|
86
|
+
};
|
|
87
|
+
const validPreviewDimension = (value) => value !== void 0 && value > 0 && value <= MAX_VML_PREVIEW_DIMENSION_PX;
|
|
88
|
+
const svgDataUrl = (svg) => svg.length <= MAX_VML_SVG_CHARACTERS ? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}` : void 0;
|
|
89
|
+
const vmlShapeSvg = (shape, width, height, x = 0, y = 0) => {
|
|
90
|
+
const fill = safeColor(getAttribute(shape, null, "fillcolor"), "white");
|
|
91
|
+
const stroke = getAttribute(shape, null, "stroked")?.toLowerCase() !== "f" ? safeColor(getAttribute(shape, null, "strokecolor"), "black") : "none";
|
|
92
|
+
const localName = getLocalName(shape.name ?? "");
|
|
93
|
+
if (localName === "oval") return `<ellipse cx="${x + width / 2}" cy="${y + height / 2}" rx="${width / 2}" ry="${height / 2}" fill="${fill}" stroke="${stroke}"/>`;
|
|
94
|
+
return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${localName === "roundrect" ? Math.min(width, height) * .1 : 0}" fill="${fill}" stroke="${stroke}"/>`;
|
|
95
|
+
};
|
|
96
|
+
const previewImage = (pictElement, svg, widthPx, heightPx, style, rootXmlns) => {
|
|
97
|
+
const src = svgDataUrl(svg);
|
|
98
|
+
if (!src) return null;
|
|
99
|
+
const leftPx = cssLengthToPx(style["margin-left"] ?? style["left"]) ?? 0;
|
|
100
|
+
const topPx = cssLengthToPx(style["margin-top"] ?? style["top"]) ?? 0;
|
|
101
|
+
const horizontalRelative = style["mso-position-horizontal-relative"] === "page";
|
|
102
|
+
const verticalRelative = style["mso-position-vertical-relative"] === "page";
|
|
103
|
+
const zIndex = finiteNumber(style["z-index"]);
|
|
104
|
+
return {
|
|
105
|
+
type: "drawing",
|
|
106
|
+
image: {
|
|
107
|
+
type: "image",
|
|
108
|
+
rId: "",
|
|
109
|
+
src,
|
|
110
|
+
mimeType: "image/svg+xml",
|
|
111
|
+
filename: "vml-shape-preview.svg",
|
|
112
|
+
size: {
|
|
113
|
+
width: pixelsToEmu(widthPx),
|
|
114
|
+
height: pixelsToEmu(heightPx)
|
|
115
|
+
},
|
|
116
|
+
wrap: { type: zIndex !== void 0 && zIndex >= 0 ? "inFront" : "behind" },
|
|
117
|
+
position: {
|
|
118
|
+
horizontal: {
|
|
119
|
+
relativeTo: horizontalRelative ? "page" : "character",
|
|
120
|
+
posOffset: pixelsToEmu(leftPx)
|
|
121
|
+
},
|
|
122
|
+
vertical: {
|
|
123
|
+
relativeTo: verticalRelative ? "page" : "paragraph",
|
|
124
|
+
posOffset: pixelsToEmu(topPx)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
rawXml: elementToXml(cloneWithXmlnsDeclarations(pictElement, rootXmlns))
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
const parseStandaloneShapePreview = (pictElement, shape, rootXmlns) => {
|
|
132
|
+
const style = parseStyleAttr(getAttribute(shape, null, "style"));
|
|
133
|
+
const widthPx = cssLengthToPx(style["width"]);
|
|
134
|
+
const heightPx = cssLengthToPx(style["height"]);
|
|
135
|
+
if (!validPreviewDimension(widthPx) || !validPreviewDimension(heightPx)) return null;
|
|
136
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${widthPx} ${heightPx}" width="${widthPx}" height="${heightPx}">${vmlShapeSvg(shape, widthPx, heightPx)}</svg>`;
|
|
137
|
+
return previewImage(pictElement, svg, widthPx, heightPx, style, rootXmlns);
|
|
138
|
+
};
|
|
139
|
+
const parseGroupPreview = (pictElement, group, rootXmlns) => {
|
|
140
|
+
const style = parseStyleAttr(getAttribute(group, null, "style"));
|
|
141
|
+
const widthPx = cssLengthToPx(style["width"]);
|
|
142
|
+
const heightPx = cssLengthToPx(style["height"]);
|
|
143
|
+
if (!validPreviewDimension(widthPx) || !validPreviewDimension(heightPx)) return null;
|
|
144
|
+
const origin = coordinatePair(getAttribute(group, null, "coordorigin")) ?? {
|
|
145
|
+
first: 0,
|
|
146
|
+
second: 0
|
|
147
|
+
};
|
|
148
|
+
const size = coordinatePair(getAttribute(group, null, "coordsize")) ?? {
|
|
149
|
+
first: widthPx,
|
|
150
|
+
second: heightPx
|
|
151
|
+
};
|
|
152
|
+
if (size.first <= 0 || size.second <= 0) return null;
|
|
153
|
+
const content = getChildElements(group).slice(0, MAX_VML_PREVIEW_SHAPES).map((child) => {
|
|
154
|
+
const localName = getLocalName(child.name ?? "");
|
|
155
|
+
if (localName === "line") {
|
|
156
|
+
const from = coordinatePair(getAttribute(child, null, "from"));
|
|
157
|
+
const to = coordinatePair(getAttribute(child, null, "to"));
|
|
158
|
+
if (!from || !to) return "";
|
|
159
|
+
const stroke = safeColor(getAttribute(child, null, "strokecolor"), "black");
|
|
160
|
+
return `<line x1="${from.first}" y1="${from.second}" x2="${to.first}" y2="${to.second}" stroke="${stroke}"/>`;
|
|
161
|
+
}
|
|
162
|
+
if (localName !== "rect" && localName !== "roundrect" && localName !== "oval") return "";
|
|
163
|
+
const childStyle = parseStyleAttr(getAttribute(child, null, "style"));
|
|
164
|
+
const x = finiteNumber(childStyle["left"]);
|
|
165
|
+
const y = finiteNumber(childStyle["top"]);
|
|
166
|
+
const width = finiteNumber(childStyle["width"]);
|
|
167
|
+
const height = finiteNumber(childStyle["height"]);
|
|
168
|
+
if (x === void 0 || y === void 0 || width === void 0 || height === void 0) return "";
|
|
169
|
+
return vmlShapeSvg(child, width, height, x, y);
|
|
170
|
+
}).join("");
|
|
171
|
+
if (!content) return null;
|
|
172
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${origin.first} ${origin.second} ${size.first} ${size.second}" width="${widthPx}" height="${heightPx}">${content}</svg>`;
|
|
173
|
+
return previewImage(pictElement, svg, widthPx, heightPx, style, rootXmlns);
|
|
174
|
+
};
|
|
43
175
|
/**
|
|
44
176
|
* Read the relationship id off a `v:imagedata` element. Word writes `r:id`;
|
|
45
177
|
* some legacy / third-party generators use `r:embed` or the office-namespace
|
|
@@ -94,6 +226,18 @@ function parseVmlImageContent(pictElement, rels, media, rootXmlns = {}) {
|
|
|
94
226
|
rawXml: elementToXml(cloneWithXmlnsDeclarations(pictElement, rootXmlns))
|
|
95
227
|
};
|
|
96
228
|
}
|
|
229
|
+
for (const child of getChildElements(pictElement)) {
|
|
230
|
+
const localName = getLocalName(child.name ?? "");
|
|
231
|
+
if (localName === "group") {
|
|
232
|
+
const preview = parseGroupPreview(pictElement, child, rootXmlns);
|
|
233
|
+
if (preview) return preview;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (localName === "rect" || localName === "roundrect" || localName === "oval") {
|
|
237
|
+
const preview = parseStandaloneShapePreview(pictElement, child, rootXmlns);
|
|
238
|
+
if (preview) return preview;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
97
241
|
return null;
|
|
98
242
|
}
|
|
99
243
|
//#endregion
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,8 @@ import { ResolvedAnchor, isSuggestionStale, resolveSuggestionAnchor } from "./ai
|
|
|
11
11
|
import { PositionalText, buildPositionalText } from "./ai-suggestions/text-positions.js";
|
|
12
12
|
import { CreateEmptyDocumentOptions, createEmptyDocument } from "./utils/createDocument.js";
|
|
13
13
|
import { createDocx } from "./docx/rezip.js";
|
|
14
|
-
import {
|
|
14
|
+
import { FOLIO_DOCX_CAPABILITY_HOSTS, FOLIO_DOCX_CAPABILITY_IDS, FOLIO_DOCX_CAPABILITY_MANIFEST, FOLIO_DOCX_CAPABILITY_MANIFEST_VERSION, FOLIO_DOCX_CAPABILITY_OPERATIONS, FOLIO_DOCX_COMPATIBILITY_HOSTS, FOLIO_DOCX_COMPATIBILITY_PROFILES, FOLIO_DOCX_PROFILES, FOLIO_DOCX_SUPPORT_STATES, FolioDocxCapabilityHost, FolioDocxCapabilityId, FolioDocxCapabilityManifest, FolioDocxCapabilityOperation, FolioDocxCompatibilityHost, FolioDocxCompatibilityProfile, FolioDocxFeatureCapability, FolioDocxProfile, FolioDocxSupportState, InvalidFolioDocxCapabilityIdError, getFolioDocxCapability, isFolioDocxCapabilityId } from "./docx/capabilities.js";
|
|
15
|
+
import { DocxCompatibility, DocxCompatibilityContext, DocxCompatibilityIssue, DocxCompatibilityLocation, DocxCompatibilityPart, InspectDocxCompatibilityOptions, inspectDocxCompatibility } from "./docx/compatibility.js";
|
|
15
16
|
import { setAISuggestionsMeta, setFocusedSuggestionMeta } from "./prosemirror/plugins/aiSuggestionDecorations.js";
|
|
16
17
|
import { scrollFolioPositionIntoView } from "./paged-layout/scrollToPmPosition.js";
|
|
17
18
|
import { getFolioCaretViewportRect, getFolioSelectionViewportRect } from "./paged-layout/selectionViewportRect.js";
|
|
@@ -26,5 +27,7 @@ import { fromMarkdown } from "./markdown/fromMarkdown.js";
|
|
|
26
27
|
import { toMarkdown, toMarkdownResult } from "./markdown/index.js";
|
|
27
28
|
import { EmbeddedFont, EmbeddedFontParts, extractEmbeddedFonts, getEmbeddedFontFaces } from "./fonts/embeddedFonts.js";
|
|
28
29
|
import { getGoogleFontsEnabled, setGoogleFontsEnabled } from "./utils/fontResolver.js";
|
|
30
|
+
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
29
31
|
type Document = document_d_exports.Document;
|
|
30
|
-
|
|
32
|
+
type DocxConformanceClass = document_d_exports.DocxConformanceClass;
|
|
33
|
+
export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCX_CONFORMANCE_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, type EmbeddedFont, type EmbeddedFontParts, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCX_CAPABILITY_HOSTS, FOLIO_DOCX_CAPABILITY_IDS, FOLIO_DOCX_CAPABILITY_MANIFEST, FOLIO_DOCX_CAPABILITY_MANIFEST_VERSION, FOLIO_DOCX_CAPABILITY_OPERATIONS, FOLIO_DOCX_COMPATIBILITY_HOSTS, FOLIO_DOCX_COMPATIBILITY_PROFILES, FOLIO_DOCX_PROFILES, FOLIO_DOCX_SUPPORT_STATES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAISignatureParty, type FolioBlockId, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCapabilityHost, type FolioDocxCapabilityId, type FolioDocxCapabilityManifest, type FolioDocxCapabilityOperation, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type FolioDocxFeatureCapability, type FolioDocxProfile, type FolioDocxSupportState, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidFolioDocumentOperationBatchError, InvalidFolioDocxCapabilityIdError, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocxCapability, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocxCapabilityId, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import { applyFolioAIEditOperations } from "./ai-edits/apply.js";
|
|
|
5
5
|
import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
|
|
6
6
|
import { createEmptyDocument } from "./utils/createDocument.js";
|
|
7
7
|
import { createDocx } from "./docx/rezip.js";
|
|
8
|
+
import { FOLIO_DOCX_CAPABILITY_HOSTS, FOLIO_DOCX_CAPABILITY_IDS, FOLIO_DOCX_CAPABILITY_MANIFEST, FOLIO_DOCX_CAPABILITY_MANIFEST_VERSION, FOLIO_DOCX_CAPABILITY_OPERATIONS, FOLIO_DOCX_COMPATIBILITY_HOSTS, FOLIO_DOCX_COMPATIBILITY_PROFILES, FOLIO_DOCX_PROFILES, FOLIO_DOCX_SUPPORT_STATES, InvalidFolioDocxCapabilityIdError, getFolioDocxCapability, isFolioDocxCapabilityId } from "./docx/capabilities.js";
|
|
9
|
+
import { inspectDocxCompatibility } from "./docx/compatibility.js";
|
|
8
10
|
import { DEFAULT_AI_SUGGESTION_PRESETS } from "./ai-suggestions/types.js";
|
|
9
11
|
import { buildPositionalText } from "./ai-suggestions/text-positions.js";
|
|
10
12
|
import { isSuggestionStale, resolveSuggestionAnchor } from "./ai-suggestions/conflict.js";
|
|
@@ -22,4 +24,5 @@ import { DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, acceptAutocompleteSuggestion, acc
|
|
|
22
24
|
import { fromMarkdown } from "./markdown/fromMarkdown.js";
|
|
23
25
|
import { toMarkdown, toMarkdownResult } from "./markdown/index.js";
|
|
24
26
|
import { extractEmbeddedFonts, getEmbeddedFontFaces } from "./fonts/embeddedFonts.js";
|
|
25
|
-
|
|
27
|
+
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
28
|
+
export { DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCX_CONFORMANCE_CLASSES, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_DOCX_CAPABILITY_HOSTS, FOLIO_DOCX_CAPABILITY_IDS, FOLIO_DOCX_CAPABILITY_MANIFEST, FOLIO_DOCX_CAPABILITY_MANIFEST_VERSION, FOLIO_DOCX_CAPABILITY_OPERATIONS, FOLIO_DOCX_COMPATIBILITY_HOSTS, FOLIO_DOCX_COMPATIBILITY_PROFILES, FOLIO_DOCX_PROFILES, FOLIO_DOCX_SUPPORT_STATES, InvalidFolioDocumentOperationBatchError, InvalidFolioDocxCapabilityIdError, UnsupportedFolioDocumentOperationVersionError, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocxCapability, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocxCompatibility, isFolioBlockId, isFolioDocumentOperationModeSupported, isFolioDocxCapabilityId, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
|
|
@@ -16,6 +16,10 @@ function hasAuthoredVisualContent(block) {
|
|
|
16
16
|
if (attrs.spacingExplicit?.before || attrs.spacingExplicit?.after) return true;
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
|
+
function preservesInheritedSpacing(block) {
|
|
20
|
+
if (!isVisuallyEmptyParagraph(block)) return false;
|
|
21
|
+
return block.attrs?.styleId !== void 0 || block.attrs?.hasDirectParagraphFormatting === true || block.attrs?.hasDirectParagraphMarkFormatting === true;
|
|
22
|
+
}
|
|
19
23
|
function normalizeHeaderFooterMeasureBlocks(blocks, section = "header") {
|
|
20
24
|
return normalizeFlowBlockArray(blocks, { suppressTrailingEmptyAfterTable: section === "header" });
|
|
21
25
|
}
|
|
@@ -33,8 +37,9 @@ function normalizeFlowBlockArray(blocks, normalization) {
|
|
|
33
37
|
const explicit = block.attrs?.spacingExplicit;
|
|
34
38
|
const hasResolvedBefore = block.attrs?.spacing?.before != null;
|
|
35
39
|
const hasResolvedAfter = block.attrs?.spacing?.after != null;
|
|
36
|
-
const
|
|
37
|
-
const
|
|
40
|
+
const preserveInherited = preservesInheritedSpacing(block);
|
|
41
|
+
const beforeIsInherited = hasResolvedBefore && !explicit?.before && !preserveInherited;
|
|
42
|
+
const afterIsInherited = hasResolvedAfter && !explicit?.after && !preserveInherited;
|
|
38
43
|
const stripsSpacing = beforeIsInherited || afterIsInherited;
|
|
39
44
|
const stripsImages = block.runs.some(isAnchoredImageRun);
|
|
40
45
|
if (!stripsSpacing && !stripsImages && !isTrailingEmpty) return block;
|
|
@@ -79,6 +84,10 @@ function isVisuallyEmptyParagraph(block) {
|
|
|
79
84
|
if (block.kind !== "paragraph") return false;
|
|
80
85
|
return block.runs.every((run) => run.kind === "text" && run.text.trim().length === 0);
|
|
81
86
|
}
|
|
87
|
+
function isPaintlessParagraph(block) {
|
|
88
|
+
if (block.kind !== "paragraph") return false;
|
|
89
|
+
return block.runs.every((run) => run.kind === "text" && run.text.trim().length === 0 || run.kind === "tab" || run.kind === "lineBreak");
|
|
90
|
+
}
|
|
82
91
|
function normalizeTableBlock(block) {
|
|
83
92
|
const blockState = { changed: false };
|
|
84
93
|
const rows = block.rows.map((row) => {
|
|
@@ -232,6 +241,10 @@ function calculateHeaderFooterVisualBounds(blocks, measures, flowHeight, metrics
|
|
|
232
241
|
* these bounds separately describe the in-flow portion for API consumers.
|
|
233
242
|
*/
|
|
234
243
|
function calculateHeaderFooterMarginPushBounds(blocks, measures, flowHeight, metrics) {
|
|
244
|
+
if (blocks.length > 0 && blocks.every((block) => isPaintlessParagraph(block) && !hasAuthoredVisualContent(block))) return {
|
|
245
|
+
top: 0,
|
|
246
|
+
bottom: 0
|
|
247
|
+
};
|
|
235
248
|
let top = 0;
|
|
236
249
|
let bottom = 0;
|
|
237
250
|
let cursorY = 0;
|
|
@@ -422,6 +435,7 @@ function serializeParagraphAttrs(attrs) {
|
|
|
422
435
|
"listMarker",
|
|
423
436
|
"listIsBullet",
|
|
424
437
|
"listMarkerHidden",
|
|
438
|
+
"listMarkerAlignment",
|
|
425
439
|
"listMarkerSuffix",
|
|
426
440
|
"tabs"
|
|
427
441
|
];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BlockId, FlowBlock } from "../../layout-engine/types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-bridge/convert/paragraphFrames.d.ts
|
|
4
|
+
type BlockIdFactory = () => BlockId;
|
|
5
|
+
declare function groupParagraphFrames(blocks: FlowBlock[], nextBlockId: BlockIdFactory): FlowBlock[];
|
|
6
|
+
//#endregion
|
|
7
|
+
export { groupParagraphFrames };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { pixelsToEmu } from "../../utils/units.js";
|
|
2
|
+
import { setTextBoxGroupId } from "../../layout-engine/textBoxGroup.js";
|
|
3
|
+
import { getParagraphFrame, markParagraphFrameTextBox } from "../../layout-engine/paragraphFrame.js";
|
|
4
|
+
//#region src/layout-bridge/convert/paragraphFrames.ts
|
|
5
|
+
const FRAME_PROPERTIES = [
|
|
6
|
+
"width",
|
|
7
|
+
"height",
|
|
8
|
+
"hSpace",
|
|
9
|
+
"vSpace",
|
|
10
|
+
"hAnchor",
|
|
11
|
+
"vAnchor",
|
|
12
|
+
"x",
|
|
13
|
+
"y",
|
|
14
|
+
"xAlign",
|
|
15
|
+
"yAlign",
|
|
16
|
+
"wrap"
|
|
17
|
+
];
|
|
18
|
+
function framesEqual(left, right) {
|
|
19
|
+
return FRAME_PROPERTIES.every((property) => left[property] === right[property]);
|
|
20
|
+
}
|
|
21
|
+
function horizontalRelativeTo(anchor) {
|
|
22
|
+
if (anchor === "page" || anchor === "margin") return anchor;
|
|
23
|
+
return "column";
|
|
24
|
+
}
|
|
25
|
+
function verticalRelativeTo(anchor) {
|
|
26
|
+
if (anchor === "page" || anchor === "margin") return anchor;
|
|
27
|
+
return "paragraph";
|
|
28
|
+
}
|
|
29
|
+
function framePosition(frame) {
|
|
30
|
+
const horizontal = frame.x !== void 0 || frame.xAlign !== void 0 || frame.hAnchor !== void 0 ? {
|
|
31
|
+
relativeTo: horizontalRelativeTo(frame.hAnchor),
|
|
32
|
+
...frame.x !== void 0 ? { posOffset: pixelsToEmu(frame.x) } : {},
|
|
33
|
+
...frame.xAlign !== void 0 ? { align: frame.xAlign } : {}
|
|
34
|
+
} : void 0;
|
|
35
|
+
const verticalAlign = frame.yAlign === "inline" ? void 0 : frame.yAlign;
|
|
36
|
+
const vertical = frame.y !== void 0 || verticalAlign !== void 0 || frame.vAnchor !== void 0 ? {
|
|
37
|
+
relativeTo: verticalRelativeTo(frame.vAnchor),
|
|
38
|
+
...frame.y !== void 0 ? { posOffset: pixelsToEmu(frame.y) } : {},
|
|
39
|
+
...verticalAlign !== void 0 ? { align: verticalAlign } : {}
|
|
40
|
+
} : void 0;
|
|
41
|
+
if (horizontal === void 0 && vertical === void 0) return;
|
|
42
|
+
return {
|
|
43
|
+
...horizontal !== void 0 ? { horizontal } : {},
|
|
44
|
+
...vertical !== void 0 ? { vertical } : {}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function frameWrapType(frame) {
|
|
48
|
+
switch (frame.wrap) {
|
|
49
|
+
case "notBeside": return "topAndBottom";
|
|
50
|
+
case "none": return "inFront";
|
|
51
|
+
case "through": return "through";
|
|
52
|
+
case "tight": return "tight";
|
|
53
|
+
case "around":
|
|
54
|
+
case "auto":
|
|
55
|
+
case void 0: return "square";
|
|
56
|
+
default:
|
|
57
|
+
frame.wrap;
|
|
58
|
+
return "square";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function toFrameTextBox(content, frame, nextBlockId) {
|
|
62
|
+
const first = content.at(0);
|
|
63
|
+
const last = content.at(-1);
|
|
64
|
+
const textBox = {
|
|
65
|
+
kind: "textBox",
|
|
66
|
+
id: nextBlockId(),
|
|
67
|
+
width: frame.width ?? 200,
|
|
68
|
+
margins: {
|
|
69
|
+
top: 0,
|
|
70
|
+
right: 0,
|
|
71
|
+
bottom: 0,
|
|
72
|
+
left: 0
|
|
73
|
+
},
|
|
74
|
+
content,
|
|
75
|
+
displayMode: "float",
|
|
76
|
+
wrapType: frameWrapType(frame),
|
|
77
|
+
wrapText: "bothSides",
|
|
78
|
+
distTop: frame.vSpace ?? 0,
|
|
79
|
+
distBottom: frame.vSpace ?? 0,
|
|
80
|
+
distLeft: frame.hSpace ?? 0,
|
|
81
|
+
distRight: frame.hSpace ?? 0,
|
|
82
|
+
...first?.pmStart !== void 0 ? { pmStart: first.pmStart } : {},
|
|
83
|
+
...last?.pmEnd !== void 0 ? { pmEnd: last.pmEnd } : {}
|
|
84
|
+
};
|
|
85
|
+
if (frame.height !== void 0) textBox.height = frame.height;
|
|
86
|
+
const position = framePosition(frame);
|
|
87
|
+
if (position !== void 0) textBox.position = position;
|
|
88
|
+
markParagraphFrameTextBox(textBox);
|
|
89
|
+
return textBox;
|
|
90
|
+
}
|
|
91
|
+
function groupParagraphFrames(blocks, nextBlockId) {
|
|
92
|
+
const grouped = [];
|
|
93
|
+
let index = 0;
|
|
94
|
+
let activeFrameSetId;
|
|
95
|
+
while (index < blocks.length) {
|
|
96
|
+
const block = blocks[index];
|
|
97
|
+
if (block?.kind !== "paragraph") {
|
|
98
|
+
if (block) grouped.push(block);
|
|
99
|
+
activeFrameSetId = void 0;
|
|
100
|
+
index += 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const frame = getParagraphFrame(block);
|
|
104
|
+
if (frame === void 0) {
|
|
105
|
+
grouped.push(block);
|
|
106
|
+
if (block.runs.length > 0 || activeFrameSetId === void 0 || !hasFollowingParagraphFrame(blocks, index + 1)) activeFrameSetId = void 0;
|
|
107
|
+
index += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
activeFrameSetId ??= `paragraph-frame-${block.pmStart ?? block.id}`;
|
|
111
|
+
const content = [];
|
|
112
|
+
while (index < blocks.length) {
|
|
113
|
+
const paragraph = blocks[index];
|
|
114
|
+
if (paragraph?.kind !== "paragraph") break;
|
|
115
|
+
const paragraphFrame = getParagraphFrame(paragraph);
|
|
116
|
+
if (paragraphFrame === void 0 || !framesEqual(frame, paragraphFrame)) break;
|
|
117
|
+
content.push(paragraph);
|
|
118
|
+
index += 1;
|
|
119
|
+
}
|
|
120
|
+
const textBox = toFrameTextBox(content, frame, nextBlockId);
|
|
121
|
+
setTextBoxGroupId(textBox, activeFrameSetId);
|
|
122
|
+
grouped.push(textBox);
|
|
123
|
+
}
|
|
124
|
+
return grouped;
|
|
125
|
+
}
|
|
126
|
+
function hasFollowingParagraphFrame(blocks, startIndex) {
|
|
127
|
+
for (let index = startIndex; index < blocks.length; index++) {
|
|
128
|
+
const block = blocks[index];
|
|
129
|
+
if (block?.kind !== "paragraph") return false;
|
|
130
|
+
if (getParagraphFrame(block) !== void 0) return true;
|
|
131
|
+
if (block.runs.length > 0) return false;
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
export { groupParagraphFrames };
|