@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.cjs
CHANGED
|
@@ -1930,12 +1930,12 @@ function imagePlugin() {
|
|
|
1930
1930
|
let url = "";
|
|
1931
1931
|
let convertedBlob = null;
|
|
1932
1932
|
let isExternal = Boolean(ctx.file.url);
|
|
1933
|
-
let
|
|
1933
|
+
let tiffSource = null;
|
|
1934
1934
|
if (isTiff) {
|
|
1935
1935
|
ctx.setLoading(true);
|
|
1936
1936
|
try {
|
|
1937
1937
|
const bytes = await sourceBytesPromise;
|
|
1938
|
-
|
|
1938
|
+
tiffSource = await createTiffPreview(bytes, ctx.file.name);
|
|
1939
1939
|
} catch (err) {
|
|
1940
1940
|
console.error("TIFF image conversion failed:", err);
|
|
1941
1941
|
url = createObjectUrl(ctx.file);
|
|
@@ -1995,12 +1995,15 @@ function imagePlugin() {
|
|
|
1995
1995
|
if (url) {
|
|
1996
1996
|
image.src = url;
|
|
1997
1997
|
}
|
|
1998
|
-
const visual =
|
|
1999
|
-
if (
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
1998
|
+
const visual = tiffSource || image;
|
|
1999
|
+
if (tiffSource) {
|
|
2000
|
+
tiffSource.classList.add("ofv-image-content");
|
|
2001
|
+
if (tiffSource.classList.contains("ofv-tiff-pages")) {
|
|
2002
|
+
stage.classList.add("ofv-image-stage-pages");
|
|
2003
|
+
}
|
|
2003
2004
|
}
|
|
2005
|
+
const visualBox = document.createElement("div");
|
|
2006
|
+
visualBox.className = "ofv-image-scrollbox";
|
|
2004
2007
|
let scale = getInitialZoom(ctx);
|
|
2005
2008
|
let rotation = 0;
|
|
2006
2009
|
let offsetX = 0;
|
|
@@ -2014,7 +2017,23 @@ function imagePlugin() {
|
|
|
2014
2017
|
let previewAvailable = true;
|
|
2015
2018
|
const zoomLabel = document.createElement("span");
|
|
2016
2019
|
zoomLabel.className = "ofv-image-zoom";
|
|
2020
|
+
const updateScrollBox = () => {
|
|
2021
|
+
if (visual.classList.contains("ofv-tiff-pages")) {
|
|
2022
|
+
visualBox.style.removeProperty("width");
|
|
2023
|
+
visualBox.style.removeProperty("height");
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
const baseWidth = visual.offsetWidth || visual.getBoundingClientRect().width || visual.naturalWidth || 0;
|
|
2027
|
+
const baseHeight = visual.offsetHeight || visual.getBoundingClientRect().height || visual.naturalHeight || 0;
|
|
2028
|
+
if (baseWidth > 0) {
|
|
2029
|
+
visualBox.style.width = `${Math.ceil(baseWidth * scale)}px`;
|
|
2030
|
+
}
|
|
2031
|
+
if (baseHeight > 0) {
|
|
2032
|
+
visualBox.style.height = `${Math.ceil(baseHeight * scale)}px`;
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2017
2035
|
const updateTransform = () => {
|
|
2036
|
+
updateScrollBox();
|
|
2018
2037
|
visual.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale}) rotate(${rotation}deg)`;
|
|
2019
2038
|
zoomLabel.textContent = `${Math.round(scale * 100)}%`;
|
|
2020
2039
|
ctx.toolbar?.setZoom(previewAvailable ? scale : void 0);
|
|
@@ -2134,11 +2153,13 @@ function imagePlugin() {
|
|
|
2134
2153
|
stage.addEventListener("lostpointercapture", onLostPointerCapture);
|
|
2135
2154
|
stage.addEventListener("pointerleave", onPointerLeave);
|
|
2136
2155
|
stage.addEventListener("wheel", onWheel, { passive: false });
|
|
2137
|
-
if (!
|
|
2156
|
+
if (!tiffSource) {
|
|
2138
2157
|
image.addEventListener("error", showImageFallback);
|
|
2158
|
+
image.addEventListener("load", updateScrollBox);
|
|
2139
2159
|
}
|
|
2140
2160
|
window.addEventListener("blur", onWindowBlur);
|
|
2141
|
-
|
|
2161
|
+
visualBox.append(visual);
|
|
2162
|
+
stage.append(visualBox);
|
|
2142
2163
|
wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
|
|
2143
2164
|
ctx.viewport.append(wrapper);
|
|
2144
2165
|
updateTransform();
|
|
@@ -2176,7 +2197,12 @@ function imagePlugin() {
|
|
|
2176
2197
|
},
|
|
2177
2198
|
resize(size) {
|
|
2178
2199
|
visual.style.maxWidth = `${size.width}px`;
|
|
2179
|
-
visual.
|
|
2200
|
+
if (visual.classList.contains("ofv-tiff-pages")) {
|
|
2201
|
+
visual.style.removeProperty("max-height");
|
|
2202
|
+
} else {
|
|
2203
|
+
visual.style.maxHeight = `${Math.max(0, size.height - controls.offsetHeight)}px`;
|
|
2204
|
+
}
|
|
2205
|
+
updateScrollBox();
|
|
2180
2206
|
},
|
|
2181
2207
|
destroy() {
|
|
2182
2208
|
ctx.toolbar?.setZoom(void 0);
|
|
@@ -2191,6 +2217,7 @@ function imagePlugin() {
|
|
|
2191
2217
|
stage.removeEventListener("pointerleave", onPointerLeave);
|
|
2192
2218
|
stage.removeEventListener("wheel", onWheel);
|
|
2193
2219
|
image.removeEventListener("error", showImageFallback);
|
|
2220
|
+
image.removeEventListener("load", updateScrollBox);
|
|
2194
2221
|
window.removeEventListener("blur", onWindowBlur);
|
|
2195
2222
|
finishDrag();
|
|
2196
2223
|
wrapper.remove();
|
|
@@ -2204,33 +2231,64 @@ function imagePlugin() {
|
|
|
2204
2231
|
}
|
|
2205
2232
|
};
|
|
2206
2233
|
}
|
|
2207
|
-
async function
|
|
2234
|
+
async function createTiffPreview(bytes, fileName) {
|
|
2208
2235
|
if (bytes.byteLength === 0) {
|
|
2209
2236
|
throw new Error("\u65E0\u6CD5\u8BFB\u53D6 TIFF \u6587\u4EF6\u5185\u5BB9\u3002");
|
|
2210
2237
|
}
|
|
2211
2238
|
const UTIF = await import("utif");
|
|
2212
2239
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2213
2240
|
const ifds = UTIF.decode(buffer);
|
|
2214
|
-
const
|
|
2215
|
-
if (
|
|
2241
|
+
const imageIfds = ifds.filter((item) => hasTiffDimensions(item));
|
|
2242
|
+
if (imageIfds.length === 0) {
|
|
2216
2243
|
throw new Error("TIFF \u6587\u4EF6\u6CA1\u6709\u53EF\u89E3\u7801\u7684\u56FE\u50CF\u76EE\u5F55\u3002");
|
|
2217
2244
|
}
|
|
2245
|
+
const pages = imageIfds.map((ifd, index) => createTiffPage(UTIF, buffer, ifd, index, imageIfds.length, fileName));
|
|
2246
|
+
if (pages.length === 1) {
|
|
2247
|
+
return pages[0].canvas;
|
|
2248
|
+
}
|
|
2249
|
+
const container = document.createElement("div");
|
|
2250
|
+
container.className = "ofv-tiff-pages";
|
|
2251
|
+
container.setAttribute("role", "group");
|
|
2252
|
+
container.setAttribute("aria-label", `${fileName}\uFF0C\u5171 ${pages.length} \u9875`);
|
|
2253
|
+
for (const page of pages) {
|
|
2254
|
+
const figure = document.createElement("figure");
|
|
2255
|
+
figure.className = "ofv-tiff-page";
|
|
2256
|
+
const caption = document.createElement("figcaption");
|
|
2257
|
+
caption.textContent = `\u7B2C ${page.index + 1} / ${pages.length} \u9875 \xB7 ${page.width} x ${page.height}px`;
|
|
2258
|
+
figure.append(page.canvas, caption);
|
|
2259
|
+
container.append(figure);
|
|
2260
|
+
}
|
|
2261
|
+
return container;
|
|
2262
|
+
}
|
|
2263
|
+
function createTiffPage(UTIF, buffer, ifd, index, total, fileName) {
|
|
2218
2264
|
UTIF.decodeImage(buffer, ifd);
|
|
2219
2265
|
const rgba = UTIF.toRGBA8(ifd);
|
|
2220
|
-
const width =
|
|
2221
|
-
const height = Number(ifd.height || ifd.t257 || 0);
|
|
2266
|
+
const { width, height } = getTiffDimensions(ifd);
|
|
2222
2267
|
if (!width || !height || rgba.length < width * height * 4) {
|
|
2223
2268
|
throw new Error("TIFF \u56FE\u50CF\u50CF\u7D20\u6570\u636E\u4E0D\u5B8C\u6574\u3002");
|
|
2224
2269
|
}
|
|
2225
2270
|
const canvas = document.createElement("canvas");
|
|
2271
|
+
canvas.className = "ofv-tiff-canvas";
|
|
2226
2272
|
canvas.width = width;
|
|
2227
2273
|
canvas.height = height;
|
|
2274
|
+
canvas.setAttribute("role", "img");
|
|
2275
|
+
canvas.setAttribute("aria-label", total > 1 ? `${fileName} \u7B2C ${index + 1} \u9875` : fileName);
|
|
2228
2276
|
const context = canvas.getContext("2d");
|
|
2229
2277
|
if (!context) {
|
|
2230
2278
|
throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Canvas 2D\uFF0C\u65E0\u6CD5\u5C55\u793A TIFF\u3002");
|
|
2231
2279
|
}
|
|
2232
2280
|
context.putImageData(new ImageData(new Uint8ClampedArray(rgba), width, height), 0, 0);
|
|
2233
|
-
return canvas;
|
|
2281
|
+
return { canvas, width, height, index };
|
|
2282
|
+
}
|
|
2283
|
+
function hasTiffDimensions(ifd) {
|
|
2284
|
+
const { width, height } = getTiffDimensions(ifd);
|
|
2285
|
+
return width > 0 && height > 0;
|
|
2286
|
+
}
|
|
2287
|
+
function getTiffDimensions(ifd) {
|
|
2288
|
+
return {
|
|
2289
|
+
width: Number(ifd.width || ifd.t256 || 0),
|
|
2290
|
+
height: Number(ifd.height || ifd.t257 || 0)
|
|
2291
|
+
};
|
|
2234
2292
|
}
|
|
2235
2293
|
function createImageFallback(fileName, url) {
|
|
2236
2294
|
const fallback = document.createElement("div");
|
|
@@ -2559,6 +2617,7 @@ function parseTiffInfo(bytes) {
|
|
|
2559
2617
|
return { format: magic === 43 ? "BigTIFF" : "TIFF", note: "IFD \u504F\u79FB\u8D85\u51FA\u6587\u4EF6\u8303\u56F4" };
|
|
2560
2618
|
}
|
|
2561
2619
|
const count = magic === 43 ? readTiffUint64AsNumber(bytes, ifdOffset, littleEndian) : readTiffUint16(bytes, ifdOffset, littleEndian);
|
|
2620
|
+
const imageCount = countTiffIfds(bytes, ifdOffset, magic === 43, littleEndian);
|
|
2562
2621
|
const entrySize = magic === 43 ? 20 : 12;
|
|
2563
2622
|
const entriesStart = ifdOffset + (magic === 43 ? 8 : 2);
|
|
2564
2623
|
let width;
|
|
@@ -2585,9 +2644,26 @@ function parseTiffInfo(bytes) {
|
|
|
2585
2644
|
height,
|
|
2586
2645
|
bitDepth: bitDepth ? `${bitDepth} bit` : void 0,
|
|
2587
2646
|
color: compression ? tiffCompressionName(compression) : void 0,
|
|
2588
|
-
count:
|
|
2647
|
+
count: imageCount || void 0
|
|
2589
2648
|
};
|
|
2590
2649
|
}
|
|
2650
|
+
function countTiffIfds(bytes, firstOffset, bigTiff, littleEndian) {
|
|
2651
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2652
|
+
let offset = firstOffset;
|
|
2653
|
+
let count = 0;
|
|
2654
|
+
while (Number.isFinite(offset) && offset > 0 && offset < bytes.length && !seen.has(offset) && count < 512) {
|
|
2655
|
+
seen.add(offset);
|
|
2656
|
+
const entryCount = bigTiff ? readTiffUint64AsNumber(bytes, offset, littleEndian) : readTiffUint16(bytes, offset, littleEndian);
|
|
2657
|
+
const entrySize = bigTiff ? 20 : 12;
|
|
2658
|
+
const nextOffsetPosition = offset + (bigTiff ? 8 : 2) + entryCount * entrySize;
|
|
2659
|
+
if (!Number.isFinite(entryCount) || nextOffsetPosition + (bigTiff ? 8 : 4) > bytes.length) {
|
|
2660
|
+
break;
|
|
2661
|
+
}
|
|
2662
|
+
count++;
|
|
2663
|
+
offset = bigTiff ? readTiffUint64AsNumber(bytes, nextOffsetPosition, littleEndian) : readTiffUint32(bytes, nextOffsetPosition, littleEndian);
|
|
2664
|
+
}
|
|
2665
|
+
return count;
|
|
2666
|
+
}
|
|
2591
2667
|
function readTiffInlineValue(bytes, offset, type, littleEndian) {
|
|
2592
2668
|
if (offset + 4 > bytes.length) {
|
|
2593
2669
|
return void 0;
|
|
@@ -3986,26 +4062,6 @@ var mimeLangMap = {
|
|
|
3986
4062
|
};
|
|
3987
4063
|
var MAX_HIGHLIGHT_CHARS = 18e4;
|
|
3988
4064
|
var MAX_RENDER_CHARS = 6e5;
|
|
3989
|
-
function loadPrismCss(theme) {
|
|
3990
|
-
const lightId = "ofv-prism-css-light";
|
|
3991
|
-
const darkId = "ofv-prism-css-dark";
|
|
3992
|
-
const activeId = theme === "dark" ? darkId : lightId;
|
|
3993
|
-
const inactiveId = theme === "dark" ? lightId : darkId;
|
|
3994
|
-
document.getElementById(inactiveId)?.remove();
|
|
3995
|
-
if (document.getElementById(activeId)) {
|
|
3996
|
-
return Promise.resolve();
|
|
3997
|
-
}
|
|
3998
|
-
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";
|
|
3999
|
-
return new Promise((resolve, reject) => {
|
|
4000
|
-
const link = document.createElement("link");
|
|
4001
|
-
link.id = activeId;
|
|
4002
|
-
link.rel = "stylesheet";
|
|
4003
|
-
link.href = href;
|
|
4004
|
-
link.onload = () => resolve();
|
|
4005
|
-
link.onerror = () => reject(new Error(`Failed to load Prism CSS: ${href}`));
|
|
4006
|
-
document.head.appendChild(link);
|
|
4007
|
-
});
|
|
4008
|
-
}
|
|
4009
4065
|
function textPlugin() {
|
|
4010
4066
|
return {
|
|
4011
4067
|
name: "text",
|
|
@@ -4029,7 +4085,6 @@ function textPlugin() {
|
|
|
4029
4085
|
}
|
|
4030
4086
|
};
|
|
4031
4087
|
}
|
|
4032
|
-
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";
|
|
4033
4088
|
if (isMarkdown) {
|
|
4034
4089
|
const [markedModule, PrismModule2, DOMPurifyModule] = await Promise.all([
|
|
4035
4090
|
import("marked"),
|
|
@@ -4050,7 +4105,6 @@ function textPlugin() {
|
|
|
4050
4105
|
try {
|
|
4051
4106
|
const codeBlocks = container.querySelectorAll("pre code");
|
|
4052
4107
|
if (codeBlocks.length > 0) {
|
|
4053
|
-
await loadPrismCss(isDark ? "dark" : "light");
|
|
4054
4108
|
codeBlocks.forEach((block) => {
|
|
4055
4109
|
const parent = block.parentElement;
|
|
4056
4110
|
if (parent && !parent.className.includes("language-")) {
|
|
@@ -4174,9 +4228,6 @@ function textPlugin() {
|
|
|
4174
4228
|
console.warn(`Prism failed to load language component for: ${lang}`, e);
|
|
4175
4229
|
}
|
|
4176
4230
|
}
|
|
4177
|
-
await loadPrismCss(isDark ? "dark" : "light").catch((error) => {
|
|
4178
|
-
console.warn("Prism CSS failed to load; rendering code without external theme:", error);
|
|
4179
|
-
});
|
|
4180
4231
|
const codeText = text.length > MAX_RENDER_CHARS ? text.slice(0, MAX_RENDER_CHARS) : text;
|
|
4181
4232
|
const totalLines = countLines(text);
|
|
4182
4233
|
const shownLines = countLines(codeText);
|
|
@@ -4704,10 +4755,11 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4704
4755
|
const baseViewport = page.getViewport({ scale: 1 });
|
|
4705
4756
|
pagesMeta.push({
|
|
4706
4757
|
width: baseViewport.width,
|
|
4707
|
-
height: baseViewport.height
|
|
4758
|
+
height: baseViewport.height,
|
|
4759
|
+
rotation: getPdfPageRotation(page)
|
|
4708
4760
|
});
|
|
4709
4761
|
} catch {
|
|
4710
|
-
pagesMeta.push({ width: 612, height: 792 });
|
|
4762
|
+
pagesMeta.push({ width: 612, height: 792, rotation: 0 });
|
|
4711
4763
|
}
|
|
4712
4764
|
}
|
|
4713
4765
|
const pageStates = [];
|
|
@@ -4742,7 +4794,7 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4742
4794
|
const page = await pdfDocument.getPage(pageIdx + 1);
|
|
4743
4795
|
const meta = pagesMeta[pageIdx];
|
|
4744
4796
|
const scale = options.fit === "actual" ? zoomFactor : Math.max(0.05, Math.min(5, getPdfAvailableWidth(size.width) / rotatedPdfWidth(meta, rotation) * zoomFactor));
|
|
4745
|
-
const viewport = page.getViewport({ scale, rotation });
|
|
4797
|
+
const viewport = page.getViewport({ scale, rotation: getPdfRenderRotation(meta, rotation) });
|
|
4746
4798
|
const outputScale = getPdfOutputScale();
|
|
4747
4799
|
const cssWidth = Math.floor(viewport.width);
|
|
4748
4800
|
const cssHeight = Math.floor(viewport.height);
|
|
@@ -4781,6 +4833,8 @@ async function renderPdfDocumentPreview(options) {
|
|
|
4781
4833
|
const span = document.createElement("span");
|
|
4782
4834
|
span.textContent = str;
|
|
4783
4835
|
span.style.fontSize = `${fontHeight}px`;
|
|
4836
|
+
span.style.lineHeight = "1";
|
|
4837
|
+
span.style.height = `${fontHeight}px`;
|
|
4784
4838
|
span.style.fontFamily = item.fontName || "sans-serif";
|
|
4785
4839
|
span.style.left = `${tx[4]}px`;
|
|
4786
4840
|
span.style.top = `${tx[5] - fontHeight}px`;
|
|
@@ -4978,6 +5032,13 @@ function appendPdfSummary(parent, label, value) {
|
|
|
4978
5032
|
function normalizePdfRotation(value) {
|
|
4979
5033
|
return (value % 360 + 360) % 360;
|
|
4980
5034
|
}
|
|
5035
|
+
function getPdfPageRotation(page) {
|
|
5036
|
+
const rotation = Number(page.rotate);
|
|
5037
|
+
return Number.isFinite(rotation) ? normalizePdfRotation(rotation) : 0;
|
|
5038
|
+
}
|
|
5039
|
+
function getPdfRenderRotation(meta, userRotation) {
|
|
5040
|
+
return normalizePdfRotation((meta.rotation || 0) + userRotation);
|
|
5041
|
+
}
|
|
4981
5042
|
function isPdfRotatedSideways(rotation) {
|
|
4982
5043
|
const normalized = normalizePdfRotation(rotation);
|
|
4983
5044
|
return normalized === 90 || normalized === 270;
|
|
@@ -5948,13 +6009,13 @@ function officePlugin(options = {}) {
|
|
|
5948
6009
|
if (conversionContext && await shouldUseOfficeConversion(options, conversionContext)) {
|
|
5949
6010
|
delegatedInstance = await renderConvertedOfficePreview(panel, ctx, options, conversionContext);
|
|
5950
6011
|
} else if (packageFormat === "docx" && !fileIsDocx(extension)) {
|
|
5951
|
-
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
6012
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
|
|
5952
6013
|
} else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
|
|
5953
6014
|
await renderSheet(panel, arrayBuffer, "xlsx");
|
|
5954
6015
|
} else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
|
|
5955
6016
|
await renderPptx(panel, arrayBuffer);
|
|
5956
6017
|
} else if (fileIsDocx(extension)) {
|
|
5957
|
-
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
6018
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer, ctx.options.fit);
|
|
5958
6019
|
} else if (extension === "rtf") {
|
|
5959
6020
|
renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
|
|
5960
6021
|
} else if (extension === "odt") {
|
|
@@ -5963,7 +6024,7 @@ function officePlugin(options = {}) {
|
|
|
5963
6024
|
renderOpenDocumentXml(panel, "FODT \u6587\u6863", await readTextFromBuffer(arrayBuffer));
|
|
5964
6025
|
} else if (extension === "fods") {
|
|
5965
6026
|
renderFlatOds(panel, await readTextFromBuffer(arrayBuffer));
|
|
5966
|
-
} else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension)) {
|
|
6027
|
+
} else if (packagedOfficeCandidates.has(extension) && await renderPackagedOfficePreview(panel, arrayBuffer, extension, ctx.options.fit)) {
|
|
5967
6028
|
} else if (sheetExtensions.has(extension)) {
|
|
5968
6029
|
await renderSheet(panel, arrayBuffer, extension);
|
|
5969
6030
|
} else if (["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
|
|
@@ -6165,7 +6226,7 @@ async function detectPackagedOfficeFormat(arrayBuffer) {
|
|
|
6165
6226
|
}
|
|
6166
6227
|
return void 0;
|
|
6167
6228
|
}
|
|
6168
|
-
async function renderDocx(panel, arrayBuffer) {
|
|
6229
|
+
async function renderDocx(panel, arrayBuffer, fit) {
|
|
6169
6230
|
const content = document.createElement("div");
|
|
6170
6231
|
content.className = "ofv-docx-document";
|
|
6171
6232
|
const styleContainer = document.createElement("div");
|
|
@@ -6204,7 +6265,7 @@ async function renderDocx(panel, arrayBuffer) {
|
|
|
6204
6265
|
return () => void 0;
|
|
6205
6266
|
}
|
|
6206
6267
|
panel.append(content);
|
|
6207
|
-
disposeFit = fitDocxPages(content);
|
|
6268
|
+
disposeFit = fitDocxPages(content, fit);
|
|
6208
6269
|
return () => {
|
|
6209
6270
|
disposeFit?.();
|
|
6210
6271
|
styleContainer.remove();
|
|
@@ -6802,7 +6863,8 @@ function looksLikeDocxTextboxHeading(value) {
|
|
|
6802
6863
|
return text.length > 0 && text.length <= 12 && !/[0-9@.::]/.test(text);
|
|
6803
6864
|
}
|
|
6804
6865
|
async function normalizeDocxLayout(container, arrayBuffer) {
|
|
6805
|
-
const hints = await readDocxLayoutHints(arrayBuffer);
|
|
6866
|
+
const [hints, charts] = await Promise.all([readDocxLayoutHints(arrayBuffer), readDocxCharts(arrayBuffer)]);
|
|
6867
|
+
repairDocxChartPlaceholders(container, charts);
|
|
6806
6868
|
const pages = container.querySelectorAll("section.ofv-docx");
|
|
6807
6869
|
for (const page of pages) {
|
|
6808
6870
|
repairDocxShapeFills(page);
|
|
@@ -6817,6 +6879,75 @@ async function normalizeDocxLayout(container, arrayBuffer) {
|
|
|
6817
6879
|
}
|
|
6818
6880
|
}
|
|
6819
6881
|
}
|
|
6882
|
+
async function readDocxCharts(arrayBuffer) {
|
|
6883
|
+
try {
|
|
6884
|
+
const zip = await import_jszip3.default.loadAsync(arrayBuffer);
|
|
6885
|
+
const documentXml = await zip.file("word/document.xml")?.async("text");
|
|
6886
|
+
const documentDoc = documentXml ? parseOfficeXml(documentXml) : void 0;
|
|
6887
|
+
if (!documentDoc) {
|
|
6888
|
+
return [];
|
|
6889
|
+
}
|
|
6890
|
+
const relationships = await readOfficeRelationships(zip, "word/document.xml");
|
|
6891
|
+
const chartDrawings = Array.from(documentDoc.getElementsByTagName("*")).filter((element) => element.localName === "inline" || element.localName === "anchor").map((element) => readDocxChartDrawing(element)).filter((item) => Boolean(item?.relationshipId));
|
|
6892
|
+
const charts = [];
|
|
6893
|
+
for (const [index, drawing] of chartDrawings.entries()) {
|
|
6894
|
+
const chartRel = relationships.find((rel) => rel.id === drawing.relationshipId && /\/chart$/i.test(rel.type));
|
|
6895
|
+
const chartPath = resolveOfficeRelationshipTarget("word/document.xml", chartRel?.target);
|
|
6896
|
+
const chartXml = chartPath ? await zip.file(chartPath)?.async("text") : void 0;
|
|
6897
|
+
const chart = chartXml ? parseChartXml(chartXml, chartPath?.split("/").pop() || `chart${index + 1}.xml`) : null;
|
|
6898
|
+
if (chart) {
|
|
6899
|
+
charts.push({ ...chart, widthPt: drawing.widthPt, heightPt: drawing.heightPt });
|
|
6900
|
+
}
|
|
6901
|
+
}
|
|
6902
|
+
return charts;
|
|
6903
|
+
} catch {
|
|
6904
|
+
return [];
|
|
6905
|
+
}
|
|
6906
|
+
}
|
|
6907
|
+
function readDocxChartDrawing(element) {
|
|
6908
|
+
const chart = Array.from(element.getElementsByTagName("*")).find((child) => child.localName === "chart");
|
|
6909
|
+
const relationshipId = chart ? getXmlAttribute3(chart, "id") : "";
|
|
6910
|
+
if (!relationshipId) {
|
|
6911
|
+
return void 0;
|
|
6912
|
+
}
|
|
6913
|
+
const extent = Array.from(element.children).find((child) => child.localName === "extent");
|
|
6914
|
+
return {
|
|
6915
|
+
relationshipId,
|
|
6916
|
+
widthPt: emuToPt(Number(extent?.getAttribute("cx") || 0)),
|
|
6917
|
+
heightPt: emuToPt(Number(extent?.getAttribute("cy") || 0))
|
|
6918
|
+
};
|
|
6919
|
+
}
|
|
6920
|
+
function repairDocxChartPlaceholders(container, charts) {
|
|
6921
|
+
if (charts.length === 0) {
|
|
6922
|
+
return;
|
|
6923
|
+
}
|
|
6924
|
+
const placeholders = Array.from(container.querySelectorAll("section.ofv-docx div")).filter(isDocxChartPlaceholder);
|
|
6925
|
+
if (placeholders.length !== charts.length) {
|
|
6926
|
+
return;
|
|
6927
|
+
}
|
|
6928
|
+
placeholders.forEach((placeholder, index) => {
|
|
6929
|
+
const chart = charts[index];
|
|
6930
|
+
placeholder.classList.add("ofv-docx-chart-preview");
|
|
6931
|
+
placeholder.dataset.ofvDocxChartPreview = "true";
|
|
6932
|
+
if (chart.widthPt > 0) {
|
|
6933
|
+
placeholder.style.width ||= `${formatCssNumber(chart.widthPt)}pt`;
|
|
6934
|
+
}
|
|
6935
|
+
if (chart.heightPt > 0) {
|
|
6936
|
+
placeholder.style.height ||= `${formatCssNumber(chart.heightPt)}pt`;
|
|
6937
|
+
}
|
|
6938
|
+
placeholder.replaceChildren(renderChartSvg(chart));
|
|
6939
|
+
});
|
|
6940
|
+
}
|
|
6941
|
+
function isDocxChartPlaceholder(element) {
|
|
6942
|
+
if (element.dataset.ofvDocxChartPreview === "true" || element.children.length > 0 || normalizePreviewText(element.textContent || "")) {
|
|
6943
|
+
return false;
|
|
6944
|
+
}
|
|
6945
|
+
const display = element.style.display;
|
|
6946
|
+
const position = element.style.position;
|
|
6947
|
+
const width = parseCssPixelValue(element.style.width);
|
|
6948
|
+
const height = parseCssPixelValue(element.style.height);
|
|
6949
|
+
return display === "inline-block" && position === "relative" && width >= 120 && height >= 80;
|
|
6950
|
+
}
|
|
6820
6951
|
function repairDocxHeadingShapeAlignment(page) {
|
|
6821
6952
|
for (const paragraph of page.querySelectorAll("p")) {
|
|
6822
6953
|
const text = normalizePreviewText(paragraph.textContent || "");
|
|
@@ -6967,14 +7098,19 @@ function repairDocxShapeFills(page) {
|
|
|
6967
7098
|
}
|
|
6968
7099
|
}
|
|
6969
7100
|
function repairDocxFloatingPictures(page, hints) {
|
|
6970
|
-
const
|
|
6971
|
-
if (
|
|
7101
|
+
const pageHints = hints.floatingPictures.filter((item) => item.relativeFrom === "column" && item.wrap === "square");
|
|
7102
|
+
if (pageHints.length === 0) {
|
|
6972
7103
|
return;
|
|
6973
7104
|
}
|
|
6974
|
-
const
|
|
6975
|
-
|
|
7105
|
+
const images = Array.from(page.querySelectorAll("img")).filter(
|
|
7106
|
+
(image) => image.closest("[data-ofv-docx-float-repaired='true']") === null
|
|
7107
|
+
);
|
|
7108
|
+
if (images.length === 0 || images.length !== pageHints.length) {
|
|
6976
7109
|
return;
|
|
6977
7110
|
}
|
|
7111
|
+
images.forEach((image, index) => repairDocxFloatingPicture(page, image, pageHints[index]));
|
|
7112
|
+
}
|
|
7113
|
+
function repairDocxFloatingPicture(page, image, hint) {
|
|
6978
7114
|
const wrapper = image.parentElement;
|
|
6979
7115
|
if (!wrapper || wrapper.dataset.ofvDocxFloatRepaired === "true") {
|
|
6980
7116
|
return;
|
|
@@ -7020,7 +7156,7 @@ function parseCssLineHeight(value) {
|
|
|
7020
7156
|
const parsed = Number.parseFloat(trimmed);
|
|
7021
7157
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
7022
7158
|
}
|
|
7023
|
-
function fitDocxPages(container) {
|
|
7159
|
+
function fitDocxPages(container, fit) {
|
|
7024
7160
|
const wrapper = container.querySelector(".ofv-docx-wrapper");
|
|
7025
7161
|
if (!wrapper) {
|
|
7026
7162
|
return () => void 0;
|
|
@@ -7033,6 +7169,7 @@ function fitDocxPages(container) {
|
|
|
7033
7169
|
return;
|
|
7034
7170
|
}
|
|
7035
7171
|
const availableWidth = Math.max(1, container.clientWidth - 48);
|
|
7172
|
+
const availableHeight = container.clientHeight > 0 ? Math.max(1, container.clientHeight - 48) : void 0;
|
|
7036
7173
|
const pageWidth = Math.max(
|
|
7037
7174
|
1,
|
|
7038
7175
|
...frames.map(({ page }) => {
|
|
@@ -7040,14 +7177,24 @@ function fitDocxPages(container) {
|
|
|
7040
7177
|
return page.offsetWidth || rectWidth || parseCssPixelValue(page.style.width) || 794;
|
|
7041
7178
|
})
|
|
7042
7179
|
);
|
|
7043
|
-
const
|
|
7180
|
+
const pageHeight = Math.max(
|
|
7181
|
+
1,
|
|
7182
|
+
...frames.map(({ page }) => page.offsetHeight || page.getBoundingClientRect().height || parseCssPixelValue(page.style.height) || 1123)
|
|
7183
|
+
);
|
|
7184
|
+
const scale = getDocxFitScale(fit, availableWidth, availableHeight, pageWidth, pageHeight);
|
|
7044
7185
|
const userZoom = parseCssPixelValue(panel?.style.getPropertyValue("--ofv-office-zoom") || "1") || 1;
|
|
7045
7186
|
wrapper.style.setProperty("--ofv-docx-scale", formatCssNumber(scale));
|
|
7046
7187
|
wrapper.style.setProperty("--ofv-docx-page-width", `${pageWidth}px`);
|
|
7188
|
+
wrapper.style.width = `${Math.ceil(pageWidth * scale * userZoom + 48)}px`;
|
|
7189
|
+
wrapper.style.maxWidth = "none";
|
|
7190
|
+
wrapper.style.overflow = "visible";
|
|
7047
7191
|
for (const { frame, page } of frames) {
|
|
7048
|
-
const
|
|
7049
|
-
|
|
7050
|
-
|
|
7192
|
+
const framePageHeight = page.offsetHeight || page.getBoundingClientRect().height || parseCssPixelValue(page.style.height);
|
|
7193
|
+
const framePageWidth = page.offsetWidth || page.getBoundingClientRect().width || parseCssPixelValue(page.style.width) || pageWidth;
|
|
7194
|
+
frame.style.width = `${Math.ceil(framePageWidth * scale * userZoom)}px`;
|
|
7195
|
+
frame.style.maxWidth = "none";
|
|
7196
|
+
if (framePageHeight > 0) {
|
|
7197
|
+
frame.style.height = `${Math.ceil(framePageHeight * scale * userZoom)}px`;
|
|
7051
7198
|
}
|
|
7052
7199
|
}
|
|
7053
7200
|
};
|
|
@@ -7072,6 +7219,26 @@ function fitDocxPages(container) {
|
|
|
7072
7219
|
observer.disconnect();
|
|
7073
7220
|
};
|
|
7074
7221
|
}
|
|
7222
|
+
function getDocxFitScale(fit, availableWidth, availableHeight, pageWidth, pageHeight) {
|
|
7223
|
+
const widthScale = availableWidth / pageWidth;
|
|
7224
|
+
const heightScale = availableHeight ? availableHeight / pageHeight : void 0;
|
|
7225
|
+
if (fit === "actual") {
|
|
7226
|
+
return 1;
|
|
7227
|
+
}
|
|
7228
|
+
if (fit === "height") {
|
|
7229
|
+
return Math.max(0.1, heightScale ?? widthScale);
|
|
7230
|
+
}
|
|
7231
|
+
if (fit === "cover") {
|
|
7232
|
+
return Math.max(0.1, Math.max(widthScale, heightScale ?? widthScale));
|
|
7233
|
+
}
|
|
7234
|
+
if (fit === "scale-down") {
|
|
7235
|
+
return Math.min(1, Math.max(0.1, Math.min(widthScale, heightScale ?? widthScale)));
|
|
7236
|
+
}
|
|
7237
|
+
if (fit === "contain") {
|
|
7238
|
+
return Math.max(0.1, Math.min(widthScale, heightScale ?? widthScale));
|
|
7239
|
+
}
|
|
7240
|
+
return Math.max(0.1, widthScale);
|
|
7241
|
+
}
|
|
7075
7242
|
function ensureDocxPageFrames(wrapper) {
|
|
7076
7243
|
const pages = Array.from(wrapper.querySelectorAll("section.ofv-docx"));
|
|
7077
7244
|
return pages.map((page) => {
|
|
@@ -7603,9 +7770,9 @@ function parseChartXml(xml, fallbackName) {
|
|
|
7603
7770
|
return null;
|
|
7604
7771
|
}
|
|
7605
7772
|
const type = detectChartType(doc);
|
|
7606
|
-
const title =
|
|
7773
|
+
const title = readChartTitle(doc, fallbackName);
|
|
7607
7774
|
const seriesElements = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ser");
|
|
7608
|
-
const series = seriesElements.map((element, index) => parseChartSeries(element, index)).filter((item) => item.values.length > 0);
|
|
7775
|
+
const series = seriesElements.map((element, index) => ({ ...parseChartSeries(element, index), color: readChartSeriesColor(element) })).filter((item) => item.values.length > 0);
|
|
7609
7776
|
if (series.length === 0) {
|
|
7610
7777
|
return null;
|
|
7611
7778
|
}
|
|
@@ -7614,9 +7781,21 @@ function parseChartXml(xml, fallbackName) {
|
|
|
7614
7781
|
type,
|
|
7615
7782
|
title,
|
|
7616
7783
|
categories: series.find((item) => item.categories.length > 0)?.categories || [],
|
|
7617
|
-
series: series.map((item) => ({ name: item.name, values: item.values }))
|
|
7784
|
+
series: series.map((item) => ({ name: item.name, values: item.values, color: item.color }))
|
|
7618
7785
|
};
|
|
7619
7786
|
}
|
|
7787
|
+
function readChartTitle(doc, fallbackName) {
|
|
7788
|
+
const titleElement = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "title");
|
|
7789
|
+
if (!titleElement) {
|
|
7790
|
+
return fallbackName.replace(/\.xml$/i, "");
|
|
7791
|
+
}
|
|
7792
|
+
const explicitTitle = chartText(titleElement);
|
|
7793
|
+
if (explicitTitle) {
|
|
7794
|
+
return explicitTitle;
|
|
7795
|
+
}
|
|
7796
|
+
const language = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "lang")?.getAttribute("val") || "";
|
|
7797
|
+
return /^zh\b/i.test(language) ? "\u56FE\u8868\u6807\u9898" : "Chart Title";
|
|
7798
|
+
}
|
|
7620
7799
|
function detectChartType(doc) {
|
|
7621
7800
|
const chartType = Array.from(doc.getElementsByTagName("*")).find(
|
|
7622
7801
|
(element) => element.localName.endsWith("Chart") && element.localName !== "chart"
|
|
@@ -7633,6 +7812,31 @@ function parseChartSeries(element, index) {
|
|
|
7633
7812
|
categories: stringsFromFirst(element, "cat")
|
|
7634
7813
|
};
|
|
7635
7814
|
}
|
|
7815
|
+
function readChartSeriesColor(element) {
|
|
7816
|
+
const shape = Array.from(element?.children || []).find((child) => child.localName === "spPr");
|
|
7817
|
+
const color = Array.from(shape?.getElementsByTagName("*") || []).find(
|
|
7818
|
+
(child) => child.localName === "srgbClr" || child.localName === "schemeClr"
|
|
7819
|
+
);
|
|
7820
|
+
if (!color) {
|
|
7821
|
+
return void 0;
|
|
7822
|
+
}
|
|
7823
|
+
if (color.localName === "srgbClr") {
|
|
7824
|
+
const value = color.getAttribute("val") || "";
|
|
7825
|
+
return /^[\da-f]{6}$/i.test(value) ? `#${value}` : void 0;
|
|
7826
|
+
}
|
|
7827
|
+
return chartSchemeColor(color.getAttribute("val") || "");
|
|
7828
|
+
}
|
|
7829
|
+
function chartSchemeColor(value) {
|
|
7830
|
+
const colors = {
|
|
7831
|
+
accent1: "#156082",
|
|
7832
|
+
accent2: "#e97132",
|
|
7833
|
+
accent3: "#196b24",
|
|
7834
|
+
accent4: "#0f9ed5",
|
|
7835
|
+
accent5: "#a02b93",
|
|
7836
|
+
accent6: "#4ea72e"
|
|
7837
|
+
};
|
|
7838
|
+
return colors[value];
|
|
7839
|
+
}
|
|
7636
7840
|
function renderChartPreviewSection(charts) {
|
|
7637
7841
|
const section = createSection("\u8868\u683C\u56FE\u8868\u9884\u89C8");
|
|
7638
7842
|
const grid = document.createElement("div");
|
|
@@ -7670,17 +7874,39 @@ function renderChartCard(chart) {
|
|
|
7670
7874
|
}
|
|
7671
7875
|
function renderChartSvg(chart) {
|
|
7672
7876
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
7673
|
-
svg.setAttribute("viewBox", "0 0 640
|
|
7877
|
+
svg.setAttribute("viewBox", "0 0 640 380");
|
|
7674
7878
|
svg.setAttribute("role", "img");
|
|
7675
7879
|
svg.setAttribute("aria-label", chart.title);
|
|
7676
7880
|
svg.classList.add("ofv-chart-svg");
|
|
7677
7881
|
const allValues = chart.series.flatMap((item) => item.values).filter((value) => Number.isFinite(value));
|
|
7678
7882
|
const max = Math.max(1, ...allValues);
|
|
7679
7883
|
const min = Math.min(0, ...allValues);
|
|
7884
|
+
const axisScale = createChartAxisScale(max, min);
|
|
7885
|
+
const axisMax = axisScale.max;
|
|
7886
|
+
const axisMin = axisScale.min;
|
|
7680
7887
|
const span = max - min || 1;
|
|
7681
|
-
const colors = ["#
|
|
7682
|
-
const plot = { x:
|
|
7683
|
-
|
|
7888
|
+
const colors = ["#156082", "#e97132", "#196b24", "#0f9ed5", "#a02b93", "#4ea72e"];
|
|
7889
|
+
const plot = { x: 74, y: 74, width: 526, height: 214 };
|
|
7890
|
+
const categories = chart.categories.length > 0 ? chart.categories : chart.series[0]?.values.map((_, index) => String(index + 1)) || [];
|
|
7891
|
+
const title = appendSvg(svg, "text", { x: 320, y: 34, class: "ofv-chart-title", "text-anchor": "middle" });
|
|
7892
|
+
title.textContent = chart.title;
|
|
7893
|
+
for (const value of axisScale.ticks) {
|
|
7894
|
+
const y = plot.y + plot.height - (value - axisMin) / (axisMax - axisMin || 1) * plot.height;
|
|
7895
|
+
appendSvg(svg, "line", {
|
|
7896
|
+
x1: plot.x,
|
|
7897
|
+
y1: Number(y.toFixed(1)),
|
|
7898
|
+
x2: plot.x + plot.width,
|
|
7899
|
+
y2: Number(y.toFixed(1)),
|
|
7900
|
+
class: value === 0 ? "ofv-chart-axis" : "ofv-chart-gridline"
|
|
7901
|
+
});
|
|
7902
|
+
const label = appendSvg(svg, "text", {
|
|
7903
|
+
x: plot.x - 12,
|
|
7904
|
+
y: Number((y + 4).toFixed(1)),
|
|
7905
|
+
class: "ofv-chart-label",
|
|
7906
|
+
"text-anchor": "end"
|
|
7907
|
+
});
|
|
7908
|
+
label.textContent = formatChartTick(value);
|
|
7909
|
+
}
|
|
7684
7910
|
appendSvg(svg, "line", {
|
|
7685
7911
|
x1: plot.x,
|
|
7686
7912
|
y1: plot.y + plot.height,
|
|
@@ -7688,28 +7914,47 @@ function renderChartSvg(chart) {
|
|
|
7688
7914
|
y2: plot.y + plot.height,
|
|
7689
7915
|
class: "ofv-chart-axis"
|
|
7690
7916
|
});
|
|
7691
|
-
chart.
|
|
7692
|
-
const
|
|
7693
|
-
const
|
|
7694
|
-
const
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
const
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7917
|
+
if (chart.type.includes("bar") || chart.type.includes("col")) {
|
|
7918
|
+
const categoryCount = Math.max(1, categories.length, ...chart.series.map((series) => series.values.length));
|
|
7919
|
+
const groupWidth = plot.width / categoryCount;
|
|
7920
|
+
const clusterWidth = groupWidth * 0.58;
|
|
7921
|
+
const barWidth = Math.max(5, Math.min(28, clusterWidth / Math.max(1, chart.series.length)));
|
|
7922
|
+
const zeroY = plot.y + plot.height - (0 - axisMin) / (axisMax - axisMin || 1) * plot.height;
|
|
7923
|
+
categories.slice(0, categoryCount).forEach((category, index) => {
|
|
7924
|
+
const x = plot.x + groupWidth * (index + 0.5);
|
|
7925
|
+
const label = appendSvg(svg, "text", {
|
|
7926
|
+
x: Number(x.toFixed(1)),
|
|
7927
|
+
y: plot.y + plot.height + 22,
|
|
7928
|
+
class: "ofv-chart-label",
|
|
7929
|
+
"text-anchor": "middle"
|
|
7930
|
+
});
|
|
7931
|
+
label.textContent = truncateChartLabel(category);
|
|
7932
|
+
});
|
|
7933
|
+
chart.series.forEach((series, seriesIndex) => {
|
|
7934
|
+
const color = series.color || colors[seriesIndex % colors.length];
|
|
7935
|
+
series.values.forEach((value, index) => {
|
|
7936
|
+
const groupCenter = plot.x + groupWidth * (index + 0.5);
|
|
7937
|
+
const x = groupCenter - barWidth * chart.series.length / 2 + seriesIndex * barWidth + barWidth * 0.12;
|
|
7938
|
+
const y = plot.y + plot.height - (value - axisMin) / (axisMax - axisMin || 1) * plot.height;
|
|
7703
7939
|
appendSvg(svg, "rect", {
|
|
7704
|
-
x,
|
|
7705
|
-
y: Math.min(
|
|
7706
|
-
width: barWidth,
|
|
7707
|
-
height: Math.max(1, Math.abs(zeroY -
|
|
7940
|
+
x: Number(x.toFixed(1)),
|
|
7941
|
+
y: Number(Math.min(y, zeroY).toFixed(1)),
|
|
7942
|
+
width: Number((barWidth * 0.76).toFixed(1)),
|
|
7943
|
+
height: Number(Math.max(1, Math.abs(zeroY - y)).toFixed(1)),
|
|
7708
7944
|
fill: color,
|
|
7709
7945
|
"data-index": index
|
|
7710
7946
|
});
|
|
7711
7947
|
});
|
|
7712
|
-
}
|
|
7948
|
+
});
|
|
7949
|
+
} else {
|
|
7950
|
+
chart.series.forEach((series, seriesIndex) => {
|
|
7951
|
+
const color = series.color || colors[seriesIndex % colors.length];
|
|
7952
|
+
const step = series.values.length > 1 ? plot.width / (series.values.length - 1) : plot.width;
|
|
7953
|
+
const lineSpan = axisMax - axisMin || span;
|
|
7954
|
+
const points = series.values.map((value, index) => ({
|
|
7955
|
+
x: plot.x + index * step,
|
|
7956
|
+
y: plot.y + plot.height - (value - axisMin) / lineSpan * plot.height
|
|
7957
|
+
}));
|
|
7713
7958
|
appendSvg(svg, "polyline", {
|
|
7714
7959
|
points: points.map((point) => `${point.x.toFixed(1)},${point.y.toFixed(1)}`).join(" "),
|
|
7715
7960
|
fill: "none",
|
|
@@ -7721,11 +7966,49 @@ function renderChartSvg(chart) {
|
|
|
7721
7966
|
for (const point of points.slice(0, 80)) {
|
|
7722
7967
|
appendSvg(svg, "circle", { cx: point.x, cy: point.y, r: 3, fill: color });
|
|
7723
7968
|
}
|
|
7724
|
-
}
|
|
7725
|
-
|
|
7726
|
-
|
|
7969
|
+
});
|
|
7970
|
+
}
|
|
7971
|
+
appendChartLegend(svg, chart, colors, 348);
|
|
7727
7972
|
return svg;
|
|
7728
7973
|
}
|
|
7974
|
+
function appendChartLegend(svg, chart, colors, y) {
|
|
7975
|
+
const itemWidth = 86;
|
|
7976
|
+
const startX = 320 - chart.series.length * itemWidth / 2;
|
|
7977
|
+
chart.series.forEach((series, seriesIndex) => {
|
|
7978
|
+
const color = series.color || colors[seriesIndex % colors.length];
|
|
7979
|
+
appendLegend(svg, series.name, color, startX + seriesIndex * itemWidth, y);
|
|
7980
|
+
});
|
|
7981
|
+
}
|
|
7982
|
+
function createChartAxisScale(max, min) {
|
|
7983
|
+
const axisMin = Math.min(0, min);
|
|
7984
|
+
const positiveMax = Math.max(1, max);
|
|
7985
|
+
const step = niceChartStep((positiveMax - axisMin) / 5);
|
|
7986
|
+
let axisMax = Math.ceil(positiveMax / step) * step;
|
|
7987
|
+
if (axisMax <= positiveMax) {
|
|
7988
|
+
axisMax += step;
|
|
7989
|
+
}
|
|
7990
|
+
const ticks = [];
|
|
7991
|
+
for (let value = axisMin; value <= axisMax + step / 2; value += step) {
|
|
7992
|
+
ticks.push(Number(value.toFixed(6)));
|
|
7993
|
+
}
|
|
7994
|
+
return { min: axisMin, max: axisMax, ticks };
|
|
7995
|
+
}
|
|
7996
|
+
function niceChartStep(rawStep) {
|
|
7997
|
+
if (rawStep <= 0) {
|
|
7998
|
+
return 1;
|
|
7999
|
+
}
|
|
8000
|
+
const magnitude = 10 ** Math.floor(Math.log10(rawStep));
|
|
8001
|
+
const normalized = rawStep / magnitude;
|
|
8002
|
+
const nice = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;
|
|
8003
|
+
return nice * magnitude;
|
|
8004
|
+
}
|
|
8005
|
+
function formatChartTick(value) {
|
|
8006
|
+
const rounded = Math.abs(value) < 1 ? Number(value.toFixed(1)) : Number(value.toFixed(0));
|
|
8007
|
+
return String(rounded);
|
|
8008
|
+
}
|
|
8009
|
+
function truncateChartLabel(value) {
|
|
8010
|
+
return value.length > 10 ? `${value.slice(0, 10)}...` : value;
|
|
8011
|
+
}
|
|
7729
8012
|
function appendLegend(svg, label, color, x, y) {
|
|
7730
8013
|
appendSvg(svg, "rect", { x, y: y - 10, width: 12, height: 12, rx: 2, fill: color });
|
|
7731
8014
|
const text = appendSvg(svg, "text", { x: x + 18, y, class: "ofv-chart-label" });
|
|
@@ -8459,7 +8742,7 @@ function renderOpenDocumentPresentationXml(panel, xml) {
|
|
|
8459
8742
|
renderPresentationInsight(panel, inspectOpenDocumentPresentation("FODP \u6F14\u793A\u6587\u7A3F", xml, 0));
|
|
8460
8743
|
renderOpenDocumentPresentation(panel, "FODP \u6F14\u793A\u6587\u7A3F", xml, []);
|
|
8461
8744
|
}
|
|
8462
|
-
async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
8745
|
+
async function renderPackagedOfficePreview(panel, arrayBuffer, extension, fit) {
|
|
8463
8746
|
let zip;
|
|
8464
8747
|
try {
|
|
8465
8748
|
zip = await import_jszip3.default.loadAsync(arrayBuffer);
|
|
@@ -8470,7 +8753,7 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
|
8470
8753
|
const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
|
|
8471
8754
|
const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
|
|
8472
8755
|
if (hasEntry("word/document.xml")) {
|
|
8473
|
-
await renderDocx(panel, arrayBuffer);
|
|
8756
|
+
await renderDocx(panel, arrayBuffer, fit);
|
|
8474
8757
|
return true;
|
|
8475
8758
|
}
|
|
8476
8759
|
if (hasEntry("xl/workbook.xml")) {
|