docxmlater 10.3.5 → 10.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +158 -7
  2. package/dist/core/Document.d.ts +102 -3
  3. package/dist/core/Document.d.ts.map +1 -1
  4. package/dist/core/Document.js +775 -50
  5. package/dist/core/Document.js.map +1 -1
  6. package/dist/core/DocumentContent.d.ts.map +1 -1
  7. package/dist/core/DocumentContent.js +0 -8
  8. package/dist/core/DocumentContent.js.map +1 -1
  9. package/dist/core/DocumentGenerator.d.ts.map +1 -1
  10. package/dist/core/DocumentGenerator.js +9 -5
  11. package/dist/core/DocumentGenerator.js.map +1 -1
  12. package/dist/core/DocumentParser.d.ts.map +1 -1
  13. package/dist/core/DocumentParser.js +588 -102
  14. package/dist/core/DocumentParser.js.map +1 -1
  15. package/dist/core/RelationshipManager.d.ts.map +1 -1
  16. package/dist/core/RelationshipManager.js +4 -3
  17. package/dist/core/RelationshipManager.js.map +1 -1
  18. package/dist/elements/Bookmark.d.ts +7 -0
  19. package/dist/elements/Bookmark.d.ts.map +1 -1
  20. package/dist/elements/Bookmark.js +24 -4
  21. package/dist/elements/Bookmark.js.map +1 -1
  22. package/dist/elements/BookmarkManager.d.ts.map +1 -1
  23. package/dist/elements/BookmarkManager.js +4 -3
  24. package/dist/elements/BookmarkManager.js.map +1 -1
  25. package/dist/elements/CommonTypes.d.ts +2 -2
  26. package/dist/elements/CommonTypes.d.ts.map +1 -1
  27. package/dist/elements/CommonTypes.js +14 -1
  28. package/dist/elements/CommonTypes.js.map +1 -1
  29. package/dist/elements/Field.d.ts +1 -1
  30. package/dist/elements/Field.d.ts.map +1 -1
  31. package/dist/elements/Field.js +1 -1
  32. package/dist/elements/Field.js.map +1 -1
  33. package/dist/elements/Footer.d.ts +2 -0
  34. package/dist/elements/Footer.d.ts.map +1 -1
  35. package/dist/elements/Footer.js +6 -0
  36. package/dist/elements/Footer.js.map +1 -1
  37. package/dist/elements/Header.d.ts +2 -0
  38. package/dist/elements/Header.d.ts.map +1 -1
  39. package/dist/elements/Header.js +6 -0
  40. package/dist/elements/Header.js.map +1 -1
  41. package/dist/elements/Image.d.ts +1 -0
  42. package/dist/elements/Image.d.ts.map +1 -1
  43. package/dist/elements/Image.js +17 -2
  44. package/dist/elements/Image.js.map +1 -1
  45. package/dist/elements/Paragraph.d.ts +81 -1
  46. package/dist/elements/Paragraph.d.ts.map +1 -1
  47. package/dist/elements/Paragraph.js +515 -21
  48. package/dist/elements/Paragraph.js.map +1 -1
  49. package/dist/elements/Revision.d.ts +0 -1
  50. package/dist/elements/Revision.d.ts.map +1 -1
  51. package/dist/elements/Revision.js +0 -12
  52. package/dist/elements/Revision.js.map +1 -1
  53. package/dist/elements/RevisionManager.d.ts +0 -1
  54. package/dist/elements/RevisionManager.d.ts.map +1 -1
  55. package/dist/elements/RevisionManager.js +0 -2
  56. package/dist/elements/RevisionManager.js.map +1 -1
  57. package/dist/elements/Run.d.ts +16 -4
  58. package/dist/elements/Run.d.ts.map +1 -1
  59. package/dist/elements/Run.js +114 -22
  60. package/dist/elements/Run.js.map +1 -1
  61. package/dist/elements/Section.d.ts +7 -1
  62. package/dist/elements/Section.d.ts.map +1 -1
  63. package/dist/elements/Section.js +185 -4
  64. package/dist/elements/Section.js.map +1 -1
  65. package/dist/elements/Shape.js.map +1 -1
  66. package/dist/elements/Table.d.ts +30 -1
  67. package/dist/elements/Table.d.ts.map +1 -1
  68. package/dist/elements/Table.js +357 -40
  69. package/dist/elements/Table.js.map +1 -1
  70. package/dist/elements/TableCell.d.ts +3 -0
  71. package/dist/elements/TableCell.d.ts.map +1 -1
  72. package/dist/elements/TableCell.js +30 -3
  73. package/dist/elements/TableCell.js.map +1 -1
  74. package/dist/elements/TableGridChange.d.ts +0 -1
  75. package/dist/elements/TableGridChange.d.ts.map +1 -1
  76. package/dist/elements/TableGridChange.js +0 -10
  77. package/dist/elements/TableGridChange.js.map +1 -1
  78. package/dist/elements/TableRow.d.ts +4 -0
  79. package/dist/elements/TableRow.d.ts.map +1 -1
  80. package/dist/elements/TableRow.js +31 -3
  81. package/dist/elements/TableRow.js.map +1 -1
  82. package/dist/formatting/AbstractNumbering.d.ts +5 -0
  83. package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
  84. package/dist/formatting/AbstractNumbering.js +22 -0
  85. package/dist/formatting/AbstractNumbering.js.map +1 -1
  86. package/dist/formatting/NumberingLevel.d.ts.map +1 -1
  87. package/dist/formatting/NumberingLevel.js +3 -3
  88. package/dist/formatting/NumberingLevel.js.map +1 -1
  89. package/dist/formatting/Style.d.ts +1 -0
  90. package/dist/formatting/Style.d.ts.map +1 -1
  91. package/dist/formatting/Style.js +25 -59
  92. package/dist/formatting/Style.js.map +1 -1
  93. package/dist/formatting/StylesManager.d.ts +1 -0
  94. package/dist/formatting/StylesManager.d.ts.map +1 -1
  95. package/dist/formatting/StylesManager.js +12 -0
  96. package/dist/formatting/StylesManager.js.map +1 -1
  97. package/dist/helpers/CleanupHelper.js.map +1 -1
  98. package/dist/images/ImageOptimizer.d.ts.map +1 -1
  99. package/dist/images/ImageOptimizer.js +0 -1
  100. package/dist/images/ImageOptimizer.js.map +1 -1
  101. package/dist/index.d.ts +1 -1
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js.map +1 -1
  104. package/dist/managers/DrawingManager.d.ts.map +1 -1
  105. package/dist/managers/DrawingManager.js +4 -2
  106. package/dist/managers/DrawingManager.js.map +1 -1
  107. package/dist/types/formatting.d.ts +2 -2
  108. package/dist/types/formatting.d.ts.map +1 -1
  109. package/dist/types/formatting.js.map +1 -1
  110. package/dist/utils/ChangelogGenerator.d.ts +2 -2
  111. package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
  112. package/dist/utils/ChangelogGenerator.js +4 -5
  113. package/dist/utils/ChangelogGenerator.js.map +1 -1
  114. package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
  115. package/dist/utils/InMemoryRevisionAcceptor.js +0 -1
  116. package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
  117. package/dist/utils/RevisionAwareProcessor.d.ts +2 -2
  118. package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
  119. package/dist/utils/RevisionAwareProcessor.js +2 -2
  120. package/dist/utils/RevisionAwareProcessor.js.map +1 -1
  121. package/dist/utils/SelectiveRevisionAcceptor.d.ts +0 -2
  122. package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
  123. package/dist/utils/SelectiveRevisionAcceptor.js +0 -26
  124. package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
  125. package/dist/utils/ShadingResolver.d.ts.map +1 -1
  126. package/dist/utils/ShadingResolver.js.map +1 -1
  127. package/dist/utils/acceptRevisions.js +1 -1
  128. package/dist/utils/acceptRevisions.js.map +1 -1
  129. package/dist/utils/stripTrackedChanges.js +1 -1
  130. package/dist/utils/stripTrackedChanges.js.map +1 -1
  131. package/dist/utils/units.d.ts.map +1 -1
  132. package/dist/utils/units.js +1 -1
  133. package/dist/utils/units.js.map +1 -1
  134. package/dist/validation/RevisionAutoFixer.d.ts +2 -1
  135. package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
  136. package/dist/validation/RevisionAutoFixer.js.map +1 -1
  137. package/package.json +10 -1
  138. package/src/constants/CLAUDE.md +28 -0
  139. package/src/core/CLAUDE.md +4 -0
  140. package/src/core/Document.ts +1888 -137
  141. package/src/core/DocumentContent.ts +0 -11
  142. package/src/core/DocumentGenerator.ts +11 -12
  143. package/src/core/DocumentParser.ts +620 -139
  144. package/src/core/RelationshipManager.ts +6 -3
  145. package/src/elements/Bookmark.ts +39 -4
  146. package/src/elements/BookmarkManager.ts +4 -3
  147. package/src/elements/CLAUDE.md +18 -2
  148. package/src/elements/CommonTypes.ts +35 -8
  149. package/src/elements/Field.ts +1 -1
  150. package/src/elements/Footer.ts +23 -0
  151. package/src/elements/Header.ts +25 -0
  152. package/src/elements/Image.ts +28 -5
  153. package/src/elements/Paragraph.ts +1069 -41
  154. package/src/elements/Revision.ts +0 -19
  155. package/src/elements/RevisionManager.ts +1 -3
  156. package/src/elements/Run.ts +265 -35
  157. package/src/elements/Section.ts +214 -8
  158. package/src/elements/Shape.ts +1 -1
  159. package/src/elements/Table.ts +850 -61
  160. package/src/elements/TableCell.ts +84 -10
  161. package/src/elements/TableGridChange.ts +2 -16
  162. package/src/elements/TableRow.ts +94 -9
  163. package/src/formatting/AbstractNumbering.ts +42 -1
  164. package/src/formatting/CLAUDE.md +4 -0
  165. package/src/formatting/NumberingLevel.ts +11 -7
  166. package/src/formatting/Style.ts +39 -71
  167. package/src/formatting/StylesManager.ts +36 -0
  168. package/src/helpers/CleanupHelper.ts +1 -1
  169. package/src/images/ImageOptimizer.ts +0 -3
  170. package/src/index.ts +1 -1
  171. package/src/managers/DrawingManager.ts +5 -3
  172. package/src/tracking/CLAUDE.md +30 -0
  173. package/src/types/CLAUDE.md +39 -0
  174. package/src/types/formatting.ts +2 -2
  175. package/src/utils/CLAUDE.md +15 -0
  176. package/src/utils/ChangelogGenerator.ts +4 -5
  177. package/src/utils/InMemoryRevisionAcceptor.ts +0 -9
  178. package/src/utils/RevisionAwareProcessor.ts +2 -3
  179. package/src/utils/SelectiveRevisionAcceptor.ts +0 -39
  180. package/src/utils/ShadingResolver.ts +0 -1
  181. package/src/utils/acceptRevisions.ts +1 -1
  182. package/src/utils/stripTrackedChanges.ts +1 -1
  183. package/src/utils/units.ts +2 -1
  184. package/src/validation/CLAUDE.md +40 -0
  185. package/src/validation/RevisionAutoFixer.ts +2 -1
