docxmlater 10.1.3 → 10.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/README.md +759 -754
  2. package/dist/constants/legacyCompatFlags.js +1 -1
  3. package/dist/constants/legacyCompatFlags.js.map +1 -1
  4. package/dist/constants/limits.js.map +1 -1
  5. package/dist/core/Document.d.ts +50 -50
  6. package/dist/core/Document.d.ts.map +1 -1
  7. package/dist/core/Document.js +483 -471
  8. package/dist/core/Document.js.map +1 -1
  9. package/dist/core/DocumentContent.d.ts +9 -9
  10. package/dist/core/DocumentContent.d.ts.map +1 -1
  11. package/dist/core/DocumentContent.js +1 -1
  12. package/dist/core/DocumentContent.js.map +1 -1
  13. package/dist/core/DocumentGenerator.d.ts +11 -11
  14. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  15. package/dist/core/DocumentGenerator.js +251 -251
  16. package/dist/core/DocumentGenerator.js.map +1 -1
  17. package/dist/core/DocumentIdManager.js.map +1 -1
  18. package/dist/core/DocumentParser.d.ts +15 -15
  19. package/dist/core/DocumentParser.d.ts.map +1 -1
  20. package/dist/core/DocumentParser.js +2123 -2155
  21. package/dist/core/DocumentParser.js.map +1 -1
  22. package/dist/core/DocumentValidator.d.ts.map +1 -1
  23. package/dist/core/DocumentValidator.js +2 -5
  24. package/dist/core/DocumentValidator.js.map +1 -1
  25. package/dist/core/Relationship.js.map +1 -1
  26. package/dist/core/RelationshipManager.d.ts.map +1 -1
  27. package/dist/core/RelationshipManager.js +3 -3
  28. package/dist/core/RelationshipManager.js.map +1 -1
  29. package/dist/elements/AlternateContent.js.map +1 -1
  30. package/dist/elements/Bookmark.d.ts.map +1 -1
  31. package/dist/elements/Bookmark.js +3 -1
  32. package/dist/elements/Bookmark.js.map +1 -1
  33. package/dist/elements/BookmarkManager.d.ts.map +1 -1
  34. package/dist/elements/BookmarkManager.js.map +1 -1
  35. package/dist/elements/Comment.d.ts.map +1 -1
  36. package/dist/elements/Comment.js +9 -6
  37. package/dist/elements/Comment.js.map +1 -1
  38. package/dist/elements/CommentManager.d.ts.map +1 -1
  39. package/dist/elements/CommentManager.js +18 -17
  40. package/dist/elements/CommentManager.js.map +1 -1
  41. package/dist/elements/CommonTypes.d.ts +21 -21
  42. package/dist/elements/CommonTypes.d.ts.map +1 -1
  43. package/dist/elements/CommonTypes.js +56 -56
  44. package/dist/elements/CommonTypes.js.map +1 -1
  45. package/dist/elements/CustomXml.js.map +1 -1
  46. package/dist/elements/Endnote.d.ts.map +1 -1
  47. package/dist/elements/Endnote.js +6 -6
  48. package/dist/elements/Endnote.js.map +1 -1
  49. package/dist/elements/EndnoteManager.d.ts.map +1 -1
  50. package/dist/elements/EndnoteManager.js +6 -7
  51. package/dist/elements/EndnoteManager.js.map +1 -1
  52. package/dist/elements/Field.d.ts.map +1 -1
  53. package/dist/elements/Field.js +82 -25
  54. package/dist/elements/Field.js.map +1 -1
  55. package/dist/elements/FieldHelpers.d.ts.map +1 -1
  56. package/dist/elements/FieldHelpers.js.map +1 -1
  57. package/dist/elements/FontManager.d.ts.map +1 -1
  58. package/dist/elements/FontManager.js +1 -1
  59. package/dist/elements/FontManager.js.map +1 -1
  60. package/dist/elements/Footer.js +2 -2
  61. package/dist/elements/Footer.js.map +1 -1
  62. package/dist/elements/Footnote.d.ts.map +1 -1
  63. package/dist/elements/Footnote.js +6 -6
  64. package/dist/elements/Footnote.js.map +1 -1
  65. package/dist/elements/FootnoteManager.d.ts.map +1 -1
  66. package/dist/elements/FootnoteManager.js +6 -7
  67. package/dist/elements/FootnoteManager.js.map +1 -1
  68. package/dist/elements/Header.js +2 -2
  69. package/dist/elements/Header.js.map +1 -1
  70. package/dist/elements/HeaderFooterManager.js.map +1 -1
  71. package/dist/elements/Hyperlink.d.ts +5 -3
  72. package/dist/elements/Hyperlink.d.ts.map +1 -1
  73. package/dist/elements/Hyperlink.js +134 -76
  74. package/dist/elements/Hyperlink.js.map +1 -1
  75. package/dist/elements/Image.d.ts.map +1 -1
  76. package/dist/elements/Image.js +238 -106
  77. package/dist/elements/Image.js.map +1 -1
  78. package/dist/elements/ImageManager.d.ts.map +1 -1
  79. package/dist/elements/ImageManager.js +1 -1
  80. package/dist/elements/ImageManager.js.map +1 -1
  81. package/dist/elements/ImageRun.js +1 -1
  82. package/dist/elements/ImageRun.js.map +1 -1
  83. package/dist/elements/MathElement.js.map +1 -1
  84. package/dist/elements/Paragraph.d.ts +24 -24
  85. package/dist/elements/Paragraph.d.ts.map +1 -1
  86. package/dist/elements/Paragraph.js +181 -188
  87. package/dist/elements/Paragraph.js.map +1 -1
  88. package/dist/elements/PreservedElement.js.map +1 -1
  89. package/dist/elements/PropertyChangeTypes.d.ts.map +1 -1
  90. package/dist/elements/PropertyChangeTypes.js +6 -6
  91. package/dist/elements/PropertyChangeTypes.js.map +1 -1
  92. package/dist/elements/RangeMarker.d.ts.map +1 -1
  93. package/dist/elements/RangeMarker.js.map +1 -1
  94. package/dist/elements/Revision.d.ts.map +1 -1
  95. package/dist/elements/Revision.js +4 -5
  96. package/dist/elements/Revision.js.map +1 -1
  97. package/dist/elements/RevisionContent.js.map +1 -1
  98. package/dist/elements/RevisionManager.d.ts.map +1 -1
  99. package/dist/elements/RevisionManager.js +40 -48
  100. package/dist/elements/RevisionManager.js.map +1 -1
  101. package/dist/elements/Run.d.ts +16 -16
  102. package/dist/elements/Run.d.ts.map +1 -1
  103. package/dist/elements/Run.js +256 -238
  104. package/dist/elements/Run.js.map +1 -1
  105. package/dist/elements/Section.d.ts.map +1 -1
  106. package/dist/elements/Section.js +36 -11
  107. package/dist/elements/Section.js.map +1 -1
  108. package/dist/elements/Shape.d.ts.map +1 -1
  109. package/dist/elements/Shape.js.map +1 -1
  110. package/dist/elements/StructuredDocumentTag.d.ts +6 -6
  111. package/dist/elements/StructuredDocumentTag.d.ts.map +1 -1
  112. package/dist/elements/StructuredDocumentTag.js +99 -104
  113. package/dist/elements/StructuredDocumentTag.js.map +1 -1
  114. package/dist/elements/Table.d.ts +11 -11
  115. package/dist/elements/Table.d.ts.map +1 -1
  116. package/dist/elements/Table.js +102 -107
  117. package/dist/elements/Table.js.map +1 -1
  118. package/dist/elements/TableCell.d.ts +10 -10
  119. package/dist/elements/TableCell.d.ts.map +1 -1
  120. package/dist/elements/TableCell.js +105 -106
  121. package/dist/elements/TableCell.js.map +1 -1
  122. package/dist/elements/TableGridChange.d.ts.map +1 -1
  123. package/dist/elements/TableGridChange.js.map +1 -1
  124. package/dist/elements/TableOfContents.d.ts.map +1 -1
  125. package/dist/elements/TableOfContents.js +4 -4
  126. package/dist/elements/TableOfContents.js.map +1 -1
  127. package/dist/elements/TableOfContentsElement.js.map +1 -1
  128. package/dist/elements/TableRow.d.ts.map +1 -1
  129. package/dist/elements/TableRow.js +13 -6
  130. package/dist/elements/TableRow.js.map +1 -1
  131. package/dist/elements/TextBox.d.ts.map +1 -1
  132. package/dist/elements/TextBox.js +3 -5
  133. package/dist/elements/TextBox.js.map +1 -1
  134. package/dist/formatting/AbstractNumbering.d.ts +4 -4
  135. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  136. package/dist/formatting/AbstractNumbering.js +54 -49
  137. package/dist/formatting/AbstractNumbering.js.map +1 -1
  138. package/dist/formatting/NumberingInstance.d.ts.map +1 -1
  139. package/dist/formatting/NumberingInstance.js +1 -3
  140. package/dist/formatting/NumberingInstance.js.map +1 -1
  141. package/dist/formatting/NumberingLevel.d.ts +5 -5
  142. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  143. package/dist/formatting/NumberingLevel.js +119 -125
  144. package/dist/formatting/NumberingLevel.js.map +1 -1
  145. package/dist/formatting/NumberingManager.d.ts.map +1 -1
  146. package/dist/formatting/NumberingManager.js +9 -9
  147. package/dist/formatting/NumberingManager.js.map +1 -1
  148. package/dist/formatting/Style.d.ts +11 -11
  149. package/dist/formatting/Style.d.ts.map +1 -1
  150. package/dist/formatting/Style.js +219 -247
  151. package/dist/formatting/Style.js.map +1 -1
  152. package/dist/formatting/StylesManager.d.ts +2 -2
  153. package/dist/formatting/StylesManager.d.ts.map +1 -1
  154. package/dist/formatting/StylesManager.js +96 -102
  155. package/dist/formatting/StylesManager.js.map +1 -1
  156. package/dist/helpers/CleanupHelper.d.ts +1 -1
  157. package/dist/helpers/CleanupHelper.d.ts.map +1 -1
  158. package/dist/helpers/CleanupHelper.js +6 -6
  159. package/dist/helpers/CleanupHelper.js.map +1 -1
  160. package/dist/images/ImageOptimizer.js +7 -7
  161. package/dist/images/ImageOptimizer.js.map +1 -1
  162. package/dist/index.d.ts +9 -9
  163. package/dist/index.d.ts.map +1 -1
  164. package/dist/index.js.map +1 -1
  165. package/dist/managers/DrawingManager.js.map +1 -1
  166. package/dist/tracking/DocumentTrackingContext.d.ts.map +1 -1
  167. package/dist/tracking/DocumentTrackingContext.js +23 -7
  168. package/dist/tracking/DocumentTrackingContext.js.map +1 -1
  169. package/dist/tracking/TrackingContext.d.ts.map +1 -1
  170. package/dist/tracking/TrackingContext.js.map +1 -1
  171. package/dist/types/compatibility-types.js.map +1 -1
  172. package/dist/types/formatting.js.map +1 -1
  173. package/dist/types/list-types.d.ts +6 -6
  174. package/dist/types/list-types.js.map +1 -1
  175. package/dist/types/settings-types.js.map +1 -1
  176. package/dist/types/styleConfig.d.ts +2 -2
  177. package/dist/types/styleConfig.js.map +1 -1
  178. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  179. package/dist/utils/ChangelogGenerator.js +97 -101
  180. package/dist/utils/ChangelogGenerator.js.map +1 -1
  181. package/dist/utils/CompatibilityUpgrader.d.ts.map +1 -1
  182. package/dist/utils/CompatibilityUpgrader.js +1 -1
  183. package/dist/utils/CompatibilityUpgrader.js.map +1 -1
  184. package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
  185. package/dist/utils/InMemoryRevisionAcceptor.js +1 -6
  186. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  187. package/dist/utils/MoveOperationHelper.d.ts.map +1 -1
  188. package/dist/utils/MoveOperationHelper.js +1 -1
  189. package/dist/utils/MoveOperationHelper.js.map +1 -1
  190. package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
  191. package/dist/utils/RevisionAwareProcessor.js +2 -4
  192. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  193. package/dist/utils/RevisionWalker.d.ts.map +1 -1
  194. package/dist/utils/RevisionWalker.js +4 -12
  195. package/dist/utils/RevisionWalker.js.map +1 -1
  196. package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
  197. package/dist/utils/SelectiveRevisionAcceptor.js +2 -6
  198. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  199. package/dist/utils/ShadingResolver.d.ts.map +1 -1
  200. package/dist/utils/ShadingResolver.js +1 -1
  201. package/dist/utils/ShadingResolver.js.map +1 -1
  202. package/dist/utils/acceptRevisions.d.ts.map +1 -1
  203. package/dist/utils/acceptRevisions.js +23 -12
  204. package/dist/utils/acceptRevisions.js.map +1 -1
  205. package/dist/utils/cnfStyleDecoder.d.ts +1 -1
  206. package/dist/utils/cnfStyleDecoder.d.ts.map +1 -1
  207. package/dist/utils/cnfStyleDecoder.js +40 -40
  208. package/dist/utils/cnfStyleDecoder.js.map +1 -1
  209. package/dist/utils/corruptionDetection.d.ts.map +1 -1
  210. package/dist/utils/corruptionDetection.js.map +1 -1
  211. package/dist/utils/dateFormatting.js.map +1 -1
  212. package/dist/utils/deepClone.js +1 -1
  213. package/dist/utils/deepClone.js.map +1 -1
  214. package/dist/utils/diagnostics.d.ts.map +1 -1
  215. package/dist/utils/diagnostics.js +1 -1
  216. package/dist/utils/diagnostics.js.map +1 -1
  217. package/dist/utils/errorHandling.js.map +1 -1
  218. package/dist/utils/formatting.d.ts.map +1 -1
  219. package/dist/utils/formatting.js +10 -2
  220. package/dist/utils/formatting.js.map +1 -1
  221. package/dist/utils/list-detection.d.ts +2 -2
  222. package/dist/utils/list-detection.d.ts.map +1 -1
  223. package/dist/utils/list-detection.js +21 -23
  224. package/dist/utils/list-detection.js.map +1 -1
  225. package/dist/utils/logger.d.ts.map +1 -1
  226. package/dist/utils/logger.js +12 -7
  227. package/dist/utils/logger.js.map +1 -1
  228. package/dist/utils/parsingHelpers.js.map +1 -1
  229. package/dist/utils/stripTrackedChanges.d.ts.map +1 -1
  230. package/dist/utils/stripTrackedChanges.js +3 -3
  231. package/dist/utils/stripTrackedChanges.js.map +1 -1
  232. package/dist/utils/textDiff.d.ts +1 -1
  233. package/dist/utils/textDiff.js +8 -8
  234. package/dist/utils/textDiff.js.map +1 -1
  235. package/dist/utils/units.js.map +1 -1
  236. package/dist/utils/validation.d.ts.map +1 -1
  237. package/dist/utils/validation.js +24 -7
  238. package/dist/utils/validation.js.map +1 -1
  239. package/dist/utils/xmlSanitization.d.ts.map +1 -1
  240. package/dist/utils/xmlSanitization.js +3 -3
  241. package/dist/utils/xmlSanitization.js.map +1 -1
  242. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  243. package/dist/validation/RevisionAutoFixer.js +5 -5
  244. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  245. package/dist/validation/RevisionValidator.d.ts.map +1 -1
  246. package/dist/validation/RevisionValidator.js +7 -9
  247. package/dist/validation/RevisionValidator.js.map +1 -1
  248. package/dist/validation/ValidationRules.js +3 -3
  249. package/dist/validation/ValidationRules.js.map +1 -1
  250. package/dist/validation/index.js.map +1 -1
  251. package/dist/xml/XMLBuilder.d.ts +1 -1
  252. package/dist/xml/XMLBuilder.d.ts.map +1 -1
  253. package/dist/xml/XMLBuilder.js +98 -100
  254. package/dist/xml/XMLBuilder.js.map +1 -1
  255. package/dist/xml/XMLParser.d.ts.map +1 -1
  256. package/dist/xml/XMLParser.js +61 -66
  257. package/dist/xml/XMLParser.js.map +1 -1
  258. package/dist/zip/ZipHandler.d.ts.map +1 -1
  259. package/dist/zip/ZipHandler.js.map +1 -1
  260. package/dist/zip/ZipReader.d.ts.map +1 -1
  261. package/dist/zip/ZipReader.js +1 -3
  262. package/dist/zip/ZipReader.js.map +1 -1
  263. package/dist/zip/ZipWriter.d.ts +1 -1
  264. package/dist/zip/ZipWriter.d.ts.map +1 -1
  265. package/dist/zip/ZipWriter.js +28 -36
  266. package/dist/zip/ZipWriter.js.map +1 -1
  267. package/dist/zip/types.js +1 -1
  268. package/dist/zip/types.js.map +1 -1
  269. package/package.json +92 -92
  270. package/src/__tests__/helper-methods.test.ts +512 -512
  271. package/src/constants/legacyCompatFlags.ts +138 -138
  272. package/src/constants/limits.ts +50 -50
  273. package/src/core/Document.ts +985 -1145
  274. package/src/core/DocumentContent.ts +461 -467
  275. package/src/core/DocumentGenerator.ts +1133 -1104
  276. package/src/core/DocumentIdManager.ts +158 -158
  277. package/src/core/DocumentParser.ts +2347 -2716
  278. package/src/core/DocumentValidator.ts +363 -372
  279. package/src/core/Relationship.ts +367 -367
  280. package/src/core/RelationshipManager.ts +429 -428
  281. package/src/elements/AlternateContent.ts +42 -42
  282. package/src/elements/Bookmark.ts +212 -210
  283. package/src/elements/BookmarkManager.ts +247 -250
  284. package/src/elements/Comment.ts +356 -359
  285. package/src/elements/CommentManager.ts +499 -502
  286. package/src/elements/CommonTypes.ts +524 -549
  287. package/src/elements/CustomXml.ts +36 -36
  288. package/src/elements/Endnote.ts +221 -217
  289. package/src/elements/EndnoteManager.ts +246 -249
  290. package/src/elements/Field.ts +1292 -1233
  291. package/src/elements/FieldHelpers.ts +329 -333
  292. package/src/elements/FontManager.ts +336 -339
  293. package/src/elements/Footer.ts +269 -269
  294. package/src/elements/Footnote.ts +221 -217
  295. package/src/elements/FootnoteManager.ts +246 -249
  296. package/src/elements/Header.ts +269 -269
  297. package/src/elements/HeaderFooterManager.ts +219 -219
  298. package/src/elements/Hyperlink.ts +1288 -1193
  299. package/src/elements/Image.ts +1982 -1756
  300. package/src/elements/ImageManager.ts +437 -432
  301. package/src/elements/ImageRun.ts +59 -59
  302. package/src/elements/MathElement.ts +65 -65
  303. package/src/elements/Paragraph.ts +4347 -4287
  304. package/src/elements/PreservedElement.ts +53 -53
  305. package/src/elements/PropertyChangeTypes.ts +458 -442
  306. package/src/elements/RangeMarker.ts +382 -400
  307. package/src/elements/Revision.ts +1198 -1217
  308. package/src/elements/RevisionContent.ts +73 -73
  309. package/src/elements/RevisionManager.ts +1070 -1070
  310. package/src/elements/Run.ts +3103 -3073
  311. package/src/elements/Section.ts +1521 -1421
  312. package/src/elements/Shape.ts +884 -873
  313. package/src/elements/StructuredDocumentTag.ts +1176 -1207
  314. package/src/elements/Table.ts +2468 -2524
  315. package/src/elements/TableCell.ts +1617 -1621
  316. package/src/elements/TableGridChange.ts +149 -151
  317. package/src/elements/TableOfContents.ts +701 -691
  318. package/src/elements/TableOfContentsElement.ts +89 -89
  319. package/src/elements/TableRow.ts +960 -929
  320. package/src/elements/TextBox.ts +766 -768
  321. package/src/formatting/AbstractNumbering.ts +580 -579
  322. package/src/formatting/NumberingInstance.ts +295 -299
  323. package/src/formatting/NumberingLevel.ts +981 -1040
  324. package/src/formatting/NumberingManager.ts +833 -827
  325. package/src/formatting/Style.ts +1785 -1879
  326. package/src/formatting/StylesManager.ts +1090 -1130
  327. package/src/helpers/CleanupHelper.ts +524 -524
  328. package/src/images/ImageOptimizer.ts +274 -274
  329. package/src/index.ts +559 -554
  330. package/src/managers/DrawingManager.ts +319 -319
  331. package/src/tracking/DocumentTrackingContext.ts +687 -674
  332. package/src/tracking/TrackingContext.ts +175 -173
  333. package/src/types/compatibility-types.ts +49 -49
  334. package/src/types/formatting.ts +210 -210
  335. package/src/types/list-types.ts +14 -14
  336. package/src/types/settings-types.ts +59 -59
  337. package/src/types/styleConfig.ts +189 -189
  338. package/src/utils/ChangelogGenerator.ts +1583 -1581
  339. package/src/utils/CompatibilityUpgrader.ts +235 -237
  340. package/src/utils/InMemoryRevisionAcceptor.ts +691 -696
  341. package/src/utils/MoveOperationHelper.ts +233 -238
  342. package/src/utils/RevisionAwareProcessor.ts +518 -526
  343. package/src/utils/RevisionWalker.ts +427 -457
  344. package/src/utils/SelectiveRevisionAcceptor.ts +662 -683
  345. package/src/utils/ShadingResolver.ts +105 -107
  346. package/src/utils/acceptRevisions.ts +723 -714
  347. package/src/utils/cnfStyleDecoder.ts +212 -217
  348. package/src/utils/corruptionDetection.ts +346 -345
  349. package/src/utils/dateFormatting.ts +20 -20
  350. package/src/utils/deepClone.ts +77 -78
  351. package/src/utils/diagnostics.ts +125 -129
  352. package/src/utils/errorHandling.ts +80 -80
  353. package/src/utils/formatting.ts +220 -213
  354. package/src/utils/list-detection.ts +32 -42
  355. package/src/utils/logger.ts +412 -404
  356. package/src/utils/parsingHelpers.ts +190 -190
  357. package/src/utils/stripTrackedChanges.ts +356 -353
  358. package/src/utils/textDiff.ts +100 -100
  359. package/src/utils/units.ts +421 -421
  360. package/src/utils/validation.ts +553 -542
  361. package/src/utils/xmlSanitization.ts +179 -182
  362. package/src/validation/RevisionAutoFixer.ts +541 -542
  363. package/src/validation/RevisionValidator.ts +470 -460
  364. package/src/validation/ValidationRules.ts +338 -338
  365. package/src/validation/index.ts +30 -30
  366. package/src/xml/XMLBuilder.ts +857 -871
  367. package/src/xml/XMLParser.ts +877 -919
  368. package/src/zip/ZipHandler.ts +629 -637
  369. package/src/zip/ZipReader.ts +295 -299
  370. package/src/zip/ZipWriter.ts +374 -390
  371. package/src/zip/types.ts +116 -116
