@open-file-viewer/core 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/index.cjs +530 -194
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +530 -194
- package/dist/index.js.map +1 -1
- package/dist/style.css +278 -6
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -606,6 +606,48 @@ function revokeObjectUrl(url, isExternal) {
|
|
|
606
606
|
}
|
|
607
607
|
}
|
|
608
608
|
|
|
609
|
+
// src/messages.ts
|
|
610
|
+
var defaultMessages = {
|
|
611
|
+
"zh-CN": {
|
|
612
|
+
loading: "\u6B63\u5728\u52A0\u8F7D\u9884\u89C8...",
|
|
613
|
+
unsupportedTitle: "\u5F53\u524D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u5728\u7EBF\u9884\u89C8",
|
|
614
|
+
downloadTitle: "\u5F53\u524D\u6587\u4EF6\u53EF\u4E0B\u8F7D\u540E\u67E5\u770B",
|
|
615
|
+
downloadFile: "\u4E0B\u8F7D\u6587\u4EF6",
|
|
616
|
+
file: "\u6587\u4EF6",
|
|
617
|
+
unnamedFile: "\u672A\u547D\u540D\u6587\u4EF6",
|
|
618
|
+
format: "\u683C\u5F0F",
|
|
619
|
+
unknown: "\u672A\u77E5",
|
|
620
|
+
mime: "MIME",
|
|
621
|
+
undeclared: "\u672A\u58F0\u660E",
|
|
622
|
+
size: "\u5927\u5C0F",
|
|
623
|
+
source: "\u6765\u6E90",
|
|
624
|
+
remoteUrl: "\u8FDC\u7A0B URL",
|
|
625
|
+
localFile: "\u672C\u5730/\u5185\u5B58\u6587\u4EF6"
|
|
626
|
+
},
|
|
627
|
+
"en-US": {
|
|
628
|
+
loading: "Loading preview...",
|
|
629
|
+
unsupportedTitle: "Preview is not available for this file",
|
|
630
|
+
downloadTitle: "This file can be downloaded and opened locally",
|
|
631
|
+
downloadFile: "Download file",
|
|
632
|
+
file: "File",
|
|
633
|
+
unnamedFile: "Untitled file",
|
|
634
|
+
format: "Format",
|
|
635
|
+
unknown: "Unknown",
|
|
636
|
+
mime: "MIME",
|
|
637
|
+
undeclared: "Not declared",
|
|
638
|
+
size: "Size",
|
|
639
|
+
source: "Source",
|
|
640
|
+
remoteUrl: "Remote URL",
|
|
641
|
+
localFile: "Local or in-memory file"
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
function resolveMessages(options) {
|
|
645
|
+
return {
|
|
646
|
+
...defaultMessages[options.locale || "zh-CN"],
|
|
647
|
+
...options.messages
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
609
651
|
// src/plugins/fallback.ts
|
|
610
652
|
function fallbackPlugin() {
|
|
611
653
|
return {
|
|
@@ -623,12 +665,12 @@ function fallbackPlugin() {
|
|
|
623
665
|
const panel = document.createElement("div");
|
|
624
666
|
panel.className = "ofv-fallback";
|
|
625
667
|
const title = document.createElement("strong");
|
|
626
|
-
title.textContent = ctx.options.fallback === "download" ?
|
|
627
|
-
const meta = createFallbackMeta(ctx.file);
|
|
668
|
+
title.textContent = ctx.options.fallback === "download" ? ctx.options.messages.downloadTitle : ctx.options.messages.unsupportedTitle;
|
|
669
|
+
const meta = createFallbackMeta(ctx.file, ctx.options.messages);
|
|
628
670
|
const download = document.createElement("a");
|
|
629
671
|
download.href = url;
|
|
630
672
|
download.download = ctx.file.name;
|
|
631
|
-
download.textContent =
|
|
673
|
+
download.textContent = ctx.options.messages.downloadFile;
|
|
632
674
|
panel.append(title, meta, download);
|
|
633
675
|
ctx.viewport.classList.add("ofv-center");
|
|
634
676
|
ctx.viewport.append(panel);
|
|
@@ -644,14 +686,14 @@ function fallbackPlugin() {
|
|
|
644
686
|
}
|
|
645
687
|
};
|
|
646
688
|
}
|
|
647
|
-
function createFallbackMeta(file) {
|
|
689
|
+
function createFallbackMeta(file, messages) {
|
|
648
690
|
const meta = document.createElement("dl");
|
|
649
691
|
meta.className = "ofv-fallback-meta";
|
|
650
|
-
appendFallbackMeta(meta,
|
|
651
|
-
appendFallbackMeta(meta,
|
|
652
|
-
appendFallbackMeta(meta,
|
|
653
|
-
appendFallbackMeta(meta,
|
|
654
|
-
appendFallbackMeta(meta,
|
|
692
|
+
appendFallbackMeta(meta, messages.file, file.name || messages.unnamedFile);
|
|
693
|
+
appendFallbackMeta(meta, messages.format, file.extension ? `.${file.extension}` : messages.unknown);
|
|
694
|
+
appendFallbackMeta(meta, messages.mime, file.mimeType || messages.undeclared);
|
|
695
|
+
appendFallbackMeta(meta, messages.size, file.size === void 0 ? messages.unknown : formatFallbackBytes(file.size));
|
|
696
|
+
appendFallbackMeta(meta, messages.source, file.url ? messages.remoteUrl : messages.localFile);
|
|
655
697
|
return meta;
|
|
656
698
|
}
|
|
657
699
|
function appendFallbackMeta(parent, label, value) {
|
|
@@ -709,15 +751,17 @@ function createViewer(options) {
|
|
|
709
751
|
host.append(status, viewport);
|
|
710
752
|
container.replaceChildren(host);
|
|
711
753
|
const normalizedOptions = {
|
|
754
|
+
...options,
|
|
712
755
|
fit: options.fit || "contain",
|
|
713
756
|
fallback: options.fallback || "inline",
|
|
714
|
-
|
|
757
|
+
zoom: normalizeInitialZoom(options.zoom),
|
|
758
|
+
messages: resolveMessages(options)
|
|
715
759
|
};
|
|
716
760
|
let destroyed = false;
|
|
717
761
|
let renderToken = 0;
|
|
718
762
|
const setLoading = (loading) => {
|
|
719
763
|
status.hidden = !loading;
|
|
720
|
-
status.textContent = loading ?
|
|
764
|
+
status.textContent = loading ? normalizedOptions.messages.loading : "";
|
|
721
765
|
};
|
|
722
766
|
const setError = (error) => {
|
|
723
767
|
status.hidden = false;
|
|
@@ -873,6 +917,9 @@ function clampIndex(index, length) {
|
|
|
873
917
|
}
|
|
874
918
|
return Math.min(Math.max(index, 0), length - 1);
|
|
875
919
|
}
|
|
920
|
+
function normalizeInitialZoom(zoom) {
|
|
921
|
+
return typeof zoom === "number" && Number.isFinite(zoom) && zoom > 0 ? zoom : 1;
|
|
922
|
+
}
|
|
876
923
|
function applyTheme(container, theme) {
|
|
877
924
|
const media = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
878
925
|
const classes = ["ofv-theme-light", "ofv-theme-dark"];
|
|
@@ -1749,6 +1796,98 @@ async function findPlugin(plugins, file) {
|
|
|
1749
1796
|
return fallbackPlugin();
|
|
1750
1797
|
}
|
|
1751
1798
|
|
|
1799
|
+
// src/plugins/utils.ts
|
|
1800
|
+
async function readArrayBuffer(file) {
|
|
1801
|
+
if (file.source instanceof ArrayBuffer) {
|
|
1802
|
+
return file.source;
|
|
1803
|
+
}
|
|
1804
|
+
if (file.blob) {
|
|
1805
|
+
return file.blob.arrayBuffer();
|
|
1806
|
+
}
|
|
1807
|
+
if (typeof file.source === "string") {
|
|
1808
|
+
const response = await fetch(file.source);
|
|
1809
|
+
if (!response.ok) {
|
|
1810
|
+
throw new Error(`Failed to fetch file: ${response.status}`);
|
|
1811
|
+
}
|
|
1812
|
+
return response.arrayBuffer();
|
|
1813
|
+
}
|
|
1814
|
+
throw new Error("Unsupported file source.");
|
|
1815
|
+
}
|
|
1816
|
+
async function readTextFile(file) {
|
|
1817
|
+
const decode = (buffer) => decodeTextBuffer(buffer);
|
|
1818
|
+
if (typeof file.source === "string") {
|
|
1819
|
+
const response = await fetch(file.source);
|
|
1820
|
+
if (!response.ok) {
|
|
1821
|
+
throw new Error(`Failed to fetch text file: ${response.status}`);
|
|
1822
|
+
}
|
|
1823
|
+
return decode(await response.arrayBuffer());
|
|
1824
|
+
}
|
|
1825
|
+
if (file.blob) {
|
|
1826
|
+
return decode(await file.blob.arrayBuffer());
|
|
1827
|
+
}
|
|
1828
|
+
if (file.source instanceof ArrayBuffer) {
|
|
1829
|
+
return decode(file.source);
|
|
1830
|
+
}
|
|
1831
|
+
return String(file.source);
|
|
1832
|
+
}
|
|
1833
|
+
function createPanel(className = "") {
|
|
1834
|
+
const panel = document.createElement("div");
|
|
1835
|
+
panel.className = `ofv-panel ${className}`.trim();
|
|
1836
|
+
return panel;
|
|
1837
|
+
}
|
|
1838
|
+
function getInitialZoom(ctx, min = 0.1, max = 8) {
|
|
1839
|
+
return Math.min(max, Math.max(min, ctx.options.zoom));
|
|
1840
|
+
}
|
|
1841
|
+
function createSection(title) {
|
|
1842
|
+
const section = document.createElement("section");
|
|
1843
|
+
section.className = "ofv-section";
|
|
1844
|
+
const heading = document.createElement("h3");
|
|
1845
|
+
heading.textContent = title;
|
|
1846
|
+
section.append(heading);
|
|
1847
|
+
return section;
|
|
1848
|
+
}
|
|
1849
|
+
function appendMeta(parent, label, value) {
|
|
1850
|
+
const row = document.createElement("div");
|
|
1851
|
+
row.className = "ofv-meta-row";
|
|
1852
|
+
const key = document.createElement("span");
|
|
1853
|
+
key.textContent = label;
|
|
1854
|
+
const content = document.createElement("strong");
|
|
1855
|
+
content.textContent = String(value);
|
|
1856
|
+
row.append(key, content);
|
|
1857
|
+
parent.append(row);
|
|
1858
|
+
}
|
|
1859
|
+
function decodeTextBuffer(buffer) {
|
|
1860
|
+
return decodeTextBytes(new Uint8Array(buffer));
|
|
1861
|
+
}
|
|
1862
|
+
function decodeTextBytes(bytes) {
|
|
1863
|
+
if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
|
|
1864
|
+
return new TextDecoder("utf-8").decode(bytes.subarray(3));
|
|
1865
|
+
}
|
|
1866
|
+
if (bytes.length >= 2) {
|
|
1867
|
+
if (bytes[0] === 255 && bytes[1] === 254) {
|
|
1868
|
+
return new TextDecoder("utf-16le").decode(bytes.subarray(2));
|
|
1869
|
+
}
|
|
1870
|
+
if (bytes[0] === 254 && bytes[1] === 255) {
|
|
1871
|
+
return new TextDecoder("utf-16be").decode(bytes.subarray(2));
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
try {
|
|
1875
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
1876
|
+
} catch {
|
|
1877
|
+
return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
function decodeWithFallback(bytes, encoding) {
|
|
1881
|
+
try {
|
|
1882
|
+
return new TextDecoder(encoding).decode(bytes);
|
|
1883
|
+
} catch {
|
|
1884
|
+
return void 0;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
function resolveFormat(file, mimeMap) {
|
|
1888
|
+
return file.extension || mimeMap[file.mimeType] || "";
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1752
1891
|
// src/plugins/image.ts
|
|
1753
1892
|
var imageExtensions = /* @__PURE__ */ new Set([
|
|
1754
1893
|
"jpg",
|
|
@@ -1791,12 +1930,12 @@ function imagePlugin() {
|
|
|
1791
1930
|
let url = "";
|
|
1792
1931
|
let convertedBlob = null;
|
|
1793
1932
|
let isExternal = Boolean(ctx.file.url);
|
|
1794
|
-
let
|
|
1933
|
+
let tiffSource = null;
|
|
1795
1934
|
if (isTiff) {
|
|
1796
1935
|
ctx.setLoading(true);
|
|
1797
1936
|
try {
|
|
1798
1937
|
const bytes = await sourceBytesPromise;
|
|
1799
|
-
|
|
1938
|
+
tiffSource = await createTiffPreview(bytes, ctx.file.name);
|
|
1800
1939
|
} catch (err) {
|
|
1801
1940
|
console.error("TIFF image conversion failed:", err);
|
|
1802
1941
|
url = createObjectUrl(ctx.file);
|
|
@@ -1856,13 +1995,16 @@ function imagePlugin() {
|
|
|
1856
1995
|
if (url) {
|
|
1857
1996
|
image.src = url;
|
|
1858
1997
|
}
|
|
1859
|
-
const visual =
|
|
1860
|
-
if (
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1998
|
+
const visual = tiffSource || image;
|
|
1999
|
+
if (tiffSource) {
|
|
2000
|
+
tiffSource.classList.add("ofv-image-content");
|
|
2001
|
+
if (tiffSource.classList.contains("ofv-tiff-pages")) {
|
|
2002
|
+
stage.classList.add("ofv-image-stage-pages");
|
|
2003
|
+
}
|
|
1864
2004
|
}
|
|
1865
|
-
|
|
2005
|
+
const visualBox = document.createElement("div");
|
|
2006
|
+
visualBox.className = "ofv-image-scrollbox";
|
|
2007
|
+
let scale = getInitialZoom(ctx);
|
|
1866
2008
|
let rotation = 0;
|
|
1867
2009
|
let offsetX = 0;
|
|
1868
2010
|
let offsetY = 0;
|
|
@@ -1875,7 +2017,23 @@ function imagePlugin() {
|
|
|
1875
2017
|
let previewAvailable = true;
|
|
1876
2018
|
const zoomLabel = document.createElement("span");
|
|
1877
2019
|
zoomLabel.className = "ofv-image-zoom";
|
|
2020
|
+
const updateScrollBox = () => {
|
|
2021
|
+
if (visual.classList.contains("ofv-tiff-pages")) {
|
|
2022
|
+
visualBox.style.removeProperty("width");
|
|
2023
|
+
visualBox.style.removeProperty("height");
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
const baseWidth = visual.offsetWidth || visual.getBoundingClientRect().width || visual.naturalWidth || 0;
|
|
2027
|
+
const baseHeight = visual.offsetHeight || visual.getBoundingClientRect().height || visual.naturalHeight || 0;
|
|
2028
|
+
if (baseWidth > 0) {
|
|
2029
|
+
visualBox.style.width = `${Math.ceil(baseWidth * scale)}px`;
|
|
2030
|
+
}
|
|
2031
|
+
if (baseHeight > 0) {
|
|
2032
|
+
visualBox.style.height = `${Math.ceil(baseHeight * scale)}px`;
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
1878
2035
|
const updateTransform = () => {
|
|
2036
|
+
updateScrollBox();
|
|
1879
2037
|
visual.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale}) rotate(${rotation}deg)`;
|
|
1880
2038
|
zoomLabel.textContent = `${Math.round(scale * 100)}%`;
|
|
1881
2039
|
ctx.toolbar?.setZoom(previewAvailable ? scale : void 0);
|
|
@@ -1995,11 +2153,13 @@ function imagePlugin() {
|
|
|
1995
2153
|
stage.addEventListener("lostpointercapture", onLostPointerCapture);
|
|
1996
2154
|
stage.addEventListener("pointerleave", onPointerLeave);
|
|
1997
2155
|
stage.addEventListener("wheel", onWheel, { passive: false });
|
|
1998
|
-
if (!
|
|
2156
|
+
if (!tiffSource) {
|
|
1999
2157
|
image.addEventListener("error", showImageFallback);
|
|
2158
|
+
image.addEventListener("load", updateScrollBox);
|
|
2000
2159
|
}
|
|
2001
2160
|
window.addEventListener("blur", onWindowBlur);
|
|
2002
|
-
|
|
2161
|
+
visualBox.append(visual);
|
|
2162
|
+
stage.append(visualBox);
|
|
2003
2163
|
wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
|
|
2004
2164
|
ctx.viewport.append(wrapper);
|
|
2005
2165
|
updateTransform();
|
|
@@ -2037,7 +2197,12 @@ function imagePlugin() {
|
|
|
2037
2197
|
},
|
|
2038
2198
|
resize(size) {
|
|
2039
2199
|
visual.style.maxWidth = `${size.width}px`;
|
|
2040
|
-
visual.
|
|
2200
|
+
if (visual.classList.contains("ofv-tiff-pages")) {
|
|
2201
|
+
visual.style.removeProperty("max-height");
|
|
2202
|
+
} else {
|
|
2203
|
+
visual.style.maxHeight = `${Math.max(0, size.height - controls.offsetHeight)}px`;
|
|
2204
|
+
}
|
|
2205
|
+
updateScrollBox();
|
|
2041
2206
|
},
|
|
2042
2207
|
destroy() {
|
|
2043
2208
|
ctx.toolbar?.setZoom(void 0);
|
|
@@ -2052,6 +2217,7 @@ function imagePlugin() {
|
|
|
2052
2217
|
stage.removeEventListener("pointerleave", onPointerLeave);
|
|
2053
2218
|
stage.removeEventListener("wheel", onWheel);
|
|
2054
2219
|
image.removeEventListener("error", showImageFallback);
|
|
2220
|
+
image.removeEventListener("load", updateScrollBox);
|
|
2055
2221
|
window.removeEventListener("blur", onWindowBlur);
|
|
2056
2222
|
finishDrag();
|
|
2057
2223
|
wrapper.remove();
|
|
@@ -2065,33 +2231,64 @@ function imagePlugin() {
|
|
|
2065
2231
|
}
|
|
2066
2232
|
};
|
|
2067
2233
|
}
|
|
2068
|
-
async function
|
|
2234
|
+
async function createTiffPreview(bytes, fileName) {
|
|
2069
2235
|
if (bytes.byteLength === 0) {
|
|
2070
2236
|
throw new Error("\u65E0\u6CD5\u8BFB\u53D6 TIFF \u6587\u4EF6\u5185\u5BB9\u3002");
|
|
2071
2237
|
}
|
|
2072
2238
|
const UTIF = await import("utif");
|
|
2073
2239
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2074
2240
|
const ifds = UTIF.decode(buffer);
|
|
2075
|
-
const
|
|
2076
|
-
if (
|
|
2241
|
+
const imageIfds = ifds.filter((item) => hasTiffDimensions(item));
|
|
2242
|
+
if (imageIfds.length === 0) {
|
|
2077
2243
|
throw new Error("TIFF \u6587\u4EF6\u6CA1\u6709\u53EF\u89E3\u7801\u7684\u56FE\u50CF\u76EE\u5F55\u3002");
|
|
2078
2244
|
}
|
|
2245
|
+
const pages = imageIfds.map((ifd, index) => createTiffPage(UTIF, buffer, ifd, index, imageIfds.length, fileName));
|
|
2246
|
+
if (pages.length === 1) {
|
|
2247
|
+
return pages[0].canvas;
|
|
2248
|
+
}
|
|
2249
|
+
const container = document.createElement("div");
|
|
2250
|
+
container.className = "ofv-tiff-pages";
|
|
2251
|
+
container.setAttribute("role", "group");
|
|
2252
|
+
container.setAttribute("aria-label", `${fileName}\uFF0C\u5171 ${pages.length} \u9875`);
|
|
2253
|
+
for (const page of pages) {
|
|
2254
|
+
const figure = document.createElement("figure");
|
|
2255
|
+
figure.className = "ofv-tiff-page";
|
|
2256
|
+
const caption = document.createElement("figcaption");
|
|
2257
|
+
caption.textContent = `\u7B2C ${page.index + 1} / ${pages.length} \u9875 \xB7 ${page.width} x ${page.height}px`;
|
|
2258
|
+
figure.append(page.canvas, caption);
|
|
2259
|
+
container.append(figure);
|
|
2260
|
+
}
|
|
2261
|
+
return container;
|
|
2262
|
+
}
|
|
2263
|
+
function createTiffPage(UTIF, buffer, ifd, index, total, fileName) {
|
|
2079
2264
|
UTIF.decodeImage(buffer, ifd);
|
|
2080
2265
|
const rgba = UTIF.toRGBA8(ifd);
|
|
2081
|
-
const width =
|
|
2082
|
-
const height = Number(ifd.height || ifd.t257 || 0);
|
|
2266
|
+
const { width, height } = getTiffDimensions(ifd);
|
|
2083
2267
|
if (!width || !height || rgba.length < width * height * 4) {
|
|
2084
2268
|
throw new Error("TIFF \u56FE\u50CF\u50CF\u7D20\u6570\u636E\u4E0D\u5B8C\u6574\u3002");
|
|
2085
2269
|
}
|
|
2086
2270
|
const canvas = document.createElement("canvas");
|
|
2271
|
+
canvas.className = "ofv-tiff-canvas";
|
|
2087
2272
|
canvas.width = width;
|
|
2088
2273
|
canvas.height = height;
|
|
2274
|
+
canvas.setAttribute("role", "img");
|
|
2275
|
+
canvas.setAttribute("aria-label", total > 1 ? `${fileName} \u7B2C ${index + 1} \u9875` : fileName);
|
|
2089
2276
|
const context = canvas.getContext("2d");
|
|
2090
2277
|
if (!context) {
|
|
2091
2278
|
throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Canvas 2D\uFF0C\u65E0\u6CD5\u5C55\u793A TIFF\u3002");
|
|
2092
2279
|
}
|
|
2093
2280
|
context.putImageData(new ImageData(new Uint8ClampedArray(rgba), width, height), 0, 0);
|
|
2094
|
-
return canvas;
|
|
2281
|
+
return { canvas, width, height, index };
|
|
2282
|
+
}
|
|
2283
|
+
function hasTiffDimensions(ifd) {
|
|
2284
|
+
const { width, height } = getTiffDimensions(ifd);
|
|
2285
|
+
return width > 0 && height > 0;
|
|
2286
|
+
}
|
|
2287
|
+
function getTiffDimensions(ifd) {
|
|
2288
|
+
return {
|
|
2289
|
+
width: Number(ifd.width || ifd.t256 || 0),
|
|
2290
|
+
height: Number(ifd.height || ifd.t257 || 0)
|
|
2291
|
+
};
|
|
2095
2292
|
}
|
|
2096
2293
|
function createImageFallback(fileName, url) {
|
|
2097
2294
|
const fallback = document.createElement("div");
|
|
@@ -2420,6 +2617,7 @@ function parseTiffInfo(bytes) {
|
|
|
2420
2617
|
return { format: magic === 43 ? "BigTIFF" : "TIFF", note: "IFD \u504F\u79FB\u8D85\u51FA\u6587\u4EF6\u8303\u56F4" };
|
|
2421
2618
|
}
|
|
2422
2619
|
const count = magic === 43 ? readTiffUint64AsNumber(bytes, ifdOffset, littleEndian) : readTiffUint16(bytes, ifdOffset, littleEndian);
|
|
2620
|
+
const imageCount = countTiffIfds(bytes, ifdOffset, magic === 43, littleEndian);
|
|
2423
2621
|
const entrySize = magic === 43 ? 20 : 12;
|
|
2424
2622
|
const entriesStart = ifdOffset + (magic === 43 ? 8 : 2);
|
|
2425
2623
|
let width;
|
|
@@ -2446,9 +2644,26 @@ function parseTiffInfo(bytes) {
|
|
|
2446
2644
|
height,
|
|
2447
2645
|
bitDepth: bitDepth ? `${bitDepth} bit` : void 0,
|
|
2448
2646
|
color: compression ? tiffCompressionName(compression) : void 0,
|
|
2449
|
-
count:
|
|
2647
|
+
count: imageCount || void 0
|
|
2450
2648
|
};
|
|
2451
2649
|
}
|
|
2650
|
+
function countTiffIfds(bytes, firstOffset, bigTiff, littleEndian) {
|
|
2651
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2652
|
+
let offset = firstOffset;
|
|
2653
|
+
let count = 0;
|
|
2654
|
+
while (Number.isFinite(offset) && offset > 0 && offset < bytes.length && !seen.has(offset) && count < 512) {
|
|
2655
|
+
seen.add(offset);
|
|
2656
|
+
const entryCount = bigTiff ? readTiffUint64AsNumber(bytes, offset, littleEndian) : readTiffUint16(bytes, offset, littleEndian);
|
|
2657
|
+
const entrySize = bigTiff ? 20 : 12;
|
|
2658
|
+
const nextOffsetPosition = offset + (bigTiff ? 8 : 2) + entryCount * entrySize;
|
|
2659
|
+
if (!Number.isFinite(entryCount) || nextOffsetPosition + (bigTiff ? 8 : 4) > bytes.length) {
|
|
2660
|
+
break;
|
|
2661
|
+
}
|
|
2662
|
+
count++;
|
|
2663
|
+
offset = bigTiff ? readTiffUint64AsNumber(bytes, nextOffsetPosition, littleEndian) : readTiffUint32(bytes, nextOffsetPosition, littleEndian);
|
|
2664
|
+
}
|
|
2665
|
+
return count;
|
|
2666
|
+
}
|
|
2452
2667
|
function readTiffInlineValue(bytes, offset, type, littleEndian) {
|
|
2453
2668
|
if (offset + 4 > bytes.length) {
|
|
2454
2669
|
return void 0;
|
|
@@ -3690,95 +3905,6 @@ function readAiffExtended(bytes, offset) {
|
|
|
3690
3905
|
return mantissa * Math.pow(2, exponent - 16383 - 63);
|
|
3691
3906
|
}
|
|
3692
3907
|
|
|
3693
|
-
// src/plugins/utils.ts
|
|
3694
|
-
async function readArrayBuffer(file) {
|
|
3695
|
-
if (file.source instanceof ArrayBuffer) {
|
|
3696
|
-
return file.source;
|
|
3697
|
-
}
|
|
3698
|
-
if (file.blob) {
|
|
3699
|
-
return file.blob.arrayBuffer();
|
|
3700
|
-
}
|
|
3701
|
-
if (typeof file.source === "string") {
|
|
3702
|
-
const response = await fetch(file.source);
|
|
3703
|
-
if (!response.ok) {
|
|
3704
|
-
throw new Error(`Failed to fetch file: ${response.status}`);
|
|
3705
|
-
}
|
|
3706
|
-
return response.arrayBuffer();
|
|
3707
|
-
}
|
|
3708
|
-
throw new Error("Unsupported file source.");
|
|
3709
|
-
}
|
|
3710
|
-
async function readTextFile(file) {
|
|
3711
|
-
const decode = (buffer) => decodeTextBuffer(buffer);
|
|
3712
|
-
if (typeof file.source === "string") {
|
|
3713
|
-
const response = await fetch(file.source);
|
|
3714
|
-
if (!response.ok) {
|
|
3715
|
-
throw new Error(`Failed to fetch text file: ${response.status}`);
|
|
3716
|
-
}
|
|
3717
|
-
return decode(await response.arrayBuffer());
|
|
3718
|
-
}
|
|
3719
|
-
if (file.blob) {
|
|
3720
|
-
return decode(await file.blob.arrayBuffer());
|
|
3721
|
-
}
|
|
3722
|
-
if (file.source instanceof ArrayBuffer) {
|
|
3723
|
-
return decode(file.source);
|
|
3724
|
-
}
|
|
3725
|
-
return String(file.source);
|
|
3726
|
-
}
|
|
3727
|
-
function createPanel(className = "") {
|
|
3728
|
-
const panel = document.createElement("div");
|
|
3729
|
-
panel.className = `ofv-panel ${className}`.trim();
|
|
3730
|
-
return panel;
|
|
3731
|
-
}
|
|
3732
|
-
function createSection(title) {
|
|
3733
|
-
const section = document.createElement("section");
|
|
3734
|
-
section.className = "ofv-section";
|
|
3735
|
-
const heading = document.createElement("h3");
|
|
3736
|
-
heading.textContent = title;
|
|
3737
|
-
section.append(heading);
|
|
3738
|
-
return section;
|
|
3739
|
-
}
|
|
3740
|
-
function appendMeta(parent, label, value) {
|
|
3741
|
-
const row = document.createElement("div");
|
|
3742
|
-
row.className = "ofv-meta-row";
|
|
3743
|
-
const key = document.createElement("span");
|
|
3744
|
-
key.textContent = label;
|
|
3745
|
-
const content = document.createElement("strong");
|
|
3746
|
-
content.textContent = String(value);
|
|
3747
|
-
row.append(key, content);
|
|
3748
|
-
parent.append(row);
|
|
3749
|
-
}
|
|
3750
|
-
function decodeTextBuffer(buffer) {
|
|
3751
|
-
return decodeTextBytes(new Uint8Array(buffer));
|
|
3752
|
-
}
|
|
3753
|
-
function decodeTextBytes(bytes) {
|
|
3754
|
-
if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
|
|
3755
|
-
return new TextDecoder("utf-8").decode(bytes.subarray(3));
|
|
3756
|
-
}
|
|
3757
|
-
if (bytes.length >= 2) {
|
|
3758
|
-
if (bytes[0] === 255 && bytes[1] === 254) {
|
|
3759
|
-
return new TextDecoder("utf-16le").decode(bytes.subarray(2));
|
|
3760
|
-
}
|
|
3761
|
-
if (bytes[0] === 254 && bytes[1] === 255) {
|
|
3762
|
-
return new TextDecoder("utf-16be").decode(bytes.subarray(2));
|
|
3763
|
-
}
|
|
3764
|
-
}
|
|
3765
|
-
try {
|
|
3766
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
3767
|
-
} catch {
|
|
3768
|
-
return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
|
|
3769
|
-
}
|
|
3770
|
-
}
|
|
3771
|
-
function decodeWithFallback(bytes, encoding) {
|
|
3772
|
-
try {
|
|
3773
|
-
return new TextDecoder(encoding).decode(bytes);
|
|
3774
|
-
} catch {
|
|
3775
|
-
return void 0;
|
|
3776
|
-
}
|
|
3777
|
-
}
|
|
3778
|
-
function resolveFormat(file, mimeMap) {
|
|
3779
|
-
return file.extension || mimeMap[file.mimeType] || "";
|
|
3780
|
-
}
|
|
3781
|
-
|
|
3782
3908
|
// src/plugins/text.ts
|
|
3783
3909
|
var langMap = {
|
|
3784
3910
|
js: "javascript",
|
|
@@ -3936,26 +4062,6 @@ var mimeLangMap = {
|
|
|
3936
4062
|
};
|
|
3937
4063
|
var MAX_HIGHLIGHT_CHARS = 18e4;
|
|
3938
4064
|
var MAX_RENDER_CHARS = 6e5;
|
|
3939
|
-
function loadPrismCss(theme) {
|
|
3940
|
-
const lightId = "ofv-prism-css-light";
|
|
3941
|
-
const darkId = "ofv-prism-css-dark";
|
|
3942
|
-
const activeId = theme === "dark" ? darkId : lightId;
|
|
3943
|
-
const inactiveId = theme === "dark" ? lightId : darkId;
|
|
3944
|
-
document.getElementById(inactiveId)?.remove();
|
|
3945
|
-
if (document.getElementById(activeId)) {
|
|
3946
|
-
return Promise.resolve();
|
|
3947
|
-
}
|
|
3948
|
-
const href = theme === "dark" ? "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" : "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css";
|
|
3949
|
-
return new Promise((resolve, reject) => {
|
|
3950
|
-
const link = document.createElement("link");
|
|
3951
|
-
link.id = activeId;
|
|
3952
|
-
link.rel = "stylesheet";
|
|
3953
|
-
link.href = href;
|
|
3954
|
-
link.onload = () => resolve();
|
|
3955
|
-
link.onerror = () => reject(new Error(`Failed to load Prism CSS: ${href}`));
|
|
3956
|
-
document.head.appendChild(link);
|
|
3957
|
-
});
|
|
3958
|
-
}
|
|
3959
4065
|
function textPlugin() {
|
|
3960
4066
|
return {
|
|
3961
4067
|
name: "text",
|
|
@@ -3979,7 +4085,6 @@ function textPlugin() {
|
|
|
3979
4085
|
}
|
|
3980
4086
|
};
|
|
3981
4087
|
}
|
|
3982
|
-
const isDark = ctx.host.parentElement?.classList.contains("ofv-theme-dark") || document.body.classList.contains("ofv-theme-dark") || ctx.options.theme === "auto" && window.matchMedia?.("(prefers-color-scheme: dark)").matches || ctx.options.theme === "dark";
|
|
3983
4088
|
if (isMarkdown) {
|
|
3984
4089
|
const [markedModule, PrismModule2, DOMPurifyModule] = await Promise.all([
|
|
3985
4090
|
import("marked"),
|
|
@@ -4000,7 +4105,6 @@ function textPlugin() {
|
|
|
4000
4105
|
try {
|
|
4001
4106
|
const codeBlocks = container.querySelectorAll("pre code");
|
|
4002
4107
|
if (codeBlocks.length > 0) {
|
|
4003
|
-
await loadPrismCss(isDark ? "dark" : "light");
|
|
4004
4108
|
codeBlocks.forEach((block) => {
|
|
4005
4109
|
const parent = block.parentElement;
|
|
4006
4110
|
if (parent && !parent.className.includes("language-")) {
|
|
@@ -4124,9 +4228,6 @@ function textPlugin() {
|
|
|
4124
4228
|
console.warn(`Prism failed to load language component for: ${lang}`, e);
|
|
4125
4229
|
}
|
|
4126
4230
|
}
|
|
4127
|
-
await loadPrismCss(isDark ? "dark" : "light").catch((error) => {
|
|
4128
|
-
console.warn("Prism CSS failed to load; rendering code without external theme:", error);
|
|
4129
|
-
});
|
|
4130
4231
|
const codeText = text.length > MAX_RENDER_CHARS ? text.slice(0, MAX_RENDER_CHARS) : text;
|
|
4131
4232
|
const totalLines = countLines(text);
|
|
4132
4233
|
const shownLines = countLines(codeText);
|
|
@@ -4247,7 +4348,7 @@ function textPlugin() {
|
|
|
4247
4348
|
};
|
|
4248
4349
|
}
|
|
4249
4350
|
function createTextZoomController(target, cssVariable, ctx) {
|
|
4250
|
-
let zoom =
|
|
4351
|
+
let zoom = getInitialZoom(ctx, 0.5, 3);
|
|
4251
4352
|
const apply = () => {
|
|
4252
4353
|
const normalized = Math.round(zoom * 100) / 100;
|
|
4253
4354
|
target.style.setProperty(cssVariable, String(normalized));
|
|
@@ -4557,6 +4658,7 @@ function pdfPlugin(options = {}) {
|
|
|
4557
4658
|
viewport: ctx.viewport,
|
|
4558
4659
|
size: ctx.size,
|
|
4559
4660
|
fit: ctx.options.fit,
|
|
4661
|
+
zoom: ctx.options.zoom,
|
|
4560
4662
|
toolbar: ctx.toolbar,
|
|
4561
4663
|
encryptedTitle: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
4562
4664
|
encryptedMessage: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
|
|
@@ -4653,16 +4755,17 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4653
4755
|
const baseViewport = page.getViewport({ scale: 1 });
|
|
4654
4756
|
pagesMeta.push({
|
|
4655
4757
|
width: baseViewport.width,
|
|
4656
|
-
height: baseViewport.height
|
|
4758
|
+
height: baseViewport.height,
|
|
4759
|
+
rotation: getPdfPageRotation(page)
|
|
4657
4760
|
});
|
|
4658
4761
|
} catch {
|
|
4659
|
-
pagesMeta.push({ width: 612, height: 792 });
|
|
4762
|
+
pagesMeta.push({ width: 612, height: 792, rotation: 0 });
|
|
4660
4763
|
}
|
|
4661
4764
|
}
|
|
4662
4765
|
const pageStates = [];
|
|
4663
4766
|
let observer = null;
|
|
4664
4767
|
let currentSize = options.size;
|
|
4665
|
-
let zoomFactor = 1;
|
|
4768
|
+
let zoomFactor = getInitialZoom({ options: { zoom: options.zoom ?? 1 } }, 0.25, 4);
|
|
4666
4769
|
let rotation = 0;
|
|
4667
4770
|
const updateSummary = () => {
|
|
4668
4771
|
renderPdfSummary(summary, pdfDocument.numPages, pagesMeta, options.fit, zoomFactor);
|
|
@@ -4691,7 +4794,7 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4691
4794
|
const page = await pdfDocument.getPage(pageIdx + 1);
|
|
4692
4795
|
const meta = pagesMeta[pageIdx];
|
|
4693
4796
|
const scale = options.fit === "actual" ? zoomFactor : Math.max(0.05, Math.min(5, getPdfAvailableWidth(size.width) / rotatedPdfWidth(meta, rotation) * zoomFactor));
|
|
4694
|
-
const viewport = page.getViewport({ scale, rotation });
|
|
4797
|
+
const viewport = page.getViewport({ scale, rotation: getPdfRenderRotation(meta, rotation) });
|
|
4695
4798
|
const outputScale = getPdfOutputScale();
|
|
4696
4799
|
const cssWidth = Math.floor(viewport.width);
|
|
4697
4800
|
const cssHeight = Math.floor(viewport.height);
|
|
@@ -4730,6 +4833,8 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4730
4833
|
const span = document.createElement("span");
|
|
4731
4834
|
span.textContent = str;
|
|
4732
4835
|
span.style.fontSize = `${fontHeight}px`;
|
|
4836
|
+
span.style.lineHeight = "1";
|
|
4837
|
+
span.style.height = `${fontHeight}px`;
|
|
4733
4838
|
span.style.fontFamily = item.fontName || "sans-serif";
|
|
4734
4839
|
span.style.left = `${tx[4]}px`;
|
|
4735
4840
|
span.style.top = `${tx[5] - fontHeight}px`;
|
|
@@ -4927,6 +5032,13 @@ function appendPdfSummary(parent, label, value) {
|
|
|
4927
5032
|
function normalizePdfRotation(value) {
|
|
4928
5033
|
return (value % 360 + 360) % 360;
|
|
4929
5034
|
}
|
|
5035
|
+
function getPdfPageRotation(page) {
|
|
5036
|
+
const rotation = Number(page.rotate);
|
|
5037
|
+
return Number.isFinite(rotation) ? normalizePdfRotation(rotation) : 0;
|
|
5038
|
+
}
|
|
5039
|
+
function getPdfRenderRotation(meta, userRotation) {
|
|
5040
|
+
return normalizePdfRotation((meta.rotation || 0) + userRotation);
|
|
5041
|
+
}
|
|
4930
5042
|
function isPdfRotatedSideways(rotation) {
|
|
4931
5043
|
const normalized = normalizePdfRotation(rotation);
|
|
4932
5044
|
return normalized === 90 || normalized === 270;
|
|
@@ -5897,13 +6009,13 @@ function officePlugin(options = {}) {
|
|
|
5897
6009
|
if (conversionContext && await shouldUseOfficeConversion(options, conversionContext)) {
|
|
5898
6010
|
delegatedInstance = await renderConvertedOfficePreview(panel, ctx, options, conversionContext);
|
|
5899
6011
|
} else if (packageFormat === "docx" && !fileIsDocx(extension)) {
|
|
5900
|
-
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
6012
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
|
|
5901
6013
|
} else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
|
|
5902
6014
|
await renderSheet(panel, arrayBuffer, "xlsx");
|
|
5903
6015
|
} else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
|
|
5904
6016
|
await renderPptx(panel, arrayBuffer);
|
|
5905
6017
|
} else if (fileIsDocx(extension)) {
|
|
5906
|
-
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
6018
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
|
|
5907
6019
|
} else if (extension === "rtf") {
|
|
5908
6020
|
renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
|
|
5909
6021
|
} else if (extension === "odt") {
|
|
@@ -5912,7 +6024,7 @@ function officePlugin(options = {}) {
|
|
|
5912
6024
|
renderOpenDocumentXml(panel, "FODT \u6587\u6863", await readTextFromBuffer(arrayBuffer));
|
|
5913
6025
|
} else if (extension === "fods") {
|
|
5914
6026
|
renderFlatOds(panel, await readTextFromBuffer(arrayBuffer));
|
|
5915
|
-
} else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension)) {
|
|
6027
|
+
} else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension, ctx.options.fit)) {
|
|
5916
6028
|
} else if (sheetExtensions.has(extension)) {
|
|
5917
6029
|
await renderSheet(panel, arrayBuffer, extension);
|
|
5918
6030
|
} else if (["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
|
|
@@ -5987,6 +6099,7 @@ async function renderConvertedOfficePreview(panel, ctx, options, conversionConte
|
|
|
5987
6099
|
viewport: panel,
|
|
5988
6100
|
size: ctx.size,
|
|
5989
6101
|
fit: ctx.options.fit,
|
|
6102
|
+
zoom: ctx.options.zoom,
|
|
5990
6103
|
toolbar: ctx.toolbar,
|
|
5991
6104
|
title: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
|
|
5992
6105
|
fallbackTitle: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
|
|
@@ -6050,7 +6163,7 @@ function createOfficeZoomController(panel, ctx) {
|
|
|
6050
6163
|
if (!canZoom) {
|
|
6051
6164
|
return void 0;
|
|
6052
6165
|
}
|
|
6053
|
-
let zoom =
|
|
6166
|
+
let zoom = getInitialZoom(ctx, 0.5, 3);
|
|
6054
6167
|
const apply = () => {
|
|
6055
6168
|
panel.style.setProperty("--ofv-office-zoom", String(zoom));
|
|
6056
6169
|
panel.dispatchEvent(new CustomEvent("ofv-office-zoom"));
|
|
@@ -6113,7 +6226,7 @@ async function detectPackagedOfficeFormat(arrayBuffer) {
|
|
|
6113
6226
|
}
|
|
6114
6227
|
return void 0;
|
|
6115
6228
|
}
|
|
6116
|
-
async function renderDocx(panel, arrayBuffer) {
|
|
6229
|
+
async function renderDocx(panel, arrayBuffer, fit) {
|
|
6117
6230
|
const content = document.createElement("div");
|
|
6118
6231
|
content.className = "ofv-docx-document";
|
|
6119
6232
|
const styleContainer = document.createElement("div");
|
|
@@ -6152,7 +6265,7 @@ async function renderDocx(panel, arrayBuffer) {
|
|
|
6152
6265
|
return () => void 0;
|
|
6153
6266
|
}
|
|
6154
6267
|
panel.append(content);
|
|
6155
|
-
disposeFit = fitDocxPages(content);
|
|
6268
|
+
disposeFit = fitDocxPages(content, fit);
|
|
6156
6269
|
return () => {
|
|
6157
6270
|
disposeFit?.();
|
|
6158
6271
|
styleContainer.remove();
|
|
@@ -6750,7 +6863,8 @@ function looksLikeDocxTextboxHeading(value) {
|
|
|
6750
6863
|
return text.length > 0 && text.length <= 12 && !/[0-9@.::]/.test(text);
|
|
6751
6864
|
}
|
|
6752
6865
|
async function normalizeDocxLayout(container, arrayBuffer) {
|
|
6753
|
-
const hints = await readDocxLayoutHints(arrayBuffer);
|
|
6866
|
+
const [hints, charts] = await Promise.all([readDocxLayoutHints(arrayBuffer), readDocxCharts(arrayBuffer)]);
|
|
6867
|
+
repairDocxChartPlaceholders(container, charts);
|
|
6754
6868
|
const pages = container.querySelectorAll("section.ofv-docx");
|
|
6755
6869
|
for (const page of pages) {
|
|
6756
6870
|
repairDocxShapeFills(page);
|
|
@@ -6765,6 +6879,75 @@ async function normalizeDocxLayout(container, arrayBuffer) {
|
|
|
6765
6879
|
}
|
|
6766
6880
|
}
|
|
6767
6881
|
}
|
|
6882
|
+
async function readDocxCharts(arrayBuffer) {
|
|
6883
|
+
try {
|
|
6884
|
+
const zip = await import_jszip3.default.loadAsync(arrayBuffer);
|
|
6885
|
+
const documentXml = await zip.file("word/document.xml")?.async("text");
|
|
6886
|
+
const documentDoc = documentXml ? parseOfficeXml(documentXml) : void 0;
|
|
6887
|
+
if (!documentDoc) {
|
|
6888
|
+
return [];
|
|
6889
|
+
}
|
|
6890
|
+
const relationships = await readOfficeRelationships(zip, "word/document.xml");
|
|
6891
|
+
const chartDrawings = Array.from(documentDoc.getElementsByTagName("*")).filter((element) => element.localName === "inline" || element.localName === "anchor").map((element) => readDocxChartDrawing(element)).filter((item) => Boolean(item?.relationshipId));
|
|
6892
|
+
const charts = [];
|
|
6893
|
+
for (const [index, drawing] of chartDrawings.entries()) {
|
|
6894
|
+
const chartRel = relationships.find((rel) => rel.id === drawing.relationshipId && /\/chart$/i.test(rel.type));
|
|
6895
|
+
const chartPath = resolveOfficeRelationshipTarget("word/document.xml", chartRel?.target);
|
|
6896
|
+
const chartXml = chartPath ? await zip.file(chartPath)?.async("text") : void 0;
|
|
6897
|
+
const chart = chartXml ? parseChartXml(chartXml, chartPath?.split("/").pop() || `chart${index + 1}.xml`) : null;
|
|
6898
|
+
if (chart) {
|
|
6899
|
+
charts.push({ ...chart, widthPt: drawing.widthPt, heightPt: drawing.heightPt });
|
|
6900
|
+
}
|
|
6901
|
+
}
|
|
6902
|
+
return charts;
|
|
6903
|
+
} catch {
|
|
6904
|
+
return [];
|
|
6905
|
+
}
|
|
6906
|
+
}
|
|
6907
|
+
function readDocxChartDrawing(element) {
|
|
6908
|
+
const chart = Array.from(element.getElementsByTagName("*")).find((child) => child.localName === "chart");
|
|
6909
|
+
const relationshipId = chart ? getXmlAttribute3(chart, "id") : "";
|
|
6910
|
+
if (!relationshipId) {
|
|
6911
|
+
return void 0;
|
|
6912
|
+
}
|
|
6913
|
+
const extent = Array.from(element.children).find((child) => child.localName === "extent");
|
|
6914
|
+
return {
|
|
6915
|
+
relationshipId,
|
|
6916
|
+
widthPt: emuToPt(Number(extent?.getAttribute("cx") || 0)),
|
|
6917
|
+
heightPt: emuToPt(Number(extent?.getAttribute("cy") || 0))
|
|
6918
|
+
};
|
|
6919
|
+
}
|
|
6920
|
+
function repairDocxChartPlaceholders(container, charts) {
|
|
6921
|
+
if (charts.length === 0) {
|
|
6922
|
+
return;
|
|
6923
|
+
}
|
|
6924
|
+
const placeholders = Array.from(container.querySelectorAll("section.ofv-docx div")).filter(isDocxChartPlaceholder);
|
|
6925
|
+
if (placeholders.length !== charts.length) {
|
|
6926
|
+
return;
|
|
6927
|
+
}
|
|
6928
|
+
placeholders.forEach((placeholder, index) => {
|
|
6929
|
+
const chart = charts[index];
|
|
6930
|
+
placeholder.classList.add("ofv-docx-chart-preview");
|
|
6931
|
+
placeholder.dataset.ofvDocxChartPreview = "true";
|
|
6932
|
+
if (chart.widthPt > 0) {
|
|
6933
|
+
placeholder.style.width ||= `${formatCssNumber(chart.widthPt)}pt`;
|
|
6934
|
+
}
|
|
6935
|
+
if (chart.heightPt > 0) {
|
|
6936
|
+
placeholder.style.height ||= `${formatCssNumber(chart.heightPt)}pt`;
|
|
6937
|
+
}
|
|
6938
|
+
placeholder.replaceChildren(renderChartSvg(chart));
|
|
6939
|
+
});
|
|
6940
|
+
}
|
|
6941
|
+
function isDocxChartPlaceholder(element) {
|
|
6942
|
+
if (element.dataset.ofvDocxChartPreview === "true" || element.children.length > 0 || normalizePreviewText(element.textContent || "")) {
|
|
6943
|
+
return false;
|
|
6944
|
+
}
|
|
6945
|
+
const display = element.style.display;
|
|
6946
|
+
const position = element.style.position;
|
|
6947
|
+
const width = parseCssPixelValue(element.style.width);
|
|
6948
|
+
const height = parseCssPixelValue(element.style.height);
|
|
6949
|
+
return display === "inline-block" && position === "relative" && width >= 120 && height >= 80;
|
|
6950
|
+
}
|
|
6768
6951
|
function repairDocxHeadingShapeAlignment(page) {
|
|
6769
6952
|
for (const paragraph of page.querySelectorAll("p")) {
|
|
6770
6953
|
const text = normalizePreviewText(paragraph.textContent || "");
|
|
@@ -6915,14 +7098,19 @@ function repairDocxShapeFills(page) {
|
|
|
6915
7098
|
}
|
|
6916
7099
|
}
|
|
6917
7100
|
function repairDocxFloatingPictures(page, hints) {
|
|
6918
|
-
const
|
|
6919
|
-
if (
|
|
7101
|
+
const pageHints = hints.floatingPictures.filter((item) => item.relativeFrom === "column" && item.wrap === "square");
|
|
7102
|
+
if (pageHints.length === 0) {
|
|
6920
7103
|
return;
|
|
6921
7104
|
}
|
|
6922
|
-
const
|
|
6923
|
-
|
|
7105
|
+
const images = Array.from(page.querySelectorAll("img")).filter(
|
|
7106
|
+
(image) => image.closest("[data-ofv-docx-float-repaired='true']") === null
|
|
7107
|
+
);
|
|
7108
|
+
if (images.length === 0 || images.length !== pageHints.length) {
|
|
6924
7109
|
return;
|
|
6925
7110
|
}
|
|
7111
|
+
images.forEach((image, index) => repairDocxFloatingPicture(page, image, pageHints[index]));
|
|
7112
|
+
}
|
|
7113
|
+
function repairDocxFloatingPicture(page, image, hint) {
|
|
6926
7114
|
const wrapper = image.parentElement;
|
|
6927
7115
|
if (!wrapper || wrapper.dataset.ofvDocxFloatRepaired === "true") {
|
|
6928
7116
|
return;
|
|
@@ -6968,7 +7156,7 @@ function parseCssLineHeight(value) {
|
|
|
6968
7156
|
const parsed = Number.parseFloat(trimmed);
|
|
6969
7157
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
6970
7158
|
}
|
|
6971
|
-
function fitDocxPages(container) {
|
|
7159
|
+
function fitDocxPages(container, fit) {
|
|
6972
7160
|
const wrapper = container.querySelector(".ofv-docx-wrapper");
|
|
6973
7161
|
if (!wrapper) {
|
|
6974
7162
|
return () => void 0;
|
|
@@ -6981,6 +7169,7 @@ function fitDocxPages(container) {
|
|
|
6981
7169
|
return;
|
|
6982
7170
|
}
|
|
6983
7171
|
const availableWidth = Math.max(1, container.clientWidth - 48);
|
|
7172
|
+
const availableHeight = container.clientHeight > 0 ? Math.max(1, container.clientHeight - 48) : void 0;
|
|
6984
7173
|
const pageWidth = Math.max(
|
|
6985
7174
|
1,
|
|
6986
7175
|
...frames.map(({ page }) => {
|
|
@@ -6988,14 +7177,24 @@ function fitDocxPages(container) {
|
|
|
6988
7177
|
return page.offsetWidth || rectWidth || parseCssPixelValue(page.style.width) || 794;
|
|
6989
7178
|
})
|
|
6990
7179
|
);
|
|
6991
|
-
const
|
|
7180
|
+
const pageHeight = Math.max(
|
|
7181
|
+
1,
|
|
7182
|
+
...frames.map(({ page }) => page.offsetHeight || page.getBoundingClientRect().height || parseCssPixelValue(page.style.height) || 1123)
|
|
7183
|
+
);
|
|
7184
|
+
const scale = getDocxFitScale(fit, availableWidth, availableHeight, pageWidth, pageHeight);
|
|
6992
7185
|
const userZoom = parseCssPixelValue(panel?.style.getPropertyValue("--ofv-office-zoom") || "1") || 1;
|
|
6993
7186
|
wrapper.style.setProperty("--ofv-docx-scale", formatCssNumber(scale));
|
|
6994
7187
|
wrapper.style.setProperty("--ofv-docx-page-width", `${pageWidth}px`);
|
|
7188
|
+
wrapper.style.width = `${Math.ceil(pageWidth * scale * userZoom + 48)}px`;
|
|
7189
|
+
wrapper.style.maxWidth = "none";
|
|
7190
|
+
wrapper.style.overflow = "visible";
|
|
6995
7191
|
for (const { frame, page } of frames) {
|
|
6996
|
-
const
|
|
6997
|
-
|
|
6998
|
-
|
|
7192
|
+
const framePageHeight = page.offsetHeight || page.getBoundingClientRect().height || parseCssPixelValue(page.style.height);
|
|
7193
|
+
const framePageWidth = page.offsetWidth || page.getBoundingClientRect().width || parseCssPixelValue(page.style.width) || pageWidth;
|
|
7194
|
+
frame.style.width = `${Math.ceil(framePageWidth * scale * userZoom)}px`;
|
|
7195
|
+
frame.style.maxWidth = "none";
|
|
7196
|
+
if (framePageHeight > 0) {
|
|
7197
|
+
frame.style.height = `${Math.ceil(framePageHeight * scale * userZoom)}px`;
|
|
6999
7198
|
}
|
|
7000
7199
|
}
|
|
7001
7200
|
};
|
|
@@ -7020,6 +7219,26 @@ function fitDocxPages(container) {
|
|
|
7020
7219
|
observer.disconnect();
|
|
7021
7220
|
};
|
|
7022
7221
|
}
|
|
7222
|
+
function getDocxFitScale(fit, availableWidth, availableHeight, pageWidth, pageHeight) {
|
|
7223
|
+
const widthScale = availableWidth / pageWidth;
|
|
7224
|
+
const heightScale = availableHeight ? availableHeight / pageHeight : void 0;
|
|
7225
|
+
if (fit === "actual") {
|
|
7226
|
+
return 1;
|
|
7227
|
+
}
|
|
7228
|
+
if (fit === "height") {
|
|
7229
|
+
return Math.max(0.1, heightScale ?? widthScale);
|
|
7230
|
+
}
|
|
7231
|
+
if (fit === "cover") {
|
|
7232
|
+
return Math.max(0.1, Math.max(widthScale, heightScale ?? widthScale));
|
|
7233
|
+
}
|
|
7234
|
+
if (fit === "scale-down") {
|
|
7235
|
+
return Math.min(1, Math.max(0.1, Math.min(widthScale, heightScale ?? widthScale)));
|
|
7236
|
+
}
|
|
7237
|
+
if (fit === "contain") {
|
|
7238
|
+
return Math.max(0.1, Math.min(widthScale, heightScale ?? widthScale));
|
|
7239
|
+
}
|
|
7240
|
+
return Math.max(0.1, widthScale);
|
|
7241
|
+
}
|
|
7023
7242
|
function ensureDocxPageFrames(wrapper) {
|
|
7024
7243
|
const pages = Array.from(wrapper.querySelectorAll("section.ofv-docx"));
|
|
7025
7244
|
return pages.map((page) => {
|
|
@@ -7551,9 +7770,9 @@ function parseChartXml(xml, fallbackName) {
|
|
|
7551
7770
|
return null;
|
|
7552
7771
|
}
|
|
7553
7772
|
const type = detectChartType(doc);
|
|
7554
|
-
const title =
|
|
7773
|
+
const title = readChartTitle(doc, fallbackName);
|
|
7555
7774
|
const seriesElements = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ser");
|
|
7556
|
-
const series = seriesElements.map((element, index) => parseChartSeries(element, index)).filter((item) => item.values.length > 0);
|
|
7775
|
+
const series = seriesElements.map((element, index) => ({ ...parseChartSeries(element, index), color: readChartSeriesColor(element) })).filter((item) => item.values.length > 0);
|
|
7557
7776
|
if (series.length === 0) {
|
|
7558
7777
|
return null;
|
|
7559
7778
|
}
|
|
@@ -7562,9 +7781,21 @@ function parseChartXml(xml, fallbackName) {
|
|
|
7562
7781
|
type,
|
|
7563
7782
|
title,
|
|
7564
7783
|
categories: series.find((item) => item.categories.length > 0)?.categories || [],
|
|
7565
|
-
series: series.map((item) => ({ name: item.name, values: item.values }))
|
|
7784
|
+
series: series.map((item) => ({ name: item.name, values: item.values, color: item.color }))
|
|
7566
7785
|
};
|
|
7567
7786
|
}
|
|
7787
|
+
function readChartTitle(doc, fallbackName) {
|
|
7788
|
+
const titleElement = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "title");
|
|
7789
|
+
if (!titleElement) {
|
|
7790
|
+
return fallbackName.replace(/\.xml$/i, "");
|
|
7791
|
+
}
|
|
7792
|
+
const explicitTitle = chartText(titleElement);
|
|
7793
|
+
if (explicitTitle) {
|
|
7794
|
+
return explicitTitle;
|
|
7795
|
+
}
|
|
7796
|
+
const language = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "lang")?.getAttribute("val") || "";
|
|
7797
|
+
return /^zh\b/i.test(language) ? "\u56FE\u8868\u6807\u9898" : "Chart Title";
|
|
7798
|
+
}
|
|
7568
7799
|
function detectChartType(doc) {
|
|
7569
7800
|
const chartType = Array.from(doc.getElementsByTagName("*")).find(
|
|
7570
7801
|
(element) => element.localName.endsWith("Chart") && element.localName !== "chart"
|
|
@@ -7581,6 +7812,31 @@ function parseChartSeries(element, index) {
|
|
|
7581
7812
|
categories: stringsFromFirst(element, "cat")
|
|
7582
7813
|
};
|
|
7583
7814
|
}
|
|
7815
|
+
function readChartSeriesColor(element) {
|
|
7816
|
+
const shape = Array.from(element?.children || []).find((child) => child.localName === "spPr");
|
|
7817
|
+
const color = Array.from(shape?.getElementsByTagName("*") || []).find(
|
|
7818
|
+
(child) => child.localName === "srgbClr" || child.localName === "schemeClr"
|
|
7819
|
+
);
|
|
7820
|
+
if (!color) {
|
|
7821
|
+
return void 0;
|
|
7822
|
+
}
|
|
7823
|
+
if (color.localName === "srgbClr") {
|
|
7824
|
+
const value = color.getAttribute("val") || "";
|
|
7825
|
+
return /^[\da-f]{6}$/i.test(value) ? `#${value}` : void 0;
|
|
7826
|
+
}
|
|
7827
|
+
return chartSchemeColor(color.getAttribute("val") || "");
|
|
7828
|
+
}
|
|
7829
|
+
function chartSchemeColor(value) {
|
|
7830
|
+
const colors = {
|
|
7831
|
+
accent1: "#156082",
|
|
7832
|
+
accent2: "#e97132",
|
|
7833
|
+
accent3: "#196b24",
|
|
7834
|
+
accent4: "#0f9ed5",
|
|
7835
|
+
accent5: "#a02b93",
|
|
7836
|
+
accent6: "#4ea72e"
|
|
7837
|
+
};
|
|
7838
|
+
return colors[value];
|
|
7839
|
+
}
|
|
7584
7840
|
function renderChartPreviewSection(charts) {
|
|
7585
7841
|
const section = createSection("\u8868\u683C\u56FE\u8868\u9884\u89C8");
|
|
7586
7842
|
const grid = document.createElement("div");
|
|
@@ -7618,17 +7874,39 @@ function renderChartCard(chart) {
|
|
|
7618
7874
|
}
|
|
7619
7875
|
function renderChartSvg(chart) {
|
|
7620
7876
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
7621
|
-
svg.setAttribute("viewBox", "0 0 640
|
|
7877
|
+
svg.setAttribute("viewBox", "0 0 640 380");
|
|
7622
7878
|
svg.setAttribute("role", "img");
|
|
7623
7879
|
svg.setAttribute("aria-label", chart.title);
|
|
7624
7880
|
svg.classList.add("ofv-chart-svg");
|
|
7625
7881
|
const allValues = chart.series.flatMap((item) => item.values).filter((value) => Number.isFinite(value));
|
|
7626
7882
|
const max = Math.max(1, ...allValues);
|
|
7627
7883
|
const min = Math.min(0, ...allValues);
|
|
7884
|
+
const axisScale = createChartAxisScale(max, min);
|
|
7885
|
+
const axisMax = axisScale.max;
|
|
7886
|
+
const axisMin = axisScale.min;
|
|
7628
7887
|
const span = max - min || 1;
|
|
7629
|
-
const colors = ["#
|
|
7630
|
-
const plot = { x:
|
|
7631
|
-
|
|
7888
|
+
const colors = ["#156082", "#e97132", "#196b24", "#0f9ed5", "#a02b93", "#4ea72e"];
|
|
7889
|
+
const plot = { x: 74, y: 74, width: 526, height: 214 };
|
|
7890
|
+
const categories = chart.categories.length > 0 ? chart.categories : chart.series[0]?.values.map((_, index) => String(index + 1)) || [];
|
|
7891
|
+
const title = appendSvg(svg, "text", { x: 320, y: 34, class: "ofv-chart-title", "text-anchor": "middle" });
|
|
7892
|
+
title.textContent = chart.title;
|
|
7893
|
+
for (const value of axisScale.ticks) {
|
|
7894
|
+
const y = plot.y + plot.height - (value - axisMin) / (axisMax - axisMin || 1) * plot.height;
|
|
7895
|
+
appendSvg(svg, "line", {
|
|
7896
|
+
x1: plot.x,
|
|
7897
|
+
y1: Number(y.toFixed(1)),
|
|
7898
|
+
x2: plot.x + plot.width,
|
|
7899
|
+
y2: Number(y.toFixed(1)),
|
|
7900
|
+
class: value === 0 ? "ofv-chart-axis" : "ofv-chart-gridline"
|
|
7901
|
+
});
|
|
7902
|
+
const label = appendSvg(svg, "text", {
|
|
7903
|
+
x: plot.x - 12,
|
|
7904
|
+
y: Number((y + 4).toFixed(1)),
|
|
7905
|
+
class: "ofv-chart-label",
|
|
7906
|
+
"text-anchor": "end"
|
|
7907
|
+
});
|
|
7908
|
+
label.textContent = formatChartTick(value);
|
|
7909
|
+
}
|
|
7632
7910
|
appendSvg(svg, "line", {
|
|
7633
7911
|
x1: plot.x,
|
|
7634
7912
|
y1: plot.y + plot.height,
|
|
@@ -7636,28 +7914,47 @@ function renderChartSvg(chart) {
|
|
|
7636
7914
|
y2: plot.y + plot.height,
|
|
7637
7915
|
class: "ofv-chart-axis"
|
|
7638
7916
|
});
|
|
7639
|
-
chart.
|
|
7640
|
-
const
|
|
7641
|
-
const
|
|
7642
|
-
const
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
const
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7917
|
+
if (chart.type.includes("bar") || chart.type.includes("col")) {
|
|
7918
|
+
const categoryCount = Math.max(1, categories.length, ...chart.series.map((series) => series.values.length));
|
|
7919
|
+
const groupWidth = plot.width / categoryCount;
|
|
7920
|
+
const clusterWidth = groupWidth * 0.58;
|
|
7921
|
+
const barWidth = Math.max(5, Math.min(28, clusterWidth / Math.max(1, chart.series.length)));
|
|
7922
|
+
const zeroY = plot.y + plot.height - (0 - axisMin) / (axisMax - axisMin || 1) * plot.height;
|
|
7923
|
+
categories.slice(0, categoryCount).forEach((category, index) => {
|
|
7924
|
+
const x = plot.x + groupWidth * (index + 0.5);
|
|
7925
|
+
const label = appendSvg(svg, "text", {
|
|
7926
|
+
x: Number(x.toFixed(1)),
|
|
7927
|
+
y: plot.y + plot.height + 22,
|
|
7928
|
+
class: "ofv-chart-label",
|
|
7929
|
+
"text-anchor": "middle"
|
|
7930
|
+
});
|
|
7931
|
+
label.textContent = truncateChartLabel(category);
|
|
7932
|
+
});
|
|
7933
|
+
chart.series.forEach((series, seriesIndex) => {
|
|
7934
|
+
const color = series.color || colors[seriesIndex % colors.length];
|
|
7935
|
+
series.values.forEach((value, index) => {
|
|
7936
|
+
const groupCenter = plot.x + groupWidth * (index + 0.5);
|
|
7937
|
+
const x = groupCenter - barWidth * chart.series.length / 2 + seriesIndex * barWidth + barWidth * 0.12;
|
|
7938
|
+
const y = plot.y + plot.height - (value - axisMin) / (axisMax - axisMin || 1) * plot.height;
|
|
7651
7939
|
appendSvg(svg, "rect", {
|
|
7652
|
-
x,
|
|
7653
|
-
y: Math.min(
|
|
7654
|
-
width: barWidth,
|
|
7655
|
-
height: Math.max(1, Math.abs(zeroY -
|
|
7940
|
+
x: Number(x.toFixed(1)),
|
|
7941
|
+
y: Number(Math.min(y, zeroY).toFixed(1)),
|
|
7942
|
+
width: Number((barWidth * 0.76).toFixed(1)),
|
|
7943
|
+
height: Number(Math.max(1, Math.abs(zeroY - y)).toFixed(1)),
|
|
7656
7944
|
fill: color,
|
|
7657
7945
|
"data-index": index
|
|
7658
7946
|
});
|
|
7659
7947
|
});
|
|
7660
|
-
}
|
|
7948
|
+
});
|
|
7949
|
+
} else {
|
|
7950
|
+
chart.series.forEach((series, seriesIndex) => {
|
|
7951
|
+
const color = series.color || colors[seriesIndex % colors.length];
|
|
7952
|
+
const step = series.values.length > 1 ? plot.width / (series.values.length - 1) : plot.width;
|
|
7953
|
+
const lineSpan = axisMax - axisMin || span;
|
|
7954
|
+
const points = series.values.map((value, index) => ({
|
|
7955
|
+
x: plot.x + index * step,
|
|
7956
|
+
y: plot.y + plot.height - (value - axisMin) / lineSpan * plot.height
|
|
7957
|
+
}));
|
|
7661
7958
|
appendSvg(svg, "polyline", {
|
|
7662
7959
|
points: points.map((point) => `${point.x.toFixed(1)},${point.y.toFixed(1)}`).join(" "),
|
|
7663
7960
|
fill: "none",
|
|
@@ -7669,11 +7966,49 @@ function renderChartSvg(chart) {
|
|
|
7669
7966
|
for (const point of points.slice(0, 80)) {
|
|
7670
7967
|
appendSvg(svg, "circle", { cx: point.x, cy: point.y, r: 3, fill: color });
|
|
7671
7968
|
}
|
|
7672
|
-
}
|
|
7673
|
-
|
|
7674
|
-
|
|
7969
|
+
});
|
|
7970
|
+
}
|
|
7971
|
+
appendChartLegend(svg, chart, colors, 348);
|
|
7675
7972
|
return svg;
|
|
7676
7973
|
}
|
|
7974
|
+
function appendChartLegend(svg, chart, colors, y) {
|
|
7975
|
+
const itemWidth = 86;
|
|
7976
|
+
const startX = 320 - chart.series.length * itemWidth / 2;
|
|
7977
|
+
chart.series.forEach((series, seriesIndex) => {
|
|
7978
|
+
const color = series.color || colors[seriesIndex % colors.length];
|
|
7979
|
+
appendLegend(svg, series.name, color, startX + seriesIndex * itemWidth, y);
|
|
7980
|
+
});
|
|
7981
|
+
}
|
|
7982
|
+
function createChartAxisScale(max, min) {
|
|
7983
|
+
const axisMin = Math.min(0, min);
|
|
7984
|
+
const positiveMax = Math.max(1, max);
|
|
7985
|
+
const step = niceChartStep((positiveMax - axisMin) / 5);
|
|
7986
|
+
let axisMax = Math.ceil(positiveMax / step) * step;
|
|
7987
|
+
if (axisMax <= positiveMax) {
|
|
7988
|
+
axisMax += step;
|
|
7989
|
+
}
|
|
7990
|
+
const ticks = [];
|
|
7991
|
+
for (let value = axisMin; value <= axisMax + step / 2; value += step) {
|
|
7992
|
+
ticks.push(Number(value.toFixed(6)));
|
|
7993
|
+
}
|
|
7994
|
+
return { min: axisMin, max: axisMax, ticks };
|
|
7995
|
+
}
|
|
7996
|
+
function niceChartStep(rawStep) {
|
|
7997
|
+
if (rawStep <= 0) {
|
|
7998
|
+
return 1;
|
|
7999
|
+
}
|
|
8000
|
+
const magnitude = 10 ** Math.floor(Math.log10(rawStep));
|
|
8001
|
+
const normalized = rawStep / magnitude;
|
|
8002
|
+
const nice = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;
|
|
8003
|
+
return nice * magnitude;
|
|
8004
|
+
}
|
|
8005
|
+
function formatChartTick(value) {
|
|
8006
|
+
const rounded = Math.abs(value) < 1 ? Number(value.toFixed(1)) : Number(value.toFixed(0));
|
|
8007
|
+
return String(rounded);
|
|
8008
|
+
}
|
|
8009
|
+
function truncateChartLabel(value) {
|
|
8010
|
+
return value.length > 10 ? `${value.slice(0, 10)}...` : value;
|
|
8011
|
+
}
|
|
7677
8012
|
function appendLegend(svg, label, color, x, y) {
|
|
7678
8013
|
appendSvg(svg, "rect", { x, y: y - 10, width: 12, height: 12, rx: 2, fill: color });
|
|
7679
8014
|
const text = appendSvg(svg, "text", { x: x + 18, y, class: "ofv-chart-label" });
|
|
@@ -8407,7 +8742,7 @@ function renderOpenDocumentPresentationXml(panel, xml) {
|
|
|
8407
8742
|
renderPresentationInsight(panel, inspectOpenDocumentPresentation("FODP \u6F14\u793A\u6587\u7A3F", xml, 0));
|
|
8408
8743
|
renderOpenDocumentPresentation(panel, "FODP \u6F14\u793A\u6587\u7A3F", xml, []);
|
|
8409
8744
|
}
|
|
8410
|
-
async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
8745
|
+
async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit) {
|
|
8411
8746
|
let zip;
|
|
8412
8747
|
try {
|
|
8413
8748
|
zip = await import_jszip3.default.loadAsync(arrayBuffer);
|
|
@@ -8418,7 +8753,7 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
|
8418
8753
|
const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
|
|
8419
8754
|
const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
|
|
8420
8755
|
if (hasEntry("word/document.xml")) {
|
|
8421
|
-
await renderDocx(panel, arrayBuffer);
|
|
8756
|
+
await renderDocx(panel, arrayBuffer, fit);
|
|
8422
8757
|
return true;
|
|
8423
8758
|
}
|
|
8424
8759
|
if (hasEntry("xl/workbook.xml")) {
|
|
@@ -18870,7 +19205,7 @@ async function createPostScriptPreview(bytes, url, fileName, size, fit, toolbar)
|
|
|
18870
19205
|
preview.append(summary);
|
|
18871
19206
|
return { element: preview };
|
|
18872
19207
|
}
|
|
18873
|
-
async function createPdfCompatibleAiPreview(bytes, url, fileName, size, fit, toolbar, pdfOffset = 0) {
|
|
19208
|
+
async function createPdfCompatibleAiPreview(bytes, url, fileName, size, fit, toolbar, pdfOffset = 0, zoom = 1) {
|
|
18874
19209
|
const wrapper = document.createElement("div");
|
|
18875
19210
|
wrapper.className = "ofv-ai-pdf-preview";
|
|
18876
19211
|
let pdfUrl = url;
|
|
@@ -18885,6 +19220,7 @@ async function createPdfCompatibleAiPreview(bytes, url, fileName, size, fit, too
|
|
|
18885
19220
|
viewport: wrapper,
|
|
18886
19221
|
size,
|
|
18887
19222
|
fit,
|
|
19223
|
+
zoom,
|
|
18888
19224
|
toolbar,
|
|
18889
19225
|
fallbackTitle: "AI PDF \u517C\u5BB9\u9884\u89C8\u5931\u8D25",
|
|
18890
19226
|
revokeUrlOnDestroy: false
|