@@ -8,6 +8,7 @@
8
8
  import { Relationship, RelationshipType } from './Relationship';
9
9
  import { XMLParser } from '../xml/XMLParser';
10
10
  import { sanitizeHyperlinkUrl } from '../utils/validation';
11
+ import { InvalidDocxError, CorruptedArchiveError } from '../zip/errors';
11
12
 
12
13
  /**
13
14
  * Manages relationships for a document or document part
@@ -221,7 +222,7 @@ export class RelationshipManager {
221
222
 
222
223
  // Verify this is a hyperlink relationship
223
224
  if (relationship.getType() !== RelationshipType.HYPERLINK) {
224
- throw new Error(
225
+ throw new InvalidDocxError(
225
226
  `Relationship ${relationshipId} is not a hyperlink relationship. ` +
226
227
  `Type is ${relationship.getType()}, expected ${RelationshipType.HYPERLINK}`
227
228
  );
@@ -384,7 +385,7 @@ export class RelationshipManager {
384
385
 
385
386
  // Prevent ReDoS: validate input size (typical .rels files are < 10KB, max 10MB)
386
387
  if (xml.length > 10000000) {
387
- throw new Error(
388
+ throw new CorruptedArchiveError(
388
389
  'Relationships XML file too large (>10MB). Possible malicious input or corrupted file.'
389
390
  );
390
391
  }
@@ -394,7 +395,9 @@ export class RelationshipManager {
394
395
 
395
396
  // Prevent infinite loops: check relationship count
396
397
  if (relationshipElements.length > 1000) {
397
- throw new Error('Too many relationships in XML file (>1000). Possible malicious input.');
398
+ throw new CorruptedArchiveError(
399
+ 'Too many relationships in XML file (>1000). Possible malicious input.'
400
+ );
398
401
  }
399
402
 
400
403
  // Process each relationship element
@@ -17,6 +17,10 @@ export interface BookmarkProperties {
17
17
  name: string;
18
18
  /** Skip name normalization (used when loading from existing documents) */
