@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.
Files changed (68) hide show
  1. package/dist/browser/modules/pdf/excel-bridge.js +27 -1
  2. package/dist/browser/modules/pdf/render/layout-engine.js +74 -9
  3. package/dist/browser/modules/pdf/render/style-converter.d.ts +1 -1
  4. package/dist/browser/modules/pdf/render/style-converter.js +98 -1
  5. package/dist/browser/modules/pdf/types.d.ts +1 -0
  6. package/dist/browser/modules/word/color-utils.d.ts +18 -0
  7. package/dist/browser/modules/word/color-utils.js +94 -0
  8. package/dist/browser/modules/word/content-types.d.ts +15 -15
  9. package/dist/browser/modules/word/content-types.js +39 -43
  10. package/dist/browser/modules/word/crypto.d.ts +17 -0
  11. package/dist/browser/modules/word/crypto.js +18 -0
  12. package/dist/browser/modules/word/document-io.d.ts +58 -0
  13. package/dist/browser/modules/word/document-io.js +239 -0
  14. package/dist/browser/modules/word/document.d.ts +64 -135
  15. package/dist/browser/modules/word/document.js +207 -469
  16. package/dist/browser/modules/word/docx-packager.js +90 -90
  17. package/dist/browser/modules/word/html-renderer.js +1 -1
  18. package/dist/browser/modules/word/html.d.ts +13 -0
  19. package/dist/browser/modules/word/html.js +12 -0
  20. package/dist/browser/modules/word/index.base.d.ts +6 -9
  21. package/dist/browser/modules/word/index.base.js +7 -10
  22. package/dist/browser/modules/word/namespaces.d.ts +159 -0
  23. package/dist/browser/modules/word/namespaces.js +189 -0
  24. package/dist/browser/modules/word/relationships.d.ts +15 -16
  25. package/dist/browser/modules/word/relationships.js +37 -45
  26. package/dist/cjs/modules/pdf/excel-bridge.js +27 -1
  27. package/dist/cjs/modules/pdf/render/layout-engine.js +74 -9
  28. package/dist/cjs/modules/pdf/render/style-converter.js +98 -1
  29. package/dist/cjs/modules/word/color-utils.js +97 -0
  30. package/dist/cjs/modules/word/content-types.js +44 -45
  31. package/dist/cjs/modules/word/crypto.js +34 -0
  32. package/dist/cjs/modules/word/document-io.js +244 -0
  33. package/dist/cjs/modules/word/document.js +209 -473
  34. package/dist/cjs/modules/word/docx-packager.js +88 -88
  35. package/dist/cjs/modules/word/html-renderer.js +2 -2
  36. package/dist/cjs/modules/word/html.js +16 -0
  37. package/dist/cjs/modules/word/index.base.js +17 -27
  38. package/dist/cjs/modules/word/namespaces.js +192 -0
  39. package/dist/cjs/modules/word/relationships.js +42 -47
  40. package/dist/esm/modules/pdf/excel-bridge.js +27 -1
  41. package/dist/esm/modules/pdf/render/layout-engine.js +74 -9
  42. package/dist/esm/modules/pdf/render/style-converter.js +98 -1
  43. package/dist/esm/modules/word/color-utils.js +94 -0
  44. package/dist/esm/modules/word/content-types.js +39 -43
  45. package/dist/esm/modules/word/crypto.js +18 -0
  46. package/dist/esm/modules/word/document-io.js +239 -0
  47. package/dist/esm/modules/word/document.js +207 -469
  48. package/dist/esm/modules/word/docx-packager.js +90 -90
  49. package/dist/esm/modules/word/html-renderer.js +1 -1
  50. package/dist/esm/modules/word/html.js +12 -0
  51. package/dist/esm/modules/word/index.base.js +7 -10
  52. package/dist/esm/modules/word/namespaces.js +189 -0
  53. package/dist/esm/modules/word/relationships.js +37 -45
  54. package/dist/iife/excelts.iife.js +153 -11
  55. package/dist/iife/excelts.iife.js.map +1 -1
  56. package/dist/iife/excelts.iife.min.js +4 -4
  57. package/dist/types/modules/pdf/render/style-converter.d.ts +1 -1
  58. package/dist/types/modules/pdf/types.d.ts +1 -0
  59. package/dist/types/modules/word/color-utils.d.ts +18 -0
  60. package/dist/types/modules/word/content-types.d.ts +15 -15
  61. package/dist/types/modules/word/crypto.d.ts +17 -0
  62. package/dist/types/modules/word/document-io.d.ts +58 -0
  63. package/dist/types/modules/word/document.d.ts +64 -135
  64. package/dist/types/modules/word/html.d.ts +13 -0
  65. package/dist/types/modules/word/index.base.d.ts +6 -9
  66. package/dist/types/modules/word/namespaces.d.ts +159 -0
  67. package/dist/types/modules/word/relationships.d.ts +15 -16
  68. 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
