docxmlater 10.1.3 → 10.1.5

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 (371) hide show
  1. package/README.md +759 -754
  2. package/dist/constants/legacyCompatFlags.js +1 -1
  3. package/dist/constants/legacyCompatFlags.js.map +1 -1
  4. package/dist/constants/limits.js.map +1 -1
  5. package/dist/core/Document.d.ts +50 -50
  6. package/dist/core/Document.d.ts.map +1 -1
  7. package/dist/core/Document.js +483 -471
  8. package/dist/core/Document.js.map +1 -1
  9. package/dist/core/DocumentContent.d.ts +9 -9
  10. package/dist/core/DocumentContent.d.ts.map +1 -1
  11. package/dist/core/DocumentContent.js +1 -1
  12. package/dist/core/DocumentContent.js.map +1 -1
  13. package/dist/core/DocumentGenerator.d.ts +11 -11
  14. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  15. package/dist/core/DocumentGenerator.js +251 -251
  16. package/dist/core/DocumentGenerator.js.map +1 -1
  17. package/dist/core/DocumentIdManager.js.map +1 -1
  18. package/dist/core/DocumentParser.d.ts +15 -15
  19. package/dist/core/DocumentParser.d.ts.map +1 -1
  20. package/dist/core/DocumentParser.js +2123 -2155
  21. package/dist/core/DocumentParser.js.map +1 -1
  22. package/dist/core/DocumentValidator.d.ts.map +1 -1
  23. package/dist/core/DocumentValidator.js +2 -5
  24. package/dist/core/DocumentValidator.js.map +1 -1
  25. package/dist/core/Relationship.js.map +1 -1
  26. package/dist/core/RelationshipManager.d.ts.map +1 -1
  27. package/dist/core/RelationshipManager.js +3 -3
  28. package/dist/core/RelationshipManager.js.map +1 -1
  29. package/dist/elements/AlternateContent.js.map +1 -1
  30. package/dist/elements/Bookmark.d.ts.map +1 -1
  31. package/dist/elements/Bookmark.js +3 -1
  32. package/dist/elements/Bookmark.js.map +1 -1
  33. package/dist/elements/BookmarkManager.d.ts.map +1 -1
  34. package/dist/elements/BookmarkManager.js.map +1 -1
  35. package/dist/elements/Comment.d.ts.map +1 -1
  36. package/dist/elements/Comment.js +9 -6
  37. package/dist/elements/Comment.js.map +1 -1
  38. package/dist/elements/CommentManager.d.ts.map +1 -1
  39. package/dist/elements/CommentManager.js +18 -17
  40. package/dist/elements/CommentManager.js.map +1 -1
  41. package/dist/elements/CommonTypes.d.ts +21 -21
  42. package/dist/elements/CommonTypes.d.ts.map +1 -1
  43. package/dist/elements/CommonTypes.js +56 -56
  44. package/dist/elements/CommonTypes.js.map +1 -1
  45. package/dist/elements/CustomXml.js.map +1 -1
  46. package/dist/elements/Endnote.d.ts.map +1 -1
  47. package/dist/elements/Endnote.js +6 -6
  48. package/dist/elements/Endnote.js.map +1 -1
  49. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  50. package/dist/elements/EndnoteManager.js +6 -7
  51. package/dist/elements/EndnoteManager.js.map +1 -1
  52. package/dist/elements/Field.d.ts.map +1 -1
  53. package/dist/elements/Field.js +82 -25
  54. package/dist/elements/Field.js.map +1 -1
  55. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  56. package/dist/elements/FieldHelpers.js.map +1 -1
  57. package/dist/elements/FontManager.d.ts.map +1 -1
  58. package/dist/elements/FontManager.js +1 -1
  59. package/dist/elements/FontManager.js.map +1 -1
  60. package/dist/elements/Footer.js +2 -2
  61. package/dist/elements/Footer.js.map +1 -1
  62. package/dist/elements/Footnote.d.ts.map +1 -1
  63. package/dist/elements/Footnote.js +6 -6
  64. package/dist/elements/Footnote.js.map +1 -1
  65. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  66. package/dist/elements/FootnoteManager.js +6 -7
  67. package/dist/elements/FootnoteManager.js.map +1 -1
  68. package/dist/elements/Header.js +2 -2
  69. package/dist/elements/Header.js.map +1 -1
  70. package/dist/elements/HeaderFooterManager.js.map +1 -1
  71. package/dist/elements/Hyperlink.d.ts +5 -3
  72. package/dist/elements/Hyperlink.d.ts.map +1 -1
  73. package/dist/elements/Hyperlink.js +134 -76
  74. package/dist/elements/Hyperlink.js.map +1 -1
  75. package/dist/elements/Image.d.ts.map +1 -1
  76. package/dist/elements/Image.js +238 -106
  77. package/dist/elements/Image.js.map +1 -1
  78. package/dist/elements/ImageManager.d.ts.map +1 -1
  79. package/dist/elements/ImageManager.js +1 -1
  80. package/dist/elements/ImageManager.js.map +1 -1
  81. package/dist/elements/ImageRun.js +1 -1
  82. package/dist/elements/ImageRun.js.map +1 -1
  83. package/dist/elements/MathElement.js.map +1 -1
  84. package/dist/elements/Paragraph.d.ts +24 -24
  85. package/dist/elements/Paragraph.d.ts.map +1 -1
  86. package/dist/elements/Paragraph.js +181 -188
  87. package/dist/elements/Paragraph.js.map +1 -1
  88. package/dist/elements/PreservedElement.js.map +1 -1
  89. package/dist/elements/PropertyChangeTypes.d.ts.map +1 -1
  90. package/dist/elements/PropertyChangeTypes.js +6 -6
  91. package/dist/elements/PropertyChangeTypes.js.map +1 -1
  92. package/dist/elements/RangeMarker.d.ts.map +1 -1
  93. package/dist/elements/RangeMarker.js.map +1 -1
  94. package/dist/elements/Revision.d.ts.map +1 -1
  95. package/dist/elements/Revision.js +4 -5
  96. package/dist/elements/Revision.js.map +1 -1
  97. package/dist/elements/RevisionContent.js.map +1 -1
  98. package/dist/elements/RevisionManager.d.ts.map +1 -1
  99. package/dist/elements/RevisionManager.js +40 -48
  100. package/dist/elements/RevisionManager.js.map +1 -1
  101. package/dist/elements/Run.d.ts +16 -16
  102. package/dist/elements/Run.d.ts.map +1 -1
  103. package/dist/elements/Run.js +256 -238
  104. package/dist/elements/Run.js.map +1 -1
  105. package/dist/elements/Section.d.ts.map +1 -1
  106. package/dist/elements/Section.js +36 -11
  107. package/dist/elements/Section.js.map +1 -1
  108. package/dist/elements/Shape.d.ts.map +1 -1
  109. package/dist/elements/Shape.js.map +1 -1
  110. package/dist/elements/StructuredDocumentTag.d.ts +6 -6
  111. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  112. package/dist/elements/StructuredDocumentTag.js +99 -104
  113. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  114. package/dist/elements/Table.d.ts +11 -11
  115. package/dist/elements/Table.d.ts.map +1 -1
  116. package/dist/elements/Table.js +102 -107
  117. package/dist/elements/Table.js.map +1 -1
  118. package/dist/elements/TableCell.d.ts +10 -10
  119. package/dist/elements/TableCell.d.ts.map +1 -1
  120. package/dist/elements/TableCell.js +105 -106
  121. package/dist/elements/TableCell.js.map +1 -1
  122. package/dist/elements/TableGridChange.d.ts.map +1 -1
  123. package/dist/elements/TableGridChange.js.map +1 -1
  124. package/dist/elements/TableOfContents.d.ts.map +1 -1
  125. package/dist/elements/TableOfContents.js +4 -4
  126. package/dist/elements/TableOfContents.js.map +1 -1
  127. package/dist/elements/TableOfContentsElement.js.map +1 -1
  128. package/dist/elements/TableRow.d.ts.map +1 -1
  129. package/dist/elements/TableRow.js +13 -6
  130. package/dist/elements/TableRow.js.map +1 -1
  131. package/dist/elements/TextBox.d.ts.map +1 -1
  132. package/dist/elements/TextBox.js +3 -5
  133. package/dist/elements/TextBox.js.map +1 -1
  134. package/dist/formatting/AbstractNumbering.d.ts +4 -4
  135. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  136. package/dist/formatting/AbstractNumbering.js +54 -49
  137. package/dist/formatting/AbstractNumbering.js.map +1 -1
  138. package/dist/formatting/NumberingInstance.d.ts.map +1 -1
  139. package/dist/formatting/NumberingInstance.js +1 -3
  140. package/dist/formatting/NumberingInstance.js.map +1 -1
  141. package/dist/formatting/NumberingLevel.d.ts +5 -5
  142. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  143. package/dist/formatting/NumberingLevel.js +119 -125
  144. package/dist/formatting/NumberingLevel.js.map +1 -1
  145. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  146. package/dist/formatting/NumberingManager.js +9 -9
  147. package/dist/formatting/NumberingManager.js.map +1 -1
  148. package/dist/formatting/Style.d.ts +11 -11
  149. package/dist/formatting/Style.d.ts.map +1 -1
  150. package/dist/formatting/Style.js +219 -247
  151. package/dist/formatting/Style.js.map +1 -1
  152. package/dist/formatting/StylesManager.d.ts +2 -2
  153. package/dist/formatting/StylesManager.d.ts.map +1 -1
  154. package/dist/formatting/StylesManager.js +96 -102
  155. package/dist/formatting/StylesManager.js.map +1 -1
  156. package/dist/helpers/CleanupHelper.d.ts +1 -1
  157. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  158. package/dist/helpers/CleanupHelper.js +6 -6
  159. package/dist/helpers/CleanupHelper.js.map +1 -1
  160. package/dist/images/ImageOptimizer.js +7 -7
  161. package/dist/images/ImageOptimizer.js.map +1 -1
  162. package/dist/index.d.ts +9 -9
  163. package/dist/index.d.ts.map +1 -1
  164. package/dist/index.js.map +1 -1
  165. package/dist/managers/DrawingManager.js.map +1 -1
  166. package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
  167. package/dist/tracking/DocumentTrackingContext.js +23 -7
  168. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  169. package/dist/tracking/TrackingContext.d.ts.map +1 -1
  170. package/dist/tracking/TrackingContext.js.map +1 -1
  171. package/dist/types/compatibility-types.js.map +1 -1
  172. package/dist/types/formatting.js.map +1 -1
  173. package/dist/types/list-types.d.ts +6 -6
  174. package/dist/types/list-types.js.map +1 -1
  175. package/dist/types/settings-types.js.map +1 -1
  176. package/dist/types/styleConfig.d.ts +2 -2
  177. package/dist/types/styleConfig.js.map +1 -1
  178. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  179. package/dist/utils/ChangelogGenerator.js +97 -101
  180. package/dist/utils/ChangelogGenerator.js.map +1 -1
  181. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  182. package/dist/utils/CompatibilityUpgrader.js +1 -1
  183. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  184. package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
  185. package/dist/utils/InMemoryRevisionAcceptor.js +1 -6
  186. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  187. package/dist/utils/MoveOperationHelper.d.ts.map +1 -1
  188. package/dist/utils/MoveOperationHelper.js +1 -1
  189. package/dist/utils/MoveOperationHelper.js.map +1 -1
  190. package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
  191. package/dist/utils/RevisionAwareProcessor.js +2 -4
  192. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  193. package/dist/utils/RevisionWalker.d.ts.map +1 -1
  194. package/dist/utils/RevisionWalker.js +4 -12
  195. package/dist/utils/RevisionWalker.js.map +1 -1
  196. package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
  197. package/dist/utils/SelectiveRevisionAcceptor.js +2 -6
  198. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  199. package/dist/utils/ShadingResolver.d.ts.map +1 -1
  200. package/dist/utils/ShadingResolver.js +1 -1
  201. package/dist/utils/ShadingResolver.js.map +1 -1
  202. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  203. package/dist/utils/acceptRevisions.js +23 -12
  204. package/dist/utils/acceptRevisions.js.map +1 -1
  205. package/dist/utils/cnfStyleDecoder.d.ts +1 -1
  206. package/dist/utils/cnfStyleDecoder.d.ts.map +1 -1
  207. package/dist/utils/cnfStyleDecoder.js +40 -40
  208. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  209. package/dist/utils/corruptionDetection.d.ts.map +1 -1
  210. package/dist/utils/corruptionDetection.js.map +1 -1
  211. package/dist/utils/dateFormatting.js.map +1 -1
  212. package/dist/utils/deepClone.js +1 -1
  213. package/dist/utils/deepClone.js.map +1 -1
  214. package/dist/utils/diagnostics.d.ts.map +1 -1
  215. package/dist/utils/diagnostics.js +1 -1
  216. package/dist/utils/diagnostics.js.map +1 -1
  217. package/dist/utils/errorHandling.js.map +1 -1
  218. package/dist/utils/formatting.d.ts.map +1 -1
  219. package/dist/utils/formatting.js +10 -2
  220. package/dist/utils/formatting.js.map +1 -1
  221. package/dist/utils/list-detection.d.ts +2 -2
  222. package/dist/utils/list-detection.d.ts.map +1 -1
  223. package/dist/utils/list-detection.js +21 -23
  224. package/dist/utils/list-detection.js.map +1 -1
  225. package/dist/utils/logger.d.ts.map +1 -1
  226. package/dist/utils/logger.js +12 -7
  227. package/dist/utils/logger.js.map +1 -1
  228. package/dist/utils/parsingHelpers.js.map +1 -1
  229. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  230. package/dist/utils/stripTrackedChanges.js +3 -3
  231. package/dist/utils/stripTrackedChanges.js.map +1 -1
  232. package/dist/utils/textDiff.d.ts +1 -1
  233. package/dist/utils/textDiff.js +8 -8
  234. package/dist/utils/textDiff.js.map +1 -1
  235. package/dist/utils/units.js.map +1 -1
  236. package/dist/utils/validation.d.ts.map +1 -1
  237. package/dist/utils/validation.js +24 -7
  238. package/dist/utils/validation.js.map +1 -1
  239. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  240. package/dist/utils/xmlSanitization.js +3 -3
  241. package/dist/utils/xmlSanitization.js.map +1 -1
  242. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  243. package/dist/validation/RevisionAutoFixer.js +5 -5
  244. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  245. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  246. package/dist/validation/RevisionValidator.js +7 -9
  247. package/dist/validation/RevisionValidator.js.map +1 -1
  248. package/dist/validation/ValidationRules.js +3 -3
  249. package/dist/validation/ValidationRules.js.map +1 -1
  250. package/dist/validation/index.js.map +1 -1
  251. package/dist/xml/XMLBuilder.d.ts +1 -1
  252. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  253. package/dist/xml/XMLBuilder.js +98 -100
  254. package/dist/xml/XMLBuilder.js.map +1 -1
  255. package/dist/xml/XMLParser.d.ts.map +1 -1
  256. package/dist/xml/XMLParser.js +61 -66
  257. package/dist/xml/XMLParser.js.map +1 -1
  258. package/dist/zip/ZipHandler.d.ts.map +1 -1
  259. package/dist/zip/ZipHandler.js.map +1 -1
  260. package/dist/zip/ZipReader.d.ts.map +1 -1
  261. package/dist/zip/ZipReader.js +1 -3
  262. package/dist/zip/ZipReader.js.map +1 -1
  263. package/dist/zip/ZipWriter.d.ts +1 -1
  264. package/dist/zip/ZipWriter.d.ts.map +1 -1
  265. package/dist/zip/ZipWriter.js +28 -36
  266. package/dist/zip/ZipWriter.js.map +1 -1
  267. package/dist/zip/types.js +1 -1
  268. package/dist/zip/types.js.map +1 -1
  269. package/package.json +92 -92
  270. package/src/__tests__/helper-methods.test.ts +512 -512
  271. package/src/constants/legacyCompatFlags.ts +138 -138
  272. package/src/constants/limits.ts +50 -50
  273. package/src/core/Document.ts +985 -1145
  274. package/src/core/DocumentContent.ts +461 -467
  275. package/src/core/DocumentGenerator.ts +1133 -1104
  276. package/src/core/DocumentIdManager.ts +158 -158
  277. package/src/core/DocumentParser.ts +2347 -2716
  278. package/src/core/DocumentValidator.ts +363 -372
  279. package/src/core/Relationship.ts +367 -367
  280. package/src/core/RelationshipManager.ts +429 -428
  281. package/src/elements/AlternateContent.ts +42 -42
  282. package/src/elements/Bookmark.ts +212 -210
  283. package/src/elements/BookmarkManager.ts +247 -250
  284. package/src/elements/Comment.ts +356 -359
  285. package/src/elements/CommentManager.ts +499 -502
  286. package/src/elements/CommonTypes.ts +524 -549
  287. package/src/elements/CustomXml.ts +36 -36
  288. package/src/elements/Endnote.ts +221 -217
  289. package/src/elements/EndnoteManager.ts +246 -249
  290. package/src/elements/Field.ts +1292 -1233
  291. package/src/elements/FieldHelpers.ts +329 -333
  292. package/src/elements/FontManager.ts +336 -339
  293. package/src/elements/Footer.ts +269 -269
  294. package/src/elements/Footnote.ts +221 -217
  295. package/src/elements/FootnoteManager.ts +246 -249
  296. package/src/elements/Header.ts +269 -269
  297. package/src/elements/HeaderFooterManager.ts +219 -219
  298. package/src/elements/Hyperlink.ts +1288 -1193
  299. package/src/elements/Image.ts +1982 -1756
  300. package/src/elements/ImageManager.ts +437 -432
  301. package/src/elements/ImageRun.ts +59 -59
  302. package/src/elements/MathElement.ts +65 -65
  303. package/src/elements/Paragraph.ts +4347 -4287
  304. package/src/elements/PreservedElement.ts +53 -53
  305. package/src/elements/PropertyChangeTypes.ts +458 -442
  306. package/src/elements/RangeMarker.ts +382 -400
  307. package/src/elements/Revision.ts +1198 -1217
  308. package/src/elements/RevisionContent.ts +73 -73
  309. package/src/elements/RevisionManager.ts +1070 -1070
  310. package/src/elements/Run.ts +3103 -3073
  311. package/src/elements/Section.ts +1521 -1421
  312. package/src/elements/Shape.ts +884 -873
  313. package/src/elements/StructuredDocumentTag.ts +1176 -1207
  314. package/src/elements/Table.ts +2468 -2524
  315. package/src/elements/TableCell.ts +1617 -1621
  316. package/src/elements/TableGridChange.ts +149 -151
  317. package/src/elements/TableOfContents.ts +701 -691
  318. package/src/elements/TableOfContentsElement.ts +89 -89
  319. package/src/elements/TableRow.ts +960 -929
  320. package/src/elements/TextBox.ts +766 -768
  321. package/src/formatting/AbstractNumbering.ts +580 -579
  322. package/src/formatting/NumberingInstance.ts +295 -299
  323. package/src/formatting/NumberingLevel.ts +981 -1040
  324. package/src/formatting/NumberingManager.ts +833 -827
  325. package/src/formatting/Style.ts +1785 -1879
  326. package/src/formatting/StylesManager.ts +1090 -1130
  327. package/src/helpers/CleanupHelper.ts +524 -524
  328. package/src/images/ImageOptimizer.ts +274 -274
  329. package/src/index.ts +559 -554
  330. package/src/managers/DrawingManager.ts +319 -319
  331. package/src/tracking/DocumentTrackingContext.ts +687 -674
  332. package/src/tracking/TrackingContext.ts +175 -173
  333. package/src/types/compatibility-types.ts +49 -49
  334. package/src/types/formatting.ts +210 -210
  335. package/src/types/list-types.ts +14 -14
  336. package/src/types/settings-types.ts +59 -59
  337. package/src/types/styleConfig.ts +189 -189
  338. package/src/utils/ChangelogGenerator.ts +1583 -1581
  339. package/src/utils/CompatibilityUpgrader.ts +235 -237
  340. package/src/utils/InMemoryRevisionAcceptor.ts +691 -696
  341. package/src/utils/MoveOperationHelper.ts +233 -238
  342. package/src/utils/RevisionAwareProcessor.ts +518 -526
  343. package/src/utils/RevisionWalker.ts +427 -457
  344. package/src/utils/SelectiveRevisionAcceptor.ts +662 -683
  345. package/src/utils/ShadingResolver.ts +105 -107
  346. package/src/utils/acceptRevisions.ts +723 -714
  347. package/src/utils/cnfStyleDecoder.ts +212 -217
  348. package/src/utils/corruptionDetection.ts +346 -345
  349. package/src/utils/dateFormatting.ts +20 -20
  350. package/src/utils/deepClone.ts +77 -78
  351. package/src/utils/diagnostics.ts +125 -129
  352. package/src/utils/errorHandling.ts +80 -80
  353. package/src/utils/formatting.ts +220 -213
  354. package/src/utils/list-detection.ts +32 -42
  355. package/src/utils/logger.ts +412 -404
  356. package/src/utils/parsingHelpers.ts +190 -190
  357. package/src/utils/stripTrackedChanges.ts +356 -353
  358. package/src/utils/textDiff.ts +100 -100
  359. package/src/utils/units.ts +421 -421
  360. package/src/utils/validation.ts +553 -542
  361. package/src/utils/xmlSanitization.ts +179 -182
  362. package/src/validation/RevisionAutoFixer.ts +541 -542
  363. package/src/validation/RevisionValidator.ts +470 -460
  364. package/src/validation/ValidationRules.ts +338 -338
  365. package/src/validation/index.ts +30 -30
  366. package/src/xml/XMLBuilder.ts +857 -871
  367. package/src/xml/XMLParser.ts +877 -919
  368. package/src/zip/ZipHandler.ts +629 -637
  369. package/src/zip/ZipReader.ts +295 -299
  370. package/src/zip/ZipWriter.ts +374 -390
  371. package/src/zip/types.ts +116 -116