19
19
  skipNormalization?: boolean;
20
+ /** First column in table bookmark range (ECMA-376 §17.16.5) */
21
+ colFirst?: number;
22
+ /** Last column in table bookmark range (ECMA-376 §17.16.5) */
23
+ colLast?: number;
20
24
  }
21
25
 
22
26
  /**
@@ -25,6 +29,8 @@ export interface BookmarkProperties {
25
29
  export class Bookmark {
26
30
  private id: number;
27
31
  private name: string;
32
+ private colFirst?: number;
33
+ private colLast?: number;
28
34
 
29
35
  /**
30
36
  * Creates a new Bookmark
@@ -37,6 +43,8 @@ export class Bookmark {
37
43
  this.name = properties.skipNormalization
38
44
  ? properties.name
39
45
  : this.normalizeName(properties.name);
46
+ this.colFirst = properties.colFirst;
47
+ this.colLast = properties.colLast;
40
48
  }
41
49
 
42
50
  /**
@@ -136,17 +144,44 @@ export class Bookmark {
136
144
  return this;
137
145
  }
138
146
 
147
+ /**
148
+ * Gets the first column in a table bookmark range
149
+ */
150
+ getColFirst(): number | undefined {
151
+ return this.colFirst;
152
+ }
153
+
154
+ /**
155
+ * Gets the last column in a table bookmark range
156
+ */
157
+ getColLast(): number | undefined {
158
+ return this.colLast;
159
+ }
160
+
161
+ /**
162
+ * Sets the column range for a table bookmark (ECMA-376 §17.16.5)
163
+ */
164
+ setColumnRange(colFirst: number, colLast: number): this {
165
+ this.colFirst = colFirst;
166
+ this.colLast = colLast;
167
+ return this;
168
+ }
169
+
139
170
  /**
140
171
  * Generates XML for the bookmark start marker
141
172
  * @returns XMLElement for bookmarkStart
142
173
  */
