@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
@@ -0,0 +1,176 @@
1
+ import { canonicalJson } from "../utils/canonicalJson.js";
2
+ import { captureVerbatimXml } from "./verbatimCapture.js";
3
+ import { findChildByLocalName, findDeep, getAttribute, getChildElements, getLocalName, parseNumericAttribute, parseXml } from "./xmlParser.js";
4
+ //#region src/docx/drawingGroupChildren.ts
5
+ /**
6
+ * The text boxes of a DrawingML group (`wpg:wgp`), read as frames folio can lay
7
+ * out, and written back into the group's authored XML.
8
+ *
9
+ * A group states a child coordinate space (`a:chOff` / `a:chExt`) that its
10
+ * own frame (`a:off` / `a:ext`) maps onto, and every child's `a:xfrm` is in
11
+ * that space; a nested `wpg:grpSp` repeats the mapping one level down
12
+ * (ECMA-376 §20.1.7.5). Composing the mappings gives each `wps:wsp` a frame
13
+ * in EMUs relative to the group's own top-left corner. Only the frame is
14
+ * mapped: the text inside keeps its authored size and the body insets stay in
15
+ * EMUs.
16
+ */
17
+ /** `a:xfrm@rot` is in 60,000ths of a degree. */
18
+ const ROTATION_UNITS_PER_DEGREE = 6e4;
19
+ /** Deeper nesting than this is not a drawing anyone authored. */
20
+ const MAX_GROUP_DEPTH = 16;
21
+ const MAX_GROUP_TEXT_BOXES = 256;
22
+ const numberAttribute = (element, name) => parseNumericAttribute(element, null, name) ?? 0;
23
+ const onOffAttribute = (element, name) => {
24
+ const value = getAttribute(element, null, name);
25
+ return value === "1" || value === "true" || value === "on";
26
+ };
27
+ /** A `CT_GroupTransform2D` or `CT_Transform2D`; absent children read as zero. */
28
+ const readTransform = (xfrm) => {
29
+ const offset = findChildByLocalName(xfrm, "off");
30
+ const extent = findChildByLocalName(xfrm, "ext");
31
+ const childOffset = findChildByLocalName(xfrm, "chOff");
32
+ const childExtent = findChildByLocalName(xfrm, "chExt");
33
+ return {
34
+ x: numberAttribute(offset, "x"),
35
+ y: numberAttribute(offset, "y"),
36
+ width: numberAttribute(extent, "cx"),
37
+ height: numberAttribute(extent, "cy"),
38
+ childX: numberAttribute(childOffset, "x"),
39
+ childY: numberAttribute(childOffset, "y"),
40
+ childWidth: numberAttribute(childExtent, "cx"),
41
+ childHeight: numberAttribute(childExtent, "cy"),
42
+ rotation: numberAttribute(xfrm, "rot") / ROTATION_UNITS_PER_DEGREE,
43
+ flipH: onOffAttribute(xfrm, "flipH"),
44
+ flipV: onOffAttribute(xfrm, "flipV")
45
+ };
46
+ };
47
+ const groupTransform = (group) => readTransform(findChildByLocalName(findChildByLocalName(group, "grpSpPr"), "xfrm"));
48
+ const shapeTransform = (shape) => readTransform(findChildByLocalName(findChildByLocalName(shape, "spPr"), "xfrm"));
49
+ /**
50
+ * The top-level mapping: the group's child space stretched over the drawing's
51
+ * extent. A missing or empty child extent means the child space is the
52
+ * drawing's own.
53
+ */
54
+ const rootMap = (group, width, height) => {
55
+ const transform = groupTransform(group);
56
+ const scaleX = transform.childWidth > 0 ? width / transform.childWidth : 1;
57
+ const scaleY = transform.childHeight > 0 ? height / transform.childHeight : 1;
58
+ return {
59
+ scaleX,
60
+ scaleY,
61
+ offsetX: -transform.childX * scaleX,
62
+ offsetY: -transform.childY * scaleY
63
+ };
64
+ };
65
+ /** A nested group's child space, mapped through its frame in the parent's space. */
66
+ const nestedMap = (parent, transform) => {
67
+ const scaleX = transform.childWidth > 0 ? transform.width / transform.childWidth : 1;
68
+ const scaleY = transform.childHeight > 0 ? transform.height / transform.childHeight : 1;
69
+ return {
70
+ scaleX: parent.scaleX * scaleX,
71
+ scaleY: parent.scaleY * scaleY,
72
+ offsetX: parent.scaleX * (transform.x - transform.childX * scaleX) + parent.offsetX,
73
+ offsetY: parent.scaleY * (transform.y - transform.childY * scaleY) + parent.offsetY
74
+ };
75
+ };
76
+ const hasTextBox = (wsp) => findChildByLocalName(findChildByLocalName(wsp, "txbx"), "txbxContent") !== null;
77
+ /**
78
+ * Every `wps:wsp` with a text box in the group, nested groups included, with
79
+ * its frame in EMUs from the group's top-left corner.
80
+ *
81
+ * A nested group that is rotated or flipped turns its children's frames into
82
+ * something other than a rectangle in the drawing's axes, so its text boxes
83
+ * are left out and stay with the preview.
84
+ */
85
+ const collectGroupTextBoxes = (group, width, height) => {
86
+ const frames = [];
87
+ const visit = (container, map, path, depth) => {
88
+ for (const [index, child] of getChildElements(container).entries()) {
89
+ if (frames.length >= MAX_GROUP_TEXT_BOXES) return;
90
+ const localName = getLocalName(child.name ?? "");
91
+ if (localName === "grpSp" && depth < MAX_GROUP_DEPTH) {
92
+ const transform = groupTransform(child);
93
+ if (transform.rotation === 0 && !transform.flipH && !transform.flipV) visit(child, nestedMap(map, transform), [...path, index], depth + 1);
94
+ continue;
95
+ }
96
+ if (localName !== "wsp" || !hasTextBox(child)) continue;
97
+ const transform = shapeTransform(child);
98
+ const frameWidth = transform.width * map.scaleX;
99
+ const frameHeight = transform.height * map.scaleY;
100
+ if (!(frameWidth > 0 && frameHeight > 0)) continue;
101
+ frames.push({
102
+ path: [...path, index],
103
+ wsp: child,
104
+ x: transform.x * map.scaleX + map.offsetX,
105
+ y: transform.y * map.scaleY + map.offsetY,
106
+ width: frameWidth,
107
+ height: frameHeight,
108
+ rotation: transform.rotation,
109
+ flipH: transform.flipH,
110
+ flipV: transform.flipV
111
+ });
112
+ }
113
+ };
114
+ visit(group, rootMap(group, width, height), [], 0);
115
+ return frames;
116
+ };
117
+ /** Two FNV-1a passes with different offsets: a short, stable identity. */
118
+ const fingerprint = (text) => {
119
+ let first = 2166136261;
120
+ let second = 16777619;
121
+ for (let index = 0; index < text.length; index += 1) {
122
+ const code = text.charCodeAt(index);
123
+ first = Math.imul(first ^ code, 16777619);
124
+ second = Math.imul(second ^ code, 96280213);
125
+ }
126
+ return `${(first >>> 0).toString(16).padStart(8, "0")}${(second >>> 0).toString(16).padStart(8, "0")}`;
127
+ };
128
+ /** Identity of a group drawing's authored XML. */
129
+ const groupXmlFingerprint = (rawXml) => fingerprint(rawXml);
130
+ /** Identity of a text box's content, compared on save to tell an edit. */
131
+ const groupTextContentFingerprint = (content) => fingerprint(canonicalJson(content));
132
+ /** The group a captured drawing carries; a Fallback branch holds none. */
133
+ const findGroup = (root) => findDeep(root, "wpg", "wgp") ?? void 0;
134
+ const childAtPath = (group, path) => {
135
+ let current = group;
136
+ for (const index of path) current = current ? getChildElements(current)[index] : void 0;
137
+ return current;
138
+ };
139
+ /**
140
+ * The text box content element a path names, or undefined when the path does
141
+ * not lead to a `wps:wsp` holding one.
142
+ */
143
+ const textBoxContentAt = (group, path) => {
144
+ const wsp = childAtPath(group, path);
145
+ if (!wsp || getLocalName(wsp.name ?? "") !== "wsp") return;
146
+ return findChildByLocalName(findChildByLocalName(wsp, "txbx"), "txbxContent") ?? void 0;
147
+ };
148
+ /**
149
+ * The captured group XML with each edited text box's content replaced, or
150
+ * undefined when an edit names no text box in it.
151
+ *
152
+ * The replacement is written as a placeholder element and substituted after
153
+ * the tree is serialized, so the new content is never reparsed and the rest
154
+ * of the capture is written exactly as it was read.
155
+ */
156
+ const replaceGroupTextBoxContent = (rawXml, edits) => {
157
+ const root = parseXml(rawXml);
158
+ const group = findGroup(root);
159
+ if (!group) return;
160
+ const replacements = /* @__PURE__ */ new Map();
161
+ for (const [index, edit] of edits.entries()) {
162
+ const content = textBoxContentAt(group, edit.path);
163
+ if (!content) return;
164
+ const placeholder = `folio-group-text-${index}-${fingerprint(rawXml)}`;
165
+ content.elements = [{
166
+ type: "element",
167
+ name: placeholder
168
+ }];
169
+ replacements.set(`<${placeholder}/>`, edit.contentXml || "<w:p/>");
170
+ }
171
+ let xml = (root.elements ?? []).map((element) => captureVerbatimXml(element)).join("");
172
+ for (const [placeholder, contentXml] of replacements) xml = xml.replace(placeholder, () => contentXml);
173
+ return xml;
174
+ };
175
+ //#endregion
176
+ export { collectGroupTextBoxes, groupTextContentFingerprint, groupXmlFingerprint, readTransform, replaceGroupTextBoxContent };
@@ -107,6 +107,7 @@ function parseFill(spPr) {
107
107
  function parseGradientFill(gradientFill) {
108
108
  let type = "linear";
109
109
  let angle;
110
+ let scaled;
110
111
  const linear = findChildByLocalName(gradientFill, "lin");
111
112
  if (linear) {
112
113
  const authoredAngle = getAttribute(linear, null, "ang");
@@ -114,6 +115,7 @@ function parseGradientFill(gradientFill) {
114
115
  const parsedAngle = Number.parseInt(authoredAngle, 10);
115
116
  angle = Number.isNaN(parsedAngle) ? void 0 : parsedAngle / 6e4;
116
117
  }
118
+ scaled = parseOnOffValue(getAttribute(linear, null, "scaled"), void 0, "a:lin@scaled");
117
119
  }
118
120
  const path = findChildByLocalName(gradientFill, "path");
119
121
  if (path) {
@@ -139,6 +141,7 @@ function parseGradientFill(gradientFill) {
139
141
  gradient: {
140
142
  type,
141
143
  ...angle !== void 0 ? { angle } : {},
144
+ ...scaled !== void 0 ? { scaled } : {},
142
145
  stops
143
146
  }
144
147
  };
@@ -64,6 +64,13 @@ declare function parseFieldInstruction(instruction: string): ParsedFieldInstruct
64
64
  * @returns Format string or undefined
65
65
  */
66
66
  declare function getFormatSwitch(instruction: ParsedFieldInstruction): string | undefined;
67
+ /**
68
+ * Get the numeric picture switch value (\#), e.g. `0#` in `PAGE \# 0#`.
69
+ *
70
+ * @param instruction - Parsed instruction
71
+ * @returns Picture string or undefined
72
+ */
73
+ declare function getNumericPictureSwitch(instruction: ParsedFieldInstruction): string | undefined;
67
74
  /**
68
75
  * Check if field has MERGEFORMAT switch (preserve formatting)
69
76
  *
@@ -237,4 +244,4 @@ declare function getMergeFields(fields: document_d_exports.Field[]): document_d_
237
244
  */
238
245
  declare function getMergeFieldNames(fields: document_d_exports.Field[]): string[];
239
246
  //#endregion
240
- export { ComplexFieldContext, ComplexFieldState, FieldSwitch, KNOWN_FIELD_TYPES, ParsedFieldInstruction, collectFields, computePageNumber, createComplexFieldContext, finalizeComplexField, formatDate, getFieldDisplayValue, getFieldsByType, getFormatSwitch, getMergeFieldNames, getMergeFields, getPageNumberFields, hasMergeFormat, isDateTimeField, isDocPropertyField, isHyperlinkField, isKnownFieldType, isMergeField, isPageNumberField, isReferenceField, isTocField, isTotalPagesField, parseFieldInstruction, parseFieldType, parseSimpleField, resetComplexFieldContext };
247
+ export { ComplexFieldContext, ComplexFieldState, FieldSwitch, KNOWN_FIELD_TYPES, ParsedFieldInstruction, collectFields, computePageNumber, createComplexFieldContext, finalizeComplexField, formatDate, getFieldDisplayValue, getFieldsByType, getFormatSwitch, getMergeFieldNames, getMergeFields, getNumericPictureSwitch, getPageNumberFields, hasMergeFormat, isDateTimeField, isDocPropertyField, isHyperlinkField, isKnownFieldType, isMergeField, isPageNumberField, isReferenceField, isTocField, isTotalPagesField, parseFieldInstruction, parseFieldType, parseSimpleField, resetComplexFieldContext };
@@ -141,6 +141,15 @@ function getFormatSwitch(instruction) {
141
141
  return instruction.switches.find((s) => s.switch === "*" || s.switch === "@")?.value;
142
142
  }
143
143
  /**
144
+ * Get the numeric picture switch value (\#), e.g. `0#` in `PAGE \# 0#`.
145
+ *
146
+ * @param instruction - Parsed instruction
147
+ * @returns Picture string or undefined
148
+ */
149
+ function getNumericPictureSwitch(instruction) {
150
+ return instruction.switches.find((s) => s.switch === "#")?.value;
151
+ }
152
+ /**
144
153
  * Check if field has MERGEFORMAT switch (preserve formatting)
145
154
  *
146
155
  * @param instruction - Parsed instruction
@@ -345,16 +354,51 @@ function isTocField(field) {
345
354
  * @returns Formatted page number string
346
355
  */
347
356
  function computePageNumber(pageNumber, instruction, sectionFormat) {
357
+ const picture = instruction ? getNumericPictureSwitch(instruction) : void 0;
358
+ if (picture !== void 0) return applyNumericPicture(pageNumber, picture);
348
359
  const format = instruction ? getFormatSwitch(instruction) : void 0;
349
360
  if (!format || format.toUpperCase() === "MERGEFORMAT") return formatSectionPageNumber(pageNumber, sectionFormat);
361
+ const isLowercaseSwitch = format === format.toLowerCase();
350
362
  switch (format.toUpperCase()) {
351
- case "ROMAN": return toRoman(pageNumber);
352
- case "ALPHABETIC": return toLetter(pageNumber);
363
+ case "ROMAN": {
364
+ const roman = toRoman(pageNumber);
365
+ return isLowercaseSwitch ? roman.toLowerCase() : roman;
366
+ }
367
+ case "ALPHABETIC": {
368
+ const letters = toLetter(pageNumber);
369
+ return isLowercaseSwitch ? letters.toLowerCase() : letters;
370
+ }
353
371
  default: return String(pageNumber);
354
372
  }
355
373
  }
356
374
  const formatSectionPageNumber = (pageNumber, format) => formatOoxmlCounter(pageNumber, format);
357
375
  /**
376
+ * Apply a `\#` numeric picture (ECMA-376 §17.16.4.2) to an integer field
377
+ * value. `0` is a mandatory digit placeholder that zero-pads once the value
378
+ * runs out of digits; `#` is an optional placeholder that is left blank
379
+ * instead; every other picture character (including a thousands separator)
380
+ * is copied through literally. Placeholders line up with the value's digits
381
+ * from the right; digits that reach past the leftmost placeholder are kept,
382
+ * not truncated.
383
+ */
384
+ function applyNumericPicture(value, picture) {
385
+ const negative = value < 0;
386
+ const digits = [...String(Math.trunc(Math.abs(value)))];
387
+ const pictureChars = [...picture];
388
+ const output = Array.from({ length: pictureChars.length });
389
+ let digitIndex = digits.length - 1;
390
+ for (let i = pictureChars.length - 1; i >= 0; i--) {
391
+ const char = pictureChars[i];
392
+ if (char === "0" || char === "#") if (digitIndex >= 0) {
393
+ output[i] = digits[digitIndex];
394
+ digitIndex--;
395
+ } else output[i] = char === "0" ? "0" : "";
396
+ else output[i] = char;
397
+ }
398
+ const overflow = digitIndex >= 0 ? digits.slice(0, digitIndex + 1).join("") : "";
399
+ return (negative ? "-" : "") + overflow + output.join("");
400
+ }
401
+ /**
358
402
  * Convert number to uppercase Roman numerals
359
403
  */
360
404
  function toRoman(num) {
@@ -555,4 +599,4 @@ function getMergeFieldNames(fields) {
555
599
  }).filter((name) => !!name);
556
600
  }
557
601
  //#endregion
558
- export { KNOWN_FIELD_TYPES, collectFields, computePageNumber, createComplexFieldContext, finalizeComplexField, formatDate, getFieldDisplayValue, getFieldsByType, getFormatSwitch, getMergeFieldNames, getMergeFields, getPageNumberFields, hasMergeFormat, isDateTimeField, isDocPropertyField, isHyperlinkField, isKnownFieldType, isMergeField, isPageNumberField, isReferenceField, isTocField, isTotalPagesField, parseFieldInstruction, parseFieldType, parseSimpleField, resetComplexFieldContext };
602
+ export { KNOWN_FIELD_TYPES, collectFields, computePageNumber, createComplexFieldContext, finalizeComplexField, formatDate, getFieldDisplayValue, getFieldsByType, getFormatSwitch, getMergeFieldNames, getMergeFields, getNumericPictureSwitch, getPageNumberFields, hasMergeFormat, isDateTimeField, isDocPropertyField, isHyperlinkField, isKnownFieldType, isMergeField, isPageNumberField, isReferenceField, isTocField, isTotalPagesField, parseFieldInstruction, parseFieldType, parseSimpleField, resetComplexFieldContext };
@@ -1,5 +1,5 @@
1
1
  import { DERIVED_PART_ROOT_ATTRIBUTES, attributeRemainder } from "./attributeRemainder.js";
2
- import { CAPTURE, dispatchChildren } from "./containerChildren.js";
2
+ import { CAPTURE, dispatchChildrenWithContext } from "./containerChildren.js";
3
3
  import { getAttribute, parseXmlDocument } from "./xmlParser.js";
4
4
  //#region src/docx/fontTableParser.ts
5
5
  const FONT_FAMILIES = [
@@ -22,14 +22,12 @@ const parseFontTable = (xml) => {
22
22
  const root = parseXmlDocument(xml);
23
23
  if (!root) return;
24
24
  const fonts = [];
25
- const preserved = dispatchChildren({
25
+ const preserved = dispatchChildrenWithContext({
26
26
  element: root,
27
27
  container: "w:fonts",
28
28
  capturePosition: () => fonts.length,
29
- handlers: { font: (element) => {
30
- const font = parseFont(element);
31
- if (font) fonts.push(font);
32
- } }
29
+ handlers: FONT_TABLE_HANDLERS,
30
+ context: fonts
33
31
  });
34
32
  const preservedAttributes = attributeRemainder({
35
33
  element: root,
@@ -45,31 +43,18 @@ const parseFontTable = (xml) => {
45
43
  const parseFont = (element) => {
46
44
  const name = getAttribute(element, "w", "name");
47
45
  if (!name) return;
48
- const font = { name };
49
- let modelled = 0;
50
- const read = (key, value) => {
51
- if (value === void 0) return;
52
- font[key] = value;
53
- modelled += 1;
46
+ const walk = {
47
+ font: { name },
48
+ modelled: 0
54
49
  };
55
- const preserved = dispatchChildren({
50
+ const preserved = dispatchChildrenWithContext({
56
51
  element,
57
52
  container: "w:font",
58
- capturePosition: () => modelled,
59
- handlers: {
60
- altName: (child) => read("altName", value(child)),
61
- panose1: (child) => read("panose1", value(child)),
62
- charset: (child) => read("charset", charsetOf(child)),
63
- family: (child) => read("family", narrow(value(child), FONT_FAMILIES)),
64
- notTrueType: CAPTURE,
65
- pitch: (child) => read("pitch", narrow(value(child), FONT_PITCHES)),
66
- sig: (child) => read("sig", signatureOf(child)),
67
- embedRegular: (child) => read("embedRegular", embedOf(child)),
68
- embedBold: (child) => read("embedBold", embedOf(child)),
69
- embedItalic: (child) => read("embedItalic", embedOf(child)),
70
- embedBoldItalic: (child) => read("embedBoldItalic", embedOf(child))
71
- }
53
+ capturePosition: () => walk.modelled,
54
+ handlers: FONT_HANDLERS,
55
+ context: walk
72
56
  });
57
+ const { font } = walk;
73
58
  if (preserved !== void 0) font.preserved = preserved;
74
59
  const preservedAttributes = attributeRemainder({
75
60
  element,
@@ -129,5 +114,27 @@ const signatureOf = (element) => {
129
114
  return Object.keys(signature).length === 0 ? void 0 : signature;
130
115
  };
131
116
  const narrow = (carried, members) => members.find((member) => member === carried);
117
+ const FONT_TABLE_HANDLERS = { font: (element, fonts) => {
118
+ const font = parseFont(element);
119
+ if (font) fonts.push(font);
120
+ } };
121
+ const read = (walk, key, carried) => {
122
+ if (carried === void 0) return;
123
+ walk.font[key] = carried;
124
+ walk.modelled += 1;
125
+ };
126
+ const FONT_HANDLERS = {
127
+ altName: (child, walk) => read(walk, "altName", value(child)),
128
+ panose1: (child, walk) => read(walk, "panose1", value(child)),
129
+ charset: (child, walk) => read(walk, "charset", charsetOf(child)),
130
+ family: (child, walk) => read(walk, "family", narrow(value(child), FONT_FAMILIES)),
131
+ notTrueType: CAPTURE,
132
+ pitch: (child, walk) => read(walk, "pitch", narrow(value(child), FONT_PITCHES)),
133
+ sig: (child, walk) => read(walk, "sig", signatureOf(child)),
134
+ embedRegular: (child, walk) => read(walk, "embedRegular", embedOf(child)),
135
+ embedBold: (child, walk) => read(walk, "embedBold", embedOf(child)),
136
+ embedItalic: (child, walk) => read(walk, "embedItalic", embedOf(child)),
137
+ embedBoldItalic: (child, walk) => read(walk, "embedBoldItalic", embedOf(child))
138
+ };
132
139
  //#endregion
133
140
  export { parseFontTable };
@@ -1,6 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { NumberingMap } from "./numberingParser.js";
3
3
  import { ParseContext } from "./parseContext.js";
4
+ import { PreviewLedger } from "./previewBudget.js";
4
5
  import { StyleMap } from "./styleParser.js";
5
6
  import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
6
7
  //#region src/docx/footnoteParser.d.ts
@@ -53,7 +54,7 @@ type EndnoteMap = {
53
54
  * @param media - Media files for images
54
55
  * @returns FootnoteMap with all footnotes
55
56
  */
56
- declare function parseFootnotes(footnotesXml: string | null, 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, context?: ParseContext): FootnoteMap;
57
+ declare function parseFootnotes(footnotesXml: string | null, 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, context?: ParseContext, previews?: PreviewLedger): FootnoteMap;
57
58
  /**
58
59
  * Parse endnotes.xml
59
60
  *
@@ -65,7 +66,7 @@ declare function parseFootnotes(footnotesXml: string | null, styles?: StyleMap |
65
66
  * @param media - Media files for images
66
67
  * @returns EndnoteMap with all endnotes
67
68
  */
68
- declare function parseEndnotes(endnotesXml: string | null, 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, context?: ParseContext): EndnoteMap;
69
+ declare function parseEndnotes(endnotesXml: string | null, 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, context?: ParseContext, previews?: PreviewLedger): EndnoteMap;
69
70
  /**
70
71
  * Get plain text content of a footnote.
71
72
  *
@@ -1,6 +1,7 @@
1
1
  import { blockPlainText } from "./blockPlainText.js";
2
2
  import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
3
3
  import { parseParagraph } from "./paragraphParser.js";
4
+ import { standalonePreviewLedger } from "./previewBudget.js";
4
5
  import { captureSdtSiblingMarkers, parseSdtProperties } from "./sdtProperties.js";
5
6
  import { parseTable } from "./tableParser.js";
6
7
  import { findChildren, findWordprocessingChild, getAttributes, getChildElements, getLocalName, parseXml } from "./xmlParser.js";
@@ -27,13 +28,13 @@ function parseNoteId(element) {
27
28
  const parsed = Number.parseInt(id, 10);
28
29
  return Number.isNaN(parsed) ? 0 : parsed;
29
30
  }
30
- function parseNoteBlockContent(element, styles, theme, numbering, rels, media) {
31
+ function parseNoteBlockContent(element, styles, theme, numbering, rels, media, previews) {
31
32
  const blocks = [];
32
33
  for (const child of getChildElements(element)) {
33
34
  const localName = getLocalName(child.name ?? "");
34
- if (localName === "p") blocks.push(parseParagraph(child, styles, theme, numbering, rels));
35
+ if (localName === "p") blocks.push(parseParagraph(child, styles, theme, numbering, rels, media, { previews }));
35
36
  else if (localName === "tbl") {
36
- const table = parseTable(child, styles, theme, numbering, rels, media);
37
+ const table = parseTable(child, styles, theme, numbering, rels, media, { previews });
37
38
  if (table) blocks.push(table);
38
39
  } else if (localName === "sdt") {
39
40
  const sdtPr = findWordprocessingChild(child, "sdtPr");
@@ -46,7 +47,7 @@ function parseNoteBlockContent(element, styles, theme, numbering, rels, media) {
46
47
  blocks.push({
47
48
  type: "blockSdt",
48
49
  properties,
49
- content: sdtContent ? parseNoteBlockContent(sdtContent, styles, theme, numbering, rels, media) : []
50
+ content: sdtContent ? parseNoteBlockContent(sdtContent, styles, theme, numbering, rels, media, previews) : []
50
51
  });
51
52
  }
52
53
  }
@@ -55,12 +56,12 @@ function parseNoteBlockContent(element, styles, theme, numbering, rels, media) {
55
56
  /**
56
57
  * Parse a single footnote element (w:footnote)
57
58
  */
58
- function parseFootnote(element, styles, theme, numbering, rels, media) {
59
+ function parseFootnote(element, styles, theme, numbering, rels, media, previews) {
59
60
  return {
60
61
  type: "footnote",
61
62
  id: parseNoteId(element),
62
63
  noteType: parseNoteType(getNoteAttribute(element, "type")),
63
- content: parseNoteBlockContent(element, styles, theme, numbering, rels, media)
64
+ content: parseNoteBlockContent(element, styles, theme, numbering, rels, media, previews)
64
65
  };
65
66
  }
66
67
  /**
@@ -74,7 +75,7 @@ function parseFootnote(element, styles, theme, numbering, rels, media) {
74
75
  * @param media - Media files for images
75
76
  * @returns FootnoteMap with all footnotes
76
77
  */
77
- function parseFootnotes(footnotesXml, styles = null, theme = null, numbering = null, rels = null, media = null, context) {
78
+ function parseFootnotes(footnotesXml, styles = null, theme = null, numbering = null, rels = null, media = null, context, previews = standalonePreviewLedger()) {
78
79
  const byId = /* @__PURE__ */ new Map();
79
80
  const footnotes = [];
80
81
  if (!footnotesXml) return createFootnoteMap(byId, footnotes);
@@ -82,13 +83,14 @@ function parseFootnotes(footnotesXml, styles = null, theme = null, numbering = n
82
83
  if (!rootElement) return createFootnoteMap(byId, footnotes);
83
84
  const footnoteElements = findChildren(rootElement, "w", "footnote");
84
85
  for (const fnEl of footnoteElements) {
85
- const footnote = parseFootnote(fnEl, styles, theme, numbering, rels, media);
86
+ const footnote = parseFootnote(fnEl, styles, theme, numbering, rels, media, previews);
86
87
  if (byId.has(footnote.id)) {
87
88
  context?.warn({
88
89
  code: PARSE_WARNING_CODES.duplicateNoteId,
89
90
  element: "w:footnote",
90
91
  at: `w:id ${String(footnote.id)}`
91
92
  });
93
+ previews.release(footnote);
92
94
  continue;
93
95
  }
94
96
  byId.set(footnote.id, footnote);
@@ -123,12 +125,12 @@ function createFootnoteMap(byId, footnotes) {
123
125
  /**
124
126
  * Parse a single endnote element (w:endnote)
125
127
  */
126
- function parseEndnote(element, styles, theme, numbering, rels, media) {
128
+ function parseEndnote(element, styles, theme, numbering, rels, media, previews) {
127
129
  return {
128
130
  type: "endnote",
129
131
  id: parseNoteId(element),
130
132
  noteType: parseNoteType(getNoteAttribute(element, "type")),
131
- content: parseNoteBlockContent(element, styles, theme, numbering, rels, media)
133
+ content: parseNoteBlockContent(element, styles, theme, numbering, rels, media, previews)
132
134
  };
133
135
  }
134
136
  /**
@@ -142,7 +144,7 @@ function parseEndnote(element, styles, theme, numbering, rels, media) {
142
144
  * @param media - Media files for images
143
145
  * @returns EndnoteMap with all endnotes
144
146
  */
145
- function parseEndnotes(endnotesXml, styles = null, theme = null, numbering = null, rels = null, media = null, context) {
147
+ function parseEndnotes(endnotesXml, styles = null, theme = null, numbering = null, rels = null, media = null, context, previews = standalonePreviewLedger()) {
146
148
  const byId = /* @__PURE__ */ new Map();
147
149
  const endnotes = [];
148
150
  if (!endnotesXml) return createEndnoteMap(byId, endnotes);
@@ -150,13 +152,14 @@ function parseEndnotes(endnotesXml, styles = null, theme = null, numbering = nul
150
152
  if (!rootElement) return createEndnoteMap(byId, endnotes);
151
153
  const endnoteElements = findChildren(rootElement, "w", "endnote");
152
154
  for (const enEl of endnoteElements) {
153
- const endnote = parseEndnote(enEl, styles, theme, numbering, rels, media);
155
+ const endnote = parseEndnote(enEl, styles, theme, numbering, rels, media, previews);
154
156
  if (byId.has(endnote.id)) {
155
157
  context?.warn({
156
158
  code: PARSE_WARNING_CODES.duplicateNoteId,
157
159
  element: "w:endnote",
158
160
  at: `w:id ${String(endnote.id)}`
159
161
  });
162
+ previews.release(endnote);
160
163
  continue;
161
164
  }
162
165
  byId.set(endnote.id, endnote);
@@ -1,6 +1,13 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { XmlElement } from "./xmlParser.js";
3
+ import { PreviewLedger } from "./previewBudget.js";
4
+ import { GroupTextBoxFrame } from "./drawingGroupChildren.js";
3
5
  //#region src/docx/groupDrawingParser.d.ts
6
+ /**
7
+ * The text boxes a group preview does not draw, which the paragraph parser
8
+ * lifts out as text boxes of their own. Undefined for any other image.
9
+ */
10
+ declare const groupPreviewTextBoxes: (image: document_d_exports.Image) => readonly GroupTextBoxFrame[] | undefined;
4
11
  /**
5
12
  * Whether a `w:drawing` carries a WordprocessingGroup payload. A group this
6
13
  * module declines to rasterize has no editable projection either — the shape
@@ -8,6 +15,6 @@ import { XmlElement } from "./xmlParser.js";
8
15
  */
9
16
  declare const isGroupDrawing: (drawing: XmlElement) => boolean;
10
17
  /** Parse a WordprocessingGroup drawing into a safe SVG-backed image preview. */
11
- declare const parseGroupDrawing: (drawing: XmlElement, rels?: document_d_exports.RelationshipMap, media?: Map<string, document_d_exports.MediaFile>) => document_d_exports.Image | null;
18
+ declare const parseGroupDrawing: (drawing: XmlElement, previews: PreviewLedger, rels?: document_d_exports.RelationshipMap, media?: Map<string, document_d_exports.MediaFile>) => document_d_exports.Image | null;
12
19
  //#endregion
13
- export { isGroupDrawing, parseGroupDrawing };
20
+ export { groupPreviewTextBoxes, isGroupDrawing, parseGroupDrawing };