@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
|
@@ -3,6 +3,6 @@ import { XmlElement } from "./xmlParser.js";
|
|
|
3
3
|
|
|
4
4
|
//#region src/docx/groupDrawingParser.d.ts
|
|
5
5
|
/** Parse a WordprocessingGroup drawing into a safe SVG-backed image preview. */
|
|
6
|
-
declare const parseGroupDrawing: (drawing: XmlElement) => document_d_exports.Image | null;
|
|
6
|
+
declare const parseGroupDrawing: (drawing: XmlElement, rels?: document_d_exports.RelationshipMap, media?: Map<string, document_d_exports.MediaFile>) => document_d_exports.Image | null;
|
|
7
7
|
//#endregion
|
|
8
8
|
export { parseGroupDrawing };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { findAllDeep, findChildByLocalName, findChildrenByLocalName, getAttribute, getLocalName, getTextContent, parseNumericAttribute } from "./xmlParser.js";
|
|
1
|
+
import { findAllDeep, findChildByLocalName, findChildrenByLocalName, getAttribute, getChildElements, getLocalName, getTextContent, parseNumericAttribute } from "./xmlParser.js";
|
|
2
2
|
import { emuToPixels } from "../utils/units.js";
|
|
3
|
-
import { parseImage } from "./imageParser.js";
|
|
3
|
+
import { parseImage, resolveImageData } from "./imageParser.js";
|
|
4
4
|
//#region src/docx/groupDrawingParser.ts
|
|
5
5
|
const HEX_COLOR = /^[0-9A-Fa-f]{6}$/u;
|
|
6
6
|
const DEFAULT_TEXT_COLOR = "000000";
|
|
@@ -8,6 +8,7 @@ const DEFAULT_FONT_HALF_POINTS = 22;
|
|
|
8
8
|
const DEFAULT_LINE_WIDTH_EMU = 9525;
|
|
9
9
|
const HALF_POINT_TO_EMU = 6350;
|
|
10
10
|
const MAX_GROUP_SHAPES = 256;
|
|
11
|
+
const CROP_SCALE = 1e5;
|
|
11
12
|
const MAX_PATH_COMMANDS = 1e4;
|
|
12
13
|
const MAX_TEXT_CHARACTERS = 2e4;
|
|
13
14
|
const MAX_SVG_CHARACTERS = 1e6;
|
|
@@ -110,18 +111,58 @@ const renderTextBox = (wsp) => {
|
|
|
110
111
|
const lineStep = lineHeight / fontSize * svgFontSize;
|
|
111
112
|
return `<text x="0" y="${svgFontSize}" transform="translate(${x} ${y}) scale(${scale})" font-family="Arial, sans-serif" font-size="${svgFontSize}" fill="#${color}">${lines.map((line, index) => `<tspan x="0" dy="${index === 0 ? 0 : lineStep}">${line}</tspan>`).join("")}</text>`;
|
|
112
113
|
};
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
114
|
+
const renderPicture = (picture, index, rels, media) => {
|
|
115
|
+
const { x, y, width, height } = childTransform(picture);
|
|
116
|
+
if (width <= 0 || height <= 0) return "";
|
|
117
|
+
const blipFill = findChildByLocalName(picture, "blipFill");
|
|
118
|
+
const blip = findChildByLocalName(blipFill, "blip");
|
|
119
|
+
const { src } = resolveImageData(getAttribute(blip, "r", "embed") ?? getAttribute(blip, "r", "link") ?? "", rels, media);
|
|
120
|
+
if (!src) return "";
|
|
121
|
+
const sourceRect = findChildByLocalName(blipFill, "srcRect");
|
|
122
|
+
const left = Math.max(0, numericAttr(sourceRect, "l")) / CROP_SCALE;
|
|
123
|
+
const top = Math.max(0, numericAttr(sourceRect, "t")) / CROP_SCALE;
|
|
124
|
+
const right = Math.max(0, numericAttr(sourceRect, "r")) / CROP_SCALE;
|
|
125
|
+
const bottom = Math.max(0, numericAttr(sourceRect, "b")) / CROP_SCALE;
|
|
126
|
+
const visibleWidth = 1 - left - right;
|
|
127
|
+
const visibleHeight = 1 - top - bottom;
|
|
128
|
+
if (visibleWidth <= 0 || visibleHeight <= 0) return "";
|
|
129
|
+
const image = `<image x="${x - width * left / visibleWidth}" y="${y - height * top / visibleHeight}" width="${width / visibleWidth}" height="${height / visibleHeight}" href="${escapeXml(src)}" preserveAspectRatio="none"/>`;
|
|
130
|
+
if (left === 0 && top === 0 && right === 0 && bottom === 0) return image;
|
|
131
|
+
const clipId = `group-picture-${index}`;
|
|
132
|
+
return `<defs><clipPath id="${clipId}"><rect x="${x}" y="${y}" width="${width}" height="${height}"/></clipPath></defs><g clip-path="url(#${clipId})">${image}</g>`;
|
|
133
|
+
};
|
|
134
|
+
const groupViewBox = (group, width, height) => {
|
|
135
|
+
const transform = findChildByLocalName(findChildByLocalName(group, "grpSpPr"), "xfrm");
|
|
136
|
+
const childOffset = findChildByLocalName(transform, "chOff");
|
|
137
|
+
const childExtent = findChildByLocalName(transform, "chExt");
|
|
138
|
+
const childWidth = numericAttr(childExtent, "cx");
|
|
139
|
+
const childHeight = numericAttr(childExtent, "cy");
|
|
140
|
+
return {
|
|
141
|
+
x: numericAttr(childOffset, "x"),
|
|
142
|
+
y: numericAttr(childOffset, "y"),
|
|
143
|
+
width: childWidth > 0 ? childWidth : width,
|
|
144
|
+
height: childHeight > 0 ? childHeight : height
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
const createSvg = (group, width, height, rels, media) => {
|
|
148
|
+
const content = getChildElements(group).slice(0, MAX_GROUP_SHAPES).map((child, index) => {
|
|
149
|
+
const localName = getLocalName(child.name ?? "");
|
|
150
|
+
if (localName === "pic") return renderPicture(child, index, rels, media);
|
|
151
|
+
if (localName !== "wsp") return "";
|
|
152
|
+
return findChildByLocalName(child, "txbx") ? renderTextBox(child) : renderGeometry(child);
|
|
153
|
+
}).join("");
|
|
154
|
+
if (!content) return null;
|
|
155
|
+
const viewBox = groupViewBox(group, width, height);
|
|
156
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}" width="${emuToPixels(width)}" height="${emuToPixels(height)}">${content}</svg>`;
|
|
116
157
|
};
|
|
117
158
|
/** Parse a WordprocessingGroup drawing into a safe SVG-backed image preview. */
|
|
118
|
-
const parseGroupDrawing = (drawing) => {
|
|
159
|
+
const parseGroupDrawing = (drawing, rels, media) => {
|
|
119
160
|
const group = findChildByLocalName(findAllDeep(drawing, "a", "graphicData").at(0) ?? null, "wgp");
|
|
120
161
|
if (!group) return null;
|
|
121
162
|
const image = parseImage(drawing, void 0, void 0);
|
|
122
163
|
if (!image || image.size.width <= 0 || image.size.height <= 0) return null;
|
|
123
|
-
const svg = createSvg(group, image.size.width, image.size.height);
|
|
124
|
-
if (svg.length > MAX_SVG_CHARACTERS) return null;
|
|
164
|
+
const svg = createSvg(group, image.size.width, image.size.height, rels, media);
|
|
165
|
+
if (!svg || svg.length > MAX_SVG_CHARACTERS) return null;
|
|
125
166
|
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
126
167
|
image.mimeType = "image/svg+xml";
|
|
127
168
|
image.filename = "wordprocessing-group.svg";
|
|
@@ -604,6 +604,7 @@ function computeListRendering(numPr, numbering) {
|
|
|
604
604
|
if (markerFont) rendering.markerFontFamily = markerFont;
|
|
605
605
|
if (level.rPr?.fontSize) rendering.markerFontSize = level.rPr.fontSize / 2;
|
|
606
606
|
if (level.rPr?.allCaps) rendering.markerAllCaps = true;
|
|
607
|
+
if (level.lvlJc) rendering.markerAlignment = level.lvlJc;
|
|
607
608
|
if (level.suffix) rendering.markerSuffix = level.suffix;
|
|
608
609
|
if (instance?.abstractNumId !== void 0) rendering.abstractNumId = instance.abstractNumId;
|
|
609
610
|
if (overrideForLevel?.startOverride !== void 0) rendering.startOverride = overrideForLevel.startOverride;
|
|
@@ -110,10 +110,18 @@ function parseTabStops(tabs) {
|
|
|
110
110
|
function parseFrameProperties(framePr) {
|
|
111
111
|
if (!framePr) return;
|
|
112
112
|
const frame = {};
|
|
113
|
+
const dropCap = getAttribute(framePr, "w", "dropCap");
|
|
114
|
+
if (dropCap === "none" || dropCap === "drop" || dropCap === "margin") frame.dropCap = dropCap;
|
|
115
|
+
const lines = parseNumericAttribute(framePr, "w", "lines");
|
|
116
|
+
if (lines !== void 0) frame.lines = lines;
|
|
113
117
|
const w = parseNumericAttribute(framePr, "w", "w");
|
|
114
118
|
if (w !== void 0) frame.width = w;
|
|
115
119
|
const h = parseNumericAttribute(framePr, "w", "h");
|
|
116
120
|
if (h !== void 0) frame.height = h;
|
|
121
|
+
const hSpace = parseNumericAttribute(framePr, "w", "hSpace");
|
|
122
|
+
if (hSpace !== void 0) frame.hSpace = hSpace;
|
|
123
|
+
const vSpace = parseNumericAttribute(framePr, "w", "vSpace");
|
|
124
|
+
if (vSpace !== void 0) frame.vSpace = vSpace;
|
|
117
125
|
const hAnchor = getAttribute(framePr, "w", "hAnchor");
|
|
118
126
|
if (hAnchor === "text" || hAnchor === "margin" || hAnchor === "page") frame.hAnchor = hAnchor;
|
|
119
127
|
const vAnchor = getAttribute(framePr, "w", "vAnchor");
|
|
@@ -974,6 +982,7 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
|
|
|
974
982
|
if (markerFont) listRendering.markerFontFamily = markerFont;
|
|
975
983
|
if (level.rPr?.fontSize) listRendering.markerFontSize = level.rPr.fontSize / 2;
|
|
976
984
|
if (level.rPr?.allCaps) listRendering.markerAllCaps = true;
|
|
985
|
+
if (level.lvlJc) listRendering.markerAlignment = level.lvlJc;
|
|
977
986
|
if (level.suffix) listRendering.markerSuffix = level.suffix;
|
|
978
987
|
let implicitChildLevelAdvances = 0;
|
|
979
988
|
const foldedMarkerSuffix = [];
|
|
@@ -1,7 +1,77 @@
|
|
|
1
|
-
import { findDeep, getChildElements, getLocalName } from "./xmlParser.js";
|
|
1
|
+
import { findDeep, getAttribute, getChildElements, getLocalName } from "./xmlParser.js";
|
|
2
|
+
import { pixelsToEmu } from "../utils/units.js";
|
|
2
3
|
import { getTextBoxContentElement, isTextBoxDrawing, parseTextBox, parseTextBoxContent } from "./textBoxParser.js";
|
|
3
4
|
import { parseParagraph } from "./paragraphParser.js";
|
|
4
5
|
//#region src/docx/paragraphTextBoxEnrichment.ts
|
|
6
|
+
const VML_HORIZONTAL_RELATIVES = /* @__PURE__ */ new Set([
|
|
7
|
+
"character",
|
|
8
|
+
"column",
|
|
9
|
+
"insideMargin",
|
|
10
|
+
"leftMargin",
|
|
11
|
+
"margin",
|
|
12
|
+
"outsideMargin",
|
|
13
|
+
"page",
|
|
14
|
+
"rightMargin"
|
|
15
|
+
]);
|
|
16
|
+
const VML_VERTICAL_RELATIVES = /* @__PURE__ */ new Set([
|
|
17
|
+
"insideMargin",
|
|
18
|
+
"line",
|
|
19
|
+
"margin",
|
|
20
|
+
"outsideMargin",
|
|
21
|
+
"page",
|
|
22
|
+
"paragraph",
|
|
23
|
+
"topMargin",
|
|
24
|
+
"bottomMargin"
|
|
25
|
+
]);
|
|
26
|
+
const parseVmlStyle = (value) => {
|
|
27
|
+
const declarations = {};
|
|
28
|
+
for (const declaration of value?.split(";") ?? []) {
|
|
29
|
+
const separator = declaration.indexOf(":");
|
|
30
|
+
if (separator < 0) continue;
|
|
31
|
+
const key = declaration.slice(0, separator).trim().toLowerCase();
|
|
32
|
+
if (key) declarations[key] = declaration.slice(separator + 1).trim();
|
|
33
|
+
}
|
|
34
|
+
return declarations;
|
|
35
|
+
};
|
|
36
|
+
const vmlLengthToPixels = (value) => {
|
|
37
|
+
const match = /^(?<amount>-?(?:\d+(?:\.\d+)?|\.\d+))\s*(?<unit>pt|in|px|cm|mm|pc)?$/iu.exec(value?.trim() ?? "");
|
|
38
|
+
const amount = Number.parseFloat(match?.groups?.["amount"] ?? "");
|
|
39
|
+
if (!Number.isFinite(amount)) return;
|
|
40
|
+
switch (match?.groups?.["unit"]?.toLowerCase()) {
|
|
41
|
+
case "pt": return amount / 72 * 96;
|
|
42
|
+
case "in": return amount * 96;
|
|
43
|
+
case "cm": return amount / 2.54 * 96;
|
|
44
|
+
case "mm": return amount / 25.4 * 96;
|
|
45
|
+
case "pc": return amount * 16;
|
|
46
|
+
case "px":
|
|
47
|
+
case void 0: return amount;
|
|
48
|
+
default: return;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const horizontalRelativeTo = (value) => {
|
|
52
|
+
for (const relative of VML_HORIZONTAL_RELATIVES) if (relative.toLowerCase() === value?.toLowerCase()) return relative;
|
|
53
|
+
return "character";
|
|
54
|
+
};
|
|
55
|
+
const verticalRelativeTo = (value) => {
|
|
56
|
+
for (const relative of VML_VERTICAL_RELATIVES) if (relative.toLowerCase() === value?.toLowerCase()) return relative;
|
|
57
|
+
return "paragraph";
|
|
58
|
+
};
|
|
59
|
+
const parseVmlInsets = (textBoxEl) => {
|
|
60
|
+
const [left, top, right, bottom, extra] = getAttribute(textBoxEl, null, "inset")?.split(",").map((value) => vmlLengthToPixels(value)) ?? [];
|
|
61
|
+
if (extra !== void 0 || left === void 0 || top === void 0 || right === void 0 || bottom === void 0) return;
|
|
62
|
+
return {
|
|
63
|
+
left: pixelsToEmu(left),
|
|
64
|
+
top: pixelsToEmu(top),
|
|
65
|
+
right: pixelsToEmu(right),
|
|
66
|
+
bottom: pixelsToEmu(bottom)
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
const parseVmlFill = (shapeEl) => getAttribute(shapeEl, null, "filled")?.toLowerCase() === "f" ? { type: "none" } : void 0;
|
|
70
|
+
const vmlWrapType = (positioned, zIndex) => {
|
|
71
|
+
if (!positioned) return "inline";
|
|
72
|
+
if (Number.isFinite(zIndex) && zIndex < 0) return "behind";
|
|
73
|
+
return "inFront";
|
|
74
|
+
};
|
|
5
75
|
const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering, rels, media) => {
|
|
6
76
|
const xmlChildren = getChildElements(paraXml);
|
|
7
77
|
let parsedIndex = 0;
|
|
@@ -11,7 +81,7 @@ const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering,
|
|
|
11
81
|
if (parsedIndex < paragraph.content.length && paragraph.content[parsedIndex]?.type !== "run") parsedIndex += 1;
|
|
12
82
|
continue;
|
|
13
83
|
}
|
|
14
|
-
const { textBoxDrawings, hasNonTextBoxContent } = scanRunForTextBoxDrawings(xmlChild);
|
|
84
|
+
const { textBoxDrawings, vmlTextBoxes, hasNonTextBoxContent } = scanRunForTextBoxDrawings(xmlChild);
|
|
15
85
|
const parsedContent = paragraph.content[parsedIndex];
|
|
16
86
|
const parsedRun = parsedContent?.type === "run" ? parsedContent : void 0;
|
|
17
87
|
const targetRun = parsedRun ?? (hasNonTextBoxContent ? lastConsumedRun : void 0);
|
|
@@ -33,6 +103,7 @@ const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering,
|
|
|
33
103
|
...textBox.outline !== void 0 ? { outline: textBox.outline } : {},
|
|
34
104
|
textBody: {
|
|
35
105
|
content: textBox.content,
|
|
106
|
+
...textBox.autoFit !== void 0 ? { autoFit: textBox.autoFit } : {},
|
|
36
107
|
...textBox.margins !== void 0 ? { margins: textBox.margins } : {}
|
|
37
108
|
}
|
|
38
109
|
};
|
|
@@ -52,6 +123,24 @@ const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering,
|
|
|
52
123
|
parsedIndex += 1;
|
|
53
124
|
}
|
|
54
125
|
}
|
|
126
|
+
for (const pictEl of vmlTextBoxes) {
|
|
127
|
+
const shape = parseVmlTextBoxShape(pictEl, styles, theme, numbering, rels, media);
|
|
128
|
+
if (!shape) continue;
|
|
129
|
+
const shapeContent = {
|
|
130
|
+
type: "shape",
|
|
131
|
+
shape
|
|
132
|
+
};
|
|
133
|
+
if (targetRun && hasNonTextBoxContent) targetRun.content.push(shapeContent);
|
|
134
|
+
else {
|
|
135
|
+
const newRun = {
|
|
136
|
+
type: "run",
|
|
137
|
+
content: [shapeContent]
|
|
138
|
+
};
|
|
139
|
+
paragraph.content.splice(parsedIndex, 0, newRun);
|
|
140
|
+
lastConsumedRun = newRun;
|
|
141
|
+
parsedIndex += 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
55
144
|
if (hasNonTextBoxContent && parsedRun) {
|
|
56
145
|
lastConsumedRun = parsedRun;
|
|
57
146
|
parsedIndex += 1;
|
|
@@ -60,6 +149,7 @@ const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering,
|
|
|
60
149
|
};
|
|
61
150
|
const scanRunForTextBoxDrawings = (xmlRun) => {
|
|
62
151
|
const textBoxDrawings = [];
|
|
152
|
+
const vmlTextBoxes = [];
|
|
63
153
|
let hasNonTextBoxContent = false;
|
|
64
154
|
const visitDrawing = (drawingEl) => {
|
|
65
155
|
if (isTextBoxDrawing(drawingEl)) {
|
|
@@ -75,6 +165,11 @@ const scanRunForTextBoxDrawings = (xmlRun) => {
|
|
|
75
165
|
visitDrawing(el);
|
|
76
166
|
continue;
|
|
77
167
|
}
|
|
168
|
+
if (name === "pict") {
|
|
169
|
+
if (findDeep(el, "v", "textbox")) vmlTextBoxes.push(el);
|
|
170
|
+
else hasNonTextBoxContent = true;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
78
173
|
if (name === "AlternateContent") {
|
|
79
174
|
const branches = getChildElements(el);
|
|
80
175
|
const choice = branches.find((branch) => getLocalName(branch.name ?? "") === "Choice");
|
|
@@ -97,8 +192,52 @@ const scanRunForTextBoxDrawings = (xmlRun) => {
|
|
|
97
192
|
}
|
|
98
193
|
return {
|
|
99
194
|
textBoxDrawings,
|
|
195
|
+
vmlTextBoxes,
|
|
100
196
|
hasNonTextBoxContent
|
|
101
197
|
};
|
|
102
198
|
};
|
|
199
|
+
const parseVmlTextBoxShape = (pictEl, styles, theme, numbering, rels, media) => {
|
|
200
|
+
const shapeEl = findDeep(pictEl, "v", "shape");
|
|
201
|
+
const textBoxEl = shapeEl ? findDeep(shapeEl, "v", "textbox") : null;
|
|
202
|
+
const contentEl = textBoxEl ? findDeep(textBoxEl, "w", "txbxContent") : null;
|
|
203
|
+
if (!shapeEl || !textBoxEl || !contentEl) return null;
|
|
204
|
+
const style = parseVmlStyle(getAttribute(shapeEl, null, "style"));
|
|
205
|
+
const width = vmlLengthToPixels(style["width"]);
|
|
206
|
+
const height = vmlLengthToPixels(style["height"]);
|
|
207
|
+
if (width === void 0 || height === void 0 || width <= 0 || height <= 0) return null;
|
|
208
|
+
const left = vmlLengthToPixels(style["margin-left"] ?? style["left"]);
|
|
209
|
+
const top = vmlLengthToPixels(style["margin-top"] ?? style["top"]);
|
|
210
|
+
const positioned = style["position"]?.toLowerCase() === "absolute";
|
|
211
|
+
const zIndex = Number.parseInt(style["z-index"] ?? "", 10);
|
|
212
|
+
const margins = parseVmlInsets(textBoxEl);
|
|
213
|
+
const fill = parseVmlFill(shapeEl);
|
|
214
|
+
const shape = {
|
|
215
|
+
type: "shape",
|
|
216
|
+
shapeType: "textBox",
|
|
217
|
+
size: {
|
|
218
|
+
width: pixelsToEmu(width),
|
|
219
|
+
height: pixelsToEmu(height)
|
|
220
|
+
},
|
|
221
|
+
...fill === void 0 ? {} : { fill },
|
|
222
|
+
wrap: { type: vmlWrapType(positioned, zIndex) },
|
|
223
|
+
textBody: {
|
|
224
|
+
content: parseTextBoxContent(contentEl, parseParagraph, null, styles, theme, numbering, rels ?? void 0, media ?? void 0),
|
|
225
|
+
...margins === void 0 ? {} : { margins }
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const id = getAttribute(shapeEl, null, "id");
|
|
229
|
+
if (id) shape.id = id;
|
|
230
|
+
if (positioned) shape.position = {
|
|
231
|
+
horizontal: {
|
|
232
|
+
relativeTo: horizontalRelativeTo(style["mso-position-horizontal-relative"]),
|
|
233
|
+
...left === void 0 ? {} : { posOffset: pixelsToEmu(left) }
|
|
234
|
+
},
|
|
235
|
+
vertical: {
|
|
236
|
+
relativeTo: verticalRelativeTo(style["mso-position-vertical-relative"]),
|
|
237
|
+
...top === void 0 ? {} : { posOffset: pixelsToEmu(top) }
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
return shape;
|
|
241
|
+
};
|
|
103
242
|
//#endregion
|
|
104
243
|
export { enrichParagraphTextBoxes };
|
package/dist/docx/parser.js
CHANGED
|
@@ -11,6 +11,7 @@ import { toArrayBuffer } from "../utils/docxInput.js";
|
|
|
11
11
|
import { loadFontsWithMapping } from "../utils/fontLoader.js";
|
|
12
12
|
import { convertTiffToPngDataUrl, isTiffMimeType } from "../utils/tiffConverter.js";
|
|
13
13
|
import { normalizeCommentReferences } from "./commentReferenceNormalization.js";
|
|
14
|
+
import { detectDocxConformanceClass } from "./conformance.js";
|
|
14
15
|
import { extractAllTemplateVariables, parseDocumentBody } from "./documentParser.js";
|
|
15
16
|
import { normalizeHeaderFooterReferences } from "./headerFooterReferenceNormalization.js";
|
|
16
17
|
import { extractMetafileRaster, isMetafileMimeType } from "./metafileRaster.js";
|
|
@@ -167,6 +168,7 @@ async function parseDocx(input, options = {}) {
|
|
|
167
168
|
onProgress("Assembling document...", 95);
|
|
168
169
|
const document = {
|
|
169
170
|
package: {
|
|
171
|
+
conformanceClass: detectDocxConformanceClass(raw.documentXml),
|
|
170
172
|
document: documentBody,
|
|
171
173
|
settings,
|
|
172
174
|
...styleDefinitions !== void 0 ? { styles: styleDefinitions } : {},
|
package/dist/docx/runParser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cloneWithXmlnsDeclarations, elementToXml, findChild, findChildren, getAttribute, getChildElements, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
|
|
1
|
+
import { cloneWithXmlnsDeclarations, elementToXml, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
|
|
2
2
|
import { EmphasisMarkSchema, FontThemeSchema, HighlightColorSchema, ShadingPatternSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
|
|
3
3
|
import { parseImage } from "./imageParser.js";
|
|
4
4
|
import { parseGroupDrawing } from "./groupDrawingParser.js";
|
|
@@ -443,7 +443,7 @@ function parseInstrText(element) {
|
|
|
443
443
|
* `shapeParser.parseShapeFromDrawing` into a `ShapeContent`.
|
|
444
444
|
*/
|
|
445
445
|
function parseDrawingContent(element, rels, media) {
|
|
446
|
-
const groupImage = parseGroupDrawing(element);
|
|
446
|
+
const groupImage = parseGroupDrawing(element, rels ?? void 0, media ?? void 0);
|
|
447
447
|
if (groupImage) return {
|
|
448
448
|
type: "drawing",
|
|
449
449
|
image: groupImage,
|
|
@@ -544,6 +544,15 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
544
544
|
const alternateChildren = getChildElements(child);
|
|
545
545
|
const choiceEl = alternateChildren.find((el) => getLocalName(el.name) === "Choice");
|
|
546
546
|
const fallbackEl = alternateChildren.find((el) => getLocalName(el.name) === "Fallback");
|
|
547
|
+
const groupedChoiceDrawing = choiceEl ? getChildElements(choiceEl).find((element) => getLocalName(element.name) === "drawing" && findAllDeep(element, "wpg", "wgp").length > 0) : void 0;
|
|
548
|
+
if (groupedChoiceDrawing) {
|
|
549
|
+
const groupedDrawing = parseDrawingContent(groupedChoiceDrawing, rels, media);
|
|
550
|
+
if (groupedDrawing?.type === "drawing" && groupedDrawing.image.src) {
|
|
551
|
+
groupedDrawing.rawXml = elementToXml(child);
|
|
552
|
+
contents.push(groupedDrawing);
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
547
556
|
const fallbackPict = fallbackEl ? getChildElements(fallbackEl).find((el) => getLocalName(el.name) === "pict") : void 0;
|
|
548
557
|
const fallbackVml = fallbackPict ? parseVmlImageContent(fallbackPict, rels, media, rootXmlns) : null;
|
|
549
558
|
if (fallbackVml?.image.src) {
|
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
* unchanged content byte-for-byte. Uses string offset tracking
|
|
7
7
|
* with proper tag depth counting (not regex) to handle nested elements.
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* Whether `char` ends an element's tag name in XML — a whitespace separator
|
|
11
|
+
* (space, tab, CR, or LF, all valid before attributes per XML 1.0 §3.1), the
|
|
12
|
+
* tag close `>`, or a self-close `/`. Manual tag scanners must accept every
|
|
13
|
+
* whitespace form, not just a literal space, so newline-formatted markup
|
|
14
|
+
* (`<w:p\n w14:paraId="…">`) is still recognized as the element rather than
|
|
15
|
+
* mistaken for a longer-named sibling.
|
|
16
|
+
*/
|
|
17
|
+
declare function isXmlNameBoundary(char: string | undefined): boolean;
|
|
9
18
|
/**
|
|
10
19
|
* Find the exact string start and end offsets of a <w:p> element
|
|
11
20
|
* identified by its w14:paraId attribute.
|
|
@@ -100,4 +109,4 @@ declare function collectChangedNumberingDefs(baselineXml: string, currentXml: st
|
|
|
100
109
|
*/
|
|
101
110
|
declare function buildPatchedNumberingXml(originalXml: string, currentXml: string, changed: ChangedNumberingDefs): string | null;
|
|
102
111
|
//#endregion
|
|
103
|
-
export { ChangedNumberingDefs, PatchSafetyOptions, PatchValidationResult, buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, validatePatchSafety };
|
|
112
|
+
export { ChangedNumberingDefs, PatchSafetyOptions, PatchValidationResult, buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };
|
|
@@ -484,4 +484,4 @@ function escapeRegExp(str) {
|
|
|
484
484
|
return str.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
485
485
|
}
|
|
486
486
|
//#endregion
|
|
487
|
-
export { buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, validatePatchSafety };
|
|
487
|
+
export { buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };
|
|
@@ -112,8 +112,12 @@ function serializeNumbering(numPr) {
|
|
|
112
112
|
function serializeFrameProperties(frame) {
|
|
113
113
|
if (!frame) return "";
|
|
114
114
|
const attrs = [];
|
|
115
|
+
if (frame.dropCap) attrs.push(`w:dropCap="${frame.dropCap}"`);
|
|
116
|
+
if (frame.lines !== void 0) attrs.push(`w:lines="${intAttr(frame.lines)}"`);
|
|
115
117
|
if (frame.width !== void 0) attrs.push(`w:w="${intAttr(frame.width)}"`);
|
|
116
118
|
if (frame.height !== void 0) attrs.push(`w:h="${intAttr(frame.height)}"`);
|
|
119
|
+
if (frame.hSpace !== void 0) attrs.push(`w:hSpace="${intAttr(frame.hSpace)}"`);
|
|
120
|
+
if (frame.vSpace !== void 0) attrs.push(`w:vSpace="${intAttr(frame.vSpace)}"`);
|
|
117
121
|
if (frame.hAnchor) attrs.push(`w:hAnchor="${frame.hAnchor}"`);
|
|
118
122
|
if (frame.vAnchor) attrs.push(`w:vAnchor="${frame.vAnchor}"`);
|
|
119
123
|
if (frame.x !== void 0) attrs.push(`w:x="${frame.x}"`);
|
|
@@ -456,13 +456,18 @@ function serializeShapeContent(content) {
|
|
|
456
456
|
if (tb.margins.right != null) bpAttrs.push(`rIns="${intAttr(tb.margins.right)}"`);
|
|
457
457
|
if (tb.margins.bottom != null) bpAttrs.push(`bIns="${intAttr(tb.margins.bottom)}"`);
|
|
458
458
|
}
|
|
459
|
+
let autoFitXml = "";
|
|
460
|
+
if (tb.autoFit === "shape") autoFitXml = "<a:spAutoFit/>";
|
|
461
|
+
else if (tb.autoFit === "normal") autoFitXml = "<a:normAutofit/>";
|
|
462
|
+
else if (tb.autoFit === "none") autoFitXml = "<a:noAutofit/>";
|
|
463
|
+
const bodyPrXml = autoFitXml ? `<wps:bodyPr ${bpAttrs.join(" ")}>${autoFitXml}</wps:bodyPr>` : `<wps:bodyPr ${bpAttrs.join(" ")}/>`;
|
|
459
464
|
if (isTextBox) textBody = [
|
|
460
465
|
"<wps:txbx><w:txbxContent>",
|
|
461
466
|
serializeShapeTextBody(tb.content),
|
|
462
467
|
"</w:txbxContent></wps:txbx>",
|
|
463
|
-
|
|
468
|
+
bodyPrXml
|
|
464
469
|
].join("");
|
|
465
|
-
else textBody =
|
|
470
|
+
else textBody = bodyPrXml;
|
|
466
471
|
}
|
|
467
472
|
const graphic = [
|
|
468
473
|
"<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">",
|
|
@@ -172,6 +172,10 @@ function serializeTablePropertyChange(change) {
|
|
|
172
172
|
function serializeTableRowFormatting(formatting, propertyChanges, structuralChange) {
|
|
173
173
|
const parts = [];
|
|
174
174
|
if (formatting) {
|
|
175
|
+
if (formatting.gridBefore) parts.push(`<w:gridBefore w:val="${intAttr(formatting.gridBefore)}"/>`);
|
|
176
|
+
if (formatting.widthBefore) parts.push(`<w:wBefore w:w="${intAttr(formatting.widthBefore.value)}" w:type="${formatting.widthBefore.type}"/>`);
|
|
177
|
+
if (formatting.gridAfter) parts.push(`<w:gridAfter w:val="${intAttr(formatting.gridAfter)}"/>`);
|
|
178
|
+
if (formatting.widthAfter) parts.push(`<w:wAfter w:w="${intAttr(formatting.widthAfter.value)}" w:type="${formatting.widthAfter.type}"/>`);
|
|
175
179
|
if (formatting.cantSplit) parts.push("<w:cantSplit/>");
|
|
176
180
|
if (formatting.header) parts.push("<w:tblHeader/>");
|
|
177
181
|
if (formatting.height) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/docx/server/boundedArchive.d.ts
|
|
2
|
+
declare const DOCX_MAX_ENTRY_BYTES: number;
|
|
3
|
+
declare const DOCX_MAX_TOTAL_BYTES: number;
|
|
4
|
+
declare const DOCX_MAX_ENTRIES = 4096;
|
|
5
|
+
declare const DocxArchiveError_base: import("better-result").TaggedErrorClass<"DocxArchiveError", {
|
|
6
|
+
message: string;
|
|
7
|
+
reason: "load-failed" | "too-many-entries" | "entry-too-large" | "total-too-large";
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
}>;
|
|
10
|
+
/** Error raised when a DOCX archive cannot be loaded within configured limits. */
|
|
11
|
+
declare class DocxArchiveError extends DocxArchiveError_base {}
|
|
12
|
+
type DocxArchiveOptions = {
|
|
13
|
+
maxEntryBytes?: number;
|
|
14
|
+
maxTotalBytes?: number;
|
|
15
|
+
maxEntries?: number;
|
|
16
|
+
};
|
|
17
|
+
type DocxArchive = {
|
|
18
|
+
entries: readonly string[];
|
|
19
|
+
readEntryString: (path: string) => Promise<string | null>;
|
|
20
|
+
readEntryUint8: (path: string) => Promise<Uint8Array | null>;
|
|
21
|
+
};
|
|
22
|
+
declare const loadDocxArchive: (bytes: ArrayBuffer | Uint8Array, options?: DocxArchiveOptions) => Promise<DocxArchive>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { DOCX_MAX_ENTRIES, DOCX_MAX_ENTRY_BYTES, DOCX_MAX_TOTAL_BYTES, DocxArchive, DocxArchiveError, DocxArchiveOptions, loadDocxArchive };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { TaggedError } from "better-result";
|
|
2
|
+
import JSZip from "jszip";
|
|
3
|
+
//#region src/docx/server/boundedArchive.ts
|
|
4
|
+
const DOCX_MAX_ENTRY_BYTES = 128 * 1024 * 1024;
|
|
5
|
+
const DOCX_MAX_TOTAL_BYTES = 256 * 1024 * 1024;
|
|
6
|
+
const DOCX_MAX_ENTRIES = 4096;
|
|
7
|
+
/** Error raised when a DOCX archive cannot be loaded within configured limits. */
|
|
8
|
+
var DocxArchiveError = class extends TaggedError("DocxArchiveError")() {};
|
|
9
|
+
const collectStream = async ({ stream, maxEntryBytes, remainingBytes, maxTotalBytes, path }) => await new Promise((resolve, reject) => {
|
|
10
|
+
const chunks = [];
|
|
11
|
+
let entryBytes = 0;
|
|
12
|
+
const fail = (reason, message) => {
|
|
13
|
+
const destroy = Reflect.get(stream, "destroy");
|
|
14
|
+
if (typeof destroy === "function") Reflect.apply(destroy, stream, []);
|
|
15
|
+
reject(new DocxArchiveError({
|
|
16
|
+
message,
|
|
17
|
+
reason
|
|
18
|
+
}));
|
|
19
|
+
};
|
|
20
|
+
stream.on("data", (chunk) => {
|
|
21
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
22
|
+
entryBytes += bytes.length;
|
|
23
|
+
if (entryBytes > maxEntryBytes) {
|
|
24
|
+
fail("entry-too-large", `DOCX entry "${path}" exceeded the ${maxEntryBytes}-byte limit`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (entryBytes > remainingBytes) {
|
|
28
|
+
fail("total-too-large", `DOCX archive exceeded the ${maxTotalBytes}-byte cumulative limit while reading "${path}"`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
chunks.push(bytes);
|
|
32
|
+
});
|
|
33
|
+
stream.on("end", () => resolve(Buffer.concat(chunks)));
|
|
34
|
+
stream.on("error", reject);
|
|
35
|
+
});
|
|
36
|
+
const loadDocxArchive = async (bytes, options = {}) => {
|
|
37
|
+
const maxEntryBytes = options.maxEntryBytes ?? 134217728;
|
|
38
|
+
const maxTotalBytes = options.maxTotalBytes ?? 268435456;
|
|
39
|
+
const maxEntries = options.maxEntries ?? 4096;
|
|
40
|
+
let zip;
|
|
41
|
+
try {
|
|
42
|
+
zip = await JSZip.loadAsync(bytes);
|
|
43
|
+
} catch (cause) {
|
|
44
|
+
throw new DocxArchiveError({
|
|
45
|
+
message: "Failed to parse DOCX archive",
|
|
46
|
+
reason: "load-failed",
|
|
47
|
+
cause
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const archiveEntries = Object.values(zip.files);
|
|
51
|
+
if (archiveEntries.length > maxEntries) throw new DocxArchiveError({
|
|
52
|
+
message: `DOCX archive declares ${archiveEntries.length} entries (max ${maxEntries})`,
|
|
53
|
+
reason: "too-many-entries"
|
|
54
|
+
});
|
|
55
|
+
let declaredTotalBytes = 0;
|
|
56
|
+
for (const entry of archiveEntries) {
|
|
57
|
+
const data = "_data" in entry ? entry._data : void 0;
|
|
58
|
+
const declaredBytes = typeof data === "object" && data !== null && "uncompressedSize" in data ? data.uncompressedSize : void 0;
|
|
59
|
+
if (typeof declaredBytes !== "number" || !Number.isFinite(declaredBytes)) {
|
|
60
|
+
declaredTotalBytes = NaN;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
if (declaredBytes > maxEntryBytes) throw new DocxArchiveError({
|
|
64
|
+
message: `DOCX entry "${entry.name}" declares ${declaredBytes} bytes (max ${maxEntryBytes})`,
|
|
65
|
+
reason: "entry-too-large"
|
|
66
|
+
});
|
|
67
|
+
declaredTotalBytes += declaredBytes;
|
|
68
|
+
}
|
|
69
|
+
if (Number.isFinite(declaredTotalBytes) && declaredTotalBytes > maxTotalBytes) throw new DocxArchiveError({
|
|
70
|
+
message: `DOCX archive declares ${declaredTotalBytes} cumulative bytes (max ${maxTotalBytes})`,
|
|
71
|
+
reason: "total-too-large"
|
|
72
|
+
});
|
|
73
|
+
let totalBytesRead = 0;
|
|
74
|
+
let readChain = Promise.resolve();
|
|
75
|
+
const readEntry = async (path) => {
|
|
76
|
+
const work = async () => {
|
|
77
|
+
const entry = zip.file(path);
|
|
78
|
+
if (!entry) return null;
|
|
79
|
+
const buffer = await collectStream({
|
|
80
|
+
stream: entry.nodeStream("nodebuffer"),
|
|
81
|
+
maxEntryBytes,
|
|
82
|
+
remainingBytes: maxTotalBytes - totalBytesRead,
|
|
83
|
+
maxTotalBytes,
|
|
84
|
+
path
|
|
85
|
+
});
|
|
86
|
+
totalBytesRead += buffer.length;
|
|
87
|
+
return buffer;
|
|
88
|
+
};
|
|
89
|
+
const next = readChain.then(work, work);
|
|
90
|
+
readChain = next.then(() => void 0, () => void 0);
|
|
91
|
+
return await next;
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
entries: Object.freeze(archiveEntries.map(({ name }) => name)),
|
|
95
|
+
async readEntryString(path) {
|
|
96
|
+
const buffer = await readEntry(path);
|
|
97
|
+
return buffer === null ? null : buffer.toString("utf-8");
|
|
98
|
+
},
|
|
99
|
+
async readEntryUint8(path) {
|
|
100
|
+
const buffer = await readEntry(path);
|
|
101
|
+
return buffer === null ? null : new Uint8Array(buffer);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
106
|
+
export { DOCX_MAX_ENTRIES, DOCX_MAX_ENTRY_BYTES, DOCX_MAX_TOTAL_BYTES, DocxArchiveError, loadDocxArchive };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/docx/server/extractDocxText.d.ts
|
|
2
|
+
/** Document part containing an extracted paragraph. */
|
|
3
|
+
type DocxParagraphSource = "header" | "body" | "footer";
|
|
4
|
+
/** Paragraph text and lightweight formatting metadata from a DOCX archive. */
|
|
5
|
+
type ExtractedDocxParagraph = {
|
|
6
|
+
index: number;
|
|
7
|
+
text: string;
|
|
8
|
+
source: DocxParagraphSource;
|
|
9
|
+
style?: string;
|
|
10
|
+
bold?: boolean;
|
|
11
|
+
fontSize?: number;
|
|
12
|
+
alignment?: "left" | "center" | "right" | "both";
|
|
13
|
+
};
|
|
14
|
+
/** Accepted-revision paragraph text extracted in deterministic part order. */
|
|
15
|
+
type ExtractedDocxText = {
|
|
16
|
+
paragraphs: ExtractedDocxParagraph[];
|
|
17
|
+
charCount: number;
|
|
18
|
+
view: "accepted";
|
|
19
|
+
};
|
|
20
|
+
/** Extract paragraph text and formatting metadata from a DOCX archive. */
|
|
21
|
+
declare const extractDocxText: (bytes: ArrayBuffer | Uint8Array) => Promise<ExtractedDocxText>;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { DocxParagraphSource, ExtractedDocxParagraph, ExtractedDocxText, extractDocxText };
|