143
174
  toStartXML(): XMLElement {
175
+ const attrs: Record<string, string | number> = {
176
+ 'w:id': this.id.toString(),
177
+ 'w:name': this.name,
178
+ };
179
+ // Table bookmark column range per ECMA-376 §17.16.5
180
+ if (this.colFirst !== undefined) attrs['w:colFirst'] = this.colFirst.toString();
181
+ if (this.colLast !== undefined) attrs['w:colLast'] = this.colLast.toString();
144
182
  return {
145
183
  name: 'w:bookmarkStart',
146
- attributes: {
147
- 'w:id': this.id.toString(),
148
- 'w:name': this.name,
149
- },
184
+ attributes: attrs,
150
185
  selfClosing: true,
151
186
  };
152
187
  }
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { Bookmark } from './Bookmark';
11
+ import { InvalidDocxError } from '../zip/errors';
11
12
 
12
13
  /**
13
14
  * Type for the centralized ID provider callback.
@@ -55,7 +56,7 @@ export class BookmarkManager {
55
56
 
56
57
  // Check for duplicate names
57
58
  if (this.bookmarks.has(name)) {
58
- throw new Error(
59
+ throw new InvalidDocxError(
59
60
  `Bookmark with name "${name}" already exists. Bookmark names must be unique within a document.`
60
61
  );
61
62
  }
@@ -83,7 +84,7 @@ export class BookmarkManager {
83
84
 
84
85
  // Check for duplicate names
85
86
  if (this.bookmarks.has(name)) {
86
- throw new Error(
87
+ throw new InvalidDocxError(
87
88
  `Bookmark with name "${name}" already exists. Bookmark names must be unique within a document.`
88
89
  );
89
90
  }
@@ -194,7 +195,7 @@ export class BookmarkManager {
194
195
  counter++;
195
196
  }
196
197
 
197
- throw new Error(`Could not generate unique bookmark name from base "${baseName}"`);
198
+ throw new InvalidDocxError(`Could not generate unique bookmark name from base "${baseName}"`);
198
199
  }
199
200
 
200
201
  /**
@@ -38,19 +38,31 @@ Header/Footer Elements
38
38
 
39
39
  Container for text and inline content. Key formatting: `alignment`, `spacing`, `indentation`, `pPrChange` (revision tracking), `keepNext`/`keepLines`/`pageBreakBefore` (pagination).
40
40
 
41
- **Rule:** `setKeepNext(true)` / `setKeepLines(true)` auto-clears `pageBreakBefore`.
41
+ **CT_OnOff properties:** `keepNext`, `keepLines`, `pageBreakBefore`, `suppressLineNumbers`, `suppressAutoHyphens`, `contextualSpacing`, `mirrorIndents`, `suppressOverlap` all use `!== undefined` semantics with `w:val` attribute, supporting explicit `false` to override style inheritance.
42
+
43
+ **Rule:** `setKeepNext(true)` / `setKeepLines(true)` auto-clears `pageBreakBefore` to `undefined` (absent, not explicit false).
44
+
45
+ **Alignment:** `ParagraphAlignment` includes bidi-aware values (`start`, `end`), CJK (`distribute`), Arabic kashida (`mediumKashida`, `highKashida`, `lowKashida`), and Thai (`thaiDistribute`).
46
+
47
+ **w14 attributes:** `paraId` and `textId` (Word 2010+) are auto-generated for new paragraphs during `prepareSave()`. Values are 8-char hex, must be < `0x80000000`.
42
48
 
43
49
  CJK methods: `setKinsoku()`, `setWordWrap()`, `setOverflowPunct()`, `setTopLinePunct()`, `setAutoSpaceDE()`, `setAutoSpaceDN()`.
44
50
 
45
51
  ### Run (`Run.ts`)
46
52
 
47
- Formatted text span. Character formatting (bold, italic, underline, strikethrough), font properties, color (normalized to uppercase 6-char hex), highlight, special chars (tabs, breaks, symbols), complex script support.
53
+ Formatted text span. Character formatting (bold, italic, underline, strikethrough), font properties, color (hex or `'auto'`), highlight, special chars (tabs, breaks, symbols), complex script support.
54
+
55
+ **CT_OnOff properties:** Boolean properties use `!== undefined` semantics: `undefined` = inherit from style, `true` = on, `false` = explicitly off (`w:val="0"`). This applies to: bold, bCs, italic, iCs, caps, smallCaps, strike, dstrike, snapToGrid.
56
+
57
+ **Vertical alignment:** `setSubscript()`, `setSuperscript()`, and `vertAlignBaseline` field for explicit baseline reset per ST_VerticalAlignRun.
48
58
 
49
59
  Theme fonts: `setFontAsciiTheme()`, `setFontHAnsiTheme()`, `setFontEastAsiaTheme()`, `setFontCsTheme()`.
50
60
  Underline: `setUnderlineColor()`, `setUnderlineThemeColor()`.
51
61
  w14 effects: `addRawW14Property(xml)` for Word 2010+ ligatures, numForm, textOutline.
52
62
  Form fields: `FormFieldData`, `FormFieldTextInput`, `FormFieldCheckBox`, `FormFieldDropDownList` interfaces.
53
63
 
64
+ **Static helper:** `Run.generateRunPropertiesXML(formatting)` — generates `w:rPr` with all 38+ CT_RPr elements in strict ECMA-376 order. Used by both Run and Style serialization.
65
+
54
66
  ### Table (`Table.ts`, `TableRow.ts`, `TableCell.ts`)
55
67
 
56
68
  Row/column management, cell merging (horizontal + vertical), border styling, property change tracking (`w:tblPrChange`), inter-row content preservation (bookmarks, comments between rows).
@@ -59,6 +71,10 @@ Navigation: `getFirstParagraph()`, `getLastParagraph()`.
59
71
  Layout: `getLayout()` returns `'fixed'` or `'auto'`.
60
72
  Legacy merge: `setHorizontalMerge()` / `getHorizontalMerge()` (hMerge).
61
73
 
74
+ **Constraints:** `removeRow()` prevents removing the last row (ECMA-376 requires >= 1 row). `insertRow()`/`insertRows()` use grid span count for correct column count with merged cells. `tblPrEx` is serialized before `trPr` in `w:tr` per CT_Row.
75
+
76
+ **TableRow:** `setHeightRule(rule)` sets height rule independently. When `w:hRule` is absent, the spec default is `"auto"` (not `"atLeast"`).
77
+
62
78
  **Nested Tables:** Raw XML passthrough via `TableCell.rawNestedContent[]`.
63
79
  ```typescript
64
80
  cell.addRawNestedContent(position, xml, "table")
@@ -223,19 +223,33 @@ export type PageVerticalAlignment = 'top' | 'center' | 'bottom' | 'both';
223
223
  export type CellVerticalAlignment = 'top' | 'center' | 'bottom';
224
224
 
225
225
  /**
226
- * Paragraph text alignment
226
+ * Paragraph text alignment per ECMA-376 §17.18.44 (ST_Jc)
227
227
  *
228
228
  * From: Paragraph.ts (ParagraphAlignment)
229
- * Note: 'both' is an alias for 'justify' in some contexts
229
+ * Note: 'both' is the OOXML name for justified text; 'justify' is accepted as an alias.
230
+ * 'start'/'end' are bidi-aware alternatives to 'left'/'right'.
230
231
  */
