@stll/folio-core 0.47.7 → 0.49.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.
Files changed (213) hide show
  1. package/dist/ai-edits/apply.js +565 -121
  2. package/dist/ai-edits/minimal-replacement.d.ts +64 -0
  3. package/dist/ai-edits/minimal-replacement.js +282 -0
  4. package/dist/controller/fontReadiness.js +3 -3
  5. package/dist/controller/hyphenationReadiness.d.ts +19 -0
  6. package/dist/controller/hyphenationReadiness.js +47 -0
  7. package/dist/controller/layoutPipeline.d.ts +3 -0
  8. package/dist/controller/layoutPipeline.js +70 -18
  9. package/dist/display-list/build/buildDisplayList.js +39 -12
  10. package/dist/display-list/build/pageFurniture.d.ts +7 -1
  11. package/dist/display-list/build/pageFurniture.js +21 -8
  12. package/dist/display-list/build/paragraphPrimitives.js +68 -11
  13. package/dist/display-list/build/tablePrimitives.js +10 -6
  14. package/dist/display-list/build/textBoxPrimitives.js +1 -1
  15. package/dist/display-list/build/unsupported.d.ts +2 -2
  16. package/dist/display-list/build/unsupported.js +0 -0
  17. package/dist/display-list/dom/renderDisplayListToDom.js +0 -1
  18. package/dist/docx/attributeRemainder.js +10 -2
  19. package/dist/docx/blockContentParser.d.ts +2 -0
  20. package/dist/docx/blockContentParser.js +79 -64
  21. package/dist/docx/bulletMarkers.d.ts +18 -2
  22. package/dist/docx/bulletMarkers.js +27 -2
  23. package/dist/docx/commentParser.d.ts +2 -1
  24. package/dist/docx/commentParser.js +57 -45
  25. package/dist/docx/containerChildren.d.ts +21 -8
  26. package/dist/docx/containerChildren.js +39 -9
  27. package/dist/docx/documentParser.d.ts +5 -1
  28. package/dist/docx/documentParser.js +8 -5
  29. package/dist/docx/documentSectionFacts.d.ts +22 -0
  30. package/dist/docx/documentSectionFacts.js +43 -0
  31. package/dist/docx/drawingGroupChildren.d.ts +60 -0
  32. package/dist/docx/drawingGroupChildren.js +176 -0
  33. package/dist/docx/drawingUtils.js +3 -0
  34. package/dist/docx/fieldParser.d.ts +8 -1
  35. package/dist/docx/fieldParser.js +47 -3
  36. package/dist/docx/fontTableParser.js +34 -27
  37. package/dist/docx/footnoteParser.d.ts +3 -2
  38. package/dist/docx/footnoteParser.js +15 -12
  39. package/dist/docx/groupDrawingParser.d.ts +9 -2
  40. package/dist/docx/groupDrawingParser.js +125 -37
  41. package/dist/docx/headerFooterParser.d.ts +7 -2
  42. package/dist/docx/headerFooterParser.js +15 -5
  43. package/dist/docx/hyperlinkParser.d.ts +48 -36
  44. package/dist/docx/hyperlinkParser.js +97 -82
  45. package/dist/docx/numberingParser.js +108 -84
  46. package/dist/docx/packageParts.d.ts +45 -1
  47. package/dist/docx/packageParts.js +93 -2
  48. package/dist/docx/paraIdAttribute.d.ts +3 -2
  49. package/dist/docx/paragraphParser.d.ts +5 -1
  50. package/dist/docx/paragraphParser.js +468 -365
  51. package/dist/docx/paragraphProperties.js +123 -109
  52. package/dist/docx/paragraphPropertySource.d.ts +6 -1
  53. package/dist/docx/paragraphPropertySource.js +9 -1
  54. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  55. package/dist/docx/paragraphTextBoxEnrichment.js +143 -10
  56. package/dist/docx/parser.d.ts +7 -1
  57. package/dist/docx/parser.js +33 -16
  58. package/dist/docx/previewBudget.d.ts +55 -33
  59. package/dist/docx/previewBudget.js +59 -65
  60. package/dist/docx/rezip.js +41 -58
  61. package/dist/docx/runParser.d.ts +4 -1
  62. package/dist/docx/runParser.js +182 -184
  63. package/dist/docx/sdtProperties.js +87 -84
  64. package/dist/docx/sectionParser.js +169 -160
  65. package/dist/docx/selectiveSaveFlags.d.ts +10 -3
  66. package/dist/docx/selectiveSaveFlags.js +1 -1
  67. package/dist/docx/selectiveXmlPatch.d.ts +56 -34
  68. package/dist/docx/selectiveXmlPatch.js +222 -115
  69. package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
  70. package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
  71. package/dist/docx/serializer/paragraphSerializer.js +47 -42
  72. package/dist/docx/serializer/runSerializer.d.ts +5 -1
  73. package/dist/docx/serializer/runSerializer.js +4 -3
  74. package/dist/docx/serializer/settingsSerializer.js +19 -1
  75. package/dist/docx/settingsParser.js +8 -0
  76. package/dist/docx/shapeAlternateContent.d.ts +25 -0
  77. package/dist/docx/shapeAlternateContent.js +52 -0
  78. package/dist/docx/streamingXmlParser.d.ts +6 -2
  79. package/dist/docx/streamingXmlParser.js +12 -7
  80. package/dist/docx/styleParser.js +34 -8
  81. package/dist/docx/tableParser.d.ts +21 -12
  82. package/dist/docx/tableParser.js +518 -441
  83. package/dist/docx/textBoxParser.d.ts +18 -5
  84. package/dist/docx/textBoxParser.js +12 -6
  85. package/dist/docx/verbatimCapture.d.ts +10 -8
  86. package/dist/docx/verbatimCapture.js +45 -2
  87. package/dist/docx/vmlImageParser.d.ts +15 -13
  88. package/dist/docx/vmlImageParser.js +75 -60
  89. package/dist/docx/vmlPreview.d.ts +3 -3
  90. package/dist/docx/vmlPreview.js +3 -4
  91. package/dist/docx/xmlNamespaceContext.d.ts +9 -0
  92. package/dist/docx/xmlNamespaceContext.js +54 -0
  93. package/dist/docx/xmlParser.d.ts +6 -3
  94. package/dist/docx/xmlParser.js +11 -41
  95. package/dist/docx/xmlResourceLimits.d.ts +2 -1
  96. package/dist/docx/xmlResourceLimits.js +4 -1
  97. package/dist/fonts/headlessMeasure.js +0 -0
  98. package/dist/headless-layout.d.ts +11 -2
  99. package/dist/headless-layout.js +153 -107
  100. package/dist/internal/headlessRevisionResolution.js +50 -13
  101. package/dist/internal/paragraphFormattingSerialization.js +3 -3
  102. package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
  103. package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
  104. package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
  105. package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
  106. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
  107. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
  108. package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
  109. package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
  110. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
  111. package/dist/layout-bridge/convert/toFlowBlocks.js +284 -39
  112. package/dist/layout-bridge/engine/hitTest.js +5 -3
  113. package/dist/layout-bridge/engine/selectionRects.js +5 -3
  114. package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
  115. package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
  116. package/dist/layout-engine/index.d.ts +2 -2
  117. package/dist/layout-engine/index.js +102 -14
  118. package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
  119. package/dist/layout-engine/justificationCompatibility.js +9 -5
  120. package/dist/layout-engine/keep-together.d.ts +15 -6
  121. package/dist/layout-engine/keep-together.js +43 -16
  122. package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
  123. package/dist/layout-engine/layoutInstrumentation.js +8 -1
  124. package/dist/layout-engine/measure/advanceComposition.js +50 -7
  125. package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
  126. package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
  127. package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
  128. package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
  129. package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
  130. package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
  131. package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
  132. package/dist/layout-engine/measure/lineBreaks.js +10 -3
  133. package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
  134. package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
  135. package/dist/layout-engine/measure/measureBlocks.js +46 -10
  136. package/dist/layout-engine/measure/measureContainer.js +51 -8
  137. package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
  138. package/dist/layout-engine/measure/measureHelpers.js +34 -8
  139. package/dist/layout-engine/measure/measureParagraph.js +143 -101
  140. package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
  141. package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
  142. package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
  143. package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
  144. package/dist/layout-engine/measure/tabCalculator.js +2 -1
  145. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  146. package/dist/layout-engine/measure/tableCellFloating.js +51 -19
  147. package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
  148. package/dist/layout-engine/measure/tableCellGrid.js +45 -5
  149. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
  150. package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
  151. package/dist/layout-engine/noteAreaFlow.js +83 -0
  152. package/dist/layout-engine/paginator.d.ts +12 -1
  153. package/dist/layout-engine/paginator.js +18 -2
  154. package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
  155. package/dist/layout-engine/paragraphSpacing.js +2 -1
  156. package/dist/layout-engine/tableRowBreak.d.ts +8 -1
  157. package/dist/layout-engine/tableRowBreak.js +25 -2
  158. package/dist/layout-engine/types.d.ts +108 -3
  159. package/dist/layout-engine/types.js +96 -1
  160. package/dist/layout-painter/index.d.ts +3 -0
  161. package/dist/layout-painter/renderPage.js +32 -83
  162. package/dist/layout-painter/renderParagraph.d.ts +6 -1
  163. package/dist/layout-painter/renderParagraph.js +75 -26
  164. package/dist/layout-painter/renderTable.js +26 -13
  165. package/dist/layout-painter/renderTextBox.d.ts +12 -2
  166. package/dist/layout-painter/renderTextBox.js +26 -2
  167. package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
  168. package/dist/prosemirror/alternateContentAttrs.js +35 -0
  169. package/dist/prosemirror/attrs/index.d.ts +9 -9
  170. package/dist/prosemirror/attrs/index.js +133 -11
  171. package/dist/prosemirror/commands/comments.js +51 -41
  172. package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
  173. package/dist/prosemirror/contentControlRevisions.js +84 -0
  174. package/dist/prosemirror/conversion/fromProseDoc.js +54 -23
  175. package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
  176. package/dist/prosemirror/conversion/markInterner.js +90 -0
  177. package/dist/prosemirror/conversion/toProseDoc.js +117 -58
  178. package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
  179. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  180. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
  181. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
  182. package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
  183. package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
  184. package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
  185. package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
  186. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  187. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +5 -1
  188. package/dist/prosemirror/listMarker.d.ts +1 -0
  189. package/dist/prosemirror/listMarker.js +4 -2
  190. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  191. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  192. package/dist/prosemirror/paragraphDirection.d.ts +29 -2
  193. package/dist/prosemirror/paragraphDirection.js +21 -2
  194. package/dist/prosemirror/plugins/suggestionMode.js +37 -8
  195. package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
  196. package/dist/prosemirror/rejoinRunCarriers.js +66 -0
  197. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
  198. package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
  199. package/dist/prosemirror/schema/nodes.d.ts +50 -6
  200. package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
  201. package/dist/prosemirror/textBoxHostParagraph.js +21 -0
  202. package/dist/prosemirror/validation.js +91 -80
  203. package/dist/types/content.d.ts +2 -2
  204. package/dist/utils/createDocument.js +10 -1
  205. package/dist/utils/scriptSegments.d.ts +33 -4
  206. package/dist/utils/scriptSegments.js +39 -6
  207. package/dist/utils/textFormattingMerge.d.ts +17 -3
  208. package/dist/utils/textFormattingMerge.js +18 -12
  209. package/dist/utils/trailingText.d.ts +22 -0
  210. package/dist/utils/trailingText.js +32 -0
  211. package/package.json +2 -2
  212. package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
  213. package/dist/layout-engine/justifiedLineFit.js +0 -6
