superdoc 1.13.0-next.9 → 1.13.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 (34) hide show
  1. package/dist/chunks/{helpers-OFep8CYR.cjs → DocxZipper-B6nm2R9-.cjs} +300 -392
  2. package/dist/chunks/{helpers-BqdwtJE0.es.js → DocxZipper-D_TE2vfz.es.js} +300 -392
  3. package/dist/chunks/{PdfViewer-CrKV8eHY.cjs → PdfViewer-BMgW1ZZ0.cjs} +2 -2
  4. package/dist/chunks/{PdfViewer-H0MTPIfL.es.js → PdfViewer-Diuw98hQ.es.js} +2 -2
  5. package/dist/chunks/{SuperConverter-BAshNZxb.es.js → SuperConverter-BfWET8X-.es.js} +181 -488
  6. package/dist/chunks/{SuperConverter-MBrfq8sX.cjs → SuperConverter-sfP7jOr1.cjs} +179 -486
  7. package/dist/chunks/helpers-CCzma9mJ.cjs +419 -0
  8. package/dist/chunks/helpers-Zjk_AcyH.es.js +420 -0
  9. package/dist/chunks/{index-DR_0n8Ql.es.js → index-B9Jx-TG0.es.js} +6 -6
  10. package/dist/chunks/{index-BzVFH_iV.cjs → index-CCq26Owl.cjs} +6 -5
  11. package/dist/chunks/{index-DM0PJv0N.es.js → index-CDnB6b65.es.js} +6 -5
  12. package/dist/chunks/{index-DCaJJXb5.cjs → index-ClQ2DTZb.cjs} +15 -15
  13. package/dist/super-editor/converter.cjs +2 -2
  14. package/dist/super-editor/converter.es.js +2 -2
  15. package/dist/super-editor/core/DocxZipper.d.ts.map +1 -1
  16. package/dist/super-editor/core/super-converter/SuperConverter.d.ts.map +1 -1
  17. package/dist/super-editor/core/super-converter/helpers.d.ts +7 -0
  18. package/dist/super-editor/core/super-converter/helpers.d.ts.map +1 -1
  19. package/dist/super-editor/core/super-converter/v3/handlers/wp/helpers/metafile-converter.d.ts.map +1 -1
  20. package/dist/super-editor/docx-zipper.cjs +4 -313
  21. package/dist/super-editor/docx-zipper.es.js +4 -313
  22. package/dist/super-editor/src/core/DocxZipper.d.ts.map +1 -1
  23. package/dist/super-editor/src/core/super-converter/SuperConverter.d.ts.map +1 -1
  24. package/dist/super-editor/src/core/super-converter/helpers.d.ts +7 -0
  25. package/dist/super-editor/src/core/super-converter/helpers.d.ts.map +1 -1
  26. package/dist/super-editor/src/core/super-converter/v3/handlers/wp/helpers/metafile-converter.d.ts.map +1 -1
  27. package/dist/super-editor/tsconfig.types.tsbuildinfo +1 -1
  28. package/dist/super-editor.cjs +4 -4
  29. package/dist/super-editor.es.js +5 -5
  30. package/dist/superdoc.cjs +5 -4
  31. package/dist/superdoc.es.js +5 -4
  32. package/dist/superdoc.umd.js +787 -1097
  33. package/dist/superdoc.umd.js.map +1 -1
  34. package/package.json +3 -3
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
- const xmlJs = require("./xml-js-D1navVqh.cjs");
2
+ const helpers = require("./helpers-CCzma9mJ.cjs");
3
+ const jszip_min = require("./jszip.min-oAFpNMh5.cjs");
3
4
  var buffer = {};
4
5
  var base64Js = {};
5
6
  base64Js.byteLength = byteLength;
@@ -1759,409 +1760,316 @@ ieee754.write = function(buffer2, value, offset, isLE, mLen, nBytes) {
1759
1760
  }
1760
1761
  })(buffer);
1761
1762
  const Buffer = buffer.Buffer;