231
- export type ParagraphAlignment = 'left' | 'center' | 'right' | 'justify' | 'both';
232
-
233
- /**
234
- * Table alignment (horizontal positioning)
232
+ export type ParagraphAlignment =
233
+ | 'left'
234
+ | 'center'
235
+ | 'right'
236
+ | 'justify'
237
+ | 'both'
238
+ | 'start'
239
+ | 'end'
240
+ | 'distribute'
241
+ | 'mediumKashida'
242
+ | 'highKashida'
243
+ | 'lowKashida'
244
+ | 'thaiDistribute';
245
+
246
+ /**
247
+ * Table alignment (horizontal positioning) per ECMA-376 §17.18.45 (ST_JcTable)
235
248
  *
236
249
  * From: Table.ts (TableAlignment)
250
+ * 'start'/'end' are bidi-aware alternatives to 'left'/'right'.
237
251
  */
238
- export type TableAlignment = 'left' | 'center' | 'right';
252
+ export type TableAlignment = 'left' | 'center' | 'right' | 'start' | 'end';
239
253
 
240
254
  /**
241
255
  * Row justification/alignment options
@@ -466,7 +480,20 @@ export function isVerticalAlignment(value: string): value is VerticalAlignment {
466
480
  * Check if a value is a valid ParagraphAlignment
467
481
  */
