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,1879 +1,1785 @@
1
- /**
2
- * Style - Represents a style definition in a Word document
3
- * Supports paragraph, character, table, and numbering styles
4
- */
5
-
6
- import { XMLBuilder, XMLElement } from "../xml/XMLBuilder";
7
- import { ParagraphFormatting } from "../elements/Paragraph";
8
- import { RunFormatting } from "../elements/Run";
9
- import { ShadingConfig, ShadingPattern, buildShadingAttributes } from "../elements/CommonTypes";
10
- import { Heading2TableOptions } from "../types/styleConfig";
11
- import { deepClone } from "../utils/deepClone";
12
- import { pointsToHalfPoints } from "../utils/units";
13
-
14
- /**
15
- * Style type
16
- */
17
- export type StyleType = "paragraph" | "character" | "table" | "numbering";
18
-
19
- /**
20
- * Table alignment
21
- */
22
- export type TableAlignment = "left" | "center" | "right";
23
-
24
- /**
25
- * Border properties
26
- */
27
- export interface BorderProperties {
28
- /** Border style */
29
- style?: "none" | "single" | "double" | "dashed" | "dotted" | "thick";
30
- /** Border size in eighths of a point */
31
- size?: number;
32
- /** Border spacing/padding in points */
33
- space?: number;
34
- /** Border color (hex without #) */
35
- color?: string;
36
- }
37
-
38
- /**
39
- * Table borders (6 possible borders)
40
- */
41
- export interface TableBorders {
42
- top?: BorderProperties;
43
- bottom?: BorderProperties;
44
- left?: BorderProperties;
45
- right?: BorderProperties;
46
- /** Inside horizontal borders */
47
- insideH?: BorderProperties;
48
- /** Inside vertical borders */
49
- insideV?: BorderProperties;
50
- }
51
-
52
- /**
53
- * Cell borders (8 possible borders, includes diagonals)
54
- */
55
- export interface CellBorders extends TableBorders {
56
- /** Top-left to bottom-right diagonal */
57
- tl2br?: BorderProperties;
58
- /** Top-right to bottom-left diagonal */
59
- tr2bl?: BorderProperties;
60
- }
61
-
62
- /**
63
- * Shading properties
64
- * @see ShadingConfig in CommonTypes.ts for the canonical definition
65
- */
66
- export type ShadingProperties = ShadingConfig;
67
-
68
- /**
69
- * Cell margins
70
- */
71
- export interface CellMargins {
72
- /** Top margin in twips */
73
- top?: number;
74
- /** Bottom margin in twips */
75
- bottom?: number;
76
- /** Left margin in twips */
77
- left?: number;
78
- /** Right margin in twips */
79
- right?: number;
80
- }
81
-
82
- /**
83
- * Table-level formatting properties (tblPr)
84
- */
85
- export interface TableStyleFormatting {
86
- /** Table indentation from left margin in twips */
87
- indent?: number;
88
- /** Default cell spacing in twips */
89
- cellSpacing?: number;
90
- /** Table borders */
91
- borders?: TableBorders;
92
- /** Default cell margins */
93
- cellMargins?: CellMargins;
94
- /** Table background shading */
95
- shading?: ShadingProperties;
96
- /** Table alignment */
97
- alignment?: TableAlignment;
98
- }
99
-
100
- /**
101
- * Table cell formatting properties (tcPr)
102
- */
103
- export interface TableCellStyleFormatting {
104
- /** Cell borders (8 possible borders) */
105
- borders?: CellBorders;
106
- /** Cell background shading */
107
- shading?: ShadingProperties;
108
- /** Cell-specific margins */
109
- margins?: CellMargins;
110
- /** Vertical alignment in cell */
111
- verticalAlignment?: "top" | "center" | "bottom";
112
- }
113
-
114
- /**
115
- * Table row formatting properties (trPr)
116
- */
117
- export interface TableRowStyleFormatting {
118
- /** Prevent row from splitting across pages */
119
- cantSplit?: boolean;
120
- /** Mark row as header row */
121
- isHeader?: boolean;
122
- /** Row height in twips */
123
- height?: number;
124
- /** Row height rule */
125
- heightRule?: "auto" | "exact" | "atLeast";
126
- }
127
-
128
- /**
129
- * Conditional formatting type for table regions
130
- */
131
- export type ConditionalFormattingType =
132
- | "wholeTable" // Entire table
133
- | "firstRow" // First row
134
- | "lastRow" // Last row
135
- | "firstCol" // First column
136
- | "lastCol" // Last column
137
- | "band1Vert" // Odd column banding
138
- | "band2Vert" // Even column banding
139
- | "band1Horz" // Odd row banding
140
- | "band2Horz" // Even row banding
141
- | "nwCell" // Northwest (top-left) corner cell
142
- | "neCell" // Northeast (top-right) corner cell
143
- | "swCell" // Southwest (bottom-left) corner cell
144
- | "seCell"; // Southeast (bottom-right) corner cell
145
-
146
- /**
147
- * Conditional table formatting for a specific region
148
- */
149
- export interface ConditionalTableFormatting {
150
- /** Region type */
151
- type: ConditionalFormattingType;
152
- /** Paragraph formatting for this region */
153
- paragraphFormatting?: ParagraphFormatting;
154
- /** Run formatting for this region */
155
- runFormatting?: RunFormatting;
156
- /** Table formatting for this region */
157
- tableFormatting?: TableStyleFormatting;
158
- /** Cell formatting for this region */
159
- cellFormatting?: TableCellStyleFormatting;
160
- /** Row formatting for this region */
161
- rowFormatting?: TableRowStyleFormatting;
162
- }
163
-
164
- /**
165
- * Table style properties (Phase 5.1)
166
- */
167
- export interface TableStyleProperties {
168
- /** Table-level formatting */
169
- table?: TableStyleFormatting;
170
- /** Default cell formatting */
171
- cell?: TableCellStyleFormatting;
172
- /** Default row formatting */
173
- row?: TableRowStyleFormatting;
174
- /** Rows per band for row banding (default 1) */
175
- rowBandSize?: number;
176
- /** Columns per band for column banding (default 1) */
177
- colBandSize?: number;
178
- /** Conditional formatting for specific table regions */
179
- conditionalFormatting?: ConditionalTableFormatting[];
180
- }
181
-
182
- /**
183
- * Style properties
184
- */
185
- export interface StyleProperties {
186
- /** Unique style identifier */
187
- styleId: string;
188
- /** Display name */
189
- name: string;
190
- /** Style type */
191
- type: StyleType;
192
- /** Parent style ID for inheritance */
193
- basedOn?: string;
194
- /** Next style ID (auto-next paragraph style) */
195
- next?: string;
196
- /** Whether this is a default style */
197
- isDefault?: boolean;
198
- /** Whether this is a custom style */
199
- customStyle?: boolean;
200
- /** Paragraph formatting (for paragraph and table styles) */
201
- paragraphFormatting?: ParagraphFormatting;
202
- /** Numbering properties for styles that inherit list formatting */
203
- numPr?: {
204
- numId?: number;
205
- ilvl?: number;
206
- };
207
- /** Run formatting (for character and paragraph styles) */
208
- runFormatting?: RunFormatting;
209
- /** Table style properties (for table styles only - Phase 5.1) */
210
- tableStyle?: TableStyleProperties;
211
-
212
- // Style Gallery Metadata (Phase 5.3)
213
- /** Quick style - show in style gallery */
214
- qFormat?: boolean;
215
- /** UI priority - sort order in style picker (0-99, lower = higher priority) */
216
- uiPriority?: number;
217
- /** Semi-hidden - hide from gallery unless in use */
218
- semiHidden?: boolean;
219
- /** Unhide when used - auto-show when applied */
220
- unhideWhenUsed?: boolean;
221
- /** Locked - prevent modification */
222
- locked?: boolean;
223
- /** Personal - user-specific style */
224
- personal?: boolean;
225
- /** Personal compose - style for composing new messages */
226
- personalCompose?: boolean;
227
- /** Personal reply - style for replying to messages */
228
- personalReply?: boolean;
229
- /** Link - linked character/paragraph style ID */
230
- link?: string;
231
- /** Auto-redefine - update style from manual formatting */
232
- autoRedefine?: boolean;
233
- /** Aliases - alternative names (comma-separated) */
234
- aliases?: string;
235
-
236
- // Document Helper Metadata
237
- /**
238
- * Table options for Heading2 wrapping
239
- * Used by Document.applyCustomFormattingToExistingStyles() to configure
240
- * how Heading2 paragraphs are wrapped in tables
241
- */
242
- heading2TableOptions?: Heading2TableOptions;
243
- }
244
-
245
- /**
246
- * Represents a style definition
247
- */
248
- export class Style {
249
- private properties: StyleProperties;
250
-
251
- /**
252
- * Creates a new Style
253
- * @param properties - Style properties
254
- */
255
- constructor(properties: StyleProperties) {
256
- this.properties = { ...properties };
257
- }
258
-
259
- /**
260
- * Gets the style ID
261
- * @returns Style ID
262
- */
263
- getStyleId(): string {
264
- return this.properties.styleId;
265
- }
266
-
267
- /**
268
- * Gets the style name
269
- * @returns Style name
270
- */
271
- getName(): string {
272
- return this.properties.name;
273
- }
274
-
275
- /**
276
- * Gets the style type
277
- * @returns Style type
278
- */
279
- getType(): StyleType {
280
- return this.properties.type;
281
- }
282
-
283
- /**
284
- * Gets all style properties
285
- * @returns Style properties
286
- */
287
- getProperties(): StyleProperties {
288
- return { ...this.properties };
289
- }
290
-
291
- /**
292
- * Gets whether this is a default style
293
- * @returns True if this is a default style for its type
294
- */
295
- getIsDefault(): boolean {
296
- return this.properties.isDefault ?? false;
297
- }
298
-
299
- /**
300
- * Sets whether this is a default style
301
- * @param value - True to mark as default style
302
- * @returns This style for chaining
303
- */
304
- setIsDefault(value: boolean): this {
305
- this.properties.isDefault = value;
306
- return this;
307
- }
308
-
309
- /**
310
- * Sets the base style
311
- * @param styleId - Parent style ID
312
- * @returns This style for chaining
313
- */
314
- setBasedOn(styleId: string): this {
315
- this.properties.basedOn = styleId;
316
- return this;
317
- }
318
-
319
- /**
320
- * Sets the next style
321
- * @param styleId - Next style ID
322
- * @returns This style for chaining
323
- */
324
- setNext(styleId: string): this {
325
- this.properties.next = styleId;
326
- return this;
327
- }
328
-
329
- /**
330
- * Sets paragraph formatting
331
- * @param formatting - Paragraph formatting options
332
- * @returns This style for chaining
333
- */
334
- setParagraphFormatting(formatting: ParagraphFormatting): this {
335
- this.properties.paragraphFormatting = { ...formatting };
336
- return this;
337
- }
338
-
339
- /**
340
- * Sets run formatting
341
- * @param formatting - Run formatting options
342
- * @returns This style for chaining
343
- */
344
- setRunFormatting(formatting: RunFormatting): this {
345
- this.properties.runFormatting = { ...formatting };
346
- return this;
347
- }
348
-
349
- /**
350
- * Gets the current run formatting
351
- * @returns Run formatting or undefined if not set
352
- */
353
- getRunFormatting(): RunFormatting | undefined {
354
- return this.properties.runFormatting;
355
- }
356
-
357
- /**
358
- * Gets the current paragraph formatting
359
- * @returns Paragraph formatting or undefined if not set
360
- */
361
- getParagraphFormatting(): ParagraphFormatting | undefined {
362
- return this.properties.paragraphFormatting;
363
- }
364
-
365
- /**
366
- * Sets Heading2 table wrapping options
367
- * Used by Document.applyCustomFormattingToExistingStyles() when styleId is 'Heading2'
368
- * @param options - Table options for wrapping Heading2 paragraphs
369
- * @returns This style for chaining
370
- */
371
- setHeading2TableOptions(options: Heading2TableOptions): this {
372
- this.properties.heading2TableOptions = options;
373
- return this;
374
- }
375
-
376
- /**
377
- * Gets the current Heading2 table options
378
- * @returns Heading2 table options or undefined if not set
379
- */
380
- getHeading2TableOptions(): Heading2TableOptions | undefined {
381
- return this.properties.heading2TableOptions;
382
- }
383
-
384
- /**
385
- * Sets whether this is a quick style (appears in style gallery)
386
- * @param enabled - True to show in quick style gallery
387
- * @returns This style for chaining
388
- */
389
- setQFormat(enabled: boolean): this {
390
- this.properties.qFormat = enabled;
391
- return this;
392
- }
393
-
394
- /**
395
- * Sets the UI priority (sort order in style picker)
396
- * @param priority - Priority value (0-99, lower = higher priority)
397
- * @returns This style for chaining
398
- */
399
- setUiPriority(priority: number): this {
400
- if (priority < 0 || priority > 99) {
401
- throw new Error("UI priority must be between 0 and 99");
402
- }
403
- this.properties.uiPriority = priority;
404
- return this;
405
- }
406
-
407
- /**
408
- * Sets whether this style is semi-hidden (hidden from recommended list)
409
- * @param hidden - True to hide from recommended list
410
- * @returns This style for chaining
411
- */
412
- setSemiHidden(hidden: boolean): this {
413
- this.properties.semiHidden = hidden;
414
- return this;
415
- }
416
-
417
- /**
418
- * Sets whether to unhide this style when first used
419
- * @param enabled - True to auto-show when applied
420
- * @returns This style for chaining
421
- */
422
- setUnhideWhenUsed(enabled: boolean): this {
423
- this.properties.unhideWhenUsed = enabled;
424
- return this;
425
- }
426
-
427
- /**
428
- * Sets whether this style is locked (prevents modification)
429
- * @param locked - True to lock the style
430
- * @returns This style for chaining
431
- */
432
- setLocked(locked: boolean): this {
433
- this.properties.locked = locked;
434
- return this;
435
- }
436
-
437
- /**
438
- * Sets whether this is a personal style (user-specific)
439
- * @param personal - True to mark as personal
440
- * @returns This style for chaining
441
- */
442
- setPersonal(personal: boolean): this {
443
- this.properties.personal = personal;
444
- return this;
445
- }
446
-
447
- /**
448
- * Sets whether this is a personal compose style (for composing new messages)
449
- * @param enabled - True to mark as personal compose
450
- */
451
- setPersonalCompose(enabled: boolean): this {
452
- this.properties.personalCompose = enabled;
453
- return this;
454
- }
455
-
456
- /**
457
- * Gets whether this is a personal compose style
458
- */
459
- getPersonalCompose(): boolean {
460
- return this.properties.personalCompose === true;
461
- }
462
-
463
- /**
464
- * Sets whether this is a personal reply style (for replying to messages)
465
- * @param enabled - True to mark as personal reply
466
- */
467
- setPersonalReply(enabled: boolean): this {
468
- this.properties.personalReply = enabled;
469
- return this;
470
- }
471
-
472
- /**
473
- * Gets whether this is a personal reply style
474
- */
475
- getPersonalReply(): boolean {
476
- return this.properties.personalReply === true;
477
- }
478
-
479
- /**
480
- * Sets the linked style ID (for character/paragraph style linking)
481
- * @param styleId - ID of the linked style
482
- * @returns This style for chaining
483
- */
484
- setLink(styleId: string): this {
485
- this.properties.link = styleId;
486
- return this;
487
- }
488
-
489
- /**
490
- * Sets whether to auto-redefine this style from manual formatting
491
- * @param enabled - True to enable auto-redefine
492
- * @returns This style for chaining
493
- */
494
- setAutoRedefine(enabled: boolean): this {
495
- this.properties.autoRedefine = enabled;
496
- return this;
497
- }
498
-
499
- /**
500
- * Sets alternative names for this style (comma-separated)
501
- * @param aliases - Comma-separated list of alternative names
502
- * @returns This style for chaining
503
- */
504
- setAliases(aliases: string): this {
505
- this.properties.aliases = aliases;
506
- return this;
507
- }
508
-
509
- /**
510
- * Sets table-level formatting properties (Phase 5.1)
511
- * @param formatting - Table formatting options
512
- * @returns This style for chaining
513
- */
514
- setTableFormatting(formatting: TableStyleFormatting): this {
515
- if (!this.properties.tableStyle) {
516
- this.properties.tableStyle = {};
517
- }
518
- this.properties.tableStyle.table = { ...formatting };
519
- return this;
520
- }
521
-
522
- /**
523
- * Sets table cell formatting properties (Phase 5.1)
524
- * @param formatting - Cell formatting options
525
- * @returns This style for chaining
526
- */
527
- setTableCellFormatting(formatting: TableCellStyleFormatting): this {
528
- if (!this.properties.tableStyle) {
529
- this.properties.tableStyle = {};
530
- }
531
- this.properties.tableStyle.cell = { ...formatting };
532
- return this;
533
- }
534
-
535
- /**
536
- * Sets table row formatting properties (Phase 5.1)
537
- * @param formatting - Row formatting options
538
- * @returns This style for chaining
539
- */
540
- setTableRowFormatting(formatting: TableRowStyleFormatting): this {
541
- if (!this.properties.tableStyle) {
542
- this.properties.tableStyle = {};
543
- }
544
- this.properties.tableStyle.row = { ...formatting };
545
- return this;
546
- }
547
-
548
- /**
549
- * Sets row band size for row banding (Phase 5.1)
550
- * @param size - Number of rows per band (default 1)
551
- * @returns This style for chaining
552
- */
553
- setRowBandSize(size: number): this {
554
- if (!this.properties.tableStyle) {
555
- this.properties.tableStyle = {};
556
- }
557
- if (size < 0) {
558
- throw new Error("Row band size must be non-negative");
559
- }
560
- this.properties.tableStyle.rowBandSize = size;
561
- return this;
562
- }
563
-
564
- /**
565
- * Sets column band size for column banding (Phase 5.1)
566
- * @param size - Number of columns per band (default 1)
567
- * @returns This style for chaining
568
- */
569
- setColBandSize(size: number): this {
570
- if (!this.properties.tableStyle) {
571
- this.properties.tableStyle = {};
572
- }
573
- if (size < 0) {
574
- throw new Error("Column band size must be non-negative");
575
- }
576
- this.properties.tableStyle.colBandSize = size;
577
- return this;
578
- }
579
-
580
- /**
581
- * Adds conditional formatting for a specific table region (Phase 5.1)
582
- * @param conditional - Conditional formatting definition
583
- * @returns This style for chaining
584
- */
585
- addConditionalFormatting(conditional: ConditionalTableFormatting): this {
586
- if (!this.properties.tableStyle) {
587
- this.properties.tableStyle = {};
588
- }
589
- if (!this.properties.tableStyle.conditionalFormatting) {
590
- this.properties.tableStyle.conditionalFormatting = [];
591
- }
592
- this.properties.tableStyle.conditionalFormatting.push({ ...conditional });
593
- return this;
594
- }
595
-
596
- /**
597
- * Validates that this style definition is valid
598
- *
599
- * Checks:
600
- * - Required fields (styleId, name, type)
601
- * - Valid type value
602
- * - No circular references (basedOn != styleId)
603
- * - Valid formatting values
604
- *
605
- * @returns True if the style is valid, false otherwise
606
- */
607
- isValid(): boolean {
608
- try {
609
- // Required fields
610
- if (
611
- !this.properties.styleId ||
612
- !this.properties.name ||
613
- !this.properties.type
614
- ) {
615
- return false;
616
- }
617
-
618
- // Valid type
619
- const validTypes: StyleType[] = [
620
- "paragraph",
621
- "character",
622
- "table",
623
- "numbering",
624
- ];
625
- if (!validTypes.includes(this.properties.type)) {
626
- return false;
627
- }
628
-
629
- // No circular reference
630
- if (this.properties.basedOn === this.properties.styleId) {
631
- return false;
632
- }
633
-
634
- // Check paragraph formatting if present
635
- if (this.properties.paragraphFormatting) {
636
- const pf = this.properties.paragraphFormatting;
637
-
638
- // Check alignment
639
- if (pf.alignment) {
640
- const validAlignments = [
641
- "left",
642
- "center",
643
- "right",
644
- "justify",
645
- "both",
646
- "distribute",
647
- ];
648
- if (!validAlignments.includes(pf.alignment)) {
649
- return false;
650
- }
651
- }
652
-
653
- // Check spacing values
654
- if (pf.spacing) {
655
- const spacing = pf.spacing;
656
- if (spacing.before !== undefined && spacing.before < 0) return false;
657
- if (spacing.after !== undefined && spacing.after < 0) return false;
658
- if (spacing.line !== undefined && spacing.line < 0) return false;
659
- if (
660
- spacing.lineRule &&
661
- !["auto", "exact", "atLeast"].includes(spacing.lineRule)
662
- ) {
663
- return false;
664
- }
665
- }
666
-
667
- // Check indentation values
668
- if (pf.indentation) {
669
- const ind = pf.indentation;
670
- // Indentation values can be negative for hanging indent
671
- if (ind.left !== undefined && ind.left < -100000) return false;
672
- if (ind.right !== undefined && ind.right < -100000) return false;
673
- }
674
- }
675
-
676
- // Check run formatting if present
677
- if (this.properties.runFormatting) {
678
- const rf = this.properties.runFormatting;
679
-
680
- // Check font size
681
- if (rf.size !== undefined && (rf.size <= 0 || rf.size > 1638)) {
682
- return false; // Max font size in Word is 1638
683
- }
684
-
685
- // Check color format (should be 6 hex characters)
686
- if (rf.color && !/^[0-9A-Fa-f]{6}$/.test(rf.color)) {
687
- return false;
688
- }
689
-
690
- // Check highlight color
691
- if (rf.highlight) {
692
- const validHighlights = [
693
- "black",
694
- "blue",
695
- "cyan",
696
- "darkBlue",
697
- "darkCyan",
698
- "darkGray",
699
- "darkGreen",
700
- "darkMagenta",
701
- "darkRed",
702
- "darkYellow",
703
- "green",
704
- "lightGray",
705
- "magenta",
706
- "none",
707
- "red",
708
- "white",
709
- "yellow",
710
- ];
711
- if (!validHighlights.includes(rf.highlight)) {
712
- return false;
713
- }
714
- }
715
- }
716
-
717
- // Check metadata properties (Phase 5.3)
718
- if (this.properties.uiPriority !== undefined) {
719
- if (this.properties.uiPriority < 0 || this.properties.uiPriority > 99) {
720
- return false;
721
- }
722
- }
723
-
724
- // Check linked style doesn't reference self
725
- if (this.properties.link === this.properties.styleId) {
726
- return false;
727
- }
728
-
729
- return true;
730
- } catch {
731
- return false;
732
- }
733
- }
734
-
735
- /**
736
- * Converts the style to WordprocessingML XML element
737
- * @returns XMLElement representing the style
738
- */
739
- toXML(): XMLElement {
740
- const styleAttrs: Record<string, string> = {
741
- "w:type": this.properties.type,
742
- "w:styleId": this.properties.styleId,
743
- };
744
-
745
- if (this.properties.isDefault) {
746
- styleAttrs["w:default"] = "1";
747
- }
748
-
749
- if (this.properties.customStyle) {
750
- styleAttrs["w:customStyle"] = "1";
751
- }
752
-
753
- const styleChildren: XMLElement[] = [];
754
-
755
- // Add style name
756
- styleChildren.push(
757
- XMLBuilder.wSelf("name", { "w:val": this.properties.name })
758
- );
759
-
760
- // aliases - Alternative names (must follow name per CT_Style)
761
- if (this.properties.aliases) {
762
- styleChildren.push(
763
- XMLBuilder.wSelf("aliases", { "w:val": this.properties.aliases })
764
- );
765
- }
766
-
767
- // Add basedOn
768
- if (this.properties.basedOn) {
769
- styleChildren.push(
770
- XMLBuilder.wSelf("basedOn", { "w:val": this.properties.basedOn })
771
- );
772
- }
773
-
774
- // Add next
775
- if (this.properties.next) {
776
- styleChildren.push(
777
- XMLBuilder.wSelf("next", { "w:val": this.properties.next })
778
- );
779
- }
780
-
781
- // Add link (linked character/paragraph style)
782
- if (this.properties.link) {
783
- styleChildren.push(
784
- XMLBuilder.wSelf("link", { "w:val": this.properties.link })
785
- );
786
- }
787
-
788
- // Add autoRedefine
789
- if (this.properties.autoRedefine) {
790
- styleChildren.push(XMLBuilder.wSelf("autoRedefine"));
791
- }
792
-
793
- // Add metadata properties — ordered per ECMA-376 CT_Style
794
- // uiPriority - Sort order in style picker
795
- if (this.properties.uiPriority !== undefined) {
796
- styleChildren.push(
797
- XMLBuilder.wSelf("uiPriority", {
798
- "w:val": String(this.properties.uiPriority),
799
- })
800
- );
801
- }
802
-
803
- // semiHidden - Hide from recommended list
804
- if (this.properties.semiHidden) {
805
- styleChildren.push(XMLBuilder.wSelf("semiHidden"));
806
- }
807
-
808
- // unhideWhenUsed - Auto-show when applied
809
- if (this.properties.unhideWhenUsed) {
810
- styleChildren.push(XMLBuilder.wSelf("unhideWhenUsed"));
811
- }
812
-
813
- // qFormat - Quick style gallery appearance
814
- if (this.properties.qFormat !== undefined) {
815
- if (this.properties.qFormat) {
816
- styleChildren.push(XMLBuilder.wSelf("qFormat"));
817
- }
818
- } else if (!this.properties.customStyle) {
819
- // Default: built-in styles have qFormat
820
- styleChildren.push(XMLBuilder.wSelf("qFormat"));
821
- }
822
-
823
- // locked - Prevent modification
824
- if (this.properties.locked) {
825
- styleChildren.push(XMLBuilder.wSelf("locked"));
826
- }
827
-
828
- // personal - User-specific style
829
- if (this.properties.personal) {
830
- styleChildren.push(XMLBuilder.wSelf("personal"));
831
- }
832
-
833
- // personalCompose - Style for composing new messages
834
- if (this.properties.personalCompose) {
835
- styleChildren.push(XMLBuilder.wSelf("personalCompose"));
836
- }
837
-
838
- // personalReply - Style for replying to messages
839
- if (this.properties.personalReply) {
840
- styleChildren.push(XMLBuilder.wSelf("personalReply"));
841
- }
842
-
843
- // Add paragraph properties
844
- if (this.properties.paragraphFormatting || this.properties.numPr) {
845
- const pPr = this.generateParagraphProperties(
846
- this.properties.paragraphFormatting || {}
847
- );
848
- // Add numPr (numbering properties) if present - styles can inherit list formatting
849
- if (this.properties.numPr) {
850
- const numPrChildren: XMLElement[] = [];
851
- if (this.properties.numPr.ilvl !== undefined) {
852
- numPrChildren.push(
853
- XMLBuilder.wSelf("ilvl", { "w:val": String(this.properties.numPr.ilvl) })
854
- );
855
- }
856
- if (this.properties.numPr.numId !== undefined) {
857
- numPrChildren.push(
858
- XMLBuilder.wSelf("numId", { "w:val": String(this.properties.numPr.numId) })
859
- );
860
- }
861
- if (numPrChildren.length > 0) {
862
- // Insert numPr at the beginning of pPr children (per ECMA-376 element order)
863
- if (pPr.children) {
864
- pPr.children.unshift(XMLBuilder.w("numPr", undefined, numPrChildren));
865
- } else {
866
- pPr.children = [XMLBuilder.w("numPr", undefined, numPrChildren)];
867
- }
868
- }
869
- }
870
- if (pPr.children && pPr.children.length > 0) {
871
- styleChildren.push(pPr);
872
- }
873
- }
874
-
875
- // Add run properties
876
- if (this.properties.runFormatting) {
877
- const rPr = this.generateRunProperties(this.properties.runFormatting);
878
- if (rPr.children && rPr.children.length > 0) {
879
- styleChildren.push(rPr);
880
- }
881
- }
882
-
883
- // Add table style properties (Phase 5.1)
884
- if (this.properties.tableStyle) {
885
- // Add tblPr (table properties)
886
- if (this.properties.tableStyle.table) {
887
- const tblPr = this.generateTableProperties(
888
- this.properties.tableStyle.table,
889
- this.properties.tableStyle
890
- );
891
- if (tblPr.children && tblPr.children.length > 0) {
892
- styleChildren.push(tblPr);
893
- }
894
- }
895
-
896
- // Add trPr (table row properties) — per CT_Style: tblPr → trPr → tcPr
897
- if (this.properties.tableStyle.row) {
898
- const trPr = this.generateTableRowProperties(
899
- this.properties.tableStyle.row
900
- );
901
- if (trPr.children && trPr.children.length > 0) {
902
- styleChildren.push(trPr);
903
- }
904
- }
905
-
906
- // Add tcPr (table cell properties)
907
- if (this.properties.tableStyle.cell) {
908
- const tcPr = this.generateTableCellProperties(
909
- this.properties.tableStyle.cell
910
- );
911
- if (tcPr.children && tcPr.children.length > 0) {
912
- styleChildren.push(tcPr);
913
- }
914
- }
915
-
916
- // Add conditional formatting (tblStylePr)
917
- if (this.properties.tableStyle.conditionalFormatting) {
918
- for (const conditional of this.properties.tableStyle
919
- .conditionalFormatting) {
920
- const tblStylePr = this.generateConditionalFormatting(conditional);
921
- if (tblStylePr.children && tblStylePr.children.length > 0) {
922
- styleChildren.push(tblStylePr);
923
- }
924
- }
925
- }
926
- }
927
-
928
- return XMLBuilder.w("style", styleAttrs, styleChildren);
929
- }
930
-
931
- /**
932
- * Generates paragraph properties XML
933
- */
934
- private generateParagraphProperties(
935
- formatting: ParagraphFormatting
936
- ): XMLElement {
937
- const pPrChildren: XMLElement[] = [];
938
-
939
- // Ordered per ECMA-376 CT_PPrBase
940
- if (formatting.keepNext) {
941
- pPrChildren.push(XMLBuilder.wSelf("keepNext"));
942
- }
943
- if (formatting.keepLines) {
944
- pPrChildren.push(XMLBuilder.wSelf("keepLines"));
945
- }
946
- if (formatting.pageBreakBefore) {
947
- pPrChildren.push(XMLBuilder.wSelf("pageBreakBefore"));
948
- }
949
-
950
- // Add spacing
951
- if (formatting.spacing) {
952
- const spc = formatting.spacing;
953
- const attributes: Record<string, number | string> = {};
954
- if (spc.before !== undefined) attributes["w:before"] = spc.before;
955
- if (spc.after !== undefined) attributes["w:after"] = spc.after;
956
- if (spc.line !== undefined) attributes["w:line"] = spc.line;
957
- if (spc.lineRule) attributes["w:lineRule"] = spc.lineRule;
958
- if (Object.keys(attributes).length > 0) {
959
- pPrChildren.push(XMLBuilder.wSelf("spacing", attributes));
960
- }
961
- }
962
-
963
- // Add indentation
964
- if (formatting.indentation) {
965
- const ind = formatting.indentation;
966
- const attributes: Record<string, number> = {};
967
- if (ind.left !== undefined) attributes["w:left"] = ind.left;
968
- if (ind.right !== undefined) attributes["w:right"] = ind.right;
969
- if (ind.firstLine !== undefined)
970
- attributes["w:firstLine"] = ind.firstLine;
971
- if (ind.hanging !== undefined) attributes["w:hanging"] = ind.hanging;
972
- if (Object.keys(attributes).length > 0) {
973
- pPrChildren.push(XMLBuilder.wSelf("ind", attributes));
974
- }
975
- }
976
-
977
- // Contextual spacing per ECMA-376 Part 1 §17.3.1.8
978
- // Removes spacing between paragraphs of the same style
979
- if (formatting.contextualSpacing) {
980
- pPrChildren.push(XMLBuilder.wSelf("contextualSpacing", { "w:val": "1" }));
981
- }
982
-
983
- // Add alignment
984
- if (formatting.alignment) {
985
- // Map 'justify' to 'both' per ECMA-376 (Word uses 'both' for justified text)
986
- const alignmentValue =
987
- formatting.alignment === "justify" ? "both" : formatting.alignment;
988
- pPrChildren.push(XMLBuilder.wSelf("jc", { "w:val": alignmentValue }));
989
- }
990
-
991
- // Add outline level for TOC support (Heading 1 = 0, Heading 2 = 1, etc.)
992
- if (formatting.outlineLevel !== undefined) {
993
- pPrChildren.push(
994
- XMLBuilder.wSelf("outlineLvl", { "w:val": formatting.outlineLevel.toString() })
995
- );
996
- }
997
-
998
- return XMLBuilder.w("pPr", undefined, pPrChildren);
999
- }
1000
-
1001
- /**
1002
- * Generates run properties XML
1003
- */
1004
- private generateRunProperties(formatting: RunFormatting): XMLElement {
1005
- const rPrChildren: XMLElement[] = [];
1006
-
1007
- // Add formatting elements — ordered per ECMA-376 CT_RPr
1008
- if (formatting.font) {
1009
- rPrChildren.push(
1010
- XMLBuilder.wSelf("rFonts", {
1011
- "w:ascii": formatting.font,
1012
- "w:hAnsi": formatting.font,
1013
- "w:cs": formatting.font,
1014
- })
1015
- );
1016
- }
1017
- if (formatting.bold) {
1018
- rPrChildren.push(XMLBuilder.wSelf("b"));
1019
- }
1020
- if (formatting.italic) {
1021
- rPrChildren.push(XMLBuilder.wSelf("i"));
1022
- }
1023
- if (formatting.allCaps) {
1024
- rPrChildren.push(XMLBuilder.wSelf("caps"));
1025
- }
1026
- if (formatting.smallCaps) {
1027
- rPrChildren.push(XMLBuilder.wSelf("smallCaps"));
1028
- }
1029
- if (formatting.strike) {
1030
- rPrChildren.push(XMLBuilder.wSelf("strike"));
1031
- }
1032
- if (formatting.dstrike) {
1033
- rPrChildren.push(XMLBuilder.wSelf("dstrike"));
1034
- }
1035
- if (formatting.color) {
1036
- rPrChildren.push(
1037
- XMLBuilder.wSelf("color", { "w:val": formatting.color })
1038
- );
1039
- }
1040
- if (formatting.size !== undefined) {
1041
- const halfPoints = pointsToHalfPoints(formatting.size);
1042
- rPrChildren.push(XMLBuilder.wSelf("sz", { "w:val": halfPoints }));
1043
- rPrChildren.push(XMLBuilder.wSelf("szCs", { "w:val": halfPoints }));
1044
- }
1045
- if (formatting.highlight) {
1046
- rPrChildren.push(
1047
- XMLBuilder.wSelf("highlight", { "w:val": formatting.highlight })
1048
- );
1049
- }
1050
- if (formatting.underline) {
1051
- const underlineValue =
1052
- typeof formatting.underline === "string"
1053
- ? formatting.underline
1054
- : "single";
1055
- rPrChildren.push(XMLBuilder.wSelf("u", { "w:val": underlineValue }));
1056
- }
1057
- if (formatting.subscript) {
1058
- rPrChildren.push(XMLBuilder.wSelf("vertAlign", { "w:val": "subscript" }));
1059
- }
1060
- if (formatting.superscript) {
1061
- rPrChildren.push(
1062
- XMLBuilder.wSelf("vertAlign", { "w:val": "superscript" })
1063
- );
1064
- }
1065
-
1066
- return XMLBuilder.w("rPr", undefined, rPrChildren);
1067
- }
1068
-
1069
- /**
1070
- * Generates table properties XML (tblPr) - Phase 5.1
1071
- */
1072
- private generateTableProperties(
1073
- formatting: TableStyleFormatting,
1074
- tableStyle: TableStyleProperties,
1075
- isConditional = false
1076
- ): XMLElement {
1077
- const tblPrChildren: XMLElement[] = [];
1078
-
1079
- // Ordered per CT_TblPrBase (ECMA-376 §17.4.60):
1080
- // tblStyleRowBandSize → tblStyleColBandSize → tblW → jc →
1081
- // tblCellSpacing → tblInd → tblBorders → shd → tblCellMar
1082
-
1083
- // Row band size (must come first)
1084
- if (tableStyle.rowBandSize !== undefined) {
1085
- tblPrChildren.push(
1086
- XMLBuilder.wSelf("tblStyleRowBandSize", {
1087
- "w:val": tableStyle.rowBandSize,
1088
- })
1089
- );
1090
- }
1091
-
1092
- // Column band size
1093
- if (tableStyle.colBandSize !== undefined) {
1094
- tblPrChildren.push(
1095
- XMLBuilder.wSelf("tblStyleColBandSize", {
1096
- "w:val": tableStyle.colBandSize,
1097
- })
1098
- );
1099
- }
1100
-
1101
- // Note: tblW is omitted for table styles. It's only valid in inline tblPr
1102
- // (CT_TblPr in table elements), not in CT_Style's tblPr.
1103
-
1104
- // Table alignment
1105
- if (formatting.alignment) {
1106
- tblPrChildren.push(
1107
- XMLBuilder.wSelf("jc", { "w:val": formatting.alignment })
1108
- );
1109
- }
1110
-
1111
- // Cell spacing
1112
- if (formatting.cellSpacing !== undefined) {
1113
- tblPrChildren.push(
1114
- XMLBuilder.wSelf("tblCellSpacing", {
1115
- "w:w": formatting.cellSpacing,
1116
- "w:type": "dxa",
1117
- })
1118
- );
1119
- }
1120
-
1121
- // Table indentation
1122
- if (formatting.indent !== undefined) {
1123
- tblPrChildren.push(
1124
- XMLBuilder.wSelf("tblInd", {
1125
- "w:w": formatting.indent,
1126
- "w:type": "dxa",
1127
- })
1128
- );
1129
- }
1130
-
1131
- // Table borders
1132
- if (formatting.borders) {
1133
- const borderElements = this.generateBorderElements(
1134
- formatting.borders,
1135
- false
1136
- );
1137
- if (borderElements.length > 0) {
1138
- tblPrChildren.push(
1139
- XMLBuilder.w("tblBorders", undefined, borderElements)
1140
- );
1141
- }
1142
- }
1143
-
1144
- // Table shading (not valid in tblStylePr conditional context)
1145
- if (formatting.shading && !isConditional) {
1146
- tblPrChildren.push(this.generateShadingElement(formatting.shading));
1147
- }
1148
-
1149
- // Cell margins
1150
- if (formatting.cellMargins) {
1151
- const marginElements: XMLElement[] = [];
1152
- if (formatting.cellMargins.top !== undefined) {
1153
- marginElements.push(
1154
- XMLBuilder.wSelf("top", {
1155
- "w:w": formatting.cellMargins.top,
1156
- "w:type": "dxa",
1157
- })
1158
- );
1159
- }
1160
- if (formatting.cellMargins.left !== undefined) {
1161
- marginElements.push(
1162
- XMLBuilder.wSelf("left", {
1163
- "w:w": formatting.cellMargins.left,
1164
- "w:type": "dxa",
1165
- })
1166
- );
1167
- }
1168
- if (formatting.cellMargins.bottom !== undefined) {
1169
- marginElements.push(
1170
- XMLBuilder.wSelf("bottom", {
1171
- "w:w": formatting.cellMargins.bottom,
1172
- "w:type": "dxa",
1173
- })
1174
- );
1175
- }
1176
- if (formatting.cellMargins.right !== undefined) {
1177
- marginElements.push(
1178
- XMLBuilder.wSelf("right", {
1179
- "w:w": formatting.cellMargins.right,
1180
- "w:type": "dxa",
1181
- })
1182
- );
1183
- }
1184
- if (marginElements.length > 0) {
1185
- tblPrChildren.push(
1186
- XMLBuilder.w("tblCellMar", undefined, marginElements)
1187
- );
1188
- }
1189
- }
1190
-
1191
- return XMLBuilder.w("tblPr", undefined, tblPrChildren);
1192
- }
1193
-
1194
- /**
1195
- * Generates table cell properties XML (tcPr) - Phase 5.1
1196
- */
1197
- private generateTableCellProperties(
1198
- formatting: TableCellStyleFormatting
1199
- ): XMLElement {
1200
- const tcPrChildren: XMLElement[] = [];
1201
-
1202
- // Cell borders
1203
- if (formatting.borders) {
1204
- const borderElements = this.generateBorderElements(
1205
- formatting.borders,
1206
- true
1207
- );
1208
- if (borderElements.length > 0) {
1209
- tcPrChildren.push(XMLBuilder.w("tcBorders", undefined, borderElements));
1210
- }
1211
- }
1212
-
1213
- // Cell shading
1214
- if (formatting.shading) {
1215
- tcPrChildren.push(this.generateShadingElement(formatting.shading));
1216
- }
1217
-
1218
- // Cell margins
1219
- if (formatting.margins) {
1220
- const marginElements: XMLElement[] = [];
1221
- if (formatting.margins.top !== undefined) {
1222
- marginElements.push(
1223
- XMLBuilder.wSelf("top", {
1224
- "w:w": formatting.margins.top,
1225
- "w:type": "dxa",
1226
- })
1227
- );
1228
- }
1229
- if (formatting.margins.left !== undefined) {
1230
- marginElements.push(
1231
- XMLBuilder.wSelf("left", {
1232
- "w:w": formatting.margins.left,
1233
- "w:type": "dxa",
1234
- })
1235
- );
1236
- }
1237
- if (formatting.margins.bottom !== undefined) {
1238
- marginElements.push(
1239
- XMLBuilder.wSelf("bottom", {
1240
- "w:w": formatting.margins.bottom,
1241
- "w:type": "dxa",
1242
- })
1243
- );
1244
- }
1245
- if (formatting.margins.right !== undefined) {
1246
- marginElements.push(
1247
- XMLBuilder.wSelf("right", {
1248
- "w:w": formatting.margins.right,
1249
- "w:type": "dxa",
1250
- })
1251
- );
1252
- }
1253
- if (marginElements.length > 0) {
1254
- tcPrChildren.push(XMLBuilder.w("tcMar", undefined, marginElements));
1255
- }
1256
- }
1257
-
1258
- // Vertical alignment
1259
- if (formatting.verticalAlignment) {
1260
- tcPrChildren.push(
1261
- XMLBuilder.wSelf("vAlign", { "w:val": formatting.verticalAlignment })
1262
- );
1263
- }
1264
-
1265
- return XMLBuilder.w("tcPr", undefined, tcPrChildren);
1266
- }
1267
-
1268
- /**
1269
- * Generates table row properties XML (trPr) - Phase 5.1
1270
- */
1271
- private generateTableRowProperties(
1272
- formatting: TableRowStyleFormatting
1273
- ): XMLElement {
1274
- const trPrChildren: XMLElement[] = [];
1275
-
1276
- // Style-level trPr has a restricted content model compared to inline trPr.
1277
- // Valid children: cantSplit, tblHeader, tblCellSpacing, jc, hidden
1278
- // NOTE: trHeight is NOT valid in style-level trPr per OOXML strict schema.
1279
-
1280
- // Can't split row across pages
1281
- if (formatting.cantSplit) {
1282
- trPrChildren.push(XMLBuilder.wSelf("cantSplit"));
1283
- }
1284
-
1285
- // Header row
1286
- if (formatting.isHeader) {
1287
- trPrChildren.push(XMLBuilder.wSelf("tblHeader"));
1288
- }
1289
-
1290
- return XMLBuilder.w("trPr", undefined, trPrChildren);
1291
- }
1292
-
1293
- /**
1294
- * Generates conditional formatting XML (tblStylePr) - Phase 5.1
1295
- */
1296
- private generateConditionalFormatting(
1297
- conditional: ConditionalTableFormatting
1298
- ): XMLElement {
1299
- const tblStylePrChildren: XMLElement[] = [];
1300
-
1301
- // Add paragraph properties if specified
1302
- if (conditional.paragraphFormatting) {
1303
- const pPr = this.generateParagraphProperties(
1304
- conditional.paragraphFormatting
1305
- );
1306
- if (pPr.children && pPr.children.length > 0) {
1307
- tblStylePrChildren.push(pPr);
1308
- }
1309
- }
1310
-
1311
- // Add run properties if specified
1312
- if (conditional.runFormatting) {
1313
- const rPr = this.generateRunProperties(conditional.runFormatting);
1314
- if (rPr.children && rPr.children.length > 0) {
1315
- tblStylePrChildren.push(rPr);
1316
- }
1317
- }
1318
-
1319
- // Add table properties if specified
1320
- if (conditional.tableFormatting) {
1321
- const tblPr = this.generateTableProperties(
1322
- conditional.tableFormatting,
1323
- {},
1324
- true
1325
- );
1326
- if (tblPr.children && tblPr.children.length > 0) {
1327
- tblStylePrChildren.push(tblPr);
1328
- }
1329
- }
1330
-
1331
- // Add cell properties if specified
1332
- if (conditional.cellFormatting) {
1333
- const tcPr = this.generateTableCellProperties(conditional.cellFormatting);
1334
- if (tcPr.children && tcPr.children.length > 0) {
1335
- tblStylePrChildren.push(tcPr);
1336
- }
1337
- }
1338
-
1339
- // Add row properties if specified
1340
- if (conditional.rowFormatting) {
1341
- const trPr = this.generateTableRowProperties(conditional.rowFormatting);
1342
- if (trPr.children && trPr.children.length > 0) {
1343
- tblStylePrChildren.push(trPr);
1344
- }
1345
- }
1346
-
1347
- return XMLBuilder.w(
1348
- "tblStylePr",
1349
- { "w:type": conditional.type },
1350
- tblStylePrChildren
1351
- );
1352
- }
1353
-
1354
- /**
1355
- * Generates border elements for tables or cells - Phase 5.1
1356
- * @param borders - Border properties
1357
- * @param includeDiagonals - Whether to include diagonal borders (for cells)
1358
- */
1359
- private generateBorderElements(
1360
- borders: TableBorders | CellBorders,
1361
- includeDiagonals: boolean
1362
- ): XMLElement[] {
1363
- const borderElements: XMLElement[] = [];
1364
-
1365
- // Ordered per ECMA-376 CT_TblBorders / CT_TcBorders: top, left, bottom, right
1366
- const borderProps = [
1367
- "top",
1368
- "left",
1369
- "bottom",
1370
- "right",
1371
- "insideH",
1372
- "insideV",
1373
- ] as const;
1374
- for (const prop of borderProps) {
1375
- const border = borders[prop];
1376
- if (border) {
1377
- const attrs: Record<string, string | number> = {};
1378
- if (border.style) attrs["w:val"] = border.style;
1379
- if (border.size !== undefined) attrs["w:sz"] = border.size;
1380
- if (border.space !== undefined) attrs["w:space"] = border.space;
1381
- if (border.color) attrs["w:color"] = border.color;
1382
-
1383
- if (Object.keys(attrs).length > 0) {
1384
- borderElements.push(XMLBuilder.wSelf(prop, attrs));
1385
- }
1386
- }
1387
- }
1388
-
1389
- // Add diagonal borders for cells
1390
- if (includeDiagonals) {
1391
- const cellBorders = borders as CellBorders;
1392
- if (cellBorders.tl2br) {
1393
- const attrs: Record<string, string | number> = {};
1394
- if (cellBorders.tl2br.style) attrs["w:val"] = cellBorders.tl2br.style;
1395
- if (cellBorders.tl2br.size !== undefined)
1396
- attrs["w:sz"] = cellBorders.tl2br.size;
1397
- if (cellBorders.tl2br.space !== undefined)
1398
- attrs["w:space"] = cellBorders.tl2br.space;
1399
- if (cellBorders.tl2br.color) attrs["w:color"] = cellBorders.tl2br.color;
1400
-
1401
- if (Object.keys(attrs).length > 0) {
1402
- borderElements.push(XMLBuilder.wSelf("tl2br", attrs));
1403
- }
1404
- }
1405
- if (cellBorders.tr2bl) {
1406
- const attrs: Record<string, string | number> = {};
1407
- if (cellBorders.tr2bl.style) attrs["w:val"] = cellBorders.tr2bl.style;
1408
- if (cellBorders.tr2bl.size !== undefined)
1409
- attrs["w:sz"] = cellBorders.tr2bl.size;
1410
- if (cellBorders.tr2bl.space !== undefined)
1411
- attrs["w:space"] = cellBorders.tr2bl.space;
1412
- if (cellBorders.tr2bl.color) attrs["w:color"] = cellBorders.tr2bl.color;
1413
-
1414
- if (Object.keys(attrs).length > 0) {
1415
- borderElements.push(XMLBuilder.wSelf("tr2bl", attrs));
1416
- }
1417
- }
1418
- }
1419
-
1420
- return borderElements;
1421
- }
1422
-
1423
- /**
1424
- * Generates shading element - Phase 5.1
1425
- */
1426
- private generateShadingElement(shading: ShadingProperties): XMLElement {
1427
- const attrs = buildShadingAttributes(shading);
1428
- return XMLBuilder.wSelf("shd", attrs);
1429
- }
1430
-
1431
- /**
1432
- * Creates a new Style
1433
- * @param properties - Style properties
1434
- * @returns New Style instance
1435
- */
1436
- static create(properties: StyleProperties): Style {
1437
- return new Style(properties);
1438
- }
1439
-
1440
- /**
1441
- * Creates the Normal style (default paragraph style)
1442
- * @returns Normal style
1443
- */
1444
- static createNormalStyle(): Style {
1445
- return new Style({
1446
- styleId: "Normal",
1447
- name: "Normal",
1448
- type: "paragraph",
1449
- isDefault: true,
1450
- next: "Normal",
1451
- qFormat: true,
1452
- uiPriority: 0,
1453
- paragraphFormatting: {
1454
- alignment: "left",
1455
- spacing: {
1456
- before: 60, // 3pt
1457
- after: 60, // 3pt
1458
- line: 240, // Single line spacing
1459
- lineRule: "auto",
1460
- },
1461
- },
1462
- runFormatting: {
1463
- font: "Verdana",
1464
- size: 12,
1465
- color: "000000",
1466
- },
1467
- });
1468
- }
1469
-
1470
- /**
1471
- * Creates a Heading style
1472
- * @param level - Heading level (1-9)
1473
- * @returns Heading style
1474
- */
1475
- static createHeadingStyle(level: number): Style {
1476
- if (level < 1 || level > 9) {
1477
- throw new Error("Heading level must be between 1 and 9");
1478
- }
1479
-
1480
- // Font sizes: H1=18pt, H2=14pt, H3=12pt, H4-9=12pt
1481
- const sizes = [18, 14, 12, 12, 12, 12, 12, 12, 12];
1482
-
1483
- // Spacing before: H1=0pt, H2=6pt, H3=3pt, H4-9=6pt (in twips: 1pt = 20 twips)
1484
- const spacingBefore =
1485
- level === 1 ? 0 : level === 2 ? 120 : level === 3 ? 60 : 120;
1486
-
1487
- // Spacing after: H1=12pt, H2=6pt, H3=3pt, H4-9=6pt (in twips)
1488
- const spacingAfter =
1489
- level === 1 ? 240 : level === 2 ? 120 : level === 3 ? 60 : 120;
1490
-
1491
- return new Style({
1492
- styleId: `Heading${level}`,
1493
- name: `Heading ${level}`,
1494
- type: "paragraph",
1495
- basedOn: "Normal",
1496
- next: "Normal",
1497
- link: `Heading${level}Char`,
1498
- qFormat: true,
1499
- uiPriority: 9,
1500
- paragraphFormatting: {
1501
- alignment: "left",
1502
- spacing: {
1503
- before: spacingBefore,
1504
- after: spacingAfter,
1505
- line: 240, // Single line spacing
1506
- lineRule: "auto",
1507
- },
1508
- keepNext: true,
1509
- keepLines: true,
1510
- outlineLevel: level - 1, // Heading 1 = 0, Heading 2 = 1, etc. Required for TOC
1511
- },
1512
- runFormatting: {
1513
- font: "Verdana",
1514
- size: sizes[level - 1],
1515
- bold: true,
1516
- color: "000000",
1517
- },
1518
- });
1519
- }
1520
-
1521
- /**
1522
- * Creates a Heading character style (linked to paragraph style)
1523
- * @param level - Heading level (1-9)
1524
- * @returns Heading character style
1525
- */
1526
- static createHeadingCharStyle(level: number): Style {
1527
- if (level < 1 || level > 9) {
1528
- throw new Error("Heading level must be between 1 and 9");
1529
- }
1530
-
1531
- // Font sizes: H1=18pt, H2=14pt, H3=12pt, H4-9=12pt
1532
- const sizes = [18, 14, 12, 12, 12, 12, 12, 12, 12];
1533
-
1534
- return new Style({
1535
- styleId: `Heading${level}Char`,
1536
- name: `Heading ${level} Char`,
1537
- type: "character",
1538
- link: `Heading${level}`,
1539
- qFormat: true,
1540
- uiPriority: 9,
1541
- runFormatting: {
1542
- font: "Verdana",
1543
- size: sizes[level - 1],
1544
- bold: true,
1545
- color: "000000",
1546
- },
1547
- });
1548
- }
1549
-
1550
- /**
1551
- * Creates the Title style
1552
- * @returns Title style
1553
- */
1554
- static createTitleStyle(): Style {
1555
- return new Style({
1556
- styleId: "Title",
1557
- name: "Title",
1558
- type: "paragraph",
1559
- basedOn: "Normal",
1560
- next: "Normal",
1561
- paragraphFormatting: {
1562
- spacing: {
1563
- after: 120,
1564
- },
1565
- },
1566
- runFormatting: {
1567
- font: "Verdana",
1568
- size: 28,
1569
- color: "000000",
1570
- },
1571
- });
1572
- }
1573
-
1574
- /**
1575
- * Creates the Subtitle style
1576
- * @returns Subtitle style
1577
- */
1578
- static createSubtitleStyle(): Style {
1579
- return new Style({
1580
- styleId: "Subtitle",
1581
- name: "Subtitle",
1582
- type: "paragraph",
1583
- basedOn: "Normal",
1584
- next: "Normal",
1585
- paragraphFormatting: {
1586
- spacing: {
1587
- after: 120,
1588
- },
1589
- },
1590
- runFormatting: {
1591
- font: "Verdana",
1592
- size: 14,
1593
- color: "000000",
1594
- italic: true,
1595
- },
1596
- });
1597
- }
1598
-
1599
- /**
1600
- * Creates a List Paragraph style (for lists)
1601
- * @returns List Paragraph style
1602
- */
1603
- static createListParagraphStyle(): Style {
1604
- return new Style({
1605
- styleId: "ListParagraph",
1606
- name: "List Paragraph",
1607
- type: "paragraph",
1608
- basedOn: "Normal",
1609
- next: "ListParagraph",
1610
- qFormat: true,
1611
- uiPriority: 34,
1612
- paragraphFormatting: {
1613
- alignment: "left",
1614
- indentation: {
1615
- left: 720, // 0.5 inch (text indentation)
1616
- hanging: 360, // 0.25 inch (bullet/number indentation)
1617
- },
1618
- spacing: {
1619
- before: 0, // 0pt
1620
- after: 60, // 3pt
1621
- line: 240, // Single line spacing
1622
- lineRule: "auto",
1623
- },
1624
- contextualSpacing: true, // No space between similar paragraphs
1625
- },
1626
- runFormatting: {
1627
- font: "Verdana",
1628
- size: 12,
1629
- color: "000000",
1630
- },
1631
- });
1632
- }
1633
-
1634
- /**
1635
- * Creates a TOC Heading style (for table of contents titles)
1636
- * @returns TOC Heading style
1637
- */
1638
- static createTOCHeadingStyle(): Style {
1639
- return new Style({
1640
- styleId: "TOCHeading",
1641
- name: "TOC Heading",
1642
- type: "paragraph",
1643
- basedOn: "Heading1",
1644
- next: "Normal",
1645
- runFormatting: {
1646
- bold: true,
1647
- font: "Verdana",
1648
- size: 14,
1649
- color: "000000", // Black
1650
- },
1651
- paragraphFormatting: {
1652
- spacing: {
1653
- before: 480, // Larger spacing before TOC
1654
- after: 240,
1655
- },
1656
- },
1657
- });
1658
- }
1659
-
1660
- /**
1661
- * Creates a TOC entry style for a specific level (1-9).
1662
- * TOC 1-9 are built-in Word styles for Table of Contents entries.
1663
- * When Word updates a TOC field, it applies these styles to the generated entries.
1664
- *
1665
- * @param level - TOC level (1-9)
1666
- * @param formatting - Optional run and paragraph formatting overrides
1667
- * @returns TOC style for the specified level
1668
- */
1669
- static createTOCStyle(
1670
- level: number,
1671
- formatting?: {
1672
- run?: RunFormatting;
1673
- paragraph?: ParagraphFormatting;
1674
- }
1675
- ): Style {
1676
- if (level < 1 || level > 9) {
1677
- throw new Error("TOC level must be between 1 and 9");
1678
- }
1679
-
1680
- // Default indentation: 220 twips per level (11pt)
1681
- const defaultLeftIndent = (level - 1) * 220;
1682
-
1683
- return Style.create({
1684
- styleId: `TOC${level}`,
1685
- name: `toc ${level}`,
1686
- type: "paragraph",
1687
- basedOn: "Normal",
1688
- uiPriority: 39,
1689
- semiHidden: true,
1690
- unhideWhenUsed: true,
1691
- runFormatting: formatting?.run,
1692
- paragraphFormatting: {
1693
- indentation: { left: defaultLeftIndent },
1694
- ...formatting?.paragraph,
1695
- },
1696
- });
1697
- }
1698
-
1699
- /**
1700
- * Creates a Table Normal style (Phase 5.1)
1701
- * @returns Table Normal style
1702
- */
1703
- static createTableNormalStyle(): Style {
1704
- return new Style({
1705
- styleId: "TableNormal",
1706
- name: "Table Normal",
1707
- type: "table",
1708
- basedOn: "Normal",
1709
- tableStyle: {
1710
- table: {
1711
- cellMargins: {
1712
- top: 0,
1713
- left: 108, // ~0.075 inch
1714
- bottom: 0,
1715
- right: 108,
1716
- },
1717
- },
1718
- rowBandSize: 1,
1719
- colBandSize: 1,
1720
- },
1721
- });
1722
- }
1723
-
1724
- /**
1725
- * Creates a Table Grid style with borders (Phase 5.1)
1726
- * @returns Table Grid style
1727
- */
1728
- static createTableGridStyle(): Style {
1729
- return new Style({
1730
- styleId: "TableGrid",
1731
- name: "Table Grid",
1732
- type: "table",
1733
- basedOn: "TableNormal",
1734
- tableStyle: {
1735
- table: {
1736
- borders: {
1737
- top: { style: "single", size: 4, color: "000000" },
1738
- bottom: { style: "single", size: 4, color: "000000" },
1739
- left: { style: "single", size: 4, color: "000000" },
1740
- right: { style: "single", size: 4, color: "000000" },
1741
- insideH: { style: "single", size: 4, color: "000000" },
1742
- insideV: { style: "single", size: 4, color: "000000" },
1743
- },
1744
- cellMargins: {
1745
- top: 0,
1746
- left: 108,
1747
- bottom: 0,
1748
- right: 108,
1749
- },
1750
- },
1751
- rowBandSize: 1,
1752
- colBandSize: 1,
1753
- },
1754
- });
1755
- }
1756
-
1757
- /**
1758
- * Creates a deep clone of this style
1759
- * @returns New Style instance with copied properties
1760
- * @example
1761
- * ```typescript
1762
- * const original = Style.createHeadingStyle(1);
1763
- * const copy = original.clone();
1764
- * copy.setRunFormatting({ color: 'FF0000' }); // Doesn't affect original
1765
- * ```
1766
- */
1767
- clone(): Style {
1768
- // Deep copy all properties
1769
- const clonedProps: StyleProperties = deepClone(this.properties);
1770
- return new Style(clonedProps);
1771
- }
1772
-
1773
- /**
1774
- * Resets the style to its minimal state
1775
- * Clears all paragraph and run formatting while preserving the style identity
1776
- * @returns This style for chaining
1777
- * @example
1778
- * ```typescript
1779
- * const style = Style.createHeadingStyle(1);
1780
- * style.reset(); // Clears all formatting, keeps styleId and name
1781
- * ```
1782
- */
1783
- reset(): this {
1784
- // Preserve identity properties
1785
- const { styleId, name, type, basedOn } = this.properties;
1786
-
1787
- // Reset to minimal properties
1788
- this.properties = {
1789
- styleId,
1790
- name,
1791
- type,
1792
- basedOn,
1793
- };
1794
-
1795
- return this;
1796
- }
1797
-
1798
- /**
1799
- * Merges properties from another style into this one
1800
- * @param otherStyle - Style to merge from
1801
- * @returns This style for chaining
1802
- * @example
1803
- * ```typescript
1804
- * const base = Style.createNormalStyle();
1805
- * const override = Style.create({
1806
- * styleId: 'Override',
1807
- * name: 'Override',
1808
- * type: 'paragraph',
1809
- * runFormatting: { bold: true, color: 'FF0000' }
1810
- * });
1811
- * base.mergeWith(override); // base now has bold red text
1812
- * ```
1813
- */
1814
- mergeWith(otherStyle: Style): this {
1815
- const otherProps = otherStyle.getProperties();
1816
-
1817
- // Merge paragraph formatting
1818
- if (otherProps.paragraphFormatting) {
1819
- if (!this.properties.paragraphFormatting) {
1820
- this.properties.paragraphFormatting = {};
1821
- }
1822
-
1823
- // Merge top-level paragraph properties
1824
- if (otherProps.paragraphFormatting.alignment) {
1825
- this.properties.paragraphFormatting.alignment =
1826
- otherProps.paragraphFormatting.alignment;
1827
- }
1828
- if (otherProps.paragraphFormatting.keepNext !== undefined) {
1829
- this.properties.paragraphFormatting.keepNext =
1830
- otherProps.paragraphFormatting.keepNext;
1831
- }
1832
- if (otherProps.paragraphFormatting.keepLines !== undefined) {
1833
- this.properties.paragraphFormatting.keepLines =
1834
- otherProps.paragraphFormatting.keepLines;
1835
- }
1836
- if (otherProps.paragraphFormatting.pageBreakBefore !== undefined) {
1837
- this.properties.paragraphFormatting.pageBreakBefore =
1838
- otherProps.paragraphFormatting.pageBreakBefore;
1839
- }
1840
-
1841
- // Merge indentation
1842
- if (otherProps.paragraphFormatting.indentation) {
1843
- if (!this.properties.paragraphFormatting.indentation) {
1844
- this.properties.paragraphFormatting.indentation = {};
1845
- }
1846
- Object.assign(
1847
- this.properties.paragraphFormatting.indentation,
1848
- otherProps.paragraphFormatting.indentation
1849
- );
1850
- }
1851
-
1852
- // Merge spacing
1853
- if (otherProps.paragraphFormatting.spacing) {
1854
- if (!this.properties.paragraphFormatting.spacing) {
1855
- this.properties.paragraphFormatting.spacing = {};
1856
- }
1857
- Object.assign(
1858
- this.properties.paragraphFormatting.spacing,
1859
- otherProps.paragraphFormatting.spacing
1860
- );
1861
- }
1862
- }
1863
-
1864
- // Merge run formatting
1865
- if (otherProps.runFormatting) {
1866
- if (!this.properties.runFormatting) {
1867
- this.properties.runFormatting = {};
1868
- }
1869
- Object.assign(this.properties.runFormatting, otherProps.runFormatting);
1870
- }
1871
-
1872
- // Merge other properties (but don't override styleId)
1873
- if (otherProps.name) this.properties.name = otherProps.name;
1874
- if (otherProps.basedOn) this.properties.basedOn = otherProps.basedOn;
1875
- if (otherProps.next) this.properties.next = otherProps.next;
1876
-
1877
- return this;
1878
- }
1879
- }
1
+ /**
2
+ * Style - Represents a style definition in a Word document
3
+ * Supports paragraph, character, table, and numbering styles
4
+ */
5
+
6
+ import { XMLBuilder, XMLElement } from '../xml/XMLBuilder';
7
+ import { ParagraphFormatting } from '../elements/Paragraph';
8
+ import { RunFormatting } from '../elements/Run';
9
+ import { ShadingConfig, ShadingPattern, buildShadingAttributes } from '../elements/CommonTypes';
10
+ import { Heading2TableOptions } from '../types/styleConfig';
11
+ import { deepClone } from '../utils/deepClone';
12
+ import { pointsToHalfPoints } from '../utils/units';
13
+
14
+ /**
15
+ * Style type
16
+ */
17
+ export type StyleType = 'paragraph' | 'character' | 'table' | 'numbering';
18
+
19
+ /**
20
+ * Table alignment
21
+ */
22
+ export type TableAlignment = 'left' | 'center' | 'right';
23
+
24
+ /**
25
+ * Border properties
26
+ */
27
+ export interface BorderProperties {
28
+ /** Border style */
29
+ style?: 'none' | 'single' | 'double' | 'dashed' | 'dotted' | 'thick';
30
+ /** Border size in eighths of a point */
31
+ size?: number;
32
+ /** Border spacing/padding in points */
33
+ space?: number;
34
+ /** Border color (hex without #) */
35
+ color?: string;
36
+ }
37
+
38
+ /**
39
+ * Table borders (6 possible borders)
40
+ */
41
+ export interface TableBorders {
42
+ top?: BorderProperties;
43
+ bottom?: BorderProperties;
44
+ left?: BorderProperties;
45
+ right?: BorderProperties;
46
+ /** Inside horizontal borders */
47
+ insideH?: BorderProperties;
48
+ /** Inside vertical borders */
49
+ insideV?: BorderProperties;
50
+ }
51
+
52
+ /**
53
+ * Cell borders (8 possible borders, includes diagonals)
54
+ */
55
+ export interface CellBorders extends TableBorders {
56
+ /** Top-left to bottom-right diagonal */
57
+ tl2br?: BorderProperties;
58
+ /** Top-right to bottom-left diagonal */
59
+ tr2bl?: BorderProperties;
60
+ }
61
+
62
+ /**
63
+ * Shading properties
64
+ * @see ShadingConfig in CommonTypes.ts for the canonical definition
65
+ */
66
+ export type ShadingProperties = ShadingConfig;
67
+
68
+ /**
69
+ * Cell margins
70
+ */
71
+ export interface CellMargins {
72
+ /** Top margin in twips */
73
+ top?: number;
74
+ /** Bottom margin in twips */
75
+ bottom?: number;
76
+ /** Left margin in twips */
77
+ left?: number;
78
+ /** Right margin in twips */
79
+ right?: number;
80
+ }
81
+
82
+ /**
83
+ * Table-level formatting properties (tblPr)
84
+ */
85
+ export interface TableStyleFormatting {
86
+ /** Table indentation from left margin in twips */
87
+ indent?: number;
88
+ /** Default cell spacing in twips */
89
+ cellSpacing?: number;
90
+ /** Table borders */
91
+ borders?: TableBorders;
92
+ /** Default cell margins */
93
+ cellMargins?: CellMargins;
94
+ /** Table background shading */
95
+ shading?: ShadingProperties;
96
+ /** Table alignment */
97
+ alignment?: TableAlignment;
98
+ }
99
+
100
+ /**
101
+ * Table cell formatting properties (tcPr)
102
+ */
103
+ export interface TableCellStyleFormatting {
104
+ /** Cell borders (8 possible borders) */
105
+ borders?: CellBorders;
106
+ /** Cell background shading */
107
+ shading?: ShadingProperties;
108
+ /** Cell-specific margins */
109
+ margins?: CellMargins;
110
+ /** Vertical alignment in cell */
111
+ verticalAlignment?: 'top' | 'center' | 'bottom';
112
+ }
113
+
114
+ /**
115
+ * Table row formatting properties (trPr)
116
+ */
117
+ export interface TableRowStyleFormatting {
118
+ /** Prevent row from splitting across pages */
119
+ cantSplit?: boolean;
120
+ /** Mark row as header row */
121
+ isHeader?: boolean;
122
+ /** Row height in twips */
123
+ height?: number;
124
+ /** Row height rule */
125
+ heightRule?: 'auto' | 'exact' | 'atLeast';
126
+ }
127
+
128
+ /**
129
+ * Conditional formatting type for table regions
130
+ */
131
+ export type ConditionalFormattingType =
132
+ | 'wholeTable' // Entire table
133
+ | 'firstRow' // First row
134
+ | 'lastRow' // Last row
135
+ | 'firstCol' // First column
136
+ | 'lastCol' // Last column
137
+ | 'band1Vert' // Odd column banding
138
+ | 'band2Vert' // Even column banding
139
+ | 'band1Horz' // Odd row banding
140
+ | 'band2Horz' // Even row banding
141
+ | 'nwCell' // Northwest (top-left) corner cell
142
+ | 'neCell' // Northeast (top-right) corner cell
143
+ | 'swCell' // Southwest (bottom-left) corner cell
144
+ | 'seCell'; // Southeast (bottom-right) corner cell
145
+
146
+ /**
147
+ * Conditional table formatting for a specific region
148
+ */
149
+ export interface ConditionalTableFormatting {
150
+ /** Region type */
151
+ type: ConditionalFormattingType;
152
+ /** Paragraph formatting for this region */
153
+ paragraphFormatting?: ParagraphFormatting;
154
+ /** Run formatting for this region */
155
+ runFormatting?: RunFormatting;
156
+ /** Table formatting for this region */
157
+ tableFormatting?: TableStyleFormatting;
158
+ /** Cell formatting for this region */
159
+ cellFormatting?: TableCellStyleFormatting;
160
+ /** Row formatting for this region */
161
+ rowFormatting?: TableRowStyleFormatting;
162
+ }
163
+
164
+ /**
165
+ * Table style properties (Phase 5.1)
166
+ */
167
+ export interface TableStyleProperties {
168
+ /** Table-level formatting */
169
+ table?: TableStyleFormatting;
170
+ /** Default cell formatting */
171
+ cell?: TableCellStyleFormatting;
172
+ /** Default row formatting */
173
+ row?: TableRowStyleFormatting;
174
+ /** Rows per band for row banding (default 1) */
175
+ rowBandSize?: number;
176
+ /** Columns per band for column banding (default 1) */
177
+ colBandSize?: number;
178
+ /** Conditional formatting for specific table regions */
179
+ conditionalFormatting?: ConditionalTableFormatting[];
180
+ }
181
+
182
+ /**
183
+ * Style properties
184
+ */
185
+ export interface StyleProperties {
186
+ /** Unique style identifier */
187
+ styleId: string;
188
+ /** Display name */
189
+ name: string;
190
+ /** Style type */
191
+ type: StyleType;
192
+ /** Parent style ID for inheritance */
193
+ basedOn?: string;
194
+ /** Next style ID (auto-next paragraph style) */
195
+ next?: string;
196
+ /** Whether this is a default style */
197
+ isDefault?: boolean;
198
+ /** Whether this is a custom style */
199
+ customStyle?: boolean;
200
+ /** Paragraph formatting (for paragraph and table styles) */
201
+ paragraphFormatting?: ParagraphFormatting;
202
+ /** Numbering properties for styles that inherit list formatting */
203
+ numPr?: {
204
+ numId?: number;
205
+ ilvl?: number;
206
+ };
207
+ /** Run formatting (for character and paragraph styles) */
208
+ runFormatting?: RunFormatting;
209
+ /** Table style properties (for table styles only - Phase 5.1) */
210
+ tableStyle?: TableStyleProperties;
211
+
212
+ // Style Gallery Metadata (Phase 5.3)
213
+ /** Quick style - show in style gallery */
214
+ qFormat?: boolean;
215
+ /** UI priority - sort order in style picker (0-99, lower = higher priority) */
216
+ uiPriority?: number;
217
+ /** Semi-hidden - hide from gallery unless in use */
218
+ semiHidden?: boolean;
219
+ /** Unhide when used - auto-show when applied */
220
+ unhideWhenUsed?: boolean;
221
+ /** Locked - prevent modification */
222
+ locked?: boolean;
223
+ /** Personal - user-specific style */
224
+ personal?: boolean;
225
+ /** Personal compose - style for composing new messages */
226
+ personalCompose?: boolean;
227
+ /** Personal reply - style for replying to messages */
228
+ personalReply?: boolean;
229
+ /** Link - linked character/paragraph style ID */
230
+ link?: string;
231
+ /** Auto-redefine - update style from manual formatting */
232
+ autoRedefine?: boolean;
233
+ /** Aliases - alternative names (comma-separated) */
234
+ aliases?: string;
235
+
236
+ // Document Helper Metadata
237
+ /**
238
+ * Table options for Heading2 wrapping
239
+ * Used by Document.applyCustomFormattingToExistingStyles() to configure
240
+ * how Heading2 paragraphs are wrapped in tables
241
+ */
242
+ heading2TableOptions?: Heading2TableOptions;
243
+ }
244
+
245
+ /**
246
+ * Represents a style definition
247
+ */
248
+ export class Style {
249
+ private properties: StyleProperties;
250
+
251
+ /**
252
+ * Creates a new Style
253
+ * @param properties - Style properties
254
+ */
255
+ constructor(properties: StyleProperties) {
256
+ this.properties = { ...properties };
257
+ }
258
+
259
+ /**
260
+ * Gets the style ID
261
+ * @returns Style ID
262
+ */
263
+ getStyleId(): string {
264
+ return this.properties.styleId;
265
+ }
266
+
267
+ /**
268
+ * Gets the style name
269
+ * @returns Style name
270
+ */
271
+ getName(): string {
272
+ return this.properties.name;
273
+ }
274
+
275
+ /**
276
+ * Gets the style type
277
+ * @returns Style type
278
+ */
279
+ getType(): StyleType {
280
+ return this.properties.type;
281
+ }
282
+
283
+ /**
284
+ * Gets all style properties
285
+ * @returns Style properties
286
+ */
287
+ getProperties(): StyleProperties {
288
+ return { ...this.properties };
289
+ }
290
+
291
+ /**
292
+ * Gets whether this is a default style
293
+ * @returns True if this is a default style for its type
294
+ */
295
+ getIsDefault(): boolean {
296
+ return this.properties.isDefault ?? false;
297
+ }
298
+
299
+ /**
300
+ * Sets whether this is a default style
301
+ * @param value - True to mark as default style
302
+ * @returns This style for chaining
303
+ */
304
+ setIsDefault(value: boolean): this {
305
+ this.properties.isDefault = value;
306
+ return this;
307
+ }
308
+
309
+ /**
310
+ * Sets the base style
311
+ * @param styleId - Parent style ID
312
+ * @returns This style for chaining
313
+ */
314
+ setBasedOn(styleId: string): this {
315
+ this.properties.basedOn = styleId;
316
+ return this;
317
+ }
318
+
319
+ /**
320
+ * Sets the next style
321
+ * @param styleId - Next style ID
322
+ * @returns This style for chaining
323
+ */
324
+ setNext(styleId: string): this {
325
+ this.properties.next = styleId;
326
+ return this;
327
+ }
328
+
329
+ /**
330
+ * Sets paragraph formatting
331
+ * @param formatting - Paragraph formatting options
332
+ * @returns This style for chaining
333
+ */
334
+ setParagraphFormatting(formatting: ParagraphFormatting): this {
335
+ this.properties.paragraphFormatting = { ...formatting };
336
+ return this;
337
+ }
338
+
339
+ /**
340
+ * Sets run formatting
341
+ * @param formatting - Run formatting options
342
+ * @returns This style for chaining
343
+ */
344
+ setRunFormatting(formatting: RunFormatting): this {
345
+ this.properties.runFormatting = { ...formatting };
346
+ return this;
347
+ }
348
+
349
+ /**
350
+ * Gets the current run formatting
351
+ * @returns Run formatting or undefined if not set
352
+ */
353
+ getRunFormatting(): RunFormatting | undefined {
354
+ return this.properties.runFormatting;
355
+ }
356
+
357
+ /**
358
+ * Gets the current paragraph formatting
359
+ * @returns Paragraph formatting or undefined if not set
360
+ */
361
+ getParagraphFormatting(): ParagraphFormatting | undefined {
362
+ return this.properties.paragraphFormatting;
363
+ }
364
+
365
+ /**
366
+ * Sets Heading2 table wrapping options
367
+ * Used by Document.applyCustomFormattingToExistingStyles() when styleId is 'Heading2'
368
+ * @param options - Table options for wrapping Heading2 paragraphs
369
+ * @returns This style for chaining
370
+ */
371
+ setHeading2TableOptions(options: Heading2TableOptions): this {
372
+ this.properties.heading2TableOptions = options;
373
+ return this;
374
+ }
375
+
376
+ /**
377
+ * Gets the current Heading2 table options
378
+ * @returns Heading2 table options or undefined if not set
379
+ */
380
+ getHeading2TableOptions(): Heading2TableOptions | undefined {
381
+ return this.properties.heading2TableOptions;
382
+ }
383
+
384
+ /**
385
+ * Sets whether this is a quick style (appears in style gallery)
386
+ * @param enabled - True to show in quick style gallery
387
+ * @returns This style for chaining
388
+ */
389
+ setQFormat(enabled: boolean): this {
390
+ this.properties.qFormat = enabled;
391
+ return this;
392
+ }
393
+
394
+ /**
395
+ * Sets the UI priority (sort order in style picker)
396
+ * @param priority - Priority value (0-99, lower = higher priority)
397
+ * @returns This style for chaining
398
+ */
399
+ setUiPriority(priority: number): this {
400
+ if (priority < 0 || priority > 99) {
401
+ throw new Error('UI priority must be between 0 and 99');
402
+ }
403
+ this.properties.uiPriority = priority;
404
+ return this;
405
+ }
406
+
407
+ /**
408
+ * Sets whether this style is semi-hidden (hidden from recommended list)
409
+ * @param hidden - True to hide from recommended list
410
+ * @returns This style for chaining
411
+ */
412
+ setSemiHidden(hidden: boolean): this {
413
+ this.properties.semiHidden = hidden;
414
+ return this;
415
+ }
416
+
417
+ /**
418
+ * Sets whether to unhide this style when first used
419
+ * @param enabled - True to auto-show when applied
420
+ * @returns This style for chaining
421
+ */
422
+ setUnhideWhenUsed(enabled: boolean): this {
423
+ this.properties.unhideWhenUsed = enabled;
424
+ return this;
425
+ }
426
+
427
+ /**
428
+ * Sets whether this style is locked (prevents modification)
429
+ * @param locked - True to lock the style
430
+ * @returns This style for chaining
431
+ */
432
+ setLocked(locked: boolean): this {
433
+ this.properties.locked = locked;
434
+ return this;
435
+ }
436
+
437
+ /**
438
+ * Sets whether this is a personal style (user-specific)
439
+ * @param personal - True to mark as personal
440
+ * @returns This style for chaining
441
+ */
442
+ setPersonal(personal: boolean): this {
443
+ this.properties.personal = personal;
444
+ return this;
445
+ }
446
+
447
+ /**
448
+ * Sets whether this is a personal compose style (for composing new messages)
449
+ * @param enabled - True to mark as personal compose
450
+ */
451
+ setPersonalCompose(enabled: boolean): this {
452
+ this.properties.personalCompose = enabled;
453
+ return this;
454
+ }
455
+
456
+ /**
457
+ * Gets whether this is a personal compose style
458
+ */
459
+ getPersonalCompose(): boolean {
460
+ return this.properties.personalCompose === true;
461
+ }
462
+
463
+ /**
464
+ * Sets whether this is a personal reply style (for replying to messages)
465
+ * @param enabled - True to mark as personal reply
466
+ */
467
+ setPersonalReply(enabled: boolean): this {
468
+ this.properties.personalReply = enabled;
469
+ return this;
470
+ }
471
+
472
+ /**
473
+ * Gets whether this is a personal reply style
474
+ */
475
+ getPersonalReply(): boolean {
476
+ return this.properties.personalReply === true;
477
+ }
478
+
479
+ /**
480
+ * Sets the linked style ID (for character/paragraph style linking)
481
+ * @param styleId - ID of the linked style
482
+ * @returns This style for chaining
483
+ */
484
+ setLink(styleId: string): this {
485
+ this.properties.link = styleId;
486
+ return this;
487
+ }
488
+
489
+ /**
490
+ * Sets whether to auto-redefine this style from manual formatting
491
+ * @param enabled - True to enable auto-redefine
492
+ * @returns This style for chaining
493
+ */
494
+ setAutoRedefine(enabled: boolean): this {
495
+ this.properties.autoRedefine = enabled;
496
+ return this;
497
+ }
498
+
499
+ /**
500
+ * Sets alternative names for this style (comma-separated)
501
+ * @param aliases - Comma-separated list of alternative names
502
+ * @returns This style for chaining
503
+ */
504
+ setAliases(aliases: string): this {
505
+ this.properties.aliases = aliases;
506
+ return this;
507
+ }
508
+
509
+ /**
510
+ * Sets table-level formatting properties (Phase 5.1)
511
+ * @param formatting - Table formatting options
512
+ * @returns This style for chaining
513
+ */
514
+ setTableFormatting(formatting: TableStyleFormatting): this {
515
+ if (!this.properties.tableStyle) {
516
+ this.properties.tableStyle = {};
517
+ }
518
+ this.properties.tableStyle.table = { ...formatting };
519
+ return this;
520
+ }
521
+
522
+ /**
523
+ * Sets table cell formatting properties (Phase 5.1)
524
+ * @param formatting - Cell formatting options
525
+ * @returns This style for chaining
526
+ */
527
+ setTableCellFormatting(formatting: TableCellStyleFormatting): this {
528
+ if (!this.properties.tableStyle) {
529
+ this.properties.tableStyle = {};
530
+ }
531
+ this.properties.tableStyle.cell = { ...formatting };
532
+ return this;
533
+ }
534
+
535
+ /**
536
+ * Sets table row formatting properties (Phase 5.1)
537
+ * @param formatting - Row formatting options
538
+ * @returns This style for chaining
539
+ */
540
+ setTableRowFormatting(formatting: TableRowStyleFormatting): this {
541
+ if (!this.properties.tableStyle) {
542
+ this.properties.tableStyle = {};
543
+ }
544
+ this.properties.tableStyle.row = { ...formatting };
545
+ return this;
546
+ }
547
+
548
+ /**
549
+ * Sets row band size for row banding (Phase 5.1)
550
+ * @param size - Number of rows per band (default 1)
551
+ * @returns This style for chaining
552
+ */
553
+ setRowBandSize(size: number): this {
554
+ if (!this.properties.tableStyle) {
555
+ this.properties.tableStyle = {};
556
+ }
557
+ if (size < 0) {
558
+ throw new Error('Row band size must be non-negative');
559
+ }
560
+ this.properties.tableStyle.rowBandSize = size;
561
+ return this;
562
+ }
563
+
564
+ /**
565
+ * Sets column band size for column banding (Phase 5.1)
566
+ * @param size - Number of columns per band (default 1)
567
+ * @returns This style for chaining
568
+ */
569
+ setColBandSize(size: number): this {
570
+ if (!this.properties.tableStyle) {
571
+ this.properties.tableStyle = {};
572
+ }
573
+ if (size < 0) {
574
+ throw new Error('Column band size must be non-negative');
575
+ }
576
+ this.properties.tableStyle.colBandSize = size;
577
+ return this;
578
+ }
579
+
580
+ /**
581
+ * Adds conditional formatting for a specific table region (Phase 5.1)
582
+ * @param conditional - Conditional formatting definition
583
+ * @returns This style for chaining
584
+ */
585
+ addConditionalFormatting(conditional: ConditionalTableFormatting): this {
586
+ if (!this.properties.tableStyle) {
587
+ this.properties.tableStyle = {};
588
+ }
589
+ if (!this.properties.tableStyle.conditionalFormatting) {
590
+ this.properties.tableStyle.conditionalFormatting = [];
591
+ }
592
+ this.properties.tableStyle.conditionalFormatting.push({ ...conditional });
593
+ return this;
594
+ }
595
+
596
+ /**
597
+ * Validates that this style definition is valid
598
+ *
599
+ * Checks:
600
+ * - Required fields (styleId, name, type)
601
+ * - Valid type value
602
+ * - No circular references (basedOn != styleId)
603
+ * - Valid formatting values
604
+ *
605
+ * @returns True if the style is valid, false otherwise
606
+ */
607
+ isValid(): boolean {
608
+ try {
609
+ // Required fields
610
+ if (!this.properties.styleId || !this.properties.name || !this.properties.type) {
611
+ return false;
612
+ }
613
+
614
+ // Valid type
615
+ const validTypes: StyleType[] = ['paragraph', 'character', 'table', 'numbering'];
616
+ if (!validTypes.includes(this.properties.type)) {
617
+ return false;
618
+ }
619
+
620
+ // No circular reference
621
+ if (this.properties.basedOn === this.properties.styleId) {
622
+ return false;
623
+ }
624
+
625
+ // Check paragraph formatting if present
626
+ if (this.properties.paragraphFormatting) {
627
+ const pf = this.properties.paragraphFormatting;
628
+
629
+ // Check alignment
630
+ if (pf.alignment) {
631
+ const validAlignments = ['left', 'center', 'right', 'justify', 'both', 'distribute'];
632
+ if (!validAlignments.includes(pf.alignment)) {
633
+ return false;
634
+ }
635
+ }
636
+
637
+ // Check spacing values
638
+ if (pf.spacing) {
639
+ const spacing = pf.spacing;
640
+ if (spacing.before !== undefined && spacing.before < 0) return false;
641
+ if (spacing.after !== undefined && spacing.after < 0) return false;
642
+ if (spacing.line !== undefined && spacing.line < 0) return false;
643
+ if (spacing.lineRule && !['auto', 'exact', 'atLeast'].includes(spacing.lineRule)) {
644
+ return false;
645
+ }
646
+ }
647
+
648
+ // Check indentation values
649
+ if (pf.indentation) {
650
+ const ind = pf.indentation;
651
+ // Indentation values can be negative for hanging indent
652
+ if (ind.left !== undefined && ind.left < -100000) return false;
653
+ if (ind.right !== undefined && ind.right < -100000) return false;
654
+ }
655
+ }
656
+
657
+ // Check run formatting if present
658
+ if (this.properties.runFormatting) {
659
+ const rf = this.properties.runFormatting;
660
+
661
+ // Check font size
662
+ if (rf.size !== undefined && (rf.size <= 0 || rf.size > 1638)) {
663
+ return false; // Max font size in Word is 1638
664
+ }
665
+
666
+ // Check color format (should be 6 hex characters)
667
+ if (rf.color && !/^[0-9A-Fa-f]{6}$/.test(rf.color)) {
668
+ return false;
669
+ }
670
+
671
+ // Check highlight color
672
+ if (rf.highlight) {
673
+ const validHighlights = [
674
+ 'black',
675
+ 'blue',
676
+ 'cyan',
677
+ 'darkBlue',
678
+ 'darkCyan',
679
+ 'darkGray',
680
+ 'darkGreen',
681
+ 'darkMagenta',
682
+ 'darkRed',
683
+ 'darkYellow',
684
+ 'green',
685
+ 'lightGray',
686
+ 'magenta',
687
+ 'none',
688
+ 'red',
689
+ 'white',
690
+ 'yellow',
691
+ ];
692
+ if (!validHighlights.includes(rf.highlight)) {
693
+ return false;
694
+ }
695
+ }
696
+ }
697
+
698
+ // Check metadata properties (Phase 5.3)
699
+ if (this.properties.uiPriority !== undefined) {
700
+ if (this.properties.uiPriority < 0 || this.properties.uiPriority > 99) {
701
+ return false;
702
+ }
703
+ }
704
+
705
+ // Check linked style doesn't reference self
706
+ if (this.properties.link === this.properties.styleId) {
707
+ return false;
708
+ }
709
+
710
+ return true;
711
+ } catch {
712
+ return false;
713
+ }
714
+ }
715
+
716
+ /**
717
+ * Converts the style to WordprocessingML XML element
718
+ * @returns XMLElement representing the style
719
+ */
720
+ toXML(): XMLElement {
721
+ const styleAttrs: Record<string, string> = {
722
+ 'w:type': this.properties.type,
723
+ 'w:styleId': this.properties.styleId,
724
+ };
725
+
726
+ if (this.properties.isDefault) {
727
+ styleAttrs['w:default'] = '1';
728
+ }
729
+
730
+ if (this.properties.customStyle) {
731
+ styleAttrs['w:customStyle'] = '1';
732
+ }
733
+
734
+ const styleChildren: XMLElement[] = [];
735
+
736
+ // Add style name
737
+ styleChildren.push(XMLBuilder.wSelf('name', { 'w:val': this.properties.name }));
738
+
739
+ // aliases - Alternative names (must follow name per CT_Style)
740
+ if (this.properties.aliases) {
741
+ styleChildren.push(XMLBuilder.wSelf('aliases', { 'w:val': this.properties.aliases }));
742
+ }
743
+
744
+ // Add basedOn
745
+ if (this.properties.basedOn) {
746
+ styleChildren.push(XMLBuilder.wSelf('basedOn', { 'w:val': this.properties.basedOn }));
747
+ }
748
+
749
+ // Add next
750
+ if (this.properties.next) {
751
+ styleChildren.push(XMLBuilder.wSelf('next', { 'w:val': this.properties.next }));
752
+ }
753
+
754
+ // Add link (linked character/paragraph style)
755
+ if (this.properties.link) {
756
+ styleChildren.push(XMLBuilder.wSelf('link', { 'w:val': this.properties.link }));
757
+ }
758
+
759
+ // Add autoRedefine
760
+ if (this.properties.autoRedefine) {
761
+ styleChildren.push(XMLBuilder.wSelf('autoRedefine'));
762
+ }
763
+
764
+ // Add metadata properties — ordered per ECMA-376 CT_Style
765
+ // uiPriority - Sort order in style picker
766
+ if (this.properties.uiPriority !== undefined) {
767
+ styleChildren.push(
768
+ XMLBuilder.wSelf('uiPriority', {
769
+ 'w:val': String(this.properties.uiPriority),
770
+ })
771
+ );
772
+ }
773
+
774
+ // semiHidden - Hide from recommended list
775
+ if (this.properties.semiHidden) {
776
+ styleChildren.push(XMLBuilder.wSelf('semiHidden'));
777
+ }
778
+
779
+ // unhideWhenUsed - Auto-show when applied
780
+ if (this.properties.unhideWhenUsed) {
781
+ styleChildren.push(XMLBuilder.wSelf('unhideWhenUsed'));
782
+ }
783
+
784
+ // qFormat - Quick style gallery appearance
785
+ if (this.properties.qFormat !== undefined) {
786
+ if (this.properties.qFormat) {
787
+ styleChildren.push(XMLBuilder.wSelf('qFormat'));
788
+ }
789
+ } else if (!this.properties.customStyle) {
790
+ // Default: built-in styles have qFormat
791
+ styleChildren.push(XMLBuilder.wSelf('qFormat'));
792
+ }
793
+
794
+ // locked - Prevent modification
795
+ if (this.properties.locked) {
796
+ styleChildren.push(XMLBuilder.wSelf('locked'));
797
+ }
798
+
799
+ // personal - User-specific style
800
+ if (this.properties.personal) {
801
+ styleChildren.push(XMLBuilder.wSelf('personal'));
802
+ }
803
+
804
+ // personalCompose - Style for composing new messages
805
+ if (this.properties.personalCompose) {
806
+ styleChildren.push(XMLBuilder.wSelf('personalCompose'));
807
+ }
808
+
809
+ // personalReply - Style for replying to messages
810
+ if (this.properties.personalReply) {
811
+ styleChildren.push(XMLBuilder.wSelf('personalReply'));
812
+ }
813
+
814
+ // Add paragraph properties
815
+ if (this.properties.paragraphFormatting || this.properties.numPr) {
816
+ const pPr = this.generateParagraphProperties(this.properties.paragraphFormatting || {});
817
+ // Add numPr (numbering properties) if present - styles can inherit list formatting
818
+ if (this.properties.numPr) {
819
+ const numPrChildren: XMLElement[] = [];
820
+ if (this.properties.numPr.ilvl !== undefined) {
821
+ numPrChildren.push(
822
+ XMLBuilder.wSelf('ilvl', { 'w:val': String(this.properties.numPr.ilvl) })
823
+ );
824
+ }
825
+ if (this.properties.numPr.numId !== undefined) {
826
+ numPrChildren.push(
827
+ XMLBuilder.wSelf('numId', { 'w:val': String(this.properties.numPr.numId) })
828
+ );
829
+ }
830
+ if (numPrChildren.length > 0) {
831
+ // Insert numPr at the beginning of pPr children (per ECMA-376 element order)
832
+ if (pPr.children) {
833
+ pPr.children.unshift(XMLBuilder.w('numPr', undefined, numPrChildren));
834
+ } else {
835
+ pPr.children = [XMLBuilder.w('numPr', undefined, numPrChildren)];
836
+ }
837
+ }
838
+ }
839
+ if (pPr.children && pPr.children.length > 0) {
840
+ styleChildren.push(pPr);
841
+ }
842
+ }
843
+
844
+ // Add run properties
845
+ if (this.properties.runFormatting) {
846
+ const rPr = this.generateRunProperties(this.properties.runFormatting);
847
+ if (rPr.children && rPr.children.length > 0) {
848
+ styleChildren.push(rPr);
849
+ }
850
+ }
851
+
852
+ // Add table style properties (Phase 5.1)
853
+ if (this.properties.tableStyle) {
854
+ // Add tblPr (table properties)
855
+ if (this.properties.tableStyle.table) {
856
+ const tblPr = this.generateTableProperties(
857
+ this.properties.tableStyle.table,
858
+ this.properties.tableStyle
859
+ );
860
+ if (tblPr.children && tblPr.children.length > 0) {
861
+ styleChildren.push(tblPr);
862
+ }
863
+ }
864
+
865
+ // Add trPr (table row properties) — per CT_Style: tblPr → trPr → tcPr
866
+ if (this.properties.tableStyle.row) {
867
+ const trPr = this.generateTableRowProperties(this.properties.tableStyle.row);
868
+ if (trPr.children && trPr.children.length > 0) {
869
+ styleChildren.push(trPr);
870
+ }
871
+ }
872
+
873
+ // Add tcPr (table cell properties)
874
+ if (this.properties.tableStyle.cell) {
875
+ const tcPr = this.generateTableCellProperties(this.properties.tableStyle.cell);
876
+ if (tcPr.children && tcPr.children.length > 0) {
877
+ styleChildren.push(tcPr);
878
+ }
879
+ }
880
+
881
+ // Add conditional formatting (tblStylePr)
882
+ if (this.properties.tableStyle.conditionalFormatting) {
883
+ for (const conditional of this.properties.tableStyle.conditionalFormatting) {
884
+ const tblStylePr = this.generateConditionalFormatting(conditional);
885
+ if (tblStylePr.children && tblStylePr.children.length > 0) {
886
+ styleChildren.push(tblStylePr);
887
+ }
888
+ }
889
+ }
890
+ }
891
+
892
+ return XMLBuilder.w('style', styleAttrs, styleChildren);
893
+ }
894
+
895
+ /**
896
+ * Generates paragraph properties XML
897
+ */
898
+ private generateParagraphProperties(formatting: ParagraphFormatting): XMLElement {
899
+ const pPrChildren: XMLElement[] = [];
900
+
901
+ // Ordered per ECMA-376 CT_PPrBase
902
+ if (formatting.keepNext) {
903
+ pPrChildren.push(XMLBuilder.wSelf('keepNext'));
904
+ }
905
+ if (formatting.keepLines) {
906
+ pPrChildren.push(XMLBuilder.wSelf('keepLines'));
907
+ }
908
+ if (formatting.pageBreakBefore) {
909
+ pPrChildren.push(XMLBuilder.wSelf('pageBreakBefore'));
910
+ }
911
+
912
+ // Add spacing
913
+ if (formatting.spacing) {
914
+ const spc = formatting.spacing;
915
+ const attributes: Record<string, number | string> = {};
916
+ if (spc.before !== undefined) attributes['w:before'] = spc.before;
917
+ if (spc.after !== undefined) attributes['w:after'] = spc.after;
918
+ if (spc.line !== undefined) attributes['w:line'] = spc.line;
919
+ if (spc.lineRule) attributes['w:lineRule'] = spc.lineRule;
920
+ if (Object.keys(attributes).length > 0) {
921
+ pPrChildren.push(XMLBuilder.wSelf('spacing', attributes));
922
+ }
923
+ }
924
+
925
+ // Add indentation
926
+ if (formatting.indentation) {
927
+ const ind = formatting.indentation;
928
+ const attributes: Record<string, number> = {};
929
+ if (ind.left !== undefined) attributes['w:left'] = ind.left;
930
+ if (ind.right !== undefined) attributes['w:right'] = ind.right;
931
+ if (ind.firstLine !== undefined) attributes['w:firstLine'] = ind.firstLine;
932
+ if (ind.hanging !== undefined) attributes['w:hanging'] = ind.hanging;
933
+ if (Object.keys(attributes).length > 0) {
934
+ pPrChildren.push(XMLBuilder.wSelf('ind', attributes));
935
+ }
936
+ }
937
+
938
+ // Contextual spacing per ECMA-376 Part 1 §17.3.1.8
939
+ // Removes spacing between paragraphs of the same style
940
+ if (formatting.contextualSpacing) {
941
+ pPrChildren.push(XMLBuilder.wSelf('contextualSpacing', { 'w:val': '1' }));
942
+ }
943
+
944
+ // Add alignment
945
+ if (formatting.alignment) {
946
+ // Map 'justify' to 'both' per ECMA-376 (Word uses 'both' for justified text)
947
+ const alignmentValue = formatting.alignment === 'justify' ? 'both' : formatting.alignment;
948
+ pPrChildren.push(XMLBuilder.wSelf('jc', { 'w:val': alignmentValue }));
949
+ }
950
+
951
+ // Add outline level for TOC support (Heading 1 = 0, Heading 2 = 1, etc.)
952
+ if (formatting.outlineLevel !== undefined) {
953
+ pPrChildren.push(
954
+ XMLBuilder.wSelf('outlineLvl', { 'w:val': formatting.outlineLevel.toString() })
955
+ );
956
+ }
957
+
958
+ return XMLBuilder.w('pPr', undefined, pPrChildren);
959
+ }
960
+
961
+ /**
962
+ * Generates run properties XML
963
+ */
964
+ private generateRunProperties(formatting: RunFormatting): XMLElement {
965
+ const rPrChildren: XMLElement[] = [];
966
+
967
+ // Add formatting elements ordered per ECMA-376 CT_RPr
968
+ if (formatting.font) {
969
+ rPrChildren.push(
970
+ XMLBuilder.wSelf('rFonts', {
971
+ 'w:ascii': formatting.font,
972
+ 'w:hAnsi': formatting.font,
973
+ 'w:cs': formatting.font,
974
+ })
975
+ );
976
+ }
977
+ if (formatting.bold) {
978
+ rPrChildren.push(XMLBuilder.wSelf('b'));
979
+ }
980
+ if (formatting.italic) {
981
+ rPrChildren.push(XMLBuilder.wSelf('i'));
982
+ }
983
+ if (formatting.allCaps) {
984
+ rPrChildren.push(XMLBuilder.wSelf('caps'));
985
+ }
986
+ if (formatting.smallCaps) {
987
+ rPrChildren.push(XMLBuilder.wSelf('smallCaps'));
988
+ }
989
+ if (formatting.strike) {
990
+ rPrChildren.push(XMLBuilder.wSelf('strike'));
991
+ }
992
+ if (formatting.dstrike) {
993
+ rPrChildren.push(XMLBuilder.wSelf('dstrike'));
994
+ }
995
+ if (formatting.color) {
996
+ rPrChildren.push(XMLBuilder.wSelf('color', { 'w:val': formatting.color }));
997
+ }
998
+ if (formatting.size !== undefined) {
999
+ const halfPoints = pointsToHalfPoints(formatting.size);
1000
+ rPrChildren.push(XMLBuilder.wSelf('sz', { 'w:val': halfPoints }));
1001
+ rPrChildren.push(XMLBuilder.wSelf('szCs', { 'w:val': halfPoints }));
1002
+ }
1003
+ if (formatting.highlight) {
1004
+ rPrChildren.push(XMLBuilder.wSelf('highlight', { 'w:val': formatting.highlight }));
1005
+ }
1006
+ if (formatting.underline) {
1007
+ const underlineValue =
1008
+ typeof formatting.underline === 'string' ? formatting.underline : 'single';
1009
+ rPrChildren.push(XMLBuilder.wSelf('u', { 'w:val': underlineValue }));
1010
+ }
1011
+ if (formatting.subscript) {
1012
+ rPrChildren.push(XMLBuilder.wSelf('vertAlign', { 'w:val': 'subscript' }));
1013
+ }
1014
+ if (formatting.superscript) {
1015
+ rPrChildren.push(XMLBuilder.wSelf('vertAlign', { 'w:val': 'superscript' }));
1016
+ }
1017
+
1018
+ return XMLBuilder.w('rPr', undefined, rPrChildren);
1019
+ }
1020
+
1021
+ /**
1022
+ * Generates table properties XML (tblPr) - Phase 5.1
1023
+ */
1024
+ private generateTableProperties(
1025
+ formatting: TableStyleFormatting,
1026
+ tableStyle: TableStyleProperties,
1027
+ isConditional = false
1028
+ ): XMLElement {
1029
+ const tblPrChildren: XMLElement[] = [];
1030
+
1031
+ // Ordered per CT_TblPrBase (ECMA-376 §17.4.60):
1032
+ // tblStyleRowBandSize → tblStyleColBandSize → tblW → jc →
1033
+ // tblCellSpacing → tblInd → tblBorders → shd → tblCellMar
1034
+
1035
+ // Row band size (must come first)
1036
+ if (tableStyle.rowBandSize !== undefined) {
1037
+ tblPrChildren.push(
1038
+ XMLBuilder.wSelf('tblStyleRowBandSize', {
1039
+ 'w:val': tableStyle.rowBandSize,
1040
+ })
1041
+ );
1042
+ }
1043
+
1044
+ // Column band size
1045
+ if (tableStyle.colBandSize !== undefined) {
1046
+ tblPrChildren.push(
1047
+ XMLBuilder.wSelf('tblStyleColBandSize', {
1048
+ 'w:val': tableStyle.colBandSize,
1049
+ })
1050
+ );
1051
+ }
1052
+
1053
+ // Note: tblW is omitted for table styles. It's only valid in inline tblPr
1054
+ // (CT_TblPr in table elements), not in CT_Style's tblPr.
1055
+
1056
+ // Table alignment
1057
+ if (formatting.alignment) {
1058
+ tblPrChildren.push(XMLBuilder.wSelf('jc', { 'w:val': formatting.alignment }));
1059
+ }
1060
+
1061
+ // Cell spacing
1062
+ if (formatting.cellSpacing !== undefined) {
1063
+ tblPrChildren.push(
1064
+ XMLBuilder.wSelf('tblCellSpacing', {
1065
+ 'w:w': formatting.cellSpacing,
1066
+ 'w:type': 'dxa',
1067
+ })
1068
+ );
1069
+ }
1070
+
1071
+ // Table indentation
1072
+ if (formatting.indent !== undefined) {
1073
+ tblPrChildren.push(
1074
+ XMLBuilder.wSelf('tblInd', {
1075
+ 'w:w': formatting.indent,
1076
+ 'w:type': 'dxa',
1077
+ })
1078
+ );
1079
+ }
1080
+
1081
+ // Table borders
1082
+ if (formatting.borders) {
1083
+ const borderElements = this.generateBorderElements(formatting.borders, false);
1084
+ if (borderElements.length > 0) {
1085
+ tblPrChildren.push(XMLBuilder.w('tblBorders', undefined, borderElements));
1086
+ }
1087
+ }
1088
+
1089
+ // Table shading (not valid in tblStylePr conditional context)
1090
+ if (formatting.shading && !isConditional) {
1091
+ tblPrChildren.push(this.generateShadingElement(formatting.shading));
1092
+ }
1093
+
1094
+ // Cell margins
1095
+ if (formatting.cellMargins) {
1096
+ const marginElements: XMLElement[] = [];
1097
+ if (formatting.cellMargins.top !== undefined) {
1098
+ marginElements.push(
1099
+ XMLBuilder.wSelf('top', {
1100
+ 'w:w': formatting.cellMargins.top,
1101
+ 'w:type': 'dxa',
1102
+ })
1103
+ );
1104
+ }
1105
+ if (formatting.cellMargins.left !== undefined) {
1106
+ marginElements.push(
1107
+ XMLBuilder.wSelf('left', {
1108
+ 'w:w': formatting.cellMargins.left,
1109
+ 'w:type': 'dxa',
1110
+ })
1111
+ );
1112
+ }
1113
+ if (formatting.cellMargins.bottom !== undefined) {
1114
+ marginElements.push(
1115
+ XMLBuilder.wSelf('bottom', {
1116
+ 'w:w': formatting.cellMargins.bottom,
1117
+ 'w:type': 'dxa',
1118
+ })
1119
+ );
1120
+ }
1121
+ if (formatting.cellMargins.right !== undefined) {
1122
+ marginElements.push(
1123
+ XMLBuilder.wSelf('right', {
1124
+ 'w:w': formatting.cellMargins.right,
1125
+ 'w:type': 'dxa',
1126
+ })
1127
+ );
1128
+ }
1129
+ if (marginElements.length > 0) {
1130
+ tblPrChildren.push(XMLBuilder.w('tblCellMar', undefined, marginElements));
1131
+ }
1132
+ }
1133
+
1134
+ return XMLBuilder.w('tblPr', undefined, tblPrChildren);
1135
+ }
1136
+
1137
+ /**
1138
+ * Generates table cell properties XML (tcPr) - Phase 5.1
1139
+ */
1140
+ private generateTableCellProperties(formatting: TableCellStyleFormatting): XMLElement {
1141
+ const tcPrChildren: XMLElement[] = [];
1142
+
1143
+ // Cell borders
1144
+ if (formatting.borders) {
1145
+ const borderElements = this.generateBorderElements(formatting.borders, true);
1146
+ if (borderElements.length > 0) {
1147
+ tcPrChildren.push(XMLBuilder.w('tcBorders', undefined, borderElements));
1148
+ }
1149
+ }
1150
+
1151
+ // Cell shading
1152
+ if (formatting.shading) {
1153
+ tcPrChildren.push(this.generateShadingElement(formatting.shading));
1154
+ }
1155
+
1156
+ // Cell margins
1157
+ if (formatting.margins) {
1158
+ const marginElements: XMLElement[] = [];
1159
+ if (formatting.margins.top !== undefined) {
1160
+ marginElements.push(
1161
+ XMLBuilder.wSelf('top', {
1162
+ 'w:w': formatting.margins.top,
1163
+ 'w:type': 'dxa',
1164
+ })
1165
+ );
1166
+ }
1167
+ if (formatting.margins.left !== undefined) {
1168
+ marginElements.push(
1169
+ XMLBuilder.wSelf('left', {
1170
+ 'w:w': formatting.margins.left,
1171
+ 'w:type': 'dxa',
1172
+ })
1173
+ );
1174
+ }
1175
+ if (formatting.margins.bottom !== undefined) {
1176
+ marginElements.push(
1177
+ XMLBuilder.wSelf('bottom', {
1178
+ 'w:w': formatting.margins.bottom,
1179
+ 'w:type': 'dxa',
1180
+ })
1181
+ );
1182
+ }
1183
+ if (formatting.margins.right !== undefined) {
1184
+ marginElements.push(
1185
+ XMLBuilder.wSelf('right', {
1186
+ 'w:w': formatting.margins.right,
1187
+ 'w:type': 'dxa',
1188
+ })
1189
+ );
1190
+ }
1191
+ if (marginElements.length > 0) {
1192
+ tcPrChildren.push(XMLBuilder.w('tcMar', undefined, marginElements));
1193
+ }
1194
+ }
1195
+
1196
+ // Vertical alignment
1197
+ if (formatting.verticalAlignment) {
1198
+ tcPrChildren.push(XMLBuilder.wSelf('vAlign', { 'w:val': formatting.verticalAlignment }));
1199
+ }
1200
+
1201
+ return XMLBuilder.w('tcPr', undefined, tcPrChildren);
1202
+ }
1203
+
1204
+ /**
1205
+ * Generates table row properties XML (trPr) - Phase 5.1
1206
+ */
1207
+ private generateTableRowProperties(formatting: TableRowStyleFormatting): XMLElement {
1208
+ const trPrChildren: XMLElement[] = [];
1209
+
1210
+ // Style-level trPr has a restricted content model compared to inline trPr.
1211
+ // Valid children: cantSplit, tblHeader, tblCellSpacing, jc, hidden
1212
+ // NOTE: trHeight is NOT valid in style-level trPr per OOXML strict schema.
1213
+
1214
+ // Can't split row across pages
1215
+ if (formatting.cantSplit) {
1216
+ trPrChildren.push(XMLBuilder.wSelf('cantSplit'));
1217
+ }
1218
+
1219
+ // Header row
1220
+ if (formatting.isHeader) {
1221
+ trPrChildren.push(XMLBuilder.wSelf('tblHeader'));
1222
+ }
1223
+
1224
+ return XMLBuilder.w('trPr', undefined, trPrChildren);
1225
+ }
1226
+
1227
+ /**
1228
+ * Generates conditional formatting XML (tblStylePr) - Phase 5.1
1229
+ */
1230
+ private generateConditionalFormatting(conditional: ConditionalTableFormatting): XMLElement {
1231
+ const tblStylePrChildren: XMLElement[] = [];
1232
+
1233
+ // Add paragraph properties if specified
1234
+ if (conditional.paragraphFormatting) {
1235
+ const pPr = this.generateParagraphProperties(conditional.paragraphFormatting);
1236
+ if (pPr.children && pPr.children.length > 0) {
1237
+ tblStylePrChildren.push(pPr);
1238
+ }
1239
+ }
1240
+
1241
+ // Add run properties if specified
1242
+ if (conditional.runFormatting) {
1243
+ const rPr = this.generateRunProperties(conditional.runFormatting);
1244
+ if (rPr.children && rPr.children.length > 0) {
1245
+ tblStylePrChildren.push(rPr);
1246
+ }
1247
+ }
1248
+
1249
+ // Add table properties if specified
1250
+ if (conditional.tableFormatting) {
1251
+ const tblPr = this.generateTableProperties(conditional.tableFormatting, {}, true);
1252
+ if (tblPr.children && tblPr.children.length > 0) {
1253
+ tblStylePrChildren.push(tblPr);
1254
+ }
1255
+ }
1256
+
1257
+ // Add cell properties if specified
1258
+ if (conditional.cellFormatting) {
1259
+ const tcPr = this.generateTableCellProperties(conditional.cellFormatting);
1260
+ if (tcPr.children && tcPr.children.length > 0) {
1261
+ tblStylePrChildren.push(tcPr);
1262
+ }
1263
+ }
1264
+
1265
+ // Add row properties if specified
1266
+ if (conditional.rowFormatting) {
1267
+ const trPr = this.generateTableRowProperties(conditional.rowFormatting);
1268
+ if (trPr.children && trPr.children.length > 0) {
1269
+ tblStylePrChildren.push(trPr);
1270
+ }
1271
+ }
1272
+
1273
+ return XMLBuilder.w('tblStylePr', { 'w:type': conditional.type }, tblStylePrChildren);
1274
+ }
1275
+
1276
+ /**
1277
+ * Generates border elements for tables or cells - Phase 5.1
1278
+ * @param borders - Border properties
1279
+ * @param includeDiagonals - Whether to include diagonal borders (for cells)
1280
+ */
1281
+ private generateBorderElements(
1282
+ borders: TableBorders | CellBorders,
1283
+ includeDiagonals: boolean
1284
+ ): XMLElement[] {
1285
+ const borderElements: XMLElement[] = [];
1286
+
1287
+ // Ordered per ECMA-376 CT_TblBorders / CT_TcBorders: top, left, bottom, right
1288
+ const borderProps = ['top', 'left', 'bottom', 'right', 'insideH', 'insideV'] as const;
1289
+ for (const prop of borderProps) {
1290
+ const border = borders[prop];
1291
+ if (border) {
1292
+ const attrs: Record<string, string | number> = {};
1293
+ if (border.style) attrs['w:val'] = border.style;
1294
+ if (border.size !== undefined) attrs['w:sz'] = border.size;
1295
+ if (border.space !== undefined) attrs['w:space'] = border.space;
1296
+ if (border.color) attrs['w:color'] = border.color;
1297
+
1298
+ if (Object.keys(attrs).length > 0) {
1299
+ borderElements.push(XMLBuilder.wSelf(prop, attrs));
1300
+ }
1301
+ }
1302
+ }
1303
+
1304
+ // Add diagonal borders for cells
1305
+ if (includeDiagonals) {
1306
+ const cellBorders = borders as CellBorders;
1307
+ if (cellBorders.tl2br) {
1308
+ const attrs: Record<string, string | number> = {};
1309
+ if (cellBorders.tl2br.style) attrs['w:val'] = cellBorders.tl2br.style;
1310
+ if (cellBorders.tl2br.size !== undefined) attrs['w:sz'] = cellBorders.tl2br.size;
1311
+ if (cellBorders.tl2br.space !== undefined) attrs['w:space'] = cellBorders.tl2br.space;
1312
+ if (cellBorders.tl2br.color) attrs['w:color'] = cellBorders.tl2br.color;
1313
+
1314
+ if (Object.keys(attrs).length > 0) {
1315
+ borderElements.push(XMLBuilder.wSelf('tl2br', attrs));
1316
+ }
1317
+ }
1318
+ if (cellBorders.tr2bl) {
1319
+ const attrs: Record<string, string | number> = {};
1320
+ if (cellBorders.tr2bl.style) attrs['w:val'] = cellBorders.tr2bl.style;
1321
+ if (cellBorders.tr2bl.size !== undefined) attrs['w:sz'] = cellBorders.tr2bl.size;
1322
+ if (cellBorders.tr2bl.space !== undefined) attrs['w:space'] = cellBorders.tr2bl.space;
1323
+ if (cellBorders.tr2bl.color) attrs['w:color'] = cellBorders.tr2bl.color;
1324
+
1325
+ if (Object.keys(attrs).length > 0) {
1326
+ borderElements.push(XMLBuilder.wSelf('tr2bl', attrs));
1327
+ }
1328
+ }
1329
+ }
1330
+
1331
+ return borderElements;
1332
+ }
1333
+
1334
+ /**
1335
+ * Generates shading element - Phase 5.1
1336
+ */
1337
+ private generateShadingElement(shading: ShadingProperties): XMLElement {
1338
+ const attrs = buildShadingAttributes(shading);
1339
+ return XMLBuilder.wSelf('shd', attrs);
1340
+ }
1341
+
1342
+ /**
1343
+ * Creates a new Style
1344
+ * @param properties - Style properties
1345
+ * @returns New Style instance
1346
+ */
1347
+ static create(properties: StyleProperties): Style {
1348
+ return new Style(properties);
1349
+ }
1350
+
1351
+ /**
1352
+ * Creates the Normal style (default paragraph style)
1353
+ * @returns Normal style
1354
+ */
1355
+ static createNormalStyle(): Style {
1356
+ return new Style({
1357
+ styleId: 'Normal',
1358
+ name: 'Normal',
1359
+ type: 'paragraph',
1360
+ isDefault: true,
1361
+ next: 'Normal',
1362
+ qFormat: true,
1363
+ uiPriority: 0,
1364
+ paragraphFormatting: {
1365
+ alignment: 'left',
1366
+ spacing: {
1367
+ before: 60, // 3pt
1368
+ after: 60, // 3pt
1369
+ line: 240, // Single line spacing
1370
+ lineRule: 'auto',
1371
+ },
1372
+ },
1373
+ runFormatting: {
1374
+ font: 'Verdana',
1375
+ size: 12,
1376
+ color: '000000',
1377
+ },
1378
+ });
1379
+ }
1380
+
1381
+ /**
1382
+ * Creates a Heading style
1383
+ * @param level - Heading level (1-9)
1384
+ * @returns Heading style
1385
+ */
1386
+ static createHeadingStyle(level: number): Style {
1387
+ if (level < 1 || level > 9) {
1388
+ throw new Error('Heading level must be between 1 and 9');
1389
+ }
1390
+
1391
+ // Font sizes: H1=18pt, H2=14pt, H3=12pt, H4-9=12pt
1392
+ const sizes = [18, 14, 12, 12, 12, 12, 12, 12, 12];
1393
+
1394
+ // Spacing before: H1=0pt, H2=6pt, H3=3pt, H4-9=6pt (in twips: 1pt = 20 twips)
1395
+ const spacingBefore = level === 1 ? 0 : level === 2 ? 120 : level === 3 ? 60 : 120;
1396
+
1397
+ // Spacing after: H1=12pt, H2=6pt, H3=3pt, H4-9=6pt (in twips)
1398
+ const spacingAfter = level === 1 ? 240 : level === 2 ? 120 : level === 3 ? 60 : 120;
1399
+
1400
+ return new Style({
1401
+ styleId: `Heading${level}`,
1402
+ name: `Heading ${level}`,
1403
+ type: 'paragraph',
1404
+ basedOn: 'Normal',
1405
+ next: 'Normal',
1406
+ link: `Heading${level}Char`,
1407
+ qFormat: true,
1408
+ uiPriority: 9,
1409
+ paragraphFormatting: {
1410
+ alignment: 'left',
1411
+ spacing: {
1412
+ before: spacingBefore,
1413
+ after: spacingAfter,
1414
+ line: 240, // Single line spacing
1415
+ lineRule: 'auto',
1416
+ },
1417
+ keepNext: true,
1418
+ keepLines: true,
1419
+ outlineLevel: level - 1, // Heading 1 = 0, Heading 2 = 1, etc. Required for TOC
1420
+ },
1421
+ runFormatting: {
1422
+ font: 'Verdana',
1423
+ size: sizes[level - 1],
1424
+ bold: true,
1425
+ color: '000000',
1426
+ },
1427
+ });
1428
+ }
1429
+
1430
+ /**
1431
+ * Creates a Heading character style (linked to paragraph style)
1432
+ * @param level - Heading level (1-9)
1433
+ * @returns Heading character style
1434
+ */
1435
+ static createHeadingCharStyle(level: number): Style {
1436
+ if (level < 1 || level > 9) {
1437
+ throw new Error('Heading level must be between 1 and 9');
1438
+ }
1439
+
1440
+ // Font sizes: H1=18pt, H2=14pt, H3=12pt, H4-9=12pt
1441
+ const sizes = [18, 14, 12, 12, 12, 12, 12, 12, 12];
1442
+
1443
+ return new Style({
1444
+ styleId: `Heading${level}Char`,
1445
+ name: `Heading ${level} Char`,
1446
+ type: 'character',
1447
+ link: `Heading${level}`,
1448
+ qFormat: true,
1449
+ uiPriority: 9,
1450
+ runFormatting: {
1451
+ font: 'Verdana',
1452
+ size: sizes[level - 1],
1453
+ bold: true,
1454
+ color: '000000',
1455
+ },
1456
+ });
1457
+ }
1458
+
1459
+ /**
1460
+ * Creates the Title style
1461
+ * @returns Title style
1462
+ */
1463
+ static createTitleStyle(): Style {
1464
+ return new Style({
1465
+ styleId: 'Title',
1466
+ name: 'Title',
1467
+ type: 'paragraph',
1468
+ basedOn: 'Normal',
1469
+ next: 'Normal',
1470
+ paragraphFormatting: {
1471
+ spacing: {
1472
+ after: 120,
1473
+ },
1474
+ },
1475
+ runFormatting: {
1476
+ font: 'Verdana',
1477
+ size: 28,
1478
+ color: '000000',
1479
+ },
1480
+ });
1481
+ }
1482
+
1483
+ /**
1484
+ * Creates the Subtitle style
1485
+ * @returns Subtitle style
1486
+ */
1487
+ static createSubtitleStyle(): Style {
1488
+ return new Style({
1489
+ styleId: 'Subtitle',
1490
+ name: 'Subtitle',
1491
+ type: 'paragraph',
1492
+ basedOn: 'Normal',
1493
+ next: 'Normal',
1494
+ paragraphFormatting: {
1495
+ spacing: {
1496
+ after: 120,
1497
+ },
1498
+ },
1499
+ runFormatting: {
1500
+ font: 'Verdana',
1501
+ size: 14,
1502
+ color: '000000',
1503
+ italic: true,
1504
+ },
1505
+ });
1506
+ }
1507
+
1508
+ /**
1509
+ * Creates a List Paragraph style (for lists)
1510
+ * @returns List Paragraph style
1511
+ */
1512
+ static createListParagraphStyle(): Style {
1513
+ return new Style({
1514
+ styleId: 'ListParagraph',
1515
+ name: 'List Paragraph',
1516
+ type: 'paragraph',
1517
+ basedOn: 'Normal',
1518
+ next: 'ListParagraph',
1519
+ qFormat: true,
1520
+ uiPriority: 34,
1521
+ paragraphFormatting: {
1522
+ alignment: 'left',
1523
+ indentation: {
1524
+ left: 720, // 0.5 inch (text indentation)
1525
+ hanging: 360, // 0.25 inch (bullet/number indentation)
1526
+ },
1527
+ spacing: {
1528
+ before: 0, // 0pt
1529
+ after: 60, // 3pt
1530
+ line: 240, // Single line spacing
1531
+ lineRule: 'auto',
1532
+ },
1533
+ contextualSpacing: true, // No space between similar paragraphs
1534
+ },
1535
+ runFormatting: {
1536
+ font: 'Verdana',
1537
+ size: 12,
1538
+ color: '000000',
1539
+ },
1540
+ });
1541
+ }
1542
+
1543
+ /**
1544
+ * Creates a TOC Heading style (for table of contents titles)
1545
+ * @returns TOC Heading style
1546
+ */
1547
+ static createTOCHeadingStyle(): Style {
1548
+ return new Style({
1549
+ styleId: 'TOCHeading',
1550
+ name: 'TOC Heading',
1551
+ type: 'paragraph',
1552
+ basedOn: 'Heading1',
1553
+ next: 'Normal',
1554
+ runFormatting: {
1555
+ bold: true,
1556
+ font: 'Verdana',
1557
+ size: 14,
1558
+ color: '000000', // Black
1559
+ },
1560
+ paragraphFormatting: {
1561
+ spacing: {
1562
+ before: 480, // Larger spacing before TOC
1563
+ after: 240,
1564
+ },
1565
+ },
1566
+ });
1567
+ }
1568
+
1569
+ /**
1570
+ * Creates a TOC entry style for a specific level (1-9).
1571
+ * TOC 1-9 are built-in Word styles for Table of Contents entries.
1572
+ * When Word updates a TOC field, it applies these styles to the generated entries.
1573
+ *
1574
+ * @param level - TOC level (1-9)
1575
+ * @param formatting - Optional run and paragraph formatting overrides
1576
+ * @returns TOC style for the specified level
1577
+ */
1578
+ static createTOCStyle(
1579
+ level: number,
1580
+ formatting?: {
1581
+ run?: RunFormatting;
1582
+ paragraph?: ParagraphFormatting;
1583
+ }
1584
+ ): Style {
1585
+ if (level < 1 || level > 9) {
1586
+ throw new Error('TOC level must be between 1 and 9');
1587
+ }
1588
+
1589
+ // Default indentation: 220 twips per level (11pt)
1590
+ const defaultLeftIndent = (level - 1) * 220;
1591
+
1592
+ return Style.create({
1593
+ styleId: `TOC${level}`,
1594
+ name: `toc ${level}`,
1595
+ type: 'paragraph',
1596
+ basedOn: 'Normal',
1597
+ uiPriority: 39,
1598
+ semiHidden: true,
1599
+ unhideWhenUsed: true,
1600
+ runFormatting: formatting?.run,
1601
+ paragraphFormatting: {
1602
+ indentation: { left: defaultLeftIndent },
1603
+ ...formatting?.paragraph,
1604
+ },
1605
+ });
1606
+ }
1607
+
1608
+ /**
1609
+ * Creates a Table Normal style (Phase 5.1)
1610
+ * @returns Table Normal style
1611
+ */
1612
+ static createTableNormalStyle(): Style {
1613
+ return new Style({
1614
+ styleId: 'TableNormal',
1615
+ name: 'Table Normal',
1616
+ type: 'table',
1617
+ basedOn: 'Normal',
1618
+ tableStyle: {
1619
+ table: {
1620
+ cellMargins: {
1621
+ top: 0,
1622
+ left: 108, // ~0.075 inch
1623
+ bottom: 0,
1624
+ right: 108,
1625
+ },
1626
+ },
1627
+ rowBandSize: 1,
1628
+ colBandSize: 1,
1629
+ },
1630
+ });
1631
+ }
1632
+
1633
+ /**
1634
+ * Creates a Table Grid style with borders (Phase 5.1)
1635
+ * @returns Table Grid style
1636
+ */
1637
+ static createTableGridStyle(): Style {
1638
+ return new Style({
1639
+ styleId: 'TableGrid',
1640
+ name: 'Table Grid',
1641
+ type: 'table',
1642
+ basedOn: 'TableNormal',
1643
+ tableStyle: {
1644
+ table: {
1645
+ borders: {
1646
+ top: { style: 'single', size: 4, color: '000000' },
1647
+ bottom: { style: 'single', size: 4, color: '000000' },
1648
+ left: { style: 'single', size: 4, color: '000000' },
1649
+ right: { style: 'single', size: 4, color: '000000' },
1650
+ insideH: { style: 'single', size: 4, color: '000000' },
1651
+ insideV: { style: 'single', size: 4, color: '000000' },
1652
+ },
1653
+ cellMargins: {
1654
+ top: 0,
1655
+ left: 108,
1656
+ bottom: 0,
1657
+ right: 108,
1658
+ },
1659
+ },
1660
+ rowBandSize: 1,
1661
+ colBandSize: 1,
1662
+ },
1663
+ });
1664
+ }
1665
+
1666
+ /**
1667
+ * Creates a deep clone of this style
1668
+ * @returns New Style instance with copied properties
1669
+ * @example
1670
+ * ```typescript
1671
+ * const original = Style.createHeadingStyle(1);
1672
+ * const copy = original.clone();
1673
+ * copy.setRunFormatting({ color: 'FF0000' }); // Doesn't affect original
1674
+ * ```
1675
+ */
1676
+ clone(): Style {
1677
+ // Deep copy all properties
1678
+ const clonedProps: StyleProperties = deepClone(this.properties);
1679
+ return new Style(clonedProps);
1680
+ }
1681
+
1682
+ /**
1683
+ * Resets the style to its minimal state
1684
+ * Clears all paragraph and run formatting while preserving the style identity
1685
+ * @returns This style for chaining
1686
+ * @example
1687
+ * ```typescript
1688
+ * const style = Style.createHeadingStyle(1);
1689
+ * style.reset(); // Clears all formatting, keeps styleId and name
1690
+ * ```
1691
+ */
1692
+ reset(): this {
1693
+ // Preserve identity properties
1694
+ const { styleId, name, type, basedOn } = this.properties;
1695
+
1696
+ // Reset to minimal properties
1697
+ this.properties = {
1698
+ styleId,
1699
+ name,
1700
+ type,
1701
+ basedOn,
1702
+ };
1703
+
1704
+ return this;
1705
+ }
1706
+
1707
+ /**
1708
+ * Merges properties from another style into this one
1709
+ * @param otherStyle - Style to merge from
1710
+ * @returns This style for chaining
1711
+ * @example
1712
+ * ```typescript
1713
+ * const base = Style.createNormalStyle();
1714
+ * const override = Style.create({
1715
+ * styleId: 'Override',
1716
+ * name: 'Override',
1717
+ * type: 'paragraph',
1718
+ * runFormatting: { bold: true, color: 'FF0000' }
1719
+ * });
1720
+ * base.mergeWith(override); // base now has bold red text
1721
+ * ```
1722
+ */
1723
+ mergeWith(otherStyle: Style): this {
1724
+ const otherProps = otherStyle.getProperties();
1725
+
1726
+ // Merge paragraph formatting
1727
+ if (otherProps.paragraphFormatting) {
1728
+ if (!this.properties.paragraphFormatting) {
1729
+ this.properties.paragraphFormatting = {};
1730
+ }
1731
+
1732
+ // Merge top-level paragraph properties
1733
+ if (otherProps.paragraphFormatting.alignment) {
1734
+ this.properties.paragraphFormatting.alignment = otherProps.paragraphFormatting.alignment;
1735
+ }
1736
+ if (otherProps.paragraphFormatting.keepNext !== undefined) {
1737
+ this.properties.paragraphFormatting.keepNext = otherProps.paragraphFormatting.keepNext;
1738
+ }
1739
+ if (otherProps.paragraphFormatting.keepLines !== undefined) {
1740
+ this.properties.paragraphFormatting.keepLines = otherProps.paragraphFormatting.keepLines;
1741
+ }
1742
+ if (otherProps.paragraphFormatting.pageBreakBefore !== undefined) {
1743
+ this.properties.paragraphFormatting.pageBreakBefore =
1744
+ otherProps.paragraphFormatting.pageBreakBefore;
1745
+ }
1746
+
1747
+ // Merge indentation
1748
+ if (otherProps.paragraphFormatting.indentation) {
1749
+ if (!this.properties.paragraphFormatting.indentation) {
1750
+ this.properties.paragraphFormatting.indentation = {};
1751
+ }
1752
+ Object.assign(
1753
+ this.properties.paragraphFormatting.indentation,
1754
+ otherProps.paragraphFormatting.indentation
1755
+ );
1756
+ }
1757
+
1758
+ // Merge spacing
1759
+ if (otherProps.paragraphFormatting.spacing) {
1760
+ if (!this.properties.paragraphFormatting.spacing) {
1761
+ this.properties.paragraphFormatting.spacing = {};
1762
+ }
1763
+ Object.assign(
1764
+ this.properties.paragraphFormatting.spacing,
1765
+ otherProps.paragraphFormatting.spacing
1766
+ );
1767
+ }
1768
+ }
1769
+
1770
+ // Merge run formatting
1771
+ if (otherProps.runFormatting) {
1772
+ if (!this.properties.runFormatting) {
1773
+ this.properties.runFormatting = {};
1774
+ }
1775
+ Object.assign(this.properties.runFormatting, otherProps.runFormatting);
1776
+ }
1777
+
1778
+ // Merge other properties (but don't override styleId)
1779
+ if (otherProps.name) this.properties.name = otherProps.name;
1780
+ if (otherProps.basedOn) this.properties.basedOn = otherProps.basedOn;
1781
+ if (otherProps.next) this.properties.next = otherProps.next;
1782
+
1783
+ return this;
1784
+ }
1785
+ }