@@ -1,6 +1,6 @@
1
1
  import { emuToPixels } from "../utils/units.js";
2
+ import { collectGroupTextBoxes, readTransform } from "./drawingGroupChildren.js";
2
3
  import { parseImage, resolveImageData } from "./imageParser.js";
3
- import { PREVIEW_KINDS } from "./previewBudget.js";
4
4
  import { findAllDeep, findChildByLocalName, findChildrenByLocalName, getAttribute, getChildElements, getLocalName, getTextContent, parseNumericAttribute } from "./xmlParser.js";
5
5
  import { escapeXmlAttribute, escapeXmlText } from "@stll/docx-core";
6
6
  //#region src/docx/groupDrawingParser.ts
@@ -10,6 +10,7 @@ const DEFAULT_FONT_HALF_POINTS = 22;
10
10
  const DEFAULT_LINE_WIDTH_EMU = 9525;
11
11
  const HALF_POINT_TO_EMU = 6350;
12
12
  const MAX_GROUP_SHAPES = 256;
13
+ const MAX_GROUP_DEPTH = 16;
13
14
  const CROP_SCALE = 1e5;
14
15
  const MAX_PATH_COMMANDS = 1e4;
15
16
  const MAX_TEXT_CHARACTERS = 2e4;
@@ -80,34 +81,66 @@ const pathData = (path) => {
80
81
  }
