@cj-tech-master/excelts 9.6.1 → 10.0.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 (207) hide show
  1. package/README.md +18 -3
  2. package/README_zh.md +18 -3
  3. package/dist/browser/modules/excel/cell.d.ts +4 -0
  4. package/dist/browser/modules/excel/note.js +5 -1
  5. package/dist/browser/modules/excel/row.js +35 -2
  6. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
  7. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +22 -2
  8. package/dist/browser/modules/excel/types.d.ts +81 -0
  9. package/dist/browser/modules/excel/utils/drawing-utils.d.ts +8 -0
  10. package/dist/browser/modules/excel/utils/drawing-utils.js +19 -2
  11. package/dist/browser/modules/excel/workbook.browser.d.ts +16 -0
  12. package/dist/browser/modules/excel/workbook.browser.js +32 -2
  13. package/dist/browser/modules/excel/worksheet.d.ts +31 -1
  14. package/dist/browser/modules/excel/worksheet.js +83 -0
  15. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
  16. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  17. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  18. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  19. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  20. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
  21. package/dist/browser/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  22. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
  23. package/dist/browser/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  24. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
  25. package/dist/browser/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  26. package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
  27. package/dist/browser/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
  28. package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  29. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  30. package/dist/browser/modules/pdf/builder/document-builder.js +22 -49
  31. package/dist/browser/modules/pdf/builder/pdf-editor.js +1 -1
  32. package/dist/browser/modules/pdf/core/pdf-stream.d.ts +28 -1
  33. package/dist/browser/modules/pdf/core/pdf-stream.js +38 -2
  34. package/dist/browser/modules/pdf/font/font-manager.d.ts +26 -0
  35. package/dist/browser/modules/pdf/font/font-manager.js +35 -18
  36. package/dist/browser/modules/pdf/render/page-renderer.d.ts +51 -3
  37. package/dist/browser/modules/pdf/render/page-renderer.js +111 -18
  38. package/dist/browser/modules/word/advanced/field-engine.js +45 -20
  39. package/dist/browser/modules/word/advanced/glossary.d.ts +10 -36
  40. package/dist/browser/modules/word/advanced/glossary.js +8 -9
  41. package/dist/browser/modules/word/advanced/math-convert.js +94 -12
  42. package/dist/browser/modules/word/advanced/ole-objects.d.ts +28 -0
  43. package/dist/browser/modules/word/advanced/ole-objects.js +122 -19
  44. package/dist/browser/modules/word/advanced/style-map.js +31 -10
  45. package/dist/browser/modules/word/builder/run-builders.d.ts +7 -1
  46. package/dist/browser/modules/word/builder/run-builders.js +7 -1
  47. package/dist/browser/modules/word/constants.d.ts +4 -0
  48. package/dist/browser/modules/word/constants.js +5 -1
  49. package/dist/browser/modules/word/convert/docx-to-semantic.d.ts +2 -1
  50. package/dist/browser/modules/word/convert/docx-to-semantic.js +135 -1
  51. package/dist/browser/modules/word/convert/html/html-import.d.ts +32 -1
  52. package/dist/browser/modules/word/convert/html/html-import.js +167 -14
  53. package/dist/browser/modules/word/convert/html/html.d.ts +2 -2
  54. package/dist/browser/modules/word/convert/html/html.js +1 -1
  55. package/dist/browser/modules/word/convert/markdown/markdown-import.d.ts +48 -18
  56. package/dist/browser/modules/word/convert/markdown/markdown-import.js +279 -69
  57. package/dist/browser/modules/word/convert/markdown/markdown.d.ts +1 -1
  58. package/dist/browser/modules/word/convert/odt/odt.js +407 -56
  59. package/dist/browser/modules/word/html.d.ts +2 -2
  60. package/dist/browser/modules/word/html.js +1 -1
  61. package/dist/browser/modules/word/index.base.d.ts +3 -3
  62. package/dist/browser/modules/word/index.base.js +1 -1
  63. package/dist/browser/modules/word/layout/layout-full.js +326 -19
  64. package/dist/browser/modules/word/layout/render-page.js +35 -8
  65. package/dist/browser/modules/word/markdown.d.ts +1 -1
  66. package/dist/browser/modules/word/query/compat.d.ts +10 -2
  67. package/dist/browser/modules/word/query/compat.js +29 -21
  68. package/dist/browser/modules/word/reader/docx-reader.js +105 -2
  69. package/dist/browser/modules/word/reader/math-parser.js +8 -2
  70. package/dist/browser/modules/word/security/cfb-reader.js +5 -5
  71. package/dist/browser/modules/word/types.d.ts +96 -1
  72. package/dist/browser/modules/word/writer/docx-packager.js +108 -2
  73. package/dist/browser/modules/word/writer/glossary-writer.d.ts +28 -0
  74. package/dist/browser/modules/word/writer/glossary-writer.js +121 -0
  75. package/dist/browser/modules/word/writer/header-footer-writer.js +105 -20
  76. package/dist/browser/modules/word/writer/math-writer.js +7 -2
  77. package/dist/browser/utils/font-metrics.d.ts +8 -0
  78. package/dist/browser/utils/font-metrics.js +43 -0
  79. package/dist/browser/utils/theme-colors.js +4 -1
  80. package/dist/cjs/modules/excel/note.js +5 -1
  81. package/dist/cjs/modules/excel/row.js +35 -2
  82. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +22 -2
  83. package/dist/cjs/modules/excel/utils/drawing-utils.js +19 -2
  84. package/dist/cjs/modules/excel/workbook.browser.js +31 -1
  85. package/dist/cjs/modules/excel/worksheet.js +83 -0
  86. package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  87. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  88. package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  89. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  90. package/dist/cjs/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  91. package/dist/cjs/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  92. package/dist/cjs/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  93. package/dist/cjs/modules/excel/xlsx/xform/drawing/shape-xform.js +112 -0
  94. package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  95. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  96. package/dist/cjs/modules/pdf/builder/document-builder.js +21 -48
  97. package/dist/cjs/modules/pdf/builder/pdf-editor.js +1 -1
  98. package/dist/cjs/modules/pdf/core/pdf-stream.js +38 -2
  99. package/dist/cjs/modules/pdf/font/font-manager.js +35 -18
  100. package/dist/cjs/modules/pdf/render/page-renderer.js +112 -18
  101. package/dist/cjs/modules/word/advanced/field-engine.js +45 -20
  102. package/dist/cjs/modules/word/advanced/glossary.js +8 -9
  103. package/dist/cjs/modules/word/advanced/math-convert.js +94 -12
  104. package/dist/cjs/modules/word/advanced/ole-objects.js +123 -19
  105. package/dist/cjs/modules/word/advanced/style-map.js +31 -10
  106. package/dist/cjs/modules/word/builder/run-builders.js +7 -1
  107. package/dist/cjs/modules/word/constants.js +5 -1
  108. package/dist/cjs/modules/word/convert/docx-to-semantic.js +135 -1
  109. package/dist/cjs/modules/word/convert/html/html-import.js +168 -14
  110. package/dist/cjs/modules/word/convert/html/html.js +2 -1
  111. package/dist/cjs/modules/word/convert/markdown/markdown-import.js +279 -69
  112. package/dist/cjs/modules/word/convert/odt/odt.js +407 -56
  113. package/dist/cjs/modules/word/html.js +2 -1
  114. package/dist/cjs/modules/word/index.base.js +4 -3
  115. package/dist/cjs/modules/word/layout/layout-full.js +325 -18
  116. package/dist/cjs/modules/word/layout/render-page.js +35 -8
  117. package/dist/cjs/modules/word/query/compat.js +29 -21
  118. package/dist/cjs/modules/word/reader/docx-reader.js +104 -1
  119. package/dist/cjs/modules/word/reader/math-parser.js +8 -2
  120. package/dist/cjs/modules/word/security/cfb-reader.js +5 -5
  121. package/dist/cjs/modules/word/writer/docx-packager.js +108 -2
  122. package/dist/cjs/modules/word/writer/glossary-writer.js +124 -0
  123. package/dist/cjs/modules/word/writer/header-footer-writer.js +105 -20
  124. package/dist/cjs/modules/word/writer/math-writer.js +7 -2
  125. package/dist/cjs/utils/font-metrics.js +44 -0
  126. package/dist/cjs/utils/theme-colors.js +4 -1
  127. package/dist/esm/modules/excel/note.js +5 -1
  128. package/dist/esm/modules/excel/row.js +35 -2
  129. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +22 -2
  130. package/dist/esm/modules/excel/utils/drawing-utils.js +19 -2
  131. package/dist/esm/modules/excel/workbook.browser.js +32 -2
  132. package/dist/esm/modules/excel/worksheet.js +83 -0
  133. package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +42 -8
  134. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +3 -1
  135. package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +5 -0
  136. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +18 -1
  137. package/dist/esm/modules/excel/xlsx/xform/drawing/blip-xform.js +38 -11
  138. package/dist/esm/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.js +5 -0
  139. package/dist/esm/modules/excel/xlsx/xform/drawing/pic-xform.js +2 -1
  140. package/dist/esm/modules/excel/xlsx/xform/drawing/shape-xform.js +109 -0
  141. package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +10 -1
  142. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +64 -1
  143. package/dist/esm/modules/pdf/builder/document-builder.js +22 -49
  144. package/dist/esm/modules/pdf/builder/pdf-editor.js +1 -1
  145. package/dist/esm/modules/pdf/core/pdf-stream.js +38 -2
  146. package/dist/esm/modules/pdf/font/font-manager.js +35 -18
  147. package/dist/esm/modules/pdf/render/page-renderer.js +111 -18
  148. package/dist/esm/modules/word/advanced/field-engine.js +45 -20
  149. package/dist/esm/modules/word/advanced/glossary.js +8 -9
  150. package/dist/esm/modules/word/advanced/math-convert.js +94 -12
  151. package/dist/esm/modules/word/advanced/ole-objects.js +122 -19
  152. package/dist/esm/modules/word/advanced/style-map.js +31 -10
  153. package/dist/esm/modules/word/builder/run-builders.js +7 -1
  154. package/dist/esm/modules/word/constants.js +5 -1
  155. package/dist/esm/modules/word/convert/docx-to-semantic.js +135 -1
  156. package/dist/esm/modules/word/convert/html/html-import.js +167 -14
  157. package/dist/esm/modules/word/convert/html/html.js +1 -1
  158. package/dist/esm/modules/word/convert/markdown/markdown-import.js +279 -69
  159. package/dist/esm/modules/word/convert/odt/odt.js +407 -56
  160. package/dist/esm/modules/word/html.js +1 -1
  161. package/dist/esm/modules/word/index.base.js +1 -1
  162. package/dist/esm/modules/word/layout/layout-full.js +326 -19
  163. package/dist/esm/modules/word/layout/render-page.js +35 -8
  164. package/dist/esm/modules/word/query/compat.js +29 -21
  165. package/dist/esm/modules/word/reader/docx-reader.js +105 -2
  166. package/dist/esm/modules/word/reader/math-parser.js +8 -2
  167. package/dist/esm/modules/word/security/cfb-reader.js +5 -5
  168. package/dist/esm/modules/word/writer/docx-packager.js +108 -2
  169. package/dist/esm/modules/word/writer/glossary-writer.js +121 -0
  170. package/dist/esm/modules/word/writer/header-footer-writer.js +105 -20
  171. package/dist/esm/modules/word/writer/math-writer.js +7 -2
  172. package/dist/esm/utils/font-metrics.js +43 -0
  173. package/dist/esm/utils/theme-colors.js +4 -1
  174. package/dist/iife/excelts.iife.js +496 -59
  175. package/dist/iife/excelts.iife.js.map +1 -1
  176. package/dist/iife/excelts.iife.min.js +39 -39
  177. package/dist/types/modules/excel/cell.d.ts +4 -0
  178. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +8 -1
  179. package/dist/types/modules/excel/types.d.ts +81 -0
  180. package/dist/types/modules/excel/utils/drawing-utils.d.ts +8 -0
  181. package/dist/types/modules/excel/workbook.browser.d.ts +16 -0
  182. package/dist/types/modules/excel/worksheet.d.ts +31 -1
  183. package/dist/types/modules/excel/xlsx/xform/comment/vml-shape-xform.d.ts +7 -0
  184. package/dist/types/modules/excel/xlsx/xform/drawing/blip-xform.d.ts +6 -0
  185. package/dist/types/modules/excel/xlsx/xform/drawing/one-cell-anchor-xform.d.ts +1 -0
  186. package/dist/types/modules/excel/xlsx/xform/drawing/pic-xform.d.ts +2 -0
  187. package/dist/types/modules/excel/xlsx/xform/drawing/shape-xform.d.ts +47 -0
  188. package/dist/types/modules/pdf/core/pdf-stream.d.ts +28 -1
  189. package/dist/types/modules/pdf/font/font-manager.d.ts +26 -0
  190. package/dist/types/modules/pdf/render/page-renderer.d.ts +51 -3
  191. package/dist/types/modules/word/advanced/glossary.d.ts +10 -36
  192. package/dist/types/modules/word/advanced/ole-objects.d.ts +28 -0
  193. package/dist/types/modules/word/builder/run-builders.d.ts +7 -1
  194. package/dist/types/modules/word/constants.d.ts +4 -0
  195. package/dist/types/modules/word/convert/docx-to-semantic.d.ts +2 -1
  196. package/dist/types/modules/word/convert/html/html-import.d.ts +32 -1
  197. package/dist/types/modules/word/convert/html/html.d.ts +2 -2
  198. package/dist/types/modules/word/convert/markdown/markdown-import.d.ts +48 -18
  199. package/dist/types/modules/word/convert/markdown/markdown.d.ts +1 -1
  200. package/dist/types/modules/word/html.d.ts +2 -2
  201. package/dist/types/modules/word/index.base.d.ts +3 -3
  202. package/dist/types/modules/word/markdown.d.ts +1 -1
  203. package/dist/types/modules/word/query/compat.d.ts +10 -2
  204. package/dist/types/modules/word/types.d.ts +96 -1
  205. package/dist/types/modules/word/writer/glossary-writer.d.ts +28 -0
  206. package/dist/types/utils/font-metrics.d.ts +8 -0
  207. package/package.json +3 -1
