docxmlater 10.0.1 → 10.0.3

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 (395) hide show
  1. package/README.md +3 -2
  2. package/dist/constants/legacyCompatFlags.d.ts.map +1 -1
  3. package/dist/constants/legacyCompatFlags.js.map +1 -1
  4. package/dist/constants/limits.d.ts +0 -27
  5. package/dist/constants/limits.d.ts.map +1 -1
  6. package/dist/constants/limits.js +13 -13
  7. package/dist/constants/limits.js.map +1 -1
  8. package/dist/core/Document.d.ts +24 -19
  9. package/dist/core/Document.d.ts.map +1 -1
  10. package/dist/core/Document.js +272 -71
  11. package/dist/core/Document.js.map +1 -1
  12. package/dist/core/DocumentContent.d.ts.map +1 -1
  13. package/dist/core/DocumentContent.js.map +1 -1
  14. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  15. package/dist/core/DocumentGenerator.js +59 -24
  16. package/dist/core/DocumentGenerator.js.map +1 -1
  17. package/dist/core/DocumentIdManager.d.ts.map +1 -1
  18. package/dist/core/DocumentIdManager.js.map +1 -1
  19. package/dist/core/DocumentParser.d.ts +6 -6
  20. package/dist/core/DocumentParser.d.ts.map +1 -1
  21. package/dist/core/DocumentParser.js +60 -54
  22. package/dist/core/DocumentParser.js.map +1 -1
  23. package/dist/core/DocumentValidator.d.ts.map +1 -1
  24. package/dist/core/DocumentValidator.js.map +1 -1
  25. package/dist/core/Relationship.d.ts.map +1 -1
  26. package/dist/core/Relationship.js +1 -1
  27. package/dist/core/Relationship.js.map +1 -1
  28. package/dist/core/RelationshipManager.js +3 -3
  29. package/dist/core/RelationshipManager.js.map +1 -1
  30. package/dist/elements/AlternateContent.js.map +1 -1
  31. package/dist/elements/Bookmark.d.ts.map +1 -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.js +1 -1
  36. package/dist/elements/Comment.js.map +1 -1
  37. package/dist/elements/CommentManager.d.ts.map +1 -1
  38. package/dist/elements/CommentManager.js +8 -2
  39. package/dist/elements/CommentManager.js.map +1 -1
  40. package/dist/elements/CommonTypes.d.ts.map +1 -1
  41. package/dist/elements/CommonTypes.js +1 -2
  42. package/dist/elements/CommonTypes.js.map +1 -1
  43. package/dist/elements/CustomXml.js.map +1 -1
  44. package/dist/elements/Endnote.d.ts.map +1 -1
  45. package/dist/elements/Endnote.js.map +1 -1
  46. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  47. package/dist/elements/EndnoteManager.js.map +1 -1
  48. package/dist/elements/Field.d.ts.map +1 -1
  49. package/dist/elements/Field.js +31 -28
  50. package/dist/elements/Field.js.map +1 -1
  51. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  52. package/dist/elements/FieldHelpers.js +6 -6
  53. package/dist/elements/FieldHelpers.js.map +1 -1
  54. package/dist/elements/FontManager.d.ts.map +1 -1
  55. package/dist/elements/FontManager.js.map +1 -1
  56. package/dist/elements/Footer.js.map +1 -1
  57. package/dist/elements/Footnote.d.ts.map +1 -1
  58. package/dist/elements/Footnote.js.map +1 -1
  59. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  60. package/dist/elements/FootnoteManager.js.map +1 -1
  61. package/dist/elements/Header.js.map +1 -1
  62. package/dist/elements/HeaderFooterManager.js.map +1 -1
  63. package/dist/elements/Hyperlink.d.ts.map +1 -1
  64. package/dist/elements/Hyperlink.js +5 -5
  65. package/dist/elements/Hyperlink.js.map +1 -1
  66. package/dist/elements/Image.d.ts +2 -2
  67. package/dist/elements/Image.d.ts.map +1 -1
  68. package/dist/elements/Image.js +21 -5
  69. package/dist/elements/Image.js.map +1 -1
  70. package/dist/elements/ImageManager.d.ts.map +1 -1
  71. package/dist/elements/ImageManager.js +2 -2
  72. package/dist/elements/ImageManager.js.map +1 -1
  73. package/dist/elements/ImageRun.js.map +1 -1
  74. package/dist/elements/MathElement.js.map +1 -1
  75. package/dist/elements/Paragraph.d.ts.map +1 -1
  76. package/dist/elements/Paragraph.js +128 -117
  77. package/dist/elements/Paragraph.js.map +1 -1
  78. package/dist/elements/PreservedElement.js.map +1 -1
  79. package/dist/elements/PropertyChangeTypes.js.map +1 -1
  80. package/dist/elements/RangeMarker.js.map +1 -1
  81. package/dist/elements/Revision.d.ts +1 -0
  82. package/dist/elements/Revision.d.ts.map +1 -1
  83. package/dist/elements/Revision.js +44 -5
  84. package/dist/elements/Revision.js.map +1 -1
  85. package/dist/elements/RevisionContent.js.map +1 -1
  86. package/dist/elements/RevisionManager.d.ts.map +1 -1
  87. package/dist/elements/RevisionManager.js.map +1 -1
  88. package/dist/elements/Run.d.ts.map +1 -1
  89. package/dist/elements/Run.js +1 -3
  90. package/dist/elements/Run.js.map +1 -1
  91. package/dist/elements/Section.d.ts.map +1 -1
  92. package/dist/elements/Section.js +127 -118
  93. package/dist/elements/Section.js.map +1 -1
  94. package/dist/elements/Shape.d.ts.map +1 -1
  95. package/dist/elements/Shape.js +21 -0
  96. package/dist/elements/Shape.js.map +1 -1
  97. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  98. package/dist/elements/StructuredDocumentTag.js +20 -8
  99. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  100. package/dist/elements/Table.d.ts +2 -2
  101. package/dist/elements/Table.d.ts.map +1 -1
  102. package/dist/elements/Table.js +29 -35
  103. package/dist/elements/Table.js.map +1 -1
  104. package/dist/elements/TableCell.d.ts +2 -2
  105. package/dist/elements/TableCell.d.ts.map +1 -1
  106. package/dist/elements/TableCell.js +63 -67
  107. package/dist/elements/TableCell.js.map +1 -1
  108. package/dist/elements/TableGridChange.js.map +1 -1
  109. package/dist/elements/TableOfContents.d.ts +6 -6
  110. package/dist/elements/TableOfContents.d.ts.map +1 -1
  111. package/dist/elements/TableOfContents.js.map +1 -1
  112. package/dist/elements/TableOfContentsElement.js.map +1 -1
  113. package/dist/elements/TableRow.d.ts.map +1 -1
  114. package/dist/elements/TableRow.js +65 -47
  115. package/dist/elements/TableRow.js.map +1 -1
  116. package/dist/elements/TextBox.d.ts.map +1 -1
  117. package/dist/elements/TextBox.js +1 -1
  118. package/dist/elements/TextBox.js.map +1 -1
  119. package/dist/formatting/AbstractNumbering.d.ts +1 -1
  120. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  121. package/dist/formatting/AbstractNumbering.js +11 -11
  122. package/dist/formatting/AbstractNumbering.js.map +1 -1
  123. package/dist/formatting/NumberingInstance.d.ts.map +1 -1
  124. package/dist/formatting/NumberingInstance.js +4 -4
  125. package/dist/formatting/NumberingInstance.js.map +1 -1
  126. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  127. package/dist/formatting/NumberingLevel.js +26 -26
  128. package/dist/formatting/NumberingLevel.js.map +1 -1
  129. package/dist/formatting/NumberingManager.d.ts +1 -1
  130. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  131. package/dist/formatting/NumberingManager.js.map +1 -1
  132. package/dist/formatting/Style.d.ts.map +1 -1
  133. package/dist/formatting/Style.js +87 -95
  134. package/dist/formatting/Style.js.map +1 -1
  135. package/dist/formatting/StylesManager.d.ts +3 -3
  136. package/dist/formatting/StylesManager.d.ts.map +1 -1
  137. package/dist/formatting/StylesManager.js.map +1 -1
  138. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  139. package/dist/helpers/CleanupHelper.js +1 -7
  140. package/dist/helpers/CleanupHelper.js.map +1 -1
  141. package/dist/images/ImageOptimizer.js.map +1 -1
  142. package/dist/index.js.map +1 -1
  143. package/dist/managers/DrawingManager.d.ts.map +1 -1
  144. package/dist/managers/DrawingManager.js.map +1 -1
  145. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  146. package/dist/tracking/TrackingContext.js.map +1 -1
  147. package/dist/types/compatibility-types.js.map +1 -1
  148. package/dist/types/formatting.js.map +1 -1
  149. package/dist/types/list-types.d.ts +4 -4
  150. package/dist/types/list-types.d.ts.map +1 -1
  151. package/dist/types/list-types.js.map +1 -1
  152. package/dist/types/settings-types.js.map +1 -1
  153. package/dist/types/styleConfig.js.map +1 -1
  154. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  155. package/dist/utils/ChangelogGenerator.js.map +1 -1
  156. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  157. package/dist/utils/CompatibilityUpgrader.js +7 -7
  158. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  159. package/dist/utils/InMemoryRevisionAcceptor.js +1 -1
  160. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  161. package/dist/utils/MoveOperationHelper.js.map +1 -1
  162. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  163. package/dist/utils/RevisionWalker.js.map +1 -1
  164. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  165. package/dist/utils/ShadingResolver.js +1 -1
  166. package/dist/utils/ShadingResolver.js.map +1 -1
  167. package/dist/utils/acceptRevisions.d.ts +0 -28
  168. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  169. package/dist/utils/acceptRevisions.js +5 -7
  170. package/dist/utils/acceptRevisions.js.map +1 -1
  171. package/dist/utils/cnfStyleDecoder.js +1 -1
  172. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  173. package/dist/utils/corruptionDetection.js.map +1 -1
  174. package/dist/utils/dateFormatting.js.map +1 -1
  175. package/dist/utils/deepClone.d.ts +0 -1
  176. package/dist/utils/deepClone.d.ts.map +1 -1
  177. package/dist/utils/deepClone.js +0 -7
  178. package/dist/utils/deepClone.js.map +1 -1
  179. package/dist/utils/diagnostics.d.ts +2 -2
  180. package/dist/utils/diagnostics.d.ts.map +1 -1
  181. package/dist/utils/diagnostics.js.map +1 -1
  182. package/dist/utils/errorHandling.js.map +1 -1
  183. package/dist/utils/formatting.js.map +1 -1
  184. package/dist/utils/list-detection.d.ts +2 -2
  185. package/dist/utils/list-detection.d.ts.map +1 -1
  186. package/dist/utils/list-detection.js +3 -3
  187. package/dist/utils/list-detection.js.map +1 -1
  188. package/dist/utils/logger.d.ts +2 -4
  189. package/dist/utils/logger.d.ts.map +1 -1
  190. package/dist/utils/logger.js +0 -2
  191. package/dist/utils/logger.js.map +1 -1
  192. package/dist/utils/parsingHelpers.js.map +1 -1
  193. package/dist/utils/stripTrackedChanges.d.ts +0 -19
  194. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  195. package/dist/utils/stripTrackedChanges.js +0 -2
  196. package/dist/utils/stripTrackedChanges.js.map +1 -1
  197. package/dist/utils/textDiff.js.map +1 -1
  198. package/dist/utils/units.js.map +1 -1
  199. package/dist/utils/validation.d.ts.map +1 -1
  200. package/dist/utils/validation.js.map +1 -1
  201. package/dist/utils/xmlSanitization.js.map +1 -1
  202. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  203. package/dist/validation/RevisionValidator.js.map +1 -1
  204. package/dist/validation/ValidationRules.js.map +1 -1
  205. package/dist/validation/index.js.map +1 -1
  206. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  207. package/dist/xml/XMLBuilder.js +10 -0
  208. package/dist/xml/XMLBuilder.js.map +1 -1
  209. package/dist/xml/XMLParser.d.ts.map +1 -1
  210. package/dist/xml/XMLParser.js +4 -5
  211. package/dist/xml/XMLParser.js.map +1 -1
  212. package/dist/zip/ZipHandler.js.map +1 -1
  213. package/dist/zip/ZipReader.js.map +1 -1
  214. package/dist/zip/ZipWriter.js.map +1 -1
  215. package/dist/zip/errors.js.map +1 -1
  216. package/dist/zip/types.js.map +1 -1
  217. package/package.json +34 -4
  218. package/src/__tests__/helper-methods.test.ts +512 -0
  219. package/src/constants/legacyCompatFlags.ts +138 -0
  220. package/src/constants/limits.ts +50 -0
  221. package/src/core/CLAUDE.md +109 -0
  222. package/src/core/Document.ts +15569 -0
  223. package/src/core/DocumentContent.ts +467 -0
  224. package/src/core/DocumentGenerator.ts +1104 -0
  225. package/src/core/DocumentIdManager.ts +158 -0
  226. package/src/core/DocumentParser.ts +10107 -0
  227. package/src/core/DocumentValidator.ts +372 -0
  228. package/src/core/Relationship.ts +367 -0
  229. package/src/core/RelationshipManager.ts +428 -0
  230. package/src/elements/AlternateContent.ts +42 -0
  231. package/src/elements/Bookmark.ts +210 -0
  232. package/src/elements/BookmarkManager.ts +250 -0
  233. package/src/elements/CLAUDE.md +126 -0
  234. package/src/elements/Comment.ts +359 -0
  235. package/src/elements/CommentManager.ts +502 -0
  236. package/src/elements/CommonTypes.ts +549 -0
  237. package/src/elements/CustomXml.ts +36 -0
  238. package/src/elements/Endnote.ts +217 -0
  239. package/src/elements/EndnoteManager.ts +249 -0
  240. package/src/elements/Field.ts +1233 -0
  241. package/src/elements/FieldHelpers.ts +333 -0
  242. package/src/elements/FontManager.ts +339 -0
  243. package/src/elements/Footer.ts +269 -0
  244. package/src/elements/Footnote.ts +217 -0
  245. package/src/elements/FootnoteManager.ts +249 -0
  246. package/src/elements/Header.ts +269 -0
  247. package/src/elements/HeaderFooterManager.ts +219 -0
  248. package/src/elements/Hyperlink.ts +1146 -0
  249. package/src/elements/Image.ts +1756 -0
  250. package/src/elements/ImageManager.ts +432 -0
  251. package/src/elements/ImageRun.ts +59 -0
  252. package/src/elements/MathElement.ts +65 -0
  253. package/src/elements/Paragraph.ts +4227 -0
  254. package/src/elements/PreservedElement.ts +53 -0
  255. package/src/elements/PropertyChangeTypes.ts +442 -0
  256. package/src/elements/RangeMarker.ts +400 -0
  257. package/src/elements/Revision.ts +1217 -0
  258. package/src/elements/RevisionContent.ts +73 -0
  259. package/src/elements/RevisionManager.ts +1070 -0
  260. package/src/elements/Run.ts +3068 -0
  261. package/src/elements/Section.ts +1421 -0
  262. package/src/elements/Shape.ts +873 -0
  263. package/src/elements/StructuredDocumentTag.ts +978 -0
  264. package/src/elements/Table.ts +2524 -0
  265. package/src/elements/TableCell.ts +1586 -0
  266. package/src/elements/TableGridChange.ts +151 -0
  267. package/src/elements/TableOfContents.ts +691 -0
  268. package/src/elements/TableOfContentsElement.ts +89 -0
  269. package/src/elements/TableRow.ts +906 -0
  270. package/src/elements/TextBox.ts +768 -0
  271. package/src/formatting/AbstractNumbering.ts +548 -0
  272. package/src/formatting/CLAUDE.md +74 -0
  273. package/src/formatting/NumberingInstance.ts +212 -0
  274. package/src/formatting/NumberingLevel.ts +1006 -0
  275. package/src/formatting/NumberingManager.ts +827 -0
  276. package/src/formatting/Style.ts +1833 -0
  277. package/src/formatting/StylesManager.ts +1005 -0
  278. package/src/helpers/CleanupHelper.ts +524 -0
  279. package/src/images/ImageOptimizer.ts +274 -0
  280. package/src/index.ts +554 -0
  281. package/src/managers/CLAUDE.md +47 -0
  282. package/src/managers/DrawingManager.ts +319 -0
  283. package/src/tracking/DocumentTrackingContext.ts +643 -0
  284. package/src/tracking/TrackingContext.ts +173 -0
  285. package/src/types/compatibility-types.ts +49 -0
  286. package/src/types/formatting.ts +210 -0
  287. package/src/types/list-types.ts +152 -0
  288. package/src/types/settings-types.ts +59 -0
  289. package/src/types/styleConfig.ts +189 -0
  290. package/src/utils/CLAUDE.md +153 -0
  291. package/src/utils/ChangelogGenerator.ts +1581 -0
  292. package/src/utils/CompatibilityUpgrader.ts +237 -0
  293. package/src/utils/InMemoryRevisionAcceptor.ts +668 -0
  294. package/src/utils/MoveOperationHelper.ts +238 -0
  295. package/src/utils/RevisionAwareProcessor.ts +526 -0
  296. package/src/utils/RevisionWalker.ts +457 -0
  297. package/src/utils/SelectiveRevisionAcceptor.ts +613 -0
  298. package/src/utils/ShadingResolver.ts +107 -0
  299. package/src/utils/acceptRevisions.ts +714 -0
  300. package/src/utils/cnfStyleDecoder.ts +217 -0
  301. package/src/utils/corruptionDetection.ts +345 -0
  302. package/src/utils/dateFormatting.ts +20 -0
  303. package/src/utils/deepClone.ts +78 -0
  304. package/src/utils/diagnostics.ts +129 -0
  305. package/src/utils/errorHandling.ts +80 -0
  306. package/src/utils/formatting.ts +213 -0
  307. package/src/utils/list-detection.ts +274 -0
  308. package/src/utils/logger.ts +404 -0
  309. package/src/utils/parsingHelpers.ts +190 -0
  310. package/src/utils/stripTrackedChanges.ts +353 -0
  311. package/src/utils/textDiff.ts +100 -0
  312. package/src/utils/units.ts +421 -0
  313. package/src/utils/validation.ts +542 -0
  314. package/src/utils/xmlSanitization.ts +182 -0
  315. package/src/validation/RevisionAutoFixer.ts +542 -0
  316. package/src/validation/RevisionValidator.ts +460 -0
  317. package/src/validation/ValidationRules.ts +338 -0
  318. package/src/validation/index.ts +30 -0
  319. package/src/xml/CLAUDE.md +65 -0
  320. package/src/xml/XMLBuilder.ts +871 -0
  321. package/src/xml/XMLParser.ts +919 -0
  322. package/src/zip/CLAUDE.md +55 -0
  323. package/src/zip/ZipHandler.ts +637 -0
  324. package/src/zip/ZipReader.ts +299 -0
  325. package/src/zip/ZipWriter.ts +390 -0
  326. package/src/zip/errors.ts +69 -0
  327. package/src/zip/types.ts +116 -0
  328. package/dist/core/ListNormalizer.d.ts +0 -23
  329. package/dist/core/ListNormalizer.d.ts.map +0 -1
  330. package/dist/core/ListNormalizer.js +0 -624
  331. package/dist/core/ListNormalizer.js.map +0 -1
  332. package/dist/images/index.d.ts +0 -2
  333. package/dist/images/index.d.ts.map +0 -1
  334. package/dist/images/index.js +0 -8
  335. package/dist/images/index.js.map +0 -1
  336. package/dist/ms-doc/cfb/CFBReader.d.ts +0 -35
  337. package/dist/ms-doc/cfb/CFBReader.d.ts.map +0 -1
  338. package/dist/ms-doc/cfb/CFBReader.js +0 -360
  339. package/dist/ms-doc/cfb/CFBReader.js.map +0 -1
  340. package/dist/ms-doc/converter/DocToDocxConverter.d.ts +0 -55
  341. package/dist/ms-doc/converter/DocToDocxConverter.d.ts.map +0 -1
  342. package/dist/ms-doc/converter/DocToDocxConverter.js +0 -324
  343. package/dist/ms-doc/converter/DocToDocxConverter.js.map +0 -1
  344. package/dist/ms-doc/fib/FIB.d.ts +0 -18
  345. package/dist/ms-doc/fib/FIB.d.ts.map +0 -1
  346. package/dist/ms-doc/fib/FIB.js +0 -342
  347. package/dist/ms-doc/fib/FIB.js.map +0 -1
  348. package/dist/ms-doc/fields/FieldParser.d.ts +0 -31
  349. package/dist/ms-doc/fields/FieldParser.d.ts.map +0 -1
  350. package/dist/ms-doc/fields/FieldParser.js +0 -266
  351. package/dist/ms-doc/fields/FieldParser.js.map +0 -1
  352. package/dist/ms-doc/images/PictureExtractor.d.ts +0 -22
  353. package/dist/ms-doc/images/PictureExtractor.d.ts.map +0 -1
  354. package/dist/ms-doc/images/PictureExtractor.js +0 -233
  355. package/dist/ms-doc/images/PictureExtractor.js.map +0 -1
  356. package/dist/ms-doc/index.d.ts +0 -20
  357. package/dist/ms-doc/index.d.ts.map +0 -1
  358. package/dist/ms-doc/index.js +0 -59
  359. package/dist/ms-doc/index.js.map +0 -1
  360. package/dist/ms-doc/properties/SPRM.d.ts +0 -210
  361. package/dist/ms-doc/properties/SPRM.d.ts.map +0 -1
  362. package/dist/ms-doc/properties/SPRM.js +0 -633
  363. package/dist/ms-doc/properties/SPRM.js.map +0 -1
  364. package/dist/ms-doc/sections/SectionParser.d.ts +0 -25
  365. package/dist/ms-doc/sections/SectionParser.d.ts.map +0 -1
  366. package/dist/ms-doc/sections/SectionParser.js +0 -214
  367. package/dist/ms-doc/sections/SectionParser.js.map +0 -1
  368. package/dist/ms-doc/styles/StyleSheet.d.ts +0 -23
  369. package/dist/ms-doc/styles/StyleSheet.d.ts.map +0 -1
  370. package/dist/ms-doc/styles/StyleSheet.js +0 -268
  371. package/dist/ms-doc/styles/StyleSheet.js.map +0 -1
  372. package/dist/ms-doc/subdocuments/SubdocumentParser.d.ts +0 -61
  373. package/dist/ms-doc/subdocuments/SubdocumentParser.d.ts.map +0 -1
  374. package/dist/ms-doc/subdocuments/SubdocumentParser.js +0 -208
  375. package/dist/ms-doc/subdocuments/SubdocumentParser.js.map +0 -1
  376. package/dist/ms-doc/tables/TableParser.d.ts +0 -29
  377. package/dist/ms-doc/tables/TableParser.d.ts.map +0 -1
  378. package/dist/ms-doc/tables/TableParser.js +0 -176
  379. package/dist/ms-doc/tables/TableParser.js.map +0 -1
  380. package/dist/ms-doc/text/PieceTable.d.ts +0 -21
  381. package/dist/ms-doc/text/PieceTable.d.ts.map +0 -1
  382. package/dist/ms-doc/text/PieceTable.js +0 -171
  383. package/dist/ms-doc/text/PieceTable.js.map +0 -1
  384. package/dist/ms-doc/types/Constants.d.ts +0 -99
  385. package/dist/ms-doc/types/Constants.d.ts.map +0 -1
  386. package/dist/ms-doc/types/Constants.js +0 -102
  387. package/dist/ms-doc/types/Constants.js.map +0 -1
  388. package/dist/ms-doc/types/DocTypes.d.ts +0 -368
  389. package/dist/ms-doc/types/DocTypes.d.ts.map +0 -1
  390. package/dist/ms-doc/types/DocTypes.js +0 -3
  391. package/dist/ms-doc/types/DocTypes.js.map +0 -1
  392. package/dist/tracking/index.d.ts +0 -3
  393. package/dist/tracking/index.d.ts.map +0 -1
  394. package/dist/tracking/index.js +0 -6
  395. package/dist/tracking/index.js.map +0 -1