468
482
  export function isParagraphAlignment(value: string): value is ParagraphAlignment {
469
- return ['left', 'center', 'right', 'justify', 'both'].includes(value);
483
+ return [
484
+ 'left',
485
+ 'center',
486
+ 'right',
487
+ 'justify',
488
+ 'both',
489
+ 'start',
490
+ 'end',
491
+ 'distribute',
492
+ 'mediumKashida',
493
+ 'highKashida',
494
+ 'lowKashida',
495
+ 'thaiDistribute',
496
+ ].includes(value);
470
497
  }
471
498
 
472
499
  /**
@@ -447,7 +447,7 @@ export class Field {
447
447
  */
448
448
  static createHyperlink(
449
449
  url: string,
450
- displayText: string = url,
450
+ _displayText: string = url,
451
451
  tooltip?: string,
452
452
  formatting?: RunFormatting
453
453
  ): Field {
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { XMLElement } from '../xml/XMLBuilder';
9
9
  import { Paragraph } from './Paragraph';
10
+ import { RunFormatting } from './Run';
10
11
  import { Table } from './Table';
11
12
 
12
13
  /**
@@ -105,6 +106,28 @@ export class Footer {
105
106
  return para;
106
107
  }
107
108
 
109
+ /**
110
+ * Adds formatted text to the footer as a new paragraph
111
+ *
112
+ * Convenience method that creates a paragraph with a single formatted run.
113
+ *
114
+ * @param text - Text content
115
+ * @param formatting - Optional run formatting (bold, font, size, etc.)
116
+ * @returns The created Paragraph for further customization
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * footer.addText('Page ', { size: 8 });
121
+ * footer.addText('Confidential', { italic: true, color: '888888' });
122
+ * ```
123
+ */
124
+ addText(text: string, formatting?: RunFormatting): Paragraph {
125
+ const para = new Paragraph();
126
+ para.addText(text, formatting);
127
+ this.elements.push(para);
128
+ return para;
129
+ }
130
+
108
131
  /**
109
132
  * Adds a table to the footer
110
133
  * @param table Table to add
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { XMLElement } from '../xml/XMLBuilder';
9
9
  import { Paragraph } from './Paragraph';
10
+ import { RunFormatting } from './Run';
10
11
  import { Table } from './Table';
11
12
 
12
13
  /**
@@ -105,6 +106,30 @@ export class Header {
105
106
  return para;
106
107
  }
107
108
 
109
+ /**
110
+ * Adds formatted text to the header as a new paragraph
111
+ *
112
+ * Convenience method that creates a paragraph with a single formatted run.
113
+ * For headers with multiple runs or complex content, use `createParagraph()`
114
+ * and build content manually.
115
+ *
116
+ * @param text - Text content
117
+ * @param formatting - Optional run formatting (bold, font, size, etc.)
118
+ * @returns The created Paragraph for further customization
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * header.addText('Company Name', { bold: true, font: 'Arial', size: 10 });
123
+ * header.addText('Confidential', { italic: true, color: 'FF0000' });
124
+ * ```
125
+ */
126
+ addText(text: string, formatting?: RunFormatting): Paragraph {
127
+ const para = new Paragraph();
128
+ para.addText(text, formatting);
129
+ this.elements.push(para);
130
+ return para;
131
+ }
132
+
108
133
  /**
109
134
  * Adds a table to the header
110
135
  * @param table Table to add
@@ -80,6 +80,8 @@ export interface ImageBorder {
80
80
  headEnd?: { type?: string; width?: string; length?: string };
81
81
  /** Tail end decoration */
82
82
  tailEnd?: { type?: string; width?: string; length?: string };
83
+ /** @internal Set by parser to suppress default fill in generator */
84
+ _fromParsed?: boolean;
83
85
  }
84
86
 
85
87
  /**
@@ -1486,9 +1488,14 @@ export class Image {
1486
1488
  if (typeof thicknessOrOptions === 'number') {
1487
1489
  this.border = { width: thicknessOrOptions };
1488
1490
  } else {
1489
- this.border = thicknessOrOptions;
1491
+ this.border = { ...thicknessOrOptions };
1492
+ delete this.border._fromParsed;
1490
1493
  }
1491
1494
 
1495
+ // Clear passthrough slots that conflict with explicit border
1496
+ this._rawPassthrough.delete('zero-width-ln');
1497
+ this._rawPassthrough.delete('spPr-noFill');
1498
+
1492
1499
  // Calculate space needed for border (half-width on each side)
1493
1500
  // Border is drawn centered on the edge
1494
1501
  const borderEmu = this.border.width * UNITS.EMUS_PER_POINT;
@@ -1512,6 +1519,7 @@ export class Image {
1512
1519
  */
1513
1520
  removeBorder(): this {
1514
1521
  this.border = undefined;
1522
+ this._rawPassthrough.delete('zero-width-ln');
1515
1523
  return this;
1516
1524
  }
1517
1525
 
@@ -1626,11 +1634,17 @@ export class Image {
1626
1634
  );
1627
1635
  }
1628
1636
 
1629
- // Border (a:ln) - full model (Group C)
1637
+ // Fill group: a:noFill (before a:ln per ECMA-376 CT_ShapeProperties ordering)
1630
1638
  if (this.border) {
1631
- // Add noFill element before the border line (required by Word)
1639
+ // Bordered images use noFill for shape fill
1640
+ spPrChildren.push(XMLBuilder.a('noFill'));
1641
+ } else if (this._rawPassthrough.has('spPr-noFill')) {
1642
+ // Preserve spPr-level a:noFill from loaded document (Bug C fix)
1632
1643
  spPrChildren.push(XMLBuilder.a('noFill'));
1644
+ }
1633
1645
 
1646
+ // Border (a:ln) - full model (Group C)
1647
+ if (this.border) {
1634
1648
  const ptToEmu = 12700;
1635
1649
  const widthEmu = this.border.width * ptToEmu;
1636
1650
  const lnAttrs: Record<string, string> = { w: widthEmu.toString() };
@@ -1655,8 +1669,9 @@ export class Image {
1655
1669
  ? XMLBuilder.a(this.border.fill.type, { val: this.border.fill.value }, colorChildren)
1656
1670
  : XMLBuilder.a(this.border.fill.type, { val: this.border.fill.value });
1657
1671
  lnChildren.push(XMLBuilder.a('solidFill', undefined, [colorEl]));
1658
- } else {
1659
- // Default: scheme color tx1 (backward compat)
1672
+ } else if (!this.border._fromParsed) {
1673
+ // Default fill for programmatic borders only (setBorder API backward compat).
1674
+ // Parsed borders without fill are intentionally left as-is.
1660
1675
  lnChildren.push(
1661
1676
  XMLBuilder.a('solidFill', undefined, [XMLBuilder.a('schemeClr', { val: 'tx1' })])
1662
1677
  );
@@ -1689,6 +1704,14 @@ export class Image {
1689
1704
  spPrChildren.push(XMLBuilder.a('ln', lnAttrs, lnChildren));
1690
1705
  }
1691
1706
 
1707
+ // Zero-width a:ln passthrough (e.g., <a:ln><a:noFill/></a:ln>) (BUG 8 fix)
1708
+ if (!this.border && this._rawPassthrough.has('zero-width-ln')) {
1709
+ spPrChildren.push({
1710
+ name: '__rawXml',
1711
+ rawXml: this._rawPassthrough.get('zero-width-ln')!,
1712
+ } as XMLElement);
1713
+ }
1714
+
1692
1715
  // Group B: Inject raw spPr effects passthrough (effectLst, scene3d, sp3d, etc.)
1693
1716
  if (this._rawPassthrough.has('spPr-effects')) {
1694
1717
  spPrChildren.push({