@@ -1,1421 +1,1521 @@
1
- /**
2
- * Section - Represents a document section with page setup properties
3
- *
4
- * Sections allow different page setups within a single document (margins, orientation, etc.)
5
- * Each section can have its own headers, footers, and page numbering.
6
- */
7
-
8
- import { XMLBuilder, XMLElement } from '../xml/XMLBuilder';
9
- import { PAGE_SIZES } from '../utils/units';
10
-
11
- /**
12
- * Page orientation
13
- */
14
- export type PageOrientation = 'portrait' | 'landscape';
15
-
16
- /**
17
- * Section break type
18
- */
19
- export type SectionType = 'nextPage' | 'continuous' | 'evenPage' | 'oddPage' | 'nextColumn';
20
-
21
- /**
22
- * Page numbering format
23
- */
24
- /**
25
- * Per ECMA-376 Part 1 §17.18.59 (ST_NumberFormat)
26
- * Comprehensive set of page number formats
27
- */
28
- export type PageNumberFormat =
29
- | 'decimal' | 'lowerRoman' | 'upperRoman' | 'lowerLetter' | 'upperLetter'
30
- | 'ordinal' | 'cardinalText' | 'ordinalText'
31
- | 'hex' | 'chicago' | 'decimalZero'
32
- | 'decimalEnclosedCircle' | 'decimalEnclosedFullstop' | 'decimalEnclosedParen'
33
- | 'ideographDigital' | 'ideographTraditional' | 'ideographLegalTraditional'
34
- | 'ideographEnclosedCircle' | 'ideographZodiac' | 'ideographZodiacTraditional'
35
- | 'chineseCounting' | 'chineseCountingThousand' | 'chineseLegalSimplified'
36
- | 'japaneseCounting' | 'japaneseDigitalTenThousand' | 'japaneseLegal'
37
- | 'koreanCounting' | 'koreanDigital' | 'koreanDigital2' | 'koreanLegal'
38
- | 'taiwaneseCounting' | 'taiwaneseCountingThousand' | 'taiwaneseDigital'
39
- | 'aiueo' | 'aiueoFullWidth' | 'iroha' | 'irohaFullWidth'
40
- | 'arabicAbjad' | 'arabicAlpha' | 'hebrew1' | 'hebrew2'
41
- | 'hindiConsonants' | 'hindiCounting' | 'hindiNumbers' | 'hindiVowels'
42
- | 'thaiCounting' | 'thaiLetters' | 'thaiNumbers'
43
- | 'vietnameseCounting'
44
- | 'russianLower' | 'russianUpper'
45
- | 'numberInDash' | 'dollarText' | 'bullet'
46
- | 'bahtText' | 'ganada' | 'chosung'
47
- | 'none'
48
- | string; // Allow any format string for forward compatibility
49
-
50
- /**
51
- * Page size properties
52
- */
53
- export interface PageSize {
54
- /** Width in twips */
55
- width: number;
56
- /** Height in twips */
57
- height: number;
58
- /** Orientation */
59
- orientation?: PageOrientation;
60
- }
61
-
62
- /**
63
- * Margin properties
64
- */
65
- export interface Margins {
66
- /** Top margin in twips */
67
- top: number;
68
- /** Bottom margin in twips */
69
- bottom: number;
70
- /** Left margin in twips */
71
- left: number;
72
- /** Right margin in twips */
73
- right: number;
74
- /** Header distance from top in twips */
75
- header?: number;
76
- /** Footer distance from bottom in twips */
77
- footer?: number;
78
- /** Gutter margin in twips */
79
- gutter?: number;
80
- }
81
-
82
- /**
83
- * Column properties
84
- */
85
- export interface Columns {
86
- /** Number of columns */
87
- count: number;
88
- /** Space between columns in twips */
89
- space?: number;
90
- /** Equal column widths */
91
- equalWidth?: boolean;
92
- /** Show column separator line */
93
- separator?: boolean;
94
- /** Individual column widths (for unequal columns) in twips */
95
- columnWidths?: number[];
96
- }
97
-
98
- /**
99
- * Page numbering properties
100
- */
101
- export interface PageNumbering {
102
- /** Starting page number */
103
- start?: number;
104
- /** Number format */
105
- format?: PageNumberFormat;
106
- }
107
-
108
- /**
109
- * Paper source (printer tray) properties
110
- */
111
- export interface PaperSource {
112
- /** First page tray number */
113
- first?: number;
114
- /** Other pages tray number */
115
- other?: number;
116
- }
117
-
118
- /**
119
- * Vertical page alignment
120
- */
121
- export type VerticalAlignment = 'top' | 'center' | 'bottom' | 'both';
122
-
123
- /**
124
- * Text direction
125
- */
126
- export type TextDirection = 'ltr' | 'rtl' | 'tbRl' | 'btLr';
127
-
128
- /**
129
- * Document grid type for East Asian typography
130
- */
131
- export type DocumentGridType = 'default' | 'lines' | 'linesAndChars' | 'snapToChars';
132
-
133
- /**
134
- * Document grid properties
135
- */
136
- export interface DocumentGrid {
137
- /** Grid type */
138
- type?: DocumentGridType;
139
- /** Lines per page */
140
- linePitch?: number;
141
- /** Characters per line */
142
- charSpace?: number;
143
- }
144
-
145
- /**
146
- * Line numbering restart mode
147
- */
148
- export type LineNumberingRestart = 'newPage' | 'newSection' | 'continuous';
149
-
150
- /**
151
- * Line numbering properties
152
- * Per ECMA-376 Part 1, Section 17.6.8 (w:lnNumType element)
153
- */
154
- export interface LineNumbering {
155
- /** Display line number every N lines (countBy attribute) */
156
- countBy?: number;
157
- /** Starting line number */
158
- start?: number;
159
- /** Distance from text margin in twips */
160
- distance?: number;
161
- /** When to restart line numbering */
162
- restart?: LineNumberingRestart;
163
- }
164
-
165
- /**
166
- * Footnote/endnote position
167
- * Per ECMA-376 Part 1 §17.11.6 and §17.11.7
168
- */
169
- export type NotePosition = 'pageBottom' | 'beneathText' | 'sectEnd' | 'docEnd';
170
-
171
- /**
172
- * Note numbering restart mode
173
- */
174
- export type NoteNumberRestart = 'continuous' | 'eachSect' | 'eachPage';
175
-
176
- /**
177
- * Chapter separator character for page numbering
178
- * Per ECMA-376 Part 1 §17.18.5
179
- */
180
- export type ChapterSeparator = 'colon' | 'emDash' | 'enDash' | 'hyphen' | 'period';
181
-
182
- /**
183
- * Footnote/endnote section-level properties
184
- */
185
- export interface NoteProperties {
186
- /** Note positioning */
187
- position?: NotePosition;
188
- /** Number format */
189
- numberFormat?: PageNumberFormat;
190
- /** Starting number */
191
- startNumber?: number;
192
- /** Restart numbering mode */
193
- restart?: NoteNumberRestart;
194
- }
195
-
196
- /**
197
- * Page border definition for a single side
198
- * Per ECMA-376 Part 1 §17.6.10
199
- */
200
- export interface PageBorderDef {
201
- /** Border style (single, double, dashed, etc.) */
202
- style?: string;
203
- /** Border size in eighths of a point */
204
- size?: number;
205
- /** Border color in hex format (without #) */
206
- color?: string;
207
- /** Space between border and page edge/text in points */
208
- space?: number;
209
- /** Whether to show shadow */
210
- shadow?: boolean;
211
- /** Whether to include frame around page */
212
- frame?: boolean;
213
- /** Theme color reference */
214
- themeColor?: string;
215
- /** Art border ID (for decorative borders) */
216
- artId?: number;
217
- }
218
-
219
- /**
220
- * Page borders configuration
221
- * Per ECMA-376 Part 1 §17.6.10
222
- */
223
- export interface PageBorders {
224
- /** Top page border */
225
- top?: PageBorderDef;
226
- /** Bottom page border */
227
- bottom?: PageBorderDef;
228
- /** Left page border */
229
- left?: PageBorderDef;
230
- /** Right page border */
231
- right?: PageBorderDef;
232
- /** Whether border is measured from page edge or text edge */
233
- offsetFrom?: 'page' | 'text';
234
- /** Display on all pages, first page only, or not first page */
235
- display?: 'allPages' | 'firstPage' | 'notFirstPage';
236
- /** Z-ordering relative to text */
237
- zOrder?: 'front' | 'back';
238
- }
239
-
240
- /**
241
- * Section properties
242
- */
243
- export interface SectionProperties {
244
- /** Page size */
245
- pageSize?: PageSize;
246
- /** Margins */
247
- margins?: Margins;
248
- /** Column layout */
249
- columns?: Columns;
250
- /** Section break type */
251
- type?: SectionType;
252
- /** Page numbering */
253
- pageNumbering?: PageNumbering;
254
- /** Header reference IDs */
255
- headers?: {
256
- default?: string; // rId for default header
257
- first?: string; // rId for first page header
258
- even?: string; // rId for even page header
259
- };
260
- /** Footer reference IDs */
261
- footers?: {
262
- default?: string; // rId for default footer
263
- first?: string; // rId for first page footer
264
- even?: string; // rId for even page footer
265
- };
266
- /** Title page (different first page) */
267
- titlePage?: boolean;
268
- /** Vertical page alignment */
269
- verticalAlignment?: VerticalAlignment;
270
- /** Paper source (printer tray) */
271
- paperSource?: PaperSource;
272
- /** Text direction (LTR/RTL support) */
273
- textDirection?: TextDirection;
274
- /** Right-to-left section layout */
275
- bidi?: boolean;
276
- /** Gutter on right side (for RTL) */
277
- rtlGutter?: boolean;
278
- /** Document grid for snapping text to grid */
279
- docGrid?: DocumentGrid;
280
- /** Line numbering configuration */
281
- lineNumbering?: LineNumbering;
282
- /** Section-level footnote properties (w:footnotePr) */
283
- footnotePr?: NoteProperties;
284
- /** Section-level endnote properties (w:endnotePr) */
285
- endnotePr?: NoteProperties;
286
- /** Suppress endnotes in this section (w:noEndnote) */
287
- noEndnote?: boolean;
288
- /** Form protection for this section (w:formProt) */
289
- formProt?: boolean;
290
- /** Page borders per ECMA-376 Part 1 §17.6.10 */
291
- pageBorders?: PageBorders;
292
- /** Printer settings relationship ID (w:printerSettings r:id) */
293
- printerSettingsId?: string;
294
- /** Chapter style heading level for page numbering (w:pgNumType w:chapStyle) */
295
- chapStyle?: number;
296
- /** Chapter separator for page numbering (w:pgNumType w:chapSep) */
297
- chapSep?: ChapterSeparator;
298
- }
299
-
300
- /**
301
- * Represents a document section
302
- */
303
- /**
304
- * Section property change tracking (w:sectPrChange)
305
- * Per ECMA-376 Part 1 §17.13.5.32
306
- */
307
- export interface SectPrChange {
308
- author: string;
309
- date: string;
310
- id: string;
311
- previousProperties: Record<string, any>;
312
- }
313
-
314
- export class Section {
315
- private properties: SectionProperties;
316
- /** Tracking context for automatic change tracking */
317
- private trackingContext?: import('../tracking/TrackingContext').TrackingContext;
318
- /** Section property change tracking (w:sectPrChange) */
319
- private sectPrChange?: SectPrChange;
320
-
321
- /**
322
- * Creates a new section
323
- * @param properties Section properties
324
- */
325
- constructor(properties: SectionProperties = {}) {
326
- // Set defaults only where necessary
327
- this.properties = {
328
- pageSize: properties.pageSize || {
329
- width: PAGE_SIZES.LETTER.width,
330
- height: PAGE_SIZES.LETTER.height,
331
- orientation: 'portrait',
332
- },
333
- margins: properties.margins || {
334
- top: 1440, // 1 inch
335
- bottom: 1440,
336
- left: 1440,
337
- right: 1440,
338
- header: 720, // 0.5 inch
339
- footer: 720,
340
- },
341
- // Default to single column layout
342
- columns: properties.columns || {
343
- count: 1,
344
- },
345
- // Default to next page section break
346
- type: properties.type || 'nextPage',
347
- pageNumbering: properties.pageNumbering,
348
- headers: properties.headers,
349
- footers: properties.footers,
350
- titlePage: properties.titlePage,
351
- // Phase 4.5 - New properties
352
- verticalAlignment: properties.verticalAlignment,
353
- paperSource: properties.paperSource,
354
- textDirection: properties.textDirection,
355
- pageBorders: properties.pageBorders,
356
- };
357
- }
358
-
359
- /**
360
- * Sets the tracking context for automatic change tracking.
361
- * Called by Document when track changes is enabled.
362
- * @internal
363
- */
364
- _setTrackingContext(context: import('../tracking/TrackingContext').TrackingContext): void {
365
- this.trackingContext = context;
366
- }
367
-
368
- /**
369
- * Gets the section property change tracking info
370
- */
371
- getSectPrChange(): SectPrChange | undefined {
372
- return this.sectPrChange;
373
- }
374
-
375
- /**
376
- * Sets the section property change tracking info
377
- */
378
- setSectPrChange(change: SectPrChange | undefined): void {
379
- this.sectPrChange = change;
380
- }
381
-
382
- /**
383
- * Clears the section property change tracking
384
- */
385
- clearSectPrChange(): void {
386
- this.sectPrChange = undefined;
387
- }
388
-
389
- /**
390
- * Gets the section properties
391
- */
392
- getProperties(): SectionProperties {
393
- return { ...this.properties };
394
- }
395
-
396
- // ============================================================================
397
- // Individual Property Getters
398
- // ============================================================================
399
-
400
- /**
401
- * Gets the page size settings
402
- * @returns Page size object with width, height, and orientation, or undefined
403
- */
404
- getPageSize(): PageSize | undefined {
405
- return this.properties.pageSize ? { ...this.properties.pageSize } : undefined;
406
- }
407
-
408
- /**
409
- * Gets the page orientation
410
- * @returns 'portrait' or 'landscape', or undefined if not set
411
- */
412
- getOrientation(): PageOrientation | undefined {
413
- return this.properties.pageSize?.orientation;
414
- }
415
-
416
- /**
417
- * Gets the margin settings
418
- * @returns Margins object or undefined if not set
419
- */
420
- getMargins(): Margins | undefined {
421
- return this.properties.margins ? { ...this.properties.margins } : undefined;
422
- }
423
-
424
- /**
425
- * Gets the column layout settings
426
- * @returns Columns object or undefined if not set
427
- */
428
- getColumns(): Columns | undefined {
429
- return this.properties.columns ? { ...this.properties.columns } : undefined;
430
- }
431
-
432
- /**
433
- * Gets the section type (break type)
434
- * @returns Section type or undefined if not set
435
- */
436
- getSectionType(): SectionType | undefined {
437
- return this.properties.type;
438
- }
439
-
440
- /**
441
- * Gets the page numbering settings
442
- * @returns PageNumbering object or undefined if not set
443
- */
444
- getPageNumbering(): PageNumbering | undefined {
445
- return this.properties.pageNumbering ? { ...this.properties.pageNumbering } : undefined;
446
- }
447
-
448
- /**
449
- * Gets whether this section has a title page (different first page)
450
- * @returns true if title page is enabled, false otherwise
451
- */
452
- getTitlePage(): boolean {
453
- return this.properties.titlePage ?? false;
454
- }
455
-
456
- /**
457
- * Gets header references
458
- * @returns Object with default, first, and even header relationship IDs, or undefined
459
- */
460
- getHeaderReferences(): { default?: string; first?: string; even?: string } | undefined {
461
- return this.properties.headers ? { ...this.properties.headers } : undefined;
462
- }
463
-
464
- /**
465
- * Gets a specific header reference
466
- * @param type Header type (default, first, even)
467
- * @returns Relationship ID or undefined if not set
468
- */
469
- getHeaderReference(type: 'default' | 'first' | 'even'): string | undefined {
470
- return this.properties.headers?.[type];
471
- }
472
-
473
- /**
474
- * Gets footer references
475
- * @returns Object with default, first, and even footer relationship IDs, or undefined
476
- */
477
- getFooterReferences(): { default?: string; first?: string; even?: string } | undefined {
478
- return this.properties.footers ? { ...this.properties.footers } : undefined;
479
- }
480
-
481
- /**
482
- * Gets a specific footer reference
483
- * @param type Footer type (default, first, even)
484
- * @returns Relationship ID or undefined if not set
485
- */
486
- getFooterReference(type: 'default' | 'first' | 'even'): string | undefined {
487
- return this.properties.footers?.[type];
488
- }
489
-
490
- /**
491
- * Gets the vertical page alignment
492
- * @returns Vertical alignment or undefined if not set
493
- */
494
- getVerticalAlignment(): VerticalAlignment | undefined {
495
- return this.properties.verticalAlignment;
496
- }
497
-
498
- /**
499
- * Gets the paper source (printer tray) settings
500
- * @returns PaperSource object or undefined if not set
501
- */
502
- getPaperSource(): PaperSource | undefined {
503
- return this.properties.paperSource ? { ...this.properties.paperSource } : undefined;
504
- }
505
-
506
- /**
507
- * Gets whether column separator is enabled
508
- * @returns true if separator is enabled, false otherwise
509
- */
510
- getColumnSeparator(): boolean {
511
- return this.properties.columns?.separator ?? false;
512
- }
513
-
514
- /**
515
- * Gets custom column widths
516
- * @returns Array of column widths in twips, or undefined if not set
517
- */
518
- getColumnWidths(): number[] | undefined {
519
- return this.properties.columns?.columnWidths ? [...this.properties.columns.columnWidths] : undefined;
520
- }
521
-
522
- /**
523
- * Gets the text direction
524
- * @returns Text direction or undefined if not set
525
- */
526
- getTextDirection(): TextDirection | undefined {
527
- return this.properties.textDirection;
528
- }
529
-
530
- /**
531
- * Gets whether the section is bidirectional (RTL)
532
- * @returns true if bidi is enabled, false otherwise
533
- */
534
- getBidi(): boolean {
535
- return this.properties.bidi ?? false;
536
- }
537
-
538
- /**
539
- * Gets whether RTL gutter is enabled (gutter on right side)
540
- * @returns true if RTL gutter is enabled, false otherwise
541
- */
542
- getRtlGutter(): boolean {
543
- return this.properties.rtlGutter ?? false;
544
- }
545
-
546
- /**
547
- * Gets the document grid settings
548
- * @returns DocumentGrid object or undefined if not set
549
- */
550
- getDocGrid(): DocumentGrid | undefined {
551
- return this.properties.docGrid ? { ...this.properties.docGrid } : undefined;
552
- }
553
-
554
- // ============================================================================
555
- // Setters
556
- // ============================================================================
557
-
558
- /**
559
- * Sets page size
560
- * @param width Width in twips
561
- * @param height Height in twips
562
- * @param orientation Page orientation
563
- */
564
- setPageSize(width: number, height: number, orientation: PageOrientation = 'portrait'): this {
565
- const prev = this.properties.pageSize ? { ...this.properties.pageSize } : undefined;
566
- this.properties.pageSize = { width, height, orientation };
567
- if (this.trackingContext?.isEnabled()) {
568
- this.trackingContext.trackSectionChange(this, 'pageSize', prev, this.properties.pageSize);
569
- }
570
- return this;
571
- }
572
-
573
- /**
574
- * Sets page orientation
575
- * @param orientation Page orientation
576
- */
577
- setOrientation(orientation: PageOrientation): this {
578
- const prev = this.properties.pageSize?.orientation;
579
- if (!this.properties.pageSize) {
580
- this.properties.pageSize = {
581
- width: PAGE_SIZES.LETTER.width,
582
- height: PAGE_SIZES.LETTER.height,
583
- };
584
- }
585
- this.properties.pageSize.orientation = orientation;
586
-
587
- // Swap width/height for landscape
588
- if (orientation === 'landscape' && this.properties.pageSize.width < this.properties.pageSize.height) {
589
- const temp = this.properties.pageSize.width;
590
- this.properties.pageSize.width = this.properties.pageSize.height;
591
- this.properties.pageSize.height = temp;
592
- }
593
-
594
- if (this.trackingContext?.isEnabled() && prev !== orientation) {
595
- this.trackingContext.trackSectionChange(this, 'orientation', prev, orientation);
596
- }
597
- return this;
598
- }
599
-
600
- /**
601
- * Sets margins
602
- * @param margins Margin properties
603
- */
604
- setMargins(margins: Margins): this {
605
- const prev = this.properties.margins ? { ...this.properties.margins } : undefined;
606
- const existing = this.properties.margins;
607
- this.properties.margins = {
608
- top: margins.top,
609
- bottom: margins.bottom,
610
- left: margins.left,
611
- right: margins.right,
612
- header: margins.header ?? existing?.header ?? 720,
613
- footer: margins.footer ?? existing?.footer ?? 720,
614
- gutter: margins.gutter ?? existing?.gutter,
615
- };
616
- if (this.trackingContext?.isEnabled()) {
617
- this.trackingContext.trackSectionChange(this, 'margins', prev, this.properties.margins);
618
- }
619
- return this;
620
- }
621
-
622
- /**
623
- * Sets column layout
624
- * @param count Number of columns
625
- * @param space Space between columns in twips
626
- */
627
- setColumns(count: number, space = 720): this {
628
- const prev = this.properties.columns ? { ...this.properties.columns } : undefined;
629
- this.properties.columns = {
630
- count,
631
- space,
632
- equalWidth: true,
633
- };
634
- if (this.trackingContext?.isEnabled()) {
635
- this.trackingContext.trackSectionChange(this, 'columns', prev, this.properties.columns);
636
- }
637
- return this;
638
- }
639
-
640
- /**
641
- * Sets section type
642
- * @param type Section break type
643
- */
644
- setSectionType(type: SectionType): this {
645
- const prev = this.properties.type;
646
- this.properties.type = type;
647
- if (this.trackingContext?.isEnabled() && prev !== type) {
648
- this.trackingContext.trackSectionChange(this, 'type', prev, type);
649
- }
650
- return this;
651
- }
652
-
653
- /**
654
- * Sets page numbering
655
- * @param start Starting page number
656
- * @param format Number format
657
- */
658
- setPageNumbering(start?: number, format?: PageNumberFormat): this {
659
- const prev = this.properties.pageNumbering ? { ...this.properties.pageNumbering } : undefined;
660
- this.properties.pageNumbering = { start, format };
661
- if (this.trackingContext?.isEnabled()) {
662
- this.trackingContext.trackSectionChange(this, 'pageNumbering', prev, this.properties.pageNumbering);
663
- }
664
- return this;
665
- }
666
-
667
- /**
668
- * Sets title page flag (different first page)
669
- * @param titlePage Whether this section has a different first page
670
- */
671
- setTitlePage(titlePage = true): this {
672
- const prev = this.properties.titlePage;
673
- this.properties.titlePage = titlePage;
674
- if (this.trackingContext?.isEnabled() && prev !== titlePage) {
675
- this.trackingContext.trackSectionChange(this, 'titlePage', prev, titlePage);
676
- }
677
- return this;
678
- }
679
-
680
- /**
681
- * Sets header reference
682
- * @param type Header type (default, first, even)
683
- * @param rId Relationship ID
684
- */
685
- setHeaderReference(type: 'default' | 'first' | 'even', rId: string): this {
686
- const prev = this.properties.headers?.[type];
687
- if (!this.properties.headers) {
688
- this.properties.headers = {};
689
- }
690
- this.properties.headers[type] = rId;
691
- if (this.trackingContext?.isEnabled() && prev !== rId) {
692
- this.trackingContext.trackSectionChange(this, `headerReference:${type}`, prev, rId);
693
- }
694
- return this;
695
- }
696
-
697
- /**
698
- * Sets footer reference
699
- * @param type Footer type (default, first, even)
700
- * @param rId Relationship ID
701
- */
702
- setFooterReference(type: 'default' | 'first' | 'even', rId: string): this {
703
- const prev = this.properties.footers?.[type];
704
- if (!this.properties.footers) {
705
- this.properties.footers = {};
706
- }
707
- this.properties.footers[type] = rId;
708
- if (this.trackingContext?.isEnabled() && prev !== rId) {
709
- this.trackingContext.trackSectionChange(this, `footerReference:${type}`, prev, rId);
710
- }
711
- return this;
712
- }
713
-
714
- /**
715
- * Sets vertical page alignment
716
- * Controls how content is vertically aligned on the page
717
- * @param alignment Vertical alignment (top, center, bottom, both=justified)
718
- */
719
- setVerticalAlignment(alignment: VerticalAlignment): this {
720
- const prev = this.properties.verticalAlignment;
721
- this.properties.verticalAlignment = alignment;
722
- if (this.trackingContext?.isEnabled() && prev !== alignment) {
723
- this.trackingContext.trackSectionChange(this, 'verticalAlignment', prev, alignment);
724
- }
725
- return this;
726
- }
727
-
728
- /**
729
- * Sets paper source (printer tray selection)
730
- * @param first First page tray number
731
- * @param other Other pages tray number
732
- */
733
- setPaperSource(first?: number, other?: number): this {
734
- const prev = this.properties.paperSource ? { ...this.properties.paperSource } : undefined;
735
- this.properties.paperSource = { first, other };
736
- if (this.trackingContext?.isEnabled()) {
737
- this.trackingContext.trackSectionChange(this, 'paperSource', prev, this.properties.paperSource);
738
- }
739
- return this;
740
- }
741
-
742
- /**
743
- * Sets column separator line
744
- * Shows a vertical line between columns
745
- * @param separator Whether to show column separator line
746
- */
747
- setColumnSeparator(separator = true): this {
748
- const prev = this.properties.columns?.separator;
749
- if (!this.properties.columns) {
750
- this.properties.columns = { count: 1 };
751
- }
752
- this.properties.columns.separator = separator;
753
- if (this.trackingContext?.isEnabled() && prev !== separator) {
754
- this.trackingContext.trackSectionChange(this, 'columnSeparator', prev, separator);
755
- }
756
- return this;
757
- }
758
-
759
- /**
760
- * Sets custom column widths (for unequal columns)
761
- * @param widths Array of column widths in twips
762
- */
763
- setColumnWidths(widths: number[]): this {
764
- const prev = this.properties.columns ? { ...this.properties.columns } : undefined;
765
- if (!this.properties.columns) {
766
- this.properties.columns = { count: widths.length };
767
- }
768
- this.properties.columns.columnWidths = widths;
769
- this.properties.columns.equalWidth = false;
770
- this.properties.columns.count = widths.length;
771
- if (this.trackingContext?.isEnabled()) {
772
- this.trackingContext.trackSectionChange(this, 'columns', prev, { ...this.properties.columns });
773
- }
774
- return this;
775
- }
776
-
777
- /**
778
- * Sets text direction for the section
779
- * @param direction Text direction (ltr=left-to-right, rtl=right-to-left, tbRl=top-to-bottom-right-to-left, btLr=bottom-to-top-left-to-right)
780
- */
781
- setTextDirection(direction: TextDirection): this {
782
- const prev = this.properties.textDirection;
783
- this.properties.textDirection = direction;
784
- if (this.trackingContext?.isEnabled() && prev !== direction) {
785
- this.trackingContext.trackSectionChange(this, 'textDirection', prev, direction);
786
- }
787
- return this;
788
- }
789
-
790
- /**
791
- * Sets line numbering for the section
792
- * Per ECMA-376 Part 1, Section 17.6.8 (w:lnNumType)
793
- * @param options Line numbering configuration
794
- */
795
- setLineNumbering(options: LineNumbering): this {
796
- const prev = this.properties.lineNumbering ? { ...this.properties.lineNumbering } : undefined;
797
- this.properties.lineNumbering = { ...options };
798
- if (this.trackingContext?.isEnabled()) {
799
- this.trackingContext.trackSectionChange(this, 'lineNumbering', prev, this.properties.lineNumbering);
800
- }
801
- return this;
802
- }
803
-
804
- /**
805
- * Gets line numbering configuration
806
- * @returns Line numbering settings or undefined if not set
807
- */
808
- getLineNumbering(): LineNumbering | undefined {
809
- return this.properties.lineNumbering ? { ...this.properties.lineNumbering } : undefined;
810
- }
811
-
812
- /**
813
- * Clears line numbering for the section
814
- */
815
- clearLineNumbering(): this {
816
- this.properties.lineNumbering = undefined;
817
- return this;
818
- }
819
-
820
- /**
821
- * Sets section-level footnote properties
822
- * Per ECMA-376 Part 1 §17.11.6
823
- */
824
- setFootnoteProperties(props: NoteProperties): this {
825
- const prev = this.properties.footnotePr ? { ...this.properties.footnotePr } : undefined;
826
- this.properties.footnotePr = { ...props };
827
- if (this.trackingContext?.isEnabled()) {
828
- this.trackingContext.trackSectionChange(this, 'footnotePr', prev, this.properties.footnotePr);
829
- }
830
- return this;
831
- }
832
-
833
- /**
834
- * Sets section-level endnote properties
835
- * Per ECMA-376 Part 1 §17.11.7
836
- */
837
- setEndnoteProperties(props: NoteProperties): this {
838
- const prev = this.properties.endnotePr ? { ...this.properties.endnotePr } : undefined;
839
- this.properties.endnotePr = { ...props };
840
- if (this.trackingContext?.isEnabled()) {
841
- this.trackingContext.trackSectionChange(this, 'endnotePr', prev, this.properties.endnotePr);
842
- }
843
- return this;
844
- }
845
-
846
- /**
847
- * Sets whether endnotes are suppressed in this section
848
- * Per ECMA-376 Part 1 §17.6.14
849
- */
850
- setNoEndnote(noEndnote = true): this {
851
- const prev = this.properties.noEndnote;
852
- this.properties.noEndnote = noEndnote;
853
- if (this.trackingContext?.isEnabled() && prev !== noEndnote) {
854
- this.trackingContext.trackSectionChange(this, 'noEndnote', prev, noEndnote);
855
- }
856
- return this;
857
- }
858
-
859
- /**
860
- * Sets form protection for this section
861
- * Per ECMA-376 Part 1 §17.6.4
862
- */
863
- setFormProtection(formProt = true): this {
864
- const prev = this.properties.formProt;
865
- this.properties.formProt = formProt;
866
- if (this.trackingContext?.isEnabled() && prev !== formProt) {
867
- this.trackingContext.trackSectionChange(this, 'formProt', prev, formProt);
868
- }
869
- return this;
870
- }
871
-
872
- /**
873
- * Sets printer settings relationship ID
874
- * Per ECMA-376 Part 1 §17.6.6
875
- */
876
- setPrinterSettings(rId: string): this {
877
- const prev = this.properties.printerSettingsId;
878
- this.properties.printerSettingsId = rId;
879
- if (this.trackingContext?.isEnabled() && prev !== rId) {
880
- this.trackingContext.trackSectionChange(this, 'printerSettings', prev, rId);
881
- }
882
- return this;
883
- }
884
-
885
- /**
886
- * Sets chapter numbering for page numbers
887
- * Per ECMA-376 Part 1 §17.6.12
888
- * @param chapStyle - Heading level (1-9) to use for chapter numbering
889
- * @param chapSep - Separator between chapter and page number
890
- */
891
- setChapterNumbering(chapStyle: number, chapSep?: ChapterSeparator): this {
892
- const prevStyle = this.properties.chapStyle;
893
- const prevSep = this.properties.chapSep;
894
- this.properties.chapStyle = chapStyle;
895
- if (chapSep) this.properties.chapSep = chapSep;
896
- if (this.trackingContext?.isEnabled()) {
897
- this.trackingContext.trackSectionChange(this, 'chapterNumbering',
898
- { chapStyle: prevStyle, chapSep: prevSep },
899
- { chapStyle, chapSep: chapSep || prevSep });
900
- }
901
- return this;
902
- }
903
-
904
- /**
905
- * Generates WordprocessingML XML for section properties
906
- */
907
- toXML(): XMLElement {
908
- const children: XMLElement[] = [];
909
-
910
- // Header references
911
- if (this.properties.headers) {
912
- if (this.properties.headers.first) {
913
- children.push(
914
- XMLBuilder.wSelf('headerReference', {
915
- 'w:type': 'first',
916
- 'r:id': this.properties.headers.first,
917
- })
918
- );
919
- }
920
- if (this.properties.headers.even) {
921
- children.push(
922
- XMLBuilder.wSelf('headerReference', {
923
- 'w:type': 'even',
924
- 'r:id': this.properties.headers.even,
925
- })
926
- );
927
- }
928
- if (this.properties.headers.default) {
929
- children.push(
930
- XMLBuilder.wSelf('headerReference', {
931
- 'w:type': 'default',
932
- 'r:id': this.properties.headers.default,
933
- })
934
- );
935
- }
936
- }
937
-
938
- // Footer references
939
- if (this.properties.footers) {
940
- if (this.properties.footers.first) {
941
- children.push(
942
- XMLBuilder.wSelf('footerReference', {
943
- 'w:type': 'first',
944
- 'r:id': this.properties.footers.first,
945
- })
946
- );
947
- }
948
- if (this.properties.footers.even) {
949
- children.push(
950
- XMLBuilder.wSelf('footerReference', {
951
- 'w:type': 'even',
952
- 'r:id': this.properties.footers.even,
953
- })
954
- );
955
- }
956
- if (this.properties.footers.default) {
957
- children.push(
958
- XMLBuilder.wSelf('footerReference', {
959
- 'w:type': 'default',
960
- 'r:id': this.properties.footers.default,
961
- })
962
- );
963
- }
964
- }
965
-
966
- // CT_SectPr element order per ECMA-376:
967
- // footnotePr → endnotePr → type → pgSz → pgMar → paperSrc → pgBorders →
968
- // lnNumType → pgNumType → cols → formProt → vAlign → noEndnote → titlePg →
969
- // textDirection bidi rtlGutter → docGrid → printerSettings → sectPrChange
970
-
971
- // Footnote properties (w:footnotePr)
972
- if (this.properties.footnotePr) {
973
- const fnChildren: XMLElement[] = [];
974
- if (this.properties.footnotePr.position) {
975
- fnChildren.push(XMLBuilder.wSelf('pos', { 'w:val': this.properties.footnotePr.position }));
976
- }
977
- if (this.properties.footnotePr.numberFormat) {
978
- fnChildren.push(XMLBuilder.wSelf('numFmt', { 'w:val': this.properties.footnotePr.numberFormat }));
979
- }
980
- if (this.properties.footnotePr.startNumber !== undefined) {
981
- fnChildren.push(XMLBuilder.wSelf('numStart', { 'w:val': this.properties.footnotePr.startNumber.toString() }));
982
- }
983
- if (this.properties.footnotePr.restart) {
984
- fnChildren.push(XMLBuilder.wSelf('numRestart', { 'w:val': this.properties.footnotePr.restart }));
985
- }
986
- if (fnChildren.length > 0) {
987
- children.push(XMLBuilder.w('footnotePr', undefined, fnChildren));
988
- }
989
- }
990
-
991
- // Endnote properties (w:endnotePr)
992
- if (this.properties.endnotePr) {
993
- const enChildren: XMLElement[] = [];
994
- if (this.properties.endnotePr.position) {
995
- enChildren.push(XMLBuilder.wSelf('pos', { 'w:val': this.properties.endnotePr.position }));
996
- }
997
- if (this.properties.endnotePr.numberFormat) {
998
- enChildren.push(XMLBuilder.wSelf('numFmt', { 'w:val': this.properties.endnotePr.numberFormat }));
999
- }
1000
- if (this.properties.endnotePr.startNumber !== undefined) {
1001
- enChildren.push(XMLBuilder.wSelf('numStart', { 'w:val': this.properties.endnotePr.startNumber.toString() }));
1002
- }
1003
- if (this.properties.endnotePr.restart) {
1004
- enChildren.push(XMLBuilder.wSelf('numRestart', { 'w:val': this.properties.endnotePr.restart }));
1005
- }
1006
- if (enChildren.length > 0) {
1007
- children.push(XMLBuilder.w('endnotePr', undefined, enChildren));
1008
- }
1009
- }
1010
-
1011
- // Section type
1012
- if (this.properties.type) {
1013
- children.push(
1014
- XMLBuilder.wSelf('type', { 'w:val': this.properties.type })
1015
- );
1016
- }
1017
-
1018
- // Page size
1019
- if (this.properties.pageSize) {
1020
- const attrs: Record<string, string> = {
1021
- 'w:w': this.properties.pageSize.width.toString(),
1022
- 'w:h': this.properties.pageSize.height.toString(),
1023
- };
1024
- if (this.properties.pageSize.orientation === 'landscape') {
1025
- attrs['w:orient'] = 'landscape';
1026
- }
1027
- children.push(XMLBuilder.wSelf('pgSz', attrs));
1028
- }
1029
-
1030
- // Margins
1031
- if (this.properties.margins) {
1032
- const attrs: Record<string, string> = {
1033
- 'w:top': this.properties.margins.top.toString(),
1034
- 'w:right': this.properties.margins.right.toString(),
1035
- 'w:bottom': this.properties.margins.bottom.toString(),
1036
- 'w:left': this.properties.margins.left.toString(),
1037
- };
1038
- attrs['w:header'] = (this.properties.margins.header ?? 720).toString();
1039
- attrs['w:footer'] = (this.properties.margins.footer ?? 720).toString();
1040
- if (this.properties.margins.gutter !== undefined) {
1041
- attrs['w:gutter'] = this.properties.margins.gutter.toString();
1042
- }
1043
- children.push(XMLBuilder.wSelf('pgMar', attrs));
1044
- }
1045
-
1046
- // Paper source
1047
- if (this.properties.paperSource) {
1048
- const attrs: Record<string, string> = {};
1049
- if (this.properties.paperSource.first !== undefined) {
1050
- attrs['w:first'] = this.properties.paperSource.first.toString();
1051
- }
1052
- if (this.properties.paperSource.other !== undefined) {
1053
- attrs['w:other'] = this.properties.paperSource.other.toString();
1054
- }
1055
- if (Object.keys(attrs).length > 0) {
1056
- children.push(XMLBuilder.wSelf('paperSrc', attrs));
1057
- }
1058
- }
1059
-
1060
- // Page borders per ECMA-376 Part 1 §17.6.10
1061
- if (this.properties.pageBorders) {
1062
- const pgBorders = this.properties.pageBorders;
1063
- const pgBordersAttrs: Record<string, string> = {};
1064
- if (pgBorders.offsetFrom) pgBordersAttrs['w:offsetFrom'] = pgBorders.offsetFrom;
1065
- if (pgBorders.display) pgBordersAttrs['w:display'] = pgBorders.display;
1066
- if (pgBorders.zOrder) pgBordersAttrs['w:zOrder'] = pgBorders.zOrder;
1067
-
1068
- const borderChildren: XMLElement[] = [];
1069
- const buildBorder = (side: string, def: PageBorderDef) => {
1070
- const bAttrs: Record<string, string | number> = {};
1071
- if (def.style) bAttrs['w:val'] = def.style;
1072
- // ECMA-376 Part 1 §17.18.2: sz valid range 2-96 (eighths of a point)
1073
- if (def.size !== undefined) bAttrs['w:sz'] = Math.max(2, Math.min(96, def.size));
1074
- if (def.color) bAttrs['w:color'] = def.color;
1075
- // ECMA-376 Part 1 §17.18.88: space valid range 0-31680 (points)
1076
- if (def.space !== undefined) bAttrs['w:space'] = Math.max(0, Math.min(31680, def.space));
1077
- if (def.shadow) bAttrs['w:shadow'] = '1';
1078
- if (def.frame) bAttrs['w:frame'] = '1';
1079
- if (def.themeColor) bAttrs['w:themeColor'] = def.themeColor;
1080
- if (def.artId !== undefined) bAttrs['w:id'] = def.artId;
1081
- borderChildren.push(XMLBuilder.wSelf(side, bAttrs));
1082
- };
1083
-
1084
- if (pgBorders.top) buildBorder('top', pgBorders.top);
1085
- if (pgBorders.left) buildBorder('left', pgBorders.left);
1086
- if (pgBorders.bottom) buildBorder('bottom', pgBorders.bottom);
1087
- if (pgBorders.right) buildBorder('right', pgBorders.right);
1088
-
1089
- children.push(XMLBuilder.w('pgBorders', pgBordersAttrs, borderChildren));
1090
- }
1091
-
1092
- // Line numbering (w:lnNumType)
1093
- if (this.properties.lineNumbering) {
1094
- const attrs: Record<string, string> = {};
1095
- if (this.properties.lineNumbering.countBy !== undefined) {
1096
- attrs['w:countBy'] = this.properties.lineNumbering.countBy.toString();
1097
- }
1098
- if (this.properties.lineNumbering.start !== undefined) {
1099
- attrs['w:start'] = this.properties.lineNumbering.start.toString();
1100
- }
1101
- if (this.properties.lineNumbering.distance !== undefined) {
1102
- attrs['w:distance'] = this.properties.lineNumbering.distance.toString();
1103
- }
1104
- if (this.properties.lineNumbering.restart) {
1105
- attrs['w:restart'] = this.properties.lineNumbering.restart;
1106
- }
1107
- if (Object.keys(attrs).length > 0) {
1108
- children.push(XMLBuilder.wSelf('lnNumType', attrs));
1109
- }
1110
- }
1111
-
1112
- // Page numbering
1113
- if (this.properties.pageNumbering || this.properties.chapStyle !== undefined) {
1114
- const attrs: Record<string, string> = {};
1115
- if (this.properties.pageNumbering?.start !== undefined) {
1116
- attrs['w:start'] = this.properties.pageNumbering.start.toString();
1117
- }
1118
- if (this.properties.pageNumbering?.format) {
1119
- attrs['w:fmt'] = this.properties.pageNumbering.format;
1120
- }
1121
- if (this.properties.chapStyle !== undefined) {
1122
- attrs['w:chapStyle'] = this.properties.chapStyle.toString();
1123
- }
1124
- if (this.properties.chapSep) {
1125
- attrs['w:chapSep'] = this.properties.chapSep;
1126
- }
1127
- if (Object.keys(attrs).length > 0) {
1128
- children.push(XMLBuilder.wSelf('pgNumType', attrs));
1129
- }
1130
- }
1131
-
1132
- // Columns
1133
- if (this.properties.columns) {
1134
- const attrs: Record<string, string> = {
1135
- 'w:num': this.properties.columns.count.toString(),
1136
- };
1137
- if (this.properties.columns.space !== undefined) {
1138
- attrs['w:space'] = this.properties.columns.space.toString();
1139
- }
1140
- if (this.properties.columns.equalWidth !== undefined) {
1141
- attrs['w:equalWidth'] = this.properties.columns.equalWidth ? '1' : '0';
1142
- }
1143
- if (this.properties.columns.separator !== undefined) {
1144
- attrs['w:sep'] = this.properties.columns.separator ? '1' : '0';
1145
- }
1146
-
1147
- const colChildren: XMLElement[] = [];
1148
- if (this.properties.columns.columnWidths) {
1149
- for (const width of this.properties.columns.columnWidths) {
1150
- colChildren.push(
1151
- XMLBuilder.wSelf('col', { 'w:w': width.toString() })
1152
- );
1153
- }
1154
- }
1155
-
1156
- children.push(
1157
- colChildren.length > 0
1158
- ? XMLBuilder.w('cols', attrs, colChildren)
1159
- : XMLBuilder.wSelf('cols', attrs)
1160
- );
1161
- }
1162
-
1163
- // Form protection (w:formProt)
1164
- if (this.properties.formProt) {
1165
- children.push(XMLBuilder.wSelf('formProt'));
1166
- }
1167
-
1168
- // Vertical alignment
1169
- if (this.properties.verticalAlignment) {
1170
- children.push(
1171
- XMLBuilder.wSelf('vAlign', { 'w:val': this.properties.verticalAlignment })
1172
- );
1173
- }
1174
-
1175
- // Suppress endnotes (w:noEndnote)
1176
- if (this.properties.noEndnote) {
1177
- children.push(XMLBuilder.wSelf('noEndnote'));
1178
- }
1179
-
1180
- // Title page
1181
- if (this.properties.titlePage) {
1182
- children.push(XMLBuilder.wSelf('titlePg', { 'w:val': '1' }));
1183
- }
1184
-
1185
- // Text direction (map to valid ST_TextDirection values per ECMA-376 §17.18.93)
1186
- if (this.properties.textDirection) {
1187
- const textDirMap: Record<string, string> = {
1188
- ltr: 'lrTb', rtl: 'tbRl', tbRl: 'tbRl', btLr: 'btLr',
1189
- lrTb: 'lrTb', lrTbV: 'lrTbV', tbRlV: 'tbRlV', tbLrV: 'tbLrV',
1190
- };
1191
- const val = textDirMap[this.properties.textDirection] || this.properties.textDirection;
1192
- children.push(
1193
- XMLBuilder.wSelf('textDirection', { 'w:val': val })
1194
- );
1195
- }
1196
-
1197
- // Bidirectional section (RTL)
1198
- if (this.properties.bidi) {
1199
- children.push(XMLBuilder.wSelf('bidi'));
1200
- }
1201
-
1202
- // RTL gutter (gutter on right side)
1203
- if (this.properties.rtlGutter) {
1204
- children.push(XMLBuilder.wSelf('rtlGutter'));
1205
- }
1206
-
1207
- // Document grid
1208
- if (this.properties.docGrid) {
1209
- const attrs: Record<string, string> = {};
1210
- if (this.properties.docGrid.type) {
1211
- attrs['w:type'] = this.properties.docGrid.type;
1212
- }
1213
- if (this.properties.docGrid.linePitch !== undefined) {
1214
- attrs['w:linePitch'] = this.properties.docGrid.linePitch.toString();
1215
- }
1216
- if (this.properties.docGrid.charSpace !== undefined) {
1217
- attrs['w:charSpace'] = this.properties.docGrid.charSpace.toString();
1218
- }
1219
- if (Object.keys(attrs).length > 0) {
1220
- children.push(XMLBuilder.wSelf('docGrid', attrs));
1221
- }
1222
- }
1223
-
1224
- // Printer settings (w:printerSettings)
1225
- if (this.properties.printerSettingsId) {
1226
- children.push(XMLBuilder.wSelf('printerSettings', {
1227
- 'r:id': this.properties.printerSettingsId,
1228
- }));
1229
- }
1230
-
1231
- // Add section property change (w:sectPrChange) per ECMA-376 Part 1 §17.13.5.32
1232
- // Must be last child of w:sectPr
1233
- if (this.sectPrChange) {
1234
- const changeAttrs: Record<string, string | number> = {
1235
- 'w:id': this.sectPrChange.id,
1236
- 'w:author': this.sectPrChange.author,
1237
- 'w:date': this.sectPrChange.date,
1238
- };
1239
- const prevChildren: XMLElement[] = [];
1240
- const prev = this.sectPrChange.previousProperties;
1241
- if (prev) {
1242
- // Ordered per CT_SectPrBase:
1243
- // type → pgSz → pgMar → lnNumType → pgNumType → cols → formProt → vAlign → titlePg → textDirection
1244
- if (prev.type) {
1245
- prevChildren.push(XMLBuilder.wSelf('type', { 'w:val': prev.type }));
1246
- }
1247
- if (prev.pageSize) {
1248
- const pgSzAttrs: Record<string, string> = {
1249
- 'w:w': prev.pageSize.width?.toString() || '12240',
1250
- 'w:h': prev.pageSize.height?.toString() || '15840',
1251
- };
1252
- if (prev.pageSize.orientation === 'landscape') {
1253
- pgSzAttrs['w:orient'] = 'landscape';
1254
- }
1255
- prevChildren.push(XMLBuilder.wSelf('pgSz', pgSzAttrs));
1256
- }
1257
- if (prev.margins) {
1258
- const pgMarAttrs: Record<string, string> = {};
1259
- if (prev.margins.top !== undefined) pgMarAttrs['w:top'] = prev.margins.top.toString();
1260
- if (prev.margins.bottom !== undefined) pgMarAttrs['w:bottom'] = prev.margins.bottom.toString();
1261
- if (prev.margins.left !== undefined) pgMarAttrs['w:left'] = prev.margins.left.toString();
1262
- if (prev.margins.right !== undefined) pgMarAttrs['w:right'] = prev.margins.right.toString();
1263
- if (prev.margins.header !== undefined) pgMarAttrs['w:header'] = prev.margins.header.toString();
1264
- if (prev.margins.footer !== undefined) pgMarAttrs['w:footer'] = prev.margins.footer.toString();
1265
- prevChildren.push(XMLBuilder.wSelf('pgMar', pgMarAttrs));
1266
- }
1267
- if (prev.lineNumbering) {
1268
- const lnAttrs: Record<string, string> = {};
1269
- if (prev.lineNumbering.countBy !== undefined) lnAttrs['w:countBy'] = prev.lineNumbering.countBy.toString();
1270
- if (prev.lineNumbering.start !== undefined) lnAttrs['w:start'] = prev.lineNumbering.start.toString();
1271
- if (prev.lineNumbering.restart) lnAttrs['w:restart'] = prev.lineNumbering.restart;
1272
- if (prev.lineNumbering.distance !== undefined) lnAttrs['w:distance'] = prev.lineNumbering.distance.toString();
1273
- if (Object.keys(lnAttrs).length > 0) {
1274
- prevChildren.push(XMLBuilder.wSelf('lnNumType', lnAttrs));
1275
- }
1276
- }
1277
- if (prev.pageNumbering) {
1278
- const pnAttrs: Record<string, string> = {};
1279
- if (prev.pageNumbering.start !== undefined) pnAttrs['w:start'] = prev.pageNumbering.start.toString();
1280
- if (prev.pageNumbering.format) pnAttrs['w:fmt'] = prev.pageNumbering.format;
1281
- if (Object.keys(pnAttrs).length > 0) {
1282
- prevChildren.push(XMLBuilder.wSelf('pgNumType', pnAttrs));
1283
- }
1284
- }
1285
- if (prev.columns) {
1286
- const colAttrs: Record<string, string> = { 'w:num': prev.columns.count?.toString() || '1' };
1287
- if (prev.columns.space !== undefined) colAttrs['w:space'] = prev.columns.space.toString();
1288
- prevChildren.push(XMLBuilder.wSelf('cols', colAttrs));
1289
- }
1290
- if (prev.formProt) {
1291
- prevChildren.push(XMLBuilder.wSelf('formProt'));
1292
- }
1293
- if (prev.verticalAlignment) {
1294
- prevChildren.push(XMLBuilder.wSelf('vAlign', { 'w:val': prev.verticalAlignment }));
1295
- }
1296
- if (prev.titlePage) {
1297
- prevChildren.push(XMLBuilder.wSelf('titlePg'));
1298
- }
1299
- if (prev.textDirection) {
1300
- const tdMap: Record<string, string> = {
1301
- ltr: 'lrTb', rtl: 'tbRl', tbRl: 'tbRl', btLr: 'btLr',
1302
- lrTb: 'lrTb', lrTbV: 'lrTbV', tbRlV: 'tbRlV', tbLrV: 'tbLrV',
1303
- };
1304
- prevChildren.push(XMLBuilder.wSelf('textDirection', { 'w:val': tdMap[prev.textDirection] || prev.textDirection }));
1305
- }
1306
- }
1307
- const prevSectPr = XMLBuilder.w('sectPr', undefined, prevChildren);
1308
- children.push(XMLBuilder.w('sectPrChange', changeAttrs, [prevSectPr]));
1309
- }
1310
-
1311
- return XMLBuilder.w('sectPr', undefined, children);
1312
- }
1313
-
1314
- /**
1315
- * Creates a deep clone of this section
1316
- * @returns New Section instance with copied properties
1317
- */
1318
- clone(): Section {
1319
- // Deep clone all nested objects
1320
- const clonedProperties: SectionProperties = {};
1321
-
1322
- if (this.properties.pageSize) {
1323
- clonedProperties.pageSize = { ...this.properties.pageSize };
1324
- }
1325
-
1326
- if (this.properties.margins) {
1327
- clonedProperties.margins = { ...this.properties.margins };
1328
- }
1329
-
1330
- if (this.properties.columns) {
1331
- clonedProperties.columns = {
1332
- ...this.properties.columns,
1333
- columnWidths: this.properties.columns.columnWidths
1334
- ? [...this.properties.columns.columnWidths]
1335
- : undefined,
1336
- };
1337
- }
1338
-
1339
- if (this.properties.pageNumbering) {
1340
- clonedProperties.pageNumbering = { ...this.properties.pageNumbering };
1341
- }
1342
-
1343
- if (this.properties.headers) {
1344
- clonedProperties.headers = { ...this.properties.headers };
1345
- }
1346
-
1347
- if (this.properties.footers) {
1348
- clonedProperties.footers = { ...this.properties.footers };
1349
- }
1350
-
1351
- if (this.properties.paperSource) {
1352
- clonedProperties.paperSource = { ...this.properties.paperSource };
1353
- }
1354
-
1355
- if (this.properties.docGrid) {
1356
- clonedProperties.docGrid = { ...this.properties.docGrid };
1357
- }
1358
-
1359
- if (this.properties.lineNumbering) {
1360
- clonedProperties.lineNumbering = { ...this.properties.lineNumbering };
1361
- }
1362
-
1363
- // Copy primitive properties
1364
- clonedProperties.type = this.properties.type;
1365
- clonedProperties.titlePage = this.properties.titlePage;
1366
- clonedProperties.verticalAlignment = this.properties.verticalAlignment;
1367
- clonedProperties.textDirection = this.properties.textDirection;
1368
- clonedProperties.bidi = this.properties.bidi;
1369
- clonedProperties.rtlGutter = this.properties.rtlGutter;
1370
-
1371
- return new Section(clonedProperties);
1372
- }
1373
-
1374
- /**
1375
- * Creates a section with default properties
1376
- */
1377
- static create(properties?: SectionProperties): Section {
1378
- return new Section(properties);
1379
- }
1380
-
1381
- /**
1382
- * Creates a letter-sized section (8.5" x 11")
1383
- */
1384
- static createLetter(): Section {
1385
- return new Section({
1386
- pageSize: {
1387
- width: PAGE_SIZES.LETTER.width,
1388
- height: PAGE_SIZES.LETTER.height,
1389
- orientation: 'portrait',
1390
- },
1391
- });
1392
- }
1393
-
1394
- /**
1395
- * Creates an A4-sized section (21cm x 29.7cm)
1396
- */
1397
- static createA4(): Section {
1398
- return new Section({
1399
- pageSize: {
1400
- width: PAGE_SIZES.A4.width,
1401
- height: PAGE_SIZES.A4.height,
1402
- orientation: 'portrait',
1403
- },
1404
- });
1405
- }
1406
-
1407
- /**
1408
- * Creates a landscape section
1409
- * @param pageSize Page size (default: Letter)
1410
- */
1411
- static createLandscape(pageSize: 'letter' | 'a4' = 'letter'): Section {
1412
- const size = pageSize === 'a4' ? PAGE_SIZES.A4 : PAGE_SIZES.LETTER;
1413
- return new Section({
1414
- pageSize: {
1415
- width: size.height, // Swap for landscape
1416
- height: size.width,
1417
- orientation: 'landscape',
1418
- },
1419
- });
1420
- }
1421
- }
1
+ /**
2
+ * Section - Represents a document section with page setup properties
3
+ *
4
+ * Sections allow different page setups within a single document (margins, orientation, etc.)
5
+ * Each section can have its own headers, footers, and page numbering.
6
+ */
7
+
8
+ import { XMLBuilder, XMLElement } from '../xml/XMLBuilder';
9
+ import { PAGE_SIZES } from '../utils/units';
10
+
11
+ /**
12
+ * Page orientation
13
+ */
14
+ export type PageOrientation = 'portrait' | 'landscape';
15
+
16
+ /**
17
+ * Section break type
18
+ */
19
+ export type SectionType = 'nextPage' | 'continuous' | 'evenPage' | 'oddPage' | 'nextColumn';
20
+
21
+ /**
22
+ * Page numbering format
23
+ */
24
+ /**
25
+ * Per ECMA-376 Part 1 §17.18.59 (ST_NumberFormat)
26
+ * Comprehensive set of page number formats
27
+ */
28
+ export type PageNumberFormat =
29
+ | 'decimal'
30
+ | 'lowerRoman'
31
+ | 'upperRoman'
32
+ | 'lowerLetter'
33
+ | 'upperLetter'
34
+ | 'ordinal'
35
+ | 'cardinalText'
36
+ | 'ordinalText'
37
+ | 'hex'
38
+ | 'chicago'
39
+ | 'decimalZero'
40
+ | 'decimalEnclosedCircle'
41
+ | 'decimalEnclosedFullstop'
42
+ | 'decimalEnclosedParen'
43
+ | 'ideographDigital'
44
+ | 'ideographTraditional'
45
+ | 'ideographLegalTraditional'
46
+ | 'ideographEnclosedCircle'
47
+ | 'ideographZodiac'
48
+ | 'ideographZodiacTraditional'
49
+ | 'chineseCounting'
50
+ | 'chineseCountingThousand'
51
+ | 'chineseLegalSimplified'
52
+ | 'japaneseCounting'
53
+ | 'japaneseDigitalTenThousand'
54
+ | 'japaneseLegal'
55
+ | 'koreanCounting'
56
+ | 'koreanDigital'
57
+ | 'koreanDigital2'
58
+ | 'koreanLegal'
59
+ | 'taiwaneseCounting'
60
+ | 'taiwaneseCountingThousand'
61
+ | 'taiwaneseDigital'
62
+ | 'aiueo'
63
+ | 'aiueoFullWidth'
64
+ | 'iroha'
65
+ | 'irohaFullWidth'
66
+ | 'arabicAbjad'
67
+ | 'arabicAlpha'
68
+ | 'hebrew1'
69
+ | 'hebrew2'
70
+ | 'hindiConsonants'
71
+ | 'hindiCounting'
72
+ | 'hindiNumbers'
73
+ | 'hindiVowels'
74
+ | 'thaiCounting'
75
+ | 'thaiLetters'
76
+ | 'thaiNumbers'
77
+ | 'vietnameseCounting'
78
+ | 'russianLower'
79
+ | 'russianUpper'
80
+ | 'numberInDash'
81
+ | 'dollarText'
82
+ | 'bullet'
83
+ | 'bahtText'
84
+ | 'ganada'
85
+ | 'chosung'
86
+ | 'none'
87
+ | string; // Allow any format string for forward compatibility
88
+
89
+ /**
90
+ * Page size properties
91
+ */
92
+ export interface PageSize {
93
+ /** Width in twips */
94
+ width: number;
95
+ /** Height in twips */
96
+ height: number;
97
+ /** Orientation */
98
+ orientation?: PageOrientation;
99
+ }
100
+
101
+ /**
102
+ * Margin properties
103
+ */
104
+ export interface Margins {
105
+ /** Top margin in twips */
106
+ top: number;
107
+ /** Bottom margin in twips */
108
+ bottom: number;
109
+ /** Left margin in twips */
110
+ left: number;
111
+ /** Right margin in twips */
112
+ right: number;
113
+ /** Header distance from top in twips */
114
+ header?: number;
115
+ /** Footer distance from bottom in twips */
116
+ footer?: number;
117
+ /** Gutter margin in twips */
118
+ gutter?: number;
119
+ }
120
+
121
+ /**
122
+ * Column properties
123
+ */
124
+ export interface Columns {
125
+ /** Number of columns */
126
+ count: number;
127
+ /** Space between columns in twips */
128
+ space?: number;
129
+ /** Equal column widths */
130
+ equalWidth?: boolean;
131
+ /** Show column separator line */
132
+ separator?: boolean;
133
+ /** Individual column widths (for unequal columns) in twips */
134
+ columnWidths?: number[];
135
+ }
136
+
137
+ /**
138
+ * Page numbering properties
139
+ */
140
+ export interface PageNumbering {
141
+ /** Starting page number */
142
+ start?: number;
143
+ /** Number format */
144
+ format?: PageNumberFormat;
145
+ }
146
+
147
+ /**
148
+ * Paper source (printer tray) properties
149
+ */
150
+ export interface PaperSource {
151
+ /** First page tray number */
152
+ first?: number;
153
+ /** Other pages tray number */
154
+ other?: number;
155
+ }
156
+
157
+ /**
158
+ * Vertical page alignment
159
+ */
160
+ export type VerticalAlignment = 'top' | 'center' | 'bottom' | 'both';
161
+
162
+ /**
163
+ * Text direction
164
+ */
165
+ export type TextDirection = 'ltr' | 'rtl' | 'tbRl' | 'btLr';
166
+
167
+ /**
168
+ * Document grid type for East Asian typography
169
+ */
170
+ export type DocumentGridType = 'default' | 'lines' | 'linesAndChars' | 'snapToChars';
171
+
172
+ /**
173
+ * Document grid properties
174
+ */
175
+ export interface DocumentGrid {
176
+ /** Grid type */
177
+ type?: DocumentGridType;
178
+ /** Lines per page */
179
+ linePitch?: number;
180
+ /** Characters per line */
181
+ charSpace?: number;
182
+ }
183
+
184
+ /**
185
+ * Line numbering restart mode
186
+ */
187
+ export type LineNumberingRestart = 'newPage' | 'newSection' | 'continuous';
188
+
189
+ /**
190
+ * Line numbering properties
191
+ * Per ECMA-376 Part 1, Section 17.6.8 (w:lnNumType element)
192
+ */
193
+ export interface LineNumbering {
194
+ /** Display line number every N lines (countBy attribute) */
195
+ countBy?: number;
196
+ /** Starting line number */
197
+ start?: number;
198
+ /** Distance from text margin in twips */
199
+ distance?: number;
200
+ /** When to restart line numbering */
201
+ restart?: LineNumberingRestart;
202
+ }
203
+
204
+ /**
205
+ * Footnote/endnote position
206
+ * Per ECMA-376 Part 1 §17.11.6 and §17.11.7
207
+ */
208
+ export type NotePosition = 'pageBottom' | 'beneathText' | 'sectEnd' | 'docEnd';
209
+
210
+ /**
211
+ * Note numbering restart mode
212
+ */
213
+ export type NoteNumberRestart = 'continuous' | 'eachSect' | 'eachPage';
214
+
215
+ /**
216
+ * Chapter separator character for page numbering
217
+ * Per ECMA-376 Part 1 §17.18.5
218
+ */
219
+ export type ChapterSeparator = 'colon' | 'emDash' | 'enDash' | 'hyphen' | 'period';
220
+
221
+ /**
222
+ * Footnote/endnote section-level properties
223
+ */
224
+ export interface NoteProperties {
225
+ /** Note positioning */
226
+ position?: NotePosition;
227
+ /** Number format */
228
+ numberFormat?: PageNumberFormat;
229
+ /** Starting number */
230
+ startNumber?: number;
231
+ /** Restart numbering mode */
232
+ restart?: NoteNumberRestart;
233
+ }
234
+
235
+ /**
236
+ * Page border definition for a single side
237
+ * Per ECMA-376 Part 1 §17.6.10
238
+ */
239
+ export interface PageBorderDef {
240
+ /** Border style (single, double, dashed, etc.) */
241
+ style?: string;
242
+ /** Border size in eighths of a point */
243
+ size?: number;
244
+ /** Border color in hex format (without #) */
245
+ color?: string;
246
+ /** Space between border and page edge/text in points */
247
+ space?: number;
248
+ /** Whether to show shadow */
249
+ shadow?: boolean;
250
+ /** Whether to include frame around page */
251
+ frame?: boolean;
252
+ /** Theme color reference */
253
+ themeColor?: string;
254
+ /** Art border ID (for decorative borders) */
255
+ artId?: number;
256
+ }
257
+
258
+ /**
259
+ * Page borders configuration
260
+ * Per ECMA-376 Part 1 §17.6.10
261
+ */
262
+ export interface PageBorders {
263
+ /** Top page border */
264
+ top?: PageBorderDef;
265
+ /** Bottom page border */
266
+ bottom?: PageBorderDef;
267
+ /** Left page border */
268
+ left?: PageBorderDef;
269
+ /** Right page border */
270
+ right?: PageBorderDef;
271
+ /** Whether border is measured from page edge or text edge */
272
+ offsetFrom?: 'page' | 'text';
273
+ /** Display on all pages, first page only, or not first page */
274
+ display?: 'allPages' | 'firstPage' | 'notFirstPage';
275
+ /** Z-ordering relative to text */
276
+ zOrder?: 'front' | 'back';
277
+ }
278
+
279
+ /**
280
+ * Section properties
281
+ */
282
+ export interface SectionProperties {
283
+ /** Page size */
284
+ pageSize?: PageSize;
285
+ /** Margins */
286
+ margins?: Margins;
287
+ /** Column layout */
288
+ columns?: Columns;
289
+ /** Section break type */
290
+ type?: SectionType;
291
+ /** Page numbering */
292
+ pageNumbering?: PageNumbering;
293
+ /** Header reference IDs */
294
+ headers?: {
295
+ default?: string; // rId for default header
296
+ first?: string; // rId for first page header
297
+ even?: string; // rId for even page header
298
+ };
299
+ /** Footer reference IDs */
300
+ footers?: {
301
+ default?: string; // rId for default footer
302
+ first?: string; // rId for first page footer
303
+ even?: string; // rId for even page footer
304
+ };
305
+ /** Title page (different first page) */
306
+ titlePage?: boolean;
307
+ /** Vertical page alignment */
308
+ verticalAlignment?: VerticalAlignment;
309
+ /** Paper source (printer tray) */
310
+ paperSource?: PaperSource;
311
+ /** Text direction (LTR/RTL support) */
312
+ textDirection?: TextDirection;
313
+ /** Right-to-left section layout */
314
+ bidi?: boolean;
315
+ /** Gutter on right side (for RTL) */
316
+ rtlGutter?: boolean;
317
+ /** Document grid for snapping text to grid */
318
+ docGrid?: DocumentGrid;
319
+ /** Line numbering configuration */
320
+ lineNumbering?: LineNumbering;
321
+ /** Section-level footnote properties (w:footnotePr) */
322
+ footnotePr?: NoteProperties;
323
+ /** Section-level endnote properties (w:endnotePr) */
324
+ endnotePr?: NoteProperties;
325
+ /** Suppress endnotes in this section (w:noEndnote) */
326
+ noEndnote?: boolean;
327
+ /** Form protection for this section (w:formProt) */
328
+ formProt?: boolean;
329
+ /** Page borders per ECMA-376 Part 1 §17.6.10 */
330
+ pageBorders?: PageBorders;
331
+ /** Printer settings relationship ID (w:printerSettings r:id) */
332
+ printerSettingsId?: string;
333
+ /** Chapter style heading level for page numbering (w:pgNumType w:chapStyle) */
334
+ chapStyle?: number;
335
+ /** Chapter separator for page numbering (w:pgNumType w:chapSep) */
336
+ chapSep?: ChapterSeparator;
337
+ }
338
+
339
+ /**
340
+ * Represents a document section
341
+ */
342
+ /**
343
+ * Section property change tracking (w:sectPrChange)
344
+ * Per ECMA-376 Part 1 §17.13.5.32
345
+ */
346
+ export interface SectPrChange {
347
+ author: string;
348
+ date: string;
349
+ id: string;
350
+ previousProperties: Record<string, any>;
351
+ }
352
+
353
+ export class Section {
354
+ private properties: SectionProperties;
355
+ /** Tracking context for automatic change tracking */
356
+ private trackingContext?: import('../tracking/TrackingContext').TrackingContext;
357
+ /** Section property change tracking (w:sectPrChange) */
358
+ private sectPrChange?: SectPrChange;
359
+
360
+ /**
361
+ * Creates a new section
362
+ * @param properties Section properties
363
+ */
364
+ constructor(properties: SectionProperties = {}) {
365
+ // Set defaults only where necessary
366
+ this.properties = {
367
+ pageSize: properties.pageSize || {
368
+ width: PAGE_SIZES.LETTER.width,
369
+ height: PAGE_SIZES.LETTER.height,
370
+ orientation: 'portrait',
371
+ },
372
+ margins: properties.margins || {
373
+ top: 1440, // 1 inch
374
+ bottom: 1440,
375
+ left: 1440,
376
+ right: 1440,
377
+ header: 720, // 0.5 inch
378
+ footer: 720,
379
+ },
380
+ // Default to single column layout
381
+ columns: properties.columns || {
382
+ count: 1,
383
+ },
384
+ // Default to next page section break
385
+ type: properties.type || 'nextPage',
386
+ pageNumbering: properties.pageNumbering,
387
+ headers: properties.headers,
388
+ footers: properties.footers,
389
+ titlePage: properties.titlePage,
390
+ // Phase 4.5 - New properties
391
+ verticalAlignment: properties.verticalAlignment,
392
+ paperSource: properties.paperSource,
393
+ textDirection: properties.textDirection,
394
+ pageBorders: properties.pageBorders,
395
+ };
396
+ }
397
+
398
+ /**
399
+ * Sets the tracking context for automatic change tracking.
400
+ * Called by Document when track changes is enabled.
401
+ * @internal
402
+ */
403
+ _setTrackingContext(context: import('../tracking/TrackingContext').TrackingContext): void {
404
+ this.trackingContext = context;
405
+ }
406
+
407
+ /**
408
+ * Gets the section property change tracking info
409
+ */
410
+ getSectPrChange(): SectPrChange | undefined {
411
+ return this.sectPrChange;
412
+ }
413
+
414
+ /**
415
+ * Sets the section property change tracking info
416
+ */
417
+ setSectPrChange(change: SectPrChange | undefined): void {
418
+ this.sectPrChange = change;
419
+ }
420
+
421
+ /**
422
+ * Clears the section property change tracking
423
+ */
424
+ clearSectPrChange(): void {
425
+ this.sectPrChange = undefined;
426
+ }
427
+
428
+ /**
429
+ * Gets the section properties
430
+ */
431
+ getProperties(): SectionProperties {
432
+ return { ...this.properties };
433
+ }
434
+
435
+ // ============================================================================
436
+ // Individual Property Getters
437
+ // ============================================================================
438
+
439
+ /**
440
+ * Gets the page size settings
441
+ * @returns Page size object with width, height, and orientation, or undefined
442
+ */
443
+ getPageSize(): PageSize | undefined {
444
+ return this.properties.pageSize ? { ...this.properties.pageSize } : undefined;
445
+ }
446
+
447
+ /**
448
+ * Gets the page orientation
449
+ * @returns 'portrait' or 'landscape', or undefined if not set
450
+ */
451
+ getOrientation(): PageOrientation | undefined {
452
+ return this.properties.pageSize?.orientation;
453
+ }
454
+
455
+ /**
456
+ * Gets the margin settings
457
+ * @returns Margins object or undefined if not set
458
+ */
459
+ getMargins(): Margins | undefined {
460
+ return this.properties.margins ? { ...this.properties.margins } : undefined;
461
+ }
462
+
463
+ /**
464
+ * Gets the column layout settings
465
+ * @returns Columns object or undefined if not set
466
+ */
467
+ getColumns(): Columns | undefined {
468
+ return this.properties.columns ? { ...this.properties.columns } : undefined;
469
+ }
470
+
471
+ /**
472
+ * Gets the section type (break type)
473
+ * @returns Section type or undefined if not set
474
+ */
475
+ getSectionType(): SectionType | undefined {
476
+ return this.properties.type;
477
+ }
478
+
479
+ /**
480
+ * Gets the page numbering settings
481
+ * @returns PageNumbering object or undefined if not set
482
+ */
483
+ getPageNumbering(): PageNumbering | undefined {
484
+ return this.properties.pageNumbering ? { ...this.properties.pageNumbering } : undefined;
485
+ }
486
+
487
+ /**
488
+ * Gets whether this section has a title page (different first page)
489
+ * @returns true if title page is enabled, false otherwise
490
+ */
491
+ getTitlePage(): boolean {
492
+ return this.properties.titlePage ?? false;
493
+ }
494
+
495
+ /**
496
+ * Gets header references
497
+ * @returns Object with default, first, and even header relationship IDs, or undefined
498
+ */
499
+ getHeaderReferences(): { default?: string; first?: string; even?: string } | undefined {
500
+ return this.properties.headers ? { ...this.properties.headers } : undefined;
501
+ }
502
+
503
+ /**
504
+ * Gets a specific header reference
505
+ * @param type Header type (default, first, even)
506
+ * @returns Relationship ID or undefined if not set
507
+ */
508
+ getHeaderReference(type: 'default' | 'first' | 'even'): string | undefined {
509
+ return this.properties.headers?.[type];
510
+ }
511
+
512
+ /**
513
+ * Gets footer references
514
+ * @returns Object with default, first, and even footer relationship IDs, or undefined
515
+ */
516
+ getFooterReferences(): { default?: string; first?: string; even?: string } | undefined {
517
+ return this.properties.footers ? { ...this.properties.footers } : undefined;
518
+ }
519
+
520
+ /**
521
+ * Gets a specific footer reference
522
+ * @param type Footer type (default, first, even)
523
+ * @returns Relationship ID or undefined if not set
524
+ */
525
+ getFooterReference(type: 'default' | 'first' | 'even'): string | undefined {
526
+ return this.properties.footers?.[type];
527
+ }
528
+
529
+ /**
530
+ * Gets the vertical page alignment
531
+ * @returns Vertical alignment or undefined if not set
532
+ */
533
+ getVerticalAlignment(): VerticalAlignment | undefined {
534
+ return this.properties.verticalAlignment;
535
+ }
536
+
537
+ /**
538
+ * Gets the paper source (printer tray) settings
539
+ * @returns PaperSource object or undefined if not set
540
+ */
541
+ getPaperSource(): PaperSource | undefined {
542
+ return this.properties.paperSource ? { ...this.properties.paperSource } : undefined;
543
+ }
544
+
545
+ /**
546
+ * Gets whether column separator is enabled
547
+ * @returns true if separator is enabled, false otherwise
548
+ */
549
+ getColumnSeparator(): boolean {
550
+ return this.properties.columns?.separator ?? false;
551
+ }
552
+
553
+ /**
554
+ * Gets custom column widths
555
+ * @returns Array of column widths in twips, or undefined if not set
556
+ */
557
+ getColumnWidths(): number[] | undefined {
558
+ return this.properties.columns?.columnWidths
559
+ ? [...this.properties.columns.columnWidths]
560
+ : undefined;
561
+ }
562
+
563
+ /**
564
+ * Gets the text direction
565
+ * @returns Text direction or undefined if not set
566
+ */
567
+ getTextDirection(): TextDirection | undefined {
568
+ return this.properties.textDirection;
569
+ }
570
+
571
+ /**
572
+ * Gets whether the section is bidirectional (RTL)
573
+ * @returns true if bidi is enabled, false otherwise
574
+ */
575
+ getBidi(): boolean {
576
+ return this.properties.bidi ?? false;
577
+ }
578
+
579
+ /**
580
+ * Gets whether RTL gutter is enabled (gutter on right side)
581
+ * @returns true if RTL gutter is enabled, false otherwise
582
+ */
583
+ getRtlGutter(): boolean {
584
+ return this.properties.rtlGutter ?? false;
585
+ }
586
+
587
+ /**
588
+ * Gets the document grid settings
589
+ * @returns DocumentGrid object or undefined if not set
590
+ */
591
+ getDocGrid(): DocumentGrid | undefined {
592
+ return this.properties.docGrid ? { ...this.properties.docGrid } : undefined;
593
+ }
594
+
595
+ // ============================================================================
596
+ // Setters
597
+ // ============================================================================
598
+
599
+ /**
600
+ * Sets page size
601
+ * @param width Width in twips
602
+ * @param height Height in twips
603
+ * @param orientation Page orientation
604
+ */
605
+ setPageSize(width: number, height: number, orientation: PageOrientation = 'portrait'): this {
606
+ const prev = this.properties.pageSize ? { ...this.properties.pageSize } : undefined;
607
+ this.properties.pageSize = { width, height, orientation };
608
+ if (this.trackingContext?.isEnabled()) {
609
+ this.trackingContext.trackSectionChange(this, 'pageSize', prev, this.properties.pageSize);
610
+ }
611
+ return this;
612
+ }
613
+
614
+ /**
615
+ * Sets page orientation
616
+ * @param orientation Page orientation
617
+ */
618
+ setOrientation(orientation: PageOrientation): this {
619
+ const prev = this.properties.pageSize?.orientation;
620
+ if (!this.properties.pageSize) {
621
+ this.properties.pageSize = {
622
+ width: PAGE_SIZES.LETTER.width,
623
+ height: PAGE_SIZES.LETTER.height,
624
+ };
625
+ }
626
+ this.properties.pageSize.orientation = orientation;
627
+
628
+ // Swap width/height for landscape
629
+ if (
630
+ orientation === 'landscape' &&
631
+ this.properties.pageSize.width < this.properties.pageSize.height
632
+ ) {
633
+ const temp = this.properties.pageSize.width;
634
+ this.properties.pageSize.width = this.properties.pageSize.height;
635
+ this.properties.pageSize.height = temp;
636
+ }
637
+
638
+ if (this.trackingContext?.isEnabled() && prev !== orientation) {
639
+ this.trackingContext.trackSectionChange(this, 'orientation', prev, orientation);
640
+ }
641
+ return this;
642
+ }
643
+
644
+ /**
645
+ * Sets margins
646
+ * @param margins Margin properties
647
+ */
648
+ setMargins(margins: Margins): this {
649
+ const prev = this.properties.margins ? { ...this.properties.margins } : undefined;
650
+ const existing = this.properties.margins;
651
+ this.properties.margins = {
652
+ top: margins.top,
653
+ bottom: margins.bottom,
654
+ left: margins.left,
655
+ right: margins.right,
656
+ header: margins.header ?? existing?.header ?? 720,
657
+ footer: margins.footer ?? existing?.footer ?? 720,
658
+ gutter: margins.gutter ?? existing?.gutter,
659
+ };
660
+ if (this.trackingContext?.isEnabled()) {
661
+ this.trackingContext.trackSectionChange(this, 'margins', prev, this.properties.margins);
662
+ }
663
+ return this;
664
+ }
665
+
666
+ /**
667
+ * Sets column layout
668
+ * @param count Number of columns
669
+ * @param space Space between columns in twips
670
+ */
671
+ setColumns(count: number, space = 720): this {
672
+ const prev = this.properties.columns ? { ...this.properties.columns } : undefined;
673
+ this.properties.columns = {
674
+ count,
675
+ space,
676
+ equalWidth: true,
677
+ };
678
+ if (this.trackingContext?.isEnabled()) {
679
+ this.trackingContext.trackSectionChange(this, 'columns', prev, this.properties.columns);
680
+ }
681
+ return this;
682
+ }
683
+
684
+ /**
685
+ * Sets section type
686
+ * @param type Section break type
687
+ */
688
+ setSectionType(type: SectionType): this {
689
+ const prev = this.properties.type;
690
+ this.properties.type = type;
691
+ if (this.trackingContext?.isEnabled() && prev !== type) {
692
+ this.trackingContext.trackSectionChange(this, 'type', prev, type);
693
+ }
694
+ return this;
695
+ }
696
+
697
+ /**
698
+ * Sets page numbering
699
+ * @param start Starting page number
700
+ * @param format Number format
701
+ */
702
+ setPageNumbering(start?: number, format?: PageNumberFormat): this {
703
+ const prev = this.properties.pageNumbering ? { ...this.properties.pageNumbering } : undefined;
704
+ this.properties.pageNumbering = { start, format };
705
+ if (this.trackingContext?.isEnabled()) {
706
+ this.trackingContext.trackSectionChange(
707
+ this,
708
+ 'pageNumbering',
709
+ prev,
710
+ this.properties.pageNumbering
711
+ );
712
+ }
713
+ return this;
714
+ }
715
+
716
+ /**
717
+ * Sets title page flag (different first page)
718
+ * @param titlePage Whether this section has a different first page
719
+ */
720
+ setTitlePage(titlePage = true): this {
721
+ const prev = this.properties.titlePage;
722
+ this.properties.titlePage = titlePage;
723
+ if (this.trackingContext?.isEnabled() && prev !== titlePage) {
724
+ this.trackingContext.trackSectionChange(this, 'titlePage', prev, titlePage);
725
+ }
726
+ return this;
727
+ }
728
+
729
+ /**
730
+ * Sets header reference
731
+ * @param type Header type (default, first, even)
732
+ * @param rId Relationship ID
733
+ */
734
+ setHeaderReference(type: 'default' | 'first' | 'even', rId: string): this {
735
+ const prev = this.properties.headers?.[type];
736
+ if (!this.properties.headers) {
737
+ this.properties.headers = {};
738
+ }
739
+ this.properties.headers[type] = rId;
740
+ if (this.trackingContext?.isEnabled() && prev !== rId) {
741
+ this.trackingContext.trackSectionChange(this, `headerReference:${type}`, prev, rId);
742
+ }
743
+ return this;
744
+ }
745
+
746
+ /**
747
+ * Sets footer reference
748
+ * @param type Footer type (default, first, even)
749
+ * @param rId Relationship ID
750
+ */
751
+ setFooterReference(type: 'default' | 'first' | 'even', rId: string): this {
752
+ const prev = this.properties.footers?.[type];
753
+ if (!this.properties.footers) {
754
+ this.properties.footers = {};
755
+ }
756
+ this.properties.footers[type] = rId;
757
+ if (this.trackingContext?.isEnabled() && prev !== rId) {
758
+ this.trackingContext.trackSectionChange(this, `footerReference:${type}`, prev, rId);
759
+ }
760
+ return this;
761
+ }
762
+
763
+ /**
764
+ * Sets vertical page alignment
765
+ * Controls how content is vertically aligned on the page
766
+ * @param alignment Vertical alignment (top, center, bottom, both=justified)
767
+ */
768
+ setVerticalAlignment(alignment: VerticalAlignment): this {
769
+ const prev = this.properties.verticalAlignment;
770
+ this.properties.verticalAlignment = alignment;
771
+ if (this.trackingContext?.isEnabled() && prev !== alignment) {
772
+ this.trackingContext.trackSectionChange(this, 'verticalAlignment', prev, alignment);
773
+ }
774
+ return this;
775
+ }
776
+
777
+ /**
778
+ * Sets paper source (printer tray selection)
779
+ * @param first First page tray number
780
+ * @param other Other pages tray number
781
+ */
782
+ setPaperSource(first?: number, other?: number): this {
783
+ const prev = this.properties.paperSource ? { ...this.properties.paperSource } : undefined;
784
+ this.properties.paperSource = { first, other };
785
+ if (this.trackingContext?.isEnabled()) {
786
+ this.trackingContext.trackSectionChange(
787
+ this,
788
+ 'paperSource',
789
+ prev,
790
+ this.properties.paperSource
791
+ );
792
+ }
793
+ return this;
794
+ }
795
+
796
+ /**
797
+ * Sets column separator line
798
+ * Shows a vertical line between columns
799
+ * @param separator Whether to show column separator line
800
+ */
801
+ setColumnSeparator(separator = true): this {
802
+ const prev = this.properties.columns?.separator;
803
+ if (!this.properties.columns) {
804
+ this.properties.columns = { count: 1 };
805
+ }
806
+ this.properties.columns.separator = separator;
807
+ if (this.trackingContext?.isEnabled() && prev !== separator) {
808
+ this.trackingContext.trackSectionChange(this, 'columnSeparator', prev, separator);
809
+ }
810
+ return this;
811
+ }
812
+
813
+ /**
814
+ * Sets custom column widths (for unequal columns)
815
+ * @param widths Array of column widths in twips
816
+ */
817
+ setColumnWidths(widths: number[]): this {
818
+ const prev = this.properties.columns ? { ...this.properties.columns } : undefined;
819
+ if (!this.properties.columns) {
820
+ this.properties.columns = { count: widths.length };
821
+ }
822
+ this.properties.columns.columnWidths = widths;
823
+ this.properties.columns.equalWidth = false;
824
+ this.properties.columns.count = widths.length;
825
+ if (this.trackingContext?.isEnabled()) {
826
+ this.trackingContext.trackSectionChange(this, 'columns', prev, {
827
+ ...this.properties.columns,
828
+ });
829
+ }
830
+ return this;
831
+ }
832
+
833
+ /**
834
+ * Sets text direction for the section
835
+ * @param direction Text direction (ltr=left-to-right, rtl=right-to-left, tbRl=top-to-bottom-right-to-left, btLr=bottom-to-top-left-to-right)
836
+ */
837
+ setTextDirection(direction: TextDirection): this {
838
+ const prev = this.properties.textDirection;
839
+ this.properties.textDirection = direction;
840
+ if (this.trackingContext?.isEnabled() && prev !== direction) {
841
+ this.trackingContext.trackSectionChange(this, 'textDirection', prev, direction);
842
+ }
843
+ return this;
844
+ }
845
+
846
+ /**
847
+ * Sets line numbering for the section
848
+ * Per ECMA-376 Part 1, Section 17.6.8 (w:lnNumType)
849
+ * @param options Line numbering configuration
850
+ */
851
+ setLineNumbering(options: LineNumbering): this {
852
+ const prev = this.properties.lineNumbering ? { ...this.properties.lineNumbering } : undefined;
853
+ this.properties.lineNumbering = { ...options };
854
+ if (this.trackingContext?.isEnabled()) {
855
+ this.trackingContext.trackSectionChange(
856
+ this,
857
+ 'lineNumbering',
858
+ prev,
859
+ this.properties.lineNumbering
860
+ );
861
+ }
862
+ return this;
863
+ }
864
+
865
+ /**
866
+ * Gets line numbering configuration
867
+ * @returns Line numbering settings or undefined if not set
868
+ */
869
+ getLineNumbering(): LineNumbering | undefined {
870
+ return this.properties.lineNumbering ? { ...this.properties.lineNumbering } : undefined;
871
+ }
872
+
873
+ /**
874
+ * Clears line numbering for the section
875
+ */
876
+ clearLineNumbering(): this {
877
+ this.properties.lineNumbering = undefined;
878
+ return this;
879
+ }
880
+
881
+ /**
882
+ * Sets section-level footnote properties
883
+ * Per ECMA-376 Part 1 §17.11.6
884
+ */
885
+ setFootnoteProperties(props: NoteProperties): this {
886
+ const prev = this.properties.footnotePr ? { ...this.properties.footnotePr } : undefined;
887
+ this.properties.footnotePr = { ...props };
888
+ if (this.trackingContext?.isEnabled()) {
889
+ this.trackingContext.trackSectionChange(this, 'footnotePr', prev, this.properties.footnotePr);
890
+ }
891
+ return this;
892
+ }
893
+
894
+ /**
895
+ * Sets section-level endnote properties
896
+ * Per ECMA-376 Part 1 §17.11.7
897
+ */
898
+ setEndnoteProperties(props: NoteProperties): this {
899
+ const prev = this.properties.endnotePr ? { ...this.properties.endnotePr } : undefined;
900
+ this.properties.endnotePr = { ...props };
901
+ if (this.trackingContext?.isEnabled()) {
902
+ this.trackingContext.trackSectionChange(this, 'endnotePr', prev, this.properties.endnotePr);
903
+ }
904
+ return this;
905
+ }
906
+
907
+ /**
908
+ * Sets whether endnotes are suppressed in this section
909
+ * Per ECMA-376 Part 1 §17.6.14
910
+ */
911
+ setNoEndnote(noEndnote = true): this {
912
+ const prev = this.properties.noEndnote;
913
+ this.properties.noEndnote = noEndnote;
914
+ if (this.trackingContext?.isEnabled() && prev !== noEndnote) {
915
+ this.trackingContext.trackSectionChange(this, 'noEndnote', prev, noEndnote);
916
+ }
917
+ return this;
918
+ }
919
+
920
+ /**
921
+ * Sets form protection for this section
922
+ * Per ECMA-376 Part 1 §17.6.4
923
+ */
924
+ setFormProtection(formProt = true): this {
925
+ const prev = this.properties.formProt;
926
+ this.properties.formProt = formProt;
927
+ if (this.trackingContext?.isEnabled() && prev !== formProt) {
928
+ this.trackingContext.trackSectionChange(this, 'formProt', prev, formProt);
929
+ }
930
+ return this;
931
+ }
932
+
933
+ /**
934
+ * Sets printer settings relationship ID
935
+ * Per ECMA-376 Part 1 §17.6.6
936
+ */
937
+ setPrinterSettings(rId: string): this {
938
+ const prev = this.properties.printerSettingsId;
939
+ this.properties.printerSettingsId = rId;
940
+ if (this.trackingContext?.isEnabled() && prev !== rId) {
941
+ this.trackingContext.trackSectionChange(this, 'printerSettings', prev, rId);
942
+ }
943
+ return this;
944
+ }
945
+
946
+ /**
947
+ * Sets chapter numbering for page numbers
948
+ * Per ECMA-376 Part 1 §17.6.12
949
+ * @param chapStyle - Heading level (1-9) to use for chapter numbering
950
+ * @param chapSep - Separator between chapter and page number
951
+ */
952
+ setChapterNumbering(chapStyle: number, chapSep?: ChapterSeparator): this {
953
+ const prevStyle = this.properties.chapStyle;
954
+ const prevSep = this.properties.chapSep;
955
+ this.properties.chapStyle = chapStyle;
956
+ if (chapSep) this.properties.chapSep = chapSep;
957
+ if (this.trackingContext?.isEnabled()) {
958
+ this.trackingContext.trackSectionChange(
959
+ this,
960
+ 'chapterNumbering',
961
+ { chapStyle: prevStyle, chapSep: prevSep },
962
+ { chapStyle, chapSep: chapSep || prevSep }
963
+ );
964
+ }
965
+ return this;
966
+ }
967
+
968
+ /**
969
+ * Generates WordprocessingML XML for section properties
970
+ */
971
+ toXML(): XMLElement {
972
+ const children: XMLElement[] = [];
973
+
974
+ // Header references
975
+ if (this.properties.headers) {
976
+ if (this.properties.headers.first) {
977
+ children.push(
978
+ XMLBuilder.wSelf('headerReference', {
979
+ 'w:type': 'first',
980
+ 'r:id': this.properties.headers.first,
981
+ })
982
+ );
983
+ }
984
+ if (this.properties.headers.even) {
985
+ children.push(
986
+ XMLBuilder.wSelf('headerReference', {
987
+ 'w:type': 'even',
988
+ 'r:id': this.properties.headers.even,
989
+ })
990
+ );
991
+ }
992
+ if (this.properties.headers.default) {
993
+ children.push(
994
+ XMLBuilder.wSelf('headerReference', {
995
+ 'w:type': 'default',
996
+ 'r:id': this.properties.headers.default,
997
+ })
998
+ );
999
+ }
1000
+ }
1001
+
1002
+ // Footer references
1003
+ if (this.properties.footers) {
1004
+ if (this.properties.footers.first) {
1005
+ children.push(
1006
+ XMLBuilder.wSelf('footerReference', {
1007
+ 'w:type': 'first',
1008
+ 'r:id': this.properties.footers.first,
1009
+ })
1010
+ );
1011
+ }
1012
+ if (this.properties.footers.even) {
1013
+ children.push(
1014
+ XMLBuilder.wSelf('footerReference', {
1015
+ 'w:type': 'even',
1016
+ 'r:id': this.properties.footers.even,
1017
+ })
1018
+ );
1019
+ }
1020
+ if (this.properties.footers.default) {
1021
+ children.push(
1022
+ XMLBuilder.wSelf('footerReference', {
1023
+ 'w:type': 'default',
1024
+ 'r:id': this.properties.footers.default,
1025
+ })
1026
+ );
1027
+ }
1028
+ }
1029
+
1030
+ // CT_SectPr element order per ECMA-376:
1031
+ // footnotePr → endnotePr → type → pgSz → pgMar → paperSrc → pgBorders →
1032
+ // lnNumType pgNumType cols → formProt → vAlign → noEndnote → titlePg →
1033
+ // textDirection → bidi → rtlGutter → docGrid → printerSettings → sectPrChange
1034
+
1035
+ // Footnote properties (w:footnotePr)
1036
+ if (this.properties.footnotePr) {
1037
+ const fnChildren: XMLElement[] = [];
1038
+ if (this.properties.footnotePr.position) {
1039
+ fnChildren.push(XMLBuilder.wSelf('pos', { 'w:val': this.properties.footnotePr.position }));
1040
+ }
1041
+ if (this.properties.footnotePr.numberFormat) {
1042
+ fnChildren.push(
1043
+ XMLBuilder.wSelf('numFmt', { 'w:val': this.properties.footnotePr.numberFormat })
1044
+ );
1045
+ }
1046
+ if (this.properties.footnotePr.startNumber !== undefined) {
1047
+ fnChildren.push(
1048
+ XMLBuilder.wSelf('numStart', {
1049
+ 'w:val': this.properties.footnotePr.startNumber.toString(),
1050
+ })
1051
+ );
1052
+ }
1053
+ if (this.properties.footnotePr.restart) {
1054
+ fnChildren.push(
1055
+ XMLBuilder.wSelf('numRestart', { 'w:val': this.properties.footnotePr.restart })
1056
+ );
1057
+ }
1058
+ if (fnChildren.length > 0) {
1059
+ children.push(XMLBuilder.w('footnotePr', undefined, fnChildren));
1060
+ }
1061
+ }
1062
+
1063
+ // Endnote properties (w:endnotePr)
1064
+ if (this.properties.endnotePr) {
1065
+ const enChildren: XMLElement[] = [];
1066
+ if (this.properties.endnotePr.position) {
1067
+ enChildren.push(XMLBuilder.wSelf('pos', { 'w:val': this.properties.endnotePr.position }));
1068
+ }
1069
+ if (this.properties.endnotePr.numberFormat) {
1070
+ enChildren.push(
1071
+ XMLBuilder.wSelf('numFmt', { 'w:val': this.properties.endnotePr.numberFormat })
1072
+ );
1073
+ }
1074
+ if (this.properties.endnotePr.startNumber !== undefined) {
1075
+ enChildren.push(
1076
+ XMLBuilder.wSelf('numStart', {
1077
+ 'w:val': this.properties.endnotePr.startNumber.toString(),
1078
+ })
1079
+ );
1080
+ }
1081
+ if (this.properties.endnotePr.restart) {
1082
+ enChildren.push(
1083
+ XMLBuilder.wSelf('numRestart', { 'w:val': this.properties.endnotePr.restart })
1084
+ );
1085
+ }
1086
+ if (enChildren.length > 0) {
1087
+ children.push(XMLBuilder.w('endnotePr', undefined, enChildren));
1088
+ }
1089
+ }
1090
+
1091
+ // Section type
1092
+ if (this.properties.type) {
1093
+ children.push(XMLBuilder.wSelf('type', { 'w:val': this.properties.type }));
1094
+ }
1095
+
1096
+ // Page size
1097
+ if (this.properties.pageSize) {
1098
+ const attrs: Record<string, string> = {
1099
+ 'w:w': this.properties.pageSize.width.toString(),
1100
+ 'w:h': this.properties.pageSize.height.toString(),
1101
+ };
1102
+ if (this.properties.pageSize.orientation === 'landscape') {
1103
+ attrs['w:orient'] = 'landscape';
1104
+ }
1105
+ children.push(XMLBuilder.wSelf('pgSz', attrs));
1106
+ }
1107
+
1108
+ // Margins
1109
+ if (this.properties.margins) {
1110
+ const attrs: Record<string, string> = {
1111
+ 'w:top': this.properties.margins.top.toString(),
1112
+ 'w:right': this.properties.margins.right.toString(),
1113
+ 'w:bottom': this.properties.margins.bottom.toString(),
1114
+ 'w:left': this.properties.margins.left.toString(),
1115
+ };
1116
+ attrs['w:header'] = (this.properties.margins.header ?? 720).toString();
1117
+ attrs['w:footer'] = (this.properties.margins.footer ?? 720).toString();
1118
+ if (this.properties.margins.gutter !== undefined) {
1119
+ attrs['w:gutter'] = this.properties.margins.gutter.toString();
1120
+ }
1121
+ children.push(XMLBuilder.wSelf('pgMar', attrs));
1122
+ }
1123
+
1124
+ // Paper source
1125
+ if (this.properties.paperSource) {
1126
+ const attrs: Record<string, string> = {};
1127
+ if (this.properties.paperSource.first !== undefined) {
1128
+ attrs['w:first'] = this.properties.paperSource.first.toString();
1129
+ }
1130
+ if (this.properties.paperSource.other !== undefined) {
1131
+ attrs['w:other'] = this.properties.paperSource.other.toString();
1132
+ }
1133
+ if (Object.keys(attrs).length > 0) {
1134
+ children.push(XMLBuilder.wSelf('paperSrc', attrs));
1135
+ }
1136
+ }
1137
+
1138
+ // Page borders per ECMA-376 Part 1 §17.6.10
1139
+ if (this.properties.pageBorders) {
1140
+ const pgBorders = this.properties.pageBorders;
1141
+ const pgBordersAttrs: Record<string, string> = {};
1142
+ if (pgBorders.offsetFrom) pgBordersAttrs['w:offsetFrom'] = pgBorders.offsetFrom;
1143
+ if (pgBorders.display) pgBordersAttrs['w:display'] = pgBorders.display;
1144
+ if (pgBorders.zOrder) pgBordersAttrs['w:zOrder'] = pgBorders.zOrder;
1145
+
1146
+ const borderChildren: XMLElement[] = [];
1147
+ const buildBorder = (side: string, def: PageBorderDef) => {
1148
+ const bAttrs: Record<string, string | number> = {};
1149
+ if (def.style) bAttrs['w:val'] = def.style;
1150
+ // ECMA-376 Part 1 §17.18.2: sz valid range 2-96 (eighths of a point)
1151
+ if (def.size !== undefined) bAttrs['w:sz'] = Math.max(2, Math.min(96, def.size));
1152
+ if (def.color) bAttrs['w:color'] = def.color;
1153
+ // ECMA-376 Part 1 §17.18.88: space valid range 0-31680 (points)
1154
+ if (def.space !== undefined) bAttrs['w:space'] = Math.max(0, Math.min(31680, def.space));
1155
+ if (def.shadow) bAttrs['w:shadow'] = '1';
1156
+ if (def.frame) bAttrs['w:frame'] = '1';
1157
+ if (def.themeColor) bAttrs['w:themeColor'] = def.themeColor;
1158
+ if (def.artId !== undefined) bAttrs['w:id'] = def.artId;
1159
+ borderChildren.push(XMLBuilder.wSelf(side, bAttrs));
1160
+ };
1161
+
1162
+ if (pgBorders.top) buildBorder('top', pgBorders.top);
1163
+ if (pgBorders.left) buildBorder('left', pgBorders.left);
1164
+ if (pgBorders.bottom) buildBorder('bottom', pgBorders.bottom);
1165
+ if (pgBorders.right) buildBorder('right', pgBorders.right);
1166
+
1167
+ children.push(XMLBuilder.w('pgBorders', pgBordersAttrs, borderChildren));
1168
+ }
1169
+
1170
+ // Line numbering (w:lnNumType)
1171
+ if (this.properties.lineNumbering) {
1172
+ const attrs: Record<string, string> = {};
1173
+ if (this.properties.lineNumbering.countBy !== undefined) {
1174
+ attrs['w:countBy'] = this.properties.lineNumbering.countBy.toString();
1175
+ }
1176
+ if (this.properties.lineNumbering.start !== undefined) {
1177
+ attrs['w:start'] = this.properties.lineNumbering.start.toString();
1178
+ }
1179
+ if (this.properties.lineNumbering.distance !== undefined) {
1180
+ attrs['w:distance'] = this.properties.lineNumbering.distance.toString();
1181
+ }
1182
+ if (this.properties.lineNumbering.restart) {
1183
+ attrs['w:restart'] = this.properties.lineNumbering.restart;
1184
+ }
1185
+ if (Object.keys(attrs).length > 0) {
1186
+ children.push(XMLBuilder.wSelf('lnNumType', attrs));
1187
+ }
1188
+ }
1189
+
1190
+ // Page numbering
1191
+ if (this.properties.pageNumbering || this.properties.chapStyle !== undefined) {
1192
+ const attrs: Record<string, string> = {};
1193
+ if (this.properties.pageNumbering?.start !== undefined) {
1194
+ attrs['w:start'] = this.properties.pageNumbering.start.toString();
1195
+ }
1196
+ if (this.properties.pageNumbering?.format) {
1197
+ attrs['w:fmt'] = this.properties.pageNumbering.format;
1198
+ }
1199
+ if (this.properties.chapStyle !== undefined) {
1200
+ attrs['w:chapStyle'] = this.properties.chapStyle.toString();
1201
+ }
1202
+ if (this.properties.chapSep) {
1203
+ attrs['w:chapSep'] = this.properties.chapSep;
1204
+ }
1205
+ if (Object.keys(attrs).length > 0) {
1206
+ children.push(XMLBuilder.wSelf('pgNumType', attrs));
1207
+ }
1208
+ }
1209
+
1210
+ // Columns
1211
+ if (this.properties.columns) {
1212
+ const attrs: Record<string, string> = {
1213
+ 'w:num': this.properties.columns.count.toString(),
1214
+ };
1215
+ if (this.properties.columns.space !== undefined) {
1216
+ attrs['w:space'] = this.properties.columns.space.toString();
1217
+ }
1218
+ if (this.properties.columns.equalWidth !== undefined) {
1219
+ attrs['w:equalWidth'] = this.properties.columns.equalWidth ? '1' : '0';
1220
+ }
1221
+ if (this.properties.columns.separator !== undefined) {
1222
+ attrs['w:sep'] = this.properties.columns.separator ? '1' : '0';
1223
+ }
1224
+
1225
+ const colChildren: XMLElement[] = [];
1226
+ if (this.properties.columns.columnWidths) {
1227
+ for (const width of this.properties.columns.columnWidths) {
1228
+ colChildren.push(XMLBuilder.wSelf('col', { 'w:w': width.toString() }));
1229
+ }
1230
+ }
1231
+
1232
+ children.push(
1233
+ colChildren.length > 0
1234
+ ? XMLBuilder.w('cols', attrs, colChildren)
1235
+ : XMLBuilder.wSelf('cols', attrs)
1236
+ );
1237
+ }
1238
+
1239
+ // Form protection (w:formProt)
1240
+ if (this.properties.formProt) {
1241
+ children.push(XMLBuilder.wSelf('formProt'));
1242
+ }
1243
+
1244
+ // Vertical alignment
1245
+ if (this.properties.verticalAlignment) {
1246
+ children.push(XMLBuilder.wSelf('vAlign', { 'w:val': this.properties.verticalAlignment }));
1247
+ }
1248
+
1249
+ // Suppress endnotes (w:noEndnote)
1250
+ if (this.properties.noEndnote) {
1251
+ children.push(XMLBuilder.wSelf('noEndnote'));
1252
+ }
1253
+
1254
+ // Title page
1255
+ if (this.properties.titlePage) {
1256
+ children.push(XMLBuilder.wSelf('titlePg', { 'w:val': '1' }));
1257
+ }
1258
+
1259
+ // Text direction (map to valid ST_TextDirection values per ECMA-376 §17.18.93)
1260
+ if (this.properties.textDirection) {
1261
+ const textDirMap: Record<string, string> = {
1262
+ ltr: 'lrTb',
1263
+ rtl: 'tbRl',
1264
+ tbRl: 'tbRl',
1265
+ btLr: 'btLr',
1266
+ lrTb: 'lrTb',
1267
+ lrTbV: 'lrTbV',
1268
+ tbRlV: 'tbRlV',
1269
+ tbLrV: 'tbLrV',
1270
+ };
1271
+ const val = textDirMap[this.properties.textDirection] || this.properties.textDirection;
1272
+ children.push(XMLBuilder.wSelf('textDirection', { 'w:val': val }));
1273
+ }
1274
+
1275
+ // Bidirectional section (RTL)
1276
+ if (this.properties.bidi) {
1277
+ children.push(XMLBuilder.wSelf('bidi'));
1278
+ }
1279
+
1280
+ // RTL gutter (gutter on right side)
1281
+ if (this.properties.rtlGutter) {
1282
+ children.push(XMLBuilder.wSelf('rtlGutter'));
1283
+ }
1284
+
1285
+ // Document grid
1286
+ if (this.properties.docGrid) {
1287
+ const attrs: Record<string, string> = {};
1288
+ if (this.properties.docGrid.type) {
1289
+ attrs['w:type'] = this.properties.docGrid.type;
1290
+ }
1291
+ if (this.properties.docGrid.linePitch !== undefined) {
1292
+ attrs['w:linePitch'] = this.properties.docGrid.linePitch.toString();
1293
+ }
1294
+ if (this.properties.docGrid.charSpace !== undefined) {
1295
+ attrs['w:charSpace'] = this.properties.docGrid.charSpace.toString();
1296
+ }
1297
+ if (Object.keys(attrs).length > 0) {
1298
+ children.push(XMLBuilder.wSelf('docGrid', attrs));
1299
+ }
1300
+ }
1301
+
1302
+ // Printer settings (w:printerSettings)
1303
+ if (this.properties.printerSettingsId) {
1304
+ children.push(
1305
+ XMLBuilder.wSelf('printerSettings', {
1306
+ 'r:id': this.properties.printerSettingsId,
1307
+ })
1308
+ );
1309
+ }
1310
+
1311
+ // Add section property change (w:sectPrChange) per ECMA-376 Part 1 §17.13.5.32
1312
+ // Must be last child of w:sectPr
1313
+ if (this.sectPrChange) {
1314
+ const changeAttrs: Record<string, string | number> = {
1315
+ 'w:id': this.sectPrChange.id,
1316
+ 'w:author': this.sectPrChange.author,
1317
+ 'w:date': this.sectPrChange.date,
1318
+ };
1319
+ const prevChildren: XMLElement[] = [];
1320
+ const prev = this.sectPrChange.previousProperties;
1321
+ if (prev) {
1322
+ // Ordered per CT_SectPrBase:
1323
+ // type pgSz → pgMar → lnNumType → pgNumType → cols → formProt → vAlign → titlePg → textDirection
1324
+ if (prev.type) {
1325
+ prevChildren.push(XMLBuilder.wSelf('type', { 'w:val': prev.type }));
1326
+ }
1327
+ if (prev.pageSize) {
1328
+ const pgSzAttrs: Record<string, string> = {
1329
+ 'w:w': prev.pageSize.width?.toString() || '12240',
1330
+ 'w:h': prev.pageSize.height?.toString() || '15840',
1331
+ };
1332
+ if (prev.pageSize.orientation === 'landscape') {
1333
+ pgSzAttrs['w:orient'] = 'landscape';
1334
+ }
1335
+ prevChildren.push(XMLBuilder.wSelf('pgSz', pgSzAttrs));
1336
+ }
1337
+ if (prev.margins) {
1338
+ const pgMarAttrs: Record<string, string> = {};
1339
+ if (prev.margins.top !== undefined) pgMarAttrs['w:top'] = prev.margins.top.toString();
1340
+ if (prev.margins.bottom !== undefined)
1341
+ pgMarAttrs['w:bottom'] = prev.margins.bottom.toString();
1342
+ if (prev.margins.left !== undefined) pgMarAttrs['w:left'] = prev.margins.left.toString();
1343
+ if (prev.margins.right !== undefined)
1344
+ pgMarAttrs['w:right'] = prev.margins.right.toString();
1345
+ if (prev.margins.header !== undefined)
1346
+ pgMarAttrs['w:header'] = prev.margins.header.toString();
1347
+ if (prev.margins.footer !== undefined)
1348
+ pgMarAttrs['w:footer'] = prev.margins.footer.toString();
1349
+ prevChildren.push(XMLBuilder.wSelf('pgMar', pgMarAttrs));
1350
+ }
1351
+ if (prev.lineNumbering) {
1352
+ const lnAttrs: Record<string, string> = {};
1353
+ if (prev.lineNumbering.countBy !== undefined)
1354
+ lnAttrs['w:countBy'] = prev.lineNumbering.countBy.toString();
1355
+ if (prev.lineNumbering.start !== undefined)
1356
+ lnAttrs['w:start'] = prev.lineNumbering.start.toString();
1357
+ if (prev.lineNumbering.restart) lnAttrs['w:restart'] = prev.lineNumbering.restart;
1358
+ if (prev.lineNumbering.distance !== undefined)
1359
+ lnAttrs['w:distance'] = prev.lineNumbering.distance.toString();
1360
+ if (Object.keys(lnAttrs).length > 0) {
1361
+ prevChildren.push(XMLBuilder.wSelf('lnNumType', lnAttrs));
1362
+ }
1363
+ }
1364
+ if (prev.pageNumbering) {
1365
+ const pnAttrs: Record<string, string> = {};
1366
+ if (prev.pageNumbering.start !== undefined)
1367
+ pnAttrs['w:start'] = prev.pageNumbering.start.toString();
1368
+ if (prev.pageNumbering.format) pnAttrs['w:fmt'] = prev.pageNumbering.format;
1369
+ if (Object.keys(pnAttrs).length > 0) {
1370
+ prevChildren.push(XMLBuilder.wSelf('pgNumType', pnAttrs));
1371
+ }
1372
+ }
1373
+ if (prev.columns) {
1374
+ const colAttrs: Record<string, string> = {
1375
+ 'w:num': prev.columns.count?.toString() || '1',
1376
+ };
1377
+ if (prev.columns.space !== undefined) colAttrs['w:space'] = prev.columns.space.toString();
1378
+ prevChildren.push(XMLBuilder.wSelf('cols', colAttrs));
1379
+ }
1380
+ if (prev.formProt) {
1381
+ prevChildren.push(XMLBuilder.wSelf('formProt'));
1382
+ }
1383
+ if (prev.verticalAlignment) {
1384
+ prevChildren.push(XMLBuilder.wSelf('vAlign', { 'w:val': prev.verticalAlignment }));
1385
+ }
1386
+ if (prev.titlePage) {
1387
+ prevChildren.push(XMLBuilder.wSelf('titlePg'));
1388
+ }
1389
+ if (prev.textDirection) {
1390
+ const tdMap: Record<string, string> = {
1391
+ ltr: 'lrTb',
1392
+ rtl: 'tbRl',
1393
+ tbRl: 'tbRl',
1394
+ btLr: 'btLr',
1395
+ lrTb: 'lrTb',
1396
+ lrTbV: 'lrTbV',
1397
+ tbRlV: 'tbRlV',
1398
+ tbLrV: 'tbLrV',
1399
+ };
1400
+ prevChildren.push(
1401
+ XMLBuilder.wSelf('textDirection', {
1402
+ 'w:val': tdMap[prev.textDirection] || prev.textDirection,
1403
+ })
1404
+ );
1405
+ }
1406
+ }
1407
+ const prevSectPr = XMLBuilder.w('sectPr', undefined, prevChildren);
1408
+ children.push(XMLBuilder.w('sectPrChange', changeAttrs, [prevSectPr]));
1409
+ }
1410
+
1411
+ return XMLBuilder.w('sectPr', undefined, children);
1412
+ }
1413
+
1414
+ /**
1415
+ * Creates a deep clone of this section
1416
+ * @returns New Section instance with copied properties
1417
+ */
1418
+ clone(): Section {
1419
+ // Deep clone all nested objects
1420
+ const clonedProperties: SectionProperties = {};
1421
+
1422
+ if (this.properties.pageSize) {
1423
+ clonedProperties.pageSize = { ...this.properties.pageSize };
1424
+ }
1425
+
1426
+ if (this.properties.margins) {
1427
+ clonedProperties.margins = { ...this.properties.margins };
1428
+ }
1429
+
1430
+ if (this.properties.columns) {
1431
+ clonedProperties.columns = {
1432
+ ...this.properties.columns,
1433
+ columnWidths: this.properties.columns.columnWidths
1434
+ ? [...this.properties.columns.columnWidths]
1435
+ : undefined,
1436
+ };
1437
+ }
1438
+
1439
+ if (this.properties.pageNumbering) {
1440
+ clonedProperties.pageNumbering = { ...this.properties.pageNumbering };
1441
+ }
1442
+
1443
+ if (this.properties.headers) {
1444
+ clonedProperties.headers = { ...this.properties.headers };
1445
+ }
1446
+
1447
+ if (this.properties.footers) {
1448
+ clonedProperties.footers = { ...this.properties.footers };
1449
+ }
1450
+
1451
+ if (this.properties.paperSource) {
1452
+ clonedProperties.paperSource = { ...this.properties.paperSource };
1453
+ }
1454
+
1455
+ if (this.properties.docGrid) {
1456
+ clonedProperties.docGrid = { ...this.properties.docGrid };
1457
+ }
1458
+
1459
+ if (this.properties.lineNumbering) {
1460
+ clonedProperties.lineNumbering = { ...this.properties.lineNumbering };
1461
+ }
1462
+
1463
+ // Copy primitive properties
1464
+ clonedProperties.type = this.properties.type;
1465
+ clonedProperties.titlePage = this.properties.titlePage;
1466
+ clonedProperties.verticalAlignment = this.properties.verticalAlignment;
1467
+ clonedProperties.textDirection = this.properties.textDirection;
1468
+ clonedProperties.bidi = this.properties.bidi;
1469
+ clonedProperties.rtlGutter = this.properties.rtlGutter;
1470
+
1471
+ return new Section(clonedProperties);
1472
+ }
1473
+
1474
+ /**
1475
+ * Creates a section with default properties
1476
+ */
1477
+ static create(properties?: SectionProperties): Section {
1478
+ return new Section(properties);
1479
+ }
1480
+
1481
+ /**
1482
+ * Creates a letter-sized section (8.5" x 11")
1483
+ */
1484
+ static createLetter(): Section {
1485
+ return new Section({
1486
+ pageSize: {
1487
+ width: PAGE_SIZES.LETTER.width,
1488
+ height: PAGE_SIZES.LETTER.height,
1489
+ orientation: 'portrait',
1490
+ },
1491
+ });
1492
+ }
1493
+
1494
+ /**
1495
+ * Creates an A4-sized section (21cm x 29.7cm)
1496
+ */
1497
+ static createA4(): Section {
1498
+ return new Section({
1499
+ pageSize: {
1500
+ width: PAGE_SIZES.A4.width,
1501
+ height: PAGE_SIZES.A4.height,
1502
+ orientation: 'portrait',
1503
+ },
1504
+ });
1505
+ }
1506
+
1507
+ /**
1508
+ * Creates a landscape section
1509
+ * @param pageSize Page size (default: Letter)
1510
+ */
1511
+ static createLandscape(pageSize: 'letter' | 'a4' = 'letter'): Section {
1512
+ const size = pageSize === 'a4' ? PAGE_SIZES.A4 : PAGE_SIZES.LETTER;
1513
+ return new Section({
1514
+ pageSize: {
1515
+ width: size.height, // Swap for landscape
1516
+ height: size.width,
1517
+ orientation: 'landscape',
1518
+ },
1519
+ });
1520
+ }
1521
+ }