odf.js 1.1.0 → 1.2.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/dist/index.cjs CHANGED
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let zod = require("zod");
3
3
  let fast_xml_parser = require("fast-xml-parser");
4
4
  let fflate = require("fflate");
5
+ let document_content_model = require("document-content-model");
5
6
  //#region src/model/node.ts
6
7
  const AttributeSchema = zod.z.object({
7
8
  name: zod.z.string(),
@@ -474,7 +475,7 @@ const STANDARD_XML_PART_NAMES = /* @__PURE__ */ new Set([
474
475
  function findChildElement(nodes, tag) {
475
476
  for (const node of nodes) if (node.type === "element" && node.tag === tag) return node;
476
477
  }
477
- function attrValue(element, name) {
478
+ function attrValue$1(element, name) {
478
479
  return element.attributes.find((attribute) => attribute.name === name)?.value;
479
480
  }
480
481
  function readManifest(pkg) {
@@ -482,15 +483,15 @@ function readManifest(pkg) {
482
483
  if (part?.kind !== "xml") throw new Error(`package has no ${MANIFEST_PART} XML part to read`);
483
484
  const root = findChildElement(part.nodes, "manifest:manifest");
484
485
  if (root === void 0) throw new Error(`${MANIFEST_PART} has no manifest:manifest root element`);
485
- const version = attrValue(root, "manifest:version");
486
+ const version = attrValue$1(root, "manifest:version");
486
487
  if (version === void 0) throw new Error(`${MANIFEST_PART}'s manifest:manifest root is missing the required manifest:version attribute`);
487
488
  const entries = [];
488
489
  for (const child of root.children) {
489
490
  if (child.type !== "element" || child.tag !== "manifest:file-entry") continue;
490
- const fullPath = attrValue(child, "manifest:full-path");
491
- const mediaType = attrValue(child, "manifest:media-type");
491
+ const fullPath = attrValue$1(child, "manifest:full-path");
492
+ const mediaType = attrValue$1(child, "manifest:media-type");
492
493
  if (fullPath === void 0 || mediaType === void 0) throw new Error(`${MANIFEST_PART} has a manifest:file-entry missing manifest:full-path or manifest:media-type`);
493
- const entryVersion = attrValue(child, "manifest:version");
494
+ const entryVersion = attrValue$1(child, "manifest:version");
494
495
  entries.push(entryVersion === void 0 ? {
495
496
  fullPath,
496
497
  mediaType
@@ -649,7 +650,7 @@ function validateManifest(pkg) {
649
650
  for (const child of root.children) {
650
651
  if (child.type !== "element" || child.tag !== "manifest:file-entry") continue;
651
652
  if (!child.children.some((grandchild) => grandchild.type === "element" && grandchild.tag === "manifest:encryption-data")) continue;
652
- const fullPath = attrValue(child, "manifest:full-path");
653
+ const fullPath = attrValue$1(child, "manifest:full-path");
653
654
  if (fullPath === void 0) continue;
654
655
  problems.push({
655
656
  severity: "warning",
@@ -681,6 +682,658 @@ function setDocumentMediaType(pkg, mediaType, version = DEFAULT_MANIFEST_VERSION
681
682
  });
682
683
  }
683
684
  //#endregion
685
+ //#region src/styles/properties.ts
686
+ const StylePropertiesSchema = zod.z.object({
687
+ bold: zod.z.boolean().optional(),
688
+ italic: zod.z.boolean().optional(),
689
+ underline: zod.z.boolean().optional(),
690
+ strike: zod.z.boolean().optional(),
691
+ fontFamily: zod.z.string().optional(),
692
+ sizePt: zod.z.number().optional(),
693
+ color: document_content_model.ColorSchema.optional(),
694
+ alignment: document_content_model.AlignmentSchema.optional(),
695
+ spacingBeforePt: zod.z.number().optional(),
696
+ spacingAfterPt: zod.z.number().optional(),
697
+ lineSpacing: zod.z.number().optional(),
698
+ indentLeftPt: zod.z.number().optional(),
699
+ indentFirstLinePt: zod.z.number().optional()
700
+ });
701
+ const ATTR = {
702
+ fontWeight: "fo:font-weight",
703
+ fontStyle: "fo:font-style",
704
+ underlineStyle: "style:text-underline-style",
705
+ underlineWidth: "style:text-underline-width",
706
+ underlineColor: "style:text-underline-color",
707
+ lineThroughStyle: "style:text-line-through-style",
708
+ lineThroughType: "style:text-line-through-type",
709
+ fontFamily: "fo:font-family",
710
+ fontSize: "fo:font-size",
711
+ color: "fo:color",
712
+ textAlign: "fo:text-align",
713
+ marginTop: "fo:margin-top",
714
+ marginBottom: "fo:margin-bottom",
715
+ lineHeight: "fo:line-height",
716
+ marginLeft: "fo:margin-left",
717
+ textIndent: "fo:text-indent"
718
+ };
719
+ const TEXT_ATTR_NAMES = /* @__PURE__ */ new Set([
720
+ ATTR.fontWeight,
721
+ ATTR.fontStyle,
722
+ ATTR.underlineStyle,
723
+ ATTR.underlineWidth,
724
+ ATTR.underlineColor,
725
+ ATTR.lineThroughStyle,
726
+ ATTR.lineThroughType,
727
+ ATTR.fontFamily,
728
+ ATTR.fontSize,
729
+ ATTR.color
730
+ ]);
731
+ const PARAGRAPH_ATTR_NAMES = /* @__PURE__ */ new Set([
732
+ ATTR.textAlign,
733
+ ATTR.marginTop,
734
+ ATTR.marginBottom,
735
+ ATTR.lineHeight,
736
+ ATTR.marginLeft,
737
+ ATTR.textIndent
738
+ ]);
739
+ function attributeMap(element) {
740
+ const map = /* @__PURE__ */ new Map();
741
+ for (const attribute of element.attributes) map.set(attribute.name, attribute.value);
742
+ return map;
743
+ }
744
+ const LENGTH_PATTERN = /^(-?(?:\d+(?:\.\d+)?|\.\d+))(cm|mm|in|pt|pc|px)$/;
745
+ function unitToPtFactor(unit) {
746
+ switch (unit) {
747
+ case "pt": return 1;
748
+ case "in": return 72;
749
+ case "cm": return 72 / 2.54;
750
+ case "mm": return 72 / 25.4;
751
+ case "pc": return 12;
752
+ case "px": return .75;
753
+ }
754
+ }
755
+ function isLengthUnit(value) {
756
+ return value === "cm" || value === "mm" || value === "in" || value === "pt" || value === "pc" || value === "px";
757
+ }
758
+ function parseLength(value) {
759
+ const match = LENGTH_PATTERN.exec(value);
760
+ if (match === null) return;
761
+ const numeric = match[1];
762
+ const unit = match[2];
763
+ if (numeric === void 0 || unit === void 0 || !isLengthUnit(unit)) return;
764
+ return Number(numeric) * unitToPtFactor(unit);
765
+ }
766
+ function formatPt(valuePt) {
767
+ return `${valuePt}pt`;
768
+ }
769
+ const PERCENTAGE_PATTERN = /^(-?(?:\d+(?:\.\d+)?|\.\d+))%$/;
770
+ function parsePercentageMultiplier(value) {
771
+ const match = PERCENTAGE_PATTERN.exec(value);
772
+ if (match === null) return;
773
+ const numeric = match[1];
774
+ if (numeric === void 0) return;
775
+ return Number(numeric) / 100;
776
+ }
777
+ function formatPercentageMultiplier(multiplier) {
778
+ return `${multiplier * 100}%`;
779
+ }
780
+ const COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
781
+ function parseColor(value) {
782
+ if (!COLOR_PATTERN.test(value)) return;
783
+ return (0, document_content_model.rgbHexToColor)(value);
784
+ }
785
+ function formatColor(color) {
786
+ return `#${(0, document_content_model.colorToRgbHex)(color)}`;
787
+ }
788
+ function parseLineDecoration(style, companionA, companionAOnValue, companionB, companionBOnValue) {
789
+ if (style === void 0 && companionA === void 0 && companionB === void 0) return;
790
+ if (style === "solid" && (companionA === void 0 || companionA === companionAOnValue) && (companionB === void 0 || companionB === companionBOnValue)) return true;
791
+ if (style === "none" && companionA === void 0 && companionB === void 0) return false;
792
+ return "unknown";
793
+ }
794
+ const RISKY_STYLE_ELEMENT_ATTRS = /* @__PURE__ */ new Set(["style:master-page-name", "style:next-style-name"]);
795
+ function parseTextProperties(element) {
796
+ const attrs = attributeMap(element);
797
+ const properties = {};
798
+ let hasUnknown = false;
799
+ for (const name of attrs.keys()) if (!TEXT_ATTR_NAMES.has(name)) hasUnknown = true;
800
+ const fontWeight = attrs.get(ATTR.fontWeight);
801
+ if (fontWeight === "bold") properties.bold = true;
802
+ else if (fontWeight === "normal") properties.bold = false;
803
+ else if (fontWeight !== void 0) hasUnknown = true;
804
+ const fontStyle = attrs.get(ATTR.fontStyle);
805
+ if (fontStyle === "italic") properties.italic = true;
806
+ else if (fontStyle === "normal") properties.italic = false;
807
+ else if (fontStyle !== void 0) hasUnknown = true;
808
+ const underline = parseLineDecoration(attrs.get(ATTR.underlineStyle), attrs.get(ATTR.underlineWidth), "auto", attrs.get(ATTR.underlineColor), "font-color");
809
+ if (underline === "unknown") hasUnknown = true;
810
+ else if (underline !== void 0) properties.underline = underline;
811
+ const strike = parseLineDecoration(attrs.get(ATTR.lineThroughStyle), attrs.get(ATTR.lineThroughType), "single", void 0, "");
812
+ if (strike === "unknown") hasUnknown = true;
813
+ else if (strike !== void 0) properties.strike = strike;
814
+ const fontFamily = attrs.get(ATTR.fontFamily);
815
+ if (fontFamily !== void 0) properties.fontFamily = fontFamily;
816
+ const fontSize = attrs.get(ATTR.fontSize);
817
+ if (fontSize !== void 0) {
818
+ const pt = parseLength(fontSize);
819
+ if (pt === void 0) hasUnknown = true;
820
+ else properties.sizePt = pt;
821
+ }
822
+ const color = attrs.get(ATTR.color);
823
+ if (color !== void 0) {
824
+ const parsed = parseColor(color);
825
+ if (parsed === void 0) hasUnknown = true;
826
+ else properties.color = parsed;
827
+ }
828
+ return {
829
+ properties,
830
+ hasUnknown
831
+ };
832
+ }
833
+ function parseParagraphProperties(element) {
834
+ const attrs = attributeMap(element);
835
+ const properties = {};
836
+ let hasUnknown = false;
837
+ for (const name of attrs.keys()) if (!PARAGRAPH_ATTR_NAMES.has(name)) hasUnknown = true;
838
+ const textAlign = attrs.get(ATTR.textAlign);
839
+ if (textAlign === "left" || textAlign === "center" || textAlign === "right" || textAlign === "justify") properties.alignment = textAlign;
840
+ else if (textAlign !== void 0) hasUnknown = true;
841
+ const marginTop = attrs.get(ATTR.marginTop);
842
+ if (marginTop !== void 0) {
843
+ const pt = parseLength(marginTop);
844
+ if (pt === void 0) hasUnknown = true;
845
+ else properties.spacingBeforePt = pt;
846
+ }
847
+ const marginBottom = attrs.get(ATTR.marginBottom);
848
+ if (marginBottom !== void 0) {
849
+ const pt = parseLength(marginBottom);
850
+ if (pt === void 0) hasUnknown = true;
851
+ else properties.spacingAfterPt = pt;
852
+ }
853
+ const marginLeft = attrs.get(ATTR.marginLeft);
854
+ if (marginLeft !== void 0) {
855
+ const pt = parseLength(marginLeft);
856
+ if (pt === void 0) hasUnknown = true;
857
+ else properties.indentLeftPt = pt;
858
+ }
859
+ const textIndent = attrs.get(ATTR.textIndent);
860
+ if (textIndent !== void 0) {
861
+ const pt = parseLength(textIndent);
862
+ if (pt === void 0) hasUnknown = true;
863
+ else properties.indentFirstLinePt = pt;
864
+ }
865
+ const lineHeight = attrs.get(ATTR.lineHeight);
866
+ if (lineHeight !== void 0) {
867
+ const multiplier = parsePercentageMultiplier(lineHeight);
868
+ if (multiplier === void 0) hasUnknown = true;
869
+ else properties.lineSpacing = multiplier;
870
+ }
871
+ return {
872
+ properties,
873
+ hasUnknown
874
+ };
875
+ }
876
+ function parseStyleElementProperties(styleElement) {
877
+ let properties = {};
878
+ let hasUnknown = false;
879
+ for (const attribute of styleElement.attributes) if (RISKY_STYLE_ELEMENT_ATTRS.has(attribute.name)) hasUnknown = true;
880
+ for (const child of styleElement.children) {
881
+ if (child.type !== "element") continue;
882
+ if (child.tag === "style:text-properties") {
883
+ const result = parseTextProperties(child);
884
+ properties = {
885
+ ...properties,
886
+ ...result.properties
887
+ };
888
+ if (result.hasUnknown) hasUnknown = true;
889
+ } else if (child.tag === "style:paragraph-properties") {
890
+ const result = parseParagraphProperties(child);
891
+ properties = {
892
+ ...properties,
893
+ ...result.properties
894
+ };
895
+ if (result.hasUnknown) hasUnknown = true;
896
+ } else hasUnknown = true;
897
+ }
898
+ return {
899
+ properties,
900
+ hasUnknown
901
+ };
902
+ }
903
+ function textPropertiesToAttributes(properties) {
904
+ const attributes = [];
905
+ if (properties.bold !== void 0) attributes.push({
906
+ name: ATTR.fontWeight,
907
+ value: properties.bold ? "bold" : "normal"
908
+ });
909
+ if (properties.italic !== void 0) attributes.push({
910
+ name: ATTR.fontStyle,
911
+ value: properties.italic ? "italic" : "normal"
912
+ });
913
+ if (properties.underline !== void 0) if (properties.underline) {
914
+ attributes.push({
915
+ name: ATTR.underlineStyle,
916
+ value: "solid"
917
+ });
918
+ attributes.push({
919
+ name: ATTR.underlineWidth,
920
+ value: "auto"
921
+ });
922
+ attributes.push({
923
+ name: ATTR.underlineColor,
924
+ value: "font-color"
925
+ });
926
+ } else attributes.push({
927
+ name: ATTR.underlineStyle,
928
+ value: "none"
929
+ });
930
+ if (properties.strike !== void 0) if (properties.strike) {
931
+ attributes.push({
932
+ name: ATTR.lineThroughStyle,
933
+ value: "solid"
934
+ });
935
+ attributes.push({
936
+ name: ATTR.lineThroughType,
937
+ value: "single"
938
+ });
939
+ } else attributes.push({
940
+ name: ATTR.lineThroughStyle,
941
+ value: "none"
942
+ });
943
+ if (properties.fontFamily !== void 0) attributes.push({
944
+ name: ATTR.fontFamily,
945
+ value: encodeXmlText(properties.fontFamily)
946
+ });
947
+ if (properties.sizePt !== void 0) attributes.push({
948
+ name: ATTR.fontSize,
949
+ value: formatPt(properties.sizePt)
950
+ });
951
+ if (properties.color !== void 0) attributes.push({
952
+ name: ATTR.color,
953
+ value: formatColor(properties.color)
954
+ });
955
+ return attributes;
956
+ }
957
+ function paragraphPropertiesToAttributes(properties) {
958
+ const attributes = [];
959
+ if (properties.alignment !== void 0) attributes.push({
960
+ name: ATTR.textAlign,
961
+ value: properties.alignment
962
+ });
963
+ if (properties.spacingBeforePt !== void 0) attributes.push({
964
+ name: ATTR.marginTop,
965
+ value: formatPt(properties.spacingBeforePt)
966
+ });
967
+ if (properties.spacingAfterPt !== void 0) attributes.push({
968
+ name: ATTR.marginBottom,
969
+ value: formatPt(properties.spacingAfterPt)
970
+ });
971
+ if (properties.lineSpacing !== void 0) attributes.push({
972
+ name: ATTR.lineHeight,
973
+ value: formatPercentageMultiplier(properties.lineSpacing)
974
+ });
975
+ if (properties.indentLeftPt !== void 0) attributes.push({
976
+ name: ATTR.marginLeft,
977
+ value: formatPt(properties.indentLeftPt)
978
+ });
979
+ if (properties.indentFirstLinePt !== void 0) attributes.push({
980
+ name: ATTR.textIndent,
981
+ value: formatPt(properties.indentFirstLinePt)
982
+ });
983
+ return attributes;
984
+ }
985
+ //#endregion
986
+ //#region src/styles/serialize.ts
987
+ function attributesToRecord(attributes) {
988
+ const record = {};
989
+ for (const attribute of attributes) record[attribute.name] = attribute.value;
990
+ return record;
991
+ }
992
+ function buildStylePropertyElements(properties) {
993
+ const elements = [];
994
+ const paragraphAttributes = paragraphPropertiesToAttributes(properties);
995
+ if (paragraphAttributes.length > 0) elements.push(el("style:paragraph-properties", attributesToRecord(paragraphAttributes)));
996
+ const textAttributes = textPropertiesToAttributes(properties);
997
+ if (textAttributes.length > 0) elements.push(el("style:text-properties", attributesToRecord(textAttributes)));
998
+ return elements;
999
+ }
1000
+ function canonicalPropertiesString(properties) {
1001
+ return [...paragraphPropertiesToAttributes(properties), ...textPropertiesToAttributes(properties)].map((attribute) => `${attribute.name}=${attribute.value}`).join("|");
1002
+ }
1003
+ //#endregion
1004
+ //#region src/styles/registry.ts
1005
+ const STYLE_FAMILIES = [
1006
+ "paragraph",
1007
+ "text",
1008
+ "table",
1009
+ "table-column",
1010
+ "table-row",
1011
+ "table-cell",
1012
+ "graphic"
1013
+ ];
1014
+ function isStyleFamily(value) {
1015
+ return value === "paragraph" || value === "text" || value === "table" || value === "table-column" || value === "table-row" || value === "table-cell" || value === "graphic";
1016
+ }
1017
+ const CONTENT_PREFIXES = {
1018
+ paragraph: "P",
1019
+ text: "T",
1020
+ table: "ta",
1021
+ "table-column": "co",
1022
+ "table-row": "ro",
1023
+ "table-cell": "ce",
1024
+ graphic: "fr"
1025
+ };
1026
+ const STYLES_PREFIXES = {
1027
+ paragraph: "PS",
1028
+ text: "TS",
1029
+ table: "taS",
1030
+ "table-column": "coS",
1031
+ "table-row": "roS",
1032
+ "table-cell": "ceS",
1033
+ graphic: "frS"
1034
+ };
1035
+ function prefixesForPart(partPath) {
1036
+ const baseName = partPath.slice(partPath.lastIndexOf("/") + 1);
1037
+ if (baseName === "content.xml") return CONTENT_PREFIXES;
1038
+ if (baseName === "styles.xml") return STYLES_PREFIXES;
1039
+ throw new Error(`StyleRegistry.forPart: expected a part named "content.xml" or "styles.xml" (by base name), got "${partPath}"`);
1040
+ }
1041
+ function emptyFamilySets() {
1042
+ return {
1043
+ paragraph: /* @__PURE__ */ new Set(),
1044
+ text: /* @__PURE__ */ new Set(),
1045
+ table: /* @__PURE__ */ new Set(),
1046
+ "table-column": /* @__PURE__ */ new Set(),
1047
+ "table-row": /* @__PURE__ */ new Set(),
1048
+ "table-cell": /* @__PURE__ */ new Set(),
1049
+ graphic: /* @__PURE__ */ new Set()
1050
+ };
1051
+ }
1052
+ function attrValue(element, name) {
1053
+ return element.attributes.find((attribute) => attribute.name === name)?.value;
1054
+ }
1055
+ function findDirectChild(nodes, tag) {
1056
+ for (const node of nodes) if (node.type === "element" && node.tag === tag) return node;
1057
+ }
1058
+ function findRootElement(nodes) {
1059
+ const root = nodes.find((node) => node.type === "element");
1060
+ if (root === void 0) throw new Error("StyleRegistry: part has no root XML element -- construct the part's minimal root (office:document-content/office:document-styles) before building a StyleRegistry for it");
1061
+ return root;
1062
+ }
1063
+ function ensureAutomaticStyles(root) {
1064
+ const existing = findDirectChild(root.children, "office:automatic-styles");
1065
+ if (existing !== void 0) return existing;
1066
+ const created = el("office:automatic-styles");
1067
+ const insertBeforeTags = /* @__PURE__ */ new Set([
1068
+ "office:body",
1069
+ "office:master-styles",
1070
+ "office:settings"
1071
+ ]);
1072
+ const insertIndex = root.children.findIndex((node) => node.type === "element" && insertBeforeTags.has(node.tag));
1073
+ if (insertIndex === -1) root.children.push(created);
1074
+ else root.children.splice(insertIndex, 0, created);
1075
+ return created;
1076
+ }
1077
+ function reserveStyleNames(container, reserved) {
1078
+ for (const child of container.children) {
1079
+ if (child.type !== "element" || child.tag !== "style:style") continue;
1080
+ const name = attrValue(child, "style:name");
1081
+ const family = attrValue(child, "style:family");
1082
+ if (name === void 0 || family === void 0 || !isStyleFamily(family)) continue;
1083
+ reserved[family].add(name);
1084
+ }
1085
+ }
1086
+ const FINGERPRINT_SEPARATOR = "\0";
1087
+ const NO_PARENT_SENTINEL = "";
1088
+ function computeFingerprint(family, properties, parentStyleName) {
1089
+ const parentComponent = parentStyleName ?? NO_PARENT_SENTINEL;
1090
+ return [
1091
+ family,
1092
+ canonicalPropertiesString(properties),
1093
+ parentComponent
1094
+ ].join(FINGERPRINT_SEPARATOR);
1095
+ }
1096
+ var StyleRegistry = class StyleRegistry {
1097
+ automaticStyles;
1098
+ prefixes;
1099
+ reservedByFamily;
1100
+ knownStyles = /* @__PURE__ */ new Map();
1101
+ fingerprintToName = /* @__PURE__ */ new Map();
1102
+ nameToFingerprint = /* @__PURE__ */ new Map();
1103
+ familyCounters = {
1104
+ paragraph: 1,
1105
+ text: 1,
1106
+ table: 1,
1107
+ "table-column": 1,
1108
+ "table-row": 1,
1109
+ "table-cell": 1,
1110
+ graphic: 1
1111
+ };
1112
+ constructor(automaticStyles, prefixes, reservedByFamily) {
1113
+ this.automaticStyles = automaticStyles;
1114
+ this.prefixes = prefixes;
1115
+ this.reservedByFamily = reservedByFamily;
1116
+ }
1117
+ static forPart(pkg, partPath, options = {}) {
1118
+ const part = pkg.parts[partPath];
1119
+ if (part?.kind !== "xml") throw new Error(`StyleRegistry.forPart: "${partPath}" is not an XML part of the given package`);
1120
+ const prefixes = prefixesForPart(partPath);
1121
+ const root = findRootElement(part.nodes);
1122
+ const automaticStyles = ensureAutomaticStyles(root);
1123
+ const reservedByFamily = emptyFamilySets();
1124
+ const registry = new StyleRegistry(automaticStyles, prefixes, reservedByFamily);
1125
+ for (const child of automaticStyles.children) {
1126
+ if (child.type !== "element" || child.tag !== "style:style") continue;
1127
+ const name = attrValue(child, "style:name");
1128
+ const family = attrValue(child, "style:family");
1129
+ if (name === void 0 || family === void 0 || !isStyleFamily(family)) continue;
1130
+ registry.knownStyles.set(name, child);
1131
+ reservedByFamily[family].add(name);
1132
+ const parsed = parseStyleElementProperties(child);
1133
+ if (!parsed.hasUnknown) {
1134
+ const parentStyleName = attrValue(child, "style:parent-style-name");
1135
+ const fingerprint = computeFingerprint(family, parsed.properties, parentStyleName);
1136
+ if (!registry.fingerprintToName.has(fingerprint)) {
1137
+ registry.fingerprintToName.set(fingerprint, name);
1138
+ registry.nameToFingerprint.set(name, fingerprint);
1139
+ }
1140
+ }
1141
+ }
1142
+ const ownStyles = findDirectChild(root.children, "office:styles");
1143
+ if (ownStyles !== void 0) reserveStyleNames(ownStyles, reservedByFamily);
1144
+ if (options.otherPart !== void 0) {
1145
+ const otherPart = options.otherPart.pkg.parts[options.otherPart.partPath];
1146
+ if (otherPart?.kind === "xml") {
1147
+ const otherRoot = findRootElement(otherPart.nodes);
1148
+ const otherAutomatic = findDirectChild(otherRoot.children, "office:automatic-styles");
1149
+ if (otherAutomatic !== void 0) reserveStyleNames(otherAutomatic, reservedByFamily);
1150
+ const otherStyles = findDirectChild(otherRoot.children, "office:styles");
1151
+ if (otherStyles !== void 0) reserveStyleNames(otherStyles, reservedByFamily);
1152
+ }
1153
+ }
1154
+ if (options.additionalReservedNames !== void 0) for (const family of STYLE_FAMILIES) for (const name of options.additionalReservedNames) reservedByFamily[family].add(name);
1155
+ return registry;
1156
+ }
1157
+ fingerprint(request) {
1158
+ return computeFingerprint(request.family, request.properties, request.parentStyleName);
1159
+ }
1160
+ intern(request) {
1161
+ const fingerprint = this.fingerprint(request);
1162
+ const existingName = this.fingerprintToName.get(fingerprint);
1163
+ if (existingName !== void 0) return existingName;
1164
+ const name = this.mintName(request.family);
1165
+ const attributes = {
1166
+ "style:name": name,
1167
+ "style:family": request.family
1168
+ };
1169
+ if (request.parentStyleName !== void 0) attributes["style:parent-style-name"] = encodeXmlText(request.parentStyleName);
1170
+ const styleElement = el("style:style", attributes, buildStylePropertyElements(request.properties));
1171
+ this.automaticStyles.children.push(styleElement);
1172
+ this.knownStyles.set(name, styleElement);
1173
+ this.reservedByFamily[request.family].add(name);
1174
+ this.fingerprintToName.set(fingerprint, name);
1175
+ this.nameToFingerprint.set(name, fingerprint);
1176
+ return name;
1177
+ }
1178
+ mintName(family) {
1179
+ const prefix = this.prefixes[family];
1180
+ const reserved = this.reservedByFamily[family];
1181
+ let counter = this.familyCounters[family];
1182
+ while (reserved.has(`${prefix}${counter}`)) counter += 1;
1183
+ const name = `${prefix}${counter}`;
1184
+ this.familyCounters[family] = counter + 1;
1185
+ return name;
1186
+ }
1187
+ names() {
1188
+ return [...this.knownStyles.keys()];
1189
+ }
1190
+ gc(referenced) {
1191
+ let removed = 0;
1192
+ for (const [name, element] of [...this.knownStyles]) {
1193
+ if (referenced.has(name)) continue;
1194
+ const index = this.automaticStyles.children.indexOf(element);
1195
+ if (index !== -1) this.automaticStyles.children.splice(index, 1);
1196
+ this.knownStyles.delete(name);
1197
+ const fingerprint = this.nameToFingerprint.get(name);
1198
+ if (fingerprint !== void 0) {
1199
+ this.fingerprintToName.delete(fingerprint);
1200
+ this.nameToFingerprint.delete(name);
1201
+ }
1202
+ removed += 1;
1203
+ }
1204
+ return removed;
1205
+ }
1206
+ };
1207
+ //#endregion
1208
+ //#region src/styles/span.ts
1209
+ function ensureSpan(paragraph, start, end, styleName) {
1210
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start) throw new Error(`ensureSpan: invalid range [${start}, ${end})`);
1211
+ const total = sumLength(paragraph.children);
1212
+ if (end > total) throw new Error(`ensureSpan: range end ${end} exceeds the container's total character length ${total}`);
1213
+ const { before, after: rest } = splitChildrenAt(paragraph.children, start);
1214
+ const { before: middle, after } = splitChildrenAt(rest, end - start);
1215
+ let span;
1216
+ const soleChild = middle.length === 1 ? middle[0] : void 0;
1217
+ if (soleChild?.type === "element" && soleChild.tag === "text:span") {
1218
+ span = soleChild;
1219
+ setStyleName(span, styleName);
1220
+ } else span = el("text:span", { "text:style-name": encodeXmlText(styleName) }, middle);
1221
+ paragraph.children = [
1222
+ ...before,
1223
+ span,
1224
+ ...after
1225
+ ];
1226
+ return span;
1227
+ }
1228
+ function cloneAttributes(attributes) {
1229
+ return attributes.map((attribute) => ({ ...attribute }));
1230
+ }
1231
+ function setStyleName(span, styleName) {
1232
+ const encoded = encodeXmlText(styleName);
1233
+ const existing = span.attributes.find((attribute) => attribute.name === "text:style-name");
1234
+ if (existing !== void 0) {
1235
+ existing.value = encoded;
1236
+ return;
1237
+ }
1238
+ span.attributes.push({
1239
+ name: "text:style-name",
1240
+ value: encoded
1241
+ });
1242
+ }
1243
+ function getSpaceCount(spaceElement) {
1244
+ const raw = spaceElement.attributes.find((attribute) => attribute.name === "text:c")?.value;
1245
+ if (raw === void 0) return 1;
1246
+ const parsed = Number.parseInt(raw, 10);
1247
+ if (!Number.isInteger(parsed) || parsed < 0 || String(parsed) !== raw) throw new Error(`ensureSpan: text:s has a malformed text:c attribute: "${raw}"`);
1248
+ return parsed;
1249
+ }
1250
+ function buildSpaceRun(count) {
1251
+ return count === 1 ? el("text:s") : el("text:s", { "text:c": String(count) });
1252
+ }
1253
+ function measureLength(node) {
1254
+ if (node.type === "text") return node.value.length;
1255
+ if (node.type !== "element") return 0;
1256
+ if (node.tag === "text:s") return getSpaceCount(node);
1257
+ if (node.tag === "text:tab" || node.tag === "text:line-break") return 1;
1258
+ if (node.tag === "text:span") return sumLength(node.children);
1259
+ return 0;
1260
+ }
1261
+ function sumLength(nodes) {
1262
+ let total = 0;
1263
+ for (const node of nodes) total += measureLength(node);
1264
+ return total;
1265
+ }
1266
+ function splitNode(node, offset) {
1267
+ if (node.type === "text") return {
1268
+ left: {
1269
+ type: "text",
1270
+ value: node.value.slice(0, offset)
1271
+ },
1272
+ right: {
1273
+ type: "text",
1274
+ value: node.value.slice(offset)
1275
+ }
1276
+ };
1277
+ if (node.type === "element" && node.tag === "text:s") {
1278
+ const count = getSpaceCount(node);
1279
+ return {
1280
+ left: buildSpaceRun(offset),
1281
+ right: buildSpaceRun(count - offset)
1282
+ };
1283
+ }
1284
+ if (node.type === "element" && node.tag === "text:span") {
1285
+ const inner = splitChildrenAt(node.children, offset);
1286
+ return {
1287
+ left: inner.before.length === 0 ? void 0 : {
1288
+ ...node,
1289
+ attributes: cloneAttributes(node.attributes),
1290
+ children: inner.before
1291
+ },
1292
+ right: inner.after.length === 0 ? void 0 : {
1293
+ ...node,
1294
+ attributes: cloneAttributes(node.attributes),
1295
+ children: inner.after
1296
+ }
1297
+ };
1298
+ }
1299
+ const label = node.type === "element" ? node.tag : node.type;
1300
+ throw new Error(`ensureSpan: cannot split "${label}" at a fractional offset -- this indicates a character-length computation bug, since every node type with length 1 or 0 should never reach this branch`);
1301
+ }
1302
+ function splitChildrenAt(children, offset) {
1303
+ if (offset <= 0) return {
1304
+ before: [],
1305
+ after: [...children]
1306
+ };
1307
+ const before = [];
1308
+ let remaining = offset;
1309
+ for (let index = 0; index < children.length; index += 1) {
1310
+ if (remaining === 0) return {
1311
+ before,
1312
+ after: children.slice(index)
1313
+ };
1314
+ const node = children[index];
1315
+ const length = measureLength(node);
1316
+ if (remaining >= length) {
1317
+ before.push(node);
1318
+ remaining -= length;
1319
+ continue;
1320
+ }
1321
+ const { left, right } = splitNode(node, remaining);
1322
+ const after = [];
1323
+ if (left !== void 0) before.push(left);
1324
+ if (right !== void 0) after.push(right);
1325
+ after.push(...children.slice(index + 1));
1326
+ return {
1327
+ before,
1328
+ after
1329
+ };
1330
+ }
1331
+ return {
1332
+ before,
1333
+ after: []
1334
+ };
1335
+ }
1336
+ //#endregion
684
1337
  exports.AttributeSchema = AttributeSchema;
685
1338
  exports.BinaryPartSchema = BinaryPartSchema;
686
1339
  exports.MANIFEST_PART = MANIFEST_PART;
@@ -692,6 +1345,9 @@ exports.ODF_MEDIA_TYPES = ODF_MEDIA_TYPES;
692
1345
  exports.ODF_NAMESPACES = ODF_NAMESPACES;
693
1346
  exports.PackageSchema = PackageSchema;
694
1347
  exports.PartSchema = PartSchema;
1348
+ exports.STYLE_FAMILIES = STYLE_FAMILIES;
1349
+ exports.StylePropertiesSchema = StylePropertiesSchema;
1350
+ exports.StyleRegistry = StyleRegistry;
695
1351
  exports.XmlCdataSchema = XmlCdataSchema;
696
1352
  exports.XmlCommentSchema = XmlCommentSchema;
697
1353
  exports.XmlDeclarationSchema = XmlDeclarationSchema;
@@ -702,16 +1358,26 @@ exports.XmlPiSchema = XmlPiSchema;
702
1358
  exports.XmlTextSchema = XmlTextSchema;
703
1359
  exports.base64ToBytes = base64ToBytes;
704
1360
  exports.buildManifest = buildManifest;
1361
+ exports.buildStylePropertyElements = buildStylePropertyElements;
705
1362
  exports.buildXml = buildXml;
706
1363
  exports.bytesToBase64 = bytesToBase64;
1364
+ exports.canonicalPropertiesString = canonicalPropertiesString;
707
1365
  exports.decodePackage = decodePackage;
708
1366
  exports.el = el;
709
1367
  exports.encodePackage = encodePackage;
710
1368
  exports.encodeXmlText = encodeXmlText;
1369
+ exports.ensureSpan = ensureSpan;
1370
+ exports.formatPercentageMultiplier = formatPercentageMultiplier;
1371
+ exports.formatPt = formatPt;
711
1372
  exports.isXmlNode = isXmlNode;
712
1373
  exports.mediaTypeForExtension = mediaTypeForExtension;
713
1374
  exports.packageCodec = packageCodec;
1375
+ exports.paragraphPropertiesToAttributes = paragraphPropertiesToAttributes;
1376
+ exports.parseLength = parseLength;
714
1377
  exports.parsePackage = parsePackage;
1378
+ exports.parseParagraphProperties = parseParagraphProperties;
1379
+ exports.parseStyleElementProperties = parseStyleElementProperties;
1380
+ exports.parseTextProperties = parseTextProperties;
715
1381
  exports.parseXml = parseXml;
716
1382
  exports.readManifest = readManifest;
717
1383
  exports.readMimetype = readMimetype;
@@ -719,6 +1385,7 @@ exports.serializePackage = serializePackage;
719
1385
  exports.setDocumentMediaType = setDocumentMediaType;
720
1386
  exports.sniffImageFormat = sniffImageFormat;
721
1387
  exports.syncManifest = syncManifest;
1388
+ exports.textPropertiesToAttributes = textPropertiesToAttributes;
722
1389
  exports.txt = txt;
723
1390
  exports.unzipPackage = unzipPackage;
724
1391
  exports.validateManifest = validateManifest;