@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
@@ -66,12 +66,17 @@ export function renderWatermarkHeader(xml, watermark, imageRId) {
66
66
  "xmlns:o": NS_O,
67
67
  "xmlns:w10": NS_W10
68
68
  });
69
- // Watermark goes in a paragraph with a single pict run. Skip pPr
70
- // entirely emitting an empty <w:pStyle/> without @w:val is a hard
71
- // schema violation Word rejects, and the default style is good enough
72
- // for an auto-generated watermark header.
69
+ // Watermark lives in a paragraph styled as a Header, matching what
70
+ // Microsoft Word emits. The run carries <w:noProof/> so the WordArt
71
+ // text is not spell-checked.
73
72
  xml.openNode("w:p");
73
+ xml.openNode("w:pPr");
74
+ xml.leafNode("w:pStyle", { "w:val": "Header" });
75
+ xml.closeNode(); // pPr
74
76
  xml.openNode("w:r");
77
+ xml.openNode("w:rPr");
78
+ xml.leafNode("w:noProof");
79
+ xml.closeNode(); // rPr
75
80
  xml.openNode("w:pict");
76
81
  if (watermark.type === "text") {
77
82
  renderTextWatermarkVml(xml, watermark);
@@ -87,49 +92,130 @@ export function renderWatermarkHeader(xml, watermark, imageRId) {
87
92
  function renderTextWatermarkVml(xml, wm) {
88
93
  const color = wm.color ?? "C0C0C0";
89
94
  const font = wm.font ?? "Calibri";
90
- const fontSize = wm.fontSize ?? 1; // half-points; Word uses pt string in style
91
- const fontPt = fontSize / 2;
92
95
  const rotation = wm.rotation ?? -45;
93
- const opacity = wm.semiTransparent !== false ? ".5" : "1";
94
- // VML shape for text watermark (PowerWash / WASHOUT style)
95
- xml.leafNode("v:shapetype", {
96
+ // Full WordArt shapetype definition (t136) exactly as Microsoft Word
97
+ // emits it. Word for Mac will NOT render the text path unless the
98
+ // shapetype carries the formula/path/textpath/handles/lock children —
99
+ // an empty <v:shapetype/> produces an invisible watermark.
100
+ xml.openNode("v:shapetype", {
96
101
  id: "_x0000_t136",
97
102
  coordsize: "21600,21600",
98
103
  "o:spt": "136",
104
+ adj: "10800",
99
105
  path: "m@7,l@8,m@5,21600l@6,21600e"
100
106
  });
107
+ xml.openNode("v:formulas");
108
+ for (const eqn of [
109
+ "sum #0 0 10800",
110
+ "prod #0 2 1",
111
+ "sum 21600 0 @1",
112
+ "sum 0 0 @2",
113
+ "sum 21600 0 @3",
114
+ "if @0 @3 0",
115
+ "if @0 21600 @1",
116
+ "if @0 0 @2",
117
+ "if @0 @4 21600",
118
+ "mid @5 @6",
119
+ "mid @8 @5",
120
+ "mid @7 @8",
121
+ "mid @6 @7",
122
+ "sum @6 0 @5"
123
+ ]) {
124
+ xml.leafNode("v:f", { eqn });
125
+ }
126
+ xml.closeNode(); // formulas
127
+ xml.leafNode("v:path", {
128
+ textpathok: "t",
129
+ "o:connecttype": "custom",
130
+ "o:connectlocs": "@9,0;@10,10800;@11,21600;@12,10800",
131
+ "o:connectangles": "270,180,90,0"
132
+ });
133
+ xml.leafNode("v:textpath", { on: "t", fitshape: "t" });
134
+ xml.openNode("v:handles");
135
+ xml.leafNode("v:h", { position: "#0,bottomRight", xrange: "6629,14971" });
136
+ xml.closeNode(); // handles
137
+ xml.leafNode("o:lock", { "v:ext": "edit", text: "t", shapetype: "t" });
138
+ xml.closeNode(); // shapetype
139
+ // The watermark shape itself. Word fixes font-size at 1pt and relies on
140
+ // fitshape="t" to scale the text to fill the shape box; rotation is
141
+ // applied on the shape via the style string.
101
142
  const style = `position:absolute;margin-left:0;margin-top:0;width:468pt;height:234pt;` +
102
- `rotation:${rotation};z-index:-251658752;mso-position-horizontal:center;` +
103
- `mso-position-horizontal-relative:margin;mso-position-vertical:center;` +
104
- `mso-position-vertical-relative:margin`;
143
+ `${rotation ? `rotation:${rotation};` : ""}z-index:-251658752;` +
144
+ `mso-position-horizontal:center;mso-position-horizontal-relative:margin;` +
145
+ `mso-position-vertical:center;mso-position-vertical-relative:margin`;
105
146
  xml.openNode("v:shape", {
106
147
  id: "PowerPlusWaterMarkObject",
107
148
  "o:spid": "_x0000_s2049",
108
149
  type: "#_x0000_t136",
150
+ alt: "",
109
151
  style,
110
152
  "o:allowincell": "f",
111
153
  fillcolor: `#${color}`,
112
154
  stroked: "f"
113
155
  });
114
- xml.leafNode("v:fill", { opacity });
156
+ if (wm.semiTransparent !== false) {
157
+ xml.leafNode("v:fill", { opacity: ".5" });
158
+ }
115
159
  xml.leafNode("v:textpath", {
116
- style: `font-family:&quot;${font}&quot;;font-size:${fontPt}pt`,
160
+ style: `font-family:"${font}";font-size:1pt`,
117
161
  string: wm.text
118
162
  });
119
- xml.leafNode("w10:wrap", { anchorx: "margin", anchory: "margin" });
120
163
  xml.closeNode(); // v:shape
121
164
  }
122
165
  function renderImageWatermarkVml(xml, wm, rId) {
123
- const scale = wm.scale ?? 100;
124
166
  const rid = rId ?? wm.rId;
125
- const style = `position:absolute;margin-left:0;margin-top:0;width:0;height:0;` +
167
+ // Default to a large area covering most of the body so the picture is
168
+ // actually visible. width:0;height:0 produces an invisible dot.
169
+ const widthPt = wm.widthPt ?? 415.2;
170
+ const heightPt = wm.heightPt ?? 233.5;
171
+ // Picture-frame shapetype (t75) as Microsoft Word emits it. Without a
172
+ // proper shapetype + non-zero size the image watermark will not render.
173
+ xml.openNode("v:shapetype", {
174
+ id: "_x0000_t75",
175
+ coordsize: "21600,21600",
176
+ "o:spt": "75",
177
+ "o:preferrelative": "t",
178
+ path: "m@4@5l@4@11@9@11@9@5xe",
179
+ filled: "f",
180
+ stroked: "f"
181
+ });
182
+ xml.openNode("v:stroke", { joinstyle: "miter" });
183
+ xml.closeNode();
184
+ xml.openNode("v:formulas");
185
+ for (const eqn of [
186
+ "if lineDrawn pixelLineWidth 0",
187
+ "sum @0 1 0",
188
+ "sum 0 0 @1",
189
+ "prod @2 1 2",
190
+ "prod @3 21600 pixelWidth",
191
+ "prod @3 21600 pixelHeight",
192
+ "sum @0 0 1",
193
+ "prod @6 1 2",
194
+ "prod @7 21600 pixelWidth",
195
+ "sum @8 21600 0",
196
+ "prod @7 21600 pixelHeight",
197
+ "sum @10 21600 0"
198
+ ]) {
199
+ xml.leafNode("v:f", { eqn });
200
+ }
201
+ xml.closeNode(); // formulas
202
+ xml.leafNode("v:path", {
203
+ "o:extrusionok": "f",
204
+ gradientshapeok: "t",
205
+ "o:connecttype": "rect"
206
+ });
207
+ xml.leafNode("o:lock", { "v:ext": "edit", aspectratio: "t" });
208
+ xml.closeNode(); // shapetype
209
+ const style = `position:absolute;margin-left:0;margin-top:0;` +
210
+ `width:${widthPt}pt;height:${heightPt}pt;` +
126
211
  `z-index:-251658752;mso-position-horizontal:center;` +
127
212
  `mso-position-horizontal-relative:margin;mso-position-vertical:center;` +
128
213
  `mso-position-vertical-relative:margin`;
129
214
  xml.openNode("v:shape", {
130
215
  id: "PowerPlusWaterMarkObject",
131
216
  "o:spid": "_x0000_s2050",
132
- type: "",
217
+ type: "#_x0000_t75",
218
+ alt: "",
133
219
  style,
134
220
  "o:allowincell": "f"
135
221
  });
@@ -139,8 +225,7 @@ function renderImageWatermarkVml(xml, wm, rId) {
139
225
  "r:id": rid,
140
226
  "o:title": "",
141
227
  gain,
142
- blacklevel,
143
- ...(scale !== 100 ? { "o:detectmouseclick": "t" } : {})
228
+ blacklevel
144
229
  });
145
230
  xml.leafNode("w10:wrap", { anchorx: "margin", anchory: "margin" });
146
231
  xml.closeNode(); // v:shape
@@ -175,8 +175,13 @@ function renderMathPhantom(xml, p) {
175
175
  p.transparent !== undefined;
176
176
  if (hasProps) {
177
177
  xml.openNode("m:phantPr");
178
- if (p.show) {
179
- xml.leafNode("m:show", { "m:val": "1" });
178
+ // `m:show` defaults to ON in OOXML (the phantom base is still drawn). To
179
+ // make a phantom "occupy space but stay invisible" the producer must
180
+ // emit `<m:show m:val="0"/>` explicitly — simply omitting it leaves the
181
+ // content visible. So serialize `show` whenever it is defined, mapping
182
+ // false → "0" and true → "1".
183
+ if (p.show !== undefined) {
184
+ xml.leafNode("m:show", { "m:val": p.show ? "1" : "0" });
180
185
  }
181
186
  if (p.zeroWidth) {
182
187
  xml.leafNode("m:zeroWid", { "m:val": "1" });
@@ -61,3 +61,11 @@ export declare function getStandardFontNames(): string[];
61
61
  * Falls back to "Helvetica" for unknown fonts.
62
62
  */
63
63
  export declare function mapToStandardFont(fontName: string): string;
64
+ /**
65
+ * Given a standard PDF base font and bold/italic flags, return the matching
66
+ * metric variant name (e.g. "Helvetica" + bold → "Helvetica-Bold"). This keeps
67
+ * width measurement consistent with the glyphs that are actually drawn, so
68
+ * bold/italic runs are measured with their true (wider) metrics rather than
69
+ * the regular ones. Falls back to the base name when a variant is unknown.
70
+ */
71
+ export declare function styledFontVariant(baseFont: string, bold?: boolean, italic?: boolean): string;
@@ -291,3 +291,46 @@ export function mapToStandardFont(fontName) {
291
291
  const lower = fontName.toLowerCase().trim();
292
292
  return FONT_FAMILY_MAP[lower] ?? "Helvetica";
293
293
  }
294
+ /**
295
+ * Given a standard PDF base font and bold/italic flags, return the matching
296
+ * metric variant name (e.g. "Helvetica" + bold → "Helvetica-Bold"). This keeps
297
+ * width measurement consistent with the glyphs that are actually drawn, so
298
+ * bold/italic runs are measured with their true (wider) metrics rather than
299
+ * the regular ones. Falls back to the base name when a variant is unknown.
300
+ */
301
+ export function styledFontVariant(baseFont, bold, italic) {
302
+ const std = mapToStandardFont(baseFont);
303
+ if (!bold && !italic) {
304
+ return std;
305
+ }
306
+ // Determine the family from the resolved standard name.
307
+ const isTimes = std.startsWith("Times");
308
+ const isCourier = std.startsWith("Courier");
309
+ let candidate;
310
+ if (isTimes) {
311
+ // Times family uses -Roman / -Bold / -Italic / -BoldItalic.
312
+ if (bold && italic) {
313
+ candidate = "Times-BoldItalic";
314
+ }
315
+ else if (bold) {
316
+ candidate = "Times-Bold";
317
+ }
318
+ else {
319
+ candidate = "Times-Italic";
320
+ }
321
+ }
322
+ else {
323
+ // Helvetica / Courier families use -Bold / -Oblique / -BoldOblique.
324
+ const family = isCourier ? "Courier" : "Helvetica";
325
+ if (bold && italic) {
326
+ candidate = `${family}-BoldOblique`;
327
+ }
328
+ else if (bold) {
329
+ candidate = `${family}-Bold`;
330
+ }
331
+ else {
332
+ candidate = `${family}-Oblique`;
333
+ }
334
+ }
335
+ return candidate in FONT_DESCRIPTORS ? candidate : std;
336
+ }
@@ -115,6 +115,9 @@ export function resolveOoxmlThemeColor(themeColorName, colors, tint, shade) {
115
115
  // Helpers
116
116
  // =============================================================================
117
117
  function toHex2(n) {
118
- const h = Math.max(0, Math.min(255, n)).toString(16);
118
+ // Emit uppercase hex to match the OOXML ST_HexColorRGB convention and the
119
+ // casing of theme scheme colors (which are stored uppercase), so a tinted /
120
+ // shaded result is consistent with an un-transformed passthrough.
121
+ const h = Math.max(0, Math.min(255, n)).toString(16).toUpperCase();
119
122
  return h.length < 2 ? "0" + h : h;
120
123
  }
@@ -39,7 +39,11 @@ class Note {
39
39
  set model(value) {
40
40
  const { note } = value;
41
41
  const { texts } = note;
42
- if (texts && texts.length === 1 && Object.keys(texts[0]).length === 1) {
42
+ // A single, plain text run with no extra box geometry can be flattened
43
+ // back to a simple string. Custom width/height must keep the full config
44
+ // so the sizing survives the model round-trip.
45
+ const hasCustomSize = note.width !== undefined || note.height !== undefined;
46
+ if (texts && texts.length === 1 && Object.keys(texts[0]).length === 1 && !hasCustomSize) {
43
47
  this.note = texts[0].text;
44
48
  }
45
49
  else {
@@ -216,12 +216,13 @@ class Row {
216
216
  // (e.g. DB entities). Cell.value setter handles unknown values via Value.getType
217
217
  // fallback to JSON type, so this cast is safe at runtime.
218
218
  this._worksheet.eachColumnKey((column, key) => {
219
- if (value[key] !== undefined) {
219
+ const resolved = resolveColumnKeyValue(value, key);
220
+ if (resolved !== undefined) {
220
221
  this.getCellEx({
221
222
  address: col_cache_1.colCache.encodeAddress(this._number, column.number),
222
223
  row: this._number,
223
224
  col: column.number
224
- }).value = value[key];
225
+ }).value = resolved;
225
226
  }
226
227
  });
227
228
  }
@@ -463,3 +464,35 @@ class Row {
463
464
  }
464
465
  }
465
466
  exports.Row = Row;
467
+ /**
468
+ * Resolve a column key against a row object, supporting dotted nested paths.
469
+ *
470
+ * A key without a `.` takes the original fast path (`obj[key]`), preserving
471
+ * exact backward compatibility — including keys that legitimately contain a
472
+ * dot only as a flat property name, which still resolve via the fast path
473
+ * first. A dotted key (e.g. `"address.city"`) is resolved by walking each
474
+ * segment; if any segment is missing or not an object, the result is
475
+ * `undefined` (the same signal the caller already uses to skip a cell).
476
+ *
477
+ * @param obj - The row object supplied to `row.values = {...}` / `addRow({...})`.
478
+ * @param key - The column key, optionally a dotted path.
479
+ * @returns The resolved value, or `undefined` when the path cannot be followed.
480
+ */
481
+ function resolveColumnKeyValue(obj, key) {
482
+ // Fast path: a flat key (no dot) or an exact flat property match. This keeps
483
+ // existing behaviour identical and also lets a literal "a.b" property win
484
+ // over nested traversal when it is actually present on the object.
485
+ const direct = obj[key];
486
+ if (direct !== undefined || !key.includes(".")) {
487
+ return direct;
488
+ }
489
+ // Dotted path: walk segments, bailing out to undefined on any gap.
490
+ let current = obj;
491
+ for (const segment of key.split(".")) {
492
+ if (current === null || typeof current !== "object") {
493
+ return undefined;
494
+ }
495
+ current = current[segment];
496
+ }
497
+ return current;
498
+ }
@@ -390,13 +390,33 @@ class WorkbookWriterBase {
390
390
  * ```
391
391
  */
392
392
  addImage(image) {
393
+ const { svg, ...raster } = image;
394
+ if (svg &&
395
+ raster.link &&
396
+ raster.buffer == null &&
397
+ raster.base64 == null &&
398
+ raster.filename == null) {
399
+ throw new errors_1.ImageError("An SVG image requires an embedded raster fallback (buffer/base64/filename); it cannot be combined with an external link.");
400
+ }
393
401
  const id = this.media.length;
394
402
  const medium = {
395
- ...image,
403
+ ...raster,
396
404
  type: "image",
397
- name: `image${id}.${image.extension}`
405
+ name: `image${id}.${raster.extension}`
398
406
  };
399
407
  this.media.push(medium);
408
+ if (svg) {
409
+ // Register the SVG companion as a second image medium and link it back to
410
+ // the raster blip so the drawing serializer emits the svgBlip extension.
411
+ const svgId = this.media.length;
412
+ this.media.push({
413
+ ...svg,
414
+ type: "image",
415
+ extension: "svg",
416
+ name: `image${svgId}.svg`
417
+ });
418
+ medium.svgMediaId = svgId;
419
+ }
400
420
  return id;
401
421
  }
402
422
  getImage(id) {
@@ -130,6 +130,23 @@ function buildDrawingAnchorsAndRels(media, existingRels, options) {
130
130
  },
131
131
  range: medium.range
132
132
  };
133
+ // SVG companion: allocate (and dedupe) a rel for the vector media, then
134
+ // record its rId so the blip serializer emits the asvg:svgBlip extension.
135
+ if (bookImage.svgMediaId !== undefined) {
136
+ const svgKey = `svg:${bookImage.svgMediaId}`;
137
+ let rIdSvg = imageRIdMap[svgKey];
138
+ if (!rIdSvg) {
139
+ const svgImage = options.getBookImage(bookImage.svgMediaId);
140
+ if (svgImage) {
141
+ rIdSvg = options.nextRId(rels);
142
+ imageRIdMap[svgKey] = rIdSvg;
143
+ rels.push(buildImageRel(rIdSvg, svgImage));
144
+ }
145
+ }
146
+ if (rIdSvg) {
147
+ anchor.picture.svgRId = rIdSvg;
148
+ }
149
+ }
133
150
  // Pass through watermark opacity as alphaModFix
134
151
  if (medium.opacity !== undefined) {
135
152
  const clamped = Math.max(0, Math.min(1, medium.opacity));
@@ -180,8 +197,8 @@ function filterDrawingAnchors(anchors) {
180
197
  if (a.range?.br && a.shape) {
181
198
  return true;
182
199
  }
183
- // One-cell anchors need a valid picture or graphicFrame (charts)
184
- if (!a.range?.br && !a.picture && !a.graphicFrame) {
200
+ // One-cell anchors need a valid picture, graphicFrame (charts) or shape.
201
+ if (!a.range?.br && !a.picture && !a.graphicFrame && !a.shape) {
185
202
  return false;
186
203
  }
187
204
  // Two-cell anchors need either picture, shape, or graphicFrame (charts)
@@ -1736,10 +1736,40 @@ class Workbook {
1736
1736
  * const id = workbook.addImage({ extension: "png", link: "https://example.com/logo.png" });
1737
1737
  * worksheet.addImage(id, "B2:D6");
1738
1738
  * ```
1739
+ *
1740
+ * @example SVG with raster fallback — crisp in modern Excel, safe everywhere
1741
+ * ```typescript
1742
+ * const id = workbook.addImage({
1743
+ * buffer: pngFallbackBytes, // shown by older Excel / non-SVG consumers
1744
+ * extension: "png",
1745
+ * svg: { buffer: svgBytes } // shown by Excel 2016+
1746
+ * });
1747
+ * worksheet.addImage(id, "B2:D6");
1748
+ * ```
1739
1749
  */
1740
1750
  addImage(image) {
1751
+ const { svg, ...raster } = image;
1752
+ if (svg &&
1753
+ raster.link &&
1754
+ raster.buffer == null &&
1755
+ raster.base64 == null &&
1756
+ raster.filename == null) {
1757
+ // An SVG companion needs an embedded raster fallback; a *linked* (external)
1758
+ // raster has no package part to attach the svgBlip extension to.
1759
+ throw new errors_1.ImageError("An SVG image requires an embedded raster fallback (buffer/base64/filename); it cannot be combined with an external link.");
1760
+ }
1741
1761
  const id = this.media.length;
1742
- this.media.push({ ...image, type: "image" });
1762
+ const rasterMedia = { ...raster, type: "image" };
1763
+ this.media.push(rasterMedia);
1764
+ if (svg) {
1765
+ // Register the SVG as a second `type: "image"` media so it flows through
1766
+ // the existing media naming, content-types, and zip-writing paths. Link
1767
+ // it back to the raster blip so the drawing serializer can emit the
1768
+ // asvg:svgBlip extension.
1769
+ const svgId = this.media.length;
1770
+ this.media.push({ ...svg, type: "image", extension: "svg" });
1771
+ rasterMedia.svgMediaId = svgId;
1772
+ }
1743
1773
  return id;
1744
1774
  }
1745
1775
  getImage(id) {
@@ -108,6 +108,8 @@ class Worksheet {
108
108
  this.autoFilter = options.autoFilter ?? null;
109
109
  // for images, etc
110
110
  this._media = [];
111
+ // for user-drawn shapes (rectangles, lines, text boxes, …)
112
+ this._shapes = [];
111
113
  // for charts
112
114
  this._charts = [];
113
115
  this._sparklineGroups = [];
@@ -1012,6 +1014,85 @@ class Worksheet {
1012
1014
  };
1013
1015
  this._media.push(new image_1.Image(this, model));
1014
1016
  }
1017
+ /**
1018
+ * Add a free-form drawing shape (rectangle, ellipse, line, text box, …) to
1019
+ * the worksheet, anchored to a cell range.
1020
+ *
1021
+ * Unlike images, shapes need no media file — the geometry, fill, outline and
1022
+ * optional text label are written directly into the drawing part.
1023
+ *
1024
+ * @example
1025
+ * ```typescript
1026
+ * worksheet.addShape({
1027
+ * type: "rect",
1028
+ * range: "B2:D5",
1029
+ * fillColor: "FFD966",
1030
+ * lineColor: "000000",
1031
+ * lineWidth: 1,
1032
+ * text: "Important"
1033
+ * });
1034
+ * ```
1035
+ */
1036
+ addShape(options) {
1037
+ const range = options.range;
1038
+ // A shape must cover an area, mirroring images. Reject inputs that resolve
1039
+ // to no size up front, with a clear shape-specific message — otherwise the
1040
+ // failure surfaces much later as a confusing `ImageError` from the internal
1041
+ // range parser when the worksheet is serialized.
1042
+ const hasArea = (typeof range === "string" && range.includes(":")) ||
1043
+ (typeof range === "object" &&
1044
+ range !== null &&
1045
+ ("br" in range || "ext" in range || "pos" in range));
1046
+ if (!hasArea) {
1047
+ throw new errors_1.ImageError('addShape requires a range covering an area: a cell range like "B2:D5", or an object with `br`, `ext`, or `pos`.');
1048
+ }
1049
+ this._shapes.push({
1050
+ type: "shape",
1051
+ shapeType: options.type ?? "rect",
1052
+ range,
1053
+ fillColor: options.fillColor,
1054
+ lineColor: options.lineColor,
1055
+ lineWidth: options.lineWidth,
1056
+ text: options.text,
1057
+ name: options.name
1058
+ });
1059
+ }
1060
+ /** All shapes added to this worksheet. */
1061
+ getShapes() {
1062
+ return this._shapes.slice();
1063
+ }
1064
+ /**
1065
+ * Resolve a shape's `range` into concrete two-cell anchor coordinates,
1066
+ * reusing the `Image` range parser so cell-address/anchor handling stays in
1067
+ * one place. Returns a serializable ShapeModel for the worksheet xform.
1068
+ */
1069
+ _resolveShapeModel(shape) {
1070
+ let range;
1071
+ try {
1072
+ const probe = new image_1.Image(this, { type: "image", imageId: "", range: shape.range });
1073
+ // The probe is always an "image" type, so its model carries `range`.
1074
+ range = probe.model.range;
1075
+ }
1076
+ catch {
1077
+ // Range could not be parsed into an anchor (addShape validates the common
1078
+ // cases up front; this guards exotic inputs). Drop the anchor so the
1079
+ // serializer skips this shape rather than failing the whole worksheet.
1080
+ range = undefined;
1081
+ }
1082
+ if (!range) {
1083
+ return { ...shape, anchorRange: undefined };
1084
+ }
1085
+ return {
1086
+ ...shape,
1087
+ anchorRange: {
1088
+ tl: range.tl,
1089
+ br: range.br,
1090
+ ext: range.ext,
1091
+ pos: range.pos,
1092
+ editAs: range.editAs
1093
+ }
1094
+ };
1095
+ }
1015
1096
  getImages() {
1016
1097
  return this._media.filter(m => m.type === "image");
1017
1098
  }
@@ -1757,6 +1838,7 @@ class Worksheet {
1757
1838
  views: this.views,
1758
1839
  autoFilter: this.autoFilter,
1759
1840
  media: this._media.map(medium => medium.model),
1841
+ shapes: this._shapes.map(shape => this._resolveShapeModel(shape)),
1760
1842
  sheetProtection: this.sheetProtection,
1761
1843
  tables: Object.values(this.tables).map(table => table.model),
1762
1844
  pivotTables: this.pivotTables,
@@ -1822,6 +1904,7 @@ class Worksheet {
1822
1904
  this.views = value.views;
1823
1905
  this.autoFilter = value.autoFilter;
1824
1906
  this._media = value.media.map(medium => new image_1.Image(this, medium));
1907
+ this._shapes = value.shapes ? value.shapes.slice() : [];
1825
1908
  // Restore watermark state from media entries
1826
1909
  this._watermark = value.watermark ?? null;
1827
1910
  if (!this._watermark) {
@@ -4,6 +4,9 @@ exports.VmlShapeXform = void 0;
4
4
  const base_xform_1 = require("../base-xform.js");
5
5
  const vml_client_data_xform_1 = require("./vml-client-data-xform.js");
6
6
  const vml_textbox_xform_1 = require("./vml-textbox-xform.js");
7
+ /** Default comment box geometry in points (matches legacy Excel notes). */
8
+ const DEFAULT_NOTE_WIDTH_PT = 97.8;
9
+ const DEFAULT_NOTE_HEIGHT_PT = 59.1;
7
10
  class VmlShapeXform extends base_xform_1.BaseXform {
8
11
  constructor() {
9
12
  super();
@@ -40,6 +43,21 @@ class VmlShapeXform extends base_xform_1.BaseXform {
40
43
  editAs: "",
41
44
  protection: {}
42
45
  };
46
+ {
47
+ // Recover the comment box geometry from the VML style string
48
+ // (e.g. "...width:120pt;height:80pt;..."). Only surface width/height
49
+ // when they differ from the legacy defaults, so untouched notes keep
50
+ // a clean model (and stay byte-compatible with prior behaviour).
51
+ const style = node.attributes.style ?? "";
52
+ const width = VmlShapeXform.parseStyleLength(style, "width");
53
+ const height = VmlShapeXform.parseStyleLength(style, "height");
54
+ if (width !== undefined && width !== DEFAULT_NOTE_WIDTH_PT) {
55
+ this.model.width = width;
56
+ }
57
+ if (height !== undefined && height !== DEFAULT_NOTE_HEIGHT_PT) {
58
+ this.model.height = height;
59
+ }
60
+ }
43
61
  break;
44
62
  default:
45
63
  this.parser = this.map[node.name];
@@ -78,13 +96,29 @@ class VmlShapeXform extends base_xform_1.BaseXform {
78
96
  return true;
79
97
  }
80
98
  }
99
+ /**
100
+ * Extract a points-valued length (e.g. `width:120pt`) from a VML style
101
+ * string. Returns `undefined` when the property is absent or not in `pt`.
102
+ */
103
+ static parseStyleLength(style, prop) {
104
+ const match = new RegExp(`(?:^|;)\\s*${prop}\\s*:\\s*([0-9.]+)pt`, "i").exec(style);
105
+ if (!match) {
106
+ return undefined;
107
+ }
108
+ const value = parseFloat(match[1]);
109
+ return Number.isFinite(value) ? value : undefined;
110
+ }
81
111
  }
82
112
  exports.VmlShapeXform = VmlShapeXform;
83
- VmlShapeXform.V_SHAPE_ATTRIBUTES = (model, index) => ({
84
- id: `_x0000_s${1025 + index}`,
85
- type: "#_x0000_t202",
86
- style: "position:absolute; margin-left:105.3pt;margin-top:10.5pt;width:97.8pt;height:59.1pt;z-index:1;visibility:hidden",
87
- fillcolor: "infoBackground [80]",
88
- strokecolor: "none [81]",
89
- "o:insetmode": model.note.margins && model.note.margins.insetmode
90
- });
113
+ VmlShapeXform.V_SHAPE_ATTRIBUTES = (model, index) => {
114
+ const width = model.note?.width ?? DEFAULT_NOTE_WIDTH_PT;
115
+ const height = model.note?.height ?? DEFAULT_NOTE_HEIGHT_PT;
116
+ return {
117
+ id: `_x0000_s${1025 + index}`,
118
+ type: "#_x0000_t202",
119
+ style: `position:absolute; margin-left:105.3pt;margin-top:10.5pt;width:${width}pt;height:${height}pt;z-index:1;visibility:hidden`,
120
+ fillcolor: "infoBackground [80]",
121
+ strokecolor: "none [81]",
122
+ "o:insetmode": model.note.margins && model.note.margins.insetmode
123
+ };
124
+ };
@@ -24,7 +24,9 @@ class ContentTypesXform extends base_xform_1.BaseXform {
24
24
  mediaHash[imageType] = true;
25
25
  xmlStream.leafNode("Default", {
26
26
  Extension: imageType,
27
- ContentType: `image/${imageType}`
27
+ // SVG's IANA media type is "image/svg+xml"; everything else follows
28
+ // the "image/<ext>" convention.
29
+ ContentType: imageType === "svg" ? "image/svg+xml" : `image/${imageType}`
28
30
  });
29
31
  }
30
32
  }