1762
- var libExports = xmlJs.requireLib();
1763
- const PIXELS_PER_INCH = 96;
1764
- function inchesToTwips(inches) {
1765
- if (inches == null) return;
1766
- if (typeof inches === "string") inches = parseFloat(inches);
1767
- return Math.round(Number(inches) * 1440);
1763
+ const isXmlLike = (name) => /\.xml$|\.rels$/i.test(name);
1764
+ function sniffEncoding(u8) {
1765
+ if (u8.length >= 2) {
1766
+ const b0 = u8[0], b1 = u8[1];
1767
+ if (b0 === 255 && b1 === 254) return "utf-16le";
1768
+ if (b0 === 254 && b1 === 255) return "utf-16be";
1769
+ }
1770
+ let nul = 0;
1771
+ for (let i = 0; i < Math.min(64, u8.length); i++) if (u8[i] === 0) nul++;
1772
+ if (nul > 16) return "utf-16le";
1773
+ return "utf-8";
1768
1774
  }
1769
- function twipsToInches(twips) {
1770
- if (twips == null) return;
1771
- const value = Number(twips);
1772
- if (Number.isNaN(value)) return;
1773
- return value / 1440;
1775
+ function stripBOM(str) {
1776
+ return str && str.charCodeAt(0) === 65279 ? str.slice(1) : str;
1774
1777
  }
1775
- function twipsToPixels(twips) {
1776
- if (twips == null) return;
1777
- const inches = twipsToInches(twips);
1778
- return inchesToPixels(inches);
1778
+ function ensureXmlString(content) {
1779
+ if (typeof content === "string") return stripBOM(content);
1780
+ let u8 = null;
1781
+ if (content && typeof content === "object") {
1782
+ if (content instanceof Uint8Array) {
1783
+ u8 = content;
1784
+ } else if (typeof Buffer !== "undefined" && Buffer.isBuffer && Buffer.isBuffer(content)) {
1785
+ u8 = new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
1786
+ } else if (ArrayBuffer.isView && ArrayBuffer.isView(content)) {
1787
+ u8 = new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
1788
+ } else if (content.constructor && (content instanceof ArrayBuffer || content.constructor.name === "ArrayBuffer")) {
1789
+ u8 = new Uint8Array(content);
1790
+ }
1791
+ }
1792
+ if (!u8) throw new Error("Unsupported content type for XML");
1793
+ const enc = sniffEncoding(u8);
1794
+ let xml = new TextDecoder(enc).decode(u8);
1795
+ return stripBOM(xml);
1779
1796
  }
1780
- function pixelsToTwips(pixels) {
1781
- const inches = pixelsToInches(pixels);
1782
- return inchesToTwips(inches);
1783
- }
1784
- function inchesToPixels(inches) {
1785
- if (inches == null) return;
1786
- const pixels = inches * PIXELS_PER_INCH;
1787
- return Math.round(pixels * 1e3) / 1e3;
1788
- }
1789
- function pixelsToInches(pixels) {
1790
- if (pixels == null) return;
1791
- const inches = Number(pixels) / PIXELS_PER_INCH;
1792
- return inches;
1793
- }
1794
- function twipsToLines(twips) {
1795
- if (twips == null) return;
1796
- return twips / 240;
1797
- }
1798
- function linesToTwips(lines) {
1799
- if (lines == null) return;
1800
- return lines * 240;
1801
- }
1802
- function halfPointToPoints(halfPoints) {
1803
- if (halfPoints == null) return;
1804
- return Math.round(halfPoints) / 2;
1805
- }
1806
- function emuToPixels(emu) {
1807
- if (emu == null) return;
1808
- if (typeof emu === "string") emu = parseFloat(emu);
1809
- const pixels = emu * PIXELS_PER_INCH / 914400;
1810
- return Math.round(pixels);
1811
- }
1812
- function pixelsToEmu(px) {
1813
- if (px == null) return;
1814
- if (typeof px === "string") px = parseFloat(px);
1815
- return Math.round(px * 9525);
1816
- }
1817
- function eighthPointsToPixels(eighthPoints) {
1818
- if (eighthPoints == null) return;
1819
- const points = parseFloat(eighthPoints) / 8;
1820
- const pixels = points * 1.3333;
1821
- return pixels;
1822
- }
1823
- function pointsToTwips(points) {
1824
- if (points == null) return;
1825
- return points * 20;
1826
- }
1827
- function pixelsToEightPoints(pixels) {
1828
- if (pixels == null) return;
1829
- return Math.round(pixels * 6);
1830
- }
1831
- function twipsToPt(twips) {
1832
- if (twips == null) return;
1833
- return twips / 20;
1834
- }
1835
- function ptToTwips(pt) {
1836
- if (pt == null) return;
1837
- return pt * 20;
1838
- }
1839
- function rotToDegrees(rot) {
1840
- if (rot == null) return;
1841
- return rot / 6e4;
1842
- }
1843
- function degreesToRot(degrees) {
1844
- if (degrees == null) return;
1845
- return degrees * 6e4;
1846
- }
1847
- function pixelsToPolygonUnits(pixels) {
1848
- if (pixels == null) return;
1849
- const pu = pixels * PIXELS_PER_INCH;
1850
- return Math.round(pu);
1851
- }
1852
- function polygonUnitsToPixels(pu) {
1853
- if (pu == null) return;
1854
- const pixels = Number(pu) / PIXELS_PER_INCH;
1855
- return Math.round(pixels * 1e3) / 1e3;
1856
- }
1857
- function polygonToObj(polygonNode) {
1858
- if (!polygonNode) return null;
1859
- const points = [];
1860
- polygonNode.elements.forEach((element) => {
1861
- if (["wp:start", "wp:lineTo"].includes(element.name)) {
1862
- const { x, y } = element.attributes;
1863
- points.push([polygonUnitsToPixels(x), polygonUnitsToPixels(y)]);
1864
- }
1865
- });
1866
- if (points.length > 1) {
1867
- const firstPoint = points[0];
1868
- const lastPoint = points[points.length - 1];
1869
- if (firstPoint[0] === lastPoint[0] && firstPoint[1] === lastPoint[1]) {
1870
- points.pop();
1797
+ class DocxZipper {
1798
+ constructor(params = {}) {
1799
+ this.debug = params.debug || false;
1800
+ this.zip = new jszip_min.JSZip();
1801
+ this.files = [];
1802
+ this.media = {};
1803
+ this.mediaFiles = {};
1804
+ this.fonts = {};
1805
+ }
1806
+ /**
1807
+ * Get all docx data from the zipped docx
1808
+ *
1809
+ * [ContentTypes].xml
1810
+ * _rels/.rels
1811
+ * word/document.xml
1812
+ * word/_rels/document.xml.rels
1813
+ * word/footnotes.xml
1814
+ * word/endnotes.xml
1815
+ * word/header1.xml
1816
+ * word/theme/theme1.xml
1817
+ * word/settings.xml
1818
+ * word/styles.xml
1819
+ * word/webSettings.xml
1820
+ * word/fontTable.xml
1821
+ * docProps/core.xml
1822
+ * docProps/app.xml
1823
+ * */
1824
+ async getDocxData(file, isNode = false) {
1825
+ const extractedFiles = await this.unzip(file);
1826
+ const files = Object.entries(extractedFiles.files);
1827
+ for (const [, zipEntry] of files) {
1828
+ const name = zipEntry.name;
1829
+ if (isXmlLike(name)) {
1830
+ const u8 = await zipEntry.async("uint8array");
1831
+ const content = ensureXmlString(u8);
1832
+ this.files.push({ name, content });
1833
+ } else if (name.startsWith("word/media") && name !== "word/media/" || zipEntry.name.startsWith("media") && zipEntry.name !== "media/" || name.startsWith("media") && name !== "media/" || name.startsWith("word/embeddings") && name !== "word/embeddings/") {
1834
+ if (isNode) {
1835
+ const buffer2 = await zipEntry.async("nodebuffer");
1836
+ const fileBase64 = buffer2.toString("base64");
1837
+ this.mediaFiles[name] = fileBase64;
1838
+ } else {
1839
+ const fileBase64 = await zipEntry.async("base64");
1840
+ const extension = this.getFileExtension(name)?.toLowerCase();
1841
+ const imageTypes = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "bmp", "tiff", "emf", "wmf", "svg", "webp"]);
1842
+ if (imageTypes.has(extension)) {
1843
+ this.mediaFiles[name] = `data:image/${extension};base64,${fileBase64}`;
1844
+ const blob = await zipEntry.async("blob");
1845
+ const fileObj = new File([blob], name, { type: blob.type });
1846
+ const imageUrl = URL.createObjectURL(fileObj);
1847
+ this.media[name] = imageUrl;
1848
+ } else {
1849
+ this.mediaFiles[name] = fileBase64;
1850
+ }
1851
+ }
1852
+ } else if (name.startsWith("word/fonts") && name !== "word/fonts/") {
1853
+ const uint8array = await zipEntry.async("uint8array");
1854
+ this.fonts[name] = uint8array;
1855
+ }
1871
1856
  }
1872
- }
1873
- return points;
1874
- }
1875
- function objToPolygon(points) {
1876
- if (!points || !Array.isArray(points)) return null;
1877
- const polygonNode = {
1878
- name: "wp:wrapPolygon",
1879
- type: "wp:wrapPolygon",
1880
- attributes: {
1881
- edited: "0"
1882
- },
1883
- elements: []
1884
- };
1885
- points.forEach((point, index) => {
1886
- const [x, y] = point;
1887
- const tagName = index === 0 ? "wp:start" : "wp:lineTo";
1888
- const pointNode = {
1889
- name: tagName,
1890
- type: tagName,
1891
- attributes: {
1892
- x: pixelsToPolygonUnits(x),
1893
- y: pixelsToPolygonUnits(y)
1857
+ return this.files;
1858
+ }
1859
+ getFileExtension(fileName) {
1860
+ const fileSplit = fileName.split(".");
1861
+ if (fileSplit.length < 2) return null;
1862
+ return fileSplit[fileSplit.length - 1];
1863
+ }
1864
+ /**
1865
+ * Update [Content_Types].xml with extensions of new Image annotations
1866
+ */
1867
+ async updateContentTypes(docx, media, fromJson, updatedDocs = {}) {
1868
+ const additionalPartNames = Object.keys(updatedDocs || {});
1869
+ const imageExts = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "bmp", "tiff", "emf", "wmf", "svg", "webp"]);
1870
+ const newMediaTypes = Object.keys(media).map((name) => this.getFileExtension(name)).filter((ext) => ext && imageExts.has(ext));
1871
+ const contentTypesPath = "[Content_Types].xml";
1872
+ let contentTypesXml;
1873
+ if (fromJson) {
1874
+ if (Array.isArray(docx.files)) {
1875
+ contentTypesXml = docx.files.find((file) => file.name === contentTypesPath)?.content || "";
1876
+ } else {
1877
+ contentTypesXml = docx.files?.[contentTypesPath] || "";
1894
1878
  }
1895
- };
1896
- polygonNode.elements.push(pointNode);
1897
- });
1898
- if (points.length > 0) {
1899
- const [startX, startY] = points[0];
1900
- const closePointNode = {
1901
- name: "wp:lineTo",
1902
- type: "wp:lineTo",
1903
- attributes: {
1904
- x: pixelsToPolygonUnits(startX),
1905
- y: pixelsToPolygonUnits(startY)
1879
+ } else contentTypesXml = await docx.file(contentTypesPath).async("string");
1880
+ let typesString = "";
1881
+ const defaultMediaTypes = helpers.getContentTypesFromXml(contentTypesXml);
1882
+ const seenTypes = /* @__PURE__ */ new Set();
1883
+ for (let type of newMediaTypes) {
1884
+ if (defaultMediaTypes.includes(type)) continue;
1885
+ if (seenTypes.has(type)) continue;
1886
+ const newContentType = `<Default Extension="${type}" ContentType="image/${type}"/>`;
1887
+ typesString += newContentType;
1888
+ seenTypes.add(type);
1889
+ }
1890
+ const xmlJson = JSON.parse(helpers.libExports.xml2json(contentTypesXml, null, 2));
1891
+ const types = xmlJson.elements?.find((el) => el.name === "Types") || {};
1892
+ const hasComments = types.elements?.some(
1893
+ (el) => el.name === "Override" && el.attributes.PartName === "/word/comments.xml"
1894
+ );
1895
+ const hasCommentsExtended = types.elements?.some(
1896
+ (el) => el.name === "Override" && el.attributes.PartName === "/word/commentsExtended.xml"
1897
+ );
1898
+ const hasCommentsIds = types.elements?.some(
1899
+ (el) => el.name === "Override" && el.attributes.PartName === "/word/commentsIds.xml"
1900
+ );
1901
+ const hasCommentsExtensible = types.elements?.some(
1902
+ (el) => el.name === "Override" && el.attributes.PartName === "/word/commentsExtensible.xml"
1903
+ );
1904
+ const hasFile = (filename) => {
1905
+ if (updatedDocs && Object.prototype.hasOwnProperty.call(updatedDocs, filename)) {
1906
+ return true;
1906
1907
  }
1908
+ if (!docx?.files) return false;
1909
+ if (!fromJson) return Boolean(docx.files[filename]);
1910
+ if (Array.isArray(docx.files)) return docx.files.some((file) => file.name === filename);
1911
+ return Boolean(docx.files[filename]);
1907
1912
  };
1908
- polygonNode.elements.push(closePointNode);
1909
- }
1910
- return polygonNode;
1911
- }
1912
- const REMOTE_RESOURCE_PATTERN = /^https?:|^blob:|^file:/i;
1913
- const DATA_URI_PATTERN = /^data:/i;
1914
- const getArrayBufferFromUrl = async (input) => {
1915
- if (input == null) {
1916
- return new ArrayBuffer(0);
1917
- }
1918
- if (input instanceof ArrayBuffer) {
1919
- return input;
1920
- }
1921
- if (ArrayBuffer.isView(input)) {
1922
- const view = input;
1923
- return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
1924
- }
1925
- if (typeof Blob !== "undefined" && input instanceof Blob) {
1926
- return await input.arrayBuffer();
1927
- }
1928
- if (typeof input !== "string") {
1929
- throw new TypeError("Unsupported media input type");
1930
- }
1931
- const trimmed = input.trim();
1932
- const shouldFetchRemote = REMOTE_RESOURCE_PATTERN.test(trimmed);
1933
- const isDataUri = DATA_URI_PATTERN.test(trimmed);
1934
- if (shouldFetchRemote) {
1935
- if (typeof fetch !== "function") {
1936
- throw new Error(`Fetch API is not available to retrieve media: ${trimmed}`);
1937
- }
1938
- const response = await fetch(trimmed);
1939
- if (!response.ok) {
1940
- throw new Error(`Fetch failed: ${response.status} ${response.statusText}`);
1941
- }
1942
- return await response.arrayBuffer();
1943
- }
1944
- const base64Payload = isDataUri ? trimmed.split(",", 2)[1] : trimmed.replace(/\s/g, "");
1945
- try {
1946
- if (typeof globalThis.atob === "function") {
1947
- const binary = globalThis.atob(base64Payload);
1948
- const bytes = new Uint8Array(binary.length);
1949
- for (let i = 0; i < binary.length; i++) {
1950
- bytes[i] = binary.charCodeAt(i);
1951
- }
1952
- return bytes.buffer;
1913
+ if (hasFile("word/comments.xml")) {
1914
+ const commentsDef = `<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml" />`;
1915
+ if (!hasComments) typesString += commentsDef;
1953
1916
  }
1954
- } catch (err) {
1955
- console.warn("atob failed, falling back to Buffer:", err);
1956
- }
1957
- const buf = Buffer.from(base64Payload, "base64");
1958
- return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1959
- };
1960
- const getContentTypesFromXml = (contentTypesXml) => {
1961
- try {
1962
- const result = libExports.xml2js(contentTypesXml, { compact: false });
1963
- const types = result?.elements?.[0]?.elements || [];
1964
- return types.filter((el) => el?.name === "Default").map((el) => el.attributes?.Extension).filter(Boolean);
1965
- } catch (err) {
1966
- console.warn("[super-editor] Failed to parse [Content_Types].xml", err);
1967
- return [];
1968
- }
1969
- };
1970
- const resolveOpcTargetPath = (target, baseDir = "word") => {
1971
- if (!target) return null;
1972
- if (target.includes("://")) return null;
1973
- if (target.startsWith("/")) {
1974
- return target.slice(1);
1975
- }
1976
- const segments = `${baseDir}/${target}`.split("/");
1977
- const resolved = [];
1978
- for (const seg of segments) {
1979
- if (seg === "..") {
1980
- resolved.pop();
1981
- } else if (seg !== "." && seg !== "") {
1982
- resolved.push(seg);
1983
- }
1984
- }
1985
- return resolved.join("/");
1986
- };
1987
- const DOCX_HIGHLIGHT_KEYWORD_MAP = /* @__PURE__ */ new Map([
1988
- ["yellow", "FFFF00"],
1989
- ["green", "00FF00"],
1990
- ["blue", "0000FF"],
1991
- ["cyan", "00FFFF"],
1992
- ["magenta", "FF00FF"],
1993
- ["red", "FF0000"],
1994
- ["darkYellow", "808000"],
1995
- ["darkGreen", "008000"],
1996
- ["darkBlue", "000080"],
1997
- ["darkCyan", "008080"],
1998
- ["darkMagenta", "800080"],
1999
- ["darkGray", "808080"],
2000
- ["darkRed", "800000"],
2001
- ["lightGray", "C0C0C0"],
2002
- ["black", "000000"],
2003
- ["white", "FFFFFF"]
2004
- ]);
2005
- const normalizeHexColor = (hex) => {
2006
- if (!hex) return null;
2007
- let value = hex.replace("#", "").trim();
2008
- if (!value) return null;
2009
- value = value.toUpperCase();
2010
- if (value.length === 3)
2011
- value = value.split("").map((c) => c + c).join("");
2012
- if (value.length === 8) value = value.slice(0, 6);
2013
- return value;
2014
- };
2015
- const getHexColorFromDocxSystem = (docxColor) => {
2016
- const hex = DOCX_HIGHLIGHT_KEYWORD_MAP.get(docxColor);
2017
- return hex ? `#${hex}` : null;
2018
- };
2019
- const getDocxHighlightKeywordFromHex = (hexColor) => {
2020
- if (!hexColor) return null;
2021
- if (DOCX_HIGHLIGHT_KEYWORD_MAP.has(hexColor)) return hexColor;
2022
- const normalized = normalizeHexColor(hexColor);
2023
- if (!normalized) return null;
2024
- for (const [keyword, hex] of DOCX_HIGHLIGHT_KEYWORD_MAP.entries()) {
2025
- if (hex === normalized) return keyword;
2026
- }
2027
- return null;
2028
- };
2029
- function isValidHexColor(color) {
2030
- if (!color || typeof color !== "string") return false;
2031
- switch (color.length) {
2032
- case 3:
2033
- return /^[0-9A-F]{3}$/i.test(color);
2034
- case 6:
2035
- return /^[0-9A-F]{6}$/i.test(color);
2036
- case 8:
2037
- return /^[0-9A-F]{8}$/i.test(color);
2038
- default:
2039
- return false;
2040
- }
2041
- }
2042
- const componentToHex = (val) => {
2043
- const a = Number(val).toString(16);
2044
- return a.length === 1 ? "0" + a : a;
2045
- };
2046
- const rgbToHex = (rgb) => {
2047
- return "#" + rgb.match(/\d+/g).map(componentToHex).join("");
2048
- };
2049
- const DEFAULT_SHADING_FOREGROUND_COLOR = "#000000";
2050
- const hexToRgb = (hex) => {
2051
- const normalized = normalizeHexColor(hex);
2052
- if (!normalized) return null;
2053
- return {
2054
- r: Number.parseInt(normalized.slice(0, 2), 16),
2055
- g: Number.parseInt(normalized.slice(2, 4), 16),
2056
- b: Number.parseInt(normalized.slice(4, 6), 16)
2057
- };
2058
- };
2059
- const clamp01 = (value) => {
2060
- if (!Number.isFinite(value)) return 0;
2061
- return Math.min(1, Math.max(0, value));
2062
- };
2063
- const blendHexColors = (backgroundHex, foregroundHex, foregroundRatio) => {
2064
- const background = hexToRgb(backgroundHex);
2065
- const foreground = hexToRgb(foregroundHex);
2066
- if (!background || !foreground) return null;
2067
- const ratio = clamp01(foregroundRatio);
2068
- const r = Math.round(background.r * (1 - ratio) + foreground.r * ratio);
2069
- const g = Math.round(background.g * (1 - ratio) + foreground.g * ratio);
2070
- const b = Math.round(background.b * (1 - ratio) + foreground.b * ratio);
2071
- const toByte = (n) => n.toString(16).padStart(2, "0").toUpperCase();
2072
- return `${toByte(r)}${toByte(g)}${toByte(b)}`;
2073
- };
2074
- const resolveShadingFillColor = (shading) => {
2075
- if (!shading || typeof shading !== "object") return null;
2076
- const fill = normalizeHexColor(shading.fill);
2077
- if (!fill) return null;
2078
- const val = typeof shading.val === "string" ? shading.val.trim().toLowerCase() : "";
2079
- const pctMatch = val.match(/^pct(\d{1,3})$/);
2080
- if (!pctMatch) return fill;
2081
- const pct = Number.parseInt(pctMatch[1], 10);
2082
- if (!Number.isFinite(pct) || pct < 0 || pct > 100) return fill;
2083
- const foreground = normalizeHexColor(shading.color) ?? DEFAULT_SHADING_FOREGROUND_COLOR;
2084
- return blendHexColors(fill, foreground, pct / 100) ?? fill;
2085
- };
2086
- const deobfuscateFont = (arrayBuffer, guidHex) => {
2087
- const dta = new Uint8Array(arrayBuffer);
2088
- const guidStr = guidHex.replace(/[-{}]/g, "");
2089
- if (guidStr.length !== 32) {
2090
- console.error("Invalid GUID");
2091
- return;
2092
- }
2093
- const guidBytes = new Uint8Array(16);
2094
- for (let i = 0, j = 0; i < 32; i += 2, j++) {
2095
- const hexByte = guidStr[i] + guidStr[i + 1];
2096
- guidBytes[j] = parseInt(hexByte, 16);
2097
- }
2098
- for (let i = 0; i < 32; i++) {
2099
- const gi = 15 - i % 16;
2100
- dta[i] ^= guidBytes[gi];
2101
- }
2102
- return dta.buffer;
2103
- };
2104
- function convertSizeToCSS(value, type) {
2105
- if (typeof value === "string" && value.endsWith("%")) {
2106
- type = "pct";
2107
- }
2108
- if (value === null || value === void 0) {
2109
- value = 0;
2110
- }
2111
- switch (type) {
2112
- case "dxa":
2113
- case null:
2114
- case void 0:
2115
- return `${twipsToPixels(value)}px`;
2116
- case "nil":
2117
- return "0";
2118
- case "auto":
2119
- return null;
2120
- case "pct":
2121
- let percent;
2122
- if (typeof value === "number") {
2123
- percent = value * 0.02;
1917
+ if (hasFile("word/commentsExtended.xml")) {
1918
+ const commentsExtendedDef = `<Override PartName="/word/commentsExtended.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml" />`;
1919
+ if (!hasCommentsExtended) typesString += commentsExtendedDef;
1920
+ }
1921
+ if (hasFile("word/commentsIds.xml")) {
1922
+ const commentsIdsDef = `<Override PartName="/word/commentsIds.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml" />`;
1923
+ if (!hasCommentsIds) typesString += commentsIdsDef;
1924
+ }
1925
+ if (hasFile("word/commentsExtensible.xml")) {
1926
+ const commentsExtendedDef = `<Override PartName="/word/commentsExtensible.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml" />`;
1927
+ if (!hasCommentsExtensible) typesString += commentsExtendedDef;
1928
+ }
1929
+ const hasFootnotes = types.elements?.some(
1930
+ (el) => el.name === "Override" && el.attributes.PartName === "/word/footnotes.xml"
1931
+ );
1932
+ if (hasFile("word/footnotes.xml")) {
1933
+ const footnotesDef = `<Override PartName="/word/footnotes.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml" />`;
1934
+ if (!hasFootnotes) typesString += footnotesDef;
1935
+ }
1936
+ const partNames = new Set(additionalPartNames);
1937
+ if (docx?.files) {
1938
+ if (fromJson && Array.isArray(docx.files)) {
1939
+ docx.files.forEach((file) => partNames.add(file.name));
2124
1940
  } else {
2125
- if (value.endsWith("%")) {
2126
- percent = parseFloat(value.slice(0, -1));
1941
+ Object.keys(docx.files).forEach((key) => partNames.add(key));
1942
+ }
1943
+ }
1944
+ partNames.forEach((name) => {
1945
+ if (name.includes(".rels")) return;
1946
+ if (!name.includes("header") && !name.includes("footer")) return;
1947
+ const hasExtensible = types.elements?.some(
1948
+ (el) => el.name === "Override" && el.attributes.PartName === `/${name}`
1949
+ );
1950
+ const type = name.includes("header") ? "header" : "footer";
1951
+ const extendedDef = `<Override PartName="/${name}" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.${type}+xml"/>`;
1952
+ if (!hasExtensible) {
1953
+ typesString += extendedDef;
1954
+ }
1955
+ });
1956
+ const beginningString = '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">';
1957
+ let updatedContentTypesXml = contentTypesXml.replace(beginningString, `${beginningString}${typesString}`);
1958
+ let relationshipsXml = updatedDocs["word/_rels/document.xml.rels"];
1959
+ if (!relationshipsXml) {
1960
+ if (fromJson) {
1961
+ if (Array.isArray(docx.files)) {
1962
+ relationshipsXml = docx.files.find((file) => file.name === "word/_rels/document.xml.rels")?.content;
2127
1963
  } else {
2128
- percent = parseFloat(value) * 0.02;
1964
+ relationshipsXml = docx.files?.["word/_rels/document.xml.rels"];
2129
1965
  }
1966
+ } else {
1967
+ relationshipsXml = await docx.file("word/_rels/document.xml.rels")?.async("string");
1968
+ }
1969
+ }
1970
+ if (relationshipsXml) {
1971
+ try {
1972
+ const relJson = helpers.libExports.xml2js(relationshipsXml, { compact: false });
1973
+ const relationships = relJson.elements?.find((el) => el.name === "Relationships");
1974
+ relationships?.elements?.forEach((rel) => {
1975
+ const type = rel.attributes?.Type;
1976
+ const target = rel.attributes?.Target;
1977
+ if (!type || !target) return;
1978
+ const isHeader = type.includes("/header");
1979
+ const isFooter = type.includes("/footer");
1980
+ if (!isHeader && !isFooter) return;
1981
+ let sanitizedTarget = target.replace(/^\.\//, "");
1982
+ if (sanitizedTarget.startsWith("../")) sanitizedTarget = sanitizedTarget.slice(3);
1983
+ if (sanitizedTarget.startsWith("/")) sanitizedTarget = sanitizedTarget.slice(1);
1984
+ const partName = sanitizedTarget.startsWith("word/") ? sanitizedTarget : `word/${sanitizedTarget}`;
1985
+ partNames.add(partName);
1986
+ });
1987
+ } catch (error) {
1988
+ console.warn("Failed to parse document relationships while updating content types", error);
2130
1989
  }
2131
- return `${percent}%`;
2132
- default:
2133
- return null;
1990
+ }
1991
+ partNames.forEach((name) => {
1992
+ if (name.includes(".rels")) return;
1993
+ if (!name.includes("header") && !name.includes("footer")) return;
1994
+ if (updatedContentTypesXml.includes(`PartName="/${name}"`)) return;
1995
+ const type = name.includes("header") ? "header" : "footer";
1996
+ const extendedDef = `<Override PartName="/${name}" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.${type}+xml"/>`;
1997
+ updatedContentTypesXml = updatedContentTypesXml.replace("</Types>", `${extendedDef}</Types>`);
1998
+ });
1999
+ if (fromJson) return updatedContentTypesXml;
2000
+ docx.file(contentTypesPath, updatedContentTypesXml);
2001
+ }
2002
+ async unzip(file) {
2003
+ const zip = await this.zip.loadAsync(file);
2004
+ return zip;
2005
+ }
2006
+ async updateZip({ docx, updatedDocs, originalDocxFile, media, fonts, isHeadless, compression = "DEFLATE" }) {
2007
+ let zip;
2008
+ if (originalDocxFile) {
2009
+ zip = await this.exportFromOriginalFile(originalDocxFile, updatedDocs, media);
2010
+ } else {
2011
+ zip = await this.exportFromCollaborativeDocx(docx, updatedDocs, media, fonts);
2012
+ }
2013
+ const exportType = isHeadless ? "nodebuffer" : "blob";
2014
+ return await zip.generateAsync({
2015
+ type: exportType,
2016
+ compression,
2017
+ compressionOptions: compression === "DEFLATE" ? { level: 6 } : void 0
2018
+ });
2019
+ }
2020
+ /**
2021
+ * Export the Editor content to a docx file, updating changed docs
2022
+ * @param {Object} docx An object containing the unzipped docx files (keys are relative file names)
2023
+ * @param {Object} updatedDocs An object containing the updated docs (keys are relative file names)
2024
+ * @returns {Promise<JSZip>} The unzipped but updated docx file ready for zipping
2025
+ */
2026
+ async exportFromCollaborativeDocx(docx, updatedDocs, media, fonts) {
2027
+ const zip = new jszip_min.JSZip();
2028
+ for (const file of docx) {
2029
+ const content = file.content;
2030
+ zip.file(file.name, content);
2031
+ }
2032
+ Object.keys(updatedDocs).forEach((key) => {
2033
+ const content = updatedDocs[key];
2034
+ zip.file(key, content);
2035
+ });
2036
+ Object.keys(media).forEach((path) => {
2037
+ const value = media[path];
2038
+ const binaryData = typeof value === "string" ? helpers.base64ToUint8Array(value) : value;
2039
+ zip.file(path, binaryData);
2040
+ });
2041
+ for (const [fontName, fontUintArray] of Object.entries(fonts)) {
2042
+ zip.file(fontName, fontUintArray);
2043
+ }
2044
+ await this.updateContentTypes(zip, media, false, updatedDocs);
2045
+ return zip;
2046
+ }
2047
+ /**
2048
+ * Export the Editor content to a docx file, updating changed docs
2049
+ * Requires the original docx file
2050
+ * @param {File} originalDocxFile The original docx file
2051
+ * @param {Object} updatedDocs An object containing the updated docs (keys are relative file names)
2052
+ * @returns {Promise<JSZip>} The unzipped but updated docx file ready for zipping
2053
+ */
2054
+ async exportFromOriginalFile(originalDocxFile, updatedDocs, media) {
2055
+ const unzippedOriginalDocx = await this.unzip(originalDocxFile);
2056
+ const filePromises = [];
2057
+ unzippedOriginalDocx.forEach((relativePath, zipEntry) => {
2058
+ const promise = zipEntry.async("string").then((content) => {
2059
+ unzippedOriginalDocx.file(zipEntry.name, content);
2060
+ });
2061
+ filePromises.push(promise);
2062
+ });
2063
+ await Promise.all(filePromises);
2064
+ Object.keys(updatedDocs).forEach((key) => {
2065
+ unzippedOriginalDocx.file(key, updatedDocs[key]);
2066
+ });
2067
+ Object.keys(media).forEach((path) => {
2068
+ unzippedOriginalDocx.file(path, media[path]);
2069
+ });
2070
+ await this.updateContentTypes(unzippedOriginalDocx, media, false, updatedDocs);
2071
+ return unzippedOriginalDocx;
2134
2072
  }
2135
2073
  }
2136
2074
  exports.Buffer = Buffer;
2137
- exports.convertSizeToCSS = convertSizeToCSS;
2138
- exports.degreesToRot = degreesToRot;
2139
- exports.deobfuscateFont = deobfuscateFont;
2140
- exports.eighthPointsToPixels = eighthPointsToPixels;
2141
- exports.emuToPixels = emuToPixels;
2142
- exports.getArrayBufferFromUrl = getArrayBufferFromUrl;
2143
- exports.getContentTypesFromXml = getContentTypesFromXml;
2144
- exports.getDocxHighlightKeywordFromHex = getDocxHighlightKeywordFromHex;
2145
- exports.getHexColorFromDocxSystem = getHexColorFromDocxSystem;
2146
- exports.halfPointToPoints = halfPointToPoints;
2147
- exports.inchesToPixels = inchesToPixels;
2148
- exports.inchesToTwips = inchesToTwips;
2149
- exports.isValidHexColor = isValidHexColor;
2150
- exports.libExports = libExports;
2151
- exports.linesToTwips = linesToTwips;
2152
- exports.normalizeHexColor = normalizeHexColor;
2153
- exports.objToPolygon = objToPolygon;
2154
- exports.pixelsToEightPoints = pixelsToEightPoints;
2155
- exports.pixelsToEmu = pixelsToEmu;
2156
- exports.pixelsToTwips = pixelsToTwips;
2157
- exports.pointsToTwips = pointsToTwips;
2158
- exports.polygonToObj = polygonToObj;
2159
- exports.ptToTwips = ptToTwips;
2160
- exports.resolveOpcTargetPath = resolveOpcTargetPath;
2161
- exports.resolveShadingFillColor = resolveShadingFillColor;
2162
- exports.rgbToHex = rgbToHex;
2163
- exports.rotToDegrees = rotToDegrees;
2164
- exports.twipsToInches = twipsToInches;
2165
- exports.twipsToLines = twipsToLines;
2166
- exports.twipsToPixels = twipsToPixels;
2167
- exports.twipsToPt = twipsToPt;
2075
+ exports.DocxZipper = DocxZipper;