- // Document Builder Class
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
- * Fluent builder for constructing DOCX documents.
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 = new DocumentBuilder()
658
- * .addHeading("Hello World", 1)
659
- * .addParagraph("This is a paragraph.")
660
- * .addTable([["Name", "Age"], ["Alice", "30"]])
661
- * .build();
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 class DocumentBuilder {
667
- constructor() {
668
- this._body = [];
669
- this._styles = [];
670
- this._abstractNumberings = [];
671
- this._numberingInstances = [];
672
- this._headers = new Map();
673
- this._footers = new Map();
674
- this._footnotes = [];
675
- this._endnotes = [];
676
- this._images = [];
677
- this._fonts = [];
678
- this._comments = [];
679
- this._customProperties = [];
680
- this._nextImageId = 1;
681
- this._nextFootnoteId = 1;
682
- this._nextEndnoteId = 1;
683
- this._nextBookmarkId = 0;
684
- this._nextAbstractNumId = 0;
685
- this._nextNumId = 1;
686
- this._nextDrawingId = 1;
687
- this._nextCommentId = 0;
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
- this._body.push(content);
692
- return this;
693
- }
699
+ addContent(doc, content) {
700
+ _toState(doc).body.push(content);
701
+ },
694
702
  /** Add a paragraph with runs. */
695
- addParagraphElement(para) {
696
- this._body.push(para);
697
- return this;
698
- }
703
+ addParagraphElement(doc, para) {
704
+ _toState(doc).body.push(para);
705
+ },
699
706
  /** Add a simple text paragraph. */
700
- addParagraph(content, properties) {
701
- this._body.push(textParagraph(content, properties));
702
- return this;
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
- this._body.push(heading(content, level));
707
- return this;
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
- this._body.push(paragraph([pageBreak()]));
712
- return this;
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
- this._body.push(simpleTable(data, options));
717
- return this;
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
- this._body.push(tbl);
722
- return this;
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 fileName = `image${this._nextImageId}.${mediaType}`;
727
- const rId = `__img_${this._nextImageId}`;
728
- const drawingId = this._nextDrawingId++;
729
- this._images.push({ data, mediaType, fileName, rId });
730
- this._body.push(paragraph([
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 ${this._nextImageId}`,
742
+ name: options?.name ?? `Picture ${s.nextImageId}`,
740
743
  drawingId
741
744
  }
742
745
  ]
743
746
  }
744
747
  ]));
745
- this._nextImageId++;
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 fileName = `image${this._nextImageId}.${mediaType}`;
751
- const rId = `__img_${this._nextImageId}`;
752
- this._images.push({ data, mediaType, fileName, rId });
753
- this._body.push(floatingImage({
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 ${this._nextImageId}`,
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
- this._nextImageId++;
778
+ s.nextImageId++;
775
779
  return rId;
776
- }
780
+ },
777
781
  /** Add a custom font definition. */
778
- addFont(font) {
779
- this._fonts.push(font);
780
- return this;
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
- this._watermark = watermark;
785
- return this;
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 id = this._nextFootnoteId++;
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
- this._footnotes.push({ id, content: paras });
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 id = this._nextEndnoteId++;
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
- this._endnotes.push({ id, content: paras });
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 id = this._nextCommentId++;
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
- this._comments.push({
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
- this._body.push({
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
- return this;
823
- }
827
+ },
824
828
  /** Add a math equation block. */
825
- addMath(content) {
826
- this._body.push(mathBlock(content));
827
- return this;
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
- this._body.push({
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
- return this;
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 = this._abstractNumberings.find(a => a.levels[0]?.format === "bullet")?.abstractNumId;
848
+ let bulletAbsId = s.abstractNumberings.find(a => a.levels[0]?.format === "bullet")?.abstractNumId;
846
849
  if (bulletAbsId === undefined) {
847
- bulletAbsId = this._nextAbstractNumId++;
848
- this._abstractNumberings.push({
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
- this._numberingInstances.push({
882
- numId: this._nextNumId++,
884
+ s.numberingInstances.push({
885
+ numId: s.nextNumId++,
883
886
  abstractNumId: bulletAbsId
884
887
  });
885
888
  }
886
- const numId = this._numberingInstances.find(n => n.abstractNumId === bulletAbsId).numId;
889
+ const numId = s.numberingInstances.find(n => n.abstractNumId === bulletAbsId).numId;
887
890
  for (const item of items) {
888
- this._body.push(textParagraph(item, { numbering: { numId, level } }));
891
+ s.body.push(textParagraph(item, { numbering: { numId, level } }));
889
892
  }
890
- return this;
891
- }
893
+ },
892
894
  /** Add a numbered list. */
893
- addNumberedList(items, level = 0) {
894
- let numAbsId = this._abstractNumberings.find(a => a.levels[0]?.format === "decimal")?.abstractNumId;
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 = this._nextAbstractNumId++;
897
- this._abstractNumberings.push({
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
- this._numberingInstances.push({
928
- numId: this._nextNumId++,
930
+ s.numberingInstances.push({
931
+ numId: s.nextNumId++,
929
932
  abstractNumId: numAbsId
930
933
  });
931
934
  }
932
- const numId = this._numberingInstances.find(n => n.abstractNumId === numAbsId).numId;
935
+ const numId = s.numberingInstances.find(n => n.abstractNumId === numAbsId).numId;
933
936
  for (const item of items) {
934
- this._body.push(textParagraph(item, { numbering: { numId, level } }));
937
+ s.body.push(textParagraph(item, { numbering: { numId, level } }));
935
938
  }
936
- return this;
937
- }
939
+ },
938
940
  /** Set section properties (page size, margins, etc.). */
939
- setSectionProperties(props) {
940
- this._sectionProperties = props;
941
- return this;
942
- }
941
+ setSectionProperties(doc, props) {
942
+ _toState(doc).sectionProperties = props;
943
+ },
943
944
  /** Set document defaults. */
944
- setDocDefaults(defaults) {
945
- this._docDefaults = defaults;
946
- return this;
947
- }
945
+ setDocDefaults(doc, defaults) {
946
+ _toState(doc).docDefaults = defaults;
947
+ },
948
948
  /** Add a style definition. */
949
- addStyle(style) {
950
- this._styles.push(style);
951
- return this;
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
- this._docDefaults = {
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
- this._styles.push({ type: "paragraph", styleId: "Normal", name: "Normal", isDefault: true, qFormat: true }, {
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
- return this;
1015
- }
1014
+ },
1016
1015
  /** Set a header for the given type. */
1017
- setHeader(type, content) {
1018
- this._headers.set(type, { content });
1019
- return this;
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
- this._footers.set(type, { content });
1024
- return this;
1025
- }
1020
+ setFooter(doc, type, content) {
1021
+ _toState(doc).footers.set(type, { content });
1022
+ },
1026
1023
  /** Set document settings. */
1027
- setSettings(settings) {
1028
- this._settings = settings;
1029
- return this;
1030
- }
1024
+ setSettings(doc, settings) {
1025
+ _toState(doc).settings = settings;
1026
+ },
1031
1027
  /** Set core properties (metadata). */
1032
- setCoreProperties(props) {
1033
- this._coreProperties = props;
1034
- return this;
1035
- }
1028
+ setCoreProperties(doc, props) {
1029
+ _toState(doc).coreProperties = props;
1030
+ },
1036
1031
  /** Set application properties. */
1037
- setAppProperties(props) {
1038
- this._appProperties = props;
1039
- return this;
1040
- }
1032
+ setAppProperties(doc, props) {
1033
+ _toState(doc).appProperties = props;
1034
+ },
1041
1035
  /** Set document background. */
1042
- setBackground(background) {
1043
- this._background = background;
1044
- return this;
1045
- }
1036
+ setBackground(doc, background) {
1037
+ _toState(doc).background = background;
1038
+ },
1046
1039
  /** Add a custom document property. */
1047
- addCustomProperty(name, value) {
1048
- this._customProperties.push({ name, value });
1049
- return this;
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 (this._body.length > 0) {
1055
- const last = this._body[this._body.length - 1];
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
- this._body[this._body.length - 1] = {
1051
+ s.body[s.body.length - 1] = {
1059
1052
  ...last,
1060
1053
  properties: { ...existingProps, sectionProperties: props }
1061
1054
  };
1062
- return this;
1055
+ return;
1063
1056
  }
1064
1057
  }
1065
1058
  // If no previous paragraph, add an empty one with section properties
1066
- this._body.push(paragraph([], { sectionProperties: props }));
1067
- return this;
1068
- }
1059
+ s.body.push(paragraph([], { sectionProperties: props }));
1060
+ },
1069
1061
  /** Get next available bookmark ID. */
1070
- nextBookmarkId() {
1071
- return this._nextBookmarkId++;
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: this._body,
1077
- sectionProperties: this._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: this._styles.length > 0 ? this._styles : undefined,
1082
- docDefaults: this._docDefaults,
1083
- abstractNumberings: this._abstractNumberings.length > 0 ? this._abstractNumberings : undefined,
1084
- numberingInstances: this._numberingInstances.length > 0 ? this._numberingInstances : undefined,
1085
- headers: this._headers.size > 0 ? this._headers : undefined,
1086
- footers: this._footers.size > 0 ? this._footers : undefined,
1087
- footnotes: this._footnotes.length > 0 ? this._footnotes : undefined,
1088
- endnotes: this._endnotes.length > 0 ? this._endnotes : undefined,
1089
- images: this._images.length > 0 ? this._images : undefined,
1090
- fonts: this._fonts.length > 0 ? this._fonts : undefined,
1091
- settings: this._settings,
1092
- coreProperties: this._coreProperties,
1093
- appProperties: this._appProperties,
1094
- comments: this._comments.length > 0 ? this._comments : undefined,
1095
- background: this._background,
1096
- customProperties: this._customProperties.length > 0 ? this._customProperties : undefined,
1097
- watermark: this._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
- /** Build and package to DOCX bytes. */
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
- }