@@ -0,0 +1,3068 @@
1
+ /**
2
+ * Run - Represents a run of text with uniform formatting
3
+ * A run is the smallest unit of text formatting in a Word document
4
+ */
5
+
6
+ import { deepClone } from "../utils/deepClone";
7
+ import { formatDateForXml } from "../utils/dateFormatting";
8
+ import { logSerialization, logTextDirection } from "../utils/diagnostics";
9
+ import { defaultLogger } from "../utils/logger";
10
+ import { normalizeColor, validateRunText } from "../utils/validation";
11
+ import { pointsToHalfPoints } from "../utils/units";
12
+ import { diffText, diffHasUnchangedParts } from "../utils/textDiff";
13
+ import { getActiveConditionalsInPriorityOrder } from "../utils/cnfStyleDecoder";
14
+ import { XMLBuilder, XMLElement } from "../xml/XMLBuilder";
15
+ import {
16
+ ShadingPattern as CommonShadingPattern,
17
+ ShadingConfig,
18
+ buildShadingAttributes,
19
+ ExtendedBorderStyle,
20
+ BorderDefinition,
21
+ } from "./CommonTypes";
22
+ import type { RunPropertyChange } from "./PropertyChangeTypes";
23
+ // Type-only import to avoid circular dependency (Revision imports Run)
24
+ import type { Revision as RevisionType } from "./Revision";
25
+
26
+ /**
27
+ * Run content element types
28
+ * Per ECMA-376 Part 1 §17.3.3 EG_RunInnerContent, runs can contain multiple types of content
29
+ */
30
+ export type RunContentType =
31
+ | "text" // <w:t> - Regular text
32
+ | "tab" // <w:tab/> - Tab character (used in TOC entries)
33
+ | "break" // <w:br/> - Line/page/column break
34
+ | "carriageReturn" // <w:cr/> - Carriage return
35
+ | "softHyphen" // <w:softHyphen/> - Optional hyphen
36
+ | "noBreakHyphen" // <w:noBreakHyphen/> - Non-breaking hyphen
37
+ | "instructionText" // <w:instrText> - Field instruction text
38
+ | "fieldChar" // <w:fldChar/> - Field character markers
39
+ | "vml" // <w:pict> - VML/legacy graphics (preserved as raw XML)
40
+ | "lastRenderedPageBreak" // <w:lastRenderedPageBreak/> - Last rendered page break position
41
+ | "separator" // <w:separator/> - Footnote/endnote separator line
42
+ | "continuationSeparator" // <w:continuationSeparator/> - Continuation separator line
43
+ | "pageNumber" // <w:pgNum/> - Page number field
44
+ | "annotationRef" // <w:annotationRef/> - Annotation reference marker
45
+ | "dayShort" // <w:dayShort/> - Short date day field
46
+ | "dayLong" // <w:dayLong/> - Long date day field
47
+ | "monthShort" // <w:monthShort/> - Short month field
48
+ | "monthLong" // <w:monthLong/> - Long month field
49
+ | "yearShort" // <w:yearShort/> - Short year field
50
+ | "yearLong" // <w:yearLong/> - Long year field
51
+ | "symbol" // <w:sym/> - Symbol character with font and char code
52
+ | "positionTab" // <w:ptab/> - Absolute position tab
53
+ | "embeddedObject" // <w:object> - Embedded OLE object (preserved as raw XML)
54
+ | "footnoteReference" // <w:footnoteReference/> - Footnote reference marker
55
+ | "endnoteReference"; // <w:endnoteReference/> - Endnote reference marker
56
+
57
+ /**
58
+ * Break type for <w:br> elements
59
+ * Per ECMA-376 Part 1 §17.18.3
60
+ */
61
+ export type BreakType = "page" | "column" | "textWrapping";
62
+
63
+ /**
64
+ * Run content element
65
+ * Represents a single content element within a run (text, tab, break, etc.)
66
+ */
67
+ export interface RunContent {
68
+ /** Type of content element */
69
+ type: RunContentType;
70
+ /** Text value (for 'text' and 'instructionText' types) */
71
+ value?: string;
72
+ /** Break type (only for 'break' type) */
73
+ breakType?: BreakType;
74
+ /** Field character subtype (only for 'fieldChar' type) */
75
+ fieldCharType?: "begin" | "separate" | "end";
76
+ /** Whether the field char is marked dirty */
77
+ fieldCharDirty?: boolean;
78
+ /** Whether the field char is locked */
79
+ fieldCharLocked?: boolean;
80
+ /** Form field data (only for 'fieldChar' type with fieldCharType='begin') per ECMA-376 Part 1 §17.16.17 */
81
+ formFieldData?: FormFieldData;
82
+ /** Raw XML content (for 'vml' type - preserves w:pict elements as-is) */
83
+ rawXml?: string;
84
+ /**
85
+ * Whether this content came from a deleted section (w:delText or w:delInstrText)
86
+ * Per ECMA-376 Part 1 §22.1.2.26-27, deleted content uses special elements
87
+ * This flag helps with proper serialization back to w:delText/w:delInstrText
88
+ */
89
+ isDeleted?: boolean;
90
+ /** Symbol font name (only for 'symbol' type, w:sym w:font) */
91
+ symbolFont?: string;
92
+ /** Symbol character code (only for 'symbol' type, w:sym w:char) */
93
+ symbolChar?: string;
94
+ /** Position tab alignment (only for 'positionTab' type, w:ptab w:alignment) */
95
+ ptabAlignment?: string;
96
+ /** Position tab relative-to (only for 'positionTab' type, w:ptab w:relativeTo) */
97
+ ptabRelativeTo?: string;
98
+ /** Position tab leader character (only for 'positionTab' type, w:ptab w:leader) */
99
+ ptabLeader?: string;
100
+ /** Footnote ID (only for 'footnoteReference' type, w:footnoteReference w:id) */
101
+ footnoteId?: number;
102
+ /** Endnote ID (only for 'endnoteReference' type, w:endnoteReference w:id) */
103
+ endnoteId?: number;
104
+ }
105
+
106
+ /**
107
+ * Border style for text
108
+ * @see CommonTypes.ExtendedBorderStyle for the canonical definition
109
+ */
110
+ export type TextBorderStyle = ExtendedBorderStyle;
111
+
112
+ /**
113
+ * Text border definition
114
+ */
115
+ export interface TextBorder {
116
+ /** Border style */
117
+ style?: TextBorderStyle;
118
+ /** Border size in eighths of a point */
119
+ size?: number;
120
+ /** Border color in hex format (without #) */
121
+ color?: string;
122
+ /** Space between border and text in points */
123
+ space?: number;
124
+ }
125
+
126
+ /**
127
+ * Shading pattern for text background
128
+ * @see CommonTypes.ShadingPattern for the canonical definition
129
+ */
130
+ export type ShadingPattern = CommonShadingPattern;
131
+
132
+ /**
133
+ * Character shading definition
134
+ * @see ShadingConfig in CommonTypes.ts for the canonical definition
135
+ */
136
+ export type CharacterShading = ShadingConfig;
137
+
138
+ /**
139
+ * East Asian typography layout options
140
+ */
141
+ export interface EastAsianLayout {
142
+ /** Layout ID for specific Asian typography */
143
+ id?: number;
144
+ /** Vertical text layout */
145
+ vert?: boolean;
146
+ /** Compress vertical text */
147
+ vertCompress?: boolean;
148
+ /** Combine characters into single character space */
149
+ combine?: boolean;
150
+ /** Bracket characters for combined text */
151
+ combineBrackets?: "none" | "round" | "square" | "angle" | "curly";
152
+ }
153
+
154
+ /**
155
+ * Form field text input data per ECMA-376 Part 1 §17.16.33
156
+ */
157
+ export interface FormFieldTextInput {
158
+ type: 'textInput';
159
+ /** Input type per ECMA-376 §17.16.34 (e.g., 'regular', 'number', 'date', 'currentDate', 'currentTime', 'calculated') */
160
+ inputType?: string;
161
+ /** Default text value */
162
+ defaultValue?: string;
163
+ /** Maximum length (0 = unlimited) */
164
+ maxLength?: number;
165
+ /** Text format (e.g., 'UPPERCASE', 'Lowercase', 'First capital') */
166
+ format?: string;
167
+ }
168
+
169
+ /**
170
+ * Form field checkbox data per ECMA-376 Part 1 §17.16.7
171
+ */
172
+ export interface FormFieldCheckBox {
173
+ type: 'checkBox';
174
+ /** Default state */
175
+ defaultChecked?: boolean;
176
+ /** Current checked state */
177
+ checked?: boolean;
178
+ /** Size (auto or specific in half-points) */
179
+ size?: number | 'auto';
180
+ }
181
+
182
+ /**
183
+ * Form field dropdown list data per ECMA-376 Part 1 §17.16.12
184
+ */
185
+ export interface FormFieldDropDownList {
186
+ type: 'dropDownList';
187
+ /** Selected item index (0-based) */
188
+ result?: number;
189
+ /** Default item index */
190
+ defaultResult?: number;
191
+ /** List entries */
192
+ listEntries?: string[];
193
+ }
194
+
195
+ /**
196
+ * Form field data per ECMA-376 Part 1 §17.16.17
197
+ */
198
+ export interface FormFieldData {
199
+ /** Field name */
200
+ name?: string;
201
+ /** Whether the field is enabled */
202
+ enabled?: boolean;
203
+ /** Calculate on exit */
204
+ calcOnExit?: boolean;
205
+ /** Help text */
206
+ helpText?: string;
207
+ /** Status bar text */
208
+ statusText?: string;
209
+ /** Entry macro name */
210
+ entryMacro?: string;
211
+ /** Exit macro name */
212
+ exitMacro?: string;
213
+ /** Field-specific data */
214
+ fieldType?: FormFieldTextInput | FormFieldCheckBox | FormFieldDropDownList;
215
+ }
216
+
217
+ /**
218
+ * Emphasis mark type - decorative marks above/below text
219
+ */
220
+ export type EmphasisMark = "dot" | "comma" | "circle" | "underDot";
221
+
222
+ /**
223
+ * Theme color values per ECMA-376 Part 1 Section 17.18.96 (ST_ThemeColor)
224
+ * These reference colors defined in the document's theme (theme1.xml)
225
+ */
226
+ export type ThemeColorValue =
227
+ | "dark1"
228
+ | "light1"
229
+ | "dark2"
230
+ | "light2"
231
+ | "accent1"
232
+ | "accent2"
233
+ | "accent3"
234
+ | "accent4"
235
+ | "accent5"
236
+ | "accent6"
237
+ | "hyperlink"
238
+ | "followedHyperlink"
239
+ | "background1"
240
+ | "text1"
241
+ | "background2"
242
+ | "text2";
243
+
244
+ /**
245
+ * Text formatting options for a run
246
+ */
247
+ export interface RunFormatting {
248
+ /** Character style reference - links to a character style definition */
249
+ characterStyle?: string;
250
+ /** Text border - draws a border around the text */
251
+ border?: TextBorder;
252
+ /** Character shading - background color/pattern for text */
253
+ shading?: CharacterShading;
254
+ /** Emphasis mark - decorative mark above/below text (e.g., dot, comma) */
255
+ emphasis?: EmphasisMark;
256
+ /** Bold text */
257
+ bold?: boolean;
258
+ /** Italic text */
259
+ italic?: boolean;
260
+ /** Bold text for complex scripts (RTL languages like Arabic, Hebrew) */
261
+ complexScriptBold?: boolean;
262
+ /** Italic text for complex scripts (RTL languages like Arabic, Hebrew) */
263
+ complexScriptItalic?: boolean;
264
+ /** Character spacing (letter spacing) in twips (1/20th of a point) */
265
+ characterSpacing?: number;
266
+ /** Horizontal scaling percentage (e.g., 200 = 200% width, 50 = 50% width) */
267
+ scaling?: number;
268
+ /** Vertical position in half-points (positive = raised, negative = lowered) */
269
+ position?: number;
270
+ /** Kerning threshold in half-points (font size at which kerning starts) */
271
+ kerning?: number;
272
+ /** Language code (e.g., 'en-US', 'fr-FR', 'es-ES') */
273
+ language?: string;
274
+ /** Underline text. Use "none" to explicitly override style underline. */
275
+ underline?: boolean | "single" | "double" | "thick" | "dotted" | "dash" | "none";
276
+ /** Underline color in hex format (without #) per ECMA-376 Part 1 §17.3.2.40 */
277
+ underlineColor?: string;
278
+ /** Underline theme color reference per ECMA-376 Part 1 §17.3.2.40 */
279
+ underlineThemeColor?: ThemeColorValue;
280
+ /** Underline theme tint (0-255) per ECMA-376 Part 1 §17.3.2.40 */
281
+ underlineThemeTint?: number;
282
+ /** Underline theme shade (0-255) per ECMA-376 Part 1 §17.3.2.40 */
283
+ underlineThemeShade?: number;
284
+ /** Strikethrough text */
285
+ strike?: boolean;
286
+ /** Double strikethrough */
287
+ dstrike?: boolean;
288
+ /** Subscript */
289
+ subscript?: boolean;
290
+ /** Superscript */
291
+ superscript?: boolean;
292
+ /** Font name */
293
+ font?: string;
294
+ /** Font size in points (half-points for Word) */
295
+ size?: number;
296
+ /** Font size for complex scripts (RTL languages) in points. If not set, uses size. */
297
+ sizeCs?: number;
298
+ /** Text color in hex format (without #) */
299
+ color?: string;
300
+ /**
301
+ * Theme color reference for text color per ECMA-376 Part 1 Section 17.3.2.6
302
+ * When set, the color is derived from the document's theme rather than a fixed hex value
303
+ */
304
+ themeColor?: ThemeColorValue;
305
+ /**
306
+ * Theme color tint (0-255, where 0=no tint, 255=full tint toward white)
307
+ * Applied to themeColor to create lighter variations
308
+ */
309
+ themeTint?: number;
310
+ /**
311
+ * Theme color shade (0-255, where 0=no shade, 255=full shade toward black)
312
+ * Applied to themeColor to create darker variations
313
+ */
314
+ themeShade?: number;
315
+ /** Highlight color */
316
+ highlight?:
317
+ | "yellow"
318
+ | "green"
319
+ | "cyan"
320
+ | "magenta"
321
+ | "blue"
322
+ | "red"
323
+ | "darkBlue"
324
+ | "darkCyan"
325
+ | "darkGreen"
326
+ | "darkMagenta"
327
+ | "darkRed"
328
+ | "darkYellow"
329
+ | "darkGray"
330
+ | "lightGray"
331
+ | "black"
332
+ | "white";
333
+ /** Small caps */
334
+ smallCaps?: boolean;
335
+ /** All caps */
336
+ allCaps?: boolean;
337
+ /** Outline text effect - displays text with an outline */
338
+ outline?: boolean;
339
+ /** Shadow text effect - displays text with a shadow */
340
+ shadow?: boolean;
341
+ /** Emboss text effect - displays text with a 3D embossed appearance */
342
+ emboss?: boolean;
343
+ /** Imprint/engrave text effect - displays text with a pressed-in appearance */
344
+ imprint?: boolean;
345
+ /** Right-to-left text direction (for languages like Arabic, Hebrew) */
346
+ rtl?: boolean;
347
+ /** Hidden/vanish text (not displayed but present in document) */
348
+ vanish?: boolean;
349
+ /** No proofing - skip spell check and grammar check for this text */
350
+ noProof?: boolean;
351
+ /** Snap to grid - align text to document grid */
352
+ snapToGrid?: boolean;
353
+ /** Special vanish - hidden text for specific scenarios (like TOC entries) */
354
+ specVanish?: boolean;
355
+ /** Text effect/animation type */
356
+ effect?:
357
+ | "none"
358
+ | "lights"
359
+ | "blinkBackground"
360
+ | "sparkleText"
361
+ | "marchingBlackAnts"
362
+ | "marchingRedAnts"
363
+ | "shimmer"
364
+ | "antsBlack"
365
+ | "antsRed";
366
+ /** Fit text to width in twips (1/20th of a point) */
367
+ fitText?: number;
368
+ /** East Asian typography layout options */
369
+ eastAsianLayout?: EastAsianLayout;
370
+ /** Complex script formatting flag (w:cs) per ECMA-376 Part 1 §17.3.2.7 */
371
+ complexScript?: boolean;
372
+ /** Web hidden - hide text in web layout view (w:webHidden) per ECMA-376 Part 1 §17.3.2.44 */
373
+ webHidden?: boolean;
374
+ /** High ANSI font (w:rFonts w:hAnsi) - font for characters in the high ANSI range */
375
+ fontHAnsi?: string;
376
+ /** East Asian font (w:rFonts w:eastAsia) - font for East Asian characters */
377
+ fontEastAsia?: string;
378
+ /** Complex script font (w:rFonts w:cs) - font for complex script (RTL) characters */
379
+ fontCs?: string;
380
+ /** Font hint (w:rFonts w:hint) - hint for font selection: 'default' | 'eastAsia' | 'cs' */
381
+ fontHint?: string;
382
+ /** ASCII theme font reference (w:rFonts w:asciiTheme) per ECMA-376 Part 1 §17.3.2.26 */
383
+ fontAsciiTheme?: string;
384
+ /** High ANSI theme font reference (w:rFonts w:hAnsiTheme) per ECMA-376 Part 1 §17.3.2.26 */
385
+ fontHAnsiTheme?: string;
386
+ /** East Asian theme font reference (w:rFonts w:eastAsiaTheme) per ECMA-376 Part 1 §17.3.2.26 */
387
+ fontEastAsiaTheme?: string;
388
+ /** Complex script theme font reference (w:rFonts w:cstheme) per ECMA-376 Part 1 §17.3.2.26 */
389
+ fontCsTheme?: string;
390
+ /**
391
+ * Raw w14: namespace elements from rPr (Word 2010+ text effects)
392
+ * Stored as raw XML strings for passthrough round-trip fidelity.
393
+ * Includes: w14:textOutline, w14:shadow, w14:reflection, w14:glow,
394
+ * w14:ligatures, w14:numForm, w14:numSpacing, w14:cntxtAlts, w14:stylisticSets
395
+ */
396
+ rawW14Properties?: string[];
397
+ /**
398
+ * Automatically clean XML-like patterns from text content.
399
+ * When true (default), removes XML tags like <w:t> from text to prevent display issues.
400
+ * Set to false to disable auto-cleaning (useful for debugging).
401
+ * Default: true (auto-clean enabled by default for defensive data handling)
402
+ */
403
+ cleanXmlFromText?: boolean;
404
+ }
405
+
406
+ /**
407
+ * Represents a run of formatted text
408
+ */
409
+ export class Run {
410
+ private content: RunContent[];
411
+ private formatting: RunFormatting;
412
+ private trackingContext?: import('../tracking/TrackingContext').TrackingContext;
413
+ private propertyChangeRevision?: RunPropertyChange;
414
+ /** Parent paragraph reference for automatic tracking */
415
+ private _parentParagraph?: import('./Paragraph').Paragraph;
416
+
417
+ /**
418
+ * Creates a new Run
419
+ * @param text - The text content
420
+ * @param formatting - Formatting options
421
+ */
422
+ constructor(text: string, formatting: RunFormatting = {}) {
423
+ // Warn about undefined/null text to help catch data quality issues
424
+ if (text === undefined || text === null) {
425
+ defaultLogger.warn(
426
+ `DocXML Text Validation Warning [Run constructor]:\n` +
427
+ ` - Received ${
428
+ text === undefined ? "undefined" : "null"
429
+ } text value\n` +
430
+ ` - Converting to empty string for Word compatibility`
431
+ );
432
+ }
433
+
434
+ // Default to auto-cleaning XML patterns unless explicitly disabled
435
+ const shouldClean = formatting.cleanXmlFromText !== false;
436
+
437
+ // Validate text for XML patterns
438
+ const validation = validateRunText(text, {
439
+ context: "Run constructor",
440
+ autoClean: shouldClean,
441
+ warnToConsole: true, // Enable warnings to help catch data quality issues
442
+ });
443
+
444
+ // Use cleaned text if available and cleaning was requested
445
+ const cleanedText = validation.cleanedText || text;
446
+
447
+ // Convert undefined/null to empty string for consistent XML generation
448
+ const normalizedText = cleanedText ?? "";
449
+
450
+ // Parse text to extract special characters into separate content elements
451
+ this.content = this.parseTextWithSpecialCharacters(normalizedText);
452
+
453
+ // Remove cleanXmlFromText from formatting as it's not a display property
454
+ const { cleanXmlFromText, ...displayFormatting } = formatting;
455
+ this.formatting = displayFormatting;
456
+ }
457
+
458
+ /**
459
+ * Parses text containing special characters and converts them to content elements
460
+ * @param text Text that may contain tabs, newlines, non-breaking hyphens, etc.
461
+ * @returns Array of content elements
462
+ * @private
463
+ */
464
+ private parseTextWithSpecialCharacters(text: string): RunContent[] {
465
+ if (!text) {
466
+ return [{ type: "text", value: "" }];
467
+ }
468
+
469
+ const content: RunContent[] = [];
470
+ let currentText = "";
471
+
472
+ for (let i = 0; i < text.length; i++) {
473
+ const char = text[i];
474
+
475
+ switch (char) {
476
+ case "\t":
477
+ // Add accumulated text before tab
478
+ if (currentText) {
479
+ content.push({ type: "text", value: currentText });
480
+ currentText = "";
481
+ }
482
+ // Add tab element
483
+ content.push({ type: "tab" });
484
+ break;
485
+
486
+ case "\n":
487
+ // Add accumulated text before newline
488
+ if (currentText) {
489
+ content.push({ type: "text", value: currentText });
490
+ currentText = "";
491
+ }
492
+ // Add break element
493
+ content.push({ type: "break" });
494
+ break;
495
+
496
+ case "\u2011": // Non-breaking hyphen
497
+ // Add accumulated text before non-breaking hyphen
498
+ if (currentText) {
499
+ content.push({ type: "text", value: currentText });
500
+ currentText = "";
501
+ }
502
+ // Add non-breaking hyphen element
503
+ content.push({ type: "noBreakHyphen" });
504
+ break;
505
+
506
+ case "\r": // Carriage return
507
+ // Add accumulated text before carriage return
508
+ if (currentText) {
509
+ content.push({ type: "text", value: currentText });
510
+ currentText = "";
511
+ }
512
+ // Add carriage return element
513
+ content.push({ type: "carriageReturn" });
514
+ break;
515
+
516
+ case "\u00AD": // Soft hyphen
517
+ // Add accumulated text before soft hyphen
518
+ if (currentText) {
519
+ content.push({ type: "text", value: currentText });
520
+ currentText = "";
521
+ }
522
+ // Add soft hyphen element
523
+ content.push({ type: "softHyphen" });
524
+ break;
525
+
526
+ default:
527
+ // Accumulate regular text
528
+ currentText += char;
529
+ break;
530
+ }
531
+ }
532
+
533
+ // Add any remaining text
534
+ if (currentText) {
535
+ content.push({ type: "text", value: currentText });
536
+ }
537
+
538
+ // If no content was added (empty string), add empty text element
539
+ if (content.length === 0) {
540
+ content.push({ type: "text", value: "" });
541
+ }
542
+
543
+ return content;
544
+ }
545
+
546
+ /**
547
+ * Gets the text content from the run
548
+ *
549
+ * Concatenates all content elements and converts special characters
550
+ * (tabs, breaks, etc.) back to their string representation.
551
+ *
552
+ * @returns The complete text string including tabs (\t) and line breaks (\n)
553
+ *
554
+ * @example
555
+ * ```typescript
556
+ * const run = new Run('Hello\tWorld');
557
+ * console.log(run.getText()); // "Hello\tWorld"
558
+ * ```
559
+ */
560
+ getText(): string {
561
+ return this.content
562
+ .map((c) => {
563
+ switch (c.type) {
564
+ case "text":
565
+ return c.value || "";
566
+ case "tab":
567
+ return "\t";
568
+ case "break":
569
+ return "\n";
570
+ case "carriageReturn":
571
+ return "\r";
572
+ case "softHyphen":
573
+ return "\u00AD";
574
+ case "noBreakHyphen":
575
+ return "\u2011";
576
+ case "instructionText":
577
+ return "";
578
+ case "fieldChar":
579
+ return "";
580
+ default:
581
+ return "";
582
+ }
583
+ })
584
+ .join("");
585
+ }
586
+
587
+ /**
588
+ * Sets the text content of the run
589
+ *
590
+ * Replaces all existing content with new text. Special characters like
591
+ * tabs (\t) and newlines (\n) are automatically converted to their
592
+ * corresponding XML elements.
593
+ *
594
+ * @remarks
595
+ * When change tracking is enabled and this run has a parent paragraph,
596
+ * calling `setText()` will replace this run in the paragraph's content
597
+ * array with multiple new elements (unchanged runs + revision wrappers).
598
+ * After this call, the original run reference may no longer be present
599
+ * in the paragraph — callers should re-query the paragraph content
600
+ * rather than continuing to use the original run reference.
601
+ *
602
+ * @param text - The new text content
603
+ *
604
+ * @example
605
+ * ```typescript
606
+ * const run = new Run('Old text');
607
+ * run.setText('New text');
608
+ * run.setText('Text with\ttab'); // Tab is preserved
609
+ * ```
610
+ */
611
+ setText(text: string): void {
612
+ // Capture old text and content for tracking before any changes
613
+ const oldText = this.getText();
614
+ const oldContent = [...this.content];
615
+
616
+ // Warn about undefined/null text to help catch data quality issues
617
+ if (text === undefined || text === null) {
618
+ defaultLogger.warn(
619
+ `DocXML Text Validation Warning [Run.setText]:\n` +
620
+ ` - Received ${
621
+ text === undefined ? "undefined" : "null"
622
+ } text value\n` +
623
+ ` - Converting to empty string for Word compatibility`
624
+ );
625
+ }
626
+
627
+ // Respect original cleanXmlFromText setting (Issue #9 fix)
628
+ // This ensures consistent behavior with constructor
629
+ const shouldClean = this.formatting.cleanXmlFromText !== false;
630
+
631
+ // Validate text for XML patterns
632
+ const validation = validateRunText(text, {
633
+ context: "Run.setText",
634
+ autoClean: shouldClean,
635
+ warnToConsole: true, // Enable warnings to help catch data quality issues
636
+ });
637
+
638
+ // Use cleaned text if available and cleaning was requested
639
+ const cleanedText = validation.cleanedText || text;
640
+
641
+ // Convert undefined/null to empty string for consistent XML generation
642
+ const normalizedText = cleanedText ?? "";
643
+
644
+ // Check if current content is all instructionText - preserve the type
645
+ // This is critical for TOC field instructions and other field codes
646
+ // Without this, calling setText() on a run with instrText would convert it to w:t
647
+ // which causes field instruction codes to appear as visible text in Word
648
+ const isAllInstructionText = this.content.length > 0 &&
649
+ this.content.every(c => c.type === "instructionText");
650
+
651
+ if (isAllInstructionText) {
652
+ // Preserve instructionText type - just update the value
653
+ this.content = [{ type: "instructionText", value: normalizedText }];
654
+ } else {
655
+ // Normal behavior - parse text to extract special characters into separate content elements
656
+ this.content = this.parseTextWithSpecialCharacters(normalizedText);
657
+ }
658
+
659
+ // Track text change if tracking is enabled and text actually changed
660
+ if (this.trackingContext?.isEnabled() && this._parentParagraph && oldText !== normalizedText && oldText) {
661
+ // Check if original content had non-text types that getText() can't faithfully represent
662
+ // (VML, fieldChar, symbol, embeddedObject) — if so, fall back to whole-run replacement
663
+ const hasNonTextContent = oldContent.some(c =>
664
+ c.type === 'vml' || c.type === 'fieldChar' || c.type === 'symbol' || c.type === 'embeddedObject'
665
+ );
666
+
667
+ const segments = hasNonTextContent ? [] : diffText(oldText, normalizedText);
668
+ const useGranular = !hasNonTextContent && diffHasUnchangedParts(segments);
669
+
670
+ if (useGranular) {
671
+ // Fine-grained tracking: split into unchanged runs + delete/insert revisions
672
+ const revManager = this.trackingContext.getRevisionManager();
673
+ const now = new Date();
674
+ const newContent: (Run | RevisionType)[] = [];
675
+
676
+ for (const seg of segments) {
677
+ if (seg.type === 'equal') {
678
+ // Create a new run with the same formatting for the unchanged portion
679
+ const equalRun = this.clone();
680
+ equalRun.content = this.parseTextWithSpecialCharacters(seg.text);
681
+ equalRun._setTrackingContext(this.trackingContext);
682
+ equalRun._setParentParagraph(this._parentParagraph);
683
+ newContent.push(equalRun);
684
+ } else if (seg.type === 'delete') {
685
+ const delRun = this.clone();
686
+ delRun.content = this.parseTextWithSpecialCharacters(seg.text);
687
+ const delRev = this.trackingContext.createDeletion(delRun, now);
688
+ revManager.register(delRev);
689
+ newContent.push(delRev);
690
+ } else if (seg.type === 'insert') {
691
+ const insRun = this.clone();
692
+ insRun.content = this.parseTextWithSpecialCharacters(seg.text);
693
+ const insRev = this.trackingContext.createInsertion(insRun, now);
694
+ revManager.register(insRev);
695
+ newContent.push(insRev);
696
+ }
697
+ }
698
+
699
+ this._parentParagraph.replaceContent(this, newContent);
700
+ } else {
701
+ // Whole-run fallback: complete replacement (no shared text, or non-text content)
702
+ const revManager = this.trackingContext.getRevisionManager();
703
+ const now = new Date();
704
+
705
+ const deleteRun = this.clone();
706
+ deleteRun.content = this.parseTextWithSpecialCharacters(oldText);
707
+
708
+ const deleteRev = this.trackingContext.createDeletion(deleteRun, now);
709
+ revManager.register(deleteRev);
710
+
711
+ const insertRev = this.trackingContext.createInsertion(this, now);
712
+ revManager.register(insertRev);
713
+
714
+ this._parentParagraph.replaceContent(this, [deleteRev, insertRev]);
715
+ }
716
+ }
717
+ }
718
+
719
+ /**
720
+ * Gets a copy of the run formatting
721
+ *
722
+ * Returns a copy of all formatting properties including font, size,
723
+ * bold, italic, color, and all other formatting attributes.
724
+ *
725
+ * @returns Copy of the run formatting object
726
+ *
727
+ * @example
728
+ * ```typescript
729
+ * const formatting = run.getFormatting();
730
+ * console.log(`Font: ${formatting.font}, Size: ${formatting.size}pt`);
731
+ * if (formatting.bold) {
732
+ * console.log('Text is bold');
733
+ * }
734
+ * ```
735
+ */
736
+ getFormatting(): RunFormatting {
737
+ return { ...this.formatting };
738
+ }
739
+
740
+ /**
741
+ * Gets the bold formatting value
742
+ * @returns True if bold, false otherwise
743
+ */
744
+ getBold(): boolean {
745
+ return this.formatting.bold ?? false;
746
+ }
747
+
748
+ /**
749
+ * Gets the italic formatting value
750
+ * @returns True if italic, false otherwise
751
+ */
752
+ getItalic(): boolean {
753
+ return this.formatting.italic ?? false;
754
+ }
755
+
756
+ /**
757
+ * Gets the underline style
758
+ * @returns Underline style (string, boolean, or undefined)
759
+ */
760
+ getUnderline(): boolean | "none" | "single" | "double" | "dotted" | "thick" | "dash" | undefined {
761
+ return this.formatting.underline;
762
+ }
763
+
764
+ /**
765
+ * Gets the strikethrough formatting value
766
+ * @returns True if strikethrough, false otherwise
767
+ */
768
+ getStrike(): boolean {
769
+ return this.formatting.strike ?? false;
770
+ }
771
+
772
+ /**
773
+ * Gets the font family name
774
+ * @returns Font name or undefined if not set
775
+ */
776
+ getFont(): string | undefined {
777
+ return this.formatting.font;
778
+ }
779
+
780
+ /**
781
+ * Gets the font size in half-points
782
+ * @returns Size in half-points or undefined if not set
783
+ */
784
+ getSize(): number | undefined {
785
+ return this.formatting.size;
786
+ }
787
+
788
+ /**
789
+ * Gets the text color as hex string
790
+ * @returns Color hex string or undefined if not set
791
+ */
792
+ getColor(): string | undefined {
793
+ return this.formatting.color;
794
+ }
795
+
796
+ /**
797
+ * Gets the highlight color
798
+ * @returns Highlight color name or undefined if not set
799
+ */
800
+ getHighlight(): string | undefined {
801
+ return this.formatting.highlight;
802
+ }
803
+
804
+ /**
805
+ * Gets the subscript formatting value
806
+ * @returns True if subscript, false otherwise
807
+ */
808
+ getSubscript(): boolean {
809
+ return this.formatting.subscript ?? false;
810
+ }
811
+
812
+ /**
813
+ * Gets the superscript formatting value
814
+ * @returns True if superscript, false otherwise
815
+ */
816
+ getSuperscript(): boolean {
817
+ return this.formatting.superscript ?? false;
818
+ }
819
+
820
+ /**
821
+ * Gets whether the run is right-to-left text
822
+ * @returns True if RTL, false otherwise
823
+ */
824
+ isRTL(): boolean {
825
+ return this.formatting.rtl ?? false;
826
+ }
827
+
828
+ /**
829
+ * Gets the small caps formatting value
830
+ * @returns True if small caps, false otherwise
831
+ */
832
+ getSmallCaps(): boolean {
833
+ return this.formatting.smallCaps ?? false;
834
+ }
835
+
836
+ /**
837
+ * Gets the all caps formatting value
838
+ * @returns True if all caps, false otherwise
839
+ */
840
+ getAllCaps(): boolean {
841
+ return this.formatting.allCaps ?? false;
842
+ }
843
+
844
+ /**
845
+ * Gets effective bold formatting, resolving from:
846
+ * 1. Direct formatting on this run
847
+ * 2. Table conditional formatting (if in a table cell)
848
+ *
849
+ * @returns True if text should render bold, undefined if not determinable
850
+ *
851
+ * @example
852
+ * ```typescript
853
+ * // Check effective bold (considers table conditional formatting)
854
+ * const isBold = run.getEffectiveBold();
855
+ * if (isBold) {
856
+ * console.log('Text appears bold (direct or via table style)');
857
+ * }
858
+ * ```
859
+ */
860
+ getEffectiveBold(): boolean | undefined {
861
+ // Direct formatting takes highest precedence
862
+ if (this.formatting.bold !== undefined) {
863
+ return this.formatting.bold;
864
+ }
865
+
866
+ // Check table conditional formatting
867
+ return this.getConditionalFormattingProperty("bold");
868
+ }
869
+
870
+ /**
871
+ * Gets effective italic formatting, resolving from:
872
+ * 1. Direct formatting on this run
873
+ * 2. Table conditional formatting (if in a table cell)
874
+ *
875
+ * @returns True if text should render italic, undefined if not determinable
876
+ */
877
+ getEffectiveItalic(): boolean | undefined {
878
+ if (this.formatting.italic !== undefined) {
879
+ return this.formatting.italic;
880
+ }
881
+ return this.getConditionalFormattingProperty("italic");
882
+ }
883
+
884
+ /**
885
+ * Gets effective color formatting, resolving from:
886
+ * 1. Direct formatting on this run
887
+ * 2. Table conditional formatting (if in a table cell)
888
+ *
889
+ * @returns Hex color string or undefined
890
+ */
891
+ getEffectiveColor(): string | undefined {
892
+ if (this.formatting.color !== undefined) {
893
+ return this.formatting.color;
894
+ }
895
+ return this.getConditionalFormattingProperty("color");
896
+ }
897
+
898
+ /**
899
+ * Gets effective font formatting, resolving from:
900
+ * 1. Direct formatting on this run
901
+ * 2. Table conditional formatting (if in a table cell)
902
+ *
903
+ * @returns Font name or undefined
904
+ */
905
+ getEffectiveFont(): string | undefined {
906
+ if (this.formatting.font !== undefined) {
907
+ return this.formatting.font;
908
+ }
909
+ return this.getConditionalFormattingProperty("font");
910
+ }
911
+
912
+ /**
913
+ * Gets effective font size formatting, resolving from:
914
+ * 1. Direct formatting on this run
915
+ * 2. Table conditional formatting (if in a table cell)
916
+ *
917
+ * @returns Font size in points or undefined
918
+ */
919
+ getEffectiveSize(): number | undefined {
920
+ if (this.formatting.size !== undefined) {
921
+ return this.formatting.size;
922
+ }
923
+ return this.getConditionalFormattingProperty("size");
924
+ }
925
+
926
+ /**
927
+ * Checks if this run has the "Hyperlink" character style applied.
928
+ *
929
+ * This is useful when applying bulk style changes to a document,
930
+ * to avoid overwriting hyperlink formatting (which would make
931
+ * hyperlinks appear as plain text).
932
+ *
933
+ * @returns True if the run has "Hyperlink" character style
934
+ *
935
+ * @example
936
+ * ```typescript
937
+ * // Skip hyperlink-styled runs when applying color changes
938
+ * for (const para of doc.getParagraphs()) {
939
+ * for (const run of para.getRuns()) {
940
+ * if (run.isHyperlinkStyled()) {
941
+ * continue; // Preserve hyperlink formatting
942
+ * }
943
+ * run.setColor('000000');
944
+ * }
945
+ * }
946
+ * ```
947
+ */
948
+ isHyperlinkStyled(): boolean {
949
+ return this.formatting.characterStyle === "Hyperlink";
950
+ }
951
+
952
+ /**
953
+ * Gets a specific property from table conditional formatting.
954
+ * Traverses the parent chain to find conditional formatting from table styles.
955
+ * @internal
956
+ */
957
+ private getConditionalFormattingProperty<K extends keyof RunFormatting>(
958
+ property: K
959
+ ): RunFormatting[K] | undefined {
960
+ // Need parent paragraph to access cell context
961
+ const para = this._parentParagraph;
962
+ if (!para) return undefined;
963
+
964
+ // Check if paragraph is in a table cell
965
+ const cell = para._getParentCell();
966
+ if (!cell) return undefined;
967
+
968
+ // Get the cnfStyle (which conditionals apply)
969
+ const cnfStyle = para.getTableConditionalStyle();
970
+ if (!cnfStyle) return undefined;
971
+
972
+ // Decode cnfStyle to find active conditionals
973
+ const activeConditionals = getActiveConditionalsInPriorityOrder(cnfStyle);
974
+
975
+ // Try explicit table style first
976
+ const tableStyleId = cell.getTableStyleId();
977
+ const stylesManager = para._getStylesManager();
978
+
979
+ if (tableStyleId && stylesManager) {
980
+ const style = stylesManager.getStyle(tableStyleId);
981
+ if (style) {
982
+ const tableStyleProps = style.getProperties().tableStyle;
983
+ if (tableStyleProps?.conditionalFormatting) {
984
+ // Check active conditionals in priority order (corners > edges > banding)
985
+ for (const conditionalType of activeConditionals) {
986
+ const conditional = tableStyleProps.conditionalFormatting.find(
987
+ (cf) => cf.type === conditionalType
988
+ );
989
+ if (conditional?.runFormatting?.[property] !== undefined) {
990
+ return conditional.runFormatting[property];
991
+ }
992
+ }
993
+
994
+ // Fallback: check wholeTable conditional (applies to all cells)
995
+ const wholeTable = tableStyleProps.conditionalFormatting.find(
996
+ (cf) => cf.type === "wholeTable"
997
+ );
998
+ if (wholeTable?.runFormatting?.[property] !== undefined) {
999
+ return wholeTable.runFormatting[property];
1000
+ }
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ // Apply Word's default formatting when no explicit style
1006
+ // Per Word behavior: firstRow, firstCol, lastRow, lastCol get bold by default
1007
+ if (property === "bold" && activeConditionals.length > 0) {
1008
+ const table = cell._getParentRow()?._getParentTable();
1009
+ if (table) {
1010
+ const tblLookFlags = table.getTblLookFlags();
1011
+
1012
+ // Check if cell's conditional matches enabled tblLook flags
1013
+ for (const conditionalType of activeConditionals) {
1014
+ if (conditionalType === "firstRow" && tblLookFlags.firstRow)
1015
+ return true as RunFormatting[K];
1016
+ if (conditionalType === "lastRow" && tblLookFlags.lastRow)
1017
+ return true as RunFormatting[K];
1018
+ if (conditionalType === "firstCol" && tblLookFlags.firstColumn)
1019
+ return true as RunFormatting[K];
1020
+ if (conditionalType === "lastCol" && tblLookFlags.lastColumn)
1021
+ return true as RunFormatting[K];
1022
+ }
1023
+ }
1024
+ }
1025
+
1026
+ return undefined;
1027
+ }
1028
+
1029
+ /**
1030
+ * Sets character style reference
1031
+ * Per ECMA-376 Part 1 §17.3.2.36
1032
+ * @param styleId - Character style ID to apply
1033
+ * @returns This run for chaining
1034
+ */
1035
+ setCharacterStyle(styleId: string): this {
1036
+ const previousValue = this.formatting.characterStyle;
1037
+ this.formatting.characterStyle = styleId;
1038
+ if (this.trackingContext?.isEnabled() && previousValue !== styleId) {
1039
+ this.trackingContext.trackRunPropertyChange(this, 'characterStyle', previousValue, styleId);
1040
+ }
1041
+ return this;
1042
+ }
1043
+
1044
+ /**
1045
+ * Sets text border
1046
+ * Per ECMA-376 Part 1 §17.3.2.5
1047
+ * @param border - Border definition
1048
+ * @returns This run for chaining
1049
+ */
1050
+ setBorder(border: TextBorder): this {
1051
+ const previousValue = this.formatting.border;
1052
+ this.formatting.border = border;
1053
+ if (this.trackingContext?.isEnabled() && previousValue !== border) {
1054
+ this.trackingContext.trackRunPropertyChange(this, 'border', previousValue, border);
1055
+ }
1056
+ return this;
1057
+ }
1058
+
1059
+ /**
1060
+ * Sets character shading (background)
1061
+ * Per ECMA-376 Part 1 §17.3.2.32
1062
+ * @param shading - Shading definition
1063
+ * @returns This run for chaining
1064
+ */
1065
+ setShading(shading: CharacterShading): this {
1066
+ const previousValue = this.formatting.shading;
1067
+ this.formatting.shading = shading;
1068
+ if (this.trackingContext?.isEnabled() && previousValue !== shading) {
1069
+ this.trackingContext.trackRunPropertyChange(this, 'shading', previousValue, shading);
1070
+ }
1071
+ return this;
1072
+ }
1073
+
1074
+ /**
1075
+ * Sets emphasis mark
1076
+ * Per ECMA-376 Part 1 §17.3.2.13
1077
+ * @param emphasis - Emphasis mark type ('dot', 'comma', 'circle', 'underDot')
1078
+ * @returns This run for chaining
1079
+ */
1080
+ setEmphasis(emphasis: EmphasisMark): this {
1081
+ const previousValue = this.formatting.emphasis;
1082
+ this.formatting.emphasis = emphasis;
1083
+ if (this.trackingContext?.isEnabled() && previousValue !== emphasis) {
1084
+ this.trackingContext.trackRunPropertyChange(this, 'emphasis', previousValue, emphasis);
1085
+ }
1086
+ return this;
1087
+ }
1088
+
1089
+ /**
1090
+ * Sets the tracking context for automatic change tracking.
1091
+ * Called by Document when track changes is enabled.
1092
+ * @internal
1093
+ */
1094
+ _setTrackingContext(context: import('../tracking/TrackingContext').TrackingContext): void {
1095
+ this.trackingContext = context;
1096
+ }
1097
+
1098
+ /**
1099
+ * Sets the parent paragraph reference for this run
1100
+ * @internal Used for content tracking
1101
+ */
1102
+ _setParentParagraph(paragraph: import('./Paragraph').Paragraph): void {
1103
+ this._parentParagraph = paragraph;
1104
+ }
1105
+
1106
+ /**
1107
+ * Gets the parent paragraph reference
1108
+ * @internal Used for content tracking
1109
+ */
1110
+ _getParentParagraph(): import('./Paragraph').Paragraph | undefined {
1111
+ return this._parentParagraph;
1112
+ }
1113
+
1114
+ /**
1115
+ * Gets the property change revision for this run (if any)
1116
+ *
1117
+ * Property change revisions (w:rPrChange) track formatting changes to runs.
1118
+ * They contain the PREVIOUS properties before the change was made.
1119
+ *
1120
+ * @returns Property change revision or undefined if not set
1121
+ *
1122
+ * @example
1123
+ * ```typescript
1124
+ * const propChange = run.getPropertyChangeRevision();
1125
+ * if (propChange) {
1126
+ * console.log(`Changed by ${propChange.author} on ${propChange.date}`);
1127
+ * console.log(`Previous: ${JSON.stringify(propChange.previousProperties)}`);
1128
+ * }
1129
+ * ```
1130
+ */
1131
+ getPropertyChangeRevision(): RunPropertyChange | undefined {
1132
+ return this.propertyChangeRevision;
1133
+ }
1134
+
1135
+ /**
1136
+ * Sets the property change revision for this run
1137
+ *
1138
+ * Property change revisions (w:rPrChange) are stored inside the run properties
1139
+ * element (w:rPr) and track what the previous formatting was before a change.
1140
+ * This is used for round-trip preservation of tracked changes.
1141
+ *
1142
+ * @param propChange - Property change revision data
1143
+ * @returns This run for method chaining
1144
+ *
1145
+ * @example
1146
+ * ```typescript
1147
+ * run.setPropertyChangeRevision({
1148
+ * id: 1,
1149
+ * author: 'John Doe',
1150
+ * date: new Date(),
1151
+ * previousProperties: { bold: true }
1152
+ * });
1153
+ * ```
1154
+ */
1155
+ setPropertyChangeRevision(propChange: RunPropertyChange): this {
1156
+ this.propertyChangeRevision = propChange;
1157
+ return this;
1158
+ }
1159
+
1160
+ /**
1161
+ * Clears the property change revision for this run
1162
+ *
1163
+ * @returns This run for method chaining
1164
+ */
1165
+ clearPropertyChangeRevision(): this {
1166
+ this.propertyChangeRevision = undefined;
1167
+ return this;
1168
+ }
1169
+
1170
+ /**
1171
+ * Checks if this run has a property change revision
1172
+ *
1173
+ * @returns True if a property change revision is set
1174
+ */
1175
+ hasPropertyChangeRevision(): boolean {
1176
+ return this.propertyChangeRevision !== undefined;
1177
+ }
1178
+
1179
+ /**
1180
+ * Sets bold formatting
1181
+ *
1182
+ * Makes the text bold or removes bold formatting.
1183
+ *
1184
+ * @param bold - If true, applies bold; if false, removes bold (default: true)
1185
+ * @returns This run instance for method chaining
1186
+ *
1187
+ * @example
1188
+ * ```typescript
1189
+ * run.setBold(); // Apply bold
1190
+ * run.setBold(true); // Apply bold
1191
+ * run.setBold(false); // Remove bold
1192
+ * ```
1193
+ */
1194
+ setBold(bold = true): this {
1195
+ const previousValue = this.formatting.bold;
1196
+ this.formatting.bold = bold;
1197
+ if (this.trackingContext?.isEnabled() && previousValue !== bold) {
1198
+ this.trackingContext.trackRunPropertyChange(this, 'bold', previousValue, bold);
1199
+ }
1200
+ return this;
1201
+ }
1202
+
1203
+ /**
1204
+ * Sets italic formatting
1205
+ *
1206
+ * Makes the text italic or removes italic formatting.
1207
+ *
1208
+ * @param italic - If true, applies italic; if false, removes italic (default: true)
1209
+ * @returns This run instance for method chaining
1210
+ *
1211
+ * @example
1212
+ * ```typescript
1213
+ * run.setItalic(); // Apply italic
1214
+ * run.setItalic(true); // Apply italic
1215
+ * run.setItalic(false); // Remove italic
1216
+ * ```
1217
+ */
1218
+ setItalic(italic = true): this {
1219
+ const previousValue = this.formatting.italic;
1220
+ this.formatting.italic = italic;
1221
+ if (this.trackingContext?.isEnabled() && previousValue !== italic) {
1222
+ this.trackingContext.trackRunPropertyChange(this, 'italic', previousValue, italic);
1223
+ }
1224
+ return this;
1225
+ }
1226
+
1227
+ /**
1228
+ * Sets bold formatting for complex scripts (RTL languages)
1229
+ * Per ECMA-376 Part 1 §17.3.2.3
1230
+ * @param bold - Whether text is bold for complex scripts
1231
+ */
1232
+ setComplexScriptBold(bold = true): this {
1233
+ const previousValue = this.formatting.complexScriptBold;
1234
+ this.formatting.complexScriptBold = bold;
1235
+ if (this.trackingContext?.isEnabled() && previousValue !== bold) {
1236
+ this.trackingContext.trackRunPropertyChange(this, 'complexScriptBold', previousValue, bold);
1237
+ }
1238
+ return this;
1239
+ }
1240
+
1241
+ /**
1242
+ * Sets italic formatting for complex scripts (RTL languages)
1243
+ * Per ECMA-376 Part 1 §17.3.2.17
1244
+ * @param italic - Whether text is italic for complex scripts
1245
+ */
1246
+ setComplexScriptItalic(italic = true): this {
1247
+ const previousValue = this.formatting.complexScriptItalic;
1248
+ this.formatting.complexScriptItalic = italic;
1249
+ if (this.trackingContext?.isEnabled() && previousValue !== italic) {
1250
+ this.trackingContext.trackRunPropertyChange(this, 'complexScriptItalic', previousValue, italic);
1251
+ }
1252
+ return this;
1253
+ }
1254
+
1255
+ /**
1256
+ * Sets character spacing (letter spacing)
1257
+ * Per ECMA-376 Part 1 §17.3.2.33
1258
+ * @param spacing - Spacing in twips (1/20th of a point). Positive values expand, negative values condense.
1259
+ */
1260
+ setCharacterSpacing(spacing: number): this {
1261
+ const previousValue = this.formatting.characterSpacing;
1262
+ this.formatting.characterSpacing = spacing;
1263
+ if (this.trackingContext?.isEnabled() && previousValue !== spacing) {
1264
+ this.trackingContext.trackRunPropertyChange(this, 'characterSpacing', previousValue, spacing);
1265
+ }
1266
+ return this;
1267
+ }
1268
+
1269
+ /**
1270
+ * Sets horizontal text scaling
1271
+ * Per ECMA-376 Part 1 §17.3.2.43
1272
+ * @param scaling - Scaling percentage (e.g., 200 = 200% width, 50 = 50% width). Default is 100.
1273
+ */
1274
+ setScaling(scaling: number): this {
1275
+ const previousValue = this.formatting.scaling;
1276
+ this.formatting.scaling = scaling;
1277
+ if (this.trackingContext?.isEnabled() && previousValue !== scaling) {
1278
+ this.trackingContext.trackRunPropertyChange(this, 'scaling', previousValue, scaling);
1279
+ }
1280
+ return this;
1281
+ }
1282
+
1283
+ /**
1284
+ * Sets vertical text position
1285
+ * Per ECMA-376 Part 1 §17.3.2.31
1286
+ * @param position - Position in half-points. Positive values raise text, negative values lower it.
1287
+ */
1288
+ setPosition(position: number): this {
1289
+ const previousValue = this.formatting.position;
1290
+ this.formatting.position = position;
1291
+ if (this.trackingContext?.isEnabled() && previousValue !== position) {
1292
+ this.trackingContext.trackRunPropertyChange(this, 'position', previousValue, position);
1293
+ }
1294
+ return this;
1295
+ }
1296
+
1297
+ /**
1298
+ * Sets kerning threshold
1299
+ * Per ECMA-376 Part 1 §17.3.2.20
1300
+ * @param kerning - Font size in half-points at which kerning starts. 0 disables kerning.
1301
+ */
1302
+ setKerning(kerning: number): this {
1303
+ const previousValue = this.formatting.kerning;
1304
+ this.formatting.kerning = kerning;
1305
+ if (this.trackingContext?.isEnabled() && previousValue !== kerning) {
1306
+ this.trackingContext.trackRunPropertyChange(this, 'kerning', previousValue, kerning);
1307
+ }
1308
+ return this;
1309
+ }
1310
+
1311
+ /**
1312
+ * Sets language
1313
+ * Per ECMA-376 Part 1 §17.3.2.20
1314
+ * @param language - Language code (e.g., 'en-US', 'fr-FR', 'es-ES')
1315
+ */
1316
+ setLanguage(language: string): this {
1317
+ const previousValue = this.formatting.language;
1318
+ this.formatting.language = language;
1319
+ if (this.trackingContext?.isEnabled() && previousValue !== language) {
1320
+ this.trackingContext.trackRunPropertyChange(this, 'language', previousValue, language);
1321
+ }
1322
+ return this;
1323
+ }
1324
+
1325
+ /**
1326
+ * Sets underline formatting
1327
+ *
1328
+ * Applies various underline styles or removes underlining.
1329
+ *
1330
+ * @param underline - Underline style or boolean (default: true = 'single')
1331
+ * - true or 'single': Single underline
1332
+ * - 'double': Double underline
1333
+ * - 'thick': Thick underline
1334
+ * - 'dotted': Dotted underline
1335
+ * - 'dash': Dashed underline
1336
+ * - false: No underline
1337
+ * @returns This run instance for method chaining
1338
+ *
1339
+ * @example
1340
+ * ```typescript
1341
+ * run.setUnderline(); // Single underline
1342
+ * run.setUnderline('double'); // Double underline
1343
+ * run.setUnderline(false); // Remove underline
1344
+ * ```
1345
+ */
1346
+ setUnderline(underline: RunFormatting["underline"] = true): this {
1347
+ const previousValue = this.formatting.underline;
1348
+ this.formatting.underline = underline;
1349
+ if (this.trackingContext?.isEnabled() && previousValue !== underline) {
1350
+ this.trackingContext.trackRunPropertyChange(this, 'underline', previousValue, underline);
1351
+ }
1352
+ return this;
1353
+ }
1354
+
1355
+ /**
1356
+ * Sets underline color per ECMA-376 Part 1 §17.3.2.40
1357
+ * @param color - Color in hex format (without #)
1358
+ * @returns This run for method chaining
1359
+ */
1360
+ setUnderlineColor(color: string): this {
1361
+ this.formatting.underlineColor = normalizeColor(color);
1362
+ return this;
1363
+ }
1364
+
1365
+ /**
1366
+ * Sets underline theme color per ECMA-376 Part 1 §17.3.2.40
1367
+ * @param themeColor - Theme color reference
1368
+ * @param themeTint - Optional tint (0-255)
1369
+ * @param themeShade - Optional shade (0-255)
1370
+ * @returns This run for method chaining
1371
+ */
1372
+ setUnderlineThemeColor(themeColor: ThemeColorValue, themeTint?: number, themeShade?: number): this {
1373
+ this.formatting.underlineThemeColor = themeColor;
1374
+ if (themeTint !== undefined) this.formatting.underlineThemeTint = themeTint;
1375
+ if (themeShade !== undefined) this.formatting.underlineThemeShade = themeShade;
1376
+ return this;
1377
+ }
1378
+
1379
+ /**
1380
+ * Sets strikethrough formatting
1381
+ *
1382
+ * Adds or removes a line through the text.
1383
+ *
1384
+ * @param strike - If true, applies strikethrough; if false, removes it (default: true)
1385
+ * @returns This run instance for method chaining
1386
+ *
1387
+ * @example
1388
+ * ```typescript
1389
+ * run.setStrike(); // Apply strikethrough
1390
+ * run.setStrike(false); // Remove strikethrough
1391
+ * ```
1392
+ */
1393
+ setStrike(strike = true): this {
1394
+ const previousValue = this.formatting.strike;
1395
+ this.formatting.strike = strike;
1396
+ if (this.trackingContext?.isEnabled() && previousValue !== strike) {
1397
+ this.trackingContext.trackRunPropertyChange(this, 'strike', previousValue, strike);
1398
+ }
1399
+ return this;
1400
+ }
1401
+
1402
+ /**
1403
+ * Sets subscript formatting
1404
+ *
1405
+ * Lowers the text below the baseline (e.g., H₂O).
1406
+ * Automatically removes superscript if set.
1407
+ *
1408
+ * @param subscript - If true, applies subscript; if false, removes it (default: true)
1409
+ * @returns This run instance for method chaining
1410
+ *
1411
+ * @example
1412
+ * ```typescript
1413
+ * run.setText('H₂O');
1414
+ * run.setSubscript(); // Format as subscript
1415
+ * ```
1416
+ */
1417
+ setSubscript(subscript = true): this {
1418
+ const previousValue = this.formatting.subscript;
1419
+ this.formatting.subscript = subscript;
1420
+ if (subscript) {
1421
+ this.formatting.superscript = false;
1422
+ }
1423
+ if (this.trackingContext?.isEnabled() && previousValue !== subscript) {
1424
+ this.trackingContext.trackRunPropertyChange(this, 'subscript', previousValue, subscript);
1425
+ }
1426
+ return this;
1427
+ }
1428
+
1429
+ /**
1430
+ * Sets superscript formatting
1431
+ *
1432
+ * Raises the text above the baseline (e.g., x²).
1433
+ * Automatically removes subscript if set.
1434
+ *
1435
+ * @param superscript - If true, applies superscript; if false, removes it (default: true)
1436
+ * @returns This run instance for method chaining
1437
+ *
1438
+ * @example
1439
+ * ```typescript
1440
+ * run.setText('x²');
1441
+ * run.setSuperscript(); // Format as superscript
1442
+ * ```
1443
+ */
1444
+ setSuperscript(superscript = true): this {
1445
+ const previousValue = this.formatting.superscript;
1446
+ this.formatting.superscript = superscript;
1447
+ if (superscript) {
1448
+ this.formatting.subscript = false;
1449
+ }
1450
+ if (this.trackingContext?.isEnabled() && previousValue !== superscript) {
1451
+ this.trackingContext.trackRunPropertyChange(this, 'superscript', previousValue, superscript);
1452
+ }
1453
+ return this;
1454
+ }
1455
+
1456
+ /**
1457
+ * Sets font family and optionally size
1458
+ *
1459
+ * Changes the font family (typeface) and optionally the font size.
1460
+ *
1461
+ * @param font - Font family name (e.g., 'Arial', 'Times New Roman', 'Verdana')
1462
+ * @param size - Optional font size in points
1463
+ * @returns This run instance for method chaining
1464
+ *
1465
+ * @example
1466
+ * ```typescript
1467
+ * run.setFont('Arial'); // Change font only
1468
+ * run.setFont('Verdana', 14); // Change font and size
1469
+ * ```
1470
+ */
1471
+ setFont(font: string, size?: number): this {
1472
+ const previousFont = this.formatting.font;
1473
+ const previousSize = this.formatting.size;
1474
+ this.formatting.font = font;
1475
+ if (size !== undefined) {
1476
+ this.formatting.size = size;
1477
+ }
1478
+ if (this.trackingContext?.isEnabled()) {
1479
+ if (previousFont !== font) {
1480
+ this.trackingContext.trackRunPropertyChange(this, 'font', previousFont, font);
1481
+ }
1482
+ if (size !== undefined && previousSize !== size) {
1483
+ this.trackingContext.trackRunPropertyChange(this, 'size', previousSize, size);
1484
+ }
1485
+ }
1486
+ return this;
1487
+ }
1488
+
1489
+ /**
1490
+ * Sets font size
1491
+ *
1492
+ * Changes the size of the text in points.
1493
+ *
1494
+ * @param size - Font size in points (e.g., 12 for 12pt text)
1495
+ * @returns This run instance for method chaining
1496
+ *
1497
+ * @example
1498
+ * ```typescript
1499
+ * run.setSize(12); // 12pt text
1500
+ * run.setSize(18); // 18pt text
1501
+ * ```
1502
+ */
1503
+ setSize(size: number): this {
1504
+ const previousValue = this.formatting.size;
1505
+ this.formatting.size = size;
1506
+ if (this.trackingContext?.isEnabled() && previousValue !== size) {
1507
+ this.trackingContext.trackRunPropertyChange(this, 'size', previousValue, size);
1508
+ }
1509
+ return this;
1510
+ }
1511
+
1512
+ /**
1513
+ * Sets font size for complex scripts (RTL languages like Arabic, Hebrew)
1514
+ *
1515
+ * Sets the font size used for complex script text (w:szCs element).
1516
+ * If not set, the regular size is used for both regular and complex script text.
1517
+ *
1518
+ * @param size - Font size in points for complex scripts
1519
+ * @returns This run instance for method chaining
1520
+ *
1521
+ * @example
1522
+ * ```typescript
1523
+ * run.setSize(12).setSizeCs(14); // 12pt for regular, 14pt for complex scripts
1524
+ * ```
1525
+ */
1526
+ setSizeCs(size: number): this {
1527
+ const previousValue = this.formatting.sizeCs;
1528
+ this.formatting.sizeCs = size;
1529
+ if (this.trackingContext?.isEnabled() && previousValue !== size) {
1530
+ this.trackingContext.trackRunPropertyChange(this, 'sizeCs', previousValue, size);
1531
+ }
1532
+ return this;
1533
+ }
1534
+
1535
+ /**
1536
+ * Sets text color
1537
+ *
1538
+ * Sets the foreground (text) color using hexadecimal format.
1539
+ * Color is automatically normalized to uppercase without the # prefix.
1540
+ *
1541
+ * @param color - Color in hex format (with or without # prefix)
1542
+ * @returns This run instance for method chaining
1543
+ *
1544
+ * @throws Error if color format is invalid (not 6 hex characters)
1545
+ *
1546
+ * @example
1547
+ * ```typescript
1548
+ * run.setColor('FF0000'); // Red text
1549
+ * run.setColor('#0000FF'); // Blue text (# is removed)
1550
+ * run.setColor('00FF00'); // Green text
1551
+ * ```
1552
+ */
1553
+ setColor(color: string): this {
1554
+ const previousValue = this.formatting.color;
1555
+ const normalizedColor = normalizeColor(color);
1556
+ this.formatting.color = normalizedColor;
1557
+ if (this.trackingContext?.isEnabled() && previousValue !== normalizedColor) {
1558
+ this.trackingContext.trackRunPropertyChange(this, 'color', previousValue, normalizedColor);
1559
+ }
1560
+ return this;
1561
+ }
1562
+
1563
+ /**
1564
+ * Sets theme color reference for text
1565
+ *
1566
+ * Uses a color from the document's theme instead of a fixed hex value.
1567
+ * Theme colors automatically update when the document theme changes.
1568
+ *
1569
+ * @param themeColor - Theme color reference (e.g., 'accent1', 'dark1', 'hyperlink')
1570
+ * @returns This run instance for method chaining
1571
+ *
1572
+ * @example
1573
+ * ```typescript
1574
+ * run.setThemeColor('accent1'); // Use theme accent color 1
1575
+ * run.setThemeColor('hyperlink'); // Use theme hyperlink color
1576
+ * ```
1577
+ */
1578
+ setThemeColor(themeColor: ThemeColorValue): this {
1579
+ const previousValue = this.formatting.themeColor;
1580
+ this.formatting.themeColor = themeColor;
1581
+ if (this.trackingContext?.isEnabled() && previousValue !== themeColor) {
1582
+ this.trackingContext.trackRunPropertyChange(this, 'themeColor', previousValue, themeColor);
1583
+ }
1584
+ return this;
1585
+ }
1586
+
1587
+ /**
1588
+ * Sets theme color tint for lighter variations
1589
+ *
1590
+ * Applied to the theme color to create a lighter shade.
1591
+ * The tint value is a percentage where 0 = no change and 255 = white.
1592
+ *
1593
+ * @param themeTint - Tint value (0-255, toward white)
1594
+ * @returns This run instance for method chaining
1595
+ *
1596
+ * @example
1597
+ * ```typescript
1598
+ * run.setThemeColor('accent1').setThemeTint(128); // 50% tint toward white
1599
+ * ```
1600
+ */
1601
+ setThemeTint(themeTint: number): this {
1602
+ const previousValue = this.formatting.themeTint;
1603
+ this.formatting.themeTint = themeTint;
1604
+ if (this.trackingContext?.isEnabled() && previousValue !== themeTint) {
1605
+ this.trackingContext.trackRunPropertyChange(this, 'themeTint', previousValue, themeTint);
1606
+ }
1607
+ return this;
1608
+ }
1609
+
1610
+ /**
1611
+ * Sets theme color shade for darker variations
1612
+ *
1613
+ * Applied to the theme color to create a darker shade.
1614
+ * The shade value is a percentage where 0 = no change and 255 = black.
1615
+ *
1616
+ * @param themeShade - Shade value (0-255, toward black)
1617
+ * @returns This run instance for method chaining
1618
+ *
1619
+ * @example
1620
+ * ```typescript
1621
+ * run.setThemeColor('accent1').setThemeShade(128); // 50% shade toward black
1622
+ * ```
1623
+ */
1624
+ setThemeShade(themeShade: number): this {
1625
+ const previousValue = this.formatting.themeShade;
1626
+ this.formatting.themeShade = themeShade;
1627
+ if (this.trackingContext?.isEnabled() && previousValue !== themeShade) {
1628
+ this.trackingContext.trackRunPropertyChange(this, 'themeShade', previousValue, themeShade);
1629
+ }
1630
+ return this;
1631
+ }
1632
+
1633
+ /**
1634
+ * Sets highlight (background) color
1635
+ *
1636
+ * Applies a background highlight color to the text, similar to using
1637
+ * a highlighter marker.
1638
+ *
1639
+ * @param highlight - Highlight color name
1640
+ * - Standard colors: 'yellow', 'green', 'cyan', 'magenta', 'blue', 'red'
1641
+ * - Dark variants: 'darkBlue', 'darkCyan', 'darkGreen', 'darkMagenta', 'darkRed', 'darkYellow'
1642
+ * - Grayscale: 'darkGray', 'lightGray', 'black', 'white'
1643
+ * @returns This run instance for method chaining
1644
+ *
1645
+ * @example
1646
+ * ```typescript
1647
+ * run.setHighlight('yellow'); // Yellow highlight
1648
+ * run.setHighlight('darkGreen'); // Dark green highlight
1649
+ * ```
1650
+ */
1651
+ setHighlight(highlight: RunFormatting["highlight"]): this {
1652
+ const previousValue = this.formatting.highlight;
1653
+ this.formatting.highlight = highlight;
1654
+ if (this.trackingContext?.isEnabled() && previousValue !== highlight) {
1655
+ this.trackingContext.trackRunPropertyChange(this, 'highlight', previousValue, highlight);
1656
+ }
1657
+ return this;
1658
+ }
1659
+
1660
+ /**
1661
+ * Sets small caps formatting
1662
+ *
1663
+ * Formats lowercase letters as smaller versions of capital letters.
1664
+ *
1665
+ * @param smallCaps - If true, applies small caps; if false, removes it (default: true)
1666
+ * @returns This run instance for method chaining
1667
+ *
1668
+ * @example
1669
+ * ```typescript
1670
+ * run.setText('Small Caps Text');
1671
+ * run.setSmallCaps(); // SMALL CAPS TEXT
1672
+ * ```
1673
+ */
1674
+ setSmallCaps(smallCaps = true): this {
1675
+ const previousValue = this.formatting.smallCaps;
1676
+ this.formatting.smallCaps = smallCaps;
1677
+ if (this.trackingContext?.isEnabled() && previousValue !== smallCaps) {
1678
+ this.trackingContext.trackRunPropertyChange(this, 'smallCaps', previousValue, smallCaps);
1679
+ }
1680
+ return this;
1681
+ }
1682
+
1683
+ /**
1684
+ * Sets all caps formatting
1685
+ *
1686
+ * Displays all text in capital letters regardless of original case.
1687
+ *
1688
+ * @param allCaps - If true, applies all caps; if false, removes it (default: true)
1689
+ * @returns This run instance for method chaining
1690
+ *
1691
+ * @example
1692
+ * ```typescript
1693
+ * run.setText('All Caps Text');
1694
+ * run.setAllCaps(); // ALL CAPS TEXT
1695
+ * ```
1696
+ */
1697
+ setAllCaps(allCaps = true): this {
1698
+ const previousValue = this.formatting.allCaps;
1699
+ this.formatting.allCaps = allCaps;
1700
+ if (this.trackingContext?.isEnabled() && previousValue !== allCaps) {
1701
+ this.trackingContext.trackRunPropertyChange(this, 'allCaps', previousValue, allCaps);
1702
+ }
1703
+ return this;
1704
+ }
1705
+
1706
+ /**
1707
+ * Sets outline text effect
1708
+ * @param outline - Whether to apply outline effect (default: true)
1709
+ * @returns This run for method chaining
1710
+ */
1711
+ setOutline(outline = true): this {
1712
+ const previousValue = this.formatting.outline;
1713
+ this.formatting.outline = outline;
1714
+ if (this.trackingContext?.isEnabled() && previousValue !== outline) {
1715
+ this.trackingContext.trackRunPropertyChange(this, 'outline', previousValue, outline);
1716
+ }
1717
+ return this;
1718
+ }
1719
+
1720
+ /**
1721
+ * Sets shadow text effect
1722
+ * @param shadow - Whether to apply shadow effect (default: true)
1723
+ * @returns This run for method chaining
1724
+ */
1725
+ setShadow(shadow = true): this {
1726
+ const previousValue = this.formatting.shadow;
1727
+ this.formatting.shadow = shadow;
1728
+ if (this.trackingContext?.isEnabled() && previousValue !== shadow) {
1729
+ this.trackingContext.trackRunPropertyChange(this, 'shadow', previousValue, shadow);
1730
+ }
1731
+ return this;
1732
+ }
1733
+
1734
+ /**
1735
+ * Sets emboss text effect
1736
+ * @param emboss - Whether to apply emboss effect (default: true)
1737
+ * @returns This run for method chaining
1738
+ */
1739
+ setEmboss(emboss = true): this {
1740
+ const previousValue = this.formatting.emboss;
1741
+ this.formatting.emboss = emboss;
1742
+ if (this.trackingContext?.isEnabled() && previousValue !== emboss) {
1743
+ this.trackingContext.trackRunPropertyChange(this, 'emboss', previousValue, emboss);
1744
+ }
1745
+ return this;
1746
+ }
1747
+
1748
+ /**
1749
+ * Sets imprint/engrave text effect
1750
+ * @param imprint - Whether to apply imprint effect (default: true)
1751
+ * @returns This run for method chaining
1752
+ */
1753
+ setImprint(imprint = true): this {
1754
+ const previousValue = this.formatting.imprint;
1755
+ this.formatting.imprint = imprint;
1756
+ if (this.trackingContext?.isEnabled() && previousValue !== imprint) {
1757
+ this.trackingContext.trackRunPropertyChange(this, 'imprint', previousValue, imprint);
1758
+ }
1759
+ return this;
1760
+ }
1761
+
1762
+ /**
1763
+ * Sets right-to-left text direction
1764
+ * @param rtl - Whether text is RTL (default: true)
1765
+ * @returns This run for method chaining
1766
+ */
1767
+ setRTL(rtl = true): this {
1768
+ const previousValue = this.formatting.rtl;
1769
+ this.formatting.rtl = rtl;
1770
+ if (this.trackingContext?.isEnabled() && previousValue !== rtl) {
1771
+ this.trackingContext.trackRunPropertyChange(this, 'rtl', previousValue, rtl);
1772
+ }
1773
+ return this;
1774
+ }
1775
+
1776
+ /**
1777
+ * Sets hidden/vanish text
1778
+ * @param vanish - Whether text is hidden (default: true)
1779
+ * @returns This run for method chaining
1780
+ */
1781
+ setVanish(vanish = true): this {
1782
+ const previousValue = this.formatting.vanish;
1783
+ this.formatting.vanish = vanish;
1784
+ if (this.trackingContext?.isEnabled() && previousValue !== vanish) {
1785
+ this.trackingContext.trackRunPropertyChange(this, 'vanish', previousValue, vanish);
1786
+ }
1787
+ return this;
1788
+ }
1789
+
1790
+ /**
1791
+ * Sets no proofing (skip spell/grammar check)
1792
+ * @param noProof - Whether to skip proofing (default: true)
1793
+ * @returns This run for method chaining
1794
+ */
1795
+ setNoProof(noProof = true): this {
1796
+ const previousValue = this.formatting.noProof;
1797
+ this.formatting.noProof = noProof;
1798
+ if (this.trackingContext?.isEnabled() && previousValue !== noProof) {
1799
+ this.trackingContext.trackRunPropertyChange(this, 'noProof', previousValue, noProof);
1800
+ }
1801
+ return this;
1802
+ }
1803
+
1804
+ /**
1805
+ * Sets snap to grid alignment
1806
+ * @param snapToGrid - Whether to snap to grid (default: true)
1807
+ * @returns This run for method chaining
1808
+ */
1809
+ setSnapToGrid(snapToGrid = true): this {
1810
+ const previousValue = this.formatting.snapToGrid;
1811
+ this.formatting.snapToGrid = snapToGrid;
1812
+ if (this.trackingContext?.isEnabled() && previousValue !== snapToGrid) {
1813
+ this.trackingContext.trackRunPropertyChange(this, 'snapToGrid', previousValue, snapToGrid);
1814
+ }
1815
+ return this;
1816
+ }
1817
+
1818
+ /**
1819
+ * Sets special vanish (hidden for specific scenarios like TOC)
1820
+ * @param specVanish - Whether to apply special vanish (default: true)
1821
+ * @returns This run for method chaining
1822
+ */
1823
+ setSpecVanish(specVanish = true): this {
1824
+ const previousValue = this.formatting.specVanish;
1825
+ this.formatting.specVanish = specVanish;
1826
+ if (this.trackingContext?.isEnabled() && previousValue !== specVanish) {
1827
+ this.trackingContext.trackRunPropertyChange(this, 'specVanish', previousValue, specVanish);
1828
+ }
1829
+ return this;
1830
+ }
1831
+
1832
+ /**
1833
+ * Sets complex script formatting flag (w:cs)
1834
+ * @param complexScript - Whether complex script formatting applies (default: true)
1835
+ * @returns This run for method chaining
1836
+ */
1837
+ setComplexScript(complexScript = true): this {
1838
+ const previousValue = this.formatting.complexScript;
1839
+ this.formatting.complexScript = complexScript;
1840
+ if (this.trackingContext?.isEnabled() && previousValue !== complexScript) {
1841
+ this.trackingContext.trackRunPropertyChange(this, 'complexScript', previousValue, complexScript);
1842
+ }
1843
+ return this;
1844
+ }
1845
+
1846
+ /**
1847
+ * Sets web hidden flag (w:webHidden) - hide text in web layout view
1848
+ * @param webHidden - Whether text is hidden in web view (default: true)
1849
+ * @returns This run for method chaining
1850
+ */
1851
+ setWebHidden(webHidden = true): this {
1852
+ const previousValue = this.formatting.webHidden;
1853
+ this.formatting.webHidden = webHidden;
1854
+ if (this.trackingContext?.isEnabled() && previousValue !== webHidden) {
1855
+ this.trackingContext.trackRunPropertyChange(this, 'webHidden', previousValue, webHidden);
1856
+ }
1857
+ return this;
1858
+ }
1859
+
1860
+ /**
1861
+ * Sets the High ANSI font (w:rFonts w:hAnsi)
1862
+ * @param font - Font name for high ANSI characters
1863
+ * @returns This run for method chaining
1864
+ */
1865
+ setFontHAnsi(font: string): this {
1866
+ const previousValue = this.formatting.fontHAnsi;
1867
+ this.formatting.fontHAnsi = font;
1868
+ if (this.trackingContext?.isEnabled() && previousValue !== font) {
1869
+ this.trackingContext.trackRunPropertyChange(this, 'fontHAnsi', previousValue, font);
1870
+ }
1871
+ return this;
1872
+ }
1873
+
1874
+ /**
1875
+ * Sets the East Asian font (w:rFonts w:eastAsia)
1876
+ * @param font - Font name for East Asian characters
1877
+ * @returns This run for method chaining
1878
+ */
1879
+ setFontEastAsia(font: string): this {
1880
+ const previousValue = this.formatting.fontEastAsia;
1881
+ this.formatting.fontEastAsia = font;
1882
+ if (this.trackingContext?.isEnabled() && previousValue !== font) {
1883
+ this.trackingContext.trackRunPropertyChange(this, 'fontEastAsia', previousValue, font);
1884
+ }
1885
+ return this;
1886
+ }
1887
+
1888
+ /**
1889
+ * Sets the complex script font (w:rFonts w:cs)
1890
+ * @param font - Font name for complex script (RTL) characters
1891
+ * @returns This run for method chaining
1892
+ */
1893
+ setFontCs(font: string): this {
1894
+ const previousValue = this.formatting.fontCs;
1895
+ this.formatting.fontCs = font;
1896
+ if (this.trackingContext?.isEnabled() && previousValue !== font) {
1897
+ this.trackingContext.trackRunPropertyChange(this, 'fontCs', previousValue, font);
1898
+ }
1899
+ return this;
1900
+ }
1901
+
1902
+ /**
1903
+ * Sets the font hint (w:rFonts w:hint)
1904
+ * @param hint - Font selection hint: 'default', 'eastAsia', or 'cs'
1905
+ * @returns This run for method chaining
1906
+ */
1907
+ setFontHint(hint: string): this {
1908
+ const previousValue = this.formatting.fontHint;
1909
+ this.formatting.fontHint = hint;
1910
+ if (this.trackingContext?.isEnabled() && previousValue !== hint) {
1911
+ this.trackingContext.trackRunPropertyChange(this, 'fontHint', previousValue, hint);
1912
+ }
1913
+ return this;
1914
+ }
1915
+
1916
+ /**
1917
+ * Sets the ASCII theme font reference (w:rFonts w:asciiTheme)
1918
+ * @param theme - Theme font name (e.g., 'majorHAnsi', 'minorHAnsi')
1919
+ * @returns This run for method chaining
1920
+ */
1921
+ setFontAsciiTheme(theme: string): this {
1922
+ this.formatting.fontAsciiTheme = theme;
1923
+ return this;
1924
+ }
1925
+
1926
+ /**
1927
+ * Sets the High ANSI theme font reference (w:rFonts w:hAnsiTheme)
1928
+ * @param theme - Theme font name
1929
+ * @returns This run for method chaining
1930
+ */
1931
+ setFontHAnsiTheme(theme: string): this {
1932
+ this.formatting.fontHAnsiTheme = theme;
1933
+ return this;
1934
+ }
1935
+
1936
+ /**
1937
+ * Sets the East Asian theme font reference (w:rFonts w:eastAsiaTheme)
1938
+ * @param theme - Theme font name
1939
+ * @returns This run for method chaining
1940
+ */
1941
+ setFontEastAsiaTheme(theme: string): this {
1942
+ this.formatting.fontEastAsiaTheme = theme;
1943
+ return this;
1944
+ }
1945
+
1946
+ /**
1947
+ * Sets the complex script theme font reference (w:rFonts w:cstheme)
1948
+ * @param theme - Theme font name
1949
+ * @returns This run for method chaining
1950
+ */
1951
+ setFontCsTheme(theme: string): this {
1952
+ this.formatting.fontCsTheme = theme;
1953
+ return this;
1954
+ }
1955
+
1956
+ /**
1957
+ * Adds a raw w14: property XML string for passthrough (Word 2010+ text effects).
1958
+ *
1959
+ * **Warning:** The input XML is embedded directly in the output without sanitization.
1960
+ * Only pass trusted w14 namespace elements (e.g., w14:textOutline, w14:ligatures).
1961
+ * Do not pass user-controlled or untrusted input to this method.
1962
+ *
1963
+ * @param rawXml - The raw XML string for a w14: element (e.g., '<w14:textOutline ...>...</w14:textOutline>')
1964
+ * @returns This run for method chaining
1965
+ */
1966
+ addRawW14Property(rawXml: string): this {
1967
+ if (!this.formatting.rawW14Properties) {
1968
+ this.formatting.rawW14Properties = [];
1969
+ }
1970
+ this.formatting.rawW14Properties.push(rawXml);
1971
+ return this;
1972
+ }
1973
+
1974
+ /**
1975
+ * Sets text effect/animation
1976
+ * @param effect - Effect type (e.g., 'shimmer', 'sparkleText')
1977
+ * @returns This run for method chaining
1978
+ */
1979
+ setEffect(
1980
+ effect:
1981
+ | "none"
1982
+ | "lights"
1983
+ | "blinkBackground"
1984
+ | "sparkleText"
1985
+ | "marchingBlackAnts"
1986
+ | "marchingRedAnts"
1987
+ | "shimmer"
1988
+ | "antsBlack"
1989
+ | "antsRed"
1990
+ ): this {
1991
+ const previousValue = this.formatting.effect;
1992
+ this.formatting.effect = effect;
1993
+ if (this.trackingContext?.isEnabled() && previousValue !== effect) {
1994
+ this.trackingContext.trackRunPropertyChange(this, 'effect', previousValue, effect);
1995
+ }
1996
+ return this;
1997
+ }
1998
+
1999
+ /**
2000
+ * Sets fit text to width
2001
+ * @param width - Width in twips (1/20th of a point)
2002
+ * @returns This run for method chaining
2003
+ */
2004
+ setFitText(width: number): this {
2005
+ const previousValue = this.formatting.fitText;
2006
+ this.formatting.fitText = width;
2007
+ if (this.trackingContext?.isEnabled() && previousValue !== width) {
2008
+ this.trackingContext.trackRunPropertyChange(this, 'fitText', previousValue, width);
2009
+ }
2010
+ return this;
2011
+ }
2012
+
2013
+ /**
2014
+ * Sets East Asian typography layout
2015
+ * @param layout - East Asian layout options
2016
+ * @returns This run for method chaining
2017
+ */
2018
+ setEastAsianLayout(layout: EastAsianLayout): this {
2019
+ const previousValue = this.formatting.eastAsianLayout;
2020
+ this.formatting.eastAsianLayout = layout;
2021
+ if (this.trackingContext?.isEnabled() && previousValue !== layout) {
2022
+ this.trackingContext.trackRunPropertyChange(this, 'eastAsianLayout', previousValue, layout);
2023
+ }
2024
+ return this;
2025
+ }
2026
+
2027
+ /**
2028
+ * Converts the run to WordprocessingML XML element
2029
+ *
2030
+ * **ECMA-376 Compliance:** Properties are generated in the order specified by
2031
+ * ECMA-376 Part 1 §17.3.2.28 to ensure strict OpenXML conformance.
2032
+ *
2033
+ * Per spec, the order is:
2034
+ * 1. rFonts (font family)
2035
+ * 2. b (bold)
2036
+ * 3. i (italic)
2037
+ * 4. caps/smallCaps (capitalization)
2038
+ * 5. strike/dstrike (strikethrough)
2039
+ * 6. u (underline)
2040
+ * 7. sz/szCs (font size)
2041
+ * 8. color (text color)
2042
+ * 9. highlight (highlight color)
2043
+ * 10. vertAlign (subscript/superscript)
2044
+ *
2045
+ * @returns XMLElement representing the run
2046
+ */
2047
+ toXML(): XMLElement {
2048
+ // Get text for diagnostic logging (backward compatibility)
2049
+ const text = this.getText();
2050
+
2051
+ // Diagnostic logging before serialization
2052
+ logSerialization(`Serializing run: "${text}"`, {
2053
+ rtl: this.formatting.rtl || false,
2054
+ });
2055
+ if (this.formatting.rtl) {
2056
+ logTextDirection(`Run with RTL being serialized: "${text}"`);
2057
+ }
2058
+
2059
+ // Build the run element
2060
+ const runChildren: XMLElement[] = [];
2061
+
2062
+ // Add run properties using the static helper
2063
+ const rPr = Run.generateRunPropertiesXML(this.formatting);
2064
+ if (rPr || this.propertyChangeRevision) {
2065
+ // If we have a property change revision, we need to add w:rPrChange to the w:rPr element
2066
+ // Per ECMA-376, w:rPrChange must come after all other run properties
2067
+ const rPrElement = rPr || { name: 'w:rPr', attributes: {}, children: [] };
2068
+
2069
+ if (this.propertyChangeRevision) {
2070
+ // Reuse generateRunPropertiesXML for full ECMA-376 property coverage
2071
+ // This ensures all 30+ run properties are serialized in correct schema order,
2072
+ // rather than the subset previously handled by manual serialization.
2073
+ const prevRPr = this.propertyChangeRevision.previousProperties
2074
+ ? Run.generateRunPropertiesXML(this.propertyChangeRevision.previousProperties as RunFormatting)
2075
+ : null;
2076
+ const rPrChangeChildren: XMLElement[] = prevRPr ? [prevRPr] : [];
2077
+
2078
+ // Create w:rPrChange element with attributes
2079
+ const rPrChange: XMLElement = {
2080
+ name: 'w:rPrChange',
2081
+ attributes: {
2082
+ 'w:id': this.propertyChangeRevision.id.toString(),
2083
+ 'w:author': this.propertyChangeRevision.author,
2084
+ 'w:date': formatDateForXml(this.propertyChangeRevision.date),
2085
+ },
2086
+ children: rPrChangeChildren,
2087
+ };
2088
+
2089
+ // Add rPrChange to rPr children (must come last per ECMA-376)
2090
+ if (rPrElement.children) {
2091
+ rPrElement.children.push(rPrChange);
2092
+ } else {
2093
+ rPrElement.children = [rPrChange];
2094
+ }
2095
+ }
2096
+
2097
+ runChildren.push(rPrElement);
2098
+ }
2099
+
2100
+ // Add run content elements (text, tabs, breaks, etc.) in order
2101
+ for (const contentElement of this.content) {
2102
+ switch (contentElement.type) {
2103
+ case "text":
2104
+ // Always generate <w:t> element, even for empty strings
2105
+ // This ensures proper Word compatibility and round-trip preservation
2106
+ runChildren.push(
2107
+ XMLBuilder.w(
2108
+ "t",
2109
+ {
2110
+ "xml:space": "preserve",
2111
+ },
2112
+ [contentElement.value || ""]
2113
+ )
2114
+ );
2115
+ break;
2116
+
2117
+ case "tab":
2118
+ runChildren.push(XMLBuilder.wSelf("tab"));
2119
+ break;
2120
+
2121
+ case "break":
2122
+ {
2123
+ const attrs: Record<string, string> = {};
2124
+ if (contentElement.breakType) {
2125
+ attrs["w:type"] = contentElement.breakType;
2126
+ }
2127
+ runChildren.push(
2128
+ XMLBuilder.wSelf(
2129
+ "br",
2130
+ Object.keys(attrs).length > 0 ? attrs : undefined
2131
+ )
2132
+ );
2133
+ }
2134
+ break;
2135
+
2136
+ case "carriageReturn":
2137
+ runChildren.push(XMLBuilder.wSelf("cr"));
2138
+ break;
2139
+
2140
+ case "softHyphen":
2141
+ runChildren.push(XMLBuilder.wSelf("softHyphen"));
2142
+ break;
2143
+
2144
+ case "noBreakHyphen":
2145
+ runChildren.push(XMLBuilder.wSelf("noBreakHyphen"));
2146
+ break;
2147
+
2148
+ case "instructionText":
2149
+ runChildren.push(
2150
+ XMLBuilder.w("instrText", { "xml:space": "preserve" }, [
2151
+ contentElement.value || "",
2152
+ ])
2153
+ );
2154
+ break;
2155
+
2156
+ case "fieldChar": {
2157
+ if (!contentElement.fieldCharType) {
2158
+ break;
2159
+ }
2160
+ const fldCharAttrs: Record<string, string> = {
2161
+ "w:fldCharType": contentElement.fieldCharType,
2162
+ };
2163
+ if (contentElement.fieldCharDirty !== undefined) {
2164
+ fldCharAttrs["w:dirty"] = contentElement.fieldCharDirty ? "1" : "0";
2165
+ }
2166
+ if (contentElement.fieldCharLocked !== undefined) {
2167
+ fldCharAttrs["w:fldLock"] = contentElement.fieldCharLocked ? "1" : "0";
2168
+ }
2169
+ // Generate ffData for begin field chars with form field data
2170
+ if (contentElement.formFieldData && contentElement.fieldCharType === "begin") {
2171
+ const ffDataChildren: (string | XMLElement)[] = [];
2172
+ const ffd = contentElement.formFieldData;
2173
+ if (ffd.name !== undefined) {
2174
+ ffDataChildren.push(XMLBuilder.wSelf("name", { "w:val": ffd.name }));
2175
+ }
2176
+ if (ffd.enabled !== undefined) {
2177
+ if (ffd.enabled) {
2178
+ ffDataChildren.push(XMLBuilder.wSelf("enabled"));
2179
+ } else {
2180
+ ffDataChildren.push(XMLBuilder.wSelf("enabled", { "w:val": "0" }));
2181
+ }
2182
+ }
2183
+ if (ffd.calcOnExit !== undefined) {
2184
+ ffDataChildren.push(XMLBuilder.wSelf("calcOnExit", { "w:val": ffd.calcOnExit ? "1" : "0" }));
2185
+ }
2186
+ if (ffd.helpText) {
2187
+ ffDataChildren.push(XMLBuilder.wSelf("helpText", { "w:type": "text", "w:val": ffd.helpText }));
2188
+ }
2189
+ if (ffd.statusText) {
2190
+ ffDataChildren.push(XMLBuilder.wSelf("statusText", { "w:type": "text", "w:val": ffd.statusText }));
2191
+ }
2192
+ if (ffd.entryMacro) {
2193
+ ffDataChildren.push(XMLBuilder.wSelf("entryMacro", { "w:val": ffd.entryMacro }));
2194
+ }
2195
+ if (ffd.exitMacro) {
2196
+ ffDataChildren.push(XMLBuilder.wSelf("exitMacro", { "w:val": ffd.exitMacro }));
2197
+ }
2198
+ if (ffd.fieldType) {
2199
+ switch (ffd.fieldType.type) {
2200
+ case 'textInput': {
2201
+ const tiChildren: (string | XMLElement)[] = [];
2202
+ if (ffd.fieldType.inputType) {
2203
+ tiChildren.push(XMLBuilder.wSelf("type", { "w:val": ffd.fieldType.inputType }));
2204
+ }
2205
+ if (ffd.fieldType.defaultValue !== undefined) {
2206
+ tiChildren.push(XMLBuilder.wSelf("default", { "w:val": ffd.fieldType.defaultValue }));
2207
+ }
2208
+ if (ffd.fieldType.maxLength !== undefined) {
2209
+ tiChildren.push(XMLBuilder.wSelf("maxLength", { "w:val": ffd.fieldType.maxLength.toString() }));
2210
+ }
2211
+ if (ffd.fieldType.format) {
2212
+ tiChildren.push(XMLBuilder.wSelf("format", { "w:val": ffd.fieldType.format }));
2213
+ }
2214
+ ffDataChildren.push(XMLBuilder.w("textInput", {}, tiChildren));
2215
+ break;
2216
+ }
2217
+ case 'checkBox': {
2218
+ const cbChildren: (string | XMLElement)[] = [];
2219
+ if (ffd.fieldType.size !== undefined && ffd.fieldType.size !== 'auto') {
2220
+ cbChildren.push(XMLBuilder.wSelf("size", { "w:val": ffd.fieldType.size.toString() }));
2221
+ } else {
2222
+ cbChildren.push(XMLBuilder.wSelf("sizeAuto"));
2223
+ }
2224
+ if (ffd.fieldType.defaultChecked !== undefined) {
2225
+ cbChildren.push(XMLBuilder.wSelf("default", { "w:val": ffd.fieldType.defaultChecked ? "1" : "0" }));
2226
+ }
2227
+ if (ffd.fieldType.checked !== undefined) {
2228
+ cbChildren.push(XMLBuilder.wSelf("checked", { "w:val": ffd.fieldType.checked ? "1" : "0" }));
2229
+ }
2230
+ ffDataChildren.push(XMLBuilder.w("checkBox", {}, cbChildren));
2231
+ break;
2232
+ }
2233
+ case 'dropDownList': {
2234
+ const ddChildren: (string | XMLElement)[] = [];
2235
+ if (ffd.fieldType.result !== undefined) {
2236
+ ddChildren.push(XMLBuilder.wSelf("result", { "w:val": ffd.fieldType.result.toString() }));
2237
+ }
2238
+ if (ffd.fieldType.defaultResult !== undefined) {
2239
+ ddChildren.push(XMLBuilder.wSelf("default", { "w:val": ffd.fieldType.defaultResult.toString() }));
2240
+ }
2241
+ if (ffd.fieldType.listEntries) {
2242
+ for (const entry of ffd.fieldType.listEntries) {
2243
+ ddChildren.push(XMLBuilder.wSelf("listEntry", { "w:val": entry }));
2244
+ }
2245
+ }
2246
+ ffDataChildren.push(XMLBuilder.w("ddList", {}, ddChildren));
2247
+ break;
2248
+ }
2249
+ }
2250
+ }
2251
+ runChildren.push(
2252
+ XMLBuilder.w("fldChar", fldCharAttrs, [XMLBuilder.w("ffData", {}, ffDataChildren)])
2253
+ );
2254
+ } else {
2255
+ runChildren.push(
2256
+ XMLBuilder.wSelf(
2257
+ "fldChar",
2258
+ Object.keys(fldCharAttrs).length > 0 ? fldCharAttrs : undefined
2259
+ )
2260
+ );
2261
+ }
2262
+ break;
2263
+ }
2264
+
2265
+ // Simple marker elements (self-closing, no attributes)
2266
+ case "lastRenderedPageBreak":
2267
+ runChildren.push(XMLBuilder.wSelf("lastRenderedPageBreak"));
2268
+ break;
2269
+
2270
+ case "separator":
2271
+ runChildren.push(XMLBuilder.wSelf("separator"));
2272
+ break;
2273
+
2274
+ case "continuationSeparator":
2275
+ runChildren.push(XMLBuilder.wSelf("continuationSeparator"));
2276
+ break;
2277
+
2278
+ case "pageNumber":
2279
+ runChildren.push(XMLBuilder.wSelf("pgNum"));
2280
+ break;
2281
+
2282
+ case "annotationRef":
2283
+ runChildren.push(XMLBuilder.wSelf("annotationRef"));
2284
+ break;
2285
+
2286
+ case "dayShort":
2287
+ runChildren.push(XMLBuilder.wSelf("dayShort"));
2288
+ break;
2289
+
2290
+ case "dayLong":
2291
+ runChildren.push(XMLBuilder.wSelf("dayLong"));
2292
+ break;
2293
+
2294
+ case "monthShort":
2295
+ runChildren.push(XMLBuilder.wSelf("monthShort"));
2296
+ break;
2297
+
2298
+ case "monthLong":
2299
+ runChildren.push(XMLBuilder.wSelf("monthLong"));
2300
+ break;
2301
+
2302
+ case "yearShort":
2303
+ runChildren.push(XMLBuilder.wSelf("yearShort"));
2304
+ break;
2305
+
2306
+ case "yearLong":
2307
+ runChildren.push(XMLBuilder.wSelf("yearLong"));
2308
+ break;
2309
+
2310
+ // Symbol character (w:sym) per ECMA-376 Part 1 §17.3.3.30
2311
+ case "symbol": {
2312
+ const symAttrs: Record<string, string> = {};
2313
+ if (contentElement.symbolFont) symAttrs["w:font"] = contentElement.symbolFont;
2314
+ if (contentElement.symbolChar) symAttrs["w:char"] = contentElement.symbolChar;
2315
+ runChildren.push(XMLBuilder.wSelf("sym", symAttrs));
2316
+ break;
2317
+ }
2318
+
2319
+ // Absolute position tab (w:ptab) per ECMA-376 Part 1 §17.3.3.23
2320
+ case "positionTab": {
2321
+ const ptabAttrs: Record<string, string> = {};
2322
+ if (contentElement.ptabAlignment) ptabAttrs["w:alignment"] = contentElement.ptabAlignment;
2323
+ if (contentElement.ptabRelativeTo) ptabAttrs["w:relativeTo"] = contentElement.ptabRelativeTo;
2324
+ if (contentElement.ptabLeader) ptabAttrs["w:leader"] = contentElement.ptabLeader;
2325
+ runChildren.push(XMLBuilder.wSelf("ptab", ptabAttrs));
2326
+ break;
2327
+ }
2328
+
2329
+ // Footnote reference (w:footnoteReference) per ECMA-376 Part 1 §17.11.13
2330
+ case "footnoteReference": {
2331
+ const fnAttrs: Record<string, string | number> = {};
2332
+ if (contentElement.footnoteId !== undefined) fnAttrs["w:id"] = contentElement.footnoteId;
2333
+ runChildren.push(XMLBuilder.wSelf("footnoteReference", fnAttrs));
2334
+ break;
2335
+ }
2336
+
2337
+ // Endnote reference (w:endnoteReference) per ECMA-376 Part 1 §17.11.2
2338
+ case "endnoteReference": {
2339
+ const enAttrs: Record<string, string | number> = {};
2340
+ if (contentElement.endnoteId !== undefined) enAttrs["w:id"] = contentElement.endnoteId;
2341
+ runChildren.push(XMLBuilder.wSelf("endnoteReference", enAttrs));
2342
+ break;
2343
+ }
2344
+
2345
+ // Embedded OLE object (w:object) - preserved as raw XML
2346
+ case "embeddedObject":
2347
+ if (contentElement.rawXml) {
2348
+ runChildren.push({
2349
+ name: "__rawXml",
2350
+ rawXml: contentElement.rawXml,
2351
+ });
2352
+ }
2353
+ break;
2354
+
2355
+ case "vml":
2356
+ // VML graphics (w:pict) - include raw XML without modification
2357
+ // This preserves legacy Word graphics like icons and symbols
2358
+ if (contentElement.rawXml) {
2359
+ // Use special __rawXml element name for passthrough (no wrapper element)
2360
+ runChildren.push({
2361
+ name: "__rawXml",
2362
+ rawXml: contentElement.rawXml,
2363
+ });
2364
+ }
2365
+ break;
2366
+ }
2367
+ }
2368
+
2369
+ return XMLBuilder.w("r", undefined, runChildren);
2370
+ }
2371
+
2372
+ /**
2373
+ * Checks if the run contains non-empty text
2374
+ *
2375
+ * @returns True if the run has text content with length > 0
2376
+ *
2377
+ * @example
2378
+ * ```typescript
2379
+ * const run1 = new Run('Hello');
2380
+ * const run2 = new Run('');
2381
+ * console.log(run1.hasText()); // true
2382
+ * console.log(run2.hasText()); // false
2383
+ * ```
2384
+ */
2385
+ hasText(): boolean {
2386
+ const text = this.getText();
2387
+ return text.length > 0;
2388
+ }
2389
+
2390
+ /**
2391
+ * Checks if the run has any formatting properties
2392
+ *
2393
+ * @returns True if any formatting properties (bold, italic, font, etc.) are set
2394
+ *
2395
+ * @example
2396
+ * ```typescript
2397
+ * const run1 = new Run('Text', { bold: true });
2398
+ * const run2 = new Run('Text');
2399
+ * console.log(run1.hasFormatting()); // true
2400
+ * console.log(run2.hasFormatting()); // false
2401
+ * ```
2402
+ */
2403
+ hasFormatting(): boolean {
2404
+ return Object.keys(this.formatting).length > 0;
2405
+ }
2406
+
2407
+ /**
2408
+ * Checks if the run is valid
2409
+ *
2410
+ * A run is considered valid if it has either text content or formatting.
2411
+ * An empty run with no formatting is invalid.
2412
+ *
2413
+ * @returns True if the run has text content or formatting properties
2414
+ *
2415
+ * @example
2416
+ * ```typescript
2417
+ * const run1 = new Run('Text');
2418
+ * const run2 = new Run('', { bold: true });
2419
+ * const run3 = new Run('');
2420
+ * console.log(run1.isValid()); // true (has text)
2421
+ * console.log(run2.isValid()); // true (has formatting)
2422
+ * console.log(run3.isValid()); // false (empty and unformatted)
2423
+ * ```
2424
+ */
2425
+ isValid(): boolean {
2426
+ return this.hasText() || this.hasFormatting();
2427
+ }
2428
+
2429
+ /**
2430
+ * Gets the run content elements
2431
+ *
2432
+ * Returns the internal content structure including text elements,
2433
+ * tabs, breaks, and other special characters.
2434
+ *
2435
+ * @returns Array of RunContent elements
2436
+ *
2437
+ * @example
2438
+ * ```typescript
2439
+ * const content = run.getContent();
2440
+ * for (const element of content) {
2441
+ * console.log(`Type: ${element.type}, Value: ${element.value || 'N/A'}`);
2442
+ * }
2443
+ * ```
2444
+ */
2445
+ getContent(): RunContent[] {
2446
+ return [...this.content];
2447
+ }
2448
+
2449
+ /**
2450
+ * Adds a tab character to the run
2451
+ *
2452
+ * Inserts a tab character, commonly used in TOC entries to separate
2453
+ * heading text from page numbers, or for general text alignment.
2454
+ *
2455
+ * @returns This run instance for method chaining
2456
+ *
2457
+ * @example
2458
+ * ```typescript
2459
+ * const run = new Run('Heading');
2460
+ * run.addTab();
2461
+ * run.appendText('Page 5'); // "Heading\tPage 5"
2462
+ * ```
2463
+ */
2464
+ addTab(): this {
2465
+ this.content.push({ type: "tab" });
2466
+ return this;
2467
+ }
2468
+
2469
+ /**
2470
+ * Adds a line, page, or column break to the run
2471
+ *
2472
+ * Inserts a break element for line breaks, page breaks, or column breaks.
2473
+ *
2474
+ * @param breakType - Type of break (default: line break if not specified)
2475
+ * - undefined or 'textWrapping': Line break (like pressing Enter within a paragraph)
2476
+ * - 'page': Page break
2477
+ * - 'column': Column break
2478
+ * @returns This run instance for method chaining
2479
+ *
2480
+ * @example
2481
+ * ```typescript
2482
+ * run.addBreak(); // Line break
2483
+ * run.addBreak('page'); // Page break
2484
+ * run.addBreak('column'); // Column break
2485
+ * ```
2486
+ */
2487
+ addBreak(breakType?: BreakType): this {
2488
+ this.content.push({ type: "break", breakType });
2489
+ return this;
2490
+ }
2491
+
2492
+ /**
2493
+ * Appends text to the run
2494
+ *
2495
+ * Adds additional text as a new content element without replacing
2496
+ * existing content. Useful for building text incrementally.
2497
+ *
2498
+ * @param text - Text to append
2499
+ * @returns This run instance for method chaining
2500
+ *
2501
+ * @example
2502
+ * ```typescript
2503
+ * const run = new Run('Hello');
2504
+ * run.appendText(' ');
2505
+ * run.appendText('World'); // "Hello World"
2506
+ * ```
2507
+ */
2508
+ appendText(text: string): this {
2509
+ if (text) {
2510
+ this.content.push({ type: "text", value: text });
2511
+ }
2512
+ return this;
2513
+ }
2514
+
2515
+ /**
2516
+ * Adds a carriage return to the run
2517
+ * @returns This run for method chaining
2518
+ */
2519
+ addCarriageReturn(): this {
2520
+ this.content.push({ type: "carriageReturn" });
2521
+ return this;
2522
+ }
2523
+
2524
+ /**
2525
+ * Creates a Run from an array of content elements
2526
+ * Factory method for advanced use cases (used by DocumentParser)
2527
+ * @param content - Array of run content elements
2528
+ * @param formatting - Run formatting options
2529
+ * @returns New Run instance
2530
+ */
2531
+ static createFromContent(
2532
+ content: RunContent[],
2533
+ formatting: RunFormatting = {}
2534
+ ): Run {
2535
+ const run = Object.create(Run.prototype) as Run;
2536
+ run.content = content;
2537
+ const { cleanXmlFromText, ...displayFormatting } = formatting;
2538
+ run.formatting = displayFormatting;
2539
+ return run;
2540
+ }
2541
+
2542
+ /**
2543
+ * Generates run properties XML (<w:rPr>) from RunFormatting
2544
+ * This is a static helper used by both Run and Paragraph (for paragraph mark properties)
2545
+ *
2546
+ * Per ECMA-376 Part 1 §17.3.2.28, properties must be in specific order for strict compliance
2547
+ *
2548
+ * @param formatting - Run formatting options
2549
+ * @returns XMLElement representing <w:rPr> or null if no formatting
2550
+ */
2551
+ static generateRunPropertiesXML(
2552
+ formatting: RunFormatting
2553
+ ): XMLElement | null {
2554
+ const rPrChildren: XMLElement[] = [];
2555
+
2556
+ // ECMA-376 Part 1 §17.3.2.28 CT_RPr element ordering
2557
+ // Elements MUST appear in this exact sequence for Word compatibility
2558
+
2559
+ // 1. w:rStyle — Character style reference
2560
+ if (formatting.characterStyle) {
2561
+ rPrChildren.push(
2562
+ XMLBuilder.wSelf("rStyle", {
2563
+ "w:val": formatting.characterStyle,
2564
+ })
2565
+ );
2566
+ }
2567
+
2568
+ // 2. w:rFonts — Font family
2569
+ if (formatting.font || formatting.fontHAnsi || formatting.fontEastAsia || formatting.fontCs || formatting.fontHint ||
2570
+ formatting.fontAsciiTheme || formatting.fontHAnsiTheme || formatting.fontEastAsiaTheme || formatting.fontCsTheme) {
2571
+ const rFontsAttrs: Record<string, string> = {};
2572
+ if (formatting.font) rFontsAttrs["w:ascii"] = formatting.font;
2573
+ rFontsAttrs["w:hAnsi"] = formatting.fontHAnsi || formatting.font || "";
2574
+ if (formatting.fontEastAsia) rFontsAttrs["w:eastAsia"] = formatting.fontEastAsia;
2575
+ rFontsAttrs["w:cs"] = formatting.fontCs || formatting.font || "";
2576
+ if (formatting.fontHint) rFontsAttrs["w:hint"] = formatting.fontHint;
2577
+ // Theme font references per ECMA-376 Part 1 §17.3.2.26
2578
+ if (formatting.fontAsciiTheme) rFontsAttrs["w:asciiTheme"] = formatting.fontAsciiTheme;
2579
+ if (formatting.fontHAnsiTheme) rFontsAttrs["w:hAnsiTheme"] = formatting.fontHAnsiTheme;
2580
+ if (formatting.fontEastAsiaTheme) rFontsAttrs["w:eastAsiaTheme"] = formatting.fontEastAsiaTheme;
2581
+ if (formatting.fontCsTheme) rFontsAttrs["w:cstheme"] = formatting.fontCsTheme;
2582
+
2583
+ // Remove empty string values (only include attributes that have actual values)
2584
+ for (const key of Object.keys(rFontsAttrs)) {
2585
+ if (rFontsAttrs[key] === "") delete rFontsAttrs[key];
2586
+ }
2587
+
2588
+ if (Object.keys(rFontsAttrs).length > 0) {
2589
+ rPrChildren.push(XMLBuilder.wSelf("rFonts", rFontsAttrs));
2590
+ }
2591
+ }
2592
+
2593
+ // 3. w:b — Bold
2594
+ if (formatting.bold) {
2595
+ rPrChildren.push(XMLBuilder.wSelf("b", { "w:val": "1" }));
2596
+ }
2597
+
2598
+ // 4. w:bCs — Bold complex script
2599
+ if (formatting.complexScriptBold) {
2600
+ rPrChildren.push(XMLBuilder.wSelf("bCs", { "w:val": "1" }));
2601
+ }
2602
+
2603
+ // 5. w:i — Italic
2604
+ if (formatting.italic) {
2605
+ rPrChildren.push(XMLBuilder.wSelf("i", { "w:val": "1" }));
2606
+ }
2607
+
2608
+ // 6. w:iCs — Italic complex script
2609
+ if (formatting.complexScriptItalic) {
2610
+ rPrChildren.push(XMLBuilder.wSelf("iCs", { "w:val": "1" }));
2611
+ }
2612
+
2613
+ // 7. w:caps — All caps
2614
+ if (formatting.allCaps) {
2615
+ rPrChildren.push(XMLBuilder.wSelf("caps", { "w:val": "1" }));
2616
+ }
2617
+
2618
+ // 8. w:smallCaps — Small caps
2619
+ if (formatting.smallCaps) {
2620
+ rPrChildren.push(XMLBuilder.wSelf("smallCaps", { "w:val": "1" }));
2621
+ }
2622
+
2623
+ // 9. w:strike — Single strikethrough
2624
+ if (formatting.strike) {
2625
+ rPrChildren.push(XMLBuilder.wSelf("strike", { "w:val": "1" }));
2626
+ }
2627
+
2628
+ // 10. w:dstrike — Double strikethrough
2629
+ if (formatting.dstrike) {
2630
+ rPrChildren.push(XMLBuilder.wSelf("dstrike", { "w:val": "1" }));
2631
+ }
2632
+
2633
+ // 11. w:outline — Outline text effect
2634
+ if (formatting.outline) {
2635
+ rPrChildren.push(XMLBuilder.wSelf("outline", { "w:val": "1" }));
2636
+ }
2637
+
2638
+ // 12. w:shadow — Shadow text effect
2639
+ if (formatting.shadow) {
2640
+ rPrChildren.push(XMLBuilder.wSelf("shadow", { "w:val": "1" }));
2641
+ }
2642
+
2643
+ // 13. w:emboss — Emboss text effect
2644
+ if (formatting.emboss) {
2645
+ rPrChildren.push(XMLBuilder.wSelf("emboss", { "w:val": "1" }));
2646
+ }
2647
+
2648
+ // 14. w:imprint — Imprint/engrave text effect
2649
+ if (formatting.imprint) {
2650
+ rPrChildren.push(XMLBuilder.wSelf("imprint", { "w:val": "1" }));
2651
+ }
2652
+
2653
+ // 15. w:noProof — No proofing
2654
+ if (formatting.noProof) {
2655
+ rPrChildren.push(XMLBuilder.wSelf("noProof", { "w:val": "1" }));
2656
+ }
2657
+
2658
+ // 16. w:snapToGrid — Snap to grid
2659
+ if (formatting.snapToGrid) {
2660
+ rPrChildren.push(XMLBuilder.wSelf("snapToGrid", { "w:val": "1" }));
2661
+ }
2662
+
2663
+ // 17. w:vanish — Hidden text
2664
+ if (formatting.vanish) {
2665
+ rPrChildren.push(XMLBuilder.wSelf("vanish", { "w:val": "1" }));
2666
+ }
2667
+
2668
+ // 18. w:webHidden — Web hidden
2669
+ if (formatting.webHidden) {
2670
+ rPrChildren.push(XMLBuilder.wSelf("webHidden", { "w:val": "1" }));
2671
+ }
2672
+
2673
+ // 19. w:color — Text color
2674
+ // Supports both hex colors and theme color references
2675
+ // w:val is REQUIRED per ECMA-376 — defaults to "000000" when only themeColor is set
2676
+ if (formatting.color || formatting.themeColor) {
2677
+ const colorAttrs: Record<string, string> = {};
2678
+
2679
+ colorAttrs["w:val"] = formatting.color || "000000";
2680
+ if (formatting.themeColor) {
2681
+ colorAttrs["w:themeColor"] = formatting.themeColor;
2682
+ }
2683
+ if (formatting.themeTint !== undefined) {
2684
+ colorAttrs["w:themeTint"] = formatting.themeTint
2685
+ .toString(16)
2686
+ .toUpperCase()
2687
+ .padStart(2, "0");
2688
+ }
2689
+ if (formatting.themeShade !== undefined) {
2690
+ colorAttrs["w:themeShade"] = formatting.themeShade
2691
+ .toString(16)
2692
+ .toUpperCase()
2693
+ .padStart(2, "0");
2694
+ }
2695
+
2696
+ rPrChildren.push(XMLBuilder.wSelf("color", colorAttrs));
2697
+ }
2698
+
2699
+ // 20. w:spacing — Character spacing
2700
+ if (formatting.characterSpacing !== undefined) {
2701
+ rPrChildren.push(
2702
+ XMLBuilder.wSelf("spacing", { "w:val": formatting.characterSpacing })
2703
+ );
2704
+ }
2705
+
2706
+ // 21. w:w — Horizontal scaling
2707
+ if (formatting.scaling !== undefined) {
2708
+ rPrChildren.push(XMLBuilder.wSelf("w", { "w:val": formatting.scaling }));
2709
+ }
2710
+
2711
+ // 22. w:kern — Kerning
2712
+ if (formatting.kerning !== undefined && formatting.kerning !== null) {
2713
+ rPrChildren.push(
2714
+ XMLBuilder.wSelf("kern", { "w:val": formatting.kerning })
2715
+ );
2716
+ }
2717
+
2718
+ // 23. w:position — Vertical position
2719
+ if (formatting.position !== undefined) {
2720
+ rPrChildren.push(
2721
+ XMLBuilder.wSelf("position", { "w:val": formatting.position })
2722
+ );
2723
+ }
2724
+
2725
+ // 24/25. w:sz / w:szCs — Font size / Font size complex script
2726
+ if (formatting.size !== undefined) {
2727
+ const halfPoints = pointsToHalfPoints(formatting.size);
2728
+ rPrChildren.push(XMLBuilder.wSelf("sz", { "w:val": halfPoints }));
2729
+ const csHalfPoints = formatting.sizeCs !== undefined ? pointsToHalfPoints(formatting.sizeCs) : halfPoints;
2730
+ rPrChildren.push(XMLBuilder.wSelf("szCs", { "w:val": csHalfPoints }));
2731
+ } else if (formatting.sizeCs !== undefined) {
2732
+ const csHalfPoints = pointsToHalfPoints(formatting.sizeCs);
2733
+ rPrChildren.push(XMLBuilder.wSelf("szCs", { "w:val": csHalfPoints }));
2734
+ }
2735
+
2736
+ // 26. w:highlight — Highlight color
2737
+ if (formatting.highlight) {
2738
+ rPrChildren.push(
2739
+ XMLBuilder.wSelf("highlight", { "w:val": formatting.highlight })
2740
+ );
2741
+ }
2742
+
2743
+ // 27. w:u — Underline
2744
+ // When a character style is applied (e.g., Hyperlink) and underline is explicitly false,
2745
+ // we need to output <w:u w:val="none"/> to prevent the style's underline from being inherited.
2746
+ if (formatting.underline) {
2747
+ const underlineValue =
2748
+ typeof formatting.underline === "string"
2749
+ ? formatting.underline
2750
+ : "single";
2751
+ const uAttrs: Record<string, string | number> = { "w:val": underlineValue };
2752
+ if (formatting.underlineColor) uAttrs["w:color"] = formatting.underlineColor;
2753
+ if (formatting.underlineThemeColor) uAttrs["w:themeColor"] = formatting.underlineThemeColor;
2754
+ if (formatting.underlineThemeTint !== undefined) uAttrs["w:themeTint"] = formatting.underlineThemeTint.toString(16).toUpperCase().padStart(2, '0');
2755
+ if (formatting.underlineThemeShade !== undefined) uAttrs["w:themeShade"] = formatting.underlineThemeShade.toString(16).toUpperCase().padStart(2, '0');
2756
+ rPrChildren.push(XMLBuilder.wSelf("u", uAttrs));
2757
+ } else if (formatting.underline === false && formatting.characterStyle) {
2758
+ rPrChildren.push(XMLBuilder.wSelf("u", { "w:val": "none" }));
2759
+ }
2760
+
2761
+ // 28. w:effect — Text effect/animation
2762
+ if (formatting.effect) {
2763
+ rPrChildren.push(
2764
+ XMLBuilder.wSelf("effect", { "w:val": formatting.effect })
2765
+ );
2766
+ }
2767
+
2768
+ // 29. w:bdr — Text border
2769
+ if (formatting.border) {
2770
+ const bdrAttrs: Record<string, string | number> = {};
2771
+ if (formatting.border.style) bdrAttrs["w:val"] = formatting.border.style;
2772
+ if (formatting.border.size !== undefined)
2773
+ bdrAttrs["w:sz"] = formatting.border.size;
2774
+ if (formatting.border.color)
2775
+ bdrAttrs["w:color"] = formatting.border.color;
2776
+ if (formatting.border.space !== undefined)
2777
+ bdrAttrs["w:space"] = formatting.border.space;
2778
+
2779
+ if (Object.keys(bdrAttrs).length > 0) {
2780
+ rPrChildren.push(XMLBuilder.wSelf("bdr", bdrAttrs));
2781
+ }
2782
+ }
2783
+
2784
+ // 30. w:shd — Character shading
2785
+ if (formatting.shading) {
2786
+ const shdAttrs = buildShadingAttributes(formatting.shading);
2787
+ if (Object.keys(shdAttrs).length > 0) {
2788
+ rPrChildren.push(XMLBuilder.wSelf("shd", shdAttrs));
2789
+ }
2790
+ }
2791
+
2792
+ // 31. w:fitText — Fit text to width
2793
+ if (formatting.fitText !== undefined) {
2794
+ rPrChildren.push(
2795
+ XMLBuilder.wSelf("fitText", { "w:val": formatting.fitText })
2796
+ );
2797
+ }
2798
+
2799
+ // 32. w:vertAlign — Subscript/superscript
2800
+ if (formatting.subscript) {
2801
+ rPrChildren.push(XMLBuilder.wSelf("vertAlign", { "w:val": "subscript" }));
2802
+ }
2803
+ if (formatting.superscript) {
2804
+ rPrChildren.push(
2805
+ XMLBuilder.wSelf("vertAlign", { "w:val": "superscript" })
2806
+ );
2807
+ }
2808
+
2809
+ // 33. w:rtl — Right-to-left text
2810
+ if (formatting.rtl) {
2811
+ rPrChildren.push(XMLBuilder.wSelf("rtl", { "w:val": "1" }));
2812
+ }
2813
+
2814
+ // 34. w:cs — Complex script flag
2815
+ if (formatting.complexScript) {
2816
+ rPrChildren.push(XMLBuilder.wSelf("cs", { "w:val": "1" }));
2817
+ }
2818
+
2819
+ // 35. w:em — Emphasis marks
2820
+ if (formatting.emphasis) {
2821
+ rPrChildren.push(
2822
+ XMLBuilder.wSelf("em", { "w:val": formatting.emphasis })
2823
+ );
2824
+ }
2825
+
2826
+ // 36. w:lang — Language
2827
+ if (formatting.language) {
2828
+ rPrChildren.push(
2829
+ XMLBuilder.wSelf("lang", { "w:val": formatting.language })
2830
+ );
2831
+ }
2832
+
2833
+ // 37. w:eastAsianLayout — East Asian layout
2834
+ if (formatting.eastAsianLayout) {
2835
+ const layout = formatting.eastAsianLayout;
2836
+ const attrs: Record<string, string | number> = {};
2837
+ if (layout.id !== undefined) attrs["w:id"] = layout.id;
2838
+ if (layout.vert) attrs["w:vert"] = "1";
2839
+ if (layout.vertCompress) attrs["w:vertCompress"] = "1";
2840
+ if (layout.combine) attrs["w:combine"] = "1";
2841
+ if (layout.combineBrackets)
2842
+ attrs["w:combineBrackets"] = layout.combineBrackets;
2843
+
2844
+ if (Object.keys(attrs).length > 0) {
2845
+ rPrChildren.push(XMLBuilder.wSelf("eastAsianLayout", attrs));
2846
+ }
2847
+ }
2848
+
2849
+ // 38. w:specVanish — Special vanish
2850
+ if (formatting.specVanish) {
2851
+ rPrChildren.push(XMLBuilder.wSelf("specVanish", { "w:val": "1" }));
2852
+ }
2853
+
2854
+ // 39. w:oMath — (not currently generated)
2855
+
2856
+ // 40. Raw w14: namespace elements (Word 2010+ text effects, after all schema elements)
2857
+ if (formatting.rawW14Properties && formatting.rawW14Properties.length > 0) {
2858
+ for (const rawXml of formatting.rawW14Properties) {
2859
+ rPrChildren.push({ name: "__rawXml", rawXml } as XMLElement);
2860
+ }
2861
+ }
2862
+
2863
+ // Return null if no properties (prevents empty <w:rPr/> elements)
2864
+ if (rPrChildren.length === 0) {
2865
+ return null;
2866
+ }
2867
+
2868
+ return XMLBuilder.w("rPr", undefined, rPrChildren);
2869
+ }
2870
+
2871
+ /**
2872
+ * Creates a new Run instance
2873
+ *
2874
+ * Factory method for creating a Run with text and optional formatting.
2875
+ *
2876
+ * @param text - The text content
2877
+ * @param formatting - Optional formatting to apply
2878
+ * @returns New Run instance
2879
+ *
2880
+ * @example
2881
+ * ```typescript
2882
+ * const run = Run.create('Hello World');
2883
+ * const boldRun = Run.create('Bold Text', { bold: true });
2884
+ * ```
2885
+ */
2886
+ static create(text: string, formatting?: RunFormatting): Run {
2887
+ return new Run(text, formatting);
2888
+ }
2889
+
2890
+ /**
2891
+ * Creates a deep clone of this run
2892
+ * @returns New Run instance with copied text and formatting
2893
+ * @example
2894
+ * ```typescript
2895
+ * const original = new Run('Hello', { bold: true });
2896
+ * const copy = original.clone();
2897
+ * copy.setText('World'); // Original unchanged
2898
+ * ```
2899
+ */
2900
+ clone(): Run {
2901
+ // Deep copy content and formatting to avoid shared references
2902
+ const clonedContent: RunContent[] = deepClone(this.content);
2903
+ const clonedFormatting: RunFormatting = deepClone(this.formatting);
2904
+ return Run.createFromContent(clonedContent, clonedFormatting);
2905
+ }
2906
+
2907
+ /**
2908
+ * Inserts text at a specific position
2909
+ * NOTE: This flattens run content (loses tabs/breaks). Use with caution.
2910
+ * @param index - Position to insert at (0-based)
2911
+ * @param text - Text to insert
2912
+ * @returns This run for chaining
2913
+ * @example
2914
+ * ```typescript
2915
+ * const run = new Run('Hello World');
2916
+ * run.insertText(6, 'Beautiful '); // "Hello Beautiful World"
2917
+ * ```
2918
+ */
2919
+ insertText(index: number, text: string): this {
2920
+ const currentText = this.getText();
2921
+ if (index < 0) index = 0;
2922
+ if (index > currentText.length) index = currentText.length;
2923
+
2924
+ const newText =
2925
+ currentText.slice(0, index) + text + currentText.slice(index);
2926
+ this.setText(newText);
2927
+ return this;
2928
+ }
2929
+
2930
+ /**
2931
+ * Replaces text in a specific range
2932
+ * NOTE: This flattens run content (loses tabs/breaks). Use with caution.
2933
+ * @param start - Start position (0-based, inclusive)
2934
+ * @param end - End position (0-based, exclusive)
2935
+ * @param text - Replacement text
2936
+ * @returns This run for chaining
2937
+ * @example
2938
+ * ```typescript
2939
+ * const run = new Run('Hello World');
2940
+ * run.replaceText(0, 5, 'Hi'); // "Hi World"
2941
+ * ```
2942
+ */
2943
+ replaceText(start: number, end: number, text: string): this {
2944
+ const currentText = this.getText();
2945
+ if (start < 0) start = 0;
2946
+ if (end > currentText.length) end = currentText.length;
2947
+ if (start > end) [start, end] = [end, start]; // Swap if reversed
2948
+
2949
+ const newText = currentText.slice(0, start) + text + currentText.slice(end);
2950
+ this.setText(newText);
2951
+ return this;
2952
+ }
2953
+
2954
+ /**
2955
+ * Clears run formatting properties that conflict with a style definition.
2956
+ * Uses smart clearing: only removes properties that DIFFER from the style.
2957
+ * Preserves properties not defined in the style (e.g., if style doesn't define bold, keeps run's bold).
2958
+ *
2959
+ * This is critical for allowing style definitions to apply properly, as direct formatting
2960
+ * in document.xml ALWAYS overrides style definitions in styles.xml per ECMA-376 §17.7.2.
2961
+ *
2962
+ * @param styleRunFormatting - Run formatting from the style definition to compare against
2963
+ * @returns This run for method chaining
2964
+ * @example
2965
+ * ```typescript
2966
+ * // Style says: black, 14pt Verdana
2967
+ * // Run has: red, 12pt Arial, bold
2968
+ * run.clearFormattingConflicts({
2969
+ * color: '000000',
2970
+ * size: 14,
2971
+ * font: 'Verdana'
2972
+ * });
2973
+ * // Result: Run keeps bold (not in style), but color/size/font are cleared
2974
+ * ```
2975
+ */
2976
+ clearFormattingConflicts(styleRunFormatting: RunFormatting): this {
2977
+ // For each property in run's formatting, check if it conflicts with style
2978
+ const conflictingProperties: (keyof RunFormatting)[] = [];
2979
+
2980
+ // Compare each property
2981
+ for (const key in this.formatting) {
2982
+ const propKey = key as keyof RunFormatting;
2983
+
2984
+ // Skip if style doesn't define this property (preserve run's property)
2985
+ if (styleRunFormatting[propKey] === undefined) {
2986
+ continue;
2987
+ }
2988
+
2989
+ // If style defines this property AND run's value differs, it's a conflict
2990
+ if (this.formatting[propKey] !== styleRunFormatting[propKey]) {
2991
+ conflictingProperties.push(propKey);
2992
+ }
2993
+ }
2994
+
2995
+ // Clear conflicting properties
2996
+ for (const prop of conflictingProperties) {
2997
+ delete this.formatting[prop];
2998
+ }
2999
+
3000
+ return this;
3001
+ }
3002
+
3003
+ /**
3004
+ * Clears run formatting properties that MATCH a style definition.
3005
+ * The inverse of clearFormattingConflicts: removes properties whose values
3006
+ * are identical to the style, so the run inherits those values from the style.
3007
+ * Preserves properties that differ from the style (direct overrides) and
3008
+ * properties not defined in the style.
3009
+ *
3010
+ * This enables style inheritance: when direct formatting matches the style,
3011
+ * removing it allows future style definition changes to propagate automatically.
3012
+ *
3013
+ * @param styleRunFormatting - Run formatting from the style definition to compare against
3014
+ * @returns This run for method chaining
3015
+ * @example
3016
+ * ```typescript
3017
+ * // Style says: black, 12pt Verdana
3018
+ * // Run has: black, 12pt Verdana, bold
3019
+ * run.clearMatchingFormatting({
3020
+ * color: '000000',
3021
+ * size: 12,
3022
+ * font: 'Verdana'
3023
+ * });
3024
+ * // Result: color/size/font cleared (inherit from style), bold kept (not in style)
3025
+ * ```
3026
+ */
3027
+ clearMatchingFormatting(styleRunFormatting: Partial<RunFormatting>): this {
3028
+ const matchingProperties: (keyof RunFormatting)[] = [];
3029
+
3030
+ for (const key in this.formatting) {
3031
+ const propKey = key as keyof RunFormatting;
3032
+
3033
+ // Skip if style doesn't define this property (preserve run's property)
3034
+ if (styleRunFormatting[propKey] === undefined) {
3035
+ continue;
3036
+ }
3037
+
3038
+ // If run's value matches the style, it's redundant direct formatting
3039
+ if (this.formatting[propKey] === styleRunFormatting[propKey]) {
3040
+ matchingProperties.push(propKey);
3041
+ }
3042
+ }
3043
+
3044
+ // Clear matching properties so run inherits from style
3045
+ for (const prop of matchingProperties) {
3046
+ delete this.formatting[prop];
3047
+ }
3048
+
3049
+ return this;
3050
+ }
3051
+
3052
+ /**
3053
+ * Clears all formatting from this run
3054
+ *
3055
+ * Removes all direct formatting properties, leaving only the text content.
3056
+ * This is useful for applying clean styles without formatting overrides.
3057
+ *
3058
+ * @returns This run for chaining
3059
+ * @example
3060
+ * ```typescript
3061
+ * run.clearFormatting();
3062
+ * ```
3063
+ */
3064
+ clearFormatting(): this {
3065
+ this.formatting = {};
3066
+ return this;
3067
+ }
3068
+ }