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