@open-file-viewer/core 0.1.15 → 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/dist/index.cjs +372 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +372 -89
- package/dist/index.js.map +1 -1
- package/dist/style.css +278 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1877,12 +1877,12 @@ function imagePlugin() {
|
|
|
1877
1877
|
let url = "";
|
|
1878
1878
|
let convertedBlob = null;
|
|
1879
1879
|
let isExternal = Boolean(ctx.file.url);
|
|
1880
|
-
let
|
|
1880
|
+
let tiffSource = null;
|
|
1881
1881
|
if (isTiff) {
|
|
1882
1882
|
ctx.setLoading(true);
|
|
1883
1883
|
try {
|
|
1884
1884
|
const bytes = await sourceBytesPromise;
|
|
1885
|
-
|
|
1885
|
+
tiffSource = await createTiffPreview(bytes, ctx.file.name);
|
|
1886
1886
|
} catch (err) {
|
|
1887
1887
|
console.error("TIFF image conversion failed:", err);
|
|
1888
1888
|
url = createObjectUrl(ctx.file);
|
|
@@ -1942,12 +1942,15 @@ function imagePlugin() {
|
|
|
1942
1942
|
if (url) {
|
|
1943
1943
|
image.src = url;
|
|
1944
1944
|
}
|
|
1945
|
-
const visual =
|
|
1946
|
-
if (
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
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
|
+
}
|
|
1950
1951
|
}
|
|
1952
|
+
const visualBox = document.createElement("div");
|
|
1953
|
+
visualBox.className = "ofv-image-scrollbox";
|
|
1951
1954
|
let scale = getInitialZoom(ctx);
|
|
1952
1955
|
let rotation = 0;
|
|
1953
1956
|
let offsetX = 0;
|
|
@@ -1961,7 +1964,23 @@ function imagePlugin() {
|
|
|
1961
1964
|
let previewAvailable = true;
|
|
1962
1965
|
const zoomLabel = document.createElement("span");
|
|
1963
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
|
+
};
|
|
1964
1982
|
const updateTransform = () => {
|
|
1983
|
+
updateScrollBox();
|
|
1965
1984
|
visual.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale}) rotate(${rotation}deg)`;
|
|
1966
1985
|
zoomLabel.textContent = `${Math.round(scale * 100)}%`;
|
|
1967
1986
|
ctx.toolbar?.setZoom(previewAvailable ? scale : void 0);
|
|
@@ -2081,11 +2100,13 @@ function imagePlugin() {
|
|
|
2081
2100
|
stage.addEventListener("lostpointercapture", onLostPointerCapture);
|
|
2082
2101
|
stage.addEventListener("pointerleave", onPointerLeave);
|
|
2083
2102
|
stage.addEventListener("wheel", onWheel, { passive: false });
|
|
2084
|
-
if (!
|
|
2103
|
+
if (!tiffSource) {
|
|
2085
2104
|
image.addEventListener("error", showImageFallback);
|
|
2105
|
+
image.addEventListener("load", updateScrollBox);
|
|
2086
2106
|
}
|
|
2087
2107
|
window.addEventListener("blur", onWindowBlur);
|
|
2088
|
-
|
|
2108
|
+
visualBox.append(visual);
|
|
2109
|
+
stage.append(visualBox);
|
|
2089
2110
|
wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
|
|
2090
2111
|
ctx.viewport.append(wrapper);
|
|
2091
2112
|
updateTransform();
|
|
@@ -2123,7 +2144,12 @@ function imagePlugin() {
|
|
|
2123
2144
|
},
|
|
2124
2145
|
resize(size) {
|
|
2125
2146
|
visual.style.maxWidth = `${size.width}px`;
|
|
2126
|
-
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();
|
|
2127
2153
|
},
|
|
2128
2154
|
destroy() {
|
|
2129
2155
|
ctx.toolbar?.setZoom(void 0);
|
|
@@ -2138,6 +2164,7 @@ function imagePlugin() {
|
|
|
2138
2164
|
stage.removeEventListener("pointerleave", onPointerLeave);
|
|
2139
2165
|
stage.removeEventListener("wheel", onWheel);
|
|
2140
2166
|
image.removeEventListener("error", showImageFallback);
|
|
2167
|
+
image.removeEventListener("load", updateScrollBox);
|
|
2141
2168
|
window.removeEventListener("blur", onWindowBlur);
|
|
2142
2169
|
finishDrag();
|
|
2143
2170
|
wrapper.remove();
|
|
@@ -2151,33 +2178,64 @@ function imagePlugin() {
|
|
|
2151
2178
|
}
|
|
2152
2179
|
};
|
|
2153
2180
|
}
|
|
2154
|
-
async function
|
|
2181
|
+
async function createTiffPreview(bytes, fileName) {
|
|
2155
2182
|
if (bytes.byteLength === 0) {
|
|
2156
2183
|
throw new Error("\u65E0\u6CD5\u8BFB\u53D6 TIFF \u6587\u4EF6\u5185\u5BB9\u3002");
|
|
2157
2184
|
}
|
|
2158
2185
|
const UTIF = await import("utif");
|
|
2159
2186
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2160
2187
|
const ifds = UTIF.decode(buffer);
|
|
2161
|
-
const
|
|
2162
|
-
if (
|
|
2188
|
+
const imageIfds = ifds.filter((item) => hasTiffDimensions(item));
|
|
2189
|
+
if (imageIfds.length === 0) {
|
|
2163
2190
|
throw new Error("TIFF \u6587\u4EF6\u6CA1\u6709\u53EF\u89E3\u7801\u7684\u56FE\u50CF\u76EE\u5F55\u3002");
|
|
2164
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) {
|
|
2165
2211
|
UTIF.decodeImage(buffer, ifd);
|
|
2166
2212
|
const rgba = UTIF.toRGBA8(ifd);
|
|
2167
|
-
const width =
|
|
2168
|
-
const height = Number(ifd.height || ifd.t257 || 0);
|
|
2213
|
+
const { width, height } = getTiffDimensions(ifd);
|
|
2169
2214
|
if (!width || !height || rgba.length < width * height * 4) {
|
|
2170
2215
|
throw new Error("TIFF \u56FE\u50CF\u50CF\u7D20\u6570\u636E\u4E0D\u5B8C\u6574\u3002");
|
|
2171
2216
|
}
|
|
2172
2217
|
const canvas = document.createElement("canvas");
|
|
2218
|
+
canvas.className = "ofv-tiff-canvas";
|
|
2173
2219
|
canvas.width = width;
|
|
2174
2220
|
canvas.height = height;
|
|
2221
|
+
canvas.setAttribute("role", "img");
|
|
2222
|
+
canvas.setAttribute("aria-label", total > 1 ? `${fileName} \u7B2C ${index + 1} \u9875` : fileName);
|
|
2175
2223
|
const context = canvas.getContext("2d");
|
|
2176
2224
|
if (!context) {
|
|
2177
2225
|
throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Canvas 2D\uFF0C\u65E0\u6CD5\u5C55\u793A TIFF\u3002");
|
|
2178
2226
|
}
|
|
2179
2227
|
context.putImageData(new ImageData(new Uint8ClampedArray(rgba), width, height), 0, 0);
|
|
2180
|
-
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
|
+
};
|
|
2181
2239
|
}
|
|
2182
2240
|
function createImageFallback(fileName, url) {
|
|
2183
2241
|
const fallback = document.createElement("div");
|
|
@@ -2506,6 +2564,7 @@ function parseTiffInfo(bytes) {
|
|
|
2506
2564
|
return { format: magic === 43 ? "BigTIFF" : "TIFF", note: "IFD \u504F\u79FB\u8D85\u51FA\u6587\u4EF6\u8303\u56F4" };
|
|
2507
2565
|
}
|
|
2508
2566
|
const count = magic === 43 ? readTiffUint64AsNumber(bytes, ifdOffset, littleEndian) : readTiffUint16(bytes, ifdOffset, littleEndian);
|
|
2567
|
+
const imageCount = countTiffIfds(bytes, ifdOffset, magic === 43, littleEndian);
|
|
2509
2568
|
const entrySize = magic === 43 ? 20 : 12;
|
|
2510
2569
|
const entriesStart = ifdOffset + (magic === 43 ? 8 : 2);
|
|
2511
2570
|
let width;
|
|
@@ -2532,9 +2591,26 @@ function parseTiffInfo(bytes) {
|
|
|
2532
2591
|
height,
|
|
2533
2592
|
bitDepth: bitDepth ? `${bitDepth} bit` : void 0,
|
|
2534
2593
|
color: compression ? tiffCompressionName(compression) : void 0,
|
|
2535
|
-
count:
|
|
2594
|
+
count: imageCount || void 0
|
|
2536
2595
|
};
|
|
2537
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
|
+
}
|
|
2538
2614
|
function readTiffInlineValue(bytes, offset, type, littleEndian) {
|
|
2539
2615
|
if (offset + 4 > bytes.length) {
|
|
2540
2616
|
return void 0;
|
|
@@ -3933,26 +4009,6 @@ var mimeLangMap = {
|
|
|
3933
4009
|
};
|
|
3934
4010
|
var MAX_HIGHLIGHT_CHARS = 18e4;
|
|
3935
4011
|
var MAX_RENDER_CHARS = 6e5;
|
|
3936
|
-
function loadPrismCss(theme) {
|
|
3937
|
-
const lightId = "ofv-prism-css-light";
|
|
3938
|
-
const darkId = "ofv-prism-css-dark";
|
|
3939
|
-
const activeId = theme === "dark" ? darkId : lightId;
|
|
3940
|
-
const inactiveId = theme === "dark" ? lightId : darkId;
|
|
3941
|
-
document.getElementById(inactiveId)?.remove();
|
|
3942
|
-
if (document.getElementById(activeId)) {
|
|
3943
|
-
return Promise.resolve();
|
|
3944
|
-
}
|
|
3945
|
-
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";
|
|
3946
|
-
return new Promise((resolve, reject) => {
|
|
3947
|
-
const link = document.createElement("link");
|
|
3948
|
-
link.id = activeId;
|
|
3949
|
-
link.rel = "stylesheet";
|
|
3950
|
-
link.href = href;
|
|
3951
|
-
link.onload = () => resolve();
|
|
3952
|
-
link.onerror = () => reject(new Error(`Failed to load Prism CSS: ${href}`));
|
|
3953
|
-
document.head.appendChild(link);
|
|
3954
|
-
});
|
|
3955
|
-
}
|
|
3956
4012
|
function textPlugin() {
|
|
3957
4013
|
return {
|
|
3958
4014
|
name: "text",
|
|
@@ -3976,7 +4032,6 @@ function textPlugin() {
|
|
|
3976
4032
|
}
|
|
3977
4033
|
};
|
|
3978
4034
|
}
|
|
3979
|
-
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";
|
|
3980
4035
|
if (isMarkdown) {
|
|
3981
4036
|
const [markedModule, PrismModule2, DOMPurifyModule] = await Promise.all([
|
|
3982
4037
|
import("marked"),
|
|
@@ -3997,7 +4052,6 @@ function textPlugin() {
|
|
|
3997
4052
|
try {
|
|
3998
4053
|
const codeBlocks = container.querySelectorAll("pre code");
|
|
3999
4054
|
if (codeBlocks.length > 0) {
|
|
4000
|
-
await loadPrismCss(isDark ? "dark" : "light");
|
|
4001
4055
|
codeBlocks.forEach((block) => {
|
|
4002
4056
|
const parent = block.parentElement;
|
|
4003
4057
|
if (parent && !parent.className.includes("language-")) {
|
|
@@ -4121,9 +4175,6 @@ function textPlugin() {
|
|
|
4121
4175
|
console.warn(`Prism failed to load language component for: ${lang}`, e);
|
|
4122
4176
|
}
|
|
4123
4177
|
}
|
|
4124
|
-
await loadPrismCss(isDark ? "dark" : "light").catch((error) => {
|
|
4125
|
-
console.warn("Prism CSS failed to load; rendering code without external theme:", error);
|
|
4126
|
-
});
|
|
4127
4178
|
const codeText = text.length > MAX_RENDER_CHARS ? text.slice(0, MAX_RENDER_CHARS) : text;
|
|
4128
4179
|
const totalLines = countLines(text);
|
|
4129
4180
|
const shownLines = countLines(codeText);
|
|
@@ -4651,10 +4702,11 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4651
4702
|
const baseViewport = page.getViewport({ scale: 1 });
|
|
4652
4703
|
pagesMeta.push({
|
|
4653
4704
|
width: baseViewport.width,
|
|
4654
|
-
height: baseViewport.height
|
|
4705
|
+
height: baseViewport.height,
|
|
4706
|
+
rotation: getPdfPageRotation(page)
|
|
4655
4707
|
});
|
|
4656
4708
|
} catch {
|
|
4657
|
-
pagesMeta.push({ width: 612, height: 792 });
|
|
4709
|
+
pagesMeta.push({ width: 612, height: 792, rotation: 0 });
|
|
4658
4710
|
}
|
|
4659
4711
|
}
|
|
4660
4712
|
const pageStates = [];
|
|
@@ -4689,7 +4741,7 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4689
4741
|
const page = await pdfDocument.getPage(pageIdx + 1);
|
|
4690
4742
|
const meta = pagesMeta[pageIdx];
|
|
4691
4743
|
const scale = options.fit === "actual" ? zoomFactor : Math.max(0.05, Math.min(5, getPdfAvailableWidth(size.width) / rotatedPdfWidth(meta, rotation) * zoomFactor));
|
|
4692
|
-
const viewport = page.getViewport({ scale, rotation });
|
|
4744
|
+
const viewport = page.getViewport({ scale, rotation: getPdfRenderRotation(meta, rotation) });
|
|
4693
4745
|
const outputScale = getPdfOutputScale();
|
|
4694
4746
|
const cssWidth = Math.floor(viewport.width);
|
|
4695
4747
|
const cssHeight = Math.floor(viewport.height);
|
|
@@ -4728,6 +4780,8 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4728
4780
|
const span = document.createElement("span");
|
|
4729
4781
|
span.textContent = str;
|
|
4730
4782
|
span.style.fontSize = `${fontHeight}px`;
|
|
4783
|
+
span.style.lineHeight = "1";
|
|
4784
|
+
span.style.height = `${fontHeight}px`;
|
|
4731
4785
|
span.style.fontFamily = item.fontName || "sans-serif";
|
|
4732
4786
|
span.style.left = `${tx[4]}px`;
|
|
4733
4787
|
span.style.top = `${tx[5] - fontHeight}px`;
|
|
@@ -4925,6 +4979,13 @@ function appendPdfSummary(parent, label, value) {
|
|
|
4925
4979
|
function normalizePdfRotation(value) {
|
|
4926
4980
|
return (value % 360 + 360) % 360;
|
|
4927
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
|
+
}
|
|
4928
4989
|
function isPdfRotatedSideways(rotation) {
|
|
4929
4990
|
const normalized = normalizePdfRotation(rotation);
|
|
4930
4991
|
return normalized === 90 || normalized === 270;
|
|
@@ -5895,13 +5956,13 @@ function officePlugin(options = {}) {
|
|
|
5895
5956
|
if (conversionContext && await shouldUseOfficeConversion(options, conversionContext)) {
|
|
5896
5957
|
delegatedInstance = await renderConvertedOfficePreview(panel, ctx, options, conversionContext);
|
|
5897
5958
|
} else if (packageFormat === "docx" && !fileIsDocx(extension)) {
|
|
5898
|
-
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
5959
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
|
|
5899
5960
|
} else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
|
|
5900
5961
|
await renderSheet(panel, arrayBuffer, "xlsx");
|
|
5901
5962
|
} else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
|
|
5902
5963
|
await renderPptx(panel, arrayBuffer);
|
|
5903
5964
|
} else if (fileIsDocx(extension)) {
|
|
5904
|
-
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
5965
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
|
|
5905
5966
|
} else if (extension === "rtf") {
|
|
5906
5967
|
renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
|
|
5907
5968
|
} else if (extension === "odt") {
|
|
@@ -5910,7 +5971,7 @@ function officePlugin(options = {}) {
|
|
|
5910
5971
|
renderOpenDocumentXml(panel, "FODT \u6587\u6863", await readTextFromBuffer(arrayBuffer));
|
|
5911
5972
|
} else if (extension === "fods") {
|
|
5912
5973
|
renderFlatOds(panel, await readTextFromBuffer(arrayBuffer));
|
|
5913
|
-
} else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension)) {
|
|
5974
|
+
} else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension, ctx.options.fit)) {
|
|
5914
5975
|
} else if (sheetExtensions.has(extension)) {
|
|
5915
5976
|
await renderSheet(panel, arrayBuffer, extension);
|
|
5916
5977
|
} else if (["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
|
|
@@ -6112,7 +6173,7 @@ async function detectPackagedOfficeFormat(arrayBuffer) {
|
|
|
6112
6173
|
}
|
|
6113
6174
|
return void 0;
|
|
6114
6175
|
}
|
|
6115
|
-
async function renderDocx(panel, arrayBuffer) {
|
|
6176
|
+
async function renderDocx(panel, arrayBuffer, fit) {
|
|
6116
6177
|
const content = document.createElement("div");
|
|
6117
6178
|
content.className = "ofv-docx-document";
|
|
6118
6179
|
const styleContainer = document.createElement("div");
|
|
@@ -6151,7 +6212,7 @@ async function renderDocx(panel, arrayBuffer) {
|
|
|
6151
6212
|
return () => void 0;
|
|
6152
6213
|
}
|
|
6153
6214
|
panel.append(content);
|
|
6154
|
-
disposeFit = fitDocxPages(content);
|
|
6215
|
+
disposeFit = fitDocxPages(content, fit);
|
|
6155
6216
|
return () => {
|
|
6156
6217
|
disposeFit?.();
|
|
6157
6218
|
styleContainer.remove();
|
|
@@ -6749,7 +6810,8 @@ function looksLikeDocxTextboxHeading(value) {
|
|
|
6749
6810
|
return text.length > 0 && text.length <= 12 && !/[0-9@.::]/.test(text);
|
|
6750
6811
|
}
|
|
6751
6812
|
async function normalizeDocxLayout(container, arrayBuffer) {
|
|
6752
|
-
const hints = await readDocxLayoutHints(arrayBuffer);
|
|
6813
|
+
const [hints, charts] = await Promise.all([readDocxLayoutHints(arrayBuffer), readDocxCharts(arrayBuffer)]);
|
|
6814
|
+
repairDocxChartPlaceholders(container, charts);
|
|
6753
6815
|
const pages = container.querySelectorAll("section.ofv-docx");
|
|
6754
6816
|
for (const page of pages) {
|
|
6755
6817
|
repairDocxShapeFills(page);
|
|
@@ -6764,6 +6826,75 @@ async function normalizeDocxLayout(container, arrayBuffer) {
|
|
|
6764
6826
|
}
|
|
6765
6827
|
}
|
|
6766
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
|
+
}
|
|
6767
6898
|
function repairDocxHeadingShapeAlignment(page) {
|
|
6768
6899
|
for (const paragraph of page.querySelectorAll("p")) {
|
|
6769
6900
|
const text = normalizePreviewText(paragraph.textContent || "");
|
|
@@ -6914,14 +7045,19 @@ function repairDocxShapeFills(page) {
|
|
|
6914
7045
|
}
|
|
6915
7046
|
}
|
|
6916
7047
|
function repairDocxFloatingPictures(page, hints) {
|
|
6917
|
-
const
|
|
6918
|
-
if (
|
|
7048
|
+
const pageHints = hints.floatingPictures.filter((item) => item.relativeFrom === "column" && item.wrap === "square");
|
|
7049
|
+
if (pageHints.length === 0) {
|
|
6919
7050
|
return;
|
|
6920
7051
|
}
|
|
6921
|
-
const
|
|
6922
|
-
|
|
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) {
|
|
6923
7056
|
return;
|
|
6924
7057
|
}
|
|
7058
|
+
images.forEach((image, index) => repairDocxFloatingPicture(page, image, pageHints[index]));
|
|
7059
|
+
}
|
|
7060
|
+
function repairDocxFloatingPicture(page, image, hint) {
|
|
6925
7061
|
const wrapper = image.parentElement;
|
|
6926
7062
|
if (!wrapper || wrapper.dataset.ofvDocxFloatRepaired === "true") {
|
|
6927
7063
|
return;
|
|
@@ -6967,7 +7103,7 @@ function parseCssLineHeight(value) {
|
|
|
6967
7103
|
const parsed = Number.parseFloat(trimmed);
|
|
6968
7104
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
6969
7105
|
}
|
|
6970
|
-
function fitDocxPages(container) {
|
|
7106
|
+
function fitDocxPages(container, fit) {
|
|
6971
7107
|
const wrapper = container.querySelector(".ofv-docx-wrapper");
|
|
6972
7108
|
if (!wrapper) {
|
|
6973
7109
|
return () => void 0;
|
|
@@ -6980,6 +7116,7 @@ function fitDocxPages(container) {
|
|
|
6980
7116
|
return;
|
|
6981
7117
|
}
|
|
6982
7118
|
const availableWidth = Math.max(1, container.clientWidth - 48);
|
|
7119
|
+
const availableHeight = container.clientHeight > 0 ? Math.max(1, container.clientHeight - 48) : void 0;
|
|
6983
7120
|
const pageWidth = Math.max(
|
|
6984
7121
|
1,
|
|
6985
7122
|
...frames.map(({ page }) => {
|
|
@@ -6987,14 +7124,24 @@ function fitDocxPages(container) {
|
|
|
6987
7124
|
return page.offsetWidth || rectWidth || parseCssPixelValue(page.style.width) || 794;
|
|
6988
7125
|
})
|
|
6989
7126
|
);
|
|
6990
|
-
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);
|
|
6991
7132
|
const userZoom = parseCssPixelValue(panel?.style.getPropertyValue("--ofv-office-zoom") || "1") || 1;
|
|
6992
7133
|
wrapper.style.setProperty("--ofv-docx-scale", formatCssNumber(scale));
|
|
6993
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";
|
|
6994
7138
|
for (const { frame, page } of frames) {
|
|
6995
|
-
const
|
|
6996
|
-
|
|
6997
|
-
|
|
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`;
|
|
6998
7145
|
}
|
|
6999
7146
|
}
|
|
7000
7147
|
};
|
|
@@ -7019,6 +7166,26 @@ function fitDocxPages(container) {
|
|
|
7019
7166
|
observer.disconnect();
|
|
7020
7167
|
};
|
|
7021
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
|
+
}
|
|
7022
7189
|
function ensureDocxPageFrames(wrapper) {
|
|
7023
7190
|
const pages = Array.from(wrapper.querySelectorAll("section.ofv-docx"));
|
|
7024
7191
|
return pages.map((page) => {
|
|
@@ -7550,9 +7717,9 @@ function parseChartXml(xml, fallbackName) {
|
|
|
7550
7717
|
return null;
|
|
7551
7718
|
}
|
|
7552
7719
|
const type = detectChartType(doc);
|
|
7553
|
-
const title =
|
|
7720
|
+
const title = readChartTitle(doc, fallbackName);
|
|
7554
7721
|
const seriesElements = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ser");
|
|
7555
|
-
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);
|
|
7556
7723
|
if (series.length === 0) {
|
|
7557
7724
|
return null;
|
|
7558
7725
|
}
|
|
@@ -7561,9 +7728,21 @@ function parseChartXml(xml, fallbackName) {
|
|
|
7561
7728
|
type,
|
|
7562
7729
|
title,
|
|
7563
7730
|
categories: series.find((item) => item.categories.length > 0)?.categories || [],
|
|
7564
|
-
series: series.map((item) => ({ name: item.name, values: item.values }))
|
|
7731
|
+
series: series.map((item) => ({ name: item.name, values: item.values, color: item.color }))
|
|
7565
7732
|
};
|
|
7566
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
|
+
}
|
|
7567
7746
|
function detectChartType(doc) {
|
|
7568
7747
|
const chartType = Array.from(doc.getElementsByTagName("*")).find(
|
|
7569
7748
|
(element) => element.localName.endsWith("Chart") && element.localName !== "chart"
|
|
@@ -7580,6 +7759,31 @@ function parseChartSeries(element, index) {
|
|
|
7580
7759
|
categories: stringsFromFirst(element, "cat")
|
|
7581
7760
|
};
|
|
7582
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
|
+
}
|
|
7583
7787
|
function renderChartPreviewSection(charts) {
|
|
7584
7788
|
const section = createSection("\u8868\u683C\u56FE\u8868\u9884\u89C8");
|
|
7585
7789
|
const grid = document.createElement("div");
|
|
@@ -7617,17 +7821,39 @@ function renderChartCard(chart) {
|
|
|
7617
7821
|
}
|
|
7618
7822
|
function renderChartSvg(chart) {
|
|
7619
7823
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
7620
|
-
svg.setAttribute("viewBox", "0 0 640
|
|
7824
|
+
svg.setAttribute("viewBox", "0 0 640 380");
|
|
7621
7825
|
svg.setAttribute("role", "img");
|
|
7622
7826
|
svg.setAttribute("aria-label", chart.title);
|
|
7623
7827
|
svg.classList.add("ofv-chart-svg");
|
|
7624
7828
|
const allValues = chart.series.flatMap((item) => item.values).filter((value) => Number.isFinite(value));
|
|
7625
7829
|
const max = Math.max(1, ...allValues);
|
|
7626
7830
|
const min = Math.min(0, ...allValues);
|
|
7831
|
+
const axisScale = createChartAxisScale(max, min);
|
|
7832
|
+
const axisMax = axisScale.max;
|
|
7833
|
+
const axisMin = axisScale.min;
|
|
7627
7834
|
const span = max - min || 1;
|
|
7628
|
-
const colors = ["#
|
|
7629
|
-
const plot = { x:
|
|
7630
|
-
|
|
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
|
+
}
|
|
7631
7857
|
appendSvg(svg, "line", {
|
|
7632
7858
|
x1: plot.x,
|
|
7633
7859
|
y1: plot.y + plot.height,
|
|
@@ -7635,28 +7861,47 @@ function renderChartSvg(chart) {
|
|
|
7635
7861
|
y2: plot.y + plot.height,
|
|
7636
7862
|
class: "ofv-chart-axis"
|
|
7637
7863
|
});
|
|
7638
|
-
chart.
|
|
7639
|
-
const
|
|
7640
|
-
const
|
|
7641
|
-
const
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
const
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
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;
|
|
7650
7886
|
appendSvg(svg, "rect", {
|
|
7651
|
-
x,
|
|
7652
|
-
y: Math.min(
|
|
7653
|
-
width: barWidth,
|
|
7654
|
-
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)),
|
|
7655
7891
|
fill: color,
|
|
7656
7892
|
"data-index": index
|
|
7657
7893
|
});
|
|
7658
7894
|
});
|
|
7659
|
-
}
|
|
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
|
+
}));
|
|
7660
7905
|
appendSvg(svg, "polyline", {
|
|
7661
7906
|
points: points.map((point) => `${point.x.toFixed(1)},${point.y.toFixed(1)}`).join(" "),
|
|
7662
7907
|
fill: "none",
|
|
@@ -7668,11 +7913,49 @@ function renderChartSvg(chart) {
|
|
|
7668
7913
|
for (const point of points.slice(0, 80)) {
|
|
7669
7914
|
appendSvg(svg, "circle", { cx: point.x, cy: point.y, r: 3, fill: color });
|
|
7670
7915
|
}
|
|
7671
|
-
}
|
|
7672
|
-
|
|
7673
|
-
|
|
7916
|
+
});
|
|
7917
|
+
}
|
|
7918
|
+
appendChartLegend(svg, chart, colors, 348);
|
|
7674
7919
|
return svg;
|
|
7675
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
|
+
}
|
|
7676
7959
|
function appendLegend(svg, label, color, x, y) {
|
|
7677
7960
|
appendSvg(svg, "rect", { x, y: y - 10, width: 12, height: 12, rx: 2, fill: color });
|
|
7678
7961
|
const text = appendSvg(svg, "text", { x: x + 18, y, class: "ofv-chart-label" });
|
|
@@ -8406,7 +8689,7 @@ function renderOpenDocumentPresentationXml(panel, xml) {
|
|
|
8406
8689
|
renderPresentationInsight(panel, inspectOpenDocumentPresentation("FODP \u6F14\u793A\u6587\u7A3F", xml, 0));
|
|
8407
8690
|
renderOpenDocumentPresentation(panel, "FODP \u6F14\u793A\u6587\u7A3F", xml, []);
|
|
8408
8691
|
}
|
|
8409
|
-
async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
8692
|
+
async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit) {
|
|
8410
8693
|
let zip;
|
|
8411
8694
|
try {
|
|
8412
8695
|
zip = await JSZip3.loadAsync(arrayBuffer);
|
|
@@ -8417,7 +8700,7 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
|
8417
8700
|
const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
|
|
8418
8701
|
const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
|
|
8419
8702
|
if (hasEntry("word/document.xml")) {
|
|
8420
|
-
await renderDocx(panel, arrayBuffer);
|
|
8703
|
+
await renderDocx(panel, arrayBuffer, fit);
|
|
8421
8704
|
return true;
|
|
8422
8705
|
}
|
|
8423
8706
|
if (hasEntry("xl/workbook.xml")) {
|