@@ -1,873 +1,884 @@
1
- /**
2
- * Shape - Represents a drawing shape in a Word document
3
- *
4
- * Shapes use DrawingML (a:) and WordprocessingML Drawing (wp:) namespaces,
5
- * plus WordprocessingShape (wps:) namespace for Word 2010+ shape features.
6
- *
7
- * Per ECMA-376 Part 4 (Transitional) and Office Open XML extensions,
8
- * shapes in Word documents use the wps:wsp element structure.
9
- */
10
-
11
- import { XMLBuilder, XMLElement } from '../xml/XMLBuilder';
12
- import { inchesToEmus, pointsToHalfPoints } from '../utils/units';
13
- import { RunFormatting } from './Run';
14
- import {
15
- ImagePosition,
16
- ImageAnchor,
17
- PositionAnchor,
18
- HorizontalAlignment,
19
- VerticalAlignment
20
- } from './Image';
21
-
22
- /**
23
- * Preset shape types available in DrawingML
24
- * Based on ECMA-376 Part 1 §20.1.10.56 ST_ShapeType
25
- */
26
- export type ShapeType =
27
- | 'rect' // Rectangle
28
- | 'ellipse' // Circle/Ellipse
29
- | 'rightArrow' // Right arrow
30
- | 'leftArrow' // Left arrow
31
- | 'upArrow' // Up arrow
32
- | 'downArrow' // Down arrow
33
- | 'straightConnector1' // Straight line
34
- | 'roundRect' // Rounded rectangle
35
- | 'triangle' // Triangle
36
- | 'diamond'; // Diamond
37
-
38
- /**
39
- * Shape fill configuration
40
- */
41
- export interface ShapeFill {
42
- /** Fill color in hex (e.g., 'FF0000' for red) */
43
- color: string;
44
- /** Transparency percentage (0-100, where 100 is fully transparent) */
45
- transparency?: number;
46
- }
47
-
48
- /**
49
- * Shape outline/border configuration
50
- */
51
- export interface ShapeOutline {
52
- /** Outline color in hex */
53
- color: string;
54
- /** Outline width in EMUs */
55
- width: number;
56
- /** Line dash style */
57
- style?: 'solid' | 'dash' | 'dot' | 'dashDot' | 'lgDash' | 'lgDashDot' | 'lgDashDotDot' | 'sysDash' | 'sysDot' | 'sysDashDot' | 'sysDashDotDot';
58
- /** Line cap style (ECMA-376 §20.1.10.31) */
59
- cap?: 'flat' | 'rnd' | 'sq';
60
- /** Line join style (ECMA-376 §20.1.10.32) */
61
- join?: 'bevel' | 'miter' | 'round';
62
- /** Compound line type (ECMA-376 §20.1.10.15) */
63
- compound?: 'sng' | 'dbl' | 'thickThin' | 'thinThick' | 'tri';
64
- }
65
-
66
- /**
67
- * Shape properties
68
- */
69
- export interface ShapeProperties {
70
- /** Shape type (preset geometry) */
71
- shapeType: ShapeType;
72
- /** Width in EMUs */
73
- width: number;
74
- /** Height in EMUs */
75
- height: number;
76
- /** Fill color and transparency */
77
- fill?: ShapeFill;
78
- /** Outline/border properties */
79
- outline?: ShapeOutline;
80
- /** Position configuration (reuses Image position types) */
81
- position?: ImagePosition;
82
- /** Anchor configuration (reuses Image anchor types) */
83
- anchor?: ImageAnchor;
84
- /** Rotation angle in degrees (0-360) */
85
- rotation?: number;
86
- /** Text content within the shape */
87
- text?: string;
88
- /** Text formatting */
89
- textFormatting?: RunFormatting;
90
- /** Shape name/title */
91
- name?: string;
92
- /** Shape description (for accessibility) */
93
- description?: string;
94
- }
95
-
96
- /**
97
- * Represents a drawing shape
98
- */
99
- export class Shape {
100
- private shapeType: ShapeType;
101
- private width: number;
102
- private height: number;
103
- private fill?: ShapeFill;
104
- private outline?: ShapeOutline;
105
- private position?: ImagePosition;
106
- private anchor?: ImageAnchor;
107
- private rotation = 0;
108
- private text?: string;
109
- private textFormatting?: RunFormatting;
110
- private name: string;
111
- private description: string;
112
- private docPrId = 1;
113
-
114
- /**
115
- * Creates a new shape
116
- * @param properties Shape properties
117
- * @private Use static factory methods instead (create, createRectangle, etc.)
118
- */
119
- private constructor(properties: ShapeProperties) {
120
- this.shapeType = properties.shapeType;
121
- this.width = properties.width;
122
- this.height = properties.height;
123
- this.fill = properties.fill;
124
- this.outline = properties.outline;
125
- this.position = properties.position;
126
- this.anchor = properties.anchor;
127
- this.rotation = properties.rotation || 0;
128
- this.text = properties.text;
129
- this.textFormatting = properties.textFormatting;
130
- this.name = properties.name || 'Shape';
131
- this.description = properties.description || '';
132
- }
133
-
134
- /**
135
- * Factory method for creating a shape
136
- * @param shapeType Shape type
137
- * @param width Width in EMUs (or use inchesToEmus)
138
- * @param height Height in EMUs (or use inchesToEmus)
139
- * @returns New Shape instance
140
- * @example
141
- * const rect = Shape.create('rect', inchesToEmus(2), inchesToEmus(1));
142
- */
143
- static create(shapeType: ShapeType, width: number, height: number): Shape {
144
- return new Shape({ shapeType, width, height });
145
- }
146
-
147
- /**
148
- * Creates a rectangle shape
149
- * @param width Width in EMUs
150
- * @param height Height in EMUs
151
- * @returns New Shape instance
152
- */
153
- static createRectangle(width: number, height: number): Shape {
154
- return Shape.create('rect', width, height);
155
- }
156
-
157
- /**
158
- * Creates a circle shape
159
- * @param diameter Diameter in EMUs
160
- * @returns New Shape instance
161
- */
162
- static createCircle(diameter: number): Shape {
163
- return Shape.create('ellipse', diameter, diameter);
164
- }
165
-
166
- /**
167
- * Creates an ellipse shape
168
- * @param width Width in EMUs
169
- * @param height Height in EMUs
170
- * @returns New Shape instance
171
- */
172
- static createEllipse(width: number, height: number): Shape {
173
- return Shape.create('ellipse', width, height);
174
- }
175
-
176
- /**
177
- * Creates an arrow shape
178
- * @param direction Arrow direction
179
- * @param width Width in EMUs
180
- * @param height Height in EMUs
181
- * @returns New Shape instance
182
- */
183
- static createArrow(
184
- direction: 'right' | 'left' | 'up' | 'down',
185
- width: number,
186
- height: number
187
- ): Shape {
188
- const shapeType = `${direction}Arrow` as ShapeType;
189
- return Shape.create(shapeType, width, height);
190
- }
191
-
192
- /**
193
- * Creates a line shape
194
- * @param width Line width in EMUs
195
- * @returns New Shape instance
196
- */
197
- static createLine(width: number): Shape {
198
- // Lines are typically very thin in height
199
- return Shape.create('straightConnector1', width, 12700); // 12700 EMU ≈ 1pt height
200
- }
201
-
202
- /**
203
- * Gets the shape type
204
- * @returns Shape type
205
- */
206
- getShapeType(): ShapeType {
207
- return this.shapeType;
208
- }
209
-
210
- /**
211
- * Gets the shape width in EMUs
212
- * @returns Width
213
- */
214
- getWidth(): number {
215
- return this.width;
216
- }
217
-
218
- /**
219
- * Gets the shape height in EMUs
220
- * @returns Height
221
- */
222
- getHeight(): number {
223
- return this.height;
224
- }
225
-
226
- /**
227
- * Sets the shape fill color
228
- * @param color Fill color in hex (e.g., 'FF0000')
229
- * @param transparency Optional transparency percentage (0-100)
230
- * @returns This shape for chaining
231
- */
232
- setFill(color: string, transparency?: number): this {
233
- this.fill = { color, transparency };
234
- return this;
235
- }
236
-
237
- /**
238
- * Gets the fill configuration
239
- * @returns Fill or undefined
240
- */
241
- getFill(): ShapeFill | undefined {
242
- return this.fill;
243
- }
244
-
245
- /**
246
- * Sets the shape outline
247
- * @param color Outline color in hex
248
- * @param width Outline width in EMUs
249
- * @param style Line style
250
- * @returns This shape for chaining
251
- */
252
- setOutline(color: string, width: number, style?: 'solid' | 'dash' | 'dot' | 'dashDot'): this {
253
- this.outline = { color, width, style: style || 'solid' };
254
- return this;
255
- }
256
-
257
- /**
258
- * Gets the outline configuration
259
- * @returns Outline or undefined
260
- */
261
- getOutline(): ShapeOutline | undefined {
262
- return this.outline;
263
- }
264
-
265
- /**
266
- * Sets shape position (for floating shapes)
267
- * @param horizontal Horizontal positioning configuration
268
- * @param vertical Vertical positioning configuration
269
- * @returns This shape for chaining
270
- */
271
- setPosition(
272
- horizontal: { anchor: PositionAnchor; offset?: number; alignment?: HorizontalAlignment },
273
- vertical: { anchor: PositionAnchor; offset?: number; alignment?: VerticalAlignment }
274
- ): this {
275
- this.position = { horizontal, vertical };
276
- return this;
277
- }
278
-
279
- /**
280
- * Gets the position configuration
281
- * @returns Position or undefined
282
- */
283
- getPosition(): ImagePosition | undefined {
284
- return this.position;
285
- }
286
-
287
- /**
288
- * Sets anchor configuration (converts shape to floating)
289
- * @param options Anchor configuration
290
- * @returns This shape for chaining
291
- */
292
- setAnchor(options: ImageAnchor): this {
293
- this.anchor = options;
294
- return this;
295
- }
296
-
297
- /**
298
- * Gets the anchor configuration
299
- * @returns Anchor configuration or undefined
300
- */
301
- getAnchor(): ImageAnchor | undefined {
302
- return this.anchor;
303
- }
304
-
305
- /**
306
- * Sets the rotation angle
307
- * @param degrees Rotation in degrees (0-360)
308
- * @returns This shape for chaining
309
- */
310
- setRotation(degrees: number): this {
311
- this.rotation = ((degrees % 360) + 360) % 360;
312
- return this;
313
- }
314
-
315
- /**
316
- * Gets the rotation angle
317
- * @returns Rotation in degrees
318
- */
319
- getRotation(): number {
320
- return this.rotation;
321
- }
322
-
323
- /**
324
- * Sets text content within the shape
325
- * @param text Text content
326
- * @param formatting Optional text formatting
327
- * @returns This shape for chaining
328
- */
329
- setText(text: string, formatting?: RunFormatting): this {
330
- this.text = text;
331
- if (formatting) {
332
- this.textFormatting = formatting;
333
- }
334
- return this;
335
- }
336
-
337
- /**
338
- * Gets the text content
339
- * @returns Text or undefined
340
- */
341
- getText(): string | undefined {
342
- return this.text;
343
- }
344
-
345
- /**
346
- * Sets the shape name
347
- * @param name Shape name
348
- * @returns This shape for chaining
349
- */
350
- setName(name: string): this {
351
- this.name = name;
352
- return this;
353
- }
354
-
355
- /**
356
- * Gets the shape name
357
- * @returns Shape name
358
- */
359
- getName(): string {
360
- return this.name;
361
- }
362
-
363
- /**
364
- * Sets the shape description (for accessibility)
365
- * @param description Shape description
366
- * @returns This shape for chaining
367
- */
368
- setDescription(description: string): this {
369
- this.description = description;
370
- return this;
371
- }
372
-
373
- /**
374
- * Gets the shape description
375
- * @returns Shape description
376
- */
377
- getDescription(): string {
378
- return this.description;
379
- }
380
-
381
- /**
382
- * Sets the docPr ID (drawing object ID)
383
- * @param id Document property ID
384
- * @returns This shape for chaining
385
- */
386
- setDocPrId(id: number): this {
387
- this.docPrId = id;
388
- return this;
389
- }
390
-
391
- /**
392
- * Checks if this shape is floating (has anchor or position configuration)
393
- * @returns True if floating, false if inline
394
- */
395
- isFloating(): boolean {
396
- return this.anchor !== undefined || this.position !== undefined;
397
- }
398
-
399
- /**
400
- * Generates DrawingML XML for the shape
401
- * Creates either inline or floating (anchor) shape based on configuration
402
- * @returns XML element representing the shape
403
- */
404
- toXML(): XMLElement {
405
- // Choose between inline and anchor based on configuration
406
- const shapeElement = this.isFloating() ? this.createAnchor() : this.createInline();
407
-
408
- // Create the drawing structure
409
- return XMLBuilder.w('drawing', undefined, [shapeElement]);
410
- }
411
-
412
- /**
413
- * Creates the wp:inline element for inline shapes
414
- * @private
415
- */
416
- private createInline(): XMLElement {
417
- const children: XMLElement[] = [];
418
-
419
- // Extent (size)
420
- children.push({
421
- name: 'wp:extent',
422
- attributes: {
423
- cx: this.width.toString(),
424
- cy: this.height.toString(),
425
- },
426
- selfClosing: true,
427
- });
428
-
429
- // Effect extent (set to 0 for shapes)
430
- children.push({
431
- name: 'wp:effectExtent',
432
- attributes: {
433
- l: '0',
434
- t: '0',
435
- r: '0',
436
- b: '0',
437
- },
438
- selfClosing: true,
439
- });
440
-
441
- // Document properties
442
- children.push({
443
- name: 'wp:docPr',
444
- attributes: {
445
- id: this.docPrId.toString(),
446
- name: this.name,
447
- descr: this.description,
448
- },
449
- selfClosing: true,
450
- });
451
-
452
- // Non-visual graphic frame properties
453
- children.push({
454
- name: 'wp:cNvGraphicFramePr',
455
- selfClosing: true,
456
- });
457
-
458
- // Graphic data (the actual shape)
459
- children.push(this.createGraphic());
460
-
461
- return {
462
- name: 'wp:inline',
463
- attributes: {
464
- distT: '0',
465
- distB: '0',
466
- distL: '0',
467
- distR: '0',
468
- },
469
- children,
470
- };
471
- }
472
-
473
- /**
474
- * Creates the wp:anchor element for floating shapes
475
- * @private
476
- */
477
- private createAnchor(): XMLElement {
478
- const children: XMLElement[] = [];
479
-
480
- const anchorConfig = this.anchor || {
481
- behindDoc: false,
482
- locked: false,
483
- layoutInCell: true,
484
- allowOverlap: false,
485
- relativeHeight: 251658240,
486
- };
487
-
488
- // simplePos (required first child per CT_Anchor)
489
- children.push({
490
- name: 'wp:simplePos',
491
- attributes: { x: '0', y: '0' },
492
- selfClosing: true,
493
- });
494
-
495
- // Position H (horizontal)
496
- if (this.position) {
497
- const posH = this.position.horizontal;
498
- const posHChildren: XMLElement[] = [];
499
-
500
- if (posH.offset !== undefined) {
501
- posHChildren.push({
502
- name: 'wp:posOffset',
503
- children: [posH.offset.toString()],
504
- });
505
- } else if (posH.alignment) {
506
- posHChildren.push({
507
- name: 'wp:align',
508
- children: [posH.alignment],
509
- });
510
- }
511
-
512
- children.push({
513
- name: 'wp:positionH',
514
- attributes: {
515
- relativeFrom: posH.anchor,
516
- },
517
- children: posHChildren,
518
- });
519
- }
520
-
521
- // Position V (vertical)
522
- if (this.position) {
523
- const posV = this.position.vertical;
524
- const posVChildren: XMLElement[] = [];
525
-
526
- if (posV.offset !== undefined) {
527
- posVChildren.push({
528
- name: 'wp:posOffset',
529
- children: [posV.offset.toString()],
530
- });
531
- } else if (posV.alignment) {
532
- posVChildren.push({
533
- name: 'wp:align',
534
- children: [posV.alignment],
535
- });
536
- }
537
-
538
- children.push({
539
- name: 'wp:positionV',
540
- attributes: {
541
- relativeFrom: posV.anchor,
542
- },
543
- children: posVChildren,
544
- });
545
- }
546
-
547
- // Extent (size)
548
- children.push({
549
- name: 'wp:extent',
550
- attributes: {
551
- cx: this.width.toString(),
552
- cy: this.height.toString(),
553
- },
554
- selfClosing: true,
555
- });
556
-
557
- // Effect extent
558
- children.push({
559
- name: 'wp:effectExtent',
560
- attributes: {
561
- l: '0',
562
- t: '0',
563
- r: '0',
564
- b: '0',
565
- },
566
- selfClosing: true,
567
- });
568
-
569
- // Wrap square (default for shapes)
570
- children.push({
571
- name: 'wp:wrapSquare',
572
- attributes: {
573
- wrapText: 'bothSides',
574
- },
575
- selfClosing: true,
576
- });
577
-
578
- // Document properties
579
- children.push({
580
- name: 'wp:docPr',
581
- attributes: {
582
- id: this.docPrId.toString(),
583
- name: this.name,
584
- descr: this.description,
585
- },
586
- selfClosing: true,
587
- });
588
-
589
- // Non-visual graphic frame properties
590
- children.push({
591
- name: 'wp:cNvGraphicFramePr',
592
- selfClosing: true,
593
- });
594
-
595
- // Graphic data (the actual shape)
596
- children.push(this.createGraphic());
597
-
598
- return {
599
- name: 'wp:anchor',
600
- attributes: {
601
- distT: '0',
602
- distB: '0',
603
- distL: '0',
604
- distR: '0',
605
- simplePos: '0',
606
- relativeHeight: anchorConfig.relativeHeight.toString(),
607
- behindDoc: anchorConfig.behindDoc ? '1' : '0',
608
- locked: anchorConfig.locked ? '1' : '0',
609
- layoutInCell: anchorConfig.layoutInCell ? '1' : '0',
610
- allowOverlap: anchorConfig.allowOverlap ? '1' : '0',
611
- },
612
- children,
613
- };
614
- }
615
-
616
- /**
617
- * Creates the a:graphic element containing the shape
618
- * @private
619
- */
620
- private createGraphic(): XMLElement {
621
- return {
622
- name: 'a:graphic',
623
- attributes: {
624
- 'xmlns:a': 'http://schemas.openxmlformats.org/drawingml/2006/main',
625
- },
626
- children: [
627
- {
628
- name: 'a:graphicData',
629
- attributes: {
630
- uri: 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape',
631
- },
632
- children: [this.createWps()],
633
- },
634
- ],
635
- };
636
- }
637
-
638
- /**
639
- * Creates the wps:wsp element (WordprocessingShape)
640
- * @private
641
- */
642
- private createWps(): XMLElement {
643
- const children: XMLElement[] = [];
644
-
645
- // Non-visual shape properties
646
- children.push({
647
- name: 'wps:cNvSpPr',
648
- attributes: {
649
- 'xmlns:wps': 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape',
650
- },
651
- selfClosing: true,
652
- });
653
-
654
- // Shape properties (geometry, fill, outline)
655
- children.push(this.createSpPr());
656
-
657
- // Text box (if text is present)
658
- if (this.text) {
659
- children.push(this.createTextBox());
660
- }
661
-
662
- // Body properties (required per CT_WordprocessingShape)
663
- children.push({
664
- name: 'wps:bodyPr',
665
- attributes: {
666
- rot: '0',
667
- vert: 'horz',
668
- wrap: 'square',
669
- lIns: '91440',
670
- tIns: '45720',
671
- rIns: '91440',
672
- bIns: '45720',
673
- anchor: 't',
674
- anchorCtr: '0',
675
- upright: '1',
676
- },
677
- selfClosing: true,
678
- });
679
-
680
- return {
681
- name: 'wps:wsp',
682
- attributes: {
683
- 'xmlns:wps': 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape',
684
- },
685
- children,
686
- };
687
- }
688
-
689
- /**
690
- * Creates the wps:spPr element (shape properties)
691
- * @private
692
- */
693
- private createSpPr(): XMLElement {
694
- const children: XMLElement[] = [];
695
-
696
- // Transform (position and size)
697
- const xfrmAttrs = this.rotation > 0 ? { rot: (this.rotation * 60000).toString() } : undefined;
698
- children.push({
699
- name: 'a:xfrm',
700
- attributes: xfrmAttrs,
701
- children: [
702
- {
703
- name: 'a:off',
704
- attributes: { x: '0', y: '0' },
705
- selfClosing: true,
706
- },
707
- {
708
- name: 'a:ext',
709
- attributes: {
710
- cx: this.width.toString(),
711
- cy: this.height.toString(),
712
- },
713
- selfClosing: true,
714
- },
715
- ],
716
- });
717
-
718
- // Preset geometry (shape type)
719
- children.push({
720
- name: 'a:prstGeom',
721
- attributes: {
722
- prst: this.shapeType,
723
- },
724
- children: [
725
- {
726
- name: 'a:avLst',
727
- selfClosing: true,
728
- },
729
- ],
730
- });
731
-
732
- // Fill
733
- if (this.fill) {
734
- const fillChildren: XMLElement[] = [];
735
-
736
- // Color
737
- const colorAttrs: Record<string, string> = {
738
- val: this.fill.color.toUpperCase(),
739
- };
740
-
741
- // Transparency (alpha percentage)
742
- if (this.fill.transparency !== undefined) {
743
- // Convert 0-100 to 0-100000 (percentage * 1000)
744
- const alpha = 100 - this.fill.transparency; // Invert (0=transparent, 100=opaque)
745
- fillChildren.push({
746
- name: 'a:alpha',
747
- attributes: {
748
- val: Math.round(alpha * 1000).toString(),
749
- },
750
- selfClosing: true,
751
- });
752
- }
753
-
754
- children.push({
755
- name: 'a:solidFill',
756
- children: [
757
- {
758
- name: 'a:srgbClr',
759
- attributes: colorAttrs,
760
- ...(fillChildren.length > 0 ? { children: fillChildren } : { selfClosing: true }),
761
- },
762
- ],
763
- });
764
- }
765
-
766
- // Outline
767
- if (this.outline) {
768
- const lnAttrs: Record<string, string> = {
769
- w: this.outline.width.toString(),
770
- };
771
- if (this.outline.cap) lnAttrs.cap = this.outline.cap;
772
- if (this.outline.compound) lnAttrs.cmpd = this.outline.compound;
773
-
774
- const lnChildren: XMLElement[] = [];
775
-
776
- // Outline color
777
- lnChildren.push({
778
- name: 'a:solidFill',
779
- children: [
780
- {
781
- name: 'a:srgbClr',
782
- attributes: {
783
- val: this.outline.color.toUpperCase(),
784
- },
785
- selfClosing: true,
786
- },
787
- ],
788
- });
789
-
790
- // Line style (prstDash)
791
- if (this.outline.style && this.outline.style !== 'solid') {
792
- lnChildren.push({
793
- name: 'a:prstDash',
794
- attributes: {
795
- val: this.outline.style,
796
- },
797
- selfClosing: true,
798
- });
799
- }
800
-
801
- // Line join style
802
- if (this.outline.join) {
803
- const joinNames = { bevel: 'a:bevel', miter: 'a:miter', round: 'a:round' } as const;
804
- const joinName = joinNames[this.outline.join];
805
- if (joinName) {
806
- lnChildren.push({
807
- name: joinName,
808
- selfClosing: true,
809
- });
810
- }
811
- }
812
-
813
- children.push({
814
- name: 'a:ln',
815
- attributes: lnAttrs,
816
- children: lnChildren,
817
- });
818
- }
819
-
820
- return {
821
- name: 'wps:spPr',
822
- children,
823
- };
824
- }
825
-
826
- /**
827
- * Creates the wps:txbx element (text box within shape)
828
- * @private
829
- */
830
- private createTextBox(): XMLElement {
831
- if (!this.text) {
832
- return { name: 'wps:txbx', selfClosing: true };
833
- }
834
-
835
- // Build run properties
836
- const rPrChildren: XMLElement[] = [];
837
- if (this.textFormatting) {
838
- if (this.textFormatting.bold) {
839
- rPrChildren.push(XMLBuilder.wSelf('b'));
840
- }
841
- if (this.textFormatting.italic) {
842
- rPrChildren.push(XMLBuilder.wSelf('i'));
843
- }
844
- if (this.textFormatting.color) {
845
- rPrChildren.push(XMLBuilder.wSelf('color', { 'w:val': this.textFormatting.color }));
846
- }
847
- if (this.textFormatting.size) {
848
- const halfPoints = pointsToHalfPoints(this.textFormatting.size);
849
- rPrChildren.push(XMLBuilder.wSelf('sz', { 'w:val': halfPoints }));
850
- rPrChildren.push(XMLBuilder.wSelf('szCs', { 'w:val': halfPoints }));
851
- }
852
- }
853
-
854
- // Build paragraph with run
855
- const runChildren: XMLElement[] = [];
856
- if (rPrChildren.length > 0) {
857
- runChildren.push(XMLBuilder.w('rPr', undefined, rPrChildren));
858
- }
859
- runChildren.push(XMLBuilder.w('t', { 'xml:space': 'preserve' }, [this.text]));
860
-
861
- const paragraph = XMLBuilder.w('p', undefined, [XMLBuilder.w('r', undefined, runChildren)]);
862
-
863
- return {
864
- name: 'wps:txbx',
865
- children: [
866
- {
867
- name: 'w:txbxContent',
868
- children: [paragraph],
869
- },
870
- ],
871
- };
872
- }
873
- }
1
+ /**
2
+ * Shape - Represents a drawing shape in a Word document
3
+ *
4
+ * Shapes use DrawingML (a:) and WordprocessingML Drawing (wp:) namespaces,
5
+ * plus WordprocessingShape (wps:) namespace for Word 2010+ shape features.
6
+ *
7
+ * Per ECMA-376 Part 4 (Transitional) and Office Open XML extensions,
8
+ * shapes in Word documents use the wps:wsp element structure.
9
+ */
10
+
11
+ import { XMLBuilder, XMLElement } from '../xml/XMLBuilder';
12
+ import { inchesToEmus, pointsToHalfPoints } from '../utils/units';
13
+ import { RunFormatting } from './Run';
14
+ import {
15
+ ImagePosition,
16
+ ImageAnchor,
17
+ PositionAnchor,
18
+ HorizontalAlignment,
19
+ VerticalAlignment,
20
+ } from './Image';
21
+
22
+ /**
23
+ * Preset shape types available in DrawingML
24
+ * Based on ECMA-376 Part 1 §20.1.10.56 ST_ShapeType
25
+ */
26
+ export type ShapeType =
27
+ | 'rect' // Rectangle
28
+ | 'ellipse' // Circle/Ellipse
29
+ | 'rightArrow' // Right arrow
30
+ | 'leftArrow' // Left arrow
31
+ | 'upArrow' // Up arrow
32
+ | 'downArrow' // Down arrow
33
+ | 'straightConnector1' // Straight line
34
+ | 'roundRect' // Rounded rectangle
35
+ | 'triangle' // Triangle
36
+ | 'diamond'; // Diamond
37
+
38
+ /**
39
+ * Shape fill configuration
40
+ */
41
+ export interface ShapeFill {
42
+ /** Fill color in hex (e.g., 'FF0000' for red) */
43
+ color: string;
44
+ /** Transparency percentage (0-100, where 100 is fully transparent) */
45
+ transparency?: number;
46
+ }
47
+
48
+ /**
49
+ * Shape outline/border configuration
50
+ */
51
+ export interface ShapeOutline {
52
+ /** Outline color in hex */
53
+ color: string;
54
+ /** Outline width in EMUs */
55
+ width: number;
56
+ /** Line dash style */
57
+ style?:
58
+ | 'solid'
59
+ | 'dash'
60
+ | 'dot'
61
+ | 'dashDot'
62
+ | 'lgDash'
63
+ | 'lgDashDot'
64
+ | 'lgDashDotDot'
65
+ | 'sysDash'
66
+ | 'sysDot'
67
+ | 'sysDashDot'
68
+ | 'sysDashDotDot';
69
+ /** Line cap style (ECMA-376 §20.1.10.31) */
70
+ cap?: 'flat' | 'rnd' | 'sq';
71
+ /** Line join style (ECMA-376 §20.1.10.32) */
72
+ join?: 'bevel' | 'miter' | 'round';
73
+ /** Compound line type (ECMA-376 §20.1.10.15) */
74
+ compound?: 'sng' | 'dbl' | 'thickThin' | 'thinThick' | 'tri';
75
+ }
76
+
77
+ /**
78
+ * Shape properties
79
+ */
80
+ export interface ShapeProperties {
81
+ /** Shape type (preset geometry) */
82
+ shapeType: ShapeType;
83
+ /** Width in EMUs */
84
+ width: number;
85
+ /** Height in EMUs */
86
+ height: number;
87
+ /** Fill color and transparency */
88
+ fill?: ShapeFill;
89
+ /** Outline/border properties */
90
+ outline?: ShapeOutline;
91
+ /** Position configuration (reuses Image position types) */
92
+ position?: ImagePosition;
93
+ /** Anchor configuration (reuses Image anchor types) */
94
+ anchor?: ImageAnchor;
95
+ /** Rotation angle in degrees (0-360) */
96
+ rotation?: number;
97
+ /** Text content within the shape */
98
+ text?: string;
99
+ /** Text formatting */
100
+ textFormatting?: RunFormatting;
101
+ /** Shape name/title */
102
+ name?: string;
103
+ /** Shape description (for accessibility) */
104
+ description?: string;
105
+ }
106
+
107
+ /**
108
+ * Represents a drawing shape
109
+ */
110
+ export class Shape {
111
+ private shapeType: ShapeType;
112
+ private width: number;
113
+ private height: number;
114
+ private fill?: ShapeFill;
115
+ private outline?: ShapeOutline;
116
+ private position?: ImagePosition;
117
+ private anchor?: ImageAnchor;
118
+ private rotation = 0;
119
+ private text?: string;
120
+ private textFormatting?: RunFormatting;
121
+ private name: string;
122
+ private description: string;
123
+ private docPrId = 1;
124
+
125
+ /**
126
+ * Creates a new shape
127
+ * @param properties Shape properties
128
+ * @private Use static factory methods instead (create, createRectangle, etc.)
129
+ */
130
+ private constructor(properties: ShapeProperties) {
131
+ this.shapeType = properties.shapeType;
132
+ this.width = properties.width;
133
+ this.height = properties.height;
134
+ this.fill = properties.fill;
135
+ this.outline = properties.outline;
136
+ this.position = properties.position;
137
+ this.anchor = properties.anchor;
138
+ this.rotation = properties.rotation || 0;
139
+ this.text = properties.text;
140
+ this.textFormatting = properties.textFormatting;
141
+ this.name = properties.name || 'Shape';
142
+ this.description = properties.description || '';
143
+ }
144
+
145
+ /**
146
+ * Factory method for creating a shape
147
+ * @param shapeType Shape type
148
+ * @param width Width in EMUs (or use inchesToEmus)
149
+ * @param height Height in EMUs (or use inchesToEmus)
150
+ * @returns New Shape instance
151
+ * @example
152
+ * const rect = Shape.create('rect', inchesToEmus(2), inchesToEmus(1));
153
+ */
154
+ static create(shapeType: ShapeType, width: number, height: number): Shape {
155
+ return new Shape({ shapeType, width, height });
156
+ }
157
+
158
+ /**
159
+ * Creates a rectangle shape
160
+ * @param width Width in EMUs
161
+ * @param height Height in EMUs
162
+ * @returns New Shape instance
163
+ */
164
+ static createRectangle(width: number, height: number): Shape {
165
+ return Shape.create('rect', width, height);
166
+ }
167
+
168
+ /**
169
+ * Creates a circle shape
170
+ * @param diameter Diameter in EMUs
171
+ * @returns New Shape instance
172
+ */
173
+ static createCircle(diameter: number): Shape {
174
+ return Shape.create('ellipse', diameter, diameter);
175
+ }
176
+
177
+ /**
178
+ * Creates an ellipse shape
179
+ * @param width Width in EMUs
180
+ * @param height Height in EMUs
181
+ * @returns New Shape instance
182
+ */
183
+ static createEllipse(width: number, height: number): Shape {
184
+ return Shape.create('ellipse', width, height);
185
+ }
186
+
187
+ /**
188
+ * Creates an arrow shape
189
+ * @param direction Arrow direction
190
+ * @param width Width in EMUs
191
+ * @param height Height in EMUs
192
+ * @returns New Shape instance
193
+ */
194
+ static createArrow(
195
+ direction: 'right' | 'left' | 'up' | 'down',
196
+ width: number,
197
+ height: number
198
+ ): Shape {
199
+ const shapeType = `${direction}Arrow` as ShapeType;
200
+ return Shape.create(shapeType, width, height);
201
+ }
202
+
203
+ /**
204
+ * Creates a line shape
205
+ * @param width Line width in EMUs
206
+ * @returns New Shape instance
207
+ */
208
+ static createLine(width: number): Shape {
209
+ // Lines are typically very thin in height
210
+ return Shape.create('straightConnector1', width, 12700); // 12700 EMU ≈ 1pt height
211
+ }
212
+
213
+ /**
214
+ * Gets the shape type
215
+ * @returns Shape type
216
+ */
217
+ getShapeType(): ShapeType {
218
+ return this.shapeType;
219
+ }
220
+
221
+ /**
222
+ * Gets the shape width in EMUs
223
+ * @returns Width
224
+ */
225
+ getWidth(): number {
226
+ return this.width;
227
+ }
228
+
229
+ /**
230
+ * Gets the shape height in EMUs
231
+ * @returns Height
232
+ */
233
+ getHeight(): number {
234
+ return this.height;
235
+ }
236
+
237
+ /**
238
+ * Sets the shape fill color
239
+ * @param color Fill color in hex (e.g., 'FF0000')
240
+ * @param transparency Optional transparency percentage (0-100)
241
+ * @returns This shape for chaining
242
+ */
243
+ setFill(color: string, transparency?: number): this {
244
+ this.fill = { color, transparency };
245
+ return this;
246
+ }
247
+
248
+ /**
249
+ * Gets the fill configuration
250
+ * @returns Fill or undefined
251
+ */
252
+ getFill(): ShapeFill | undefined {
253
+ return this.fill;
254
+ }
255
+
256
+ /**
257
+ * Sets the shape outline
258
+ * @param color Outline color in hex
259
+ * @param width Outline width in EMUs
260
+ * @param style Line style
261
+ * @returns This shape for chaining
262
+ */
263
+ setOutline(color: string, width: number, style?: 'solid' | 'dash' | 'dot' | 'dashDot'): this {
264
+ this.outline = { color, width, style: style || 'solid' };
265
+ return this;
266
+ }
267
+
268
+ /**
269
+ * Gets the outline configuration
270
+ * @returns Outline or undefined
271
+ */
272
+ getOutline(): ShapeOutline | undefined {
273
+ return this.outline;
274
+ }
275
+
276
+ /**
277
+ * Sets shape position (for floating shapes)
278
+ * @param horizontal Horizontal positioning configuration
279
+ * @param vertical Vertical positioning configuration
280
+ * @returns This shape for chaining
281
+ */
282
+ setPosition(
283
+ horizontal: { anchor: PositionAnchor; offset?: number; alignment?: HorizontalAlignment },
284
+ vertical: { anchor: PositionAnchor; offset?: number; alignment?: VerticalAlignment }
285
+ ): this {
286
+ this.position = { horizontal, vertical };
287
+ return this;
288
+ }
289
+
290
+ /**
291
+ * Gets the position configuration
292
+ * @returns Position or undefined
293
+ */
294
+ getPosition(): ImagePosition | undefined {
295
+ return this.position;
296
+ }
297
+
298
+ /**
299
+ * Sets anchor configuration (converts shape to floating)
300
+ * @param options Anchor configuration
301
+ * @returns This shape for chaining
302
+ */
303
+ setAnchor(options: ImageAnchor): this {
304
+ this.anchor = options;
305
+ return this;
306
+ }
307
+
308
+ /**
309
+ * Gets the anchor configuration
310
+ * @returns Anchor configuration or undefined
311
+ */
312
+ getAnchor(): ImageAnchor | undefined {
313
+ return this.anchor;
314
+ }
315
+
316
+ /**
317
+ * Sets the rotation angle
318
+ * @param degrees Rotation in degrees (0-360)
319
+ * @returns This shape for chaining
320
+ */
321
+ setRotation(degrees: number): this {
322
+ this.rotation = ((degrees % 360) + 360) % 360;
323
+ return this;
324
+ }
325
+
326
+ /**
327
+ * Gets the rotation angle
328
+ * @returns Rotation in degrees
329
+ */
330
+ getRotation(): number {
331
+ return this.rotation;
332
+ }
333
+
334
+ /**
335
+ * Sets text content within the shape
336
+ * @param text Text content
337
+ * @param formatting Optional text formatting
338
+ * @returns This shape for chaining
339
+ */
340
+ setText(text: string, formatting?: RunFormatting): this {
341
+ this.text = text;
342
+ if (formatting) {
343
+ this.textFormatting = formatting;
344
+ }
345
+ return this;
346
+ }
347
+
348
+ /**
349
+ * Gets the text content
350
+ * @returns Text or undefined
351
+ */
352
+ getText(): string | undefined {
353
+ return this.text;
354
+ }
355
+
356
+ /**
357
+ * Sets the shape name
358
+ * @param name Shape name
359
+ * @returns This shape for chaining
360
+ */
361
+ setName(name: string): this {
362
+ this.name = name;
363
+ return this;
364
+ }
365
+
366
+ /**
367
+ * Gets the shape name
368
+ * @returns Shape name
369
+ */
370
+ getName(): string {
371
+ return this.name;
372
+ }
373
+
374
+ /**
375
+ * Sets the shape description (for accessibility)
376
+ * @param description Shape description
377
+ * @returns This shape for chaining
378
+ */
379
+ setDescription(description: string): this {
380
+ this.description = description;
381
+ return this;
382
+ }
383
+
384
+ /**
385
+ * Gets the shape description
386
+ * @returns Shape description
387
+ */
388
+ getDescription(): string {
389
+ return this.description;
390
+ }
391
+
392
+ /**
393
+ * Sets the docPr ID (drawing object ID)
394
+ * @param id Document property ID
395
+ * @returns This shape for chaining
396
+ */
397
+ setDocPrId(id: number): this {
398
+ this.docPrId = id;
399
+ return this;
400
+ }
401
+
402
+ /**
403
+ * Checks if this shape is floating (has anchor or position configuration)
404
+ * @returns True if floating, false if inline
405
+ */
406
+ isFloating(): boolean {
407
+ return this.anchor !== undefined || this.position !== undefined;
408
+ }
409
+
410
+ /**
411
+ * Generates DrawingML XML for the shape
412
+ * Creates either inline or floating (anchor) shape based on configuration
413
+ * @returns XML element representing the shape
414
+ */
415
+ toXML(): XMLElement {
416
+ // Choose between inline and anchor based on configuration
417
+ const shapeElement = this.isFloating() ? this.createAnchor() : this.createInline();
418
+
419
+ // Create the drawing structure
420
+ return XMLBuilder.w('drawing', undefined, [shapeElement]);
421
+ }
422
+
423
+ /**
424
+ * Creates the wp:inline element for inline shapes
425
+ * @private
426
+ */
427
+ private createInline(): XMLElement {
428
+ const children: XMLElement[] = [];
429
+
430
+ // Extent (size)
431
+ children.push({
432
+ name: 'wp:extent',
433
+ attributes: {
434
+ cx: this.width.toString(),
435
+ cy: this.height.toString(),
436
+ },
437
+ selfClosing: true,
438
+ });
439
+
440
+ // Effect extent (set to 0 for shapes)
441
+ children.push({
442
+ name: 'wp:effectExtent',
443
+ attributes: {
444
+ l: '0',
445
+ t: '0',
446
+ r: '0',
447
+ b: '0',
448
+ },
449
+ selfClosing: true,
450
+ });
451
+
452
+ // Document properties
453
+ children.push({
454
+ name: 'wp:docPr',
455
+ attributes: {
456
+ id: this.docPrId.toString(),
457
+ name: this.name,
458
+ descr: this.description,
459
+ },
460
+ selfClosing: true,
461
+ });
462
+
463
+ // Non-visual graphic frame properties
464
+ children.push({
465
+ name: 'wp:cNvGraphicFramePr',
466
+ selfClosing: true,
467
+ });
468
+
469
+ // Graphic data (the actual shape)
470
+ children.push(this.createGraphic());
471
+
472
+ return {
473
+ name: 'wp:inline',
474
+ attributes: {
475
+ distT: '0',
476
+ distB: '0',
477
+ distL: '0',
478
+ distR: '0',
479
+ },
480
+ children,
481
+ };
482
+ }
483
+
484
+ /**
485
+ * Creates the wp:anchor element for floating shapes
486
+ * @private
487
+ */
488
+ private createAnchor(): XMLElement {
489
+ const children: XMLElement[] = [];
490
+
491
+ const anchorConfig = this.anchor || {
492
+ behindDoc: false,
493
+ locked: false,
494
+ layoutInCell: true,
495
+ allowOverlap: false,
496
+ relativeHeight: 251658240,
497
+ };
498
+
499
+ // simplePos (required first child per CT_Anchor)
500
+ children.push({
501
+ name: 'wp:simplePos',
502
+ attributes: { x: '0', y: '0' },
503
+ selfClosing: true,
504
+ });
505
+
506
+ // Position H (horizontal)
507
+ if (this.position) {
508
+ const posH = this.position.horizontal;
509
+ const posHChildren: XMLElement[] = [];
510
+
511
+ if (posH.offset !== undefined) {
512
+ posHChildren.push({
513
+ name: 'wp:posOffset',
514
+ children: [posH.offset.toString()],
515
+ });
516
+ } else if (posH.alignment) {
517
+ posHChildren.push({
518
+ name: 'wp:align',
519
+ children: [posH.alignment],
520
+ });
521
+ }
522
+
523
+ children.push({
524
+ name: 'wp:positionH',
525
+ attributes: {
526
+ relativeFrom: posH.anchor,
527
+ },
528
+ children: posHChildren,
529
+ });
530
+ }
531
+
532
+ // Position V (vertical)
533
+ if (this.position) {
534
+ const posV = this.position.vertical;
535
+ const posVChildren: XMLElement[] = [];
536
+
537
+ if (posV.offset !== undefined) {
538
+ posVChildren.push({
539
+ name: 'wp:posOffset',
540
+ children: [posV.offset.toString()],
541
+ });
542
+ } else if (posV.alignment) {
543
+ posVChildren.push({
544
+ name: 'wp:align',
545
+ children: [posV.alignment],
546
+ });
547
+ }
548
+
549
+ children.push({
550
+ name: 'wp:positionV',
551
+ attributes: {
552
+ relativeFrom: posV.anchor,
553
+ },
554
+ children: posVChildren,
555
+ });
556
+ }
557
+
558
+ // Extent (size)
559
+ children.push({
560
+ name: 'wp:extent',
561
+ attributes: {
562
+ cx: this.width.toString(),
563
+ cy: this.height.toString(),
564
+ },
565
+ selfClosing: true,
566
+ });
567
+
568
+ // Effect extent
569
+ children.push({
570
+ name: 'wp:effectExtent',
571
+ attributes: {
572
+ l: '0',
573
+ t: '0',
574
+ r: '0',
575
+ b: '0',
576
+ },
577
+ selfClosing: true,
578
+ });
579
+
580
+ // Wrap square (default for shapes)
581
+ children.push({
582
+ name: 'wp:wrapSquare',
583
+ attributes: {
584
+ wrapText: 'bothSides',
585
+ },
586
+ selfClosing: true,
587
+ });
588
+
589
+ // Document properties
590
+ children.push({
591
+ name: 'wp:docPr',
592
+ attributes: {
593
+ id: this.docPrId.toString(),
594
+ name: this.name,
595
+ descr: this.description,
596
+ },
597
+ selfClosing: true,
598
+ });
599
+
600
+ // Non-visual graphic frame properties
601
+ children.push({
602
+ name: 'wp:cNvGraphicFramePr',
603
+ selfClosing: true,
604
+ });
605
+
606
+ // Graphic data (the actual shape)
607
+ children.push(this.createGraphic());
608
+
609
+ return {
610
+ name: 'wp:anchor',
611
+ attributes: {
612
+ distT: '0',
613
+ distB: '0',
614
+ distL: '0',
615
+ distR: '0',
616
+ simplePos: '0',
617
+ relativeHeight: anchorConfig.relativeHeight.toString(),
618
+ behindDoc: anchorConfig.behindDoc ? '1' : '0',
619
+ locked: anchorConfig.locked ? '1' : '0',
620
+ layoutInCell: anchorConfig.layoutInCell ? '1' : '0',
621
+ allowOverlap: anchorConfig.allowOverlap ? '1' : '0',
622
+ },
623
+ children,
624
+ };
625
+ }
626
+
627
+ /**
628
+ * Creates the a:graphic element containing the shape
629
+ * @private
630
+ */
631
+ private createGraphic(): XMLElement {
632
+ return {
633
+ name: 'a:graphic',
634
+ attributes: {
635
+ 'xmlns:a': 'http://schemas.openxmlformats.org/drawingml/2006/main',
636
+ },
637
+ children: [
638
+ {
639
+ name: 'a:graphicData',
640
+ attributes: {
641
+ uri: 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape',
642
+ },
643
+ children: [this.createWps()],
644
+ },
645
+ ],
646
+ };
647
+ }
648
+
649
+ /**
650
+ * Creates the wps:wsp element (WordprocessingShape)
651
+ * @private
652
+ */
653
+ private createWps(): XMLElement {
654
+ const children: XMLElement[] = [];
655
+
656
+ // Non-visual shape properties
657
+ children.push({
658
+ name: 'wps:cNvSpPr',
659
+ attributes: {
660
+ 'xmlns:wps': 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape',
661
+ },
662
+ selfClosing: true,
663
+ });
664
+
665
+ // Shape properties (geometry, fill, outline)
666
+ children.push(this.createSpPr());
667
+
668
+ // Text box (if text is present)
669
+ if (this.text) {
670
+ children.push(this.createTextBox());
671
+ }
672
+
673
+ // Body properties (required per CT_WordprocessingShape)
674
+ children.push({
675
+ name: 'wps:bodyPr',
676
+ attributes: {
677
+ rot: '0',
678
+ vert: 'horz',
679
+ wrap: 'square',
680
+ lIns: '91440',
681
+ tIns: '45720',
682
+ rIns: '91440',
683
+ bIns: '45720',
684
+ anchor: 't',
685
+ anchorCtr: '0',
686
+ upright: '1',
687
+ },
688
+ selfClosing: true,
689
+ });
690
+
691
+ return {
692
+ name: 'wps:wsp',
693
+ attributes: {
694
+ 'xmlns:wps': 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape',
695
+ },
696
+ children,
697
+ };
698
+ }
699
+
700
+ /**
701
+ * Creates the wps:spPr element (shape properties)
702
+ * @private
703
+ */
704
+ private createSpPr(): XMLElement {
705
+ const children: XMLElement[] = [];
706
+
707
+ // Transform (position and size)
708
+ const xfrmAttrs = this.rotation > 0 ? { rot: (this.rotation * 60000).toString() } : undefined;
709
+ children.push({
710
+ name: 'a:xfrm',
711
+ attributes: xfrmAttrs,
712
+ children: [
713
+ {
714
+ name: 'a:off',
715
+ attributes: { x: '0', y: '0' },
716
+ selfClosing: true,
717
+ },
718
+ {
719
+ name: 'a:ext',
720
+ attributes: {
721
+ cx: this.width.toString(),
722
+ cy: this.height.toString(),
723
+ },
724
+ selfClosing: true,
725
+ },
726
+ ],
727
+ });
728
+
729
+ // Preset geometry (shape type)
730
+ children.push({
731
+ name: 'a:prstGeom',
732
+ attributes: {
733
+ prst: this.shapeType,
734
+ },
735
+ children: [
736
+ {
737
+ name: 'a:avLst',
738
+ selfClosing: true,
739
+ },
740
+ ],
741
+ });
742
+
743
+ // Fill
744
+ if (this.fill) {
745
+ const fillChildren: XMLElement[] = [];
746
+
747
+ // Color
748
+ const colorAttrs: Record<string, string> = {
749
+ val: this.fill.color.toUpperCase(),
750
+ };
751
+
752
+ // Transparency (alpha percentage)
753
+ if (this.fill.transparency !== undefined) {
754
+ // Convert 0-100 to 0-100000 (percentage * 1000)
755
+ const alpha = 100 - this.fill.transparency; // Invert (0=transparent, 100=opaque)
756
+ fillChildren.push({
757
+ name: 'a:alpha',
758
+ attributes: {
759
+ val: Math.round(alpha * 1000).toString(),
760
+ },
761
+ selfClosing: true,
762
+ });
763
+ }
764
+
765
+ children.push({
766
+ name: 'a:solidFill',
767
+ children: [
768
+ {
769
+ name: 'a:srgbClr',
770
+ attributes: colorAttrs,
771
+ ...(fillChildren.length > 0 ? { children: fillChildren } : { selfClosing: true }),
772
+ },
773
+ ],
774
+ });
775
+ }
776
+
777
+ // Outline
778
+ if (this.outline) {
779
+ const lnAttrs: Record<string, string> = {
780
+ w: this.outline.width.toString(),
781
+ };
782
+ if (this.outline.cap) lnAttrs.cap = this.outline.cap;
783
+ if (this.outline.compound) lnAttrs.cmpd = this.outline.compound;
784
+
785
+ const lnChildren: XMLElement[] = [];
786
+
787
+ // Outline color
788
+ lnChildren.push({
789
+ name: 'a:solidFill',
790
+ children: [
791
+ {
792
+ name: 'a:srgbClr',
793
+ attributes: {
794
+ val: this.outline.color.toUpperCase(),
795
+ },
796
+ selfClosing: true,
797
+ },
798
+ ],
799
+ });
800
+
801
+ // Line style (prstDash)
802
+ if (this.outline.style && this.outline.style !== 'solid') {
803
+ lnChildren.push({
804
+ name: 'a:prstDash',
805
+ attributes: {
806
+ val: this.outline.style,
807
+ },
808
+ selfClosing: true,
809
+ });
810
+ }
811
+
812
+ // Line join style
813
+ if (this.outline.join) {
814
+ const joinNames = { bevel: 'a:bevel', miter: 'a:miter', round: 'a:round' } as const;
815
+ const joinName = joinNames[this.outline.join];
816
+ if (joinName) {
817
+ lnChildren.push({
818
+ name: joinName,
819
+ selfClosing: true,
820
+ });
821
+ }
822
+ }
823
+
824
+ children.push({
825
+ name: 'a:ln',
826
+ attributes: lnAttrs,
827
+ children: lnChildren,
828
+ });
829
+ }
830
+
831
+ return {
832
+ name: 'wps:spPr',
833
+ children,
834
+ };
835
+ }
836
+
837
+ /**
838
+ * Creates the wps:txbx element (text box within shape)
839
+ * @private
840
+ */
841
+ private createTextBox(): XMLElement {
842
+ if (!this.text) {
843
+ return { name: 'wps:txbx', selfClosing: true };
844
+ }
845
+
846
+ // Build run properties
847
+ const rPrChildren: XMLElement[] = [];
848
+ if (this.textFormatting) {
849
+ if (this.textFormatting.bold) {
850
+ rPrChildren.push(XMLBuilder.wSelf('b'));
851
+ }
852
+ if (this.textFormatting.italic) {
853
+ rPrChildren.push(XMLBuilder.wSelf('i'));
854
+ }
855
+ if (this.textFormatting.color) {
856
+ rPrChildren.push(XMLBuilder.wSelf('color', { 'w:val': this.textFormatting.color }));
857
+ }
858
+ if (this.textFormatting.size) {
859
+ const halfPoints = pointsToHalfPoints(this.textFormatting.size);
860
+ rPrChildren.push(XMLBuilder.wSelf('sz', { 'w:val': halfPoints }));
861
+ rPrChildren.push(XMLBuilder.wSelf('szCs', { 'w:val': halfPoints }));
862
+ }
863
+ }
864
+
865
+ // Build paragraph with run
866
+ const runChildren: XMLElement[] = [];
867
+ if (rPrChildren.length > 0) {
868
+ runChildren.push(XMLBuilder.w('rPr', undefined, rPrChildren));
869
+ }
870
+ runChildren.push(XMLBuilder.w('t', { 'xml:space': 'preserve' }, [this.text]));
871
+
872
+ const paragraph = XMLBuilder.w('p', undefined, [XMLBuilder.w('r', undefined, runChildren)]);
873
+
874
+ return {
875
+ name: 'wps:txbx',
876
+ children: [
877
+ {
878
+ name: 'w:txbxContent',
879
+ children: [paragraph],
880
+ },
881
+ ],
882
+ };
883
+ }
884
+ }