@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.js
CHANGED
|
@@ -553,6 +553,48 @@ function revokeObjectUrl(url, isExternal) {
|
|
|
553
553
|
}
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
+
// src/messages.ts
|
|
557
|
+
var defaultMessages = {
|
|
558
|
+
"zh-CN": {
|
|
559
|
+
loading: "\u6B63\u5728\u52A0\u8F7D\u9884\u89C8...",
|
|
560
|
+
unsupportedTitle: "\u5F53\u524D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u5728\u7EBF\u9884\u89C8",
|
|
561
|
+
downloadTitle: "\u5F53\u524D\u6587\u4EF6\u53EF\u4E0B\u8F7D\u540E\u67E5\u770B",
|
|
562
|
+
downloadFile: "\u4E0B\u8F7D\u6587\u4EF6",
|
|
563
|
+
file: "\u6587\u4EF6",
|
|
564
|
+
unnamedFile: "\u672A\u547D\u540D\u6587\u4EF6",
|
|
565
|
+
format: "\u683C\u5F0F",
|
|
566
|
+
unknown: "\u672A\u77E5",
|
|
567
|
+
mime: "MIME",
|
|
568
|
+
undeclared: "\u672A\u58F0\u660E",
|
|
569
|
+
size: "\u5927\u5C0F",
|
|
570
|
+
source: "\u6765\u6E90",
|
|
571
|
+
remoteUrl: "\u8FDC\u7A0B URL",
|
|
572
|
+
localFile: "\u672C\u5730/\u5185\u5B58\u6587\u4EF6"
|
|
573
|
+
},
|
|
574
|
+
"en-US": {
|
|
575
|
+
loading: "Loading preview...",
|
|
576
|
+
unsupportedTitle: "Preview is not available for this file",
|
|
577
|
+
downloadTitle: "This file can be downloaded and opened locally",
|
|
578
|
+
downloadFile: "Download file",
|
|
579
|
+
file: "File",
|
|
580
|
+
unnamedFile: "Untitled file",
|
|
581
|
+
format: "Format",
|
|
582
|
+
unknown: "Unknown",
|
|
583
|
+
mime: "MIME",
|
|
584
|
+
undeclared: "Not declared",
|
|
585
|
+
size: "Size",
|
|
586
|
+
source: "Source",
|
|
587
|
+
remoteUrl: "Remote URL",
|
|
588
|
+
localFile: "Local or in-memory file"
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
function resolveMessages(options) {
|
|
592
|
+
return {
|
|
593
|
+
...defaultMessages[options.locale || "zh-CN"],
|
|
594
|
+
...options.messages
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
556
598
|
// src/plugins/fallback.ts
|
|
557
599
|
function fallbackPlugin() {
|
|
558
600
|
return {
|
|
@@ -570,12 +612,12 @@ function fallbackPlugin() {
|
|
|
570
612
|
const panel = document.createElement("div");
|
|
571
613
|
panel.className = "ofv-fallback";
|
|
572
614
|
const title = document.createElement("strong");
|
|
573
|
-
title.textContent = ctx.options.fallback === "download" ?
|
|
574
|
-
const meta = createFallbackMeta(ctx.file);
|
|
615
|
+
title.textContent = ctx.options.fallback === "download" ? ctx.options.messages.downloadTitle : ctx.options.messages.unsupportedTitle;
|
|
616
|
+
const meta = createFallbackMeta(ctx.file, ctx.options.messages);
|
|
575
617
|
const download = document.createElement("a");
|
|
576
618
|
download.href = url;
|
|
577
619
|
download.download = ctx.file.name;
|
|
578
|
-
download.textContent =
|
|
620
|
+
download.textContent = ctx.options.messages.downloadFile;
|
|
579
621
|
panel.append(title, meta, download);
|
|
580
622
|
ctx.viewport.classList.add("ofv-center");
|
|
581
623
|
ctx.viewport.append(panel);
|
|
@@ -591,14 +633,14 @@ function fallbackPlugin() {
|
|
|
591
633
|
}
|
|
592
634
|
};
|
|
593
635
|
}
|
|
594
|
-
function createFallbackMeta(file) {
|
|
636
|
+
function createFallbackMeta(file, messages) {
|
|
595
637
|
const meta = document.createElement("dl");
|
|
596
638
|
meta.className = "ofv-fallback-meta";
|
|
597
|
-
appendFallbackMeta(meta,
|
|
598
|
-
appendFallbackMeta(meta,
|
|
599
|
-
appendFallbackMeta(meta,
|
|
600
|
-
appendFallbackMeta(meta,
|
|
601
|
-
appendFallbackMeta(meta,
|
|
639
|
+
appendFallbackMeta(meta, messages.file, file.name || messages.unnamedFile);
|
|
640
|
+
appendFallbackMeta(meta, messages.format, file.extension ? `.${file.extension}` : messages.unknown);
|
|
641
|
+
appendFallbackMeta(meta, messages.mime, file.mimeType || messages.undeclared);
|
|
642
|
+
appendFallbackMeta(meta, messages.size, file.size === void 0 ? messages.unknown : formatFallbackBytes(file.size));
|
|
643
|
+
appendFallbackMeta(meta, messages.source, file.url ? messages.remoteUrl : messages.localFile);
|
|
602
644
|
return meta;
|
|
603
645
|
}
|
|
604
646
|
function appendFallbackMeta(parent, label, value) {
|
|
@@ -656,15 +698,17 @@ function createViewer(options) {
|
|
|
656
698
|
host.append(status, viewport);
|
|
657
699
|
container.replaceChildren(host);
|
|
658
700
|
const normalizedOptions = {
|
|
701
|
+
...options,
|
|
659
702
|
fit: options.fit || "contain",
|
|
660
703
|
fallback: options.fallback || "inline",
|
|
661
|
-
|
|
704
|
+
zoom: normalizeInitialZoom(options.zoom),
|
|
705
|
+
messages: resolveMessages(options)
|
|
662
706
|
};
|
|
663
707
|
let destroyed = false;
|
|
664
708
|
let renderToken = 0;
|
|
665
709
|
const setLoading = (loading) => {
|
|
666
710
|
status.hidden = !loading;
|
|
667
|
-
status.textContent = loading ?
|
|
711
|
+
status.textContent = loading ? normalizedOptions.messages.loading : "";
|
|
668
712
|
};
|
|
669
713
|
const setError = (error) => {
|
|
670
714
|
status.hidden = false;
|
|
@@ -820,6 +864,9 @@ function clampIndex(index, length) {
|
|
|
820
864
|
}
|
|
821
865
|
return Math.min(Math.max(index, 0), length - 1);
|
|
822
866
|
}
|
|
867
|
+
function normalizeInitialZoom(zoom) {
|
|
868
|
+
return typeof zoom === "number" && Number.isFinite(zoom) && zoom > 0 ? zoom : 1;
|
|
869
|
+
}
|
|
823
870
|
function applyTheme(container, theme) {
|
|
824
871
|
const media = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
825
872
|
const classes = ["ofv-theme-light", "ofv-theme-dark"];
|
|
@@ -1696,6 +1743,98 @@ async function findPlugin(plugins, file) {
|
|
|
1696
1743
|
return fallbackPlugin();
|
|
1697
1744
|
}
|
|
1698
1745
|
|
|
1746
|
+
// src/plugins/utils.ts
|
|
1747
|
+
async function readArrayBuffer(file) {
|
|
1748
|
+
if (file.source instanceof ArrayBuffer) {
|
|
1749
|
+
return file.source;
|
|
1750
|
+
}
|
|
1751
|
+
if (file.blob) {
|
|
1752
|
+
return file.blob.arrayBuffer();
|
|
1753
|
+
}
|
|
1754
|
+
if (typeof file.source === "string") {
|
|
1755
|
+
const response = await fetch(file.source);
|
|
1756
|
+
if (!response.ok) {
|
|
1757
|
+
throw new Error(`Failed to fetch file: ${response.status}`);
|
|
1758
|
+
}
|
|
1759
|
+
return response.arrayBuffer();
|
|
1760
|
+
}
|
|
1761
|
+
throw new Error("Unsupported file source.");
|
|
1762
|
+
}
|
|
1763
|
+
async function readTextFile(file) {
|
|
1764
|
+
const decode = (buffer) => decodeTextBuffer(buffer);
|
|
1765
|
+
if (typeof file.source === "string") {
|
|
1766
|
+
const response = await fetch(file.source);
|
|
1767
|
+
if (!response.ok) {
|
|
1768
|
+
throw new Error(`Failed to fetch text file: ${response.status}`);
|
|
1769
|
+
}
|
|
1770
|
+
return decode(await response.arrayBuffer());
|
|
1771
|
+
}
|
|
1772
|
+
if (file.blob) {
|
|
1773
|
+
return decode(await file.blob.arrayBuffer());
|
|
1774
|
+
}
|
|
1775
|
+
if (file.source instanceof ArrayBuffer) {
|
|
1776
|
+
return decode(file.source);
|
|
1777
|
+
}
|
|
1778
|
+
return String(file.source);
|
|
1779
|
+
}
|
|
1780
|
+
function createPanel(className = "") {
|
|
1781
|
+
const panel = document.createElement("div");
|
|
1782
|
+
panel.className = `ofv-panel ${className}`.trim();
|
|
1783
|
+
return panel;
|
|
1784
|
+
}
|
|
1785
|
+
function getInitialZoom(ctx, min = 0.1, max = 8) {
|
|
1786
|
+
return Math.min(max, Math.max(min, ctx.options.zoom));
|
|
1787
|
+
}
|
|
1788
|
+
function createSection(title) {
|
|
1789
|
+
const section = document.createElement("section");
|
|
1790
|
+
section.className = "ofv-section";
|
|
1791
|
+
const heading = document.createElement("h3");
|
|
1792
|
+
heading.textContent = title;
|
|
1793
|
+
section.append(heading);
|
|
1794
|
+
return section;
|
|
1795
|
+
}
|
|
1796
|
+
function appendMeta(parent, label, value) {
|
|
1797
|
+
const row = document.createElement("div");
|
|
1798
|
+
row.className = "ofv-meta-row";
|
|
1799
|
+
const key = document.createElement("span");
|
|
1800
|
+
key.textContent = label;
|
|
1801
|
+
const content = document.createElement("strong");
|
|
1802
|
+
content.textContent = String(value);
|
|
1803
|
+
row.append(key, content);
|
|
1804
|
+
parent.append(row);
|
|
1805
|
+
}
|
|
1806
|
+
function decodeTextBuffer(buffer) {
|
|
1807
|
+
return decodeTextBytes(new Uint8Array(buffer));
|
|
1808
|
+
}
|
|
1809
|
+
function decodeTextBytes(bytes) {
|
|
1810
|
+
if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
|
|
1811
|
+
return new TextDecoder("utf-8").decode(bytes.subarray(3));
|
|
1812
|
+
}
|
|
1813
|
+
if (bytes.length >= 2) {
|
|
1814
|
+
if (bytes[0] === 255 && bytes[1] === 254) {
|
|
1815
|
+
return new TextDecoder("utf-16le").decode(bytes.subarray(2));
|
|
1816
|
+
}
|
|
1817
|
+
if (bytes[0] === 254 && bytes[1] === 255) {
|
|
1818
|
+
return new TextDecoder("utf-16be").decode(bytes.subarray(2));
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
try {
|
|
1822
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
1823
|
+
} catch {
|
|
1824
|
+
return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
function decodeWithFallback(bytes, encoding) {
|
|
1828
|
+
try {
|
|
1829
|
+
return new TextDecoder(encoding).decode(bytes);
|
|
1830
|
+
} catch {
|
|
1831
|
+
return void 0;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
function resolveFormat(file, mimeMap) {
|
|
1835
|
+
return file.extension || mimeMap[file.mimeType] || "";
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1699
1838
|
// src/plugins/image.ts
|
|
1700
1839
|
var imageExtensions = /* @__PURE__ */ new Set([
|
|
1701
1840
|
"jpg",
|
|
@@ -1738,12 +1877,12 @@ function imagePlugin() {
|
|
|
1738
1877
|
let url = "";
|
|
1739
1878
|
let convertedBlob = null;
|
|
1740
1879
|
let isExternal = Boolean(ctx.file.url);
|
|
1741
|
-
let
|
|
1880
|
+
let tiffSource = null;
|
|
1742
1881
|
if (isTiff) {
|
|
1743
1882
|
ctx.setLoading(true);
|
|
1744
1883
|
try {
|
|
1745
1884
|
const bytes = await sourceBytesPromise;
|
|
1746
|
-
|
|
1885
|
+
tiffSource = await createTiffPreview(bytes, ctx.file.name);
|
|
1747
1886
|
} catch (err) {
|
|
1748
1887
|
console.error("TIFF image conversion failed:", err);
|
|
1749
1888
|
url = createObjectUrl(ctx.file);
|
|
@@ -1803,13 +1942,16 @@ function imagePlugin() {
|
|
|
1803
1942
|
if (url) {
|
|
1804
1943
|
image.src = url;
|
|
1805
1944
|
}
|
|
1806
|
-
const visual =
|
|
1807
|
-
if (
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1945
|
+
const visual = tiffSource || image;
|
|
1946
|
+
if (tiffSource) {
|
|
1947
|
+
tiffSource.classList.add("ofv-image-content");
|
|
1948
|
+
if (tiffSource.classList.contains("ofv-tiff-pages")) {
|
|
1949
|
+
stage.classList.add("ofv-image-stage-pages");
|
|
1950
|
+
}
|
|
1811
1951
|
}
|
|
1812
|
-
|
|
1952
|
+
const visualBox = document.createElement("div");
|
|
1953
|
+
visualBox.className = "ofv-image-scrollbox";
|
|
1954
|
+
let scale = getInitialZoom(ctx);
|
|
1813
1955
|
let rotation = 0;
|
|
1814
1956
|
let offsetX = 0;
|
|
1815
1957
|
let offsetY = 0;
|
|
@@ -1822,7 +1964,23 @@ function imagePlugin() {
|
|
|
1822
1964
|
let previewAvailable = true;
|
|
1823
1965
|
const zoomLabel = document.createElement("span");
|
|
1824
1966
|
zoomLabel.className = "ofv-image-zoom";
|
|
1967
|
+
const updateScrollBox = () => {
|
|
1968
|
+
if (visual.classList.contains("ofv-tiff-pages")) {
|
|
1969
|
+
visualBox.style.removeProperty("width");
|
|
1970
|
+
visualBox.style.removeProperty("height");
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
const baseWidth = visual.offsetWidth || visual.getBoundingClientRect().width || visual.naturalWidth || 0;
|
|
1974
|
+
const baseHeight = visual.offsetHeight || visual.getBoundingClientRect().height || visual.naturalHeight || 0;
|
|
1975
|
+
if (baseWidth > 0) {
|
|
1976
|
+
visualBox.style.width = `${Math.ceil(baseWidth * scale)}px`;
|
|
1977
|
+
}
|
|
1978
|
+
if (baseHeight > 0) {
|
|
1979
|
+
visualBox.style.height = `${Math.ceil(baseHeight * scale)}px`;
|
|
1980
|
+
}
|
|
1981
|
+
};
|
|
1825
1982
|
const updateTransform = () => {
|
|
1983
|
+
updateScrollBox();
|
|
1826
1984
|
visual.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale}) rotate(${rotation}deg)`;
|
|
1827
1985
|
zoomLabel.textContent = `${Math.round(scale * 100)}%`;
|
|
1828
1986
|
ctx.toolbar?.setZoom(previewAvailable ? scale : void 0);
|
|
@@ -1942,11 +2100,13 @@ function imagePlugin() {
|
|
|
1942
2100
|
stage.addEventListener("lostpointercapture", onLostPointerCapture);
|
|
1943
2101
|
stage.addEventListener("pointerleave", onPointerLeave);
|
|
1944
2102
|
stage.addEventListener("wheel", onWheel, { passive: false });
|
|
1945
|
-
if (!
|
|
2103
|
+
if (!tiffSource) {
|
|
1946
2104
|
image.addEventListener("error", showImageFallback);
|
|
2105
|
+
image.addEventListener("load", updateScrollBox);
|
|
1947
2106
|
}
|
|
1948
2107
|
window.addEventListener("blur", onWindowBlur);
|
|
1949
|
-
|
|
2108
|
+
visualBox.append(visual);
|
|
2109
|
+
stage.append(visualBox);
|
|
1950
2110
|
wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
|
|
1951
2111
|
ctx.viewport.append(wrapper);
|
|
1952
2112
|
updateTransform();
|
|
@@ -1984,7 +2144,12 @@ function imagePlugin() {
|
|
|
1984
2144
|
},
|
|
1985
2145
|
resize(size) {
|
|
1986
2146
|
visual.style.maxWidth = `${size.width}px`;
|
|
1987
|
-
visual.
|
|
2147
|
+
if (visual.classList.contains("ofv-tiff-pages")) {
|
|
2148
|
+
visual.style.removeProperty("max-height");
|
|
2149
|
+
} else {
|
|
2150
|
+
visual.style.maxHeight = `${Math.max(0, size.height - controls.offsetHeight)}px`;
|
|
2151
|
+
}
|
|
2152
|
+
updateScrollBox();
|
|
1988
2153
|
},
|
|
1989
2154
|
destroy() {
|
|
1990
2155
|
ctx.toolbar?.setZoom(void 0);
|
|
@@ -1999,6 +2164,7 @@ function imagePlugin() {
|
|
|
1999
2164
|
stage.removeEventListener("pointerleave", onPointerLeave);
|
|
2000
2165
|
stage.removeEventListener("wheel", onWheel);
|
|
2001
2166
|
image.removeEventListener("error", showImageFallback);
|
|
2167
|
+
image.removeEventListener("load", updateScrollBox);
|
|
2002
2168
|
window.removeEventListener("blur", onWindowBlur);
|
|
2003
2169
|
finishDrag();
|
|
2004
2170
|
wrapper.remove();
|
|
@@ -2012,33 +2178,64 @@ function imagePlugin() {
|
|
|
2012
2178
|
}
|
|
2013
2179
|
};
|
|
2014
2180
|
}
|
|
2015
|
-
async function
|
|
2181
|
+
async function createTiffPreview(bytes, fileName) {
|
|
2016
2182
|
if (bytes.byteLength === 0) {
|
|
2017
2183
|
throw new Error("\u65E0\u6CD5\u8BFB\u53D6 TIFF \u6587\u4EF6\u5185\u5BB9\u3002");
|
|
2018
2184
|
}
|
|
2019
2185
|
const UTIF = await import("utif");
|
|
2020
2186
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2021
2187
|
const ifds = UTIF.decode(buffer);
|
|
2022
|
-
const
|
|
2023
|
-
if (
|
|
2188
|
+
const imageIfds = ifds.filter((item) => hasTiffDimensions(item));
|
|
2189
|
+
if (imageIfds.length === 0) {
|
|
2024
2190
|
throw new Error("TIFF \u6587\u4EF6\u6CA1\u6709\u53EF\u89E3\u7801\u7684\u56FE\u50CF\u76EE\u5F55\u3002");
|
|
2025
2191
|
}
|
|
2192
|
+
const pages = imageIfds.map((ifd, index) => createTiffPage(UTIF, buffer, ifd, index, imageIfds.length, fileName));
|
|
2193
|
+
if (pages.length === 1) {
|
|
2194
|
+
return pages[0].canvas;
|
|
2195
|
+
}
|
|
2196
|
+
const container = document.createElement("div");
|
|
2197
|
+
container.className = "ofv-tiff-pages";
|
|
2198
|
+
container.setAttribute("role", "group");
|
|
2199
|
+
container.setAttribute("aria-label", `${fileName}\uFF0C\u5171 ${pages.length} \u9875`);
|
|
2200
|
+
for (const page of pages) {
|
|
2201
|
+
const figure = document.createElement("figure");
|
|
2202
|
+
figure.className = "ofv-tiff-page";
|
|
2203
|
+
const caption = document.createElement("figcaption");
|
|
2204
|
+
caption.textContent = `\u7B2C ${page.index + 1} / ${pages.length} \u9875 \xB7 ${page.width} x ${page.height}px`;
|
|
2205
|
+
figure.append(page.canvas, caption);
|
|
2206
|
+
container.append(figure);
|
|
2207
|
+
}
|
|
2208
|
+
return container;
|
|
2209
|
+
}
|
|
2210
|
+
function createTiffPage(UTIF, buffer, ifd, index, total, fileName) {
|
|
2026
2211
|
UTIF.decodeImage(buffer, ifd);
|
|
2027
2212
|
const rgba = UTIF.toRGBA8(ifd);
|
|
2028
|
-
const width =
|
|
2029
|
-
const height = Number(ifd.height || ifd.t257 || 0);
|
|
2213
|
+
const { width, height } = getTiffDimensions(ifd);
|
|
2030
2214
|
if (!width || !height || rgba.length < width * height * 4) {
|
|
2031
2215
|
throw new Error("TIFF \u56FE\u50CF\u50CF\u7D20\u6570\u636E\u4E0D\u5B8C\u6574\u3002");
|
|
2032
2216
|
}
|
|
2033
2217
|
const canvas = document.createElement("canvas");
|
|
2218
|
+
canvas.className = "ofv-tiff-canvas";
|
|
2034
2219
|
canvas.width = width;
|
|
2035
2220
|
canvas.height = height;
|
|
2221
|
+
canvas.setAttribute("role", "img");
|
|
2222
|
+
canvas.setAttribute("aria-label", total > 1 ? `${fileName} \u7B2C ${index + 1} \u9875` : fileName);
|
|
2036
2223
|
const context = canvas.getContext("2d");
|
|
2037
2224
|
if (!context) {
|
|
2038
2225
|
throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Canvas 2D\uFF0C\u65E0\u6CD5\u5C55\u793A TIFF\u3002");
|
|
2039
2226
|
}
|
|
2040
2227
|
context.putImageData(new ImageData(new Uint8ClampedArray(rgba), width, height), 0, 0);
|
|
2041
|
-
return canvas;
|
|
2228
|
+
return { canvas, width, height, index };
|
|
2229
|
+
}
|
|
2230
|
+
function hasTiffDimensions(ifd) {
|
|
2231
|
+
const { width, height } = getTiffDimensions(ifd);
|
|
2232
|
+
return width > 0 && height > 0;
|
|
2233
|
+
}
|
|
2234
|
+
function getTiffDimensions(ifd) {
|
|
2235
|
+
return {
|
|
2236
|
+
width: Number(ifd.width || ifd.t256 || 0),
|
|
2237
|
+
height: Number(ifd.height || ifd.t257 || 0)
|
|
2238
|
+
};
|
|
2042
2239
|
}
|
|
2043
2240
|
function createImageFallback(fileName, url) {
|
|
2044
2241
|
const fallback = document.createElement("div");
|
|
@@ -2367,6 +2564,7 @@ function parseTiffInfo(bytes) {
|
|
|
2367
2564
|
return { format: magic === 43 ? "BigTIFF" : "TIFF", note: "IFD \u504F\u79FB\u8D85\u51FA\u6587\u4EF6\u8303\u56F4" };
|
|
2368
2565
|
}
|
|
2369
2566
|
const count = magic === 43 ? readTiffUint64AsNumber(bytes, ifdOffset, littleEndian) : readTiffUint16(bytes, ifdOffset, littleEndian);
|
|
2567
|
+
const imageCount = countTiffIfds(bytes, ifdOffset, magic === 43, littleEndian);
|
|
2370
2568
|
const entrySize = magic === 43 ? 20 : 12;
|
|
2371
2569
|
const entriesStart = ifdOffset + (magic === 43 ? 8 : 2);
|
|
2372
2570
|
let width;
|
|
@@ -2393,9 +2591,26 @@ function parseTiffInfo(bytes) {
|
|
|
2393
2591
|
height,
|
|
2394
2592
|
bitDepth: bitDepth ? `${bitDepth} bit` : void 0,
|
|
2395
2593
|
color: compression ? tiffCompressionName(compression) : void 0,
|
|
2396
|
-
count:
|
|
2594
|
+
count: imageCount || void 0
|
|
2397
2595
|
};
|
|
2398
2596
|
}
|
|
2597
|
+
function countTiffIfds(bytes, firstOffset, bigTiff, littleEndian) {
|
|
2598
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2599
|
+
let offset = firstOffset;
|
|
2600
|
+
let count = 0;
|
|
2601
|
+
while (Number.isFinite(offset) && offset > 0 && offset < bytes.length && !seen.has(offset) && count < 512) {
|
|
2602
|
+
seen.add(offset);
|
|
2603
|
+
const entryCount = bigTiff ? readTiffUint64AsNumber(bytes, offset, littleEndian) : readTiffUint16(bytes, offset, littleEndian);
|
|
2604
|
+
const entrySize = bigTiff ? 20 : 12;
|
|
2605
|
+
const nextOffsetPosition = offset + (bigTiff ? 8 : 2) + entryCount * entrySize;
|
|
2606
|
+
if (!Number.isFinite(entryCount) || nextOffsetPosition + (bigTiff ? 8 : 4) > bytes.length) {
|
|
2607
|
+
break;
|
|
2608
|
+
}
|
|
2609
|
+
count++;
|
|
2610
|
+
offset = bigTiff ? readTiffUint64AsNumber(bytes, nextOffsetPosition, littleEndian) : readTiffUint32(bytes, nextOffsetPosition, littleEndian);
|
|
2611
|
+
}
|
|
2612
|
+
return count;
|
|
2613
|
+
}
|
|
2399
2614
|
function readTiffInlineValue(bytes, offset, type, littleEndian) {
|
|
2400
2615
|
if (offset + 4 > bytes.length) {
|
|
2401
2616
|
return void 0;
|
|
@@ -3637,95 +3852,6 @@ function readAiffExtended(bytes, offset) {
|
|
|
3637
3852
|
return mantissa * Math.pow(2, exponent - 16383 - 63);
|
|
3638
3853
|
}
|
|
3639
3854
|
|
|
3640
|
-
// src/plugins/utils.ts
|
|
3641
|
-
async function readArrayBuffer(file) {
|
|
3642
|
-
if (file.source instanceof ArrayBuffer) {
|
|
3643
|
-
return file.source;
|
|
3644
|
-
}
|
|
3645
|
-
if (file.blob) {
|
|
3646
|
-
return file.blob.arrayBuffer();
|
|
3647
|
-
}
|
|
3648
|
-
if (typeof file.source === "string") {
|
|
3649
|
-
const response = await fetch(file.source);
|
|
3650
|
-
if (!response.ok) {
|
|
3651
|
-
throw new Error(`Failed to fetch file: ${response.status}`);
|
|
3652
|
-
}
|
|
3653
|
-
return response.arrayBuffer();
|
|
3654
|
-
}
|
|
3655
|
-
throw new Error("Unsupported file source.");
|
|
3656
|
-
}
|
|
3657
|
-
async function readTextFile(file) {
|
|
3658
|
-
const decode = (buffer) => decodeTextBuffer(buffer);
|
|
3659
|
-
if (typeof file.source === "string") {
|
|
3660
|
-
const response = await fetch(file.source);
|
|
3661
|
-
if (!response.ok) {
|
|
3662
|
-
throw new Error(`Failed to fetch text file: ${response.status}`);
|
|
3663
|
-
}
|
|
3664
|
-
return decode(await response.arrayBuffer());
|
|
3665
|
-
}
|
|
3666
|
-
if (file.blob) {
|
|
3667
|
-
return decode(await file.blob.arrayBuffer());
|
|
3668
|
-
}
|
|
3669
|
-
if (file.source instanceof ArrayBuffer) {
|
|
3670
|
-
return decode(file.source);
|
|
3671
|
-
}
|
|
3672
|
-
return String(file.source);
|
|
3673
|
-
}
|
|
3674
|
-
function createPanel(className = "") {
|
|
3675
|
-
const panel = document.createElement("div");
|
|
3676
|
-
panel.className = `ofv-panel ${className}`.trim();
|
|
3677
|
-
return panel;
|
|
3678
|
-
}
|
|
3679
|
-
function createSection(title) {
|
|
3680
|
-
const section = document.createElement("section");
|
|
3681
|
-
section.className = "ofv-section";
|
|
3682
|
-
const heading = document.createElement("h3");
|
|
3683
|
-
heading.textContent = title;
|
|
3684
|
-
section.append(heading);
|
|
3685
|
-
return section;
|
|
3686
|
-
}
|
|
3687
|
-
function appendMeta(parent, label, value) {
|
|
3688
|
-
const row = document.createElement("div");
|
|
3689
|
-
row.className = "ofv-meta-row";
|
|
3690
|
-
const key = document.createElement("span");
|
|
3691
|
-
key.textContent = label;
|
|
3692
|
-
const content = document.createElement("strong");
|
|
3693
|
-
content.textContent = String(value);
|
|
3694
|
-
row.append(key, content);
|
|
3695
|
-
parent.append(row);
|
|
3696
|
-
}
|
|
3697
|
-
function decodeTextBuffer(buffer) {
|
|
3698
|
-
return decodeTextBytes(new Uint8Array(buffer));
|
|
3699
|
-
}
|
|
3700
|
-
function decodeTextBytes(bytes) {
|
|
3701
|
-
if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
|
|
3702
|
-
return new TextDecoder("utf-8").decode(bytes.subarray(3));
|
|
3703
|
-
}
|
|
3704
|
-
if (bytes.length >= 2) {
|
|
3705
|
-
if (bytes[0] === 255 && bytes[1] === 254) {
|
|
3706
|
-
return new TextDecoder("utf-16le").decode(bytes.subarray(2));
|
|
3707
|
-
}
|
|
3708
|
-
if (bytes[0] === 254 && bytes[1] === 255) {
|
|
3709
|
-
return new TextDecoder("utf-16be").decode(bytes.subarray(2));
|
|
3710
|
-
}
|
|
3711
|
-
}
|
|
3712
|
-
try {
|
|
3713
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
3714
|
-
} catch {
|
|
3715
|
-
return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
|
|
3716
|
-
}
|
|
3717
|
-
}
|
|
3718
|
-
function decodeWithFallback(bytes, encoding) {
|
|
3719
|
-
try {
|
|
3720
|
-
return new TextDecoder(encoding).decode(bytes);
|
|
3721
|
-
} catch {
|
|
3722
|
-
return void 0;
|
|
3723
|
-
}
|
|
3724
|
-
}
|
|
3725
|
-
function resolveFormat(file, mimeMap) {
|
|
3726
|
-
return file.extension || mimeMap[file.mimeType] || "";
|
|
3727
|
-
}
|
|
3728
|
-
|
|
3729
3855
|
// src/plugins/text.ts
|
|
3730
3856
|
var langMap = {
|
|
3731
3857
|
js: "javascript",
|
|
@@ -3883,26 +4009,6 @@ var mimeLangMap = {
|
|
|
3883
4009
|
};
|
|
3884
4010
|
var MAX_HIGHLIGHT_CHARS = 18e4;
|
|
3885
4011
|
var MAX_RENDER_CHARS = 6e5;
|
|
3886
|
-
function loadPrismCss(theme) {
|
|
3887
|
-
const lightId = "ofv-prism-css-light";
|
|
3888
|
-
const darkId = "ofv-prism-css-dark";
|
|
3889
|
-
const activeId = theme === "dark" ? darkId : lightId;
|
|
3890
|
-
const inactiveId = theme === "dark" ? lightId : darkId;
|
|
3891
|
-
document.getElementById(inactiveId)?.remove();
|
|
3892
|
-
if (document.getElementById(activeId)) {
|
|
3893
|
-
return Promise.resolve();
|
|
3894
|
-
}
|
|
3895
|
-
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";
|
|
3896
|
-
return new Promise((resolve, reject) => {
|
|
3897
|
-
const link = document.createElement("link");
|
|
3898
|
-
link.id = activeId;
|
|
3899
|
-
link.rel = "stylesheet";
|
|
3900
|
-
link.href = href;
|
|
3901
|
-
link.onload = () => resolve();
|
|
3902
|
-
link.onerror = () => reject(new Error(`Failed to load Prism CSS: ${href}`));
|
|
3903
|
-
document.head.appendChild(link);
|
|
3904
|
-
});
|
|
3905
|
-
}
|
|
3906
4012
|
function textPlugin() {
|
|
3907
4013
|
return {
|
|
3908
4014
|
name: "text",
|
|
@@ -3926,7 +4032,6 @@ function textPlugin() {
|
|
|
3926
4032
|
}
|
|
3927
4033
|
};
|
|
3928
4034
|
}
|
|
3929
|
-
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";
|
|
3930
4035
|
if (isMarkdown) {
|
|
3931
4036
|
const [markedModule, PrismModule2, DOMPurifyModule] = await Promise.all([
|
|
3932
4037
|
import("marked"),
|
|
@@ -3947,7 +4052,6 @@ function textPlugin() {
|
|
|
3947
4052
|
try {
|
|
3948
4053
|
const codeBlocks = container.querySelectorAll("pre code");
|
|
3949
4054
|
if (codeBlocks.length > 0) {
|
|
3950
|
-
await loadPrismCss(isDark ? "dark" : "light");
|
|
3951
4055
|
codeBlocks.forEach((block) => {
|
|
3952
4056
|
const parent = block.parentElement;
|
|
3953
4057
|
if (parent && !parent.className.includes("language-")) {
|
|
@@ -4071,9 +4175,6 @@ function textPlugin() {
|
|
|
4071
4175
|
console.warn(`Prism failed to load language component for: ${lang}`, e);
|
|
4072
4176
|
}
|
|
4073
4177
|
}
|
|
4074
|
-
await loadPrismCss(isDark ? "dark" : "light").catch((error) => {
|
|
4075
|
-
console.warn("Prism CSS failed to load; rendering code without external theme:", error);
|
|
4076
|
-
});
|
|
4077
4178
|
const codeText = text.length > MAX_RENDER_CHARS ? text.slice(0, MAX_RENDER_CHARS) : text;
|
|
4078
4179
|
const totalLines = countLines(text);
|
|
4079
4180
|
const shownLines = countLines(codeText);
|
|
@@ -4194,7 +4295,7 @@ function textPlugin() {
|
|
|
4194
4295
|
};
|
|
4195
4296
|
}
|
|
4196
4297
|
function createTextZoomController(target, cssVariable, ctx) {
|
|
4197
|
-
let zoom =
|
|
4298
|
+
let zoom = getInitialZoom(ctx, 0.5, 3);
|
|
4198
4299
|
const apply = () => {
|
|
4199
4300
|
const normalized = Math.round(zoom * 100) / 100;
|
|
4200
4301
|
target.style.setProperty(cssVariable, String(normalized));
|
|
@@ -4504,6 +4605,7 @@ function pdfPlugin(options = {}) {
|
|
|
4504
4605
|
viewport: ctx.viewport,
|
|
4505
4606
|
size: ctx.size,
|
|
4506
4607
|
fit: ctx.options.fit,
|
|
4608
|
+
zoom: ctx.options.zoom,
|
|
4507
4609
|
toolbar: ctx.toolbar,
|
|
4508
4610
|
encryptedTitle: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
4509
4611
|
encryptedMessage: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
|
|
@@ -4600,16 +4702,17 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4600
4702
|
const baseViewport = page.getViewport({ scale: 1 });
|
|
4601
4703
|
pagesMeta.push({
|
|
4602
4704
|
width: baseViewport.width,
|
|
4603
|
-
height: baseViewport.height
|
|
4705
|
+
height: baseViewport.height,
|
|
4706
|
+
rotation: getPdfPageRotation(page)
|
|
4604
4707
|
});
|
|
4605
4708
|
} catch {
|
|
4606
|
-
pagesMeta.push({ width: 612, height: 792 });
|
|
4709
|
+
pagesMeta.push({ width: 612, height: 792, rotation: 0 });
|
|
4607
4710
|
}
|
|
4608
4711
|
}
|
|
4609
4712
|
const pageStates = [];
|
|
4610
4713
|
let observer = null;
|
|
4611
4714
|
let currentSize = options.size;
|
|
4612
|
-
let zoomFactor = 1;
|
|
4715
|
+
let zoomFactor = getInitialZoom({ options: { zoom: options.zoom ?? 1 } }, 0.25, 4);
|
|
4613
4716
|
let rotation = 0;
|
|
4614
4717
|
const updateSummary = () => {
|
|
4615
4718
|
renderPdfSummary(summary, pdfDocument.numPages, pagesMeta, options.fit, zoomFactor);
|
|
@@ -4638,7 +4741,7 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4638
4741
|
const page = await pdfDocument.getPage(pageIdx + 1);
|
|
4639
4742
|
const meta = pagesMeta[pageIdx];
|
|
4640
4743
|
const scale = options.fit === "actual" ? zoomFactor : Math.max(0.05, Math.min(5, getPdfAvailableWidth(size.width) / rotatedPdfWidth(meta, rotation) * zoomFactor));
|
|
4641
|
-
const viewport = page.getViewport({ scale, rotation });
|
|
4744
|
+
const viewport = page.getViewport({ scale, rotation: getPdfRenderRotation(meta, rotation) });
|
|
4642
4745
|
const outputScale = getPdfOutputScale();
|
|
4643
4746
|
const cssWidth = Math.floor(viewport.width);
|
|
4644
4747
|
const cssHeight = Math.floor(viewport.height);
|
|
@@ -4677,6 +4780,8 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4677
4780
|
const span = document.createElement("span");
|
|
4678
4781
|
span.textContent = str;
|
|
4679
4782
|
span.style.fontSize = `${fontHeight}px`;
|
|
4783
|
+
span.style.lineHeight = "1";
|
|
4784
|
+
span.style.height = `${fontHeight}px`;
|
|
4680
4785
|
span.style.fontFamily = item.fontName || "sans-serif";
|
|
4681
4786
|
span.style.left = `${tx[4]}px`;
|
|
4682
4787
|
span.style.top = `${tx[5] - fontHeight}px`;
|
|
@@ -4874,6 +4979,13 @@ function appendPdfSummary(parent, label, value) {
|
|
|
4874
4979
|
function normalizePdfRotation(value) {
|
|
4875
4980
|
return (value % 360 + 360) % 360;
|
|
4876
4981
|
}
|
|
4982
|
+
function getPdfPageRotation(page) {
|
|
4983
|
+
const rotation = Number(page.rotate);
|
|
4984
|
+
return Number.isFinite(rotation) ? normalizePdfRotation(rotation) : 0;
|
|
4985
|
+
}
|
|
4986
|
+
function getPdfRenderRotation(meta, userRotation) {
|
|
4987
|
+
return normalizePdfRotation((meta.rotation || 0) + userRotation);
|
|
4988
|
+
}
|
|
4877
4989
|
function isPdfRotatedSideways(rotation) {
|
|
4878
4990
|
const normalized = normalizePdfRotation(rotation);
|
|
4879
4991
|
return normalized === 90 || normalized === 270;
|
|
@@ -5844,13 +5956,13 @@ function officePlugin(options = {}) {
|
|
|
5844
5956
|
if (conversionContext && await shouldUseOfficeConversion(options, conversionContext)) {
|
|
5845
5957
|
delegatedInstance = await renderConvertedOfficePreview(panel, ctx, options, conversionContext);
|
|
5846
5958
|
} else if (packageFormat === "docx" && !fileIsDocx(extension)) {
|
|
5847
|
-
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
5959
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
|
|
5848
5960
|
} else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
|
|
5849
5961
|
await renderSheet(panel, arrayBuffer, "xlsx");
|
|
5850
5962
|
} else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
|
|
5851
5963
|
await renderPptx(panel, arrayBuffer);
|
|
5852
5964
|
} else if (fileIsDocx(extension)) {
|
|
5853
|
-
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
5965
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
|
|
5854
5966
|
} else if (extension === "rtf") {
|
|
5855
5967
|
renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
|
|
5856
5968
|
} else if (extension === "odt") {
|
|
@@ -5859,7 +5971,7 @@ function officePlugin(options = {}) {
|
|
|
5859
5971
|
renderOpenDocumentXml(panel, "FODT \u6587\u6863", await readTextFromBuffer(arrayBuffer));
|
|
5860
5972
|
} else if (extension === "fods") {
|
|
5861
5973
|
renderFlatOds(panel, await readTextFromBuffer(arrayBuffer));
|
|
5862
|
-
} else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension)) {
|
|
5974
|
+
} else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension, ctx.options.fit)) {
|
|
5863
5975
|
} else if (sheetExtensions.has(extension)) {
|
|
5864
5976
|
await renderSheet(panel, arrayBuffer, extension);
|
|
5865
5977
|
} else if (["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
|
|
@@ -5934,6 +6046,7 @@ async function renderConvertedOfficePreview(panel, ctx, options, conversionConte
|
|
|
5934
6046
|
viewport: panel,
|
|
5935
6047
|
size: ctx.size,
|
|
5936
6048
|
fit: ctx.options.fit,
|
|
6049
|
+
zoom: ctx.options.zoom,
|
|
5937
6050
|
toolbar: ctx.toolbar,
|
|
5938
6051
|
title: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
|
|
5939
6052
|
fallbackTitle: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
|
|
@@ -5997,7 +6110,7 @@ function createOfficeZoomController(panel, ctx) {
|
|
|
5997
6110
|
if (!canZoom) {
|
|
5998
6111
|
return void 0;
|
|
5999
6112
|
}
|
|
6000
|
-
let zoom =
|
|
6113
|
+
let zoom = getInitialZoom(ctx, 0.5, 3);
|
|
6001
6114
|
const apply = () => {
|
|
6002
6115
|
panel.style.setProperty("--ofv-office-zoom", String(zoom));
|
|
6003
6116
|
panel.dispatchEvent(new CustomEvent("ofv-office-zoom"));
|
|
@@ -6060,7 +6173,7 @@ async function detectPackagedOfficeFormat(arrayBuffer) {
|
|
|
6060
6173
|
}
|
|
6061
6174
|
return void 0;
|
|
6062
6175
|
}
|
|
6063
|
-
async function renderDocx(panel, arrayBuffer) {
|
|
6176
|
+
async function renderDocx(panel, arrayBuffer, fit) {
|
|
6064
6177
|
const content = document.createElement("div");
|
|
6065
6178
|
content.className = "ofv-docx-document";
|
|
6066
6179
|
const styleContainer = document.createElement("div");
|
|
@@ -6099,7 +6212,7 @@ async function renderDocx(panel, arrayBuffer) {
|
|
|
6099
6212
|
return () => void 0;
|
|
6100
6213
|
}
|
|
6101
6214
|
panel.append(content);
|
|
6102
|
-
disposeFit = fitDocxPages(content);
|
|
6215
|
+
disposeFit = fitDocxPages(content, fit);
|
|
6103
6216
|
return () => {
|
|
6104
6217
|
disposeFit?.();
|
|
6105
6218
|
styleContainer.remove();
|
|
@@ -6697,7 +6810,8 @@ function looksLikeDocxTextboxHeading(value) {
|
|
|
6697
6810
|
return text.length > 0 && text.length <= 12 && !/[0-9@.::]/.test(text);
|
|
6698
6811
|
}
|
|
6699
6812
|
async function normalizeDocxLayout(container, arrayBuffer) {
|
|
6700
|
-
const hints = await readDocxLayoutHints(arrayBuffer);
|
|
6813
|
+
const [hints, charts] = await Promise.all([readDocxLayoutHints(arrayBuffer), readDocxCharts(arrayBuffer)]);
|
|
6814
|
+
repairDocxChartPlaceholders(container, charts);
|
|
6701
6815
|
const pages = container.querySelectorAll("section.ofv-docx");
|
|
6702
6816
|
for (const page of pages) {
|
|
6703
6817
|
repairDocxShapeFills(page);
|
|
@@ -6712,6 +6826,75 @@ async function normalizeDocxLayout(container, arrayBuffer) {
|
|
|
6712
6826
|
}
|
|
6713
6827
|
}
|
|
6714
6828
|
}
|
|
6829
|
+
async function readDocxCharts(arrayBuffer) {
|
|
6830
|
+
try {
|
|
6831
|
+
const zip = await JSZip3.loadAsync(arrayBuffer);
|
|
6832
|
+
const documentXml = await zip.file("word/document.xml")?.async("text");
|
|
6833
|
+
const documentDoc = documentXml ? parseOfficeXml(documentXml) : void 0;
|
|
6834
|
+
if (!documentDoc) {
|
|
6835
|
+
return [];
|
|
6836
|
+
}
|
|
6837
|
+
const relationships = await readOfficeRelationships(zip, "word/document.xml");
|
|
6838
|
+
const chartDrawings = Array.from(documentDoc.getElementsByTagName("*")).filter((element) => element.localName === "inline" || element.localName === "anchor").map((element) => readDocxChartDrawing(element)).filter((item) => Boolean(item?.relationshipId));
|
|
6839
|
+
const charts = [];
|
|
6840
|
+
for (const [index, drawing] of chartDrawings.entries()) {
|
|
6841
|
+
const chartRel = relationships.find((rel) => rel.id === drawing.relationshipId && /\/chart$/i.test(rel.type));
|
|
6842
|
+
const chartPath = resolveOfficeRelationshipTarget("word/document.xml", chartRel?.target);
|
|
6843
|
+
const chartXml = chartPath ? await zip.file(chartPath)?.async("text") : void 0;
|
|
6844
|
+
const chart = chartXml ? parseChartXml(chartXml, chartPath?.split("/").pop() || `chart${index + 1}.xml`) : null;
|
|
6845
|
+
if (chart) {
|
|
6846
|
+
charts.push({ ...chart, widthPt: drawing.widthPt, heightPt: drawing.heightPt });
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
return charts;
|
|
6850
|
+
} catch {
|
|
6851
|
+
return [];
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6854
|
+
function readDocxChartDrawing(element) {
|
|
6855
|
+
const chart = Array.from(element.getElementsByTagName("*")).find((child) => child.localName === "chart");
|
|
6856
|
+
const relationshipId = chart ? getXmlAttribute3(chart, "id") : "";
|
|
6857
|
+
if (!relationshipId) {
|
|
6858
|
+
return void 0;
|
|
6859
|
+
}
|
|
6860
|
+
const extent = Array.from(element.children).find((child) => child.localName === "extent");
|
|
6861
|
+
return {
|
|
6862
|
+
relationshipId,
|
|
6863
|
+
widthPt: emuToPt(Number(extent?.getAttribute("cx") || 0)),
|
|
6864
|
+
heightPt: emuToPt(Number(extent?.getAttribute("cy") || 0))
|
|
6865
|
+
};
|
|
6866
|
+
}
|
|
6867
|
+
function repairDocxChartPlaceholders(container, charts) {
|
|
6868
|
+
if (charts.length === 0) {
|
|
6869
|
+
return;
|
|
6870
|
+
}
|
|
6871
|
+
const placeholders = Array.from(container.querySelectorAll("section.ofv-docx div")).filter(isDocxChartPlaceholder);
|
|
6872
|
+
if (placeholders.length !== charts.length) {
|
|
6873
|
+
return;
|
|
6874
|
+
}
|
|
6875
|
+
placeholders.forEach((placeholder, index) => {
|
|
6876
|
+
const chart = charts[index];
|
|
6877
|
+
placeholder.classList.add("ofv-docx-chart-preview");
|
|
6878
|
+
placeholder.dataset.ofvDocxChartPreview = "true";
|
|
6879
|
+
if (chart.widthPt > 0) {
|
|
6880
|
+
placeholder.style.width ||= `${formatCssNumber(chart.widthPt)}pt`;
|
|
6881
|
+
}
|
|
6882
|
+
if (chart.heightPt > 0) {
|
|
6883
|
+
placeholder.style.height ||= `${formatCssNumber(chart.heightPt)}pt`;
|
|
6884
|
+
}
|
|
6885
|
+
placeholder.replaceChildren(renderChartSvg(chart));
|
|
6886
|
+
});
|
|
6887
|
+
}
|
|
6888
|
+
function isDocxChartPlaceholder(element) {
|
|
6889
|
+
if (element.dataset.ofvDocxChartPreview === "true" || element.children.length > 0 || normalizePreviewText(element.textContent || "")) {
|
|
6890
|
+
return false;
|
|
6891
|
+
}
|
|
6892
|
+
const display = element.style.display;
|
|
6893
|
+
const position = element.style.position;
|
|
6894
|
+
const width = parseCssPixelValue(element.style.width);
|
|
6895
|
+
const height = parseCssPixelValue(element.style.height);
|
|
6896
|
+
return display === "inline-block" && position === "relative" && width >= 120 && height >= 80;
|
|
6897
|
+
}
|
|
6715
6898
|
function repairDocxHeadingShapeAlignment(page) {
|
|
6716
6899
|
for (const paragraph of page.querySelectorAll("p")) {
|
|
6717
6900
|
const text = normalizePreviewText(paragraph.textContent || "");
|
|
@@ -6862,14 +7045,19 @@ function repairDocxShapeFills(page) {
|
|
|
6862
7045
|
}
|
|
6863
7046
|
}
|
|
6864
7047
|
function repairDocxFloatingPictures(page, hints) {
|
|
6865
|
-
const
|
|
6866
|
-
if (
|
|
7048
|
+
const pageHints = hints.floatingPictures.filter((item) => item.relativeFrom === "column" && item.wrap === "square");
|
|
7049
|
+
if (pageHints.length === 0) {
|
|
6867
7050
|
return;
|
|
6868
7051
|
}
|
|
6869
|
-
const
|
|
6870
|
-
|
|
7052
|
+
const images = Array.from(page.querySelectorAll("img")).filter(
|
|
7053
|
+
(image) => image.closest("[data-ofv-docx-float-repaired='true']") === null
|
|
7054
|
+
);
|
|
7055
|
+
if (images.length === 0 || images.length !== pageHints.length) {
|
|
6871
7056
|
return;
|
|
6872
7057
|
}
|
|
7058
|
+
images.forEach((image, index) => repairDocxFloatingPicture(page, image, pageHints[index]));
|
|
7059
|
+
}
|
|
7060
|
+
function repairDocxFloatingPicture(page, image, hint) {
|
|
6873
7061
|
const wrapper = image.parentElement;
|
|
6874
7062
|
if (!wrapper || wrapper.dataset.ofvDocxFloatRepaired === "true") {
|
|
6875
7063
|
return;
|
|
@@ -6915,7 +7103,7 @@ function parseCssLineHeight(value) {
|
|
|
6915
7103
|
const parsed = Number.parseFloat(trimmed);
|
|
6916
7104
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
6917
7105
|
}
|
|
6918
|
-
function fitDocxPages(container) {
|
|
7106
|
+
function fitDocxPages(container, fit) {
|
|
6919
7107
|
const wrapper = container.querySelector(".ofv-docx-wrapper");
|
|
6920
7108
|
if (!wrapper) {
|
|
6921
7109
|
return () => void 0;
|
|
@@ -6928,6 +7116,7 @@ function fitDocxPages(container) {
|
|
|
6928
7116
|
return;
|
|
6929
7117
|
}
|
|
6930
7118
|
const availableWidth = Math.max(1, container.clientWidth - 48);
|
|
7119
|
+
const availableHeight = container.clientHeight > 0 ? Math.max(1, container.clientHeight - 48) : void 0;
|
|
6931
7120
|
const pageWidth = Math.max(
|
|
6932
7121
|
1,
|
|
6933
7122
|
...frames.map(({ page }) => {
|
|
@@ -6935,14 +7124,24 @@ function fitDocxPages(container) {
|
|
|
6935
7124
|
return page.offsetWidth || rectWidth || parseCssPixelValue(page.style.width) || 794;
|
|
6936
7125
|
})
|
|
6937
7126
|
);
|
|
6938
|
-
const
|
|
7127
|
+
const pageHeight = Math.max(
|
|
7128
|
+
1,
|
|
7129
|
+
...frames.map(({ page }) => page.offsetHeight || page.getBoundingClientRect().height || parseCssPixelValue(page.style.height) || 1123)
|
|
7130
|
+
);
|
|
7131
|
+
const scale = getDocxFitScale(fit, availableWidth, availableHeight, pageWidth, pageHeight);
|
|
6939
7132
|
const userZoom = parseCssPixelValue(panel?.style.getPropertyValue("--ofv-office-zoom") || "1") || 1;
|
|
6940
7133
|
wrapper.style.setProperty("--ofv-docx-scale", formatCssNumber(scale));
|
|
6941
7134
|
wrapper.style.setProperty("--ofv-docx-page-width", `${pageWidth}px`);
|
|
7135
|
+
wrapper.style.width = `${Math.ceil(pageWidth * scale * userZoom + 48)}px`;
|
|
7136
|
+
wrapper.style.maxWidth = "none";
|
|
7137
|
+
wrapper.style.overflow = "visible";
|
|
6942
7138
|
for (const { frame, page } of frames) {
|
|
6943
|
-
const
|
|
6944
|
-
|
|
6945
|
-
|
|
7139
|
+
const framePageHeight = page.offsetHeight || page.getBoundingClientRect().height || parseCssPixelValue(page.style.height);
|
|
7140
|
+
const framePageWidth = page.offsetWidth || page.getBoundingClientRect().width || parseCssPixelValue(page.style.width) || pageWidth;
|
|
7141
|
+
frame.style.width = `${Math.ceil(framePageWidth * scale * userZoom)}px`;
|
|
7142
|
+
frame.style.maxWidth = "none";
|
|
7143
|
+
if (framePageHeight > 0) {
|
|
7144
|
+
frame.style.height = `${Math.ceil(framePageHeight * scale * userZoom)}px`;
|
|
6946
7145
|
}
|
|
6947
7146
|
}
|
|
6948
7147
|
};
|
|
@@ -6967,6 +7166,26 @@ function fitDocxPages(container) {
|
|
|
6967
7166
|
observer.disconnect();
|
|
6968
7167
|
};
|
|
6969
7168
|
}
|
|
7169
|
+
function getDocxFitScale(fit, availableWidth, availableHeight, pageWidth, pageHeight) {
|
|
7170
|
+
const widthScale = availableWidth / pageWidth;
|
|
7171
|
+
const heightScale = availableHeight ? availableHeight / pageHeight : void 0;
|
|
7172
|
+
if (fit === "actual") {
|
|
7173
|
+
return 1;
|
|
7174
|
+
}
|
|
7175
|
+
if (fit === "height") {
|
|
7176
|
+
return Math.max(0.1, heightScale ?? widthScale);
|
|
7177
|
+
}
|
|
7178
|
+
if (fit === "cover") {
|
|
7179
|
+
return Math.max(0.1, Math.max(widthScale, heightScale ?? widthScale));
|
|
7180
|
+
}
|
|
7181
|
+
if (fit === "scale-down") {
|
|
7182
|
+
return Math.min(1, Math.max(0.1, Math.min(widthScale, heightScale ?? widthScale)));
|
|
7183
|
+
}
|
|
7184
|
+
if (fit === "contain") {
|
|
7185
|
+
return Math.max(0.1, Math.min(widthScale, heightScale ?? widthScale));
|
|
7186
|
+
}
|
|
7187
|
+
return Math.max(0.1, widthScale);
|
|
7188
|
+
}
|
|
6970
7189
|
function ensureDocxPageFrames(wrapper) {
|
|
6971
7190
|
const pages = Array.from(wrapper.querySelectorAll("section.ofv-docx"));
|
|
6972
7191
|
return pages.map((page) => {
|
|
@@ -7498,9 +7717,9 @@ function parseChartXml(xml, fallbackName) {
|
|
|
7498
7717
|
return null;
|
|
7499
7718
|
}
|
|
7500
7719
|
const type = detectChartType(doc);
|
|
7501
|
-
const title =
|
|
7720
|
+
const title = readChartTitle(doc, fallbackName);
|
|
7502
7721
|
const seriesElements = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ser");
|
|
7503
|
-
const series = seriesElements.map((element, index) => parseChartSeries(element, index)).filter((item) => item.values.length > 0);
|
|
7722
|
+
const series = seriesElements.map((element, index) => ({ ...parseChartSeries(element, index), color: readChartSeriesColor(element) })).filter((item) => item.values.length > 0);
|
|
7504
7723
|
if (series.length === 0) {
|
|
7505
7724
|
return null;
|
|
7506
7725
|
}
|
|
@@ -7509,9 +7728,21 @@ function parseChartXml(xml, fallbackName) {
|
|
|
7509
7728
|
type,
|
|
7510
7729
|
title,
|
|
7511
7730
|
categories: series.find((item) => item.categories.length > 0)?.categories || [],
|
|
7512
|
-
series: series.map((item) => ({ name: item.name, values: item.values }))
|
|
7731
|
+
series: series.map((item) => ({ name: item.name, values: item.values, color: item.color }))
|
|
7513
7732
|
};
|
|
7514
7733
|
}
|
|
7734
|
+
function readChartTitle(doc, fallbackName) {
|
|
7735
|
+
const titleElement = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "title");
|
|
7736
|
+
if (!titleElement) {
|
|
7737
|
+
return fallbackName.replace(/\.xml$/i, "");
|
|
7738
|
+
}
|
|
7739
|
+
const explicitTitle = chartText(titleElement);
|
|
7740
|
+
if (explicitTitle) {
|
|
7741
|
+
return explicitTitle;
|
|
7742
|
+
}
|
|
7743
|
+
const language = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "lang")?.getAttribute("val") || "";
|
|
7744
|
+
return /^zh\b/i.test(language) ? "\u56FE\u8868\u6807\u9898" : "Chart Title";
|
|
7745
|
+
}
|
|
7515
7746
|
function detectChartType(doc) {
|
|
7516
7747
|
const chartType = Array.from(doc.getElementsByTagName("*")).find(
|
|
7517
7748
|
(element) => element.localName.endsWith("Chart") && element.localName !== "chart"
|
|
@@ -7528,6 +7759,31 @@ function parseChartSeries(element, index) {
|
|
|
7528
7759
|
categories: stringsFromFirst(element, "cat")
|
|
7529
7760
|
};
|
|
7530
7761
|
}
|
|
7762
|
+
function readChartSeriesColor(element) {
|
|
7763
|
+
const shape = Array.from(element?.children || []).find((child) => child.localName === "spPr");
|
|
7764
|
+
const color = Array.from(shape?.getElementsByTagName("*") || []).find(
|
|
7765
|
+
(child) => child.localName === "srgbClr" || child.localName === "schemeClr"
|
|
7766
|
+
);
|
|
7767
|
+
if (!color) {
|
|
7768
|
+
return void 0;
|
|
7769
|
+
}
|
|
7770
|
+
if (color.localName === "srgbClr") {
|
|
7771
|
+
const value = color.getAttribute("val") || "";
|
|
7772
|
+
return /^[\da-f]{6}$/i.test(value) ? `#${value}` : void 0;
|
|
7773
|
+
}
|
|
7774
|
+
return chartSchemeColor(color.getAttribute("val") || "");
|
|
7775
|
+
}
|
|
7776
|
+
function chartSchemeColor(value) {
|
|
7777
|
+
const colors = {
|
|
7778
|
+
accent1: "#156082",
|
|
7779
|
+
accent2: "#e97132",
|
|
7780
|
+
accent3: "#196b24",
|
|
7781
|
+
accent4: "#0f9ed5",
|
|
7782
|
+
accent5: "#a02b93",
|
|
7783
|
+
accent6: "#4ea72e"
|
|
7784
|
+
};
|
|
7785
|
+
return colors[value];
|
|
7786
|
+
}
|
|
7531
7787
|
function renderChartPreviewSection(charts) {
|
|
7532
7788
|
const section = createSection("\u8868\u683C\u56FE\u8868\u9884\u89C8");
|
|
7533
7789
|
const grid = document.createElement("div");
|
|
@@ -7565,17 +7821,39 @@ function renderChartCard(chart) {
|
|
|
7565
7821
|
}
|
|
7566
7822
|
function renderChartSvg(chart) {
|
|
7567
7823
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
7568
|
-
svg.setAttribute("viewBox", "0 0 640
|
|
7824
|
+
svg.setAttribute("viewBox", "0 0 640 380");
|
|
7569
7825
|
svg.setAttribute("role", "img");
|
|
7570
7826
|
svg.setAttribute("aria-label", chart.title);
|
|
7571
7827
|
svg.classList.add("ofv-chart-svg");
|
|
7572
7828
|
const allValues = chart.series.flatMap((item) => item.values).filter((value) => Number.isFinite(value));
|
|
7573
7829
|
const max = Math.max(1, ...allValues);
|
|
7574
7830
|
const min = Math.min(0, ...allValues);
|
|
7831
|
+
const axisScale = createChartAxisScale(max, min);
|
|
7832
|
+
const axisMax = axisScale.max;
|
|
7833
|
+
const axisMin = axisScale.min;
|
|
7575
7834
|
const span = max - min || 1;
|
|
7576
|
-
const colors = ["#
|
|
7577
|
-
const plot = { x:
|
|
7578
|
-
|
|
7835
|
+
const colors = ["#156082", "#e97132", "#196b24", "#0f9ed5", "#a02b93", "#4ea72e"];
|
|
7836
|
+
const plot = { x: 74, y: 74, width: 526, height: 214 };
|
|
7837
|
+
const categories = chart.categories.length > 0 ? chart.categories : chart.series[0]?.values.map((_, index) => String(index + 1)) || [];
|
|
7838
|
+
const title = appendSvg(svg, "text", { x: 320, y: 34, class: "ofv-chart-title", "text-anchor": "middle" });
|
|
7839
|
+
title.textContent = chart.title;
|
|
7840
|
+
for (const value of axisScale.ticks) {
|
|
7841
|
+
const y = plot.y + plot.height - (value - axisMin) / (axisMax - axisMin || 1) * plot.height;
|
|
7842
|
+
appendSvg(svg, "line", {
|
|
7843
|
+
x1: plot.x,
|
|
7844
|
+
y1: Number(y.toFixed(1)),
|
|
7845
|
+
x2: plot.x + plot.width,
|
|
7846
|
+
y2: Number(y.toFixed(1)),
|
|
7847
|
+
class: value === 0 ? "ofv-chart-axis" : "ofv-chart-gridline"
|
|
7848
|
+
});
|
|
7849
|
+
const label = appendSvg(svg, "text", {
|
|
7850
|
+
x: plot.x - 12,
|
|
7851
|
+
y: Number((y + 4).toFixed(1)),
|
|
7852
|
+
class: "ofv-chart-label",
|
|
7853
|
+
"text-anchor": "end"
|
|
7854
|
+
});
|
|
7855
|
+
label.textContent = formatChartTick(value);
|
|
7856
|
+
}
|
|
7579
7857
|
appendSvg(svg, "line", {
|
|
7580
7858
|
x1: plot.x,
|
|
7581
7859
|
y1: plot.y + plot.height,
|
|
@@ -7583,28 +7861,47 @@ function renderChartSvg(chart) {
|
|
|
7583
7861
|
y2: plot.y + plot.height,
|
|
7584
7862
|
class: "ofv-chart-axis"
|
|
7585
7863
|
});
|
|
7586
|
-
chart.
|
|
7587
|
-
const
|
|
7588
|
-
const
|
|
7589
|
-
const
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
const
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7864
|
+
if (chart.type.includes("bar") || chart.type.includes("col")) {
|
|
7865
|
+
const categoryCount = Math.max(1, categories.length, ...chart.series.map((series) => series.values.length));
|
|
7866
|
+
const groupWidth = plot.width / categoryCount;
|
|
7867
|
+
const clusterWidth = groupWidth * 0.58;
|
|
7868
|
+
const barWidth = Math.max(5, Math.min(28, clusterWidth / Math.max(1, chart.series.length)));
|
|
7869
|
+
const zeroY = plot.y + plot.height - (0 - axisMin) / (axisMax - axisMin || 1) * plot.height;
|
|
7870
|
+
categories.slice(0, categoryCount).forEach((category, index) => {
|
|
7871
|
+
const x = plot.x + groupWidth * (index + 0.5);
|
|
7872
|
+
const label = appendSvg(svg, "text", {
|
|
7873
|
+
x: Number(x.toFixed(1)),
|
|
7874
|
+
y: plot.y + plot.height + 22,
|
|
7875
|
+
class: "ofv-chart-label",
|
|
7876
|
+
"text-anchor": "middle"
|
|
7877
|
+
});
|
|
7878
|
+
label.textContent = truncateChartLabel(category);
|
|
7879
|
+
});
|
|
7880
|
+
chart.series.forEach((series, seriesIndex) => {
|
|
7881
|
+
const color = series.color || colors[seriesIndex % colors.length];
|
|
7882
|
+
series.values.forEach((value, index) => {
|
|
7883
|
+
const groupCenter = plot.x + groupWidth * (index + 0.5);
|
|
7884
|
+
const x = groupCenter - barWidth * chart.series.length / 2 + seriesIndex * barWidth + barWidth * 0.12;
|
|
7885
|
+
const y = plot.y + plot.height - (value - axisMin) / (axisMax - axisMin || 1) * plot.height;
|
|
7598
7886
|
appendSvg(svg, "rect", {
|
|
7599
|
-
x,
|
|
7600
|
-
y: Math.min(
|
|
7601
|
-
width: barWidth,
|
|
7602
|
-
height: Math.max(1, Math.abs(zeroY -
|
|
7887
|
+
x: Number(x.toFixed(1)),
|
|
7888
|
+
y: Number(Math.min(y, zeroY).toFixed(1)),
|
|
7889
|
+
width: Number((barWidth * 0.76).toFixed(1)),
|
|
7890
|
+
height: Number(Math.max(1, Math.abs(zeroY - y)).toFixed(1)),
|
|
7603
7891
|
fill: color,
|
|
7604
7892
|
"data-index": index
|
|
7605
7893
|
});
|
|
7606
7894
|
});
|
|
7607
|
-
}
|
|
7895
|
+
});
|
|
7896
|
+
} else {
|
|
7897
|
+
chart.series.forEach((series, seriesIndex) => {
|
|
7898
|
+
const color = series.color || colors[seriesIndex % colors.length];
|
|
7899
|
+
const step = series.values.length > 1 ? plot.width / (series.values.length - 1) : plot.width;
|
|
7900
|
+
const lineSpan = axisMax - axisMin || span;
|
|
7901
|
+
const points = series.values.map((value, index) => ({
|
|
7902
|
+
x: plot.x + index * step,
|
|
7903
|
+
y: plot.y + plot.height - (value - axisMin) / lineSpan * plot.height
|
|
7904
|
+
}));
|
|
7608
7905
|
appendSvg(svg, "polyline", {
|
|
7609
7906
|
points: points.map((point) => `${point.x.toFixed(1)},${point.y.toFixed(1)}`).join(" "),
|
|
7610
7907
|
fill: "none",
|
|
@@ -7616,11 +7913,49 @@ function renderChartSvg(chart) {
|
|
|
7616
7913
|
for (const point of points.slice(0, 80)) {
|
|
7617
7914
|
appendSvg(svg, "circle", { cx: point.x, cy: point.y, r: 3, fill: color });
|
|
7618
7915
|
}
|
|
7619
|
-
}
|
|
7620
|
-
|
|
7621
|
-
|
|
7916
|
+
});
|
|
7917
|
+
}
|
|
7918
|
+
appendChartLegend(svg, chart, colors, 348);
|
|
7622
7919
|
return svg;
|
|
7623
7920
|
}
|
|
7921
|
+
function appendChartLegend(svg, chart, colors, y) {
|
|
7922
|
+
const itemWidth = 86;
|
|
7923
|
+
const startX = 320 - chart.series.length * itemWidth / 2;
|
|
7924
|
+
chart.series.forEach((series, seriesIndex) => {
|
|
7925
|
+
const color = series.color || colors[seriesIndex % colors.length];
|
|
7926
|
+
appendLegend(svg, series.name, color, startX + seriesIndex * itemWidth, y);
|
|
7927
|
+
});
|
|
7928
|
+
}
|
|
7929
|
+
function createChartAxisScale(max, min) {
|
|
7930
|
+
const axisMin = Math.min(0, min);
|
|
7931
|
+
const positiveMax = Math.max(1, max);
|
|
7932
|
+
const step = niceChartStep((positiveMax - axisMin) / 5);
|
|
7933
|
+
let axisMax = Math.ceil(positiveMax / step) * step;
|
|
7934
|
+
if (axisMax <= positiveMax) {
|
|
7935
|
+
axisMax += step;
|
|
7936
|
+
}
|
|
7937
|
+
const ticks = [];
|
|
7938
|
+
for (let value = axisMin; value <= axisMax + step / 2; value += step) {
|
|
7939
|
+
ticks.push(Number(value.toFixed(6)));
|
|
7940
|
+
}
|
|
7941
|
+
return { min: axisMin, max: axisMax, ticks };
|
|
7942
|
+
}
|
|
7943
|
+
function niceChartStep(rawStep) {
|
|
7944
|
+
if (rawStep <= 0) {
|
|
7945
|
+
return 1;
|
|
7946
|
+
}
|
|
7947
|
+
const magnitude = 10 ** Math.floor(Math.log10(rawStep));
|
|
7948
|
+
const normalized = rawStep / magnitude;
|
|
7949
|
+
const nice = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;
|
|
7950
|
+
return nice * magnitude;
|
|
7951
|
+
}
|
|
7952
|
+
function formatChartTick(value) {
|
|
7953
|
+
const rounded = Math.abs(value) < 1 ? Number(value.toFixed(1)) : Number(value.toFixed(0));
|
|
7954
|
+
return String(rounded);
|
|
7955
|
+
}
|
|
7956
|
+
function truncateChartLabel(value) {
|
|
7957
|
+
return value.length > 10 ? `${value.slice(0, 10)}...` : value;
|
|
7958
|
+
}
|
|
7624
7959
|
function appendLegend(svg, label, color, x, y) {
|
|
7625
7960
|
appendSvg(svg, "rect", { x, y: y - 10, width: 12, height: 12, rx: 2, fill: color });
|
|
7626
7961
|
const text = appendSvg(svg, "text", { x: x + 18, y, class: "ofv-chart-label" });
|
|
@@ -8354,7 +8689,7 @@ function renderOpenDocumentPresentationXml(panel, xml) {
|
|
|
8354
8689
|
renderPresentationInsight(panel, inspectOpenDocumentPresentation("FODP \u6F14\u793A\u6587\u7A3F", xml, 0));
|
|
8355
8690
|
renderOpenDocumentPresentation(panel, "FODP \u6F14\u793A\u6587\u7A3F", xml, []);
|
|
8356
8691
|
}
|
|
8357
|
-
async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
8692
|
+
async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit) {
|
|
8358
8693
|
let zip;
|
|
8359
8694
|
try {
|
|
8360
8695
|
zip = await JSZip3.loadAsync(arrayBuffer);
|
|
@@ -8365,7 +8700,7 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
|
8365
8700
|
const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
|
|
8366
8701
|
const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
|
|
8367
8702
|
if (hasEntry("word/document.xml")) {
|
|
8368
|
-
await renderDocx(panel, arrayBuffer);
|
|
8703
|
+
await renderDocx(panel, arrayBuffer, fit);
|
|
8369
8704
|
return true;
|
|
8370
8705
|
}
|
|
8371
8706
|
if (hasEntry("xl/workbook.xml")) {
|
|
@@ -18817,7 +19152,7 @@ async function createPostScriptPreview(bytes, url, fileName, size, fit, toolbar)
|
|
|
18817
19152
|
preview.append(summary);
|
|
18818
19153
|
return { element: preview };
|
|
18819
19154
|
}
|
|
18820
|
-
async function createPdfCompatibleAiPreview(bytes, url, fileName, size, fit, toolbar, pdfOffset = 0) {
|
|
19155
|
+
async function createPdfCompatibleAiPreview(bytes, url, fileName, size, fit, toolbar, pdfOffset = 0, zoom = 1) {
|
|
18821
19156
|
const wrapper = document.createElement("div");
|
|
18822
19157
|
wrapper.className = "ofv-ai-pdf-preview";
|
|
18823
19158
|
let pdfUrl = url;
|
|
@@ -18832,6 +19167,7 @@ async function createPdfCompatibleAiPreview(bytes, url, fileName, size, fit, too
|
|
|
18832
19167
|
viewport: wrapper,
|
|
18833
19168
|
size,
|
|
18834
19169
|
fit,
|
|
19170
|
+
zoom,
|
|
18835
19171
|
toolbar,
|
|
18836
19172
|
fallbackTitle: "AI PDF \u517C\u5BB9\u9884\u89C8\u5931\u8D25",
|
|
18837
19173
|
revokeUrlOnDestroy: false
|