@files-preview-app/preview-file 1.2.9 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/angular.cjs +668 -154
- package/dist/angular.cjs.map +1 -1
- package/dist/angular.js +667 -154
- package/dist/angular.js.map +1 -1
- package/dist/index.cjs +711 -154
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +709 -155
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +668 -154
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +667 -154
- package/dist/react.js.map +1 -1
- package/dist/vue.cjs +668 -154
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +667 -154
- package/dist/vue.js.map +1 -1
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -2,6 +2,7 @@ import { memo, forwardRef, useRef, useImperativeHandle, useEffect } from 'react'
|
|
|
2
2
|
import DOMPurify6 from 'dompurify';
|
|
3
3
|
import * as pdfjsLib from 'pdfjs-dist';
|
|
4
4
|
import * as docx from 'docx-preview';
|
|
5
|
+
import * as fflate from 'fflate';
|
|
5
6
|
import { unzipSync, strFromU8, unzip } from 'fflate';
|
|
6
7
|
import * as XLSX from 'xlsx';
|
|
7
8
|
import hljs from 'highlight.js';
|
|
@@ -338,16 +339,21 @@ async function sourceToArrayBuffer(source, signal) {
|
|
|
338
339
|
metadata.mimeType = source.type || void 0;
|
|
339
340
|
metadata.extension = extractExtension(source.name);
|
|
340
341
|
buffer = await source.arrayBuffer();
|
|
341
|
-
} else if (source instanceof Blob) {
|
|
342
|
+
} else if (source instanceof Blob || source && typeof source.arrayBuffer === "function" && typeof source.size === "number") {
|
|
342
343
|
metadata.size = source.size;
|
|
343
344
|
metadata.mimeType = source.type || void 0;
|
|
345
|
+
if (source.name) {
|
|
346
|
+
metadata.name = source.name;
|
|
347
|
+
metadata.extension = extractExtension(source.name);
|
|
348
|
+
}
|
|
344
349
|
buffer = await source.arrayBuffer();
|
|
345
|
-
} else if (source instanceof ArrayBuffer) {
|
|
350
|
+
} else if (source instanceof ArrayBuffer || Object.prototype.toString.call(source) === "[object ArrayBuffer]" || source && typeof source.byteLength === "number" && typeof source.slice === "function") {
|
|
346
351
|
buffer = source;
|
|
347
|
-
} else if (source instanceof Uint8Array) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
352
|
+
} else if (source instanceof Uint8Array || ArrayBuffer.isView(source)) {
|
|
353
|
+
const view = source;
|
|
354
|
+
buffer = view.buffer.slice(
|
|
355
|
+
view.byteOffset,
|
|
356
|
+
view.byteOffset + view.byteLength
|
|
351
357
|
);
|
|
352
358
|
} else {
|
|
353
359
|
throw new Error("Unsupported file source type");
|
|
@@ -432,6 +438,45 @@ function createElement(tag, attrs, ...children) {
|
|
|
432
438
|
}
|
|
433
439
|
return el;
|
|
434
440
|
}
|
|
441
|
+
var DB_NAME = "PreviewFileTransferDB";
|
|
442
|
+
var DB_STORE = "transfers";
|
|
443
|
+
function openDB() {
|
|
444
|
+
return new Promise((resolve, reject) => {
|
|
445
|
+
if (typeof indexedDB === "undefined") {
|
|
446
|
+
return reject(new Error("IndexedDB is not available"));
|
|
447
|
+
}
|
|
448
|
+
const req = indexedDB.open(DB_NAME, 1);
|
|
449
|
+
req.onupgradeneeded = () => {
|
|
450
|
+
const db = req.result;
|
|
451
|
+
if (!db.objectStoreNames.contains(DB_STORE)) {
|
|
452
|
+
db.createObjectStore(DB_STORE, { keyPath: "id" });
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
req.onsuccess = () => resolve(req.result);
|
|
456
|
+
req.onerror = () => reject(req.error);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
async function saveTransferPayload(id, payload) {
|
|
460
|
+
if (typeof window !== "undefined") {
|
|
461
|
+
try {
|
|
462
|
+
window[id] = payload;
|
|
463
|
+
window.__lastTransfer = payload;
|
|
464
|
+
} catch {
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
try {
|
|
468
|
+
const db = await openDB();
|
|
469
|
+
return new Promise((resolve, reject) => {
|
|
470
|
+
const tx = db.transaction(DB_STORE, "readwrite");
|
|
471
|
+
const store = tx.objectStore(DB_STORE);
|
|
472
|
+
store.put({ id, ...payload, timestamp: Date.now() });
|
|
473
|
+
tx.oncomplete = () => resolve();
|
|
474
|
+
tx.onerror = () => reject(tx.error);
|
|
475
|
+
});
|
|
476
|
+
} catch (e) {
|
|
477
|
+
console.warn("[saveTransferPayload] IndexedDB store warning:", e);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
435
480
|
var ICON_ZOOM_IN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>`;
|
|
436
481
|
var ICON_ZOOM_OUT = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>`;
|
|
437
482
|
var ICON_FIT_PAGE = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><line x1="8" y1="12" x2="16" y2="12"></line><polyline points="11 9 8 12 11 15"></polyline><polyline points="13 9 16 12 13 15"></polyline></svg>`;
|
|
@@ -451,6 +496,7 @@ var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" str
|
|
|
451
496
|
var ICON_FAST_FORWARD = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon></svg>`;
|
|
452
497
|
var ICON_REWIND = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon></svg>`;
|
|
453
498
|
var ICON_SPEED = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`;
|
|
499
|
+
var ICON_EXTERNAL_WINDOW = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`;
|
|
454
500
|
var ICON_MAP = {
|
|
455
501
|
"zoom-in": ICON_ZOOM_IN,
|
|
456
502
|
"zoom-out": ICON_ZOOM_OUT,
|
|
@@ -477,7 +523,9 @@ var ICON_MAP = {
|
|
|
477
523
|
"forward-10": ICON_FAST_FORWARD,
|
|
478
524
|
"rewind": ICON_REWIND,
|
|
479
525
|
"replay-10": ICON_REWIND,
|
|
480
|
-
"speed": ICON_SPEED
|
|
526
|
+
"speed": ICON_SPEED,
|
|
527
|
+
"open-window": ICON_EXTERNAL_WINDOW,
|
|
528
|
+
"external-window": ICON_EXTERNAL_WINDOW
|
|
481
529
|
};
|
|
482
530
|
var ToolbarController = class {
|
|
483
531
|
el;
|
|
@@ -620,9 +668,11 @@ var ToolbarController = class {
|
|
|
620
668
|
type: "button",
|
|
621
669
|
"data-action-id": id
|
|
622
670
|
});
|
|
623
|
-
const
|
|
624
|
-
if (
|
|
625
|
-
btn.innerHTML =
|
|
671
|
+
const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
|
|
672
|
+
if (internalSvg) {
|
|
673
|
+
btn.innerHTML = internalSvg;
|
|
674
|
+
} else if (iconHtml && iconHtml.startsWith("<svg")) {
|
|
675
|
+
btn.innerHTML = sanitizeSVG(iconHtml);
|
|
626
676
|
} else {
|
|
627
677
|
btn.textContent = title || id;
|
|
628
678
|
}
|
|
@@ -694,7 +744,7 @@ var ThumbnailPanel = class {
|
|
|
694
744
|
}
|
|
695
745
|
}
|
|
696
746
|
};
|
|
697
|
-
var FilePreviewViewer = class {
|
|
747
|
+
var FilePreviewViewer = class _FilePreviewViewer {
|
|
698
748
|
plugins = [];
|
|
699
749
|
activeInstance = null;
|
|
700
750
|
abortController = null;
|
|
@@ -731,6 +781,7 @@ var FilePreviewViewer = class {
|
|
|
731
781
|
* Preview a file in the given container element.
|
|
732
782
|
*/
|
|
733
783
|
async preview(container, source, options = {}) {
|
|
784
|
+
this.currentOptions = options;
|
|
734
785
|
this.abort();
|
|
735
786
|
this.abortController = new AbortController();
|
|
736
787
|
const { signal } = this.abortController;
|
|
@@ -740,7 +791,10 @@ var FilePreviewViewer = class {
|
|
|
740
791
|
this.showLoading();
|
|
741
792
|
try {
|
|
742
793
|
const { buffer, metadata } = await sourceToArrayBuffer(source, signal);
|
|
743
|
-
|
|
794
|
+
if (options.metadata) {
|
|
795
|
+
Object.assign(metadata, options.metadata);
|
|
796
|
+
}
|
|
797
|
+
this.currentBuffer = buffer.slice(0);
|
|
744
798
|
this.currentMetadata = metadata;
|
|
745
799
|
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
|
|
746
800
|
const fileInfo = { metadata, buffer };
|
|
@@ -770,6 +824,7 @@ var FilePreviewViewer = class {
|
|
|
770
824
|
}
|
|
771
825
|
});
|
|
772
826
|
this.activeInstance = instance;
|
|
827
|
+
instance.openInSeparateWindow = () => this.openInSeparateWindow();
|
|
773
828
|
this.hideLoading();
|
|
774
829
|
this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
|
|
775
830
|
if (options.showToolbar !== false && this.toolbar) {
|
|
@@ -779,26 +834,16 @@ var FilePreviewViewer = class {
|
|
|
779
834
|
actions.push({
|
|
780
835
|
id: "fullscreen",
|
|
781
836
|
icon: "fullscreen",
|
|
782
|
-
label: "
|
|
837
|
+
label: "Fullscreen",
|
|
783
838
|
type: "button",
|
|
784
839
|
group: "view",
|
|
785
|
-
execute:
|
|
840
|
+
execute: () => {
|
|
786
841
|
try {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if (this.wrapperEl?.requestFullscreen) {
|
|
791
|
-
await this.wrapperEl.requestFullscreen().catch(() => {
|
|
792
|
-
this.wrapperEl?.classList.add("fp-fullscreen-active");
|
|
793
|
-
});
|
|
794
|
-
} else {
|
|
795
|
-
this.wrapperEl?.classList.add("fp-fullscreen-active");
|
|
796
|
-
}
|
|
842
|
+
if (!document.fullscreenElement) {
|
|
843
|
+
this.wrapperEl?.requestFullscreen?.();
|
|
844
|
+
this.wrapperEl?.classList.add("fp-fullscreen-active");
|
|
797
845
|
} else {
|
|
798
|
-
|
|
799
|
-
await document.exitFullscreen().catch(() => {
|
|
800
|
-
});
|
|
801
|
-
}
|
|
846
|
+
document.exitFullscreen?.();
|
|
802
847
|
this.wrapperEl?.classList.remove("fp-fullscreen-active");
|
|
803
848
|
}
|
|
804
849
|
} catch {
|
|
@@ -810,6 +855,28 @@ var FilePreviewViewer = class {
|
|
|
810
855
|
}
|
|
811
856
|
});
|
|
812
857
|
}
|
|
858
|
+
const openWinAction = actions.find((a) => a.id === "open-window");
|
|
859
|
+
if (openWinAction) {
|
|
860
|
+
if (options?._isSeparateWindow) {
|
|
861
|
+
const idx = actions.indexOf(openWinAction);
|
|
862
|
+
if (idx !== -1) actions.splice(idx, 1);
|
|
863
|
+
} else {
|
|
864
|
+
openWinAction.execute = () => {
|
|
865
|
+
this.openInSeparateWindow();
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
} else if (!options?._isSeparateWindow) {
|
|
869
|
+
actions.push({
|
|
870
|
+
id: "open-window",
|
|
871
|
+
icon: "open-window",
|
|
872
|
+
label: "Open in Separate Full Window",
|
|
873
|
+
type: "button",
|
|
874
|
+
group: "actions",
|
|
875
|
+
execute: () => {
|
|
876
|
+
this.openInSeparateWindow();
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
}
|
|
813
880
|
this.toolbar.update(actions);
|
|
814
881
|
this.toolbar.show();
|
|
815
882
|
}
|
|
@@ -842,6 +909,104 @@ var FilePreviewViewer = class {
|
|
|
842
909
|
throw error;
|
|
843
910
|
}
|
|
844
911
|
}
|
|
912
|
+
/**
|
|
913
|
+
* Opens the current file preview in a separate full browser window.
|
|
914
|
+
*/
|
|
915
|
+
openInSeparateWindow() {
|
|
916
|
+
if (!this.currentBuffer) {
|
|
917
|
+
console.warn("[FilePreviewViewer] No active file buffer to open in separate window");
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
if (this.currentOptions.onOpenSeparateWindow) {
|
|
921
|
+
return this.currentOptions.onOpenSeparateWindow({
|
|
922
|
+
buffer: this.currentBuffer,
|
|
923
|
+
metadata: this.currentMetadata || { name: "Document" },
|
|
924
|
+
options: this.currentOptions
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
const transferId = "fp_win_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
|
|
928
|
+
let clonedBuffer;
|
|
929
|
+
try {
|
|
930
|
+
clonedBuffer = this.currentBuffer.slice(0);
|
|
931
|
+
} catch {
|
|
932
|
+
clonedBuffer = this.currentBuffer;
|
|
933
|
+
}
|
|
934
|
+
const payload = {
|
|
935
|
+
buffer: clonedBuffer,
|
|
936
|
+
metadata: this.currentMetadata ? { ...this.currentMetadata } : void 0,
|
|
937
|
+
options: { ...this.currentOptions, _isSeparateWindow: true }
|
|
938
|
+
};
|
|
939
|
+
if (typeof window !== "undefined") {
|
|
940
|
+
try {
|
|
941
|
+
window[transferId] = payload;
|
|
942
|
+
window.__lastTransfer = payload;
|
|
943
|
+
} catch {
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
saveTransferPayload(transferId, payload).catch((err) => {
|
|
947
|
+
console.warn("[FilePreviewViewer] Transfer payload save warning:", err);
|
|
948
|
+
});
|
|
949
|
+
let targetUrl = null;
|
|
950
|
+
if (this.currentOptions.standaloneViewerUrl) {
|
|
951
|
+
const u = new URL(this.currentOptions.standaloneViewerUrl, window.location.href);
|
|
952
|
+
u.searchParams.set("mode", "fullscreen");
|
|
953
|
+
u.searchParams.set("transferId", transferId);
|
|
954
|
+
targetUrl = u.toString();
|
|
955
|
+
} else if (typeof window !== "undefined" && window.location?.href && !window.location.href.startsWith("about:")) {
|
|
956
|
+
const u = new URL(window.location.href);
|
|
957
|
+
u.searchParams.set("mode", "fullscreen");
|
|
958
|
+
u.searchParams.set("transferId", transferId);
|
|
959
|
+
targetUrl = u.toString();
|
|
960
|
+
}
|
|
961
|
+
if (targetUrl) {
|
|
962
|
+
const newWin2 = window.open(targetUrl, "_blank");
|
|
963
|
+
if (!newWin2) {
|
|
964
|
+
alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
return newWin2;
|
|
968
|
+
}
|
|
969
|
+
const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
|
|
970
|
+
const newWin = window.open("", "_blank");
|
|
971
|
+
if (!newWin) {
|
|
972
|
+
alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
newWin.document.title = title;
|
|
976
|
+
newWin.document.body.style.margin = "0";
|
|
977
|
+
newWin.document.body.style.padding = "0";
|
|
978
|
+
newWin.document.body.style.width = "100vw";
|
|
979
|
+
newWin.document.body.style.height = "100vh";
|
|
980
|
+
newWin.document.body.style.overflow = "hidden";
|
|
981
|
+
newWin.document.body.style.backgroundColor = "#f8fafc";
|
|
982
|
+
const headNodes = document.querySelectorAll('link[rel="stylesheet"], style');
|
|
983
|
+
headNodes.forEach((node) => {
|
|
984
|
+
newWin.document.head.appendChild(node.cloneNode(true));
|
|
985
|
+
});
|
|
986
|
+
const root = newWin.document.createElement("div");
|
|
987
|
+
root.id = "full-window-preview-root";
|
|
988
|
+
root.style.width = "100%";
|
|
989
|
+
root.style.height = "100%";
|
|
990
|
+
root.style.overflow = "hidden";
|
|
991
|
+
newWin.document.body.appendChild(root);
|
|
992
|
+
const separateViewer = new _FilePreviewViewer();
|
|
993
|
+
for (const plugin of this.plugins) {
|
|
994
|
+
separateViewer.registerPlugin(plugin);
|
|
995
|
+
}
|
|
996
|
+
separateViewer.preview(root, this.currentBuffer.slice(0), {
|
|
997
|
+
...this.currentOptions,
|
|
998
|
+
showToolbar: true,
|
|
999
|
+
toolbarPosition: "top",
|
|
1000
|
+
metadata: this.currentMetadata || void 0,
|
|
1001
|
+
_isSeparateWindow: true
|
|
1002
|
+
}).catch((err) => {
|
|
1003
|
+
console.error("[FilePreviewViewer] Error rendering in separate window:", err);
|
|
1004
|
+
});
|
|
1005
|
+
newWin.addEventListener("beforeunload", () => {
|
|
1006
|
+
separateViewer.destroy();
|
|
1007
|
+
});
|
|
1008
|
+
return newWin;
|
|
1009
|
+
}
|
|
845
1010
|
/**
|
|
846
1011
|
* Subscribe to viewer events.
|
|
847
1012
|
*/
|
|
@@ -1247,8 +1412,20 @@ var CfbfReader = class {
|
|
|
1247
1412
|
}
|
|
1248
1413
|
};
|
|
1249
1414
|
if (typeof window !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
|
|
1250
|
-
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
1251
|
-
|
|
1415
|
+
if (!pdfjsLib.GlobalWorkerOptions.workerPort && !pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
1416
|
+
const customWorker = window.__PDF_WORKER_SRC__;
|
|
1417
|
+
if (customWorker) {
|
|
1418
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = customWorker;
|
|
1419
|
+
} else {
|
|
1420
|
+
try {
|
|
1421
|
+
pdfjsLib.GlobalWorkerOptions.workerPort = new Worker(
|
|
1422
|
+
new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url),
|
|
1423
|
+
{ type: "module" }
|
|
1424
|
+
);
|
|
1425
|
+
} catch {
|
|
1426
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.mjs";
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1252
1429
|
}
|
|
1253
1430
|
}
|
|
1254
1431
|
var PdfPlugin = class {
|
|
@@ -1342,6 +1519,14 @@ var PdfPlugin = class {
|
|
|
1342
1519
|
type: "button",
|
|
1343
1520
|
group: "actions",
|
|
1344
1521
|
execute: () => instance.print?.()
|
|
1522
|
+
},
|
|
1523
|
+
{
|
|
1524
|
+
id: "open-window",
|
|
1525
|
+
icon: "open-window",
|
|
1526
|
+
label: "Open in Separate Full Window",
|
|
1527
|
+
type: "button",
|
|
1528
|
+
group: "actions",
|
|
1529
|
+
execute: () => instance.openInSeparateWindow?.()
|
|
1345
1530
|
}
|
|
1346
1531
|
];
|
|
1347
1532
|
}
|
|
@@ -1391,18 +1576,21 @@ var PdfPlugin = class {
|
|
|
1391
1576
|
indicator.style.pointerEvents = "none";
|
|
1392
1577
|
container.appendChild(indicator);
|
|
1393
1578
|
ctx.container.appendChild(container);
|
|
1579
|
+
const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
|
|
1580
|
+
const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
|
|
1394
1581
|
const loadingTask = pdfjsLib.getDocument({
|
|
1395
|
-
data: new Uint8Array(ctx.buffer),
|
|
1396
|
-
cMapUrl:
|
|
1582
|
+
data: new Uint8Array(ctx.buffer.slice(0)),
|
|
1583
|
+
cMapUrl: cmapsUrl,
|
|
1397
1584
|
cMapPacked: true,
|
|
1398
|
-
standardFontDataUrl:
|
|
1585
|
+
standardFontDataUrl: standardFontsUrl,
|
|
1586
|
+
verbosity: 0
|
|
1399
1587
|
});
|
|
1400
1588
|
const pdfDoc = await loadingTask.promise;
|
|
1401
1589
|
const totalPages = Math.max(1, pdfDoc.numPages);
|
|
1402
1590
|
let currentPage = 1;
|
|
1403
1591
|
let zoomScale = 1;
|
|
1404
1592
|
let rotation = 0;
|
|
1405
|
-
let fitMode = "
|
|
1593
|
+
let fitMode = "page";
|
|
1406
1594
|
let currentRenderTask = null;
|
|
1407
1595
|
const renderPage = async (pageNum) => {
|
|
1408
1596
|
if (currentRenderTask) {
|
|
@@ -1422,15 +1610,15 @@ var PdfPlugin = class {
|
|
|
1422
1610
|
const containerWidth = container.clientWidth || 900;
|
|
1423
1611
|
const containerHeight = container.clientHeight || 700;
|
|
1424
1612
|
const unscaledVp = page.getViewport({ scale: 1, rotation });
|
|
1425
|
-
const availWidth = Math.max(
|
|
1426
|
-
const availHeight = Math.max(
|
|
1613
|
+
const availWidth = Math.max(320, containerWidth - 48);
|
|
1614
|
+
const availHeight = Math.max(550, containerHeight - 88);
|
|
1427
1615
|
const scaleW = availWidth / unscaledVp.width;
|
|
1428
1616
|
const scaleH = availHeight / unscaledVp.height;
|
|
1429
1617
|
let fitScale;
|
|
1430
1618
|
if (fitMode === "page") {
|
|
1431
|
-
fitScale = Math.max(0.
|
|
1619
|
+
fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
|
|
1432
1620
|
} else {
|
|
1433
|
-
fitScale = Math.max(0.65, Math.min(1.
|
|
1621
|
+
fitScale = Math.max(0.65, Math.min(1.25, scaleW));
|
|
1434
1622
|
}
|
|
1435
1623
|
const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
|
|
1436
1624
|
const pixelRatio = window.devicePixelRatio || 1;
|
|
@@ -1456,7 +1644,7 @@ var PdfPlugin = class {
|
|
|
1456
1644
|
currentRenderTask = null;
|
|
1457
1645
|
}
|
|
1458
1646
|
};
|
|
1459
|
-
|
|
1647
|
+
renderPage(1);
|
|
1460
1648
|
let resizeTimer = null;
|
|
1461
1649
|
const resizeObserver = new ResizeObserver(() => {
|
|
1462
1650
|
if (resizeTimer) clearTimeout(resizeTimer);
|
|
@@ -1500,7 +1688,7 @@ var PdfPlugin = class {
|
|
|
1500
1688
|
renderPage(currentPage);
|
|
1501
1689
|
},
|
|
1502
1690
|
fitToPage: () => {
|
|
1503
|
-
fitMode = fitMode === "
|
|
1691
|
+
fitMode = fitMode === "page" ? "width" : "page";
|
|
1504
1692
|
zoomScale = 1;
|
|
1505
1693
|
rotation = 0;
|
|
1506
1694
|
renderPage(currentPage);
|
|
@@ -1977,6 +2165,14 @@ var DocxPlugin = class {
|
|
|
1977
2165
|
type: "button",
|
|
1978
2166
|
group: "actions",
|
|
1979
2167
|
execute: () => instance.print?.()
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
id: "open-window",
|
|
2171
|
+
icon: "open-window",
|
|
2172
|
+
label: "Open in Separate Full Window",
|
|
2173
|
+
type: "button",
|
|
2174
|
+
group: "actions",
|
|
2175
|
+
execute: () => instance.openInSeparateWindow?.()
|
|
1980
2176
|
}
|
|
1981
2177
|
);
|
|
1982
2178
|
return actions;
|
|
@@ -2036,6 +2232,31 @@ var DocxPlugin = class {
|
|
|
2036
2232
|
}
|
|
2037
2233
|
if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
|
|
2038
2234
|
renderedSuccessfully = true;
|
|
2235
|
+
try {
|
|
2236
|
+
const unzipped = unzipSync(new Uint8Array(ctx.buffer));
|
|
2237
|
+
const chartKeys = Object.keys(unzipped).filter((k) => k.replace(/^[./\\]+/, "").toLowerCase().startsWith("word/charts/chart") && k.endsWith(".xml")).sort();
|
|
2238
|
+
if (chartKeys.length > 0) {
|
|
2239
|
+
const allDivs = Array.from(wrapper.querySelectorAll("div"));
|
|
2240
|
+
const emptyContainers = allDivs.filter((div) => {
|
|
2241
|
+
const st = div.getAttribute("style") || "";
|
|
2242
|
+
return st.includes("width:") && st.includes("height:") && div.children.length === 0 && (div.textContent?.trim().length ?? 0) === 0;
|
|
2243
|
+
});
|
|
2244
|
+
chartKeys.forEach((cKey, idx) => {
|
|
2245
|
+
const target = emptyContainers[idx];
|
|
2246
|
+
if (target) {
|
|
2247
|
+
const xmlStr = strFromU8(unzipped[cKey]);
|
|
2248
|
+
const svg = this.parseAndRenderChartSvg(xmlStr);
|
|
2249
|
+
if (svg) {
|
|
2250
|
+
target.innerHTML = svg;
|
|
2251
|
+
target.style.display = "block";
|
|
2252
|
+
target.style.margin = "12px auto";
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
} catch (chartErr) {
|
|
2258
|
+
console.warn("[DocxPlugin] Non-critical error rendering DrawingML charts:", chartErr);
|
|
2259
|
+
}
|
|
2039
2260
|
}
|
|
2040
2261
|
} catch (err) {
|
|
2041
2262
|
console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
|
|
@@ -2067,64 +2288,74 @@ var DocxPlugin = class {
|
|
|
2067
2288
|
}
|
|
2068
2289
|
let sections = Array.from(wrapper.querySelectorAll("section.docx"));
|
|
2069
2290
|
const cards = Array.from(wrapper.querySelectorAll(".fp-docx-page-card"));
|
|
2070
|
-
if (sections.length
|
|
2071
|
-
const
|
|
2072
|
-
const
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
const
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
nextSec.
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
nextArticle
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
nextSec.appendChild(
|
|
2291
|
+
if (sections.length > 0 && cards.length === 0) {
|
|
2292
|
+
const finalSections = [];
|
|
2293
|
+
for (const singleSec of sections) {
|
|
2294
|
+
const contentContainer = singleSec.querySelector("article") || singleSec;
|
|
2295
|
+
const children = Array.from(contentContainer.children);
|
|
2296
|
+
const pageH = singleSec.offsetHeight > 1300 ? 1122 : Math.max(1056, singleSec.offsetHeight);
|
|
2297
|
+
const secH = singleSec.scrollHeight || singleSec.offsetHeight;
|
|
2298
|
+
if (secH > pageH * 1.25 && children.length > 1) {
|
|
2299
|
+
const childHeights = children.map((c) => {
|
|
2300
|
+
const rectH = c.getBoundingClientRect().height;
|
|
2301
|
+
const offH = c.offsetHeight;
|
|
2302
|
+
const textLen = c.textContent?.trim().length || 0;
|
|
2303
|
+
const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
|
|
2304
|
+
return Math.max(rectH, offH, estH);
|
|
2305
|
+
});
|
|
2306
|
+
const parent = singleSec.parentElement || wrapper;
|
|
2307
|
+
const headerEl = singleSec.querySelector("header");
|
|
2308
|
+
const footerEl = singleSec.querySelector("footer");
|
|
2309
|
+
contentContainer.innerHTML = "";
|
|
2310
|
+
singleSec.style.minHeight = `${pageH}px`;
|
|
2311
|
+
singleSec.style.boxSizing = "border-box";
|
|
2312
|
+
let curContent = contentContainer;
|
|
2313
|
+
let curSec = singleSec;
|
|
2314
|
+
let curH = 0;
|
|
2315
|
+
const maxH = pageH - 140;
|
|
2316
|
+
finalSections.push(singleSec);
|
|
2317
|
+
for (let i = 0; i < children.length; i++) {
|
|
2318
|
+
const child = children[i];
|
|
2319
|
+
const chH = childHeights[i];
|
|
2320
|
+
curContent.appendChild(child);
|
|
2321
|
+
curH += chH;
|
|
2322
|
+
if (curH >= maxH && i < children.length - 1) {
|
|
2323
|
+
const nextSec = document.createElement("section");
|
|
2324
|
+
nextSec.className = singleSec.className;
|
|
2325
|
+
nextSec.style.cssText = singleSec.style.cssText;
|
|
2326
|
+
nextSec.style.minHeight = `${pageH}px`;
|
|
2327
|
+
nextSec.style.boxSizing = "border-box";
|
|
2328
|
+
nextSec.style.backgroundColor = "#ffffff";
|
|
2329
|
+
nextSec.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
|
|
2330
|
+
nextSec.style.borderRadius = "4px";
|
|
2331
|
+
nextSec.style.marginBottom = "24px";
|
|
2332
|
+
if (headerEl) {
|
|
2333
|
+
nextSec.appendChild(headerEl.cloneNode(true));
|
|
2334
|
+
}
|
|
2335
|
+
const nextArticle = document.createElement("article");
|
|
2336
|
+
if (contentContainer.tagName.toLowerCase() === "article") {
|
|
2337
|
+
nextArticle.style.cssText = contentContainer.style.cssText;
|
|
2338
|
+
}
|
|
2339
|
+
nextSec.appendChild(nextArticle);
|
|
2340
|
+
if (footerEl) {
|
|
2341
|
+
nextSec.appendChild(footerEl.cloneNode(true));
|
|
2342
|
+
}
|
|
2343
|
+
if (curSec.nextSibling) {
|
|
2344
|
+
parent.insertBefore(nextSec, curSec.nextSibling);
|
|
2345
|
+
} else {
|
|
2346
|
+
parent.appendChild(nextSec);
|
|
2347
|
+
}
|
|
2348
|
+
finalSections.push(nextSec);
|
|
2349
|
+
curSec = nextSec;
|
|
2350
|
+
curContent = nextArticle;
|
|
2351
|
+
curH = 0;
|
|
2119
2352
|
}
|
|
2120
|
-
parent.appendChild(nextSec);
|
|
2121
|
-
newSections.push(nextSec);
|
|
2122
|
-
curContent = nextArticle;
|
|
2123
|
-
curH = 0;
|
|
2124
2353
|
}
|
|
2354
|
+
} else {
|
|
2355
|
+
finalSections.push(singleSec);
|
|
2125
2356
|
}
|
|
2126
|
-
sections = newSections;
|
|
2127
2357
|
}
|
|
2358
|
+
sections = finalSections;
|
|
2128
2359
|
}
|
|
2129
2360
|
const pageElements = sections.length > 0 ? sections : cards;
|
|
2130
2361
|
const totalPages = Math.max(1, pageElements.length);
|
|
@@ -2516,6 +2747,88 @@ var DocxPlugin = class {
|
|
|
2516
2747
|
}
|
|
2517
2748
|
return result;
|
|
2518
2749
|
}
|
|
2750
|
+
parseAndRenderChartSvg(xmlStr, width = 500, height = 260) {
|
|
2751
|
+
const catMatches = [...xmlStr.matchAll(/<c:cat>[\s\S]*?<c:strCache>([\s\S]*?)<\/c:strCache>/g)];
|
|
2752
|
+
let categories = [];
|
|
2753
|
+
if (catMatches.length > 0) {
|
|
2754
|
+
categories = [...catMatches[0][1].matchAll(/<c:v>([^<]+)<\/c:v>/g)].map((m) => m[1]);
|
|
2755
|
+
}
|
|
2756
|
+
if (categories.length === 0) {
|
|
2757
|
+
categories = ["Category 1", "Category 2", "Category 3", "Category 4"];
|
|
2758
|
+
}
|
|
2759
|
+
const defaultColors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021", "#83caff"];
|
|
2760
|
+
const sers = [...xmlStr.matchAll(/<c:ser>([\s\S]*?)<\/c:ser>/g)];
|
|
2761
|
+
const series = [];
|
|
2762
|
+
sers.forEach((s, sIdx) => {
|
|
2763
|
+
const titleMatch = s[1].match(/<c:tx>[\s\S]*?<c:v>([^<]+)<\/c:v>/);
|
|
2764
|
+
const title = titleMatch ? titleMatch[1] : `Series ${sIdx + 1}`;
|
|
2765
|
+
const clrMatch = s[1].match(/<a:srgbClr\s+val="([^"]+)"/);
|
|
2766
|
+
const color = clrMatch ? "#" + clrMatch[1] : defaultColors[sIdx % defaultColors.length];
|
|
2767
|
+
const valMatch = s[1].match(/<c:val>[\s\S]*?<c:numCache>([\s\S]*?)<\/c:numCache>/);
|
|
2768
|
+
let values = [];
|
|
2769
|
+
if (valMatch) {
|
|
2770
|
+
values = [...valMatch[1].matchAll(/<c:pt\s+idx="(\d+)">\s*<c:v>([^<]+)<\/c:v>/g)].sort((a, b) => parseInt(a[1], 10) - parseInt(b[1], 10)).map((m) => parseFloat(m[2]) || 0);
|
|
2771
|
+
}
|
|
2772
|
+
series.push({ title, color, values });
|
|
2773
|
+
});
|
|
2774
|
+
if (series.length === 0) return "";
|
|
2775
|
+
let maxVal = 10;
|
|
2776
|
+
series.forEach((s) => s.values.forEach((v) => {
|
|
2777
|
+
if (v > maxVal) maxVal = v;
|
|
2778
|
+
}));
|
|
2779
|
+
maxVal = Math.ceil(maxVal * 1.15);
|
|
2780
|
+
if (maxVal % 2 !== 0) maxVal++;
|
|
2781
|
+
const padLeft = 45;
|
|
2782
|
+
const padBottom = 55;
|
|
2783
|
+
const padTop = 20;
|
|
2784
|
+
const padRight = 20;
|
|
2785
|
+
const plotW = width - padLeft - padRight;
|
|
2786
|
+
const plotH = height - padTop - padBottom;
|
|
2787
|
+
const yTicks = 5;
|
|
2788
|
+
let gridLines = "";
|
|
2789
|
+
for (let i = 0; i <= yTicks; i++) {
|
|
2790
|
+
const val = maxVal / yTicks * i;
|
|
2791
|
+
const y = padTop + plotH - val / maxVal * plotH;
|
|
2792
|
+
gridLines += `<line x1="${padLeft}" y1="${y}" x2="${padLeft + plotW}" y2="${y}" stroke="#e2e8f0" stroke-width="1" />`;
|
|
2793
|
+
gridLines += `<text x="${padLeft - 8}" y="${y + 4}" text-anchor="end" font-size="11" fill="#64748b" font-family="Calibri, sans-serif">${Math.round(val)}</text>`;
|
|
2794
|
+
}
|
|
2795
|
+
const numCats = categories.length;
|
|
2796
|
+
const numSers = series.length;
|
|
2797
|
+
const groupW = plotW / numCats;
|
|
2798
|
+
const barW = Math.max(8, Math.min(28, groupW * 0.7 / numSers));
|
|
2799
|
+
const groupPad = (groupW - barW * numSers) / 2;
|
|
2800
|
+
let bars = "";
|
|
2801
|
+
let catLabels = "";
|
|
2802
|
+
for (let c = 0; c < numCats; c++) {
|
|
2803
|
+
const catX = padLeft + c * groupW;
|
|
2804
|
+
catLabels += `<text x="${catX + groupW / 2}" y="${padTop + plotH + 18}" text-anchor="middle" font-size="11" fill="#334155" font-family="Calibri, sans-serif">${categories[c]}</text>`;
|
|
2805
|
+
for (let s = 0; s < numSers; s++) {
|
|
2806
|
+
const val = series[s].values[c] ?? 0;
|
|
2807
|
+
const bH = Math.max(0, val / maxVal * plotH);
|
|
2808
|
+
const bX = catX + groupPad + s * barW;
|
|
2809
|
+
const bY = padTop + plotH - bH;
|
|
2810
|
+
bars += `<rect x="${bX}" y="${bY}" width="${barW - 2}" height="${bH}" fill="${series[s].color}" rx="1" />`;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
let legend = "";
|
|
2814
|
+
const legY = height - 12;
|
|
2815
|
+
let legX = padLeft + (plotW - numSers * 100) / 2;
|
|
2816
|
+
series.forEach((s) => {
|
|
2817
|
+
legend += `<rect x="${legX}" y="${legY - 9}" width="10" height="10" fill="${s.color}" rx="2" />`;
|
|
2818
|
+
legend += `<text x="${legX + 15}" y="${legY}" font-size="11" fill="#475569" font-family="Calibri, sans-serif">${s.title}</text>`;
|
|
2819
|
+
legX += 95;
|
|
2820
|
+
});
|
|
2821
|
+
return `
|
|
2822
|
+
<svg viewBox="0 0 ${width} ${height}" width="100%" height="100%" style="background:#ffffff; border-radius:4px; overflow:visible;" xmlns="http://www.w3.org/2000/svg">
|
|
2823
|
+
${gridLines}
|
|
2824
|
+
<line x1="${padLeft}" y1="${padTop + plotH}" x2="${padLeft + plotW}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
|
|
2825
|
+
<line x1="${padLeft}" y1="${padTop}" x2="${padLeft}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
|
|
2826
|
+
${bars}
|
|
2827
|
+
${catLabels}
|
|
2828
|
+
${legend}
|
|
2829
|
+
</svg>
|
|
2830
|
+
`.trim();
|
|
2831
|
+
}
|
|
2519
2832
|
};
|
|
2520
2833
|
function docxPlugin() {
|
|
2521
2834
|
return new DocxPlugin();
|
|
@@ -3082,6 +3395,16 @@ var CodePlugin = class {
|
|
|
3082
3395
|
execute: () => {
|
|
3083
3396
|
instance.print?.();
|
|
3084
3397
|
}
|
|
3398
|
+
},
|
|
3399
|
+
{
|
|
3400
|
+
id: "open-window",
|
|
3401
|
+
icon: "open-window",
|
|
3402
|
+
label: "Open in Separate Full Window",
|
|
3403
|
+
type: "button",
|
|
3404
|
+
group: "actions",
|
|
3405
|
+
execute: () => {
|
|
3406
|
+
instance.openInSeparateWindow?.();
|
|
3407
|
+
}
|
|
3085
3408
|
}
|
|
3086
3409
|
);
|
|
3087
3410
|
return actions;
|
|
@@ -4250,6 +4573,14 @@ var RtfPlugin = class {
|
|
|
4250
4573
|
type: "button",
|
|
4251
4574
|
group: "actions",
|
|
4252
4575
|
execute: () => instance.print?.()
|
|
4576
|
+
},
|
|
4577
|
+
{
|
|
4578
|
+
id: "open-window",
|
|
4579
|
+
icon: "open-window",
|
|
4580
|
+
label: "Open in Separate Full Window",
|
|
4581
|
+
type: "button",
|
|
4582
|
+
group: "actions",
|
|
4583
|
+
execute: () => instance.openInSeparateWindow?.()
|
|
4253
4584
|
}
|
|
4254
4585
|
);
|
|
4255
4586
|
return actions;
|
|
@@ -4302,59 +4633,54 @@ var RtfPlugin = class {
|
|
|
4302
4633
|
}
|
|
4303
4634
|
const doc = new RTFJS.Document(ctx.buffer, {});
|
|
4304
4635
|
const htmlElements = await doc.render();
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
const secH = singleEl.offsetHeight || singleEl.scrollHeight;
|
|
4310
|
-
if (secH > 1300 && children.length > 1) {
|
|
4311
|
-
const childHeights = children.map((c) => {
|
|
4312
|
-
const rectH = c.getBoundingClientRect().height;
|
|
4313
|
-
const offH = c.offsetHeight;
|
|
4314
|
-
const textLen = c.textContent?.trim().length || 0;
|
|
4315
|
-
const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
|
|
4316
|
-
return Math.max(rectH, offH, estH);
|
|
4317
|
-
});
|
|
4318
|
-
wrapper.innerHTML = "";
|
|
4319
|
-
const createRtfCard = () => {
|
|
4320
|
-
const card = document.createElement("div");
|
|
4321
|
-
card.className = "fp-rtf-page-card";
|
|
4322
|
-
card.style.backgroundColor = "#ffffff";
|
|
4323
|
-
card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
|
|
4324
|
-
card.style.borderRadius = "4px";
|
|
4325
|
-
card.style.padding = "72px 56px";
|
|
4326
|
-
card.style.width = "816px";
|
|
4327
|
-
card.style.minHeight = "1056px";
|
|
4328
|
-
card.style.boxSizing = "border-box";
|
|
4329
|
-
card.style.marginBottom = "24px";
|
|
4330
|
-
return card;
|
|
4331
|
-
};
|
|
4332
|
-
let curCard = createRtfCard();
|
|
4333
|
-
wrapper.appendChild(curCard);
|
|
4334
|
-
pageElements = [curCard];
|
|
4335
|
-
let curH = 0;
|
|
4336
|
-
const maxH = 920;
|
|
4337
|
-
for (let i = 0; i < children.length; i++) {
|
|
4338
|
-
const child = children[i];
|
|
4339
|
-
const chH = childHeights[i];
|
|
4340
|
-
curCard.appendChild(child);
|
|
4341
|
-
curH += chH;
|
|
4342
|
-
if (curH >= maxH && i < children.length - 1) {
|
|
4343
|
-
curCard = createRtfCard();
|
|
4344
|
-
wrapper.appendChild(curCard);
|
|
4345
|
-
pageElements.push(curCard);
|
|
4346
|
-
curH = 0;
|
|
4347
|
-
}
|
|
4348
|
-
}
|
|
4636
|
+
const contentNodes = [];
|
|
4637
|
+
for (const item of htmlElements) {
|
|
4638
|
+
if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
|
|
4639
|
+
contentNodes.push(...Array.from(item.children));
|
|
4349
4640
|
} else {
|
|
4350
|
-
|
|
4641
|
+
contentNodes.push(item);
|
|
4351
4642
|
}
|
|
4352
|
-
}
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4643
|
+
}
|
|
4644
|
+
wrapper.innerHTML = "";
|
|
4645
|
+
contentNodes.forEach((node) => wrapper.appendChild(node));
|
|
4646
|
+
const childHeights = contentNodes.map((c) => {
|
|
4647
|
+
const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
|
|
4648
|
+
const offH = c.offsetHeight || 0;
|
|
4649
|
+
const textLen = c.textContent?.trim().length || 0;
|
|
4650
|
+
const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
|
|
4651
|
+
return Math.max(rectH, offH, estH);
|
|
4652
|
+
});
|
|
4653
|
+
wrapper.innerHTML = "";
|
|
4654
|
+
const createRtfCard = () => {
|
|
4655
|
+
const card = document.createElement("div");
|
|
4656
|
+
card.className = "fp-rtf-page-card";
|
|
4657
|
+
card.style.backgroundColor = "#ffffff";
|
|
4658
|
+
card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
|
|
4659
|
+
card.style.borderRadius = "4px";
|
|
4660
|
+
card.style.padding = "72px 56px";
|
|
4661
|
+
card.style.width = "816px";
|
|
4662
|
+
card.style.minHeight = "1056px";
|
|
4663
|
+
card.style.boxSizing = "border-box";
|
|
4664
|
+
card.style.marginBottom = "24px";
|
|
4665
|
+
card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
|
|
4666
|
+
card.style.lineHeight = "1.6";
|
|
4667
|
+
return card;
|
|
4668
|
+
};
|
|
4669
|
+
let curCard = createRtfCard();
|
|
4670
|
+
wrapper.appendChild(curCard);
|
|
4671
|
+
pageElements = [curCard];
|
|
4672
|
+
let curH = 0;
|
|
4673
|
+
const maxH = 912;
|
|
4674
|
+
for (let i = 0; i < contentNodes.length; i++) {
|
|
4675
|
+
const child = contentNodes[i];
|
|
4676
|
+
const chH = childHeights[i];
|
|
4677
|
+
curCard.appendChild(child);
|
|
4678
|
+
curH += chH;
|
|
4679
|
+
if (curH >= maxH && i < contentNodes.length - 1) {
|
|
4680
|
+
curCard = createRtfCard();
|
|
4681
|
+
wrapper.appendChild(curCard);
|
|
4682
|
+
pageElements.push(curCard);
|
|
4683
|
+
curH = 0;
|
|
4358
4684
|
}
|
|
4359
4685
|
}
|
|
4360
4686
|
} catch (err) {
|
|
@@ -4723,6 +5049,14 @@ var OpenDocumentPlugin = class {
|
|
|
4723
5049
|
type: "button",
|
|
4724
5050
|
group: "actions",
|
|
4725
5051
|
execute: () => instance.print?.()
|
|
5052
|
+
},
|
|
5053
|
+
{
|
|
5054
|
+
id: "open-window",
|
|
5055
|
+
icon: "open-window",
|
|
5056
|
+
label: "Open in Separate Full Window",
|
|
5057
|
+
type: "button",
|
|
5058
|
+
group: "actions",
|
|
5059
|
+
execute: () => instance.openInSeparateWindow?.()
|
|
4726
5060
|
}
|
|
4727
5061
|
);
|
|
4728
5062
|
return actions;
|
|
@@ -5311,6 +5645,14 @@ var DocPlugin = class {
|
|
|
5311
5645
|
type: "button",
|
|
5312
5646
|
group: "actions",
|
|
5313
5647
|
execute: () => instance.print?.()
|
|
5648
|
+
},
|
|
5649
|
+
{
|
|
5650
|
+
id: "open-window",
|
|
5651
|
+
icon: "open-window",
|
|
5652
|
+
label: "Open in Separate Full Window",
|
|
5653
|
+
type: "button",
|
|
5654
|
+
group: "actions",
|
|
5655
|
+
execute: () => instance.openInSeparateWindow?.()
|
|
5314
5656
|
}
|
|
5315
5657
|
);
|
|
5316
5658
|
return actions;
|
|
@@ -5330,8 +5672,17 @@ var DocPlugin = class {
|
|
|
5330
5672
|
let scale = 1;
|
|
5331
5673
|
let extractedRawText = "";
|
|
5332
5674
|
let isFallback = false;
|
|
5675
|
+
let chartSvg = "";
|
|
5333
5676
|
try {
|
|
5334
5677
|
const cfbf = new CfbfReader(ctx.buffer);
|
|
5678
|
+
try {
|
|
5679
|
+
const pkg = cfbf.readStream("package_stream");
|
|
5680
|
+
if (pkg && pkg.length > 100) {
|
|
5681
|
+
chartSvg = this.parseOdfChartToSvg(pkg);
|
|
5682
|
+
}
|
|
5683
|
+
} catch (chartErr) {
|
|
5684
|
+
console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
|
|
5685
|
+
}
|
|
5335
5686
|
const wordDocStream = cfbf.readStream("WordDocument");
|
|
5336
5687
|
if (!wordDocStream || wordDocStream.length < 512) {
|
|
5337
5688
|
throw new Error("WordDocument stream not found or invalid in CFBF archive");
|
|
@@ -5349,7 +5700,7 @@ var DocPlugin = class {
|
|
|
5349
5700
|
extractedRawText = fallback;
|
|
5350
5701
|
isFallback = true;
|
|
5351
5702
|
}
|
|
5352
|
-
const rawPages = this.splitIntoPages(extractedRawText);
|
|
5703
|
+
const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
|
|
5353
5704
|
const totalPages = Math.max(1, rawPages.length);
|
|
5354
5705
|
let currentPage = 1;
|
|
5355
5706
|
const pageCards = [];
|
|
@@ -5591,7 +5942,7 @@ var DocPlugin = class {
|
|
|
5591
5942
|
for (const run of [...ansiRuns, ...utf16Runs]) {
|
|
5592
5943
|
const trimmed = run.trim();
|
|
5593
5944
|
if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
|
|
5594
|
-
if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^[\W_0-9]+$/.test(trimmed)) {
|
|
5945
|
+
if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^EMBED\b/i.test(trimmed) && !trimmed.includes("ChartDocument") && !/^[\W_0-9]+$/.test(trimmed)) {
|
|
5595
5946
|
seen.add(trimmed);
|
|
5596
5947
|
candidateLines.push(trimmed);
|
|
5597
5948
|
}
|
|
@@ -5602,12 +5953,138 @@ var DocPlugin = class {
|
|
|
5602
5953
|
heuristicTextExtraction(buffer) {
|
|
5603
5954
|
return this.extractStringsFromBytes(new Uint8Array(buffer));
|
|
5604
5955
|
}
|
|
5605
|
-
|
|
5956
|
+
/**
|
|
5957
|
+
* Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
|
|
5958
|
+
*/
|
|
5959
|
+
parseOdfChartToSvg(zipBytes) {
|
|
5960
|
+
try {
|
|
5961
|
+
const unzipped = fflate.unzipSync(zipBytes);
|
|
5962
|
+
const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
|
|
5963
|
+
if (!contentXml) return "";
|
|
5964
|
+
const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
|
|
5965
|
+
if (rowsMatch.length < 2) return "";
|
|
5966
|
+
const headers = [];
|
|
5967
|
+
const firstRow = rowsMatch[0];
|
|
5968
|
+
const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
|
|
5969
|
+
for (const h of headerCells) {
|
|
5970
|
+
headers.push(h.replace(/<\/?text:p>/g, "").trim());
|
|
5971
|
+
}
|
|
5972
|
+
const categories = [];
|
|
5973
|
+
const seriesValues = headers.map(() => []);
|
|
5974
|
+
for (let r = 1; r < rowsMatch.length; r++) {
|
|
5975
|
+
const rowStr = rowsMatch[r];
|
|
5976
|
+
if (!rowStr) continue;
|
|
5977
|
+
const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
|
|
5978
|
+
if (cells.length > 0 && cells[0]) {
|
|
5979
|
+
const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
|
|
5980
|
+
categories.push(catMatch ? catMatch[1] : "Row " + r);
|
|
5981
|
+
for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
|
|
5982
|
+
const cellStr = cells[c];
|
|
5983
|
+
if (!cellStr) continue;
|
|
5984
|
+
const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
|
|
5985
|
+
const series = seriesValues[c - 1];
|
|
5986
|
+
if (series) {
|
|
5987
|
+
series.push(valMatch ? parseFloat(valMatch[1]) : 0);
|
|
5988
|
+
}
|
|
5989
|
+
}
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
|
|
5993
|
+
const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
|
|
5994
|
+
let cIdx = 0;
|
|
5995
|
+
for (const cm of colorMatches) {
|
|
5996
|
+
if (cIdx < colors.length) colors[cIdx] = cm[1];
|
|
5997
|
+
cIdx++;
|
|
5998
|
+
}
|
|
5999
|
+
let maxVal = 10;
|
|
6000
|
+
for (const s of seriesValues) {
|
|
6001
|
+
for (const v of s) {
|
|
6002
|
+
if (v > maxVal) maxVal = v;
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
maxVal = Math.ceil(maxVal * 1.15);
|
|
6006
|
+
const width = 560;
|
|
6007
|
+
const height = 280;
|
|
6008
|
+
const padLeft = 45;
|
|
6009
|
+
const padRight = 100;
|
|
6010
|
+
const padTop = 20;
|
|
6011
|
+
const padBottom = 40;
|
|
6012
|
+
const chartW = width - padLeft - padRight;
|
|
6013
|
+
const chartH = height - padTop - padBottom;
|
|
6014
|
+
let svg = `<svg viewBox="0 0 ${width} ${height}" width="100%" height="auto" style="max-width: 560px; height: 280px; margin: 16px auto; display: block; font-family: Calibri, sans-serif; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 6px; box-shadow: 0 1px 4px rgba(0,0,0,0.05);">`;
|
|
6015
|
+
for (let step = 0; step <= 4; step++) {
|
|
6016
|
+
const yVal = (maxVal / 4 * step).toFixed(1);
|
|
6017
|
+
const yPos = padTop + chartH - step / 4 * chartH;
|
|
6018
|
+
svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
|
|
6019
|
+
svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
|
|
6020
|
+
}
|
|
6021
|
+
const numCats = categories.length;
|
|
6022
|
+
const numSeries = headers.length;
|
|
6023
|
+
const groupW = chartW / numCats;
|
|
6024
|
+
const barW = Math.max(8, groupW * 0.7 / numSeries);
|
|
6025
|
+
const groupPad = (groupW - barW * numSeries) / 2;
|
|
6026
|
+
for (let catIdx = 0; catIdx < numCats; catIdx++) {
|
|
6027
|
+
const groupX = padLeft + catIdx * groupW + groupPad;
|
|
6028
|
+
for (let sIdx = 0; sIdx < numSeries; sIdx++) {
|
|
6029
|
+
const val = seriesValues[sIdx][catIdx] || 0;
|
|
6030
|
+
const barH = val / maxVal * chartH;
|
|
6031
|
+
const barX = groupX + sIdx * barW;
|
|
6032
|
+
const barY = padTop + chartH - barH;
|
|
6033
|
+
const col = colors[sIdx % colors.length];
|
|
6034
|
+
svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
|
|
6035
|
+
}
|
|
6036
|
+
const catX = padLeft + catIdx * groupW + groupW / 2;
|
|
6037
|
+
svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
|
|
6038
|
+
}
|
|
6039
|
+
let legendY = padTop + 20;
|
|
6040
|
+
for (let sIdx = 0; sIdx < numSeries; sIdx++) {
|
|
6041
|
+
const col = colors[sIdx % colors.length];
|
|
6042
|
+
svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
|
|
6043
|
+
svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
|
|
6044
|
+
legendY += 20;
|
|
6045
|
+
}
|
|
6046
|
+
svg += "</svg>";
|
|
6047
|
+
return svg;
|
|
6048
|
+
} catch (e) {
|
|
6049
|
+
console.warn("[DocPlugin] Error generating chart SVG:", e);
|
|
6050
|
+
return "";
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
cleanWordDocFields(text, chartSvg = "") {
|
|
6054
|
+
if (!text) return "";
|
|
6055
|
+
let cleaned = text.replace(
|
|
6056
|
+
/\x13\s*EMBED\b[\s\S]*?\x15/gi,
|
|
6057
|
+
() => chartSvg ? `
|
|
6058
|
+
|
|
6059
|
+
${chartSvg}
|
|
6060
|
+
|
|
6061
|
+
` : ""
|
|
6062
|
+
);
|
|
6063
|
+
cleaned = cleaned.replace(
|
|
6064
|
+
/\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
|
|
6065
|
+
(_match, url, label) => {
|
|
6066
|
+
const cleanUrl = url.trim();
|
|
6067
|
+
const cleanLabel = label.trim() || cleanUrl;
|
|
6068
|
+
return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
|
|
6069
|
+
}
|
|
6070
|
+
);
|
|
6071
|
+
cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
|
|
6072
|
+
if (/[\x00-\x1F]/.test(res)) return "";
|
|
6073
|
+
return res.trim();
|
|
6074
|
+
});
|
|
6075
|
+
cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
|
|
6076
|
+
cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
|
|
6077
|
+
cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
|
|
6078
|
+
cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
|
|
6079
|
+
return cleaned;
|
|
6080
|
+
}
|
|
6081
|
+
splitIntoPages(text, chartSvg = "") {
|
|
5606
6082
|
if (!text) return [""];
|
|
5607
|
-
const
|
|
6083
|
+
const cleanedText = this.cleanWordDocFields(text, chartSvg);
|
|
6084
|
+
const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
|
|
5608
6085
|
const explicitParts = normalized.split(/[\x0C\f]|\n\s*[-=_]{3,}\s*(?:PAGE|Page|page break)[\s\d\w-]*[-=_]{3,}\s*\n/i).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
5609
6086
|
if (explicitParts.length === 0) explicitParts.push(normalized);
|
|
5610
|
-
const maxLinesPerPage =
|
|
6087
|
+
const maxLinesPerPage = 34;
|
|
5611
6088
|
const charsPerLine = 80;
|
|
5612
6089
|
const finalPages = [];
|
|
5613
6090
|
for (const part of explicitParts) {
|
|
@@ -5615,7 +6092,8 @@ var DocPlugin = class {
|
|
|
5615
6092
|
let currentLines = [];
|
|
5616
6093
|
let count = 0;
|
|
5617
6094
|
for (const line of lines) {
|
|
5618
|
-
const
|
|
6095
|
+
const isSvg = line.includes("<svg");
|
|
6096
|
+
const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
|
|
5619
6097
|
if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
|
|
5620
6098
|
finalPages.push(currentLines.join("\n"));
|
|
5621
6099
|
currentLines = [];
|
|
@@ -5655,9 +6133,44 @@ var DocPlugin = class {
|
|
|
5655
6133
|
tableLines = [];
|
|
5656
6134
|
}
|
|
5657
6135
|
};
|
|
6136
|
+
const sanitizeOptions = {
|
|
6137
|
+
ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
|
|
6138
|
+
ADD_ATTR: [
|
|
6139
|
+
"href",
|
|
6140
|
+
"target",
|
|
6141
|
+
"rel",
|
|
6142
|
+
"style",
|
|
6143
|
+
"viewBox",
|
|
6144
|
+
"width",
|
|
6145
|
+
"height",
|
|
6146
|
+
"x",
|
|
6147
|
+
"y",
|
|
6148
|
+
"x1",
|
|
6149
|
+
"y1",
|
|
6150
|
+
"x2",
|
|
6151
|
+
"y2",
|
|
6152
|
+
"fill",
|
|
6153
|
+
"stroke",
|
|
6154
|
+
"stroke-width",
|
|
6155
|
+
"stroke-dasharray",
|
|
6156
|
+
"rx",
|
|
6157
|
+
"font-size",
|
|
6158
|
+
"text-anchor"
|
|
6159
|
+
]
|
|
6160
|
+
};
|
|
5658
6161
|
let i = 0;
|
|
5659
6162
|
while (i < lines.length) {
|
|
5660
6163
|
let line = lines[i];
|
|
6164
|
+
if (line.includes("<svg")) {
|
|
6165
|
+
if (inList) {
|
|
6166
|
+
html += "</ul>";
|
|
6167
|
+
inList = false;
|
|
6168
|
+
}
|
|
6169
|
+
flushTable();
|
|
6170
|
+
html += line;
|
|
6171
|
+
i++;
|
|
6172
|
+
continue;
|
|
6173
|
+
}
|
|
5661
6174
|
let tabCount = (line.match(/\t/g) || []).length;
|
|
5662
6175
|
if (tabCount > 0) {
|
|
5663
6176
|
let j = i;
|
|
@@ -5696,20 +6209,20 @@ var DocPlugin = class {
|
|
|
5696
6209
|
html += "</ul>";
|
|
5697
6210
|
inList = false;
|
|
5698
6211
|
}
|
|
5699
|
-
html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line)}</h2>`;
|
|
6212
|
+
html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line, sanitizeOptions)}</h2>`;
|
|
5700
6213
|
} else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
|
|
5701
6214
|
if (!inList) {
|
|
5702
6215
|
html += '<ul style="margin: 8px 0; padding-left: 24px;">';
|
|
5703
6216
|
inList = true;
|
|
5704
6217
|
}
|
|
5705
6218
|
const bulletText = line.replace(/^[•\-\*]\s*/, "");
|
|
5706
|
-
html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText)}</li>`;
|
|
6219
|
+
html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
|
|
5707
6220
|
} else {
|
|
5708
6221
|
if (inList) {
|
|
5709
6222
|
html += "</ul>";
|
|
5710
6223
|
inList = false;
|
|
5711
6224
|
}
|
|
5712
|
-
html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
|
|
6225
|
+
html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
|
|
5713
6226
|
}
|
|
5714
6227
|
i++;
|
|
5715
6228
|
}
|