@@ -13,6 +13,7 @@
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.renderPage = renderPage;
15
15
  exports.emitTextWithMatrix = emitTextWithMatrix;
16
+ exports.emitTextBlock = emitTextBlock;
16
17
  exports.alphaGsName = alphaGsName;
17
18
  exports.computeTextStartY = computeTextStartY;
18
19
  exports.computeTextX = computeTextX;
@@ -684,7 +685,7 @@ function drawRotatedGeneral(stream, cell, lines, fontManager, resourceName, font
684
685
  emitTextWithMatrix(stream, line, cos, sin, -sin, cos, tx, ty, resourceName, fontSize, fontManager, useType3);
685
686
  }
686
687
  }
687
- /** Emit a text string with hex encoding if available. */
688
+ /** Emit a text string with hex encoding if available, onto a sink stream. */
688
689
  function emitText(stream, fontManager, text, resourceName) {
689
690
  const hex = fontManager.encodeText(text, resourceName);
690
691
  if (hex) {
@@ -699,40 +700,133 @@ function emitText(stream, fontManager, text, resourceName) {
699
700
  * when needed. For each sub-run the matrix origin is advanced along the
700
701
  * text direction (cos, sin) by the rendered width.
701
702
  *
702
- * When `useType3` is false this collapses to a single BT/ET pair — identical
703
- * to the old `emitText()` path but wrapped in begin/end for convenience.
703
+ * The emitted operators are written as a *deferred* fragment (see
704
+ * `PdfContentStream.deferred`). The fragment is only evaluated at
705
+ * serialization time, by which point `PdfDocumentBuilder.build()` has
706
+ * finalised the document's fonts (auto-discovered embedded CIDFont,
707
+ * Type3 fallback, or plain Type1). This is essential: at draw time the
708
+ * font manager has not yet decided whether a non-WinAnsi code point (e.g.
709
+ * U+2192 →) will be served by an embedded font or a Type3 glyph, so eager
710
+ * encoding would irreversibly degrade those characters to spaces via the
711
+ * WinAnsi fallback. Deferring the encode keeps the fragment at its exact
712
+ * draw-order slot (preserving z-order) while choosing the correct bytes
713
+ * once fonts are known.
714
+ *
715
+ * The `useType3` argument is the caller's *draw-time* guess and is ignored;
716
+ * the deferred body recomputes the routing from the now-settled font
717
+ * manager state.
718
+ */
719
+ function emitTextWithMatrix(stream, text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager, _useType3) {
720
+ stream.deferred(() => renderTextBlock(text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager));
721
+ }
722
+ /**
723
+ * Emit a text block as a single *deferred* fragment so that anchor
724
+ * alignment, word wrapping, and glyph encoding are all computed at
725
+ * serialization time — after `PdfDocumentBuilder.build()` has finalised the
726
+ * document's fonts.
727
+ *
728
+ * This matters because text measurement (anchor offset, line breaking) must
729
+ * use the *same* font that ultimately renders the glyphs. At draw time the
730
+ * font may still be unresolved (a non-WinAnsi run can trigger a build-time
731
+ * auto-embed of a system CIDFont), so measuring against the provisional
732
+ * Type1/Helvetica metrics would misplace centred/right-aligned text and
733
+ * break lines at the wrong points. Deferring keeps measurement and encoding
734
+ * consistent while preserving the fragment's draw-order slot (z-order).
735
+ */
736
+ function emitTextBlock(stream, options, fontManager) {
737
+ stream.deferred(() => renderTextBlockLayout(options, fontManager));
738
+ }
739
+ /**
740
+ * Lay out and render a text block from the font manager's *current*
741
+ * (build-time) state. Resolves the render resource name once and uses it for
742
+ * both measurement and encoding so the two never disagree.
743
+ *
744
+ * Layout is computed in the text's *local* coordinate frame — x grows along
745
+ * the baseline, y grows upward — then mapped to page space through the
746
+ * rotation matrix. This makes anchor alignment, multi-line word wrapping, and
747
+ * rotation compose correctly together: each line is offset by its anchor
748
+ * shift (along local x) and its line index (down local y), and a single
749
+ * rotation maps the whole block into place. Upright text (rotation 0) reduces
750
+ * to the identity mapping.
751
+ */
752
+ function renderTextBlockLayout(options, fontManager) {
753
+ const { text, x, y, type1ResourceName, fontSize, anchor, maxWidth, lineHeightFactor, rotation } = options;
754
+ // Resolve the resource name once; measurement and rendering share it so a
755
+ // build-time auto-embedded CIDFont (or Type3 fallback) is measured with the
756
+ // metrics that will actually render the glyphs.
757
+ const measureResource = fontManager.resolveRenderResourceName(type1ResourceName);
758
+ const measure = (s) => fontManager.measureText(s, measureResource, fontSize);
759
+ const lines = maxWidth ? wrapTextLines(text, measure, maxWidth) : [text];
760
+ const leading = fontSize * lineHeightFactor;
761
+ // Rotation matrix [a b; c d] = [cos sin; -sin cos]; identity when upright.
762
+ const theta = (rotation * Math.PI) / 180;
763
+ const cos = rotation === 0 ? 1 : Math.cos(theta);
764
+ const sin = rotation === 0 ? 0 : Math.sin(theta);
765
+ const anchorFactor = anchor === "middle" ? 0.5 : anchor === "end" ? 1 : 0;
766
+ const parts = [];
767
+ for (let i = 0; i < lines.length; i++) {
768
+ // Local-frame origin of this line: anchor shift along x, line index down y.
769
+ const localX = anchorFactor === 0 ? 0 : -measure(lines[i]) * anchorFactor;
770
+ const localY = -i * leading;
771
+ // Map local origin into page space through the rotation matrix.
772
+ const tx = x + localX * cos + localY * -sin;
773
+ const ty = y + localX * sin + localY * cos;
774
+ parts.push(renderTextBlock(lines[i], cos, sin, -sin, cos, tx, ty, type1ResourceName, fontSize, fontManager));
775
+ }
776
+ return parts.join("\n");
777
+ }
778
+ /**
779
+ * Produce the PDF operator string for a positioned text run, choosing the
780
+ * encoding from the font manager's *current* (build-time) state:
781
+ * - embedded font → single BT/ET with CIDFont hex encoding
782
+ * - Type3 fallback → split into WinAnsi (Type1) and per-glyph Type3 runs
783
+ * - neither → single BT/ET with Type1/WinAnsi encoding
784
+ *
785
+ * Must only be called after font resolution (i.e. from a deferred fragment).
704
786
  */
705
- function emitTextWithMatrix(stream, text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager, useType3) {
787
+ function renderTextBlock(text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager) {
788
+ const sink = new pdf_stream_1.PdfContentStream();
789
+ // Type3 splitting only applies when there is no embedded font but Type3
790
+ // fallback glyphs were generated. Otherwise the run renders as a single
791
+ // BT/ET pair, choosing the resource name from the now-settled state:
792
+ // if an embedded font exists (possibly auto-discovered at build time,
793
+ // after this run was drawn against a Type1 resource), the run must use it
794
+ // so `emitText` → `encodeText` produces CIDFont hex; without that switch
795
+ // the stale Type1 resource name would make `encodeText` return null and
796
+ // non-WinAnsi characters would degrade to spaces via the WinAnsi fallback.
797
+ const useType3 = fontManager.hasType3Fonts() && !fontManager.hasEmbeddedFont();
706
798
  if (!useType3) {
707
- stream.beginText();
708
- stream.setFont(type1ResourceName, fontSize);
709
- stream.setTextMatrix(a, b, c, d, tx, ty);
710
- emitText(stream, fontManager, text, type1ResourceName);
711
- stream.endText();
712
- return;
799
+ const resourceName = fontManager.resolveRenderResourceName(type1ResourceName);
800
+ sink.beginText();
801
+ sink.setFont(resourceName, fontSize);
802
+ sink.setTextMatrix(a, b, c, d, tx, ty);
803
+ emitText(sink, fontManager, text, resourceName);
804
+ sink.endText();
805
+ return sink.toString();
713
806
  }
714
807
  // Type3 path: split into runs and advance origin along text direction
715
808
  const runs = splitTextRuns(text, fontManager);
716
809
  let curTx = tx;
717
810
  let curTy = ty;
718
811
  for (const run of runs) {
719
- stream.beginText();
812
+ sink.beginText();
720
813
  if (run.type3) {
721
- stream.setFont(run.type3.resourceName, fontSize);
722
- stream.setTextMatrix(a, b, c, d, curTx, curTy);
723
- stream.showTextHex(run.type3.hex);
814
+ sink.setFont(run.type3.resourceName, fontSize);
815
+ sink.setTextMatrix(a, b, c, d, curTx, curTy);
816
+ sink.showTextHex(run.type3.hex);
724
817
  }
725
818
  else {
726
- stream.setFont(type1ResourceName, fontSize);
727
- stream.setTextMatrix(a, b, c, d, curTx, curTy);
728
- emitText(stream, fontManager, run.text, type1ResourceName);
819
+ sink.setFont(type1ResourceName, fontSize);
820
+ sink.setTextMatrix(a, b, c, d, curTx, curTy);
821
+ emitText(sink, fontManager, run.text, type1ResourceName);
729
822
  }
730
- stream.endText();
823
+ sink.endText();
731
824
  const w = fontManager.measureText(run.text, run.type3?.resourceName ?? type1ResourceName, fontSize);
732
825
  // Advance along the text direction (first column of the matrix)
733
826
  curTx += a * w;
734
827
  curTy += b * w;
735
828
  }
829
+ return sink.toString();
736
830
  }
737
831
  /**
738
832
  * Split a line of text into runs of consecutive WinAnsi and non-WinAnsi
@@ -46,7 +46,7 @@ function updateFields(doc, options) {
46
46
  // Build style → paragraphs index for STYLEREF
47
47
  const styleIndex = buildStyleIndex(doc);
48
48
  // Collect INDEX entries (XE fields) from all body content
49
- const indexEntries = collectIndexEntries(doc);
49
+ const indexEntries = collectIndexEntries(doc, layout);
50
50
  // Update body content
51
51
  const newBody = updateBody(doc, layout, bookmarkInfo, seqValues, styleIndex, indexEntries, opts);
52
52
  // If TOC was updated, register the TOC1..TOCn paragraph styles so the
@@ -614,23 +614,33 @@ function findStyleRef(styleIndex, styleName, bodyIndex) {
614
614
  // =============================================================================
615
615
  // Index Entry Collection (for INDEX field)
616
616
  // =============================================================================
617
- /** Collect all XE (Index Entry) fields from the document body. */
618
- function collectIndexEntries(doc) {
617
+ /**
618
+ * Collect all XE (Index Entry) fields from the document body, tagging each
619
+ * with the page it falls on. Page numbers come from `layout.contentPages`
620
+ * keyed by the enclosing top-level body index (the same mechanism used for
621
+ * headings/TOC); entries inside nested structures inherit their outer block's
622
+ * page, falling back to 1 when layout produced no page for that block.
623
+ */
624
+ function collectIndexEntries(doc, layout) {
619
625
  const entries = [];
620
- (0, walker_1.walkBlocks)(doc.body, {
621
- visitRunContent(content) {
622
- if (content.type !== "field") {
623
- return;
624
- }
625
- const { type, args } = parseFieldInstruction(content.instruction);
626
- if (type === "XE") {
627
- const term = parseXeTerm(args);
628
- if (term) {
629
- entries.push({ term, page: 1 });
626
+ const { contentPages } = layout;
627
+ for (let i = 0; i < doc.body.length; i++) {
628
+ const page = contentPages[i] ?? 1;
629
+ (0, walker_1.walkBlocks)([doc.body[i]], {
630
+ visitRunContent(content) {
631
+ if (content.type !== "field") {
632
+ return;
633
+ }
634
+ const { type, args } = parseFieldInstruction(content.instruction);
635
+ if (type === "XE") {
636
+ const term = parseXeTerm(args);
637
+ if (term) {
638
+ entries.push({ term, page });
639
+ }
630
640
  }
631
641
  }
632
- }
633
- });
642
+ });
643
+ }
634
644
  return entries;
635
645
  }
636
646
  /** Parse the term from an XE field argument: XE "term" or XE term. */
@@ -647,17 +657,32 @@ function buildIndexContent(entries, args) {
647
657
  if (entries.length === 0) {
648
658
  return "";
649
659
  }
650
- // Sort entries alphabetically by term
651
- const sorted = [...entries].sort((a, b) => a.term.localeCompare(b.term));
660
+ // Merge entries that share the same term: a term marked on several pages
661
+ // produces a single index line listing each distinct page (Word behaviour),
662
+ // e.g. "widget, 1, 4" rather than two separate "widget" rows.
663
+ const byTerm = new Map();
664
+ for (const entry of entries) {
665
+ let pages = byTerm.get(entry.term);
666
+ if (!pages) {
667
+ pages = new Set();
668
+ byTerm.set(entry.term, pages);
669
+ }
670
+ pages.add(entry.page);
671
+ }
672
+ // Sort terms alphabetically; within each term sort pages numerically.
673
+ const merged = [...byTerm.entries()]
674
+ .map(([term, pages]) => ({ term, pages: [...pages].sort((a, b) => a - b) }))
675
+ .sort((a, b) => a.term.localeCompare(b.term));
676
+ const formatEntry = (e) => `${e.term}\t${e.pages.join(", ")}`;
652
677
  // Check for \h switch (group by first letter with headings)
653
678
  const grouped = /\\h\b/i.test(args);
654
679
  if (!grouped) {
655
- return sorted.map(e => `${e.term}\t${e.page}`).join("\n");
680
+ return merged.map(formatEntry).join("\n");
656
681
  }
657
682
  // Group by first letter
658
683
  const lines = [];
659
684
  let currentLetter = "";
660
- for (const entry of sorted) {
685
+ for (const entry of merged) {
661
686
  const letter = entry.term.charAt(0).toUpperCase();
662
687
  if (letter !== currentLetter) {
663
688
  currentLetter = letter;
@@ -666,7 +691,7 @@ function buildIndexContent(entries, args) {
666
691
  }
667
692
  lines.push(currentLetter);
668
693
  }
669
- lines.push(`${entry.term}\t${entry.page}`);
694
+ lines.push(formatEntry(entry));
670
695
  }
671
696
  return lines.join("\n");
672
697
  }
@@ -5,17 +5,16 @@
5
5
  * Provides types and utilities for working with Glossary Document parts,
6
6
  * which contain AutoText entries, Quick Parts, and other Building Blocks.
7
7
  *
8
- * INTEGRATION STATUS: This module provides data structures and query helpers
9
- * for building blocks. The actual reading/writing of glossary parts from/to
10
- * DOCX archives is handled via the opaqueParts round-trip mechanism —
11
- * glossary parts in existing files are preserved as opaque parts during
12
- * read/write. This module is useful for:
8
+ * INTEGRATION STATUS: This module provides the glossary data model and query
9
+ * helpers. To embed a glossary in a document, assign a {@link GlossaryDocument}
10
+ * to `doc.glossary`; the packager then serialises it to
11
+ * `word/glossary/document.xml`, registers the `glossaryDocument` relationship,
12
+ * and adds the `[Content_Types].xml` override (the canonical OOXML location
13
+ * Word reads Quick Parts / AutoText from). Glossary parts in existing files are
14
+ * round-tripped via the same channel. This module is useful for:
13
15
  * - Building glossary data structures programmatically
14
16
  * - Querying/filtering building block collections
15
- * - Preparing data for future direct glossary part writing
16
- *
17
- * To add glossary content to a document currently, include it as an
18
- * OpaquePart with path "word/glossary/document.xml".
17
+ * - Assembling a glossary to attach via `doc.glossary`
19
18
  */
20
19
  Object.defineProperty(exports, "__esModule", { value: true });
21
20
  exports.createBuildingBlock = createBuildingBlock;
@@ -83,20 +83,98 @@ function convertNodeToMathML(node) {
83
83
  }
84
84
  }
85
85
  function convertMathRunToMathML(node) {
86
- const text = (0, encode_1.xmlEncode)(node.text);
87
- // Operators and special characters
88
- if (isOperator(node.text)) {
89
- return `<mo>${text}</mo>`;
86
+ const raw = node.text;
87
+ // A single OMML run can hold a mix of letters, digits, operators and
88
+ // whitespace (e.g. "a + b = "). MathML presentation markup expects each
89
+ // token to be wrapped in the element matching its semantic category:
90
+ // <mi> for identifiers, <mn> for numbers, <mo> for operators and <mtext>
91
+ // for runs of whitespace / other text. Tokenize the run and emit one
92
+ // element per token so a mixed run is no longer flattened into a single
93
+ // (incorrect) <mi>.
94
+ const normalVariant = node.properties?.italic === false;
95
+ const tokens = tokenizeMathRun(raw);
96
+ // Fast path / backwards-compatibility: a run that is a single token keeps
97
+ // producing exactly one element (e.g. "x" -> <mi>x</mi>, "42" -> <mn>42</mn>,
98
+ // "+" -> <mo>+</mo>), matching the historical output.
99
+ return tokens.map(tok => emitMathToken(tok, normalVariant)).join("");
100
+ }
101
+ function classifyMathChar(ch) {
102
+ if (/\s/.test(ch)) {
103
+ return "text";
104
+ }
105
+ if (isOperator(ch)) {
106
+ return "operator";
107
+ }
108
+ if (/[0-9.]/.test(ch)) {
109
+ return "number";
110
+ }
111
+ if (/[A-Za-z]/.test(ch)) {
112
+ return "identifier";
113
+ }
114
+ // Letters outside ASCII (Greek, CJK, etc.) and any other symbol fall back to
115
+ // identifier — this matches how OMML treats variable names.
116
+ return "identifier";
117
+ }
118
+ function tokenizeMathRun(text) {
119
+ const tokens = [];
120
+ // Use code points so astral / combined characters are not split mid-symbol.
121
+ const chars = Array.from(text);
122
+ for (const ch of chars) {
123
+ const kind = classifyMathChar(ch);
124
+ const last = tokens[tokens.length - 1];
125
+ // Operators are always emitted as their own token (each operator is a
126
+ // distinct <mo>). Numbers, identifiers and text coalesce with the
127
+ // previous token of the same kind so "42" stays a single <mn> and a run
128
+ // of spaces stays a single <mtext>.
129
+ if (last && last.kind === kind && kind !== "operator") {
130
+ last.value += ch;
131
+ }
132
+ else {
133
+ tokens.push({ kind, value: ch });
134
+ }
90
135
  }
91
- // Numbers
92
- if (/^\d+(\.\d+)?$/.test(node.text)) {
93
- return `<mn>${text}</mn>`;
136
+ return mergeDecimalPoints(tokens);
137
+ }
138
+ /**
139
+ * A "." between two digit groups is a decimal point, not an operator. The
140
+ * char classifier sees "." as an operator (it is a valid math operator on its
141
+ * own, e.g. function composition), so stitch `<number> "." <number>` back into
142
+ * a single number token here.
143
+ */
144
+ function mergeDecimalPoints(tokens) {
145
+ const out = [];
146
+ for (let i = 0; i < tokens.length; i++) {
147
+ const tok = tokens[i];
148
+ const prev = out[out.length - 1];
149
+ const next = tokens[i + 1];
150
+ if (tok.kind === "operator" &&
151
+ tok.value === "." &&
152
+ prev &&
153
+ prev.kind === "number" &&
154
+ next &&
155
+ next.kind === "number") {
156
+ prev.value += "." + next.value;
157
+ i++; // consume the following number token
158
+ continue;
159
+ }
160
+ out.push(tok);
94
161
  }
95
- // Identifiers
96
- if (node.properties?.italic === false) {
97
- return `<mi mathvariant="normal">${text}</mi>`;
162
+ return out;
163
+ }
164
+ function emitMathToken(tok, normalVariant) {
165
+ const text = (0, encode_1.xmlEncode)(tok.value);
166
+ switch (tok.kind) {
167
+ case "operator":
168
+ return `<mo>${text}</mo>`;
169
+ case "number":
170
+ // A lone "." is not a number; treat it as an operator/text fallback.
171
+ return /\d/.test(tok.value) ? `<mn>${text}</mn>` : `<mo>${text}</mo>`;
172
+ case "text":
173
+ return `<mtext>${text}</mtext>`;
174
+ case "identifier":
175
+ default:
176
+ return normalVariant ? `<mi mathvariant="normal">${text}</mi>` : `<mi>${text}</mi>`;
98
177
  }
99
- return `<mi>${text}</mi>`;
100
178
  }
101
179
  function convertFractionToMathML(node) {
102
180
  const num = childrenToMathML(node.numerator);
@@ -258,7 +336,11 @@ function convertMMLElement(el) {
258
336
  }
259
337
  case "msqrt": {
260
338
  const content = convertMMLChildren(el.children);
261
- return { type: "mathRadical", content };
339
+ // A bare square root has no degree. OOXML still emits an (empty)
340
+ // <m:deg/>, so we must set hideDegree → <m:degHide m:val="1"/>;
341
+ // otherwise Word treats the empty degree as visible and draws an empty
342
+ // degree box (a small square) at the radical's upper-left.
343
+ return { type: "mathRadical", content, hideDegree: true };
262
344
  }
263
345
  case "mroot": {
264
346
  const children = getElementChildren(el);
@@ -19,6 +19,9 @@ exports.extractOleObjects = extractOleObjects;
19
19
  exports.hasOleObjects = hasOleObjects;
20
20
  exports.getOleObjectData = getOleObjectData;
21
21
  exports.createOleEmbedding = createOleEmbedding;
22
+ exports.addOleObject = addOleObject;
23
+ const encode_1 = require("../../xml/encode.js");
24
+ const constants_1 = require("../constants");
22
25
  const opc_paths_1 = require("../core/opc-paths");
23
26
  // =============================================================================
24
27
  // OLE Object Extraction
@@ -34,14 +37,33 @@ const opc_paths_1 = require("../core/opc-paths");
34
37
  function extractOleObjects(doc) {
35
38
  const objects = [];
36
39
  const summary = {};
37
- // Scan opaque parts for OLE embeddings
40
+ const pushObject = (obj) => {
41
+ objects.push(obj);
42
+ summary[obj.progId] = (summary[obj.progId] ?? 0) + 1;
43
+ };
44
+ // Structured OLE objects wired on document.xml.rels (preferred form —
45
+ // these carry the real rId and, when available, the round-tripped progId).
46
+ if (doc.oleObjects) {
47
+ for (const ole of doc.oleObjects) {
48
+ pushObject({
49
+ rId: ole.rId,
50
+ progId: ole.progId ?? detectProgIdFromData(ole.data),
51
+ objectType: "embedded",
52
+ displayAs: "icon",
53
+ imageRId: ole.previewRId,
54
+ fileName: (0, opc_paths_1.getFileName)(ole.path),
55
+ data: ole.data
56
+ });
57
+ }
58
+ }
59
+ // Scan opaque parts for OLE embeddings (legacy / hand-built documents that
60
+ // did not go through the structured oleObjects channel).
38
61
  if (doc.opaqueParts) {
39
62
  for (const part of doc.opaqueParts) {
40
63
  if (isOleEmbedding(part.path)) {
41
64
  const obj = parseOlePartMetadata(part);
42
65
  if (obj) {
43
- objects.push(obj);
44
- summary[obj.progId] = (summary[obj.progId] ?? 0) + 1;
66
+ pushObject(obj);
45
67
  }
46
68
  }
47
69
  }
@@ -51,11 +73,10 @@ function extractOleObjects(doc) {
51
73
  if (element.type === "opaqueDrawing") {
52
74
  const oleFromDrawing = extractOleFromRawXml(element.rawXml);
53
75
  if (oleFromDrawing) {
54
- // Check if we already have this object from opaque parts
55
- const exists = objects.some(o => o.rId === oleFromDrawing.rId);
76
+ // Check if we already have this object (by rId)
77
+ const exists = objects.some(o => o.rId === oleFromDrawing.rId && o.rId !== "");
56
78
  if (!exists) {
57
- objects.push(oleFromDrawing);
58
- summary[oleFromDrawing.progId] = (summary[oleFromDrawing.progId] ?? 0) + 1;
79
+ pushObject(oleFromDrawing);
59
80
  }
60
81
  }
61
82
  }
@@ -66,6 +87,9 @@ function extractOleObjects(doc) {
66
87
  * Check if a document contains any OLE embedded objects.
67
88
  */
68
89
  function hasOleObjects(doc) {
90
+ if (doc.oleObjects && doc.oleObjects.length > 0) {
91
+ return true;
92
+ }
69
93
  if (doc.opaqueParts) {
70
94
  for (const part of doc.opaqueParts) {
71
95
  if (isOleEmbedding(part.path)) {
@@ -80,6 +104,14 @@ function hasOleObjects(doc) {
80
104
  * Returns undefined if not found.
81
105
  */
82
106
  function getOleObjectData(doc, rId) {
107
+ // Structured OLE objects carry the exact rId used on document.xml.rels.
108
+ if (doc.oleObjects) {
109
+ for (const ole of doc.oleObjects) {
110
+ if (ole.rId === rId) {
111
+ return ole.data;
112
+ }
113
+ }
114
+ }
83
115
  if (!doc.opaqueParts) {
84
116
  return undefined;
85
117
  }
@@ -132,16 +164,14 @@ function createOleEmbedding(data, progId, options) {
132
164
  const olePart = {
133
165
  path: `word/embeddings/${fileName}`,
134
166
  data,
135
- contentType: "application/vnd.openxmlformats-officedocument.oleObject",
167
+ contentType: constants_1.ContentType.OleObject,
136
168
  relationships: undefined
137
169
  };
138
170
  const oleRId = `rIdOle${oleSeq}`;
139
- // progId is metadata for downstream consumers not stored on
140
- // OpaquePart but accepted in the signature so callers can pass it
141
- // alongside without a separate channel. We don't need it here.
142
- void progId;
171
+ // progId is carried back on the result so addOleObject() can persist it
172
+ // into the body `<o:OLEObject ProgID="…">` markup for round-trip.
143
173
  if (!options?.previewImage) {
144
- return { olePart, oleRId };
174
+ return { olePart, oleRId, progId };
145
175
  }
146
176
  if (!options.previewContentType) {
147
177
  throw new Error("createOleEmbedding: options.previewImage requires options.previewContentType");
@@ -156,7 +186,78 @@ function createOleEmbedding(data, progId, options) {
156
186
  relationships: undefined
157
187
  };
158
188
  const previewRId = `rIdOleImg${previewSeq}`;
159
- return { olePart, oleRId, previewPart, previewRId };
189
+ return { olePart, oleRId, progId, previewPart, previewRId };
190
+ }
191
+ /**
192
+ * Wire an {@link OleEmbeddingResult} into a document so the OLE object is
193
+ * actually rendered and resolvable, returning a new {@link DocxDocument}.
194
+ *
195
+ * Unlike just stuffing the part into `opaqueParts` (which leaves the binary
196
+ * dangling — no relationship, no body reference), this:
197
+ *
198
+ * - registers the OLE binary (and optional preview) on
199
+ * `doc.oleObjects` so the packager emits a `word/_rels/document.xml.rels`
200
+ * relationship with the exact rId and a `[Content_Types].xml` override;
201
+ * - appends a body paragraph carrying a `<w:object>` / `<o:OLEObject>`
202
+ * that references the same rId and embeds the ProgId, so the object is
203
+ * visible in Word and round-trips through `readDocx`.
204
+ *
205
+ * @param doc - The document to add the OLE object to.
206
+ * @param embedding - Result from {@link createOleEmbedding}.
207
+ * @param options - Display geometry (defaults to a 2"×2" icon box).
208
+ */
209
+ function addOleObject(doc, embedding, options) {
210
+ const widthPt = options?.widthPt ?? 96;
211
+ const heightPt = options?.heightPt ?? 96;
212
+ const drawAspect = (options?.displayAs ?? "icon") === "icon" ? "Icon" : "Content";
213
+ const olePartEntry = {
214
+ path: embedding.olePart.path,
215
+ data: embedding.olePart.data,
216
+ rId: embedding.oleRId,
217
+ progId: embedding.progId,
218
+ contentType: embedding.olePart.contentType,
219
+ ...(embedding.previewPart && embedding.previewRId
220
+ ? {
221
+ previewPath: embedding.previewPart.path,
222
+ previewData: embedding.previewPart.data,
223
+ previewRId: embedding.previewRId,
224
+ previewContentType: embedding.previewPart.contentType
225
+ }
226
+ : {})
227
+ };
228
+ // Build the VML-hosted <w:object>. The o:OLEObject carries ProgID + the
229
+ // r:id of the binary; the v:shape provides geometry and (when present) the
230
+ // preview image fill via v:imagedata. This is the canonical OOXML shape for
231
+ // an embedded OLE object (ECMA-376 §17.3.3.19 + VML).
232
+ const shapeId = `_ole_${embedding.oleRId}`;
233
+ const styleWidth = widthPt.toFixed(0);
234
+ const styleHeight = heightPt.toFixed(0);
235
+ const imageData = embedding.previewRId != null
236
+ ? `<v:imagedata r:id="${(0, encode_1.xmlEncodeAttr)(embedding.previewRId)}" o:title=""/>`
237
+ : "";
238
+ const rawXml = `<w:object>` +
239
+ `<v:shape id="${(0, encode_1.xmlEncodeAttr)(shapeId)}" type="#_x0000_t75" ` +
240
+ `style="width:${styleWidth}pt;height:${styleHeight}pt">` +
241
+ imageData +
242
+ `</v:shape>` +
243
+ `<o:OLEObject Type="Embed" ProgID="${(0, encode_1.xmlEncodeAttr)(embedding.progId)}" ` +
244
+ `ShapeID="${(0, encode_1.xmlEncodeAttr)(shapeId)}" DrawAspect="${drawAspect}" ` +
245
+ `r:id="${(0, encode_1.xmlEncodeAttr)(embedding.oleRId)}"/>` +
246
+ `</w:object>`;
247
+ const referencedRIds = [embedding.oleRId];
248
+ if (embedding.previewRId != null) {
249
+ referencedRIds.push(embedding.previewRId);
250
+ }
251
+ const drawing = {
252
+ type: "opaqueDrawing",
253
+ rawXml,
254
+ referencedRIds
255
+ };
256
+ return {
257
+ ...doc,
258
+ body: [...doc.body, drawing],
259
+ oleObjects: [...(doc.oleObjects ?? []), olePartEntry]
260
+ };
160
261
  }
161
262
  /** Module-level counters used to allocate unique file names per call. */
162
263
  let _oleSeq = 0;
@@ -248,17 +349,20 @@ function tryDecodeAscii(data) {
248
349
  return str;
249
350
  }
250
351
  function extractOleFromRawXml(rawXml) {
251
- // Parse OLE object info from raw XML using regex (lightweight)
252
- const progIdMatch = rawXml.match(/ProgID="([^"]+)"/i) ?? rawXml.match(/progId="([^"]+)"/i);
253
- const rIdMatch = rawXml.match(/r:id="([^"]+)"/i);
254
- const typeMatch = rawXml.match(/Type="([^"]+)"/i);
352
+ // Pull metadata from the <o:OLEObject> element specifically, so a preview
353
+ // image's <v:imagedata r:id=""> earlier in the markup is not mistaken for
354
+ // the OLE binary's relationship id.
355
+ const oleTag = rawXml.match(/<o:OLEObject\b[^>]*>/i)?.[0] ?? rawXml;
356
+ const progIdMatch = oleTag.match(/ProgID="([^"]+)"/i) ?? oleTag.match(/progId="([^"]+)"/i);
357
+ const rIdMatch = oleTag.match(/r:id="([^"]+)"/i);
358
+ const typeMatch = oleTag.match(/Type="([^"]+)"/i);
255
359
  if (!progIdMatch) {
256
360
  return null;
257
361
  }
258
362
  const progId = progIdMatch[1];
259
363
  const rId = rIdMatch ? rIdMatch[1] : "";
260
364
  const objectType = typeMatch && typeMatch[1].toLowerCase().includes("link") ? "linked" : "embedded";
261
- // Extract dimensions
365
+ // Extract dimensions (from the surrounding shape, hence full rawXml)
262
366
  const widthMatch = rawXml.match(/(?:cx|width)="(\d+)"/i);
263
367
  const heightMatch = rawXml.match(/(?:cy|height)="(\d+)"/i);
264
368
  return {