@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
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @cj-tech-master/excelts v9.6.1
2
+ * @cj-tech-master/excelts v10.0.0
3
3
  * Zero-dependency TypeScript toolkit — Excel (XLSX), PDF, CSV, Markdown, XML, ZIP/TAR, and streaming.
4
4
  * (c) 2026 cjnoname
5
5
  * Released under the MIT License
@@ -17695,7 +17695,8 @@ onmessage = async (ev) => {
17695
17695
  set model(value) {
17696
17696
  const { note } = value;
17697
17697
  const { texts } = note;
17698
- if (texts && texts.length === 1 && Object.keys(texts[0]).length === 1) this.note = texts[0].text;
17698
+ const hasCustomSize = note.width !== void 0 || note.height !== void 0;
17699
+ if (texts && texts.length === 1 && Object.keys(texts[0]).length === 1 && !hasCustomSize) this.note = texts[0].text;
17699
17700
  else this.note = note;
17700
17701
  this.author = value.author;
17701
17702
  }
@@ -19865,6 +19866,30 @@ onmessage = async (ev) => {
19865
19866
  }));
19866
19867
  //#endregion
19867
19868
  //#region src/modules/excel/row.ts
19869
+ /**
19870
+ * Resolve a column key against a row object, supporting dotted nested paths.
19871
+ *
19872
+ * A key without a `.` takes the original fast path (`obj[key]`), preserving
19873
+ * exact backward compatibility — including keys that legitimately contain a
19874
+ * dot only as a flat property name, which still resolve via the fast path
19875
+ * first. A dotted key (e.g. `"address.city"`) is resolved by walking each
19876
+ * segment; if any segment is missing or not an object, the result is
19877
+ * `undefined` (the same signal the caller already uses to skip a cell).
19878
+ *
19879
+ * @param obj - The row object supplied to `row.values = {...}` / `addRow({...})`.
19880
+ * @param key - The column key, optionally a dotted path.
19881
+ * @returns The resolved value, or `undefined` when the path cannot be followed.
19882
+ */
19883
+ function resolveColumnKeyValue(obj, key) {
19884
+ const direct = obj[key];
19885
+ if (direct !== void 0 || !key.includes(".")) return direct;
19886
+ let current = obj;
19887
+ for (const segment of key.split(".")) {
19888
+ if (current === null || typeof current !== "object") return;
19889
+ current = current[segment];
19890
+ }
19891
+ return current;
19892
+ }
19868
19893
  var Row;
19869
19894
  var init_row = __esmMin((() => {
19870
19895
  init_cell();
@@ -20031,11 +20056,12 @@ onmessage = async (ev) => {
20031
20056
  }).value = item;
20032
20057
  });
20033
20058
  } else this._worksheet.eachColumnKey((column, key) => {
20034
- if (value[key] !== void 0) this.getCellEx({
20059
+ const resolved = resolveColumnKeyValue(value, key);
20060
+ if (resolved !== void 0) this.getCellEx({
20035
20061
  address: colCache.encodeAddress(this._number, column.number),
20036
20062
  row: this._number,
20037
20063
  col: column.number
20038
- }).value = value[key];
20064
+ }).value = resolved;
20039
20065
  });
20040
20066
  }