81
82
  return commands.join(" ");
82
83
  };
83
- const renderGeometry = (wsp) => {
84
+ /**
85
+ * A child's own `a:xfrm` rotation and flips, applied about its centre in the
86
+ * coordinate space it is drawn in (ECMA-376 §20.1.7.6).
87
+ */
88
+ const withChildTransform = (element, piece) => {
89
+ if (!piece) return piece;
90
+ const transform = readTransform(findChildByLocalName(findChildByLocalName(element, "spPr"), "xfrm"));
91
+ return wrapTransformed(transform, piece);
92
+ };
93
+ const wrapTransformed = (transform, piece) => {
94
+ if (transform.rotation === 0 && !transform.flipH && !transform.flipV) return piece;
95
+ const centerX = transform.x + transform.width / 2;
96
+ const centerY = transform.y + transform.height / 2;
97
+ const operations = [];
98
+ if (transform.rotation !== 0) operations.push(`rotate(${transform.rotation} ${centerX} ${centerY})`);
99
+ if (transform.flipH || transform.flipV) operations.push(`translate(${centerX} ${centerY}) scale(${transform.flipH ? -1 : 1} ${transform.flipV ? -1 : 1}) translate(${-centerX} ${-centerY})`);
100
+ return `<g transform="${operations.join(" ")}">${piece}</g>`;
101
+ };
102
+ /**
103
+ * `unitScale` is how many EMUs one unit of the child coordinate space spans.
104
+ * Line widths are authored in EMUs, so they are divided by it to be drawn in
105
+ * the space the geometry is in.
106
+ */
107
+ const renderGeometry = (wsp, unitScale) => {
84
108
  const { x, y, width, height } = childTransform(wsp);
85
- if (width <= 0 || height <= 0) return "";
86
109
  const spPr = findChildByLocalName(wsp, "spPr");
87
110
  const style = findChildByLocalName(wsp, "style");
88
111
  const styleStroke = colorFrom(findChildByLocalName(style, "lnRef"), "none");
89
112
  const fill = colorFrom(spPr, "none");
90
113
  const line = findChildByLocalName(spPr, "ln");
91
114
  const stroke = colorFrom(line, styleStroke);
92
- const strokeWidth = line ? parseNumericAttribute(line, null, "w") ?? DEFAULT_LINE_WIDTH_EMU : 0;
115
+ const strokeWidth = (line ? parseNumericAttribute(line, null, "w") ?? DEFAULT_LINE_WIDTH_EMU : 0) / unitScale;
116
+ const strokeColor = stroke === "none" ? "none" : `#${stroke}`;
117
+ if (width <= 0 || height <= 0) {
118
+ if (width < 0 || height < 0 || width === 0 && height === 0 || strokeColor === "none") return "";
119
+ return `<line x1="${x}" y1="${y}" x2="${x + width}" y2="${y + height}" stroke="${strokeColor}" stroke-width="${strokeWidth}"/>`;
120
+ }
93
121
  const paths = findAllDeep(findChildByLocalName(spPr, "custGeom"), "a", "path");
94
- if (paths.length === 0) return `<rect x="${x}" y="${y}" width="${width}" height="${height}" fill="${fill === "none" ? "none" : `#${fill}`}" stroke="${stroke === "none" ? "none" : `#${stroke}`}" stroke-width="${strokeWidth}"/>`;
122
+ if (paths.length === 0) return `<rect x="${x}" y="${y}" width="${width}" height="${height}" fill="${fill === "none" ? "none" : `#${fill}`}" stroke="${strokeColor}" stroke-width="${strokeWidth}"/>`;
95
123
  return paths.map((path) => {
96
124
  const viewWidth = numericAttr(path, "w") || width;
97
125
  const viewHeight = numericAttr(path, "h") || height;
98
126
  const d = pathData(path);
99
127
  if (!d) return "";
100
- return `<path d="${d}" transform="translate(${x} ${y}) scale(${width / viewWidth} ${height / viewHeight})" fill="${fill === "none" ? "none" : `#${fill}`}" stroke="${stroke === "none" ? "none" : `#${stroke}`}" stroke-width="${strokeWidth}"/>`;
128
+ return `<path d="${d}" transform="translate(${x} ${y}) scale(${width / viewWidth} ${height / viewHeight})" fill="${fill === "none" ? "none" : `#${fill}`}" stroke="${strokeColor}" stroke-width="${strokeWidth}"/>`;
101
129
  }).join("");
102
130
  };
103
- const renderTextBox = (wsp) => {
131
+ /**
132
+ * An approximate rendering of a text box the layout does not host (an inline
133
+ * group, or one placed by alignment rather than offset). Font sizes are in
134
+ * EMUs and the drawing is in child units, hence `unitScale`.
135
+ */
136
+ const renderTextBox = (wsp, unitScale) => {
104
137
  const { x, y, width, height } = childTransform(wsp);
105
138
  if (width <= 0 || height <= 0) return "";
106
139
  const paragraphs = findAllDeep(wsp, "w", "p");
107
140
  const firstSize = findAllDeep(wsp, "w", "sz").at(0);
108
141
  const halfPoints = numericAttr(firstSize ?? null, "val") || DEFAULT_FONT_HALF_POINTS;
109
142
  const color = colorFrom(findAllDeep(wsp, "w", "color").at(0) ?? null, DEFAULT_TEXT_COLOR);
110
- const fontSize = halfPoints * HALF_POINT_TO_EMU;
143
+ const fontSize = halfPoints * HALF_POINT_TO_EMU / unitScale;
111
144
  const maxCharacters = Math.max(1, Math.floor(width / (fontSize * .38)));
112
145
  const lines = paragraphs.flatMap((paragraph) => wrapLine(getTextContent(paragraph).slice(0, MAX_TEXT_CHARACTERS), maxCharacters)).map(escapeXmlText);
113
146
  if (lines.length === 0) return "";
@@ -138,55 +171,110 @@ const renderPicture = (picture, index, rels, media) => {
138
171
  return `<defs><clipPath id="${clipId}"><rect x="${x}" y="${y}" width="${width}" height="${height}"/></clipPath></defs><g clip-path="url(#${clipId})">${image}</g>`;
139
172
  };
140
173
  const groupViewBox = (group, width, height) => {
141
- const transform = findChildByLocalName(findChildByLocalName(group, "grpSpPr"), "xfrm");
142
- const childOffset = findChildByLocalName(transform, "chOff");
143
- const childExtent = findChildByLocalName(transform, "chExt");
144
- const childWidth = numericAttr(childExtent, "cx");
145
- const childHeight = numericAttr(childExtent, "cy");
174
+ const transform = readTransform(findChildByLocalName(findChildByLocalName(group, "grpSpPr"), "xfrm"));
146
175
  return {
147
- x: numericAttr(childOffset, "x"),
148
- y: numericAttr(childOffset, "y"),
149
- width: childWidth > 0 ? childWidth : width,
150
- height: childHeight > 0 ? childHeight : height
176
+ x: transform.childX,
177
+ y: transform.childY,
178
+ width: transform.childWidth > 0 ? transform.childWidth : width,
179
+ height: transform.childHeight > 0 ? transform.childHeight : height
151
180
  };
152
181
  };
153
- const createSvg = (group, width, height, rels, media) => {
154
- const children = getChildElements(group).slice(0, MAX_GROUP_SHAPES);
155
- let content = "";
156
- for (const [index, child] of children.entries()) {
157
- const localName = getLocalName(child.name ?? "");
158
- let piece;
159
- if (localName === "pic") piece = renderPicture(child, index, rels, media);
160
- else if (localName === "wsp") piece = findChildByLocalName(child, "txbx") ? renderTextBox(child) : renderGeometry(child);
161
- else continue;
162
- content += piece;
163
- if (content.length > MAX_SVG_CHARACTERS) return null;
164
- }
165
- if (!content) return null;
182
+ /**
183
+ * A nested group's child space drawn inside its frame in the parent's space,
184
+ * then turned by the group's own rotation and flips.
185
+ */
186
+ const nestedGroupTransform = (transform) => {
187
+ const scaleX = transform.childWidth > 0 ? transform.width / transform.childWidth : 1;
188
+ const scaleY = transform.childHeight > 0 ? transform.height / transform.childHeight : 1;
189
+ return `translate(${transform.x} ${transform.y}) scale(${scaleX} ${scaleY}) translate(${-transform.childX} ${-transform.childY})`;
190
+ };
191
+ const averageScale = (scale) => (scale.x + scale.y) / 2 || 1;
192
+ const createSvg = (group, width, height, rels, media, hostedTextBoxes) => {
193
+ let total = 0;
194
+ let shapes = 0;
195
+ let serial = 0;
196
+ /** One container's markup, or null once the budget is spent. */
197
+ const render = (container, scale, depth) => {
198
+ let content = "";
199
+ for (const child of getChildElements(container)) {
200
+ const index = serial;
201
+ serial += 1;
202
+ if (shapes >= MAX_GROUP_SHAPES) break;
203
+ const localName = getLocalName(child.name ?? "");
204
+ let piece;
205
+ if (localName === "pic") {
206
+ shapes += 1;
207
+ piece = withChildTransform(child, renderPicture(child, index, rels, media));
208
+ } else if (localName === "wsp") {
209
+ shapes += 1;
210
+ if (hostedTextBoxes.has(child)) continue;
211
+ const unitScale = averageScale(scale);
212
+ piece = withChildTransform(child, findChildByLocalName(child, "txbx") ? `${renderGeometry(child, unitScale)}${renderTextBox(child, unitScale)}` : renderGeometry(child, unitScale));
213
+ } else if (localName === "grpSp" && depth < MAX_GROUP_DEPTH) {
214
+ const transform = readTransform(findChildByLocalName(findChildByLocalName(child, "grpSpPr"), "xfrm"));
215
+ const scaleX = transform.childWidth > 0 ? transform.width / transform.childWidth : 1;
216
+ const scaleY = transform.childHeight > 0 ? transform.height / transform.childHeight : 1;
217
+ const nested = render(child, {
218
+ x: scale.x * scaleX,
219
+ y: scale.y * scaleY
220
+ }, depth + 1);
221
+ if (nested === null) return null;
222
+ piece = nested ? wrapTransformed(transform, `<g transform="${nestedGroupTransform(transform)}">${nested}</g>`) : "";
223
+ total -= nested.length;
224
+ } else continue;
225
+ content += piece;
226
+ total += piece.length;
227
+ if (total > MAX_SVG_CHARACTERS) return null;
228
+ }
229
+ return content;
230
+ };
166
231
  const viewBox = groupViewBox(group, width, height);
232
+ const content = render(group, {
233
+ x: width / viewBox.width,
234
+ y: height / viewBox.height
235
+ }, 0);
236
+ if (content === null || !content && hostedTextBoxes.size === 0) return null;
167
237
  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>`;
168
238
  };
169
239
  const groupElement = (drawing) => {
170
240
  return findChildByLocalName(findAllDeep(drawing, "a", "graphicData").at(0) ?? null, "wgp");
171
241
  };
172
242
  /**
243
+ * Whether the layout can place the group's text boxes itself. A floating group
244
+ * positioned by offsets on both axes puts each child at the group's offset
245
+ * plus the child's frame, which a text box anchored beside the group can state
246
+ * exactly; an inline group or one placed by alignment has no such offset.
247
+ */
248
+ const hostsTextBoxes = (image) => {
249
+ const position = image.position;
250
+ return image.wrap.type !== "inline" && image.anchor?.useSimplePosition !== true && position !== void 0 && position.horizontal.alignment === void 0 && position.horizontal.posOffset !== void 0 && position.vertical.alignment === void 0 && position.vertical.posOffset !== void 0;
251
+ };
252
+ /** The text boxes each group preview leaves to the layout, keyed by the preview. */
253
+ const hostedTextBoxFrames = /* @__PURE__ */ new WeakMap();
254
+ /**
255
+ * The text boxes a group preview does not draw, which the paragraph parser
256
+ * lifts out as text boxes of their own. Undefined for any other image.
257
+ */
258
+ const groupPreviewTextBoxes = (image) => hostedTextBoxFrames.get(image);
259
+ /**
173
260
  * Whether a `w:drawing` carries a WordprocessingGroup payload. A group this
174
261
  * module declines to rasterize has no editable projection either — the shape
175
262
  * model holds one shape, not a group — so the caller must preserve it raw.
176
263
  */
177
264
  const isGroupDrawing = (drawing) => groupElement(drawing) !== null;
178
265
  /** Parse a WordprocessingGroup drawing into a safe SVG-backed image preview. */
179
- const parseGroupDrawing = (drawing, rels, media) => {
266
+ const parseGroupDrawing = (drawing, previews, rels, media) => {
180
267
  const group = groupElement(drawing);
181
268
  if (!group) return null;
182
269
  const image = parseImage(drawing, void 0, void 0);
183
270
  if (!image || image.size.width <= 0 || image.size.height <= 0) return null;
184
- const svg = createSvg(group, image.size.width, image.size.height, rels, media);
271
+ const hosted = hostsTextBoxes(image) ? collectGroupTextBoxes(group, image.size.width, image.size.height) : [];
272
+ const svg = createSvg(group, image.size.width, image.size.height, rels, media, new Set(hosted.map(({ wsp }) => wsp)));
185
273
  if (!svg || svg.length > MAX_SVG_CHARACTERS) return null;
186
- image.src = `${PREVIEW_KINDS.wpGroup.srcPrefix}${encodeURIComponent(svg)}`;
187
- image.mimeType = PREVIEW_KINDS.wpGroup.mimeType;
188
- image.filename = PREVIEW_KINDS.wpGroup.filename;
189
- return image;
274
+ const { rId: _rId, src: _src, mimeType: _mimeType, filename: _filename, ...frame } = image;
275
+ const preview = previews.svgImage("wpGroup", svg, frame);
276
+ if (hosted.length > 0) hostedTextBoxFrames.set(preview, hosted);
277
+ return preview;
190
278
  };
191
279
  //#endregion
192
- export { isGroupDrawing, parseGroupDrawing };
280
+ export { groupPreviewTextBoxes, isGroupDrawing, parseGroupDrawing };
@@ -1,5 +1,6 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { NumberingMap } from "./numberingParser.js";
3
+ import { PreviewLedger } from "./previewBudget.js";
3
4
  import { StyleMap } from "./styleParser.js";
4
5
  import { parseFooterReference, parseFooterReferences, parseHeaderReference, parseHeaderReferences } from "./headerFooterRefParser.js";
5
6
  //#region src/docx/headerFooterParser.d.ts
@@ -28,9 +29,11 @@ type HeaderFooterMap = {
28
29
  * @param numbering - Parsed numbering definitions for lists
29
30
  * @param rels - Relationships for resolving hyperlinks/images
30
31
  * @param media - Media files for images
32
+ * @param previews - The ledger of the package this part belongs to; a part
33
+ * read on its own charges its previews to no package
31
34
  * @returns HeaderFooter object
32
35
  */
33
- declare function parseHeader(headerXml: string, hdrFtrType?: document_d_exports.HeaderFooterType, 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): document_d_exports.HeaderFooter;
36
+ declare function parseHeader(headerXml: string, hdrFtrType?: document_d_exports.HeaderFooterType, 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, previews?: PreviewLedger): document_d_exports.HeaderFooter;
34
37
  /**
35
38
  * Parse a footer XML file (word/footer*.xml)
36
39
  *
@@ -41,9 +44,11 @@ declare function parseHeader(headerXml: string, hdrFtrType?: document_d_exports.
41
44
  * @param numbering - Parsed numbering definitions for lists
42
45
  * @param rels - Relationships for resolving hyperlinks/images
43
46
  * @param media - Media files for images
47
+ * @param previews - The ledger of the package this part belongs to; a part
48
+ * read on its own charges its previews to no package
44
49
  * @returns HeaderFooter object
45
50
  */
46
- declare function parseFooter(footerXml: string, hdrFtrType?: document_d_exports.HeaderFooterType, 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): document_d_exports.HeaderFooter;
51
+ declare function parseFooter(footerXml: string, hdrFtrType?: document_d_exports.HeaderFooterType, 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, previews?: PreviewLedger): document_d_exports.HeaderFooter;
47
52
  /**
48
53
  * Generic function to parse either header or footer
49
54
  *
@@ -3,6 +3,7 @@ import { blockPlainText } from "./blockPlainText.js";
3
3
  import { parseFooterReference, parseFooterReferences, parseHeaderReference, parseHeaderReferences } from "./headerFooterRefParser.js";
4
4
  import { assignHeaderFooterVerbatimXml } from "./headerFooterVerbatim.js";
5
5
  import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
6
+ import { standalonePreviewLedger } from "./previewBudget.js";
6
7
  import { parseWatermark } from "./watermarkParser.js";
7
8
  import { collectXmlnsDeclarations, parseXml } from "./xmlParser.js";
8
9
  //#region src/docx/headerFooterParser.ts
@@ -16,9 +17,11 @@ import { collectXmlnsDeclarations, parseXml } from "./xmlParser.js";
16
17
  * @param numbering - Parsed numbering definitions for lists
17
18
  * @param rels - Relationships for resolving hyperlinks/images
18
19
  * @param media - Media files for images
20
+ * @param previews - The ledger of the package this part belongs to; a part
21
+ * read on its own charges its previews to no package
19
22
  * @returns HeaderFooter object
20
23
  */
21
- function parseHeader(headerXml, hdrFtrType = "default", styles = null, theme = null, numbering = null, rels = null, media = null) {
24
+ function parseHeader(headerXml, hdrFtrType = "default", styles = null, theme = null, numbering = null, rels = null, media = null, previews = standalonePreviewLedger()) {
22
25
  const result = {
23
26
  type: "header",
24
27
  hdrFtrType,
@@ -35,11 +38,15 @@ function parseHeader(headerXml, hdrFtrType = "default", styles = null, theme = n
35
38
  }
36
39
  result.content = parseBlockContent(rootElement, styles, theme, numbering, rels, media, {
37
40
  inHeaderFooter: true,
38
- rootXmlns: collectXmlnsDeclarations(rootElement)
41
+ rootXmlns: collectXmlnsDeclarations(rootElement),
42
+ previews
39
43
  });
40
44
  if (watermarkResult) {
41
45
  const host = result.content.at(watermarkResult.blockIndex);
42
- if (host?.type === "paragraph") result.content[watermarkResult.blockIndex] = cloneParagraphWithPropertySource(host, { content: [] });
46
+ if (host?.type === "paragraph") {
47
+ previews.release(host.content);
48
+ result.content[watermarkResult.blockIndex] = cloneParagraphWithPropertySource(host, { content: [] });
49
+ }
43
50
  }
44
51
  assignHeaderFooterVerbatimXml(result, headerXml);
45
52
  return result;
@@ -54,9 +61,11 @@ function parseHeader(headerXml, hdrFtrType = "default", styles = null, theme = n
54
61
  * @param numbering - Parsed numbering definitions for lists
55
62
  * @param rels - Relationships for resolving hyperlinks/images
56
63
  * @param media - Media files for images
64
+ * @param previews - The ledger of the package this part belongs to; a part
65
+ * read on its own charges its previews to no package
57
66
  * @returns HeaderFooter object
58
67
  */
59
- function parseFooter(footerXml, hdrFtrType = "default", styles = null, theme = null, numbering = null, rels = null, media = null) {
68
+ function parseFooter(footerXml, hdrFtrType = "default", styles = null, theme = null, numbering = null, rels = null, media = null, previews = standalonePreviewLedger()) {
60
69
  const result = {
61
70
  type: "footer",
62
71
  hdrFtrType,
@@ -67,7 +76,8 @@ function parseFooter(footerXml, hdrFtrType = "default", styles = null, theme = n
67
76
  if (!rootElement) return result;
68
77
  result.content = parseBlockContent(rootElement, styles, theme, numbering, rels, media, {
69
78
  inHeaderFooter: true,
70
- rootXmlns: collectXmlnsDeclarations(rootElement)
79
+ rootXmlns: collectXmlnsDeclarations(rootElement),
80
+ previews
71
81
  });
72
82
  assignHeaderFooterVerbatimXml(result, footerXml);
73
83
  return result;
@@ -1,6 +1,8 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { XmlElement } from "./xmlParser.js";
3
+ import { PreviewLedger } from "./previewBudget.js";
3
4
  import { StyleMap } from "./styleParser.js";
5
+ import { ChildReader } from "./containerChildren.js";
4
6
  //#region src/docx/hyperlinkParser.d.ts
5
7
  /**
6
8
  * Parse a hyperlink element (w:hyperlink)
@@ -13,10 +15,19 @@ import { StyleMap } from "./styleParser.js";
13
15
  * @param styles - Style map for resolving run styles
14
16
  * @param theme - Theme for resolving colors/fonts
15
17
  * @param media - Media files map for image data
18
+ * @param previews - The ledger of the package this link belongs to; a link
19
+ * read on its own charges its previews to no package
16
20
  * @returns Parsed Hyperlink object
17
21
  */
18
- declare function parseHyperlink(node: XmlElement, rels: document_d_exports.RelationshipMap | null, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, media?: Map<string, document_d_exports.MediaFile> | null, rootXmlns?: Record<string, string>): document_d_exports.Hyperlink;
19
- /** What {@link hyperlinkChildHandlers} needs to read one child of a link. */
22
+ declare function parseHyperlink(node: XmlElement, rels: document_d_exports.RelationshipMap | null, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, media?: Map<string, document_d_exports.MediaFile> | null, rootXmlns?: Record<string, string>, previews?: PreviewLedger): document_d_exports.Hyperlink;
23
+ /**
24
+ * A `w:hyperlink`'s own attributes as a link with no children yet.
25
+ *
26
+ * The paragraph parser's revision-segmenting walk reads the children itself,
27
+ * so it takes the shell rather than a link whose children it would discard.
28
+ */
29
+ declare function parseHyperlinkShell(node: XmlElement, rels: document_d_exports.RelationshipMap | null): document_d_exports.Hyperlink;
30
+ /** What {@link HYPERLINK_CHILD_HANDLERS} needs to read one child of a link. */
20
31
  type HyperlinkChildContext = {
21
32
  /** Where a parsed or captured child lands, in source order. */
22
33
  push: (child: document_d_exports.Hyperlink["children"][number]) => void;
@@ -24,6 +35,7 @@ type HyperlinkChildContext = {
24
35
  theme: document_d_exports.Theme | null;
25
36
  rels: document_d_exports.RelationshipMap | null;
26
37
  media: Map<string, document_d_exports.MediaFile> | null;
38
+ previews: PreviewLedger;
27
39
  inScopeXmlns: Record<string, string>;
28
40
  };
29
41
  /**
@@ -43,39 +55,39 @@ type HyperlinkChildContext = {
43
55
  * recognising is recognised by all of them rather than by whichever list
44
56
  * somebody remembered to update.
45
57
  */
46
- declare const hyperlinkChildHandlers: (context: HyperlinkChildContext) => {
47
- r: (child: XmlElement) => void;
48
- bookmarkStart: (child: XmlElement) => void;
49
- bookmarkEnd: (child: XmlElement) => void;
50
- bdo: (child: XmlElement) => void;
51
- dir: (child: XmlElement) => void;
52
- customXml: (child: XmlElement) => void;
53
- smartTag: (child: XmlElement) => void;
54
- del: "capture";
55
- ins: "capture";
56
- moveFrom: "capture";
57
- moveTo: "capture";
58
- commentRangeEnd: "capture";
59
- commentRangeStart: "capture";
60
- customXmlDelRangeEnd: "capture";
61
- customXmlDelRangeStart: "capture";
62
- customXmlInsRangeEnd: "capture";
63
- customXmlInsRangeStart: "capture";
64
- customXmlMoveFromRangeEnd: "capture";
65
- customXmlMoveFromRangeStart: "capture";
66
- customXmlMoveToRangeEnd: "capture";
67
- customXmlMoveToRangeStart: "capture";
68
- fldSimple: (child: XmlElement) => void;
69
- hyperlink: (child: XmlElement) => void;
70
- moveFromRangeEnd: "capture";
71
- moveFromRangeStart: "capture";
72
- moveToRangeEnd: "capture";
73
- moveToRangeStart: "capture";
74
- permEnd: "capture";
75
- permStart: "capture";
76
- proofErr: "capture";
77
- sdt: "capture";
78
- subDoc: "capture";
58
+ declare const HYPERLINK_CHILD_HANDLERS: {
59
+ readonly r: (child: XmlElement, { push, styles, theme, rels, media, previews, inScopeXmlns }: HyperlinkChildContext) => void;
60
+ readonly bookmarkStart: (child: XmlElement, { push }: HyperlinkChildContext) => void;
61
+ readonly bookmarkEnd: (child: XmlElement, { push }: HyperlinkChildContext) => void;
62
+ readonly bdo: ChildReader<HyperlinkChildContext>;
63
+ readonly dir: ChildReader<HyperlinkChildContext>;
64
+ readonly customXml: ChildReader<HyperlinkChildContext>;
65
+ readonly smartTag: ChildReader<HyperlinkChildContext>;
66
+ readonly del: "capture";
67
+ readonly ins: "capture";
68
+ readonly moveFrom: "capture";
69
+ readonly moveTo: "capture";
70
+ readonly commentRangeEnd: "capture";
71
+ readonly commentRangeStart: "capture";
72
+ readonly customXmlDelRangeEnd: "capture";
73
+ readonly customXmlDelRangeStart: "capture";
74
+ readonly customXmlInsRangeEnd: "capture";
75
+ readonly customXmlInsRangeStart: "capture";
76
+ readonly customXmlMoveFromRangeEnd: "capture";
77
+ readonly customXmlMoveFromRangeStart: "capture";
78
+ readonly customXmlMoveToRangeEnd: "capture";
79
+ readonly customXmlMoveToRangeStart: "capture";
80
+ readonly fldSimple: (child: XmlElement, { push }: HyperlinkChildContext) => void;
81
+ readonly hyperlink: (child: XmlElement, { push }: HyperlinkChildContext) => void;
82
+ readonly moveFromRangeEnd: "capture";
83
+ readonly moveFromRangeStart: "capture";
84
+ readonly moveToRangeEnd: "capture";
85
+ readonly moveToRangeStart: "capture";
86
+ readonly permEnd: "capture";
87
+ readonly permStart: "capture";
88
+ readonly proofErr: "capture";
89
+ readonly sdt: "capture";
90
+ readonly subDoc: "capture";
79
91
  };
80
92
  /**
81
93
  * Get the display text of a hyperlink
@@ -169,4 +181,4 @@ declare function createExternalHyperlink(url: string, children: document_d_expor
169
181
  target?: string;
170
182
  }): document_d_exports.Hyperlink;
171
183
  //#endregion
172
- export { HyperlinkChildContext, createExternalHyperlink, createInternalHyperlink, getHyperlinkRuns, getHyperlinkText, getHyperlinkUrl, hasContent, hyperlinkChildHandlers, isExternalLink, isInternalLink, parseHyperlink, resolveHyperlinkUrl };
184
+ export { HYPERLINK_CHILD_HANDLERS, HyperlinkChildContext, createExternalHyperlink, createInternalHyperlink, getHyperlinkRuns, getHyperlinkText, getHyperlinkUrl, hasContent, isExternalLink, isInternalLink, parseHyperlink, parseHyperlinkShell, resolveHyperlinkUrl };