@cj-tech-master/excelts 9.5.0 → 9.5.1
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/dist/browser/modules/pdf/excel-bridge.js +27 -1
- package/dist/browser/modules/pdf/render/layout-engine.js +74 -9
- package/dist/browser/modules/pdf/render/style-converter.d.ts +1 -1
- package/dist/browser/modules/pdf/render/style-converter.js +98 -1
- package/dist/browser/modules/pdf/types.d.ts +1 -0
- package/dist/browser/modules/word/color-utils.d.ts +18 -0
- package/dist/browser/modules/word/color-utils.js +94 -0
- package/dist/browser/modules/word/content-types.d.ts +15 -15
- package/dist/browser/modules/word/content-types.js +39 -43
- package/dist/browser/modules/word/crypto.d.ts +17 -0
- package/dist/browser/modules/word/crypto.js +18 -0
- package/dist/browser/modules/word/document-io.d.ts +58 -0
- package/dist/browser/modules/word/document-io.js +239 -0
- package/dist/browser/modules/word/document.d.ts +64 -135
- package/dist/browser/modules/word/document.js +207 -469
- package/dist/browser/modules/word/docx-packager.js +90 -90
- package/dist/browser/modules/word/html-renderer.js +1 -1
- package/dist/browser/modules/word/html.d.ts +13 -0
- package/dist/browser/modules/word/html.js +12 -0
- package/dist/browser/modules/word/index.base.d.ts +6 -9
- package/dist/browser/modules/word/index.base.js +7 -10
- package/dist/browser/modules/word/namespaces.d.ts +159 -0
- package/dist/browser/modules/word/namespaces.js +189 -0
- package/dist/browser/modules/word/relationships.d.ts +15 -16
- package/dist/browser/modules/word/relationships.js +37 -45
- package/dist/cjs/modules/pdf/excel-bridge.js +27 -1
- package/dist/cjs/modules/pdf/render/layout-engine.js +74 -9
- package/dist/cjs/modules/pdf/render/style-converter.js +98 -1
- package/dist/cjs/modules/word/color-utils.js +97 -0
- package/dist/cjs/modules/word/content-types.js +44 -45
- package/dist/cjs/modules/word/crypto.js +34 -0
- package/dist/cjs/modules/word/document-io.js +244 -0
- package/dist/cjs/modules/word/document.js +209 -473
- package/dist/cjs/modules/word/docx-packager.js +88 -88
- package/dist/cjs/modules/word/html-renderer.js +2 -2
- package/dist/cjs/modules/word/html.js +16 -0
- package/dist/cjs/modules/word/index.base.js +17 -27
- package/dist/cjs/modules/word/namespaces.js +192 -0
- package/dist/cjs/modules/word/relationships.js +42 -47
- package/dist/esm/modules/pdf/excel-bridge.js +27 -1
- package/dist/esm/modules/pdf/render/layout-engine.js +74 -9
- package/dist/esm/modules/pdf/render/style-converter.js +98 -1
- package/dist/esm/modules/word/color-utils.js +94 -0
- package/dist/esm/modules/word/content-types.js +39 -43
- package/dist/esm/modules/word/crypto.js +18 -0
- package/dist/esm/modules/word/document-io.js +239 -0
- package/dist/esm/modules/word/document.js +207 -469
- package/dist/esm/modules/word/docx-packager.js +90 -90
- package/dist/esm/modules/word/html-renderer.js +1 -1
- package/dist/esm/modules/word/html.js +12 -0
- package/dist/esm/modules/word/index.base.js +7 -10
- package/dist/esm/modules/word/namespaces.js +189 -0
- package/dist/esm/modules/word/relationships.js +37 -45
- package/dist/iife/excelts.iife.js +153 -11
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +4 -4
- package/dist/types/modules/pdf/render/style-converter.d.ts +1 -1
- package/dist/types/modules/pdf/types.d.ts +1 -0
- package/dist/types/modules/word/color-utils.d.ts +18 -0
- package/dist/types/modules/word/content-types.d.ts +15 -15
- package/dist/types/modules/word/crypto.d.ts +17 -0
- package/dist/types/modules/word/document-io.d.ts +58 -0
- package/dist/types/modules/word/document.d.ts +64 -135
- package/dist/types/modules/word/html.d.ts +13 -0
- package/dist/types/modules/word/index.base.d.ts +6 -9
- package/dist/types/modules/word/namespaces.d.ts +159 -0
- package/dist/types/modules/word/relationships.d.ts +15 -16
- package/package.json +1 -1
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* High-level fluent API for constructing DOCX documents programmatically.
|
|
5
5
|
* Provides convenience methods for common operations including comments,
|
|
6
6
|
* track changes, TOC, math, text boxes, checkboxes, and custom properties.
|
|
7
|
+
*
|
|
8
|
+
* This file has NO static imports from docx-packager or docx-reader,
|
|
9
|
+
* ensuring that importing builder helpers does not pull in archive/xml code.
|
|
7
10
|
*/
|
|
8
|
-
import { packageDocx } from "./docx-packager.js";
|
|
9
|
-
import { readDocx } from "./docx-reader.js";
|
|
10
|
-
import { bytesToBase64 } from "./internal-utils.js";
|
|
11
11
|
// =============================================================================
|
|
12
12
|
// Helper Builders
|
|
13
13
|
// =============================================================================
|
|
@@ -646,88 +646,91 @@ export function simpleTable(data, options) {
|
|
|
646
646
|
borders: opts.borders ? gridBorders() : undefined
|
|
647
647
|
}, opts.columnWidths);
|
|
648
648
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
649
|
+
/** Cast internal state to opaque handle. */
|
|
650
|
+
function _toHandle(state) {
|
|
651
|
+
return state;
|
|
652
|
+
}
|
|
653
|
+
/** Cast opaque handle back to internal state. */
|
|
654
|
+
function _toState(handle) {
|
|
655
|
+
return handle;
|
|
656
|
+
}
|
|
652
657
|
/**
|
|
653
|
-
*
|
|
658
|
+
* Namespace of free functions for building DOCX documents.
|
|
659
|
+
*
|
|
660
|
+
* Replaces the former `DocumentBuilder` class with tree-shakeable free functions.
|
|
661
|
+
* Each function operates on an opaque `DocumentHandle`.
|
|
654
662
|
*
|
|
655
663
|
* @example
|
|
656
664
|
* ```ts
|
|
657
|
-
* const doc =
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
* const bytes = await doc.toBuffer();
|
|
665
|
+
* const doc = Document.create();
|
|
666
|
+
* Document.addHeading(doc, "Hello World", 1);
|
|
667
|
+
* Document.addParagraph(doc, "This is a paragraph.");
|
|
668
|
+
* Document.addTable(doc, [["Name", "Age"], ["Alice", "30"]]);
|
|
669
|
+
* const bytes = await Document.toBuffer(doc);
|
|
664
670
|
* ```
|
|
665
671
|
*/
|
|
666
|
-
export
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
672
|
+
export const Document = {
|
|
673
|
+
/** Create a new document handle. */
|
|
674
|
+
create() {
|
|
675
|
+
return _toHandle({
|
|
676
|
+
body: [],
|
|
677
|
+
styles: [],
|
|
678
|
+
abstractNumberings: [],
|
|
679
|
+
numberingInstances: [],
|
|
680
|
+
headers: new Map(),
|
|
681
|
+
footers: new Map(),
|
|
682
|
+
footnotes: [],
|
|
683
|
+
endnotes: [],
|
|
684
|
+
images: [],
|
|
685
|
+
fonts: [],
|
|
686
|
+
comments: [],
|
|
687
|
+
customProperties: [],
|
|
688
|
+
nextImageId: 1,
|
|
689
|
+
nextFootnoteId: 1,
|
|
690
|
+
nextEndnoteId: 1,
|
|
691
|
+
nextBookmarkId: 0,
|
|
692
|
+
nextAbstractNumId: 0,
|
|
693
|
+
nextNumId: 1,
|
|
694
|
+
nextDrawingId: 1,
|
|
695
|
+
nextCommentId: 0
|
|
696
|
+
});
|
|
697
|
+
},
|
|
689
698
|
/** Add raw body content. */
|
|
690
|
-
addContent(content) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
}
|
|
699
|
+
addContent(doc, content) {
|
|
700
|
+
_toState(doc).body.push(content);
|
|
701
|
+
},
|
|
694
702
|
/** Add a paragraph with runs. */
|
|
695
|
-
addParagraphElement(para) {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
703
|
+
addParagraphElement(doc, para) {
|
|
704
|
+
_toState(doc).body.push(para);
|
|
705
|
+
},
|
|
699
706
|
/** Add a simple text paragraph. */
|
|
700
|
-
addParagraph(content, properties) {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
707
|
+
addParagraph(doc, content, properties) {
|
|
708
|
+
_toState(doc).body.push(textParagraph(content, properties));
|
|
709
|
+
},
|
|
704
710
|
/** Add a heading. */
|
|
705
|
-
addHeading(content, level = 1) {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
}
|
|
711
|
+
addHeading(doc, content, level = 1) {
|
|
712
|
+
_toState(doc).body.push(heading(content, level));
|
|
713
|
+
},
|
|
709
714
|
/** Add a page break. */
|
|
710
|
-
addPageBreak() {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
715
|
+
addPageBreak(doc) {
|
|
716
|
+
_toState(doc).body.push(paragraph([pageBreak()]));
|
|
717
|
+
},
|
|
714
718
|
/** Add a table from a 2D array. */
|
|
715
|
-
addTable(data, options) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
719
|
+
addTable(doc, data, options) {
|
|
720
|
+
_toState(doc).body.push(simpleTable(data, options));
|
|
721
|
+
},
|
|
719
722
|
/** Add a table element. */
|
|
720
|
-
addTableElement(tbl) {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
}
|
|
723
|
+
addTableElement(doc, tbl) {
|
|
724
|
+
_toState(doc).body.push(tbl);
|
|
725
|
+
},
|
|
724
726
|
/** Add an inline image. Returns the image relationship ID and drawing ID. */
|
|
725
|
-
addImage(data, mediaType, width, height, options) {
|
|
726
|
-
const
|
|
727
|
-
const
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
727
|
+
addImage(doc, data, mediaType, width, height, options) {
|
|
728
|
+
const s = _toState(doc);
|
|
729
|
+
const fileName = `image${s.nextImageId}.${mediaType}`;
|
|
730
|
+
const rId = `__img_${s.nextImageId}`;
|
|
731
|
+
const drawingId = s.nextDrawingId++;
|
|
732
|
+
s.images.push({ data, mediaType, fileName, rId });
|
|
733
|
+
s.body.push(paragraph([
|
|
731
734
|
{
|
|
732
735
|
content: [
|
|
733
736
|
{
|
|
@@ -736,26 +739,27 @@ export class DocumentBuilder {
|
|
|
736
739
|
width,
|
|
737
740
|
height,
|
|
738
741
|
altText: options?.altText,
|
|
739
|
-
name: options?.name ?? `Picture ${
|
|
742
|
+
name: options?.name ?? `Picture ${s.nextImageId}`,
|
|
740
743
|
drawingId
|
|
741
744
|
}
|
|
742
745
|
]
|
|
743
746
|
}
|
|
744
747
|
]));
|
|
745
|
-
|
|
748
|
+
s.nextImageId++;
|
|
746
749
|
return { rId, drawingId };
|
|
747
|
-
}
|
|
750
|
+
},
|
|
748
751
|
/** Add a floating image. Returns the image relationship ID. */
|
|
749
|
-
addFloatingImage(data, mediaType, width, height, options) {
|
|
750
|
-
const
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
752
|
+
addFloatingImage(doc, data, mediaType, width, height, options) {
|
|
753
|
+
const s = _toState(doc);
|
|
754
|
+
const fileName = `image${s.nextImageId}.${mediaType}`;
|
|
755
|
+
const rId = `__img_${s.nextImageId}`;
|
|
756
|
+
s.images.push({ data, mediaType, fileName, rId });
|
|
757
|
+
s.body.push(floatingImage({
|
|
754
758
|
rId,
|
|
755
759
|
width,
|
|
756
760
|
height,
|
|
757
761
|
altText: options?.altText,
|
|
758
|
-
name: options?.name ?? `Picture ${
|
|
762
|
+
name: options?.name ?? `Picture ${s.nextImageId}`,
|
|
759
763
|
horizontalPosition: options?.horizontalPosition,
|
|
760
764
|
verticalPosition: options?.verticalPosition,
|
|
761
765
|
wrap: options?.wrap,
|
|
@@ -771,38 +775,39 @@ export class DocumentBuilder {
|
|
|
771
775
|
flipHorizontal: options?.flipHorizontal,
|
|
772
776
|
flipVertical: options?.flipVertical
|
|
773
777
|
}));
|
|
774
|
-
|
|
778
|
+
s.nextImageId++;
|
|
775
779
|
return rId;
|
|
776
|
-
}
|
|
780
|
+
},
|
|
777
781
|
/** Add a custom font definition. */
|
|
778
|
-
addFont(font) {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
+
addFont(doc, font) {
|
|
783
|
+
_toState(doc).fonts.push(font);
|
|
784
|
+
},
|
|
782
785
|
/** Set a text watermark on the document. */
|
|
783
|
-
setWatermark(watermark) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
}
|
|
786
|
+
setWatermark(doc, watermark) {
|
|
787
|
+
_toState(doc).watermark = watermark;
|
|
788
|
+
},
|
|
787
789
|
/** Add a footnote. Returns the footnote ID. */
|
|
788
|
-
addFootnote(content) {
|
|
789
|
-
const
|
|
790
|
+
addFootnote(doc, content) {
|
|
791
|
+
const s = _toState(doc);
|
|
792
|
+
const id = s.nextFootnoteId++;
|
|
790
793
|
const paras = typeof content === "string" ? [textParagraph(content)] : content;
|
|
791
|
-
|
|
794
|
+
s.footnotes.push({ id, content: paras });
|
|
792
795
|
return id;
|
|
793
|
-
}
|
|
796
|
+
},
|
|
794
797
|
/** Add an endnote. Returns the endnote ID. */
|
|
795
|
-
addEndnote(content) {
|
|
796
|
-
const
|
|
798
|
+
addEndnote(doc, content) {
|
|
799
|
+
const s = _toState(doc);
|
|
800
|
+
const id = s.nextEndnoteId++;
|
|
797
801
|
const paras = typeof content === "string" ? [textParagraph(content)] : content;
|
|
798
|
-
|
|
802
|
+
s.endnotes.push({ id, content: paras });
|
|
799
803
|
return id;
|
|
800
|
-
}
|
|
804
|
+
},
|
|
801
805
|
/** Add a comment. Returns the comment ID. */
|
|
802
|
-
addComment(author, content, options) {
|
|
803
|
-
const
|
|
806
|
+
addComment(doc, author, content, options) {
|
|
807
|
+
const s = _toState(doc);
|
|
808
|
+
const id = s.nextCommentId++;
|
|
804
809
|
const paras = typeof content === "string" ? [textParagraph(content)] : content;
|
|
805
|
-
|
|
810
|
+
s.comments.push({
|
|
806
811
|
id,
|
|
807
812
|
author,
|
|
808
813
|
date: options?.date,
|
|
@@ -810,26 +815,24 @@ export class DocumentBuilder {
|
|
|
810
815
|
content: paras
|
|
811
816
|
});
|
|
812
817
|
return id;
|
|
813
|
-
}
|
|
818
|
+
},
|
|
814
819
|
/** Add a Table of Contents. */
|
|
815
|
-
addTableOfContents(options) {
|
|
816
|
-
|
|
820
|
+
addTableOfContents(doc, options) {
|
|
821
|
+
_toState(doc).body.push({
|
|
817
822
|
type: "tableOfContents",
|
|
818
823
|
headingStyleRange: options?.headingStyleRange ?? "1-3",
|
|
819
824
|
hyperlink: options?.hyperlink ?? true,
|
|
820
825
|
...options
|
|
821
826
|
});
|
|
822
|
-
|
|
823
|
-
}
|
|
827
|
+
},
|
|
824
828
|
/** Add a math equation block. */
|
|
825
|
-
addMath(content) {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
+
addMath(doc, content) {
|
|
830
|
+
_toState(doc).body.push(mathBlock(content));
|
|
831
|
+
},
|
|
829
832
|
/** Add a text box. */
|
|
830
|
-
addTextBox(content, options) {
|
|
833
|
+
addTextBox(doc, content, options) {
|
|
831
834
|
const paras = typeof content === "string" ? [textParagraph(content)] : content;
|
|
832
|
-
|
|
835
|
+
_toState(doc).body.push({
|
|
833
836
|
type: "textBox",
|
|
834
837
|
content: paras,
|
|
835
838
|
width: options?.width,
|
|
@@ -837,15 +840,15 @@ export class DocumentBuilder {
|
|
|
837
840
|
stroke: options?.stroke,
|
|
838
841
|
fill: options?.fill
|
|
839
842
|
});
|
|
840
|
-
|
|
841
|
-
}
|
|
843
|
+
},
|
|
842
844
|
/** Add a bullet list. */
|
|
843
|
-
addBulletList(items, level = 0) {
|
|
845
|
+
addBulletList(doc, items, level = 0) {
|
|
846
|
+
const s = _toState(doc);
|
|
844
847
|
// Create abstract numbering for bullets if not exists
|
|
845
|
-
let bulletAbsId =
|
|
848
|
+
let bulletAbsId = s.abstractNumberings.find(a => a.levels[0]?.format === "bullet")?.abstractNumId;
|
|
846
849
|
if (bulletAbsId === undefined) {
|
|
847
|
-
bulletAbsId =
|
|
848
|
-
|
|
850
|
+
bulletAbsId = s.nextAbstractNumId++;
|
|
851
|
+
s.abstractNumberings.push({
|
|
849
852
|
abstractNumId: bulletAbsId,
|
|
850
853
|
multiLevelType: "hybridMultilevel",
|
|
851
854
|
levels: [
|
|
@@ -878,23 +881,23 @@ export class DocumentBuilder {
|
|
|
878
881
|
}
|
|
879
882
|
]
|
|
880
883
|
});
|
|
881
|
-
|
|
882
|
-
numId:
|
|
884
|
+
s.numberingInstances.push({
|
|
885
|
+
numId: s.nextNumId++,
|
|
883
886
|
abstractNumId: bulletAbsId
|
|
884
887
|
});
|
|
885
888
|
}
|
|
886
|
-
const numId =
|
|
889
|
+
const numId = s.numberingInstances.find(n => n.abstractNumId === bulletAbsId).numId;
|
|
887
890
|
for (const item of items) {
|
|
888
|
-
|
|
891
|
+
s.body.push(textParagraph(item, { numbering: { numId, level } }));
|
|
889
892
|
}
|
|
890
|
-
|
|
891
|
-
}
|
|
893
|
+
},
|
|
892
894
|
/** Add a numbered list. */
|
|
893
|
-
addNumberedList(items, level = 0) {
|
|
894
|
-
|
|
895
|
+
addNumberedList(doc, items, level = 0) {
|
|
896
|
+
const s = _toState(doc);
|
|
897
|
+
let numAbsId = s.abstractNumberings.find(a => a.levels[0]?.format === "decimal")?.abstractNumId;
|
|
895
898
|
if (numAbsId === undefined) {
|
|
896
|
-
numAbsId =
|
|
897
|
-
|
|
899
|
+
numAbsId = s.nextAbstractNumId++;
|
|
900
|
+
s.abstractNumberings.push({
|
|
898
901
|
abstractNumId: numAbsId,
|
|
899
902
|
multiLevelType: "hybridMultilevel",
|
|
900
903
|
levels: [
|
|
@@ -924,35 +927,32 @@ export class DocumentBuilder {
|
|
|
924
927
|
}
|
|
925
928
|
]
|
|
926
929
|
});
|
|
927
|
-
|
|
928
|
-
numId:
|
|
930
|
+
s.numberingInstances.push({
|
|
931
|
+
numId: s.nextNumId++,
|
|
929
932
|
abstractNumId: numAbsId
|
|
930
933
|
});
|
|
931
934
|
}
|
|
932
|
-
const numId =
|
|
935
|
+
const numId = s.numberingInstances.find(n => n.abstractNumId === numAbsId).numId;
|
|
933
936
|
for (const item of items) {
|
|
934
|
-
|
|
937
|
+
s.body.push(textParagraph(item, { numbering: { numId, level } }));
|
|
935
938
|
}
|
|
936
|
-
|
|
937
|
-
}
|
|
939
|
+
},
|
|
938
940
|
/** Set section properties (page size, margins, etc.). */
|
|
939
|
-
setSectionProperties(props) {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
}
|
|
941
|
+
setSectionProperties(doc, props) {
|
|
942
|
+
_toState(doc).sectionProperties = props;
|
|
943
|
+
},
|
|
943
944
|
/** Set document defaults. */
|
|
944
|
-
setDocDefaults(defaults) {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
}
|
|
945
|
+
setDocDefaults(doc, defaults) {
|
|
946
|
+
_toState(doc).docDefaults = defaults;
|
|
947
|
+
},
|
|
948
948
|
/** Add a style definition. */
|
|
949
|
-
addStyle(style) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
}
|
|
949
|
+
addStyle(doc, style) {
|
|
950
|
+
_toState(doc).styles.push(style);
|
|
951
|
+
},
|
|
953
952
|
/** Set default styles (Normal, Heading1-6, Hyperlink, etc.). */
|
|
954
|
-
useDefaultStyles() {
|
|
955
|
-
|
|
953
|
+
useDefaultStyles(doc) {
|
|
954
|
+
const s = _toState(doc);
|
|
955
|
+
s.docDefaults = {
|
|
956
956
|
runProperties: {
|
|
957
957
|
font: { ascii: "Calibri", hAnsi: "Calibri", eastAsia: "SimSun", cs: "Times New Roman" },
|
|
958
958
|
size: 22,
|
|
@@ -963,7 +963,7 @@ export class DocumentBuilder {
|
|
|
963
963
|
spacing: { after: 160, line: 259, lineRule: "auto" }
|
|
964
964
|
}
|
|
965
965
|
};
|
|
966
|
-
|
|
966
|
+
s.styles.push({ type: "paragraph", styleId: "Normal", name: "Normal", isDefault: true, qFormat: true }, {
|
|
967
967
|
type: "paragraph",
|
|
968
968
|
styleId: "Heading1",
|
|
969
969
|
name: "heading 1",
|
|
@@ -1011,192 +1011,90 @@ export class DocumentBuilder {
|
|
|
1011
1011
|
uiPriority: 39,
|
|
1012
1012
|
tableProperties: { borders: gridBorders(4, "auto") }
|
|
1013
1013
|
});
|
|
1014
|
-
|
|
1015
|
-
}
|
|
1014
|
+
},
|
|
1016
1015
|
/** Set a header for the given type. */
|
|
1017
|
-
setHeader(type, content) {
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
}
|
|
1016
|
+
setHeader(doc, type, content) {
|
|
1017
|
+
_toState(doc).headers.set(type, { content });
|
|
1018
|
+
},
|
|
1021
1019
|
/** Set a footer for the given type. */
|
|
1022
|
-
setFooter(type, content) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
}
|
|
1020
|
+
setFooter(doc, type, content) {
|
|
1021
|
+
_toState(doc).footers.set(type, { content });
|
|
1022
|
+
},
|
|
1026
1023
|
/** Set document settings. */
|
|
1027
|
-
setSettings(settings) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
}
|
|
1024
|
+
setSettings(doc, settings) {
|
|
1025
|
+
_toState(doc).settings = settings;
|
|
1026
|
+
},
|
|
1031
1027
|
/** Set core properties (metadata). */
|
|
1032
|
-
setCoreProperties(props) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
}
|
|
1028
|
+
setCoreProperties(doc, props) {
|
|
1029
|
+
_toState(doc).coreProperties = props;
|
|
1030
|
+
},
|
|
1036
1031
|
/** Set application properties. */
|
|
1037
|
-
setAppProperties(props) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
}
|
|
1032
|
+
setAppProperties(doc, props) {
|
|
1033
|
+
_toState(doc).appProperties = props;
|
|
1034
|
+
},
|
|
1041
1035
|
/** Set document background. */
|
|
1042
|
-
setBackground(background) {
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
}
|
|
1036
|
+
setBackground(doc, background) {
|
|
1037
|
+
_toState(doc).background = background;
|
|
1038
|
+
},
|
|
1046
1039
|
/** Add a custom document property. */
|
|
1047
|
-
addCustomProperty(name, value) {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
}
|
|
1040
|
+
addCustomProperty(doc, name, value) {
|
|
1041
|
+
_toState(doc).customProperties.push({ name, value });
|
|
1042
|
+
},
|
|
1051
1043
|
/** Add a section break with properties. */
|
|
1052
|
-
addSectionBreak(props) {
|
|
1044
|
+
addSectionBreak(doc, props) {
|
|
1045
|
+
const s = _toState(doc);
|
|
1053
1046
|
// Insert as the last paragraph's section properties
|
|
1054
|
-
if (
|
|
1055
|
-
const last =
|
|
1047
|
+
if (s.body.length > 0) {
|
|
1048
|
+
const last = s.body[s.body.length - 1];
|
|
1056
1049
|
if (last.type === "paragraph") {
|
|
1057
1050
|
const existingProps = last.properties ?? {};
|
|
1058
|
-
|
|
1051
|
+
s.body[s.body.length - 1] = {
|
|
1059
1052
|
...last,
|
|
1060
1053
|
properties: { ...existingProps, sectionProperties: props }
|
|
1061
1054
|
};
|
|
1062
|
-
return
|
|
1055
|
+
return;
|
|
1063
1056
|
}
|
|
1064
1057
|
}
|
|
1065
1058
|
// If no previous paragraph, add an empty one with section properties
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
}
|
|
1059
|
+
s.body.push(paragraph([], { sectionProperties: props }));
|
|
1060
|
+
},
|
|
1069
1061
|
/** Get next available bookmark ID. */
|
|
1070
|
-
nextBookmarkId() {
|
|
1071
|
-
return
|
|
1072
|
-
}
|
|
1073
|
-
/** Build the DocxDocument model. */
|
|
1074
|
-
build() {
|
|
1062
|
+
nextBookmarkId(doc) {
|
|
1063
|
+
return _toState(doc).nextBookmarkId++;
|
|
1064
|
+
},
|
|
1065
|
+
/** Build the DocxDocument model from the handle. */
|
|
1066
|
+
build(doc) {
|
|
1067
|
+
const s = _toState(doc);
|
|
1075
1068
|
return {
|
|
1076
|
-
body:
|
|
1077
|
-
sectionProperties:
|
|
1069
|
+
body: s.body,
|
|
1070
|
+
sectionProperties: s.sectionProperties ?? {
|
|
1078
1071
|
pageSize: { width: 12240, height: 15840 },
|
|
1079
1072
|
margins: { top: 1440, right: 1440, bottom: 1440, left: 1440 }
|
|
1080
1073
|
},
|
|
1081
|
-
styles:
|
|
1082
|
-
docDefaults:
|
|
1083
|
-
abstractNumberings:
|
|
1084
|
-
numberingInstances:
|
|
1085
|
-
headers:
|
|
1086
|
-
footers:
|
|
1087
|
-
footnotes:
|
|
1088
|
-
endnotes:
|
|
1089
|
-
images:
|
|
1090
|
-
fonts:
|
|
1091
|
-
settings:
|
|
1092
|
-
coreProperties:
|
|
1093
|
-
appProperties:
|
|
1094
|
-
comments:
|
|
1095
|
-
background:
|
|
1096
|
-
customProperties:
|
|
1097
|
-
watermark:
|
|
1074
|
+
styles: s.styles.length > 0 ? s.styles : undefined,
|
|
1075
|
+
docDefaults: s.docDefaults,
|
|
1076
|
+
abstractNumberings: s.abstractNumberings.length > 0 ? s.abstractNumberings : undefined,
|
|
1077
|
+
numberingInstances: s.numberingInstances.length > 0 ? s.numberingInstances : undefined,
|
|
1078
|
+
headers: s.headers.size > 0 ? s.headers : undefined,
|
|
1079
|
+
footers: s.footers.size > 0 ? s.footers : undefined,
|
|
1080
|
+
footnotes: s.footnotes.length > 0 ? s.footnotes : undefined,
|
|
1081
|
+
endnotes: s.endnotes.length > 0 ? s.endnotes : undefined,
|
|
1082
|
+
images: s.images.length > 0 ? s.images : undefined,
|
|
1083
|
+
fonts: s.fonts.length > 0 ? s.fonts : undefined,
|
|
1084
|
+
settings: s.settings,
|
|
1085
|
+
coreProperties: s.coreProperties,
|
|
1086
|
+
appProperties: s.appProperties,
|
|
1087
|
+
comments: s.comments.length > 0 ? s.comments : undefined,
|
|
1088
|
+
background: s.background,
|
|
1089
|
+
customProperties: s.customProperties.length > 0 ? s.customProperties : undefined,
|
|
1090
|
+
watermark: s.watermark
|
|
1098
1091
|
};
|
|
1099
1092
|
}
|
|
1100
|
-
|
|
1101
|
-
async toBuffer(compressionLevel) {
|
|
1102
|
-
return packageDocx(this.build(), compressionLevel);
|
|
1103
|
-
}
|
|
1104
|
-
/** Build and package to base64 string. */
|
|
1105
|
-
async toBase64(compressionLevel) {
|
|
1106
|
-
const bytes = await this.toBuffer(compressionLevel);
|
|
1107
|
-
return bytesToBase64(bytes);
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1093
|
+
};
|
|
1110
1094
|
// =============================================================================
|
|
1111
|
-
// Theme Color Resolution
|
|
1095
|
+
// Theme Color Resolution (re-export from color-utils for backward compat)
|
|
1112
1096
|
// =============================================================================
|
|
1113
|
-
|
|
1114
|
-
* Map OOXML theme color attribute names to theme color scheme keys.
|
|
1115
|
-
* Word uses different names in run/paragraph properties vs the theme XML.
|
|
1116
|
-
*/
|
|
1117
|
-
const THEME_COLOR_MAP = {
|
|
1118
|
-
dark1: "dk1",
|
|
1119
|
-
light1: "lt1",
|
|
1120
|
-
dark2: "dk2",
|
|
1121
|
-
light2: "lt2",
|
|
1122
|
-
accent1: "accent1",
|
|
1123
|
-
accent2: "accent2",
|
|
1124
|
-
accent3: "accent3",
|
|
1125
|
-
accent4: "accent4",
|
|
1126
|
-
accent5: "accent5",
|
|
1127
|
-
accent6: "accent6",
|
|
1128
|
-
hyperlink: "hlink",
|
|
1129
|
-
followedHyperlink: "folHlink",
|
|
1130
|
-
// Direct names also work
|
|
1131
|
-
dk1: "dk1",
|
|
1132
|
-
lt1: "lt1",
|
|
1133
|
-
dk2: "dk2",
|
|
1134
|
-
lt2: "lt2",
|
|
1135
|
-
hlink: "hlink",
|
|
1136
|
-
folHlink: "folHlink"
|
|
1137
|
-
};
|
|
1138
|
-
/**
|
|
1139
|
-
* Resolve a ColorSpec to an actual hex RGB color using the document theme.
|
|
1140
|
-
*
|
|
1141
|
-
* Applies theme color lookup + tint/shade transformations per OOXML spec.
|
|
1142
|
-
*
|
|
1143
|
-
* @param color - The color value (HexColor string or ColorSpec).
|
|
1144
|
-
* @param theme - The document theme (from `doc.theme`).
|
|
1145
|
-
* @returns Resolved hex color string (6 chars, no #), or undefined if unresolvable.
|
|
1146
|
-
*/
|
|
1147
|
-
export function resolveThemeColor(color, theme) {
|
|
1148
|
-
if (color === undefined) {
|
|
1149
|
-
return undefined;
|
|
1150
|
-
}
|
|
1151
|
-
if (typeof color === "string") {
|
|
1152
|
-
return color;
|
|
1153
|
-
}
|
|
1154
|
-
// ColorSpec with val — use directly
|
|
1155
|
-
if (color.val && color.val !== "auto") {
|
|
1156
|
-
return color.val;
|
|
1157
|
-
}
|
|
1158
|
-
// Resolve via theme
|
|
1159
|
-
if (!color.themeColor || !theme) {
|
|
1160
|
-
return color.val;
|
|
1161
|
-
}
|
|
1162
|
-
const key = THEME_COLOR_MAP[color.themeColor] ?? color.themeColor;
|
|
1163
|
-
const base = theme.colorScheme.colors[key];
|
|
1164
|
-
if (!base) {
|
|
1165
|
-
return color.val;
|
|
1166
|
-
}
|
|
1167
|
-
// Apply tint or shade
|
|
1168
|
-
if (color.themeTint) {
|
|
1169
|
-
return applyTint(base, parseInt(color.themeTint, 16) / 255);
|
|
1170
|
-
}
|
|
1171
|
-
if (color.themeShade) {
|
|
1172
|
-
return applyShade(base, parseInt(color.themeShade, 16) / 255);
|
|
1173
|
-
}
|
|
1174
|
-
return base;
|
|
1175
|
-
}
|
|
1176
|
-
/** Apply tint to a hex color. tint=1 → white, tint=0 → original. */
|
|
1177
|
-
function applyTint(hex, tint) {
|
|
1178
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
1179
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
1180
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
1181
|
-
const nr = Math.round(r + (255 - r) * tint);
|
|
1182
|
-
const ng = Math.round(g + (255 - g) * tint);
|
|
1183
|
-
const nb = Math.round(b + (255 - b) * tint);
|
|
1184
|
-
return toHex2(nr) + toHex2(ng) + toHex2(nb);
|
|
1185
|
-
}
|
|
1186
|
-
/** Apply shade to a hex color. shade=1 → original, shade=0 → black. */
|
|
1187
|
-
function applyShade(hex, shade) {
|
|
1188
|
-
const r = parseInt(hex.slice(0, 2), 16);
|
|
1189
|
-
const g = parseInt(hex.slice(2, 4), 16);
|
|
1190
|
-
const b = parseInt(hex.slice(4, 6), 16);
|
|
1191
|
-
const nr = Math.round(r * shade);
|
|
1192
|
-
const ng = Math.round(g * shade);
|
|
1193
|
-
const nb = Math.round(b * shade);
|
|
1194
|
-
return toHex2(nr) + toHex2(ng) + toHex2(nb);
|
|
1195
|
-
}
|
|
1196
|
-
function toHex2(n) {
|
|
1197
|
-
const h = Math.max(0, Math.min(255, n)).toString(16);
|
|
1198
|
-
return h.length < 2 ? "0" + h : h;
|
|
1199
|
-
}
|
|
1097
|
+
export { resolveThemeColor } from "./color-utils.js";
|
|
1200
1098
|
/** Extract concatenated plain text from a paragraph's runs. */
|
|
1201
1099
|
function paragraphText(para) {
|
|
1202
1100
|
let text = "";
|
|
@@ -1633,163 +1531,3 @@ function countOccurrences(str, search) {
|
|
|
1633
1531
|
}
|
|
1634
1532
|
return str.split(search).length - 1;
|
|
1635
1533
|
}
|
|
1636
|
-
/**
|
|
1637
|
-
* Read an existing DOCX file, replace placeholders with content, and produce a new DOCX.
|
|
1638
|
-
*
|
|
1639
|
-
* Placeholders are strings like `{{name}}` embedded in the document text.
|
|
1640
|
-
* They may span across multiple runs — the patcher handles cross-run matching.
|
|
1641
|
-
*
|
|
1642
|
-
* Supported patch content types:
|
|
1643
|
-
* - `text` — simple text replacement (preserves formatting of the first run)
|
|
1644
|
-
* - `paragraph` — replaces the entire paragraph containing the placeholder
|
|
1645
|
-
* - `table` — replaces the entire paragraph with a table
|
|
1646
|
-
* - `image` — replaces the placeholder with an inline image
|
|
1647
|
-
*
|
|
1648
|
-
* @param buffer - The source DOCX file as a Uint8Array.
|
|
1649
|
-
* @param patches - Array of patch operations to apply.
|
|
1650
|
-
* @param options - Optional compression settings.
|
|
1651
|
-
* @returns New DOCX file as a Uint8Array.
|
|
1652
|
-
*/
|
|
1653
|
-
export async function patchDocument(buffer, patches, options) {
|
|
1654
|
-
const doc = await readDocx(buffer);
|
|
1655
|
-
// Build lookup map for quick placeholder matching
|
|
1656
|
-
const patchMap = new Map();
|
|
1657
|
-
for (const patch of patches) {
|
|
1658
|
-
patchMap.set(patch.placeholder, patch);
|
|
1659
|
-
}
|
|
1660
|
-
// Process body content
|
|
1661
|
-
const newBody = [];
|
|
1662
|
-
for (const block of doc.body) {
|
|
1663
|
-
if (block.type === "paragraph") {
|
|
1664
|
-
const result = patchParagraph(block, patchMap);
|
|
1665
|
-
if (result) {
|
|
1666
|
-
if (Array.isArray(result)) {
|
|
1667
|
-
newBody.push(...result);
|
|
1668
|
-
}
|
|
1669
|
-
else {
|
|
1670
|
-
newBody.push(result);
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
else if (block.type === "table") {
|
|
1675
|
-
patchTable(block, patchMap);
|
|
1676
|
-
newBody.push(block);
|
|
1677
|
-
}
|
|
1678
|
-
else {
|
|
1679
|
-
newBody.push(block);
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
// Patch headers
|
|
1683
|
-
if (doc.headers) {
|
|
1684
|
-
for (const [, headerDef] of doc.headers) {
|
|
1685
|
-
patchHeaderFooterContent(headerDef.content, patchMap);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
// Patch footers
|
|
1689
|
-
if (doc.footers) {
|
|
1690
|
-
for (const [, footerDef] of doc.footers) {
|
|
1691
|
-
patchHeaderFooterContent(footerDef.content, patchMap);
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
// Add any new images from patches
|
|
1695
|
-
const images = doc.images ? [...doc.images] : [];
|
|
1696
|
-
for (const patch of patches) {
|
|
1697
|
-
if (patch.content.type === "image") {
|
|
1698
|
-
const imgContent = patch.content;
|
|
1699
|
-
const existing = images.find(i => i.fileName === imgContent.image.fileName);
|
|
1700
|
-
if (!existing) {
|
|
1701
|
-
images.push(imgContent.image);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
const patched = {
|
|
1706
|
-
...doc,
|
|
1707
|
-
body: newBody,
|
|
1708
|
-
images: images.length > 0 ? images : undefined
|
|
1709
|
-
};
|
|
1710
|
-
return packageDocx(patched, options?.compressionLevel);
|
|
1711
|
-
}
|
|
1712
|
-
/** Patch a paragraph — returns replacement content or null to remove. */
|
|
1713
|
-
function patchParagraph(para, patchMap) {
|
|
1714
|
-
const text = paragraphText(para);
|
|
1715
|
-
for (const [placeholder, patch] of patchMap) {
|
|
1716
|
-
if (!text.includes(placeholder)) {
|
|
1717
|
-
continue;
|
|
1718
|
-
}
|
|
1719
|
-
switch (patch.content.type) {
|
|
1720
|
-
case "text": {
|
|
1721
|
-
replaceInParagraph(para, placeholder, patch.content.text);
|
|
1722
|
-
return para;
|
|
1723
|
-
}
|
|
1724
|
-
case "paragraph": {
|
|
1725
|
-
return patch.content.children;
|
|
1726
|
-
}
|
|
1727
|
-
case "table": {
|
|
1728
|
-
return patch.content.table;
|
|
1729
|
-
}
|
|
1730
|
-
case "image": {
|
|
1731
|
-
const img = patch.content.image;
|
|
1732
|
-
const rId = img.rId ?? `rId_img_${img.fileName}`;
|
|
1733
|
-
const imgContent = {
|
|
1734
|
-
type: "image",
|
|
1735
|
-
rId,
|
|
1736
|
-
width: patch.content.width,
|
|
1737
|
-
height: patch.content.height,
|
|
1738
|
-
altText: img.fileName,
|
|
1739
|
-
name: img.fileName
|
|
1740
|
-
};
|
|
1741
|
-
const newPara = {
|
|
1742
|
-
type: "paragraph",
|
|
1743
|
-
properties: para.properties,
|
|
1744
|
-
children: [{ content: [imgContent] }]
|
|
1745
|
-
};
|
|
1746
|
-
return newPara;
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
return para;
|
|
1751
|
-
}
|
|
1752
|
-
/** Patch text inside table cells recursively. */
|
|
1753
|
-
function patchTable(table, patchMap) {
|
|
1754
|
-
for (const row of table.rows) {
|
|
1755
|
-
for (const cell of row.cells) {
|
|
1756
|
-
const newContent = [];
|
|
1757
|
-
for (const block of cell.content) {
|
|
1758
|
-
if (block.type === "paragraph") {
|
|
1759
|
-
const result = patchParagraph(block, patchMap);
|
|
1760
|
-
if (result) {
|
|
1761
|
-
if (Array.isArray(result)) {
|
|
1762
|
-
newContent.push(...result);
|
|
1763
|
-
}
|
|
1764
|
-
else {
|
|
1765
|
-
newContent.push(result);
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
else if (block.type === "table") {
|
|
1770
|
-
patchTable(block, patchMap);
|
|
1771
|
-
newContent.push(block);
|
|
1772
|
-
}
|
|
1773
|
-
else {
|
|
1774
|
-
newContent.push(block);
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
cell.content = newContent;
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
/** Patch text in header/footer content. */
|
|
1782
|
-
function patchHeaderFooterContent(content, patchMap) {
|
|
1783
|
-
for (const child of content.children) {
|
|
1784
|
-
if (child.type === "paragraph") {
|
|
1785
|
-
for (const [placeholder, patch] of patchMap) {
|
|
1786
|
-
if (patch.content.type === "text") {
|
|
1787
|
-
const text = paragraphText(child);
|
|
1788
|
-
if (text.includes(placeholder)) {
|
|
1789
|
-
replaceInParagraph(child, placeholder, patch.content.text);
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
}
|