20041
20067
  /**
@@ -28148,11 +28174,13 @@ self.onmessage = async function(event) {
28148
28174
  }));
28149
28175
  //#endregion
28150
28176
  //#region src/modules/excel/xlsx/xform/comment/vml-shape-xform.ts
28151
- var VmlShapeXform;
28177
+ var DEFAULT_NOTE_WIDTH_PT, DEFAULT_NOTE_HEIGHT_PT, VmlShapeXform;
28152
28178
  var init_vml_shape_xform = __esmMin((() => {
28153
28179
  init_base_xform();
28154
28180
  init_vml_client_data_xform();
28155
28181
  init_vml_textbox_xform();
28182
+ DEFAULT_NOTE_WIDTH_PT = 97.8;
28183
+ DEFAULT_NOTE_HEIGHT_PT = 59.1;
28156
28184
  VmlShapeXform = class VmlShapeXform extends BaseXform {
28157
28185
  constructor() {
28158
28186
  super();
@@ -28190,6 +28218,13 @@ self.onmessage = async function(event) {
28190
28218
  editAs: "",
28191
28219
  protection: {}
28192
28220
  };
28221
+ {
28222
+ const style = node.attributes.style ?? "";
28223
+ const width = VmlShapeXform.parseStyleLength(style, "width");
28224
+ const height = VmlShapeXform.parseStyleLength(style, "height");
28225
+ if (width !== void 0 && width !== DEFAULT_NOTE_WIDTH_PT) this.model.width = width;
28226
+ if (height !== void 0 && height !== DEFAULT_NOTE_HEIGHT_PT) this.model.height = height;
28227
+ }
28193
28228
  break;
28194
28229
  default:
28195
28230
  this.parser = this.map[node.name];
@@ -28220,15 +28255,29 @@ self.onmessage = async function(event) {
28220
28255
  default: return true;
28221
28256
  }
28222
28257
  }
28258
+ /**
28259
+ * Extract a points-valued length (e.g. `width:120pt`) from a VML style
28260
+ * string. Returns `undefined` when the property is absent or not in `pt`.
28261
+ */
28262
+ static parseStyleLength(style, prop) {
28263
+ const match = new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([0-9.]+)pt`, "i").exec(style);
28264
+ if (!match) return;
28265
+ const value = parseFloat(match[1]);
28266
+ return Number.isFinite(value) ? value : void 0;
28267
+ }
28223
28268
  static {
28224
- this.V_SHAPE_ATTRIBUTES = (model, index) => ({
28225
- id: `_x0000_s${1025 + index}`,
28226
- type: "#_x0000_t202",
28227
- style: "position:absolute; margin-left:105.3pt;margin-top:10.5pt;width:97.8pt;height:59.1pt;z-index:1;visibility:hidden",
28228
- fillcolor: "infoBackground [80]",
28229
- strokecolor: "none [81]",
28230
- "o:insetmode": model.note.margins && model.note.margins.insetmode
28231
- });
28269
+ this.V_SHAPE_ATTRIBUTES = (model, index) => {
28270
+ const width = model.note?.width ?? DEFAULT_NOTE_WIDTH_PT;
28271
+ const height = model.note?.height ?? DEFAULT_NOTE_HEIGHT_PT;
28272
+ return {
28273
+ id: `_x0000_s${1025 + index}`,
28274
+ type: "#_x0000_t202",
28275
+ style: `position:absolute; margin-left:105.3pt;margin-top:10.5pt;width:${width}pt;height:${height}pt;z-index:1;visibility:hidden`,
28276
+ fillcolor: "infoBackground [80]",
28277
+ strokecolor: "none [81]",
28278
+ "o:insetmode": model.note.margins && model.note.margins.insetmode
28279
+ };
28280
+ };
28232
28281
  }
28233
28282
  };
28234
28283
  }));
@@ -28549,6 +28598,19 @@ self.onmessage = async function(event) {
28549
28598
  },
28550
28599
  range: medium.range
28551
28600
  };
28601
+ if (bookImage.svgMediaId !== void 0) {
28602
+ const svgKey = `svg:${bookImage.svgMediaId}`;
28603
+ let rIdSvg = imageRIdMap[svgKey];
28604
+ if (!rIdSvg) {
28605
+ const svgImage = options.getBookImage(bookImage.svgMediaId);
28606
+ if (svgImage) {
28607
+ rIdSvg = options.nextRId(rels);
28608
+ imageRIdMap[svgKey] = rIdSvg;
28609
+ rels.push(buildImageRel(rIdSvg, svgImage));
28610
+ }
28611
+ }
28612
+ if (rIdSvg) anchor.picture.svgRId = rIdSvg;
28613
+ }
28552
28614
  if (medium.opacity !== void 0) {
28553
28615
  const clamped = Math.max(0, Math.min(1, medium.opacity));
28554
28616
  anchor.picture.alphaModFix = Math.round(clamped * 1e5);
@@ -28584,7 +28646,7 @@ self.onmessage = async function(event) {
28584
28646
  if (a == null) return false;
28585
28647
  if (a.range?.pos !== void 0) return !!a.picture || !!a.graphicFrame || !!a.shape;
28586
28648
  if (a.range?.br && a.shape) return true;
28587
- if (!a.range?.br && !a.picture && !a.graphicFrame) return false;
28649
+ if (!a.range?.br && !a.picture && !a.graphicFrame && !a.shape) return false;
28588
28650
  if (a.range?.br && !a.picture && !a.shape && !a.graphicFrame) return false;
28589
28651
  return true;
28590
28652
  });
@@ -33201,7 +33263,7 @@ self.onmessage = async function(event) {
33201
33263
  mediaHash[imageType] = true;
33202
33264
  xmlStream.leafNode("Default", {
33203
33265
  Extension: imageType,
33204
- ContentType: `image/${imageType}`
33266
+ ContentType: imageType === "svg" ? "image/svg+xml" : `image/${imageType}`
33205
33267
  });
33206
33268
  }
33207
33269
  }
@@ -33634,7 +33696,16 @@ self.onmessage = async function(event) {
33634
33696
  const name = match[1];
33635
33697
  const mediaId = options.mediaIndex[name];
33636
33698
  const medium = options.media[mediaId];
33637
- if (medium && model.alphaModFix !== void 0) return {
33699
+ if (!medium) return;
33700
+ if (model.svgRId) {
33701
+ const svgRel = options.rels[model.svgRId];
33702
+ const svgMatch = svgRel && svgRel.Target.match(/.*\/media\/(.+[.][a-zA-Z]{3,4})/);
33703
+ if (svgMatch) {
33704
+ const svgMediaId = options.mediaIndex[svgMatch[1]];
33705
+ if (svgMediaId !== void 0) medium.svgMediaId = svgMediaId;
33706
+ }
33707
+ }
33708
+ if (model.alphaModFix !== void 0) return {
33638
33709
  ...medium,
33639
33710
  alphaModFix: model.alphaModFix
33640
33711
  };
@@ -33873,9 +33944,12 @@ self.onmessage = async function(event) {
33873
33944
  }));
33874
33945
  //#endregion
33875
33946
  //#region src/modules/excel/xlsx/xform/drawing/blip-xform.ts
33876
- var BlipXform;
33947
+ var SVG_BLIP_EXT_URI, SVG_BLIP_NS, REL_NS, BlipXform;
33877
33948
  var init_blip_xform = __esmMin((() => {
33878
33949
  init_base_xform();
33950
+ SVG_BLIP_EXT_URI = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}";
33951
+ SVG_BLIP_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main";
33952
+ REL_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
33879
33953
  BlipXform = class extends BaseXform {
33880
33954
  constructor() {
33881
33955
  super();
@@ -33886,19 +33960,33 @@ self.onmessage = async function(event) {
33886
33960
  }
33887
33961
  render(xmlStream, model) {
33888
33962
  const relAttr = model.external ? "r:link" : "r:embed";
33889
- if (model.alphaModFix !== void 0 && model.alphaModFix < 1e5) {
33890
- xmlStream.openNode(this.tag, {
33891
- "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
33963
+ const hasAlpha = model.alphaModFix !== void 0 && model.alphaModFix < 1e5;
33964
+ const hasSvg = model.svgRId !== void 0;
33965
+ if (!hasAlpha && !hasSvg) {
33966
+ xmlStream.leafNode(this.tag, {
33967
+ "xmlns:r": REL_NS,
33892
33968
  [relAttr]: model.rId,
33893
33969
  cstate: "print"
33894
33970
  });
33895
- xmlStream.leafNode("a:alphaModFix", { amt: String(model.alphaModFix) });
33896
- xmlStream.closeNode();
33897
- } else xmlStream.leafNode(this.tag, {
33898
- "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
33971
+ return;
33972
+ }
33973
+ xmlStream.openNode(this.tag, {
33974
+ "xmlns:r": REL_NS,
33899
33975
  [relAttr]: model.rId,
33900
33976
  cstate: "print"
33901
33977
  });
33978
+ if (hasAlpha) xmlStream.leafNode("a:alphaModFix", { amt: String(model.alphaModFix) });
33979
+ if (hasSvg) {
33980
+ xmlStream.openNode("a:extLst");
33981
+ xmlStream.openNode("a:ext", { uri: SVG_BLIP_EXT_URI });
33982
+ xmlStream.leafNode("asvg:svgBlip", {
33983
+ "xmlns:asvg": SVG_BLIP_NS,
33984
+ "r:embed": model.svgRId
33985
+ });
33986
+ xmlStream.closeNode();
33987
+ xmlStream.closeNode();
33988
+ }
33989
+ xmlStream.closeNode();
33902
33990
  }
33903
33991
  parseOpen(node) {
33904
33992
  switch (node.name) {
@@ -33914,6 +34002,11 @@ self.onmessage = async function(event) {
33914
34002
  case "a:alphaModFix":
33915
34003
  if (node.attributes.amt) this.model.alphaModFix = parseInt(node.attributes.amt, 10);
33916
34004
  return true;
34005
+ case "asvg:svgBlip": {
34006
+ const embed = node.attributes["r:embed"];
34007
+ if (embed !== void 0 && this.model) this.model.svgRId = embed;
34008
+ return true;
34009
+ }
33917
34010
  default: return true;
33918
34011
  }
33919
34012
  }
@@ -34251,7 +34344,8 @@ self.onmessage = async function(event) {
34251
34344
  this.map["xdr:blipFill"].render(xmlStream, {
34252
34345
  rId: model.rId,
34253
34346
  alphaModFix: model.alphaModFix,
34254
- external: model.external
34347
+ external: model.external,
34348
+ svgRId: model.svgRId
34255
34349
  });
34256
34350
  this.map["xdr:spPr"].render(xmlStream, model);
34257
34351
  xmlStream.closeNode();
@@ -34289,6 +34383,105 @@ self.onmessage = async function(event) {
34289
34383
  };
34290
34384
  }));
34291
34385
  //#endregion
34386
+ //#region src/modules/excel/xlsx/xform/drawing/shape-xform.ts
34387
+ /**
34388
+ * Normalize a user-supplied colour to the bare 6-digit RGB hex that OOXML's
34389
+ * `<a:srgbClr val="...">` requires:
34390
+ * - strips a leading `#`
34391
+ * - accepts 8-digit ARGB (the form excelts uses for cell fills) and drops the
34392
+ * leading alpha byte, since `srgbClr` carries no alpha channel
34393
+ * - upper-cases
34394
+ *
34395
+ * Anything that isn't a 6- or 8-digit hex string is passed through unchanged so
34396
+ * a caller using a less common form is not silently broken.
34397
+ */
34398
+ function normalizeColor(color) {
34399
+ const hex = color.startsWith("#") ? color.slice(1) : color;
34400
+ if (/^[0-9a-fA-F]{8}$/.test(hex)) return hex.slice(2).toUpperCase();
34401
+ if (/^[0-9a-fA-F]{6}$/.test(hex)) return hex.toUpperCase();
34402
+ return hex;
34403
+ }
34404
+ var EMU_PER_POINT$1, ShapeXform;
34405
+ var init_shape_xform = __esmMin((() => {
34406
+ init_base_xform();
34407
+ EMU_PER_POINT$1 = 12700;
34408
+ ShapeXform = class extends BaseXform {
34409
+ get tag() {
34410
+ return "xdr:sp";
34411
+ }
34412
+ render(xmlStream, model) {
34413
+ xmlStream.openNode("xdr:sp", {
34414
+ macro: "",
34415
+ textlink: ""
34416
+ });
34417
+ xmlStream.openNode("xdr:nvSpPr");
34418
+ xmlStream.leafNode("xdr:cNvPr", {
34419
+ id: model.cNvPrId,
34420
+ name: model.name
34421
+ });
34422
+ xmlStream.leafNode("xdr:cNvSpPr", {});
34423
+ xmlStream.closeNode();
34424
+ xmlStream.openNode("xdr:spPr");
34425
+ xmlStream.openNode("a:xfrm");
34426
+ xmlStream.leafNode("a:off", {
34427
+ x: 0,
34428
+ y: 0
34429
+ });
34430
+ xmlStream.leafNode("a:ext", {
34431
+ cx: 0,
34432
+ cy: 0
34433
+ });
34434
+ xmlStream.closeNode();
34435
+ xmlStream.openNode("a:prstGeom", { prst: model.shapeType });
34436
+ xmlStream.leafNode("a:avLst");
34437
+ xmlStream.closeNode();
34438
+ if (model.fill && model.fill.color) {
34439
+ xmlStream.openNode("a:solidFill");
34440
+ xmlStream.leafNode("a:srgbClr", { val: normalizeColor(model.fill.color) });
34441
+ xmlStream.closeNode();
34442
+ } else xmlStream.leafNode("a:noFill");
34443
+ if (model.line && (model.line.color || model.line.width !== void 0)) {
34444
+ const lnAttrs = {};
34445
+ if (model.line.width !== void 0) lnAttrs.w = Math.round(model.line.width * EMU_PER_POINT$1);
34446
+ xmlStream.openNode("a:ln", lnAttrs);
34447
+ if (model.line.color) {
34448
+ xmlStream.openNode("a:solidFill");
34449
+ xmlStream.leafNode("a:srgbClr", { val: normalizeColor(model.line.color) });
34450
+ xmlStream.closeNode();
34451
+ }
34452
+ xmlStream.closeNode();
34453
+ } else {
34454
+ xmlStream.openNode("a:ln");
34455
+ xmlStream.leafNode("a:noFill");
34456
+ xmlStream.closeNode();
34457
+ }
34458
+ xmlStream.closeNode();
34459
+ xmlStream.openNode("xdr:txBody");
34460
+ xmlStream.leafNode("a:bodyPr", {
34461
+ vertOverflow: "clip",
34462
+ wrap: "square",
34463
+ anchor: "ctr"
34464
+ });
34465
+ xmlStream.leafNode("a:lstStyle");
34466
+ xmlStream.openNode("a:p");
34467
+ xmlStream.openNode("a:pPr", { algn: "ctr" });
34468
+ xmlStream.closeNode();
34469
+ if (model.text) {
34470
+ xmlStream.openNode("a:r");
34471
+ xmlStream.openNode("a:rPr", { lang: "en-US" });
34472
+ xmlStream.closeNode();
34473
+ xmlStream.openNode("a:t");
34474
+ xmlStream.writeText(model.text);
34475
+ xmlStream.closeNode();
34476
+ xmlStream.closeNode();
34477
+ } else xmlStream.leafNode("a:endParaRPr", { lang: "en-US" });
34478
+ xmlStream.closeNode();
34479
+ xmlStream.closeNode();
34480
+ xmlStream.closeNode();
34481
+ }
34482
+ };
34483
+ }));
34484
+ //#endregion
34292
34485
  //#region src/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.ts
34293
34486
  var EMU_PER_PIXEL_AT_96_DPI, PosXform, AbsoluteAnchorXform;
34294
34487
  var init_absolute_anchor_xform = __esmMin((() => {
@@ -34297,6 +34490,7 @@ self.onmessage = async function(event) {
34297
34490
  init_ext_xform();
34298
34491
  init_graphic_frame_xform();
34299
34492
  init_pic_xform();
34493
+ init_shape_xform();
34300
34494
  init_static_xform();
34301
34495
  EMU_PER_PIXEL_AT_96_DPI = 9525;
34302
34496
  PosXform = class extends BaseXform {
@@ -34340,6 +34534,7 @@ self.onmessage = async function(event) {
34340
34534
  "xdr:ext": new ExtXform({ tag: "xdr:ext" }),
34341
34535
  "xdr:pic": new PicXform(),
34342
34536
  "xdr:graphicFrame": new GraphicFrameXform(),
34537
+ "xdr:userShape": new ShapeXform(),
34343
34538
  "xdr:clientData": new StaticXform({ tag: "xdr:clientData" })
34344
34539
  };
34345
34540
  }
@@ -34359,6 +34554,7 @@ self.onmessage = async function(event) {
34359
34554
  this.map["xdr:ext"].render(xmlStream, model.range.ext);
34360
34555
  if (model.picture) this.map["xdr:pic"].render(xmlStream, model.picture);
34361
34556
  else if (model.graphicFrame) this.map["xdr:graphicFrame"].render(xmlStream, model.graphicFrame);
34557
+ else if (model.shape?.kind === "userShape") this.map["xdr:userShape"].render(xmlStream, model.shape);
34362
34558
  this.map["xdr:clientData"].render(xmlStream, {});
34363
34559
  xmlStream.closeNode();
34364
34560
  }
@@ -34472,6 +34668,7 @@ self.onmessage = async function(event) {
34472
34668
  init_ext_xform();
34473
34669
  init_graphic_frame_xform();
34474
34670
  init_pic_xform();
34671
+ init_shape_xform();
34475
34672
  init_static_xform();
34476
34673
  OneCellAnchorXform = class extends BaseCellAnchorXform {
34477
34674
  constructor() {
@@ -34480,6 +34677,7 @@ self.onmessage = async function(event) {
34480
34677
  "xdr:from": new CellPositionXform({ tag: "xdr:from" }),
34481
34678
  "xdr:ext": new ExtXform({ tag: "xdr:ext" }),
34482
34679
  "xdr:pic": new PicXform(),
34680
+ "xdr:userShape": new ShapeXform(),
34483
34681
  "xdr:graphicFrame": new GraphicFrameXform(),
34484
34682
  "xdr:clientData": new StaticXform({ tag: "xdr:clientData" })
34485
34683
  };
@@ -34497,6 +34695,7 @@ self.onmessage = async function(event) {
34497
34695
  this.map["xdr:ext"].render(xmlStream, model.range.ext);
34498
34696
  if (model.picture) this.map["xdr:pic"].render(xmlStream, model.picture);
34499
34697
  else if (model.graphicFrame) this.map["xdr:graphicFrame"].render(xmlStream, model.graphicFrame);
34698
+ else if (model.shape?.kind === "userShape") this.map["xdr:userShape"].render(xmlStream, model.shape);
34500
34699
  this.map["xdr:clientData"].render(xmlStream, {});
34501
34700
  xmlStream.closeNode();
34502
34701
  }
@@ -34659,6 +34858,7 @@ self.onmessage = async function(event) {
34659
34858
  init_cell_position_xform();
34660
34859
  init_graphic_frame_xform();
34661
34860
  init_pic_xform();
34861
+ init_shape_xform();
34662
34862
  init_sp_xform();
34663
34863
  init_static_xform();
34664
34864
  TwoCellAnchorXform = class extends BaseCellAnchorXform {
@@ -34673,6 +34873,7 @@ self.onmessage = async function(event) {
34673
34873
  "xdr:to": new CellPositionXform({ tag: "xdr:to" }),
34674
34874
  "xdr:pic": new PicXform(),
34675
34875
  "xdr:sp": new SpXform(),
34876
+ "xdr:userShape": new ShapeXform(),
34676
34877
  "xdr:graphicFrame": new GraphicFrameXform(),
34677
34878
  "xdr:clientData": new StaticXform({ tag: "xdr:clientData" })
34678
34879
  };
@@ -34703,7 +34904,8 @@ self.onmessage = async function(event) {
34703
34904
  if (isChartEx) this.renderChartExAlternateContent(xmlStream, model);
34704
34905
  else if (model.picture) this.map["xdr:pic"].render(xmlStream, model.picture);
34705
34906
  else if (model.graphicFrame) this.map["xdr:graphicFrame"].render(xmlStream, model.graphicFrame);
34706
- else if (model.shape) this.map["xdr:sp"].render(xmlStream, model.shape);
34907
+ else if (model.shape) if (model.shape.kind === "userShape") this.map["xdr:userShape"].render(xmlStream, model.shape);
34908
+ else this.map["xdr:sp"].render(xmlStream, model.shape);
34707
34909
  this.map["xdr:clientData"].render(xmlStream, {});
34708
34910
  xmlStream.closeNode();
34709
34911
  if (wrapAnchorInAc) {
@@ -35561,13 +35763,25 @@ self.onmessage = async function(event) {
35561
35763
  * ```
35562
35764
  */
35563
35765
  addImage(image) {
35766
+ const { svg, ...raster } = image;
35767
+ if (svg && raster.link && raster.buffer == null && raster.base64 == null && raster.filename == null) throw new ImageError("An SVG image requires an embedded raster fallback (buffer/base64/filename); it cannot be combined with an external link.");
35564
35768
  const id = this.media.length;
35565
35769
  const medium = {
35566
- ...image,
35770
+ ...raster,
35567
35771
  type: "image",
35568
- name: `image${id}.${image.extension}`
35772
+ name: `image${id}.${raster.extension}`
35569
35773
  };
35570
35774
  this.media.push(medium);
35775
+ if (svg) {
35776
+ const svgId = this.media.length;
35777
+ this.media.push({
35778
+ ...svg,
35779
+ type: "image",
35780
+ extension: "svg",
35781
+ name: `image${svgId}.svg`
35782
+ });
35783
+ medium.svgMediaId = svgId;
35784
+ }
35571
35785
  return id;
35572
35786
  }
35573
35787
  getImage(id) {
@@ -41539,6 +41753,7 @@ self.onmessage = async function(event) {
41539
41753
  this.views = options.views ?? [];
41540
41754
  this.autoFilter = options.autoFilter ?? null;
41541
41755
  this._media = [];
41756
+ this._shapes = [];
41542
41757
  this._charts = [];
41543
41758
  this._sparklineGroups = [];
41544
41759
  this.sheetProtection = null;
@@ -42139,6 +42354,74 @@ self.onmessage = async function(event) {
42139
42354
  };
42140
42355
  this._media.push(new Image(this, model));
42141
42356
  }
42357
+ /**
42358
+ * Add a free-form drawing shape (rectangle, ellipse, line, text box, …) to
42359
+ * the worksheet, anchored to a cell range.
42360
+ *
42361
+ * Unlike images, shapes need no media file — the geometry, fill, outline and
42362
+ * optional text label are written directly into the drawing part.
42363
+ *
42364
+ * @example
42365
+ * ```typescript
42366
+ * worksheet.addShape({
42367
+ * type: "rect",
42368
+ * range: "B2:D5",
42369
+ * fillColor: "FFD966",
42370
+ * lineColor: "000000",
42371
+ * lineWidth: 1,
42372
+ * text: "Important"
42373
+ * });
42374
+ * ```
42375
+ */
42376
+ addShape(options) {
42377
+ const range = options.range;
42378
+ if (!(typeof range === "string" && range.includes(":") || typeof range === "object" && range !== null && ("br" in range || "ext" in range || "pos" in range))) throw new ImageError("addShape requires a range covering an area: a cell range like \"B2:D5\", or an object with `br`, `ext`, or `pos`.");
42379
+ this._shapes.push({
42380
+ type: "shape",
42381
+ shapeType: options.type ?? "rect",
42382
+ range,
42383
+ fillColor: options.fillColor,
42384
+ lineColor: options.lineColor,
42385
+ lineWidth: options.lineWidth,
42386
+ text: options.text,
42387
+ name: options.name
42388
+ });
42389
+ }
42390
+ /** All shapes added to this worksheet. */
42391
+ getShapes() {
42392
+ return this._shapes.slice();
42393
+ }
42394
+ /**
42395
+ * Resolve a shape's `range` into concrete two-cell anchor coordinates,
42396
+ * reusing the `Image` range parser so cell-address/anchor handling stays in
42397
+ * one place. Returns a serializable ShapeModel for the worksheet xform.
42398
+ */
42399
+ _resolveShapeModel(shape) {
42400
+ let range;
42401
+ try {
42402
+ range = new Image(this, {
42403
+ type: "image",
42404
+ imageId: "",
42405
+ range: shape.range
42406
+ }).model.range;
42407
+ } catch {
42408
+ range = void 0;
42409
+ }
42410
+ if (!range) return {
42411
+ ...shape,
42412
+ anchorRange: void 0
42413
+ };
42414
+ return {
42415
+ ...shape,
42416
+ anchorRange: {
42417
+ tl: range.tl,
42418
+ br: range.br,
42419
+ ext: range.ext,
42420
+ pos: range.pos,
42421
+ editAs: range.editAs
42422
+ }
42423
+ };
42424
+ }
42142
42425
  getImages() {
42143
42426
  return this._media.filter((m) => m.type === "image");
42144
42427
  }
@@ -42787,6 +43070,7 @@ self.onmessage = async function(event) {
42787
43070
  views: this.views,
42788
43071
  autoFilter: this.autoFilter,
42789
43072
  media: this._media.map((medium) => medium.model),
43073
+ shapes: this._shapes.map((shape) => this._resolveShapeModel(shape)),
42790
43074
  sheetProtection: this.sheetProtection,
42791
43075
  tables: Object.values(this.tables).map((table) => table.model),
42792
43076
  pivotTables: this.pivotTables,
@@ -42840,6 +43124,7 @@ self.onmessage = async function(event) {
42840
43124
  this.views = value.views;
42841
43125
  this.autoFilter = value.autoFilter;
42842
43126
  this._media = value.media.map((medium) => new Image(this, medium));
43127
+ this._shapes = value.shapes ? value.shapes.slice() : [];
42843
43128
  this._watermark = value.watermark ?? null;
42844
43129
  if (!this._watermark) {
42845
43130
  for (const medium of this._media) if (medium.type === "watermark") {
@@ -51335,6 +51620,60 @@ self.onmessage = async function(event) {
51335
51620
  });
51336
51621
  }
51337
51622
  }
51623
+ const shapes = model.shapes ?? [];
51624
+ if (shapes.length > 0) {
51625
+ let { drawing } = model;
51626
+ if (!drawing) {
51627
+ drawing = model.drawing = {
51628
+ rId: nextRid(rels),
51629
+ name: `drawing${++options.drawingsCount}`,
51630
+ anchors: [],
51631
+ rels: []
51632
+ };
51633
+ options.drawings.push(drawing);
51634
+ rels.push({
51635
+ Id: drawing.rId,
51636
+ Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
51637
+ Target: drawingRelTargetFromWorksheet(drawing.name)
51638
+ });
51639
+ }
51640
+ for (const shape of shapes) {
51641
+ const anchorRange = shape.anchorRange;
51642
+ if (!anchorRange) continue;
51643
+ let range;
51644
+ if (anchorRange.pos) range = {
51645
+ pos: anchorRange.pos,
51646
+ ext: anchorRange.ext,
51647
+ editAs: "absolute"
51648
+ };
51649
+ else if (anchorRange.br) range = {
51650
+ tl: anchorRange.tl,
51651
+ br: anchorRange.br,
51652
+ editAs: anchorRange.editAs ?? "oneCell"
51653
+ };
51654
+ else range = {
51655
+ tl: anchorRange.tl,
51656
+ ext: anchorRange.ext,
51657
+ editAs: anchorRange.editAs ?? "oneCell"
51658
+ };
51659
+ const cNvPrId = drawing.anchors.length + 1;
51660
+ drawing.anchors.push({
51661
+ range,
51662
+ shape: {
51663
+ kind: "userShape",
51664
+ cNvPrId,
51665
+ name: shape.name ?? `Shape ${cNvPrId}`,
51666
+ shapeType: shape.shapeType,
51667
+ fill: shape.fillColor ? { color: shape.fillColor } : void 0,
51668
+ line: shape.lineColor !== void 0 || shape.lineWidth !== void 0 ? {
51669
+ color: shape.lineColor,
51670
+ width: shape.lineWidth
51671
+ } : void 0,
51672
+ text: shape.text
51673
+ }
51674
+ });
51675
+ }
51676
+ }
51338
51677
  if (headerImageMedia.length > 0) {
51339
51678
  const medium = headerImageMedia[0];
51340
51679
  const bookImage = options.media[medium.imageId];
@@ -58425,13 +58764,35 @@ self.onmessage = async function(event) {
58425
58764
  * const id = workbook.addImage({ extension: "png", link: "https://example.com/logo.png" });
58426
58765
  * worksheet.addImage(id, "B2:D6");
58427
58766
  * ```
58767
+ *
58768
+ * @example SVG with raster fallback — crisp in modern Excel, safe everywhere
58769
+ * ```typescript
58770
+ * const id = workbook.addImage({
58771
+ * buffer: pngFallbackBytes, // shown by older Excel / non-SVG consumers
58772
+ * extension: "png",
58773
+ * svg: { buffer: svgBytes } // shown by Excel 2016+
58774
+ * });
58775
+ * worksheet.addImage(id, "B2:D6");
58776
+ * ```
58428
58777
  */
58429
58778
  addImage(image) {
58779
+ const { svg, ...raster } = image;
58780
+ if (svg && raster.link && raster.buffer == null && raster.base64 == null && raster.filename == null) throw new ImageError("An SVG image requires an embedded raster fallback (buffer/base64/filename); it cannot be combined with an external link.");
58430
58781
  const id = this.media.length;
58431
- this.media.push({
58432
- ...image,
58782
+ const rasterMedia = {
58783
+ ...raster,
58433
58784
  type: "image"
58434
- });
58785
+ };
58786
+ this.media.push(rasterMedia);
58787
+ if (svg) {
58788
+ const svgId = this.media.length;
58789
+ this.media.push({
58790
+ ...svg,
58791
+ type: "image",
58792
+ extension: "svg"
58793
+ });
58794
+ rasterMedia.svgMediaId = svgId;
58795
+ }
58435
58796
  return id;
58436
58797
  }
58437
58798
  getImage(id) {
@@ -60675,6 +61036,17 @@ self.onmessage = async function(event) {
60675
61036
  return this;
60676
61037
  }
60677
61038
  /**
61039
+ * Append a deferred fragment whose body is evaluated only at serialization
61040
+ * time. Used by text drawing so the final byte encoding can be chosen after
61041
+ * the document's fonts are resolved at build time. The fragment occupies its
61042
+ * draw-order slot immediately, so z-order relative to other operators is
61043
+ * preserved.
61044
+ */
61045
+ deferred(produce) {
61046
+ this.parts.push(produce);
61047
+ return this;
61048
+ }
61049
+ /**
60678
61050
  * Save the current graphics state (push onto state stack).
60679
61051
  * Must be balanced with a corresponding restore().
60680
61052
  */
@@ -61033,10 +61405,23 @@ self.onmessage = async function(event) {
61033
61405
  return this;
61034
61406
  }
61035
61407
  /**
61036
- * Get the content stream as a string.
61408
+ * Whether any fragment has been appended. Unlike `toString().length > 0`,
61409
+ * this does NOT evaluate deferred fragments, so it is safe to call before
61410
+ * fonts are resolved (e.g. when probing for overlay content during an
61411
+ * editor save, prior to `writeFontResources`). A deferred text fragment
61412
+ * counts as content even though its bytes are not produced yet.
61413
+ */
61414
+ hasContent() {
61415
+ return this.parts.length > 0;
61416
+ }
61417
+ /**
61418
+ * Get the content stream as a string. Deferred fragments (see `deferred`)
61419
+ * are evaluated here, after font resolution has completed at build time.
61037
61420
  */
61038
61421
  toString() {
61039
- return this.parts.join("\n");
61422
+ const out = [];
61423
+ for (const part of this.parts) out.push(typeof part === "function" ? part() : part);
61424
+ return out.join("\n");
61040
61425
  }
61041
61426
  /**
61042
61427
  * Get the content stream as a Uint8Array (UTF-8 encoded).
@@ -87314,18 +87699,42 @@ self.onmessage = async function(event) {
87314
87699
  return this.embeddedResourceName;
87315
87700
  }
87316
87701
  /**
87702
+ * Resolve the resource name a draw-time-resolved Type1 resource should
87703
+ * actually render (and be measured) with, given the font manager's
87704
+ * *current* state. If an embedded font exists (possibly auto-discovered
87705
+ * at build time, after the text was drawn against a Type1 resource), the
87706
+ * embedded resource name is returned so both measurement and encoding go
87707
+ * through the CIDFont. Otherwise the original Type1 resource name is kept;
87708
+ * `measureText` handles Type3-fallback widths internally from that name.
87709
+ *
87710
+ * Centralises the routing rule shared by the deferred text renderer and
87711
+ * any deferred measurement (anchor alignment, word wrapping) so the two
87712
+ * never disagree.
87713
+ */
87714
+ resolveRenderResourceName(type1ResourceName) {
87715
+ return this.embeddedFont ? this.embeddedResourceName : type1ResourceName;
87716
+ }
87717
+ /**
87317
87718
  * Record that a text string will be rendered, tracking its code points.
87318
87719
  * Must be called for every text string before writing the PDF.
87720
+ *
87721
+ * Two sets are maintained because font selection may be decided *after*
87722
+ * drawing (e.g. `PdfDocumentBuilder.build()` auto-discovers and embeds a
87723
+ * system font once it sees the accumulated non-WinAnsi code points):
87724
+ *
87725
+ * - `usedCodePoints` — every code point seen, always. If an embedded
87726
+ * font ends up being used (whether registered up front or
87727
+ * auto-discovered at build time), the subset must cover all of these,
87728
+ * including plain ASCII, so the CIDFont can encode the full run.
87729
+ * - `type3CodePoints` — non-WinAnsi code points only. Drives the
87730
+ * build-time decision to auto-embed a system font, and the Type3
87731
+ * fallback when none is available.
87319
87732
  */
87320
87733
  trackText(text) {
87321
- if (this.embeddedFont) for (let i = 0; i < text.length; i++) {
87322
- const cp = text.codePointAt(i);
87323
- this.usedCodePoints.add(cp);
87324
- if (cp > 65535) i++;
87325
- }
87326
- else for (let i = 0; i < text.length; i++) {
87734
+ for (let i = 0; i < text.length; i++) {
87327
87735
  const cp = text.codePointAt(i);
87328
87736
  if (cp > 65535) i++;
87737
+ this.usedCodePoints.add(cp);
87329
87738
  if (!isWinAnsiCodePoint(cp)) this.type3CodePoints.add(cp);
87330
87739
  }
87331
87740
  }
@@ -88540,7 +88949,7 @@ self.onmessage = async function(event) {
88540
88949
  emitTextWithMatrix(stream, line, cos, sin, -sin, cos, tx, ty, resourceName, fontSize, fontManager, useType3);
88541
88950
  }
88542
88951
  }
88543
- /** Emit a text string with hex encoding if available. */
88952
+ /** Emit a text string with hex encoding if available, onto a sink stream. */
88544
88953
  function emitText(stream, fontManager, text, resourceName) {
88545
88954
  const hex = fontManager.encodeText(text, resourceName);
88546
88955
  if (hex) stream.showTextHex(hex);
@@ -88551,37 +88960,65 @@ self.onmessage = async function(event) {
88551
88960
  * when needed. For each sub-run the matrix origin is advanced along the
88552
88961
  * text direction (cos, sin) by the rendered width.
88553
88962
  *
88554
- * When `useType3` is false this collapses to a single BT/ET pair — identical
88555
- * to the old `emitText()` path but wrapped in begin/end for convenience.
88556
- */
88557
- function emitTextWithMatrix(stream, text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager, useType3) {
88558
- if (!useType3) {
88559
- stream.beginText();
88560
- stream.setFont(type1ResourceName, fontSize);
88561
- stream.setTextMatrix(a, b, c, d, tx, ty);
88562
- emitText(stream, fontManager, text, type1ResourceName);
88563
- stream.endText();
88564
- return;
88963
+ * The emitted operators are written as a *deferred* fragment (see
88964
+ * `PdfContentStream.deferred`). The fragment is only evaluated at
88965
+ * serialization time, by which point `PdfDocumentBuilder.build()` has
88966
+ * finalised the document's fonts (auto-discovered embedded CIDFont,
88967
+ * Type3 fallback, or plain Type1). This is essential: at draw time the
88968
+ * font manager has not yet decided whether a non-WinAnsi code point (e.g.
88969
+ * U+2192 →) will be served by an embedded font or a Type3 glyph, so eager
88970
+ * encoding would irreversibly degrade those characters to spaces via the
88971
+ * WinAnsi fallback. Deferring the encode keeps the fragment at its exact
88972
+ * draw-order slot (preserving z-order) while choosing the correct bytes
88973
+ * once fonts are known.
88974
+ *
88975
+ * The `useType3` argument is the caller's *draw-time* guess and is ignored;
88976
+ * the deferred body recomputes the routing from the now-settled font
88977
+ * manager state.
88978
+ */
88979
+ function emitTextWithMatrix(stream, text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager, _useType3) {
88980
+ stream.deferred(() => renderTextBlock(text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager));
88981
+ }
88982
+ /**
88983
+ * Produce the PDF operator string for a positioned text run, choosing the
88984
+ * encoding from the font manager's *current* (build-time) state:
88985
+ * - embedded font → single BT/ET with CIDFont hex encoding
88986
+ * - Type3 fallback → split into WinAnsi (Type1) and per-glyph Type3 runs
88987
+ * - neither → single BT/ET with Type1/WinAnsi encoding
88988
+ *
88989
+ * Must only be called after font resolution (i.e. from a deferred fragment).
88990
+ */
88991
+ function renderTextBlock(text, a, b, c, d, tx, ty, type1ResourceName, fontSize, fontManager) {
88992
+ const sink = new PdfContentStream();
88993
+ if (!(fontManager.hasType3Fonts() && !fontManager.hasEmbeddedFont())) {
88994
+ const resourceName = fontManager.resolveRenderResourceName(type1ResourceName);
88995
+ sink.beginText();
88996
+ sink.setFont(resourceName, fontSize);
88997
+ sink.setTextMatrix(a, b, c, d, tx, ty);
88998
+ emitText(sink, fontManager, text, resourceName);
88999
+ sink.endText();
89000
+ return sink.toString();
88565
89001
  }
88566
89002
  const runs = splitTextRuns(text, fontManager);
88567
89003
  let curTx = tx;
88568
89004
  let curTy = ty;
88569
89005
  for (const run of runs) {
88570
- stream.beginText();
89006
+ sink.beginText();
88571
89007
  if (run.type3) {
88572
- stream.setFont(run.type3.resourceName, fontSize);
88573
- stream.setTextMatrix(a, b, c, d, curTx, curTy);
88574
- stream.showTextHex(run.type3.hex);
89008
+ sink.setFont(run.type3.resourceName, fontSize);
89009
+ sink.setTextMatrix(a, b, c, d, curTx, curTy);
89010
+ sink.showTextHex(run.type3.hex);
88575
89011
  } else {
88576
- stream.setFont(type1ResourceName, fontSize);
88577
- stream.setTextMatrix(a, b, c, d, curTx, curTy);
88578
- emitText(stream, fontManager, run.text, type1ResourceName);
89012
+ sink.setFont(type1ResourceName, fontSize);
89013
+ sink.setTextMatrix(a, b, c, d, curTx, curTy);
89014
+ emitText(sink, fontManager, run.text, type1ResourceName);
88579
89015
  }
88580
- stream.endText();
89016
+ sink.endText();
88581
89017
  const w = fontManager.measureText(run.text, run.type3?.resourceName ?? type1ResourceName, fontSize);
88582
89018
  curTx += a * w;
88583
89019
  curTy += b * w;
88584
89020
  }
89021
+ return sink.toString();
88585
89022
  }
88586
89023
  /**
88587
89024
  * Split a line of text into runs of consecutive WinAnsi and non-WinAnsi