docxmlater 10.3.6 → 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.
- package/README.md +158 -7
- package/dist/core/Document.d.ts +97 -3
- package/dist/core/Document.d.ts.map +1 -1
- package/dist/core/Document.js +727 -50
- package/dist/core/Document.js.map +1 -1
- package/dist/core/DocumentContent.d.ts.map +1 -1
- package/dist/core/DocumentContent.js +0 -8
- package/dist/core/DocumentContent.js.map +1 -1
- package/dist/core/DocumentGenerator.d.ts.map +1 -1
- package/dist/core/DocumentGenerator.js +9 -5
- package/dist/core/DocumentGenerator.js.map +1 -1
- package/dist/core/DocumentParser.d.ts.map +1 -1
- package/dist/core/DocumentParser.js +573 -101
- package/dist/core/DocumentParser.js.map +1 -1
- package/dist/core/RelationshipManager.d.ts.map +1 -1
- package/dist/core/RelationshipManager.js +4 -3
- package/dist/core/RelationshipManager.js.map +1 -1
- package/dist/elements/Bookmark.d.ts +7 -0
- package/dist/elements/Bookmark.d.ts.map +1 -1
- package/dist/elements/Bookmark.js +24 -4
- package/dist/elements/Bookmark.js.map +1 -1
- package/dist/elements/BookmarkManager.d.ts.map +1 -1
- package/dist/elements/BookmarkManager.js +4 -3
- package/dist/elements/BookmarkManager.js.map +1 -1
- package/dist/elements/CommonTypes.d.ts +2 -2
- package/dist/elements/CommonTypes.d.ts.map +1 -1
- package/dist/elements/CommonTypes.js +14 -1
- package/dist/elements/CommonTypes.js.map +1 -1
- package/dist/elements/Field.d.ts +1 -1
- package/dist/elements/Field.d.ts.map +1 -1
- package/dist/elements/Field.js +1 -1
- package/dist/elements/Field.js.map +1 -1
- package/dist/elements/Footer.d.ts +2 -0
- package/dist/elements/Footer.d.ts.map +1 -1
- package/dist/elements/Footer.js +6 -0
- package/dist/elements/Footer.js.map +1 -1
- package/dist/elements/Header.d.ts +2 -0
- package/dist/elements/Header.d.ts.map +1 -1
- package/dist/elements/Header.js +6 -0
- package/dist/elements/Header.js.map +1 -1
- package/dist/elements/Image.d.ts.map +1 -1
- package/dist/elements/Image.js +3 -0
- package/dist/elements/Image.js.map +1 -1
- package/dist/elements/Paragraph.d.ts +81 -1
- package/dist/elements/Paragraph.d.ts.map +1 -1
- package/dist/elements/Paragraph.js +515 -21
- package/dist/elements/Paragraph.js.map +1 -1
- package/dist/elements/Revision.d.ts +0 -1
- package/dist/elements/Revision.d.ts.map +1 -1
- package/dist/elements/Revision.js +0 -12
- package/dist/elements/Revision.js.map +1 -1
- package/dist/elements/RevisionManager.d.ts +0 -1
- package/dist/elements/RevisionManager.d.ts.map +1 -1
- package/dist/elements/RevisionManager.js +0 -2
- package/dist/elements/RevisionManager.js.map +1 -1
- package/dist/elements/Run.d.ts +16 -4
- package/dist/elements/Run.d.ts.map +1 -1
- package/dist/elements/Run.js +114 -22
- package/dist/elements/Run.js.map +1 -1
- package/dist/elements/Section.d.ts +7 -1
- package/dist/elements/Section.d.ts.map +1 -1
- package/dist/elements/Section.js +185 -4
- package/dist/elements/Section.js.map +1 -1
- package/dist/elements/Shape.js.map +1 -1
- package/dist/elements/Table.d.ts +30 -1
- package/dist/elements/Table.d.ts.map +1 -1
- package/dist/elements/Table.js +357 -40
- package/dist/elements/Table.js.map +1 -1
- package/dist/elements/TableCell.d.ts +3 -0
- package/dist/elements/TableCell.d.ts.map +1 -1
- package/dist/elements/TableCell.js +30 -3
- package/dist/elements/TableCell.js.map +1 -1
- package/dist/elements/TableGridChange.d.ts +0 -1
- package/dist/elements/TableGridChange.d.ts.map +1 -1
- package/dist/elements/TableGridChange.js +0 -10
- package/dist/elements/TableGridChange.js.map +1 -1
- package/dist/elements/TableRow.d.ts +4 -0
- package/dist/elements/TableRow.d.ts.map +1 -1
- package/dist/elements/TableRow.js +31 -3
- package/dist/elements/TableRow.js.map +1 -1
- package/dist/formatting/AbstractNumbering.d.ts +5 -0
- package/dist/formatting/AbstractNumbering.d.ts.map +1 -1
- package/dist/formatting/AbstractNumbering.js +22 -0
- package/dist/formatting/AbstractNumbering.js.map +1 -1
- package/dist/formatting/NumberingLevel.d.ts.map +1 -1
- package/dist/formatting/NumberingLevel.js +3 -3
- package/dist/formatting/NumberingLevel.js.map +1 -1
- package/dist/formatting/Style.d.ts +1 -0
- package/dist/formatting/Style.d.ts.map +1 -1
- package/dist/formatting/Style.js +25 -59
- package/dist/formatting/Style.js.map +1 -1
- package/dist/formatting/StylesManager.d.ts +1 -0
- package/dist/formatting/StylesManager.d.ts.map +1 -1
- package/dist/formatting/StylesManager.js +12 -0
- package/dist/formatting/StylesManager.js.map +1 -1
- package/dist/helpers/CleanupHelper.js.map +1 -1
- package/dist/images/ImageOptimizer.d.ts.map +1 -1
- package/dist/images/ImageOptimizer.js +0 -1
- package/dist/images/ImageOptimizer.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/managers/DrawingManager.d.ts.map +1 -1
- package/dist/managers/DrawingManager.js +4 -2
- package/dist/managers/DrawingManager.js.map +1 -1
- package/dist/types/formatting.d.ts +2 -2
- package/dist/types/formatting.d.ts.map +1 -1
- package/dist/types/formatting.js.map +1 -1
- package/dist/utils/ChangelogGenerator.d.ts +2 -2
- package/dist/utils/ChangelogGenerator.d.ts.map +1 -1
- package/dist/utils/ChangelogGenerator.js +4 -5
- package/dist/utils/ChangelogGenerator.js.map +1 -1
- package/dist/utils/InMemoryRevisionAcceptor.d.ts.map +1 -1
- package/dist/utils/InMemoryRevisionAcceptor.js +0 -1
- package/dist/utils/InMemoryRevisionAcceptor.js.map +1 -1
- package/dist/utils/RevisionAwareProcessor.d.ts +2 -2
- package/dist/utils/RevisionAwareProcessor.d.ts.map +1 -1
- package/dist/utils/RevisionAwareProcessor.js +2 -2
- package/dist/utils/RevisionAwareProcessor.js.map +1 -1
- package/dist/utils/SelectiveRevisionAcceptor.d.ts +0 -2
- package/dist/utils/SelectiveRevisionAcceptor.d.ts.map +1 -1
- package/dist/utils/SelectiveRevisionAcceptor.js +0 -26
- package/dist/utils/SelectiveRevisionAcceptor.js.map +1 -1
- package/dist/utils/ShadingResolver.d.ts.map +1 -1
- package/dist/utils/ShadingResolver.js.map +1 -1
- package/dist/utils/acceptRevisions.js +1 -1
- package/dist/utils/acceptRevisions.js.map +1 -1
- package/dist/utils/stripTrackedChanges.js +1 -1
- package/dist/utils/stripTrackedChanges.js.map +1 -1
- package/dist/utils/units.d.ts.map +1 -1
- package/dist/utils/units.js +1 -1
- package/dist/utils/units.js.map +1 -1
- package/dist/validation/RevisionAutoFixer.d.ts +2 -1
- package/dist/validation/RevisionAutoFixer.d.ts.map +1 -1
- package/dist/validation/RevisionAutoFixer.js.map +1 -1
- package/package.json +10 -1
- package/src/constants/CLAUDE.md +28 -0
- package/src/core/CLAUDE.md +4 -0
- package/src/core/Document.ts +1755 -85
- package/src/core/DocumentContent.ts +0 -11
- package/src/core/DocumentGenerator.ts +11 -12
- package/src/core/DocumentParser.ts +599 -138
- package/src/core/RelationshipManager.ts +6 -3
- package/src/elements/Bookmark.ts +39 -4
- package/src/elements/BookmarkManager.ts +4 -3
- package/src/elements/CLAUDE.md +18 -2
- package/src/elements/CommonTypes.ts +35 -8
- package/src/elements/Field.ts +1 -1
- package/src/elements/Footer.ts +23 -0
- package/src/elements/Header.ts +25 -0
- package/src/elements/Image.ts +5 -0
- package/src/elements/Paragraph.ts +1069 -41
- package/src/elements/Revision.ts +0 -19
- package/src/elements/RevisionManager.ts +1 -3
- package/src/elements/Run.ts +265 -35
- package/src/elements/Section.ts +214 -8
- package/src/elements/Shape.ts +1 -1
- package/src/elements/Table.ts +850 -61
- package/src/elements/TableCell.ts +84 -10
- package/src/elements/TableGridChange.ts +2 -16
- package/src/elements/TableRow.ts +94 -9
- package/src/formatting/AbstractNumbering.ts +42 -1
- package/src/formatting/CLAUDE.md +4 -0
- package/src/formatting/NumberingLevel.ts +11 -7
- package/src/formatting/Style.ts +39 -71
- package/src/formatting/StylesManager.ts +36 -0
- package/src/helpers/CleanupHelper.ts +1 -1
- package/src/images/ImageOptimizer.ts +0 -3
- package/src/index.ts +1 -1
- package/src/managers/DrawingManager.ts +5 -3
- package/src/tracking/CLAUDE.md +30 -0
- package/src/types/CLAUDE.md +39 -0
- package/src/types/formatting.ts +2 -2
- package/src/utils/CLAUDE.md +15 -0
- package/src/utils/ChangelogGenerator.ts +4 -5
- package/src/utils/InMemoryRevisionAcceptor.ts +0 -9
- package/src/utils/RevisionAwareProcessor.ts +2 -3
- package/src/utils/SelectiveRevisionAcceptor.ts +0 -39
- package/src/utils/ShadingResolver.ts +0 -1
- package/src/utils/acceptRevisions.ts +1 -1
- package/src/utils/stripTrackedChanges.ts +1 -1
- package/src/utils/units.ts +2 -1
- package/src/validation/CLAUDE.md +40 -0
- package/src/validation/RevisionAutoFixer.ts +2 -1
package/src/elements/Revision.ts
CHANGED
|
@@ -866,27 +866,8 @@ export class Revision {
|
|
|
866
866
|
};
|
|
867
867
|
}
|
|
868
868
|
|
|
869
|
-
private createDeletedHyperlinkXml(hyperlink: import('./Hyperlink').Hyperlink): XMLElement {
|
|
870
|
-
// Get the hyperlink's normal XML
|
|
871
|
-
const hyperlinkXml = hyperlink.toXML();
|
|
872
|
-
|
|
873
|
-
// Transform nested runs: w:t -> w:delText (or w:delInstrText for field instructions)
|
|
874
|
-
if (hyperlinkXml.children) {
|
|
875
|
-
hyperlinkXml.children = hyperlinkXml.children.map((child) => {
|
|
876
|
-
if (typeof child === 'object' && child.name === 'w:r') {
|
|
877
|
-
// Transform the run's text elements
|
|
878
|
-
return this.convertRunXmlToDeleted(child);
|
|
879
|
-
}
|
|
880
|
-
return child;
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
return hyperlinkXml;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
869
|
/**
|
|
888
870
|
* Converts a run XMLElement to use deleted text elements
|
|
889
|
-
* Helper for createDeletedHyperlinkXml
|
|
890
871
|
*/
|
|
891
872
|
private convertRunXmlToDeleted(runXml: XMLElement): XMLElement {
|
|
892
873
|
const deletedTextElement = this.isFieldInstruction ? 'w:delInstrText' : 'w:delText';
|
|
@@ -67,8 +67,6 @@ export class RevisionManager {
|
|
|
67
67
|
private revisionsByTypeCache = new Map<RevisionType, Revision[]>();
|
|
68
68
|
private revisionsByAuthorCache = new Map<string, Revision[]>();
|
|
69
69
|
private revisionsByCategoryCache = new Map<RevisionCategory, Revision[]>();
|
|
70
|
-
private cacheValid = true;
|
|
71
|
-
|
|
72
70
|
/**
|
|
73
71
|
* Invalidates all caches. Called when revisions are added/removed.
|
|
74
72
|
* @private
|
|
@@ -77,7 +75,7 @@ export class RevisionManager {
|
|
|
77
75
|
this.revisionsByTypeCache.clear();
|
|
78
76
|
this.revisionsByAuthorCache.clear();
|
|
79
77
|
this.revisionsByCategoryCache.clear();
|
|
80
|
-
|
|
78
|
+
// Cache cleared
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
/**
|
package/src/elements/Run.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { deepClone } from '../utils/deepClone';
|
|
7
7
|
import { formatDateForXml } from '../utils/dateFormatting';
|
|
8
|
+
import { isEqualFormatting } from '../utils/formatting';
|
|
8
9
|
import { logSerialization, logTextDirection } from '../utils/diagnostics';
|
|
9
10
|
import { defaultLogger } from '../utils/logger';
|
|
10
11
|
import { normalizeColor, validateRunText } from '../utils/validation';
|
|
@@ -17,7 +18,6 @@ import {
|
|
|
17
18
|
ShadingConfig,
|
|
18
19
|
buildShadingAttributes,
|
|
19
20
|
ExtendedBorderStyle,
|
|
20
|
-
BorderDefinition,
|
|
21
21
|
} from './CommonTypes';
|
|
22
22
|
import type { RunPropertyChange } from './PropertyChangeTypes';
|
|
23
23
|
// Type-only import to avoid circular dependency (Revision imports Run)
|
|
@@ -71,6 +71,8 @@ export interface RunContent {
|
|
|
71
71
|
value?: string;
|
|
72
72
|
/** Break type (only for 'break' type) */
|
|
73
73
|
breakType?: BreakType;
|
|
74
|
+
/** Break clear type (only for 'break' with type='textWrapping') per ECMA-376 §17.3.3.1 */
|
|
75
|
+
breakClear?: 'none' | 'left' | 'right' | 'all';
|
|
74
76
|
/** Field character subtype (only for 'fieldChar' type) */
|
|
75
77
|
fieldCharType?: 'begin' | 'separate' | 'end';
|
|
76
78
|
/** Whether the field char is marked dirty */
|
|
@@ -135,6 +137,18 @@ export type ShadingPattern = CommonShadingPattern;
|
|
|
135
137
|
*/
|
|
136
138
|
export type CharacterShading = ShadingConfig;
|
|
137
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Language configuration per ECMA-376 CT_Language (§17.3.2.20)
|
|
142
|
+
*/
|
|
143
|
+
export interface LanguageConfig {
|
|
144
|
+
/** Language for Latin text (e.g., 'en-US') */
|
|
145
|
+
val?: string;
|
|
146
|
+
/** Language for East Asian text (e.g., 'ja-JP', 'zh-CN') */
|
|
147
|
+
eastAsia?: string;
|
|
148
|
+
/** Language for complex script / bidirectional text (e.g., 'ar-SA', 'he-IL') */
|
|
149
|
+
bidi?: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
/**
|
|
139
153
|
* East Asian typography layout options
|
|
140
154
|
*/
|
|
@@ -269,8 +283,8 @@ export interface RunFormatting {
|
|
|
269
283
|
position?: number;
|
|
270
284
|
/** Kerning threshold in half-points (font size at which kerning starts) */
|
|
271
285
|
kerning?: number;
|
|
272
|
-
/** Language code (e.g., 'en-US', 'fr-FR', 'es-ES') */
|
|
273
|
-
language?: string;
|
|
286
|
+
/** Language code (e.g., 'en-US', 'fr-FR', 'es-ES') or CT_Language object */
|
|
287
|
+
language?: string | LanguageConfig;
|
|
274
288
|
/** Underline text. Use "none" to explicitly override style underline. */
|
|
275
289
|
underline?: boolean | 'single' | 'double' | 'thick' | 'dotted' | 'dash' | 'none';
|
|
276
290
|
/** Underline color in hex format (without #) per ECMA-376 Part 1 §17.3.2.40 */
|
|
@@ -289,6 +303,8 @@ export interface RunFormatting {
|
|
|
289
303
|
subscript?: boolean;
|
|
290
304
|
/** Superscript */
|
|
291
305
|
superscript?: boolean;
|
|
306
|
+
/** Explicit baseline vertical alignment (w:vertAlign w:val="baseline" per §17.18.96) */
|
|
307
|
+
vertAlignBaseline?: boolean;
|
|
292
308
|
/** Font name */
|
|
293
309
|
font?: string;
|
|
294
310
|
/** Font size in points (half-points for Word) */
|
|
@@ -312,7 +328,7 @@ export interface RunFormatting {
|
|
|
312
328
|
* Applied to themeColor to create darker variations
|
|
313
329
|
*/
|
|
314
330
|
themeShade?: number;
|
|
315
|
-
/** Highlight color */
|
|
331
|
+
/** Highlight color per ECMA-376 ST_HighlightColor ('none' explicitly removes highlight) */
|
|
316
332
|
highlight?:
|
|
317
333
|
| 'yellow'
|
|
318
334
|
| 'green'
|
|
@@ -329,7 +345,8 @@ export interface RunFormatting {
|
|
|
329
345
|
| 'darkGray'
|
|
330
346
|
| 'lightGray'
|
|
331
347
|
| 'black'
|
|
332
|
-
| 'white'
|
|
348
|
+
| 'white'
|
|
349
|
+
| 'none';
|
|
333
350
|
/** Small caps */
|
|
334
351
|
smallCaps?: boolean;
|
|
335
352
|
/** All caps */
|
|
@@ -582,6 +599,82 @@ export class Run {
|
|
|
582
599
|
.join('');
|
|
583
600
|
}
|
|
584
601
|
|
|
602
|
+
/**
|
|
603
|
+
* Gets only the literal text content, excluding special characters
|
|
604
|
+
*
|
|
605
|
+
* Unlike `getText()` which maps tabs to `\t` and breaks to `\n`,
|
|
606
|
+
* this method returns only the actual text content from `w:t` elements,
|
|
607
|
+
* ignoring tabs, breaks, carriage returns, hyphens, and field chars.
|
|
608
|
+
* Useful for word counting, search indexing, and plain-text extraction.
|
|
609
|
+
*
|
|
610
|
+
* @returns Text content only, with no special character representations
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* ```typescript
|
|
614
|
+
* const run = new Run('');
|
|
615
|
+
* run.appendText('Hello');
|
|
616
|
+
* run.addTab();
|
|
617
|
+
* run.appendText('World');
|
|
618
|
+
*
|
|
619
|
+
* run.getText(); // "Hello\tWorld"
|
|
620
|
+
* run.getPlainText(); // "HelloWorld"
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
623
|
+
getPlainText(): string {
|
|
624
|
+
return this.content
|
|
625
|
+
.filter((c) => c.type === 'text')
|
|
626
|
+
.map((c) => c.value || '')
|
|
627
|
+
.join('');
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Checks whether this run is equal to another (same text and formatting)
|
|
632
|
+
*
|
|
633
|
+
* Compares both the text content (via `getText()`) and the formatting
|
|
634
|
+
* properties (deep equality). Useful for deduplication, testing, and
|
|
635
|
+
* determining whether two runs can be merged.
|
|
636
|
+
*
|
|
637
|
+
* @param other - Run to compare against
|
|
638
|
+
* @returns True if both text and formatting are identical
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* ```typescript
|
|
642
|
+
* const a = new Run('Hello', { bold: true });
|
|
643
|
+
* const b = new Run('Hello', { bold: true });
|
|
644
|
+
* const c = new Run('Hello', { italic: true });
|
|
645
|
+
*
|
|
646
|
+
* a.equals(b); // true
|
|
647
|
+
* a.equals(c); // false
|
|
648
|
+
* ```
|
|
649
|
+
*/
|
|
650
|
+
equals(other: Run): boolean {
|
|
651
|
+
if (this.getText() !== other.getText()) return false;
|
|
652
|
+
return isEqualFormatting(
|
|
653
|
+
this.getFormatting() as unknown as Record<string, unknown>,
|
|
654
|
+
other.getFormatting() as unknown as Record<string, unknown>
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Checks whether this run has the same formatting as another (ignoring text)
|
|
660
|
+
*
|
|
661
|
+
* @param other - Run to compare formatting against
|
|
662
|
+
* @returns True if formatting is identical
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```typescript
|
|
666
|
+
* const a = new Run('Hello', { bold: true });
|
|
667
|
+
* const b = new Run('World', { bold: true });
|
|
668
|
+
* a.hasSameFormatting(b); // true (different text, same formatting)
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
671
|
+
hasSameFormatting(other: Run): boolean {
|
|
672
|
+
return isEqualFormatting(
|
|
673
|
+
this.getFormatting() as unknown as Record<string, unknown>,
|
|
674
|
+
other.getFormatting() as unknown as Record<string, unknown>
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
585
678
|
/**
|
|
586
679
|
* Sets the text content of the run
|
|
587
680
|
*
|
|
@@ -1319,11 +1412,11 @@ export class Run {
|
|
|
1319
1412
|
}
|
|
1320
1413
|
|
|
1321
1414
|
/**
|
|
1322
|
-
* Sets language
|
|
1323
|
-
*
|
|
1324
|
-
*
|
|
1415
|
+
* Sets language per ECMA-376 Part 1 §17.3.2.20 (CT_Language)
|
|
1416
|
+
* @param language - Language code string (sets w:val) or LanguageConfig object
|
|
1417
|
+
* with val (Latin), eastAsia (CJK), bidi (RTL/complex script) attributes
|
|
1325
1418
|
*/
|
|
1326
|
-
setLanguage(language: string): this {
|
|
1419
|
+
setLanguage(language: string | LanguageConfig): this {
|
|
1327
1420
|
const previousValue = this.formatting.language;
|
|
1328
1421
|
this.formatting.language = language;
|
|
1329
1422
|
if (this.trackingContext?.isEnabled() && previousValue !== language) {
|
|
@@ -2150,6 +2243,9 @@ export class Run {
|
|
|
2150
2243
|
if (contentElement.breakType) {
|
|
2151
2244
|
attrs['w:type'] = contentElement.breakType;
|
|
2152
2245
|
}
|
|
2246
|
+
if (contentElement.breakClear) {
|
|
2247
|
+
attrs['w:clear'] = contentElement.breakClear;
|
|
2248
|
+
}
|
|
2153
2249
|
runChildren.push(
|
|
2154
2250
|
XMLBuilder.wSelf('br', Object.keys(attrs).length > 0 ? attrs : undefined)
|
|
2155
2251
|
);
|
|
@@ -2530,8 +2626,8 @@ export class Run {
|
|
|
2530
2626
|
* run.addBreak('column'); // Column break
|
|
2531
2627
|
* ```
|
|
2532
2628
|
*/
|
|
2533
|
-
addBreak(breakType?: BreakType): this {
|
|
2534
|
-
this.content.push({ type: 'break', breakType });
|
|
2629
|
+
addBreak(breakType?: BreakType, breakClear?: 'none' | 'left' | 'right' | 'all'): this {
|
|
2630
|
+
this.content.push({ type: 'break', breakType, breakClear });
|
|
2535
2631
|
return this;
|
|
2536
2632
|
}
|
|
2537
2633
|
|
|
@@ -2642,43 +2738,50 @@ export class Run {
|
|
|
2642
2738
|
}
|
|
2643
2739
|
|
|
2644
2740
|
// 3. w:b — Bold
|
|
2645
|
-
|
|
2646
|
-
|
|
2741
|
+
// Emit val="0" when explicitly false to override style-inherited bold
|
|
2742
|
+
if (formatting.bold !== undefined) {
|
|
2743
|
+
rPrChildren.push(XMLBuilder.wSelf('b', { 'w:val': formatting.bold ? '1' : '0' }));
|
|
2647
2744
|
}
|
|
2648
2745
|
|
|
2649
2746
|
// 4. w:bCs — Bold complex script
|
|
2650
|
-
if (formatting.complexScriptBold) {
|
|
2651
|
-
rPrChildren.push(
|
|
2747
|
+
if (formatting.complexScriptBold !== undefined) {
|
|
2748
|
+
rPrChildren.push(
|
|
2749
|
+
XMLBuilder.wSelf('bCs', { 'w:val': formatting.complexScriptBold ? '1' : '0' })
|
|
2750
|
+
);
|
|
2652
2751
|
}
|
|
2653
2752
|
|
|
2654
2753
|
// 5. w:i — Italic
|
|
2655
|
-
if (formatting.italic) {
|
|
2656
|
-
rPrChildren.push(XMLBuilder.wSelf('i', { 'w:val': '1' }));
|
|
2754
|
+
if (formatting.italic !== undefined) {
|
|
2755
|
+
rPrChildren.push(XMLBuilder.wSelf('i', { 'w:val': formatting.italic ? '1' : '0' }));
|
|
2657
2756
|
}
|
|
2658
2757
|
|
|
2659
2758
|
// 6. w:iCs — Italic complex script
|
|
2660
|
-
if (formatting.complexScriptItalic) {
|
|
2661
|
-
rPrChildren.push(
|
|
2759
|
+
if (formatting.complexScriptItalic !== undefined) {
|
|
2760
|
+
rPrChildren.push(
|
|
2761
|
+
XMLBuilder.wSelf('iCs', { 'w:val': formatting.complexScriptItalic ? '1' : '0' })
|
|
2762
|
+
);
|
|
2662
2763
|
}
|
|
2663
2764
|
|
|
2664
|
-
// 7. w:caps — All caps
|
|
2665
|
-
if (formatting.allCaps) {
|
|
2666
|
-
rPrChildren.push(XMLBuilder.wSelf('caps', { 'w:val': '1' }));
|
|
2765
|
+
// 7. w:caps — All caps (emit val="0" when explicitly false to override style)
|
|
2766
|
+
if (formatting.allCaps !== undefined) {
|
|
2767
|
+
rPrChildren.push(XMLBuilder.wSelf('caps', { 'w:val': formatting.allCaps ? '1' : '0' }));
|
|
2667
2768
|
}
|
|
2668
2769
|
|
|
2669
2770
|
// 8. w:smallCaps — Small caps
|
|
2670
|
-
if (formatting.smallCaps) {
|
|
2671
|
-
rPrChildren.push(
|
|
2771
|
+
if (formatting.smallCaps !== undefined) {
|
|
2772
|
+
rPrChildren.push(
|
|
2773
|
+
XMLBuilder.wSelf('smallCaps', { 'w:val': formatting.smallCaps ? '1' : '0' })
|
|
2774
|
+
);
|
|
2672
2775
|
}
|
|
2673
2776
|
|
|
2674
2777
|
// 9. w:strike — Single strikethrough
|
|
2675
|
-
if (formatting.strike) {
|
|
2676
|
-
rPrChildren.push(XMLBuilder.wSelf('strike', { 'w:val': '1' }));
|
|
2778
|
+
if (formatting.strike !== undefined) {
|
|
2779
|
+
rPrChildren.push(XMLBuilder.wSelf('strike', { 'w:val': formatting.strike ? '1' : '0' }));
|
|
2677
2780
|
}
|
|
2678
2781
|
|
|
2679
2782
|
// 10. w:dstrike — Double strikethrough
|
|
2680
|
-
if (formatting.dstrike) {
|
|
2681
|
-
rPrChildren.push(XMLBuilder.wSelf('dstrike', { 'w:val': '1' }));
|
|
2783
|
+
if (formatting.dstrike !== undefined) {
|
|
2784
|
+
rPrChildren.push(XMLBuilder.wSelf('dstrike', { 'w:val': formatting.dstrike ? '1' : '0' }));
|
|
2682
2785
|
}
|
|
2683
2786
|
|
|
2684
2787
|
// 11. w:outline — Outline text effect
|
|
@@ -2707,8 +2810,12 @@ export class Run {
|
|
|
2707
2810
|
}
|
|
2708
2811
|
|
|
2709
2812
|
// 16. w:snapToGrid — Snap to grid
|
|
2710
|
-
|
|
2711
|
-
|
|
2813
|
+
// Per ECMA-376 §17.3.2.34, the default when absent is true (snap to grid ON).
|
|
2814
|
+
// Must emit w:val="0" when explicitly false to preserve the override on round-trip.
|
|
2815
|
+
if (formatting.snapToGrid !== undefined) {
|
|
2816
|
+
rPrChildren.push(
|
|
2817
|
+
XMLBuilder.wSelf('snapToGrid', { 'w:val': formatting.snapToGrid ? '1' : '0' })
|
|
2818
|
+
);
|
|
2712
2819
|
}
|
|
2713
2820
|
|
|
2714
2821
|
// 17. w:vanish — Hidden text
|
|
@@ -2839,12 +2946,13 @@ export class Run {
|
|
|
2839
2946
|
rPrChildren.push(XMLBuilder.wSelf('fitText', { 'w:val': formatting.fitText }));
|
|
2840
2947
|
}
|
|
2841
2948
|
|
|
2842
|
-
// 32. w:vertAlign — Subscript/superscript
|
|
2949
|
+
// 32. w:vertAlign — Subscript/superscript/baseline per ECMA-376 §17.18.96
|
|
2843
2950
|
if (formatting.subscript) {
|
|
2844
2951
|
rPrChildren.push(XMLBuilder.wSelf('vertAlign', { 'w:val': 'subscript' }));
|
|
2845
|
-
}
|
|
2846
|
-
if (formatting.superscript) {
|
|
2952
|
+
} else if (formatting.superscript) {
|
|
2847
2953
|
rPrChildren.push(XMLBuilder.wSelf('vertAlign', { 'w:val': 'superscript' }));
|
|
2954
|
+
} else if (formatting.vertAlignBaseline) {
|
|
2955
|
+
rPrChildren.push(XMLBuilder.wSelf('vertAlign', { 'w:val': 'baseline' }));
|
|
2848
2956
|
}
|
|
2849
2957
|
|
|
2850
2958
|
// 33. w:rtl — Right-to-left text
|
|
@@ -2862,9 +2970,19 @@ export class Run {
|
|
|
2862
2970
|
rPrChildren.push(XMLBuilder.wSelf('em', { 'w:val': formatting.emphasis }));
|
|
2863
2971
|
}
|
|
2864
2972
|
|
|
2865
|
-
// 36. w:lang — Language
|
|
2973
|
+
// 36. w:lang — Language (CT_Language: w:val, w:eastAsia, w:bidi)
|
|
2866
2974
|
if (formatting.language) {
|
|
2867
|
-
|
|
2975
|
+
const langAttrs: Record<string, string> = {};
|
|
2976
|
+
if (typeof formatting.language === 'string') {
|
|
2977
|
+
langAttrs['w:val'] = formatting.language;
|
|
2978
|
+
} else {
|
|
2979
|
+
if (formatting.language.val) langAttrs['w:val'] = formatting.language.val;
|
|
2980
|
+
if (formatting.language.eastAsia) langAttrs['w:eastAsia'] = formatting.language.eastAsia;
|
|
2981
|
+
if (formatting.language.bidi) langAttrs['w:bidi'] = formatting.language.bidi;
|
|
2982
|
+
}
|
|
2983
|
+
if (Object.keys(langAttrs).length > 0) {
|
|
2984
|
+
rPrChildren.push(XMLBuilder.wSelf('lang', langAttrs));
|
|
2985
|
+
}
|
|
2868
2986
|
}
|
|
2869
2987
|
|
|
2870
2988
|
// 37. w:eastAsianLayout — East Asian layout
|
|
@@ -2986,6 +3104,118 @@ export class Run {
|
|
|
2986
3104
|
return this;
|
|
2987
3105
|
}
|
|
2988
3106
|
|
|
3107
|
+
/**
|
|
3108
|
+
* Splits this run at a character offset, returning the removed tail as a new run
|
|
3109
|
+
*
|
|
3110
|
+
* The current run keeps content before the offset; the returned run gets content
|
|
3111
|
+
* from the offset onward. Both runs share the same formatting (deep-cloned).
|
|
3112
|
+
* Content-aware: correctly handles splits within text elements and preserves
|
|
3113
|
+
* special elements (tabs, breaks, soft hyphens) at their correct positions.
|
|
3114
|
+
*
|
|
3115
|
+
* Character offsets follow the same mapping as `getText()`:
|
|
3116
|
+
* text characters count by length, tabs/breaks/CR each count as 1.
|
|
3117
|
+
*
|
|
3118
|
+
* @param offset - Character position to split at (0-based). Content from this
|
|
3119
|
+
* position onward moves to the new run.
|
|
3120
|
+
* @returns A new Run containing content from `offset` onward, with cloned formatting.
|
|
3121
|
+
* Returns a new empty Run if offset >= text length. Returns a clone of the full
|
|
3122
|
+
* run (and empties this one) if offset <= 0.
|
|
3123
|
+
*
|
|
3124
|
+
* @example
|
|
3125
|
+
* ```typescript
|
|
3126
|
+
* const run = new Run('Hello World', { bold: true });
|
|
3127
|
+
* const tail = run.splitAt(5);
|
|
3128
|
+
* run.getText(); // "Hello"
|
|
3129
|
+
* tail.getText(); // " World"
|
|
3130
|
+
* tail.getFormatting().bold; // true (formatting preserved)
|
|
3131
|
+
* ```
|
|
3132
|
+
*
|
|
3133
|
+
* @example
|
|
3134
|
+
* ```typescript
|
|
3135
|
+
* // Split within content containing tabs
|
|
3136
|
+
* const run = new Run('');
|
|
3137
|
+
* run.appendText('Name');
|
|
3138
|
+
* run.addTab();
|
|
3139
|
+
* run.appendText('Value');
|
|
3140
|
+
* const tail = run.splitAt(5); // Split after "Name\t"
|
|
3141
|
+
* run.getText(); // "Name\t"
|
|
3142
|
+
* tail.getText(); // "Value"
|
|
3143
|
+
* ```
|
|
3144
|
+
*/
|
|
3145
|
+
splitAt(offset: number): Run {
|
|
3146
|
+
const totalLength = this.getText().length;
|
|
3147
|
+
|
|
3148
|
+
// Edge case: split at or past end — return empty run
|
|
3149
|
+
if (offset >= totalLength) {
|
|
3150
|
+
return Run.createFromContent([], deepClone(this.formatting));
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
// Edge case: split at or before start — move everything to new run
|
|
3154
|
+
if (offset <= 0) {
|
|
3155
|
+
const allContent = this.content;
|
|
3156
|
+
this.content = [];
|
|
3157
|
+
return Run.createFromContent(allContent, deepClone(this.formatting));
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
const beforeContent: RunContent[] = [];
|
|
3161
|
+
const afterContent: RunContent[] = [];
|
|
3162
|
+
let charPos = 0;
|
|
3163
|
+
let splitDone = false;
|
|
3164
|
+
|
|
3165
|
+
for (const item of this.content) {
|
|
3166
|
+
if (splitDone) {
|
|
3167
|
+
afterContent.push(deepClone(item));
|
|
3168
|
+
continue;
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
const itemLength = this.getContentItemLength(item);
|
|
3172
|
+
|
|
3173
|
+
if (charPos + itemLength <= offset) {
|
|
3174
|
+
// Entire item goes to "before"
|
|
3175
|
+
beforeContent.push(deepClone(item));
|
|
3176
|
+
charPos += itemLength;
|
|
3177
|
+
} else if (charPos >= offset) {
|
|
3178
|
+
// Entire item goes to "after"
|
|
3179
|
+
afterContent.push(deepClone(item));
|
|
3180
|
+
splitDone = true;
|
|
3181
|
+
} else {
|
|
3182
|
+
// Split is within this item — only text elements can be split mid-item
|
|
3183
|
+
if (item.type === 'text' && item.value) {
|
|
3184
|
+
const splitIndex = offset - charPos;
|
|
3185
|
+
beforeContent.push({ type: 'text', value: item.value.slice(0, splitIndex) });
|
|
3186
|
+
afterContent.push({ type: 'text', value: item.value.slice(splitIndex) });
|
|
3187
|
+
} else {
|
|
3188
|
+
// Non-text item at boundary goes to "after"
|
|
3189
|
+
afterContent.push(deepClone(item));
|
|
3190
|
+
}
|
|
3191
|
+
splitDone = true;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
this.content = beforeContent;
|
|
3196
|
+
return Run.createFromContent(afterContent, deepClone(this.formatting));
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
/**
|
|
3200
|
+
* Gets the character length contribution of a single content item.
|
|
3201
|
+
* Matches the mapping used by getText().
|
|
3202
|
+
* @internal
|
|
3203
|
+
*/
|
|
3204
|
+
private getContentItemLength(item: RunContent): number {
|
|
3205
|
+
switch (item.type) {
|
|
3206
|
+
case 'text':
|
|
3207
|
+
return (item.value || '').length;
|
|
3208
|
+
case 'tab':
|
|
3209
|
+
case 'break':
|
|
3210
|
+
case 'carriageReturn':
|
|
3211
|
+
case 'softHyphen':
|
|
3212
|
+
case 'noBreakHyphen':
|
|
3213
|
+
return 1;
|
|
3214
|
+
default:
|
|
3215
|
+
return 0;
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
|
|
2989
3219
|
/**
|
|
2990
3220
|
* Clears run formatting properties that conflict with a style definition.
|
|
2991
3221
|
* Uses smart clearing: only removes properties that DIFFER from the style.
|