@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/angular.js
CHANGED
|
@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
|
|
|
3
3
|
import DOMPurify6 from 'dompurify';
|
|
4
4
|
import * as pdfjsLib from 'pdfjs-dist';
|
|
5
5
|
import * as docx from 'docx-preview';
|
|
6
|
+
import * as fflate from 'fflate';
|
|
6
7
|
import { unzipSync, strFromU8, unzip } from 'fflate';
|
|
7
8
|
import * as XLSX from 'xlsx';
|
|
8
9
|
import hljs from 'highlight.js';
|
|
@@ -385,16 +386,21 @@ async function sourceToArrayBuffer(source, signal) {
|
|
|
385
386
|
metadata.mimeType = source.type || void 0;
|
|
386
387
|
metadata.extension = extractExtension(source.name);
|
|
387
388
|
buffer = await source.arrayBuffer();
|
|
388
|
-
} else if (source instanceof Blob) {
|
|
389
|
+
} else if (source instanceof Blob || source && typeof source.arrayBuffer === "function" && typeof source.size === "number") {
|
|
389
390
|
metadata.size = source.size;
|
|
390
391
|
metadata.mimeType = source.type || void 0;
|
|
392
|
+
if (source.name) {
|
|
393
|
+
metadata.name = source.name;
|
|
394
|
+
metadata.extension = extractExtension(source.name);
|
|
395
|
+
}
|
|
391
396
|
buffer = await source.arrayBuffer();
|
|
392
|
-
} else if (source instanceof ArrayBuffer) {
|
|
397
|
+
} else if (source instanceof ArrayBuffer || Object.prototype.toString.call(source) === "[object ArrayBuffer]" || source && typeof source.byteLength === "number" && typeof source.slice === "function") {
|
|
393
398
|
buffer = source;
|
|
394
|
-
} else if (source instanceof Uint8Array) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
399
|
+
} else if (source instanceof Uint8Array || ArrayBuffer.isView(source)) {
|
|
400
|
+
const view = source;
|
|
401
|
+
buffer = view.buffer.slice(
|
|
402
|
+
view.byteOffset,
|
|
403
|
+
view.byteOffset + view.byteLength
|
|
398
404
|
);
|
|
399
405
|
} else {
|
|
400
406
|
throw new Error("Unsupported file source type");
|
|
@@ -479,6 +485,45 @@ function createElement(tag, attrs, ...children) {
|
|
|
479
485
|
}
|
|
480
486
|
return el;
|
|
481
487
|
}
|
|
488
|
+
var DB_NAME = "PreviewFileTransferDB";
|
|
489
|
+
var DB_STORE = "transfers";
|
|
490
|
+
function openDB() {
|
|
491
|
+
return new Promise((resolve, reject) => {
|
|
492
|
+
if (typeof indexedDB === "undefined") {
|
|
493
|
+
return reject(new Error("IndexedDB is not available"));
|
|
494
|
+
}
|
|
495
|
+
const req = indexedDB.open(DB_NAME, 1);
|
|
496
|
+
req.onupgradeneeded = () => {
|
|
497
|
+
const db = req.result;
|
|
498
|
+
if (!db.objectStoreNames.contains(DB_STORE)) {
|
|
499
|
+
db.createObjectStore(DB_STORE, { keyPath: "id" });
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
req.onsuccess = () => resolve(req.result);
|
|
503
|
+
req.onerror = () => reject(req.error);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
async function saveTransferPayload(id, payload) {
|
|
507
|
+
if (typeof window !== "undefined") {
|
|
508
|
+
try {
|
|
509
|
+
window[id] = payload;
|
|
510
|
+
window.__lastTransfer = payload;
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
const db = await openDB();
|
|
516
|
+
return new Promise((resolve, reject) => {
|
|
517
|
+
const tx = db.transaction(DB_STORE, "readwrite");
|
|
518
|
+
const store = tx.objectStore(DB_STORE);
|
|
519
|
+
store.put({ id, ...payload, timestamp: Date.now() });
|
|
520
|
+
tx.oncomplete = () => resolve();
|
|
521
|
+
tx.onerror = () => reject(tx.error);
|
|
522
|
+
});
|
|
523
|
+
} catch (e) {
|
|
524
|
+
console.warn("[saveTransferPayload] IndexedDB store warning:", e);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
482
527
|
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>`;
|
|
483
528
|
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>`;
|
|
484
529
|
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>`;
|
|
@@ -498,6 +543,7 @@ var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" str
|
|
|
498
543
|
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>`;
|
|
499
544
|
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>`;
|
|
500
545
|
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>`;
|
|
546
|
+
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>`;
|
|
501
547
|
var ICON_MAP = {
|
|
502
548
|
"zoom-in": ICON_ZOOM_IN,
|
|
503
549
|
"zoom-out": ICON_ZOOM_OUT,
|
|
@@ -524,7 +570,9 @@ var ICON_MAP = {
|
|
|
524
570
|
"forward-10": ICON_FAST_FORWARD,
|
|
525
571
|
"rewind": ICON_REWIND,
|
|
526
572
|
"replay-10": ICON_REWIND,
|
|
527
|
-
"speed": ICON_SPEED
|
|
573
|
+
"speed": ICON_SPEED,
|
|
574
|
+
"open-window": ICON_EXTERNAL_WINDOW,
|
|
575
|
+
"external-window": ICON_EXTERNAL_WINDOW
|
|
528
576
|
};
|
|
529
577
|
var ToolbarController = class {
|
|
530
578
|
el;
|
|
@@ -667,9 +715,11 @@ var ToolbarController = class {
|
|
|
667
715
|
type: "button",
|
|
668
716
|
"data-action-id": id
|
|
669
717
|
});
|
|
670
|
-
const
|
|
671
|
-
if (
|
|
672
|
-
btn.innerHTML =
|
|
718
|
+
const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
|
|
719
|
+
if (internalSvg) {
|
|
720
|
+
btn.innerHTML = internalSvg;
|
|
721
|
+
} else if (iconHtml && iconHtml.startsWith("<svg")) {
|
|
722
|
+
btn.innerHTML = sanitizeSVG(iconHtml);
|
|
673
723
|
} else {
|
|
674
724
|
btn.textContent = title || id;
|
|
675
725
|
}
|
|
@@ -741,7 +791,7 @@ var ThumbnailPanel = class {
|
|
|
741
791
|
}
|
|
742
792
|
}
|
|
743
793
|
};
|
|
744
|
-
var FilePreviewViewer = class {
|
|
794
|
+
var FilePreviewViewer = class _FilePreviewViewer {
|
|
745
795
|
plugins = [];
|
|
746
796
|
activeInstance = null;
|
|
747
797
|
abortController = null;
|
|
@@ -778,6 +828,7 @@ var FilePreviewViewer = class {
|
|
|
778
828
|
* Preview a file in the given container element.
|
|
779
829
|
*/
|
|
780
830
|
async preview(container, source, options = {}) {
|
|
831
|
+
this.currentOptions = options;
|
|
781
832
|
this.abort();
|
|
782
833
|
this.abortController = new AbortController();
|
|
783
834
|
const { signal } = this.abortController;
|
|
@@ -787,7 +838,10 @@ var FilePreviewViewer = class {
|
|
|
787
838
|
this.showLoading();
|
|
788
839
|
try {
|
|
789
840
|
const { buffer, metadata } = await sourceToArrayBuffer(source, signal);
|
|
790
|
-
|
|
841
|
+
if (options.metadata) {
|
|
842
|
+
Object.assign(metadata, options.metadata);
|
|
843
|
+
}
|
|
844
|
+
this.currentBuffer = buffer.slice(0);
|
|
791
845
|
this.currentMetadata = metadata;
|
|
792
846
|
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
|
|
793
847
|
const fileInfo = { metadata, buffer };
|
|
@@ -817,6 +871,7 @@ var FilePreviewViewer = class {
|
|
|
817
871
|
}
|
|
818
872
|
});
|
|
819
873
|
this.activeInstance = instance;
|
|
874
|
+
instance.openInSeparateWindow = () => this.openInSeparateWindow();
|
|
820
875
|
this.hideLoading();
|
|
821
876
|
this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
|
|
822
877
|
if (options.showToolbar !== false && this.toolbar) {
|
|
@@ -826,26 +881,16 @@ var FilePreviewViewer = class {
|
|
|
826
881
|
actions.push({
|
|
827
882
|
id: "fullscreen",
|
|
828
883
|
icon: "fullscreen",
|
|
829
|
-
label: "
|
|
884
|
+
label: "Fullscreen",
|
|
830
885
|
type: "button",
|
|
831
886
|
group: "view",
|
|
832
|
-
execute:
|
|
887
|
+
execute: () => {
|
|
833
888
|
try {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
if (this.wrapperEl?.requestFullscreen) {
|
|
838
|
-
await this.wrapperEl.requestFullscreen().catch(() => {
|
|
839
|
-
this.wrapperEl?.classList.add("fp-fullscreen-active");
|
|
840
|
-
});
|
|
841
|
-
} else {
|
|
842
|
-
this.wrapperEl?.classList.add("fp-fullscreen-active");
|
|
843
|
-
}
|
|
889
|
+
if (!document.fullscreenElement) {
|
|
890
|
+
this.wrapperEl?.requestFullscreen?.();
|
|
891
|
+
this.wrapperEl?.classList.add("fp-fullscreen-active");
|
|
844
892
|
} else {
|
|
845
|
-
|
|
846
|
-
await document.exitFullscreen().catch(() => {
|
|
847
|
-
});
|
|
848
|
-
}
|
|
893
|
+
document.exitFullscreen?.();
|
|
849
894
|
this.wrapperEl?.classList.remove("fp-fullscreen-active");
|
|
850
895
|
}
|
|
851
896
|
} catch {
|
|
@@ -857,6 +902,28 @@ var FilePreviewViewer = class {
|
|
|
857
902
|
}
|
|
858
903
|
});
|
|
859
904
|
}
|
|
905
|
+
const openWinAction = actions.find((a) => a.id === "open-window");
|
|
906
|
+
if (openWinAction) {
|
|
907
|
+
if (options?._isSeparateWindow) {
|
|
908
|
+
const idx = actions.indexOf(openWinAction);
|
|
909
|
+
if (idx !== -1) actions.splice(idx, 1);
|
|
910
|
+
} else {
|
|
911
|
+
openWinAction.execute = () => {
|
|
912
|
+
this.openInSeparateWindow();
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
} else if (!options?._isSeparateWindow) {
|
|
916
|
+
actions.push({
|
|
917
|
+
id: "open-window",
|
|
918
|
+
icon: "open-window",
|
|
919
|
+
label: "Open in Separate Full Window",
|
|
920
|
+
type: "button",
|
|
921
|
+
group: "actions",
|
|
922
|
+
execute: () => {
|
|
923
|
+
this.openInSeparateWindow();
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
}
|
|
860
927
|
this.toolbar.update(actions);
|
|
861
928
|
this.toolbar.show();
|
|
862
929
|
}
|
|
@@ -889,6 +956,104 @@ var FilePreviewViewer = class {
|
|
|
889
956
|
throw error;
|
|
890
957
|
}
|
|
891
958
|
}
|
|
959
|
+
/**
|
|
960
|
+
* Opens the current file preview in a separate full browser window.
|
|
961
|
+
*/
|
|
962
|
+
openInSeparateWindow() {
|
|
963
|
+
if (!this.currentBuffer) {
|
|
964
|
+
console.warn("[FilePreviewViewer] No active file buffer to open in separate window");
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
if (this.currentOptions.onOpenSeparateWindow) {
|
|
968
|
+
return this.currentOptions.onOpenSeparateWindow({
|
|
969
|
+
buffer: this.currentBuffer,
|
|
970
|
+
metadata: this.currentMetadata || { name: "Document" },
|
|
971
|
+
options: this.currentOptions
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
const transferId = "fp_win_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
|
|
975
|
+
let clonedBuffer;
|
|
976
|
+
try {
|
|
977
|
+
clonedBuffer = this.currentBuffer.slice(0);
|
|
978
|
+
} catch {
|
|
979
|
+
clonedBuffer = this.currentBuffer;
|
|
980
|
+
}
|
|
981
|
+
const payload = {
|
|
982
|
+
buffer: clonedBuffer,
|
|
983
|
+
metadata: this.currentMetadata ? { ...this.currentMetadata } : void 0,
|
|
984
|
+
options: { ...this.currentOptions, _isSeparateWindow: true }
|
|
985
|
+
};
|
|
986
|
+
if (typeof window !== "undefined") {
|
|
987
|
+
try {
|
|
988
|
+
window[transferId] = payload;
|
|
989
|
+
window.__lastTransfer = payload;
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
saveTransferPayload(transferId, payload).catch((err) => {
|
|
994
|
+
console.warn("[FilePreviewViewer] Transfer payload save warning:", err);
|
|
995
|
+
});
|
|
996
|
+
let targetUrl = null;
|
|
997
|
+
if (this.currentOptions.standaloneViewerUrl) {
|
|
998
|
+
const u = new URL(this.currentOptions.standaloneViewerUrl, window.location.href);
|
|
999
|
+
u.searchParams.set("mode", "fullscreen");
|
|
1000
|
+
u.searchParams.set("transferId", transferId);
|
|
1001
|
+
targetUrl = u.toString();
|
|
1002
|
+
} else if (typeof window !== "undefined" && window.location?.href && !window.location.href.startsWith("about:")) {
|
|
1003
|
+
const u = new URL(window.location.href);
|
|
1004
|
+
u.searchParams.set("mode", "fullscreen");
|
|
1005
|
+
u.searchParams.set("transferId", transferId);
|
|
1006
|
+
targetUrl = u.toString();
|
|
1007
|
+
}
|
|
1008
|
+
if (targetUrl) {
|
|
1009
|
+
const newWin2 = window.open(targetUrl, "_blank");
|
|
1010
|
+
if (!newWin2) {
|
|
1011
|
+
alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
|
|
1012
|
+
return null;
|
|
1013
|
+
}
|
|
1014
|
+
return newWin2;
|
|
1015
|
+
}
|
|
1016
|
+
const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
|
|
1017
|
+
const newWin = window.open("", "_blank");
|
|
1018
|
+
if (!newWin) {
|
|
1019
|
+
alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
newWin.document.title = title;
|
|
1023
|
+
newWin.document.body.style.margin = "0";
|
|
1024
|
+
newWin.document.body.style.padding = "0";
|
|
1025
|
+
newWin.document.body.style.width = "100vw";
|
|
1026
|
+
newWin.document.body.style.height = "100vh";
|
|
1027
|
+
newWin.document.body.style.overflow = "hidden";
|
|
1028
|
+
newWin.document.body.style.backgroundColor = "#f8fafc";
|
|
1029
|
+
const headNodes = document.querySelectorAll('link[rel="stylesheet"], style');
|
|
1030
|
+
headNodes.forEach((node) => {
|
|
1031
|
+
newWin.document.head.appendChild(node.cloneNode(true));
|
|
1032
|
+
});
|
|
1033
|
+
const root = newWin.document.createElement("div");
|
|
1034
|
+
root.id = "full-window-preview-root";
|
|
1035
|
+
root.style.width = "100%";
|
|
1036
|
+
root.style.height = "100%";
|
|
1037
|
+
root.style.overflow = "hidden";
|
|
1038
|
+
newWin.document.body.appendChild(root);
|
|
1039
|
+
const separateViewer = new _FilePreviewViewer();
|
|
1040
|
+
for (const plugin of this.plugins) {
|
|
1041
|
+
separateViewer.registerPlugin(plugin);
|
|
1042
|
+
}
|
|
1043
|
+
separateViewer.preview(root, this.currentBuffer.slice(0), {
|
|
1044
|
+
...this.currentOptions,
|
|
1045
|
+
showToolbar: true,
|
|
1046
|
+
toolbarPosition: "top",
|
|
1047
|
+
metadata: this.currentMetadata || void 0,
|
|
1048
|
+
_isSeparateWindow: true
|
|
1049
|
+
}).catch((err) => {
|
|
1050
|
+
console.error("[FilePreviewViewer] Error rendering in separate window:", err);
|
|
1051
|
+
});
|
|
1052
|
+
newWin.addEventListener("beforeunload", () => {
|
|
1053
|
+
separateViewer.destroy();
|
|
1054
|
+
});
|
|
1055
|
+
return newWin;
|
|
1056
|
+
}
|
|
892
1057
|
/**
|
|
893
1058
|
* Subscribe to viewer events.
|
|
894
1059
|
*/
|
|
@@ -1294,8 +1459,20 @@ var CfbfReader = class {
|
|
|
1294
1459
|
}
|
|
1295
1460
|
};
|
|
1296
1461
|
if (typeof window !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
|
|
1297
|
-
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
1298
|
-
|
|
1462
|
+
if (!pdfjsLib.GlobalWorkerOptions.workerPort && !pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
|
1463
|
+
const customWorker = window.__PDF_WORKER_SRC__;
|
|
1464
|
+
if (customWorker) {
|
|
1465
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = customWorker;
|
|
1466
|
+
} else {
|
|
1467
|
+
try {
|
|
1468
|
+
pdfjsLib.GlobalWorkerOptions.workerPort = new Worker(
|
|
1469
|
+
new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url),
|
|
1470
|
+
{ type: "module" }
|
|
1471
|
+
);
|
|
1472
|
+
} catch {
|
|
1473
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.mjs";
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1299
1476
|
}
|
|
1300
1477
|
}
|
|
1301
1478
|
var PdfPlugin = class {
|
|
@@ -1389,6 +1566,14 @@ var PdfPlugin = class {
|
|
|
1389
1566
|
type: "button",
|
|
1390
1567
|
group: "actions",
|
|
1391
1568
|
execute: () => instance.print?.()
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
id: "open-window",
|
|
1572
|
+
icon: "open-window",
|
|
1573
|
+
label: "Open in Separate Full Window",
|
|
1574
|
+
type: "button",
|
|
1575
|
+
group: "actions",
|
|
1576
|
+
execute: () => instance.openInSeparateWindow?.()
|
|
1392
1577
|
}
|
|
1393
1578
|
];
|
|
1394
1579
|
}
|
|
@@ -1438,18 +1623,21 @@ var PdfPlugin = class {
|
|
|
1438
1623
|
indicator.style.pointerEvents = "none";
|
|
1439
1624
|
container.appendChild(indicator);
|
|
1440
1625
|
ctx.container.appendChild(container);
|
|
1626
|
+
const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
|
|
1627
|
+
const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
|
|
1441
1628
|
const loadingTask = pdfjsLib.getDocument({
|
|
1442
|
-
data: new Uint8Array(ctx.buffer),
|
|
1443
|
-
cMapUrl:
|
|
1629
|
+
data: new Uint8Array(ctx.buffer.slice(0)),
|
|
1630
|
+
cMapUrl: cmapsUrl,
|
|
1444
1631
|
cMapPacked: true,
|
|
1445
|
-
standardFontDataUrl:
|
|
1632
|
+
standardFontDataUrl: standardFontsUrl,
|
|
1633
|
+
verbosity: 0
|
|
1446
1634
|
});
|
|
1447
1635
|
const pdfDoc = await loadingTask.promise;
|
|
1448
1636
|
const totalPages = Math.max(1, pdfDoc.numPages);
|
|
1449
1637
|
let currentPage = 1;
|
|
1450
1638
|
let zoomScale = 1;
|
|
1451
1639
|
let rotation = 0;
|
|
1452
|
-
let fitMode = "
|
|
1640
|
+
let fitMode = "page";
|
|
1453
1641
|
let currentRenderTask = null;
|
|
1454
1642
|
const renderPage = async (pageNum) => {
|
|
1455
1643
|
if (currentRenderTask) {
|
|
@@ -1469,15 +1657,15 @@ var PdfPlugin = class {
|
|
|
1469
1657
|
const containerWidth = container.clientWidth || 900;
|
|
1470
1658
|
const containerHeight = container.clientHeight || 700;
|
|
1471
1659
|
const unscaledVp = page.getViewport({ scale: 1, rotation });
|
|
1472
|
-
const availWidth = Math.max(
|
|
1473
|
-
const availHeight = Math.max(
|
|
1660
|
+
const availWidth = Math.max(320, containerWidth - 48);
|
|
1661
|
+
const availHeight = Math.max(550, containerHeight - 88);
|
|
1474
1662
|
const scaleW = availWidth / unscaledVp.width;
|
|
1475
1663
|
const scaleH = availHeight / unscaledVp.height;
|
|
1476
1664
|
let fitScale;
|
|
1477
1665
|
if (fitMode === "page") {
|
|
1478
|
-
fitScale = Math.max(0.
|
|
1666
|
+
fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
|
|
1479
1667
|
} else {
|
|
1480
|
-
fitScale = Math.max(0.65, Math.min(1.
|
|
1668
|
+
fitScale = Math.max(0.65, Math.min(1.25, scaleW));
|
|
1481
1669
|
}
|
|
1482
1670
|
const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
|
|
1483
1671
|
const pixelRatio = window.devicePixelRatio || 1;
|
|
@@ -1503,7 +1691,7 @@ var PdfPlugin = class {
|
|
|
1503
1691
|
currentRenderTask = null;
|
|
1504
1692
|
}
|
|
1505
1693
|
};
|
|
1506
|
-
|
|
1694
|
+
renderPage(1);
|
|
1507
1695
|
let resizeTimer = null;
|
|
1508
1696
|
const resizeObserver = new ResizeObserver(() => {
|
|
1509
1697
|
if (resizeTimer) clearTimeout(resizeTimer);
|
|
@@ -1547,7 +1735,7 @@ var PdfPlugin = class {
|
|
|
1547
1735
|
renderPage(currentPage);
|
|
1548
1736
|
},
|
|
1549
1737
|
fitToPage: () => {
|
|
1550
|
-
fitMode = fitMode === "
|
|
1738
|
+
fitMode = fitMode === "page" ? "width" : "page";
|
|
1551
1739
|
zoomScale = 1;
|
|
1552
1740
|
rotation = 0;
|
|
1553
1741
|
renderPage(currentPage);
|
|
@@ -2024,6 +2212,14 @@ var DocxPlugin = class {
|
|
|
2024
2212
|
type: "button",
|
|
2025
2213
|
group: "actions",
|
|
2026
2214
|
execute: () => instance.print?.()
|
|
2215
|
+
},
|
|
2216
|
+
{
|
|
2217
|
+
id: "open-window",
|
|
2218
|
+
icon: "open-window",
|
|
2219
|
+
label: "Open in Separate Full Window",
|
|
2220
|
+
type: "button",
|
|
2221
|
+
group: "actions",
|
|
2222
|
+
execute: () => instance.openInSeparateWindow?.()
|
|
2027
2223
|
}
|
|
2028
2224
|
);
|
|
2029
2225
|
return actions;
|
|
@@ -2083,6 +2279,31 @@ var DocxPlugin = class {
|
|
|
2083
2279
|
}
|
|
2084
2280
|
if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
|
|
2085
2281
|
renderedSuccessfully = true;
|
|
2282
|
+
try {
|
|
2283
|
+
const unzipped = unzipSync(new Uint8Array(ctx.buffer));
|
|
2284
|
+
const chartKeys = Object.keys(unzipped).filter((k) => k.replace(/^[./\\]+/, "").toLowerCase().startsWith("word/charts/chart") && k.endsWith(".xml")).sort();
|
|
2285
|
+
if (chartKeys.length > 0) {
|
|
2286
|
+
const allDivs = Array.from(wrapper.querySelectorAll("div"));
|
|
2287
|
+
const emptyContainers = allDivs.filter((div) => {
|
|
2288
|
+
const st = div.getAttribute("style") || "";
|
|
2289
|
+
return st.includes("width:") && st.includes("height:") && div.children.length === 0 && (div.textContent?.trim().length ?? 0) === 0;
|
|
2290
|
+
});
|
|
2291
|
+
chartKeys.forEach((cKey, idx) => {
|
|
2292
|
+
const target = emptyContainers[idx];
|
|
2293
|
+
if (target) {
|
|
2294
|
+
const xmlStr = strFromU8(unzipped[cKey]);
|
|
2295
|
+
const svg = this.parseAndRenderChartSvg(xmlStr);
|
|
2296
|
+
if (svg) {
|
|
2297
|
+
target.innerHTML = svg;
|
|
2298
|
+
target.style.display = "block";
|
|
2299
|
+
target.style.margin = "12px auto";
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
} catch (chartErr) {
|
|
2305
|
+
console.warn("[DocxPlugin] Non-critical error rendering DrawingML charts:", chartErr);
|
|
2306
|
+
}
|
|
2086
2307
|
}
|
|
2087
2308
|
} catch (err) {
|
|
2088
2309
|
console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
|
|
@@ -2114,64 +2335,74 @@ var DocxPlugin = class {
|
|
|
2114
2335
|
}
|
|
2115
2336
|
let sections = Array.from(wrapper.querySelectorAll("section.docx"));
|
|
2116
2337
|
const cards = Array.from(wrapper.querySelectorAll(".fp-docx-page-card"));
|
|
2117
|
-
if (sections.length
|
|
2118
|
-
const
|
|
2119
|
-
const
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
const
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
nextSec.
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
nextArticle
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
nextSec.appendChild(
|
|
2338
|
+
if (sections.length > 0 && cards.length === 0) {
|
|
2339
|
+
const finalSections = [];
|
|
2340
|
+
for (const singleSec of sections) {
|
|
2341
|
+
const contentContainer = singleSec.querySelector("article") || singleSec;
|
|
2342
|
+
const children = Array.from(contentContainer.children);
|
|
2343
|
+
const pageH = singleSec.offsetHeight > 1300 ? 1122 : Math.max(1056, singleSec.offsetHeight);
|
|
2344
|
+
const secH = singleSec.scrollHeight || singleSec.offsetHeight;
|
|
2345
|
+
if (secH > pageH * 1.25 && children.length > 1) {
|
|
2346
|
+
const childHeights = children.map((c) => {
|
|
2347
|
+
const rectH = c.getBoundingClientRect().height;
|
|
2348
|
+
const offH = c.offsetHeight;
|
|
2349
|
+
const textLen = c.textContent?.trim().length || 0;
|
|
2350
|
+
const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
|
|
2351
|
+
return Math.max(rectH, offH, estH);
|
|
2352
|
+
});
|
|
2353
|
+
const parent = singleSec.parentElement || wrapper;
|
|
2354
|
+
const headerEl = singleSec.querySelector("header");
|
|
2355
|
+
const footerEl = singleSec.querySelector("footer");
|
|
2356
|
+
contentContainer.innerHTML = "";
|
|
2357
|
+
singleSec.style.minHeight = `${pageH}px`;
|
|
2358
|
+
singleSec.style.boxSizing = "border-box";
|
|
2359
|
+
let curContent = contentContainer;
|
|
2360
|
+
let curSec = singleSec;
|
|
2361
|
+
let curH = 0;
|
|
2362
|
+
const maxH = pageH - 140;
|
|
2363
|
+
finalSections.push(singleSec);
|
|
2364
|
+
for (let i = 0; i < children.length; i++) {
|
|
2365
|
+
const child = children[i];
|
|
2366
|
+
const chH = childHeights[i];
|
|
2367
|
+
curContent.appendChild(child);
|
|
2368
|
+
curH += chH;
|
|
2369
|
+
if (curH >= maxH && i < children.length - 1) {
|
|
2370
|
+
const nextSec = document.createElement("section");
|
|
2371
|
+
nextSec.className = singleSec.className;
|
|
2372
|
+
nextSec.style.cssText = singleSec.style.cssText;
|
|
2373
|
+
nextSec.style.minHeight = `${pageH}px`;
|
|
2374
|
+
nextSec.style.boxSizing = "border-box";
|
|
2375
|
+
nextSec.style.backgroundColor = "#ffffff";
|
|
2376
|
+
nextSec.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
|
|
2377
|
+
nextSec.style.borderRadius = "4px";
|
|
2378
|
+
nextSec.style.marginBottom = "24px";
|
|
2379
|
+
if (headerEl) {
|
|
2380
|
+
nextSec.appendChild(headerEl.cloneNode(true));
|
|
2381
|
+
}
|
|
2382
|
+
const nextArticle = document.createElement("article");
|
|
2383
|
+
if (contentContainer.tagName.toLowerCase() === "article") {
|
|
2384
|
+
nextArticle.style.cssText = contentContainer.style.cssText;
|
|
2385
|
+
}
|
|
2386
|
+
nextSec.appendChild(nextArticle);
|
|
2387
|
+
if (footerEl) {
|
|
2388
|
+
nextSec.appendChild(footerEl.cloneNode(true));
|
|
2389
|
+
}
|
|
2390
|
+
if (curSec.nextSibling) {
|
|
2391
|
+
parent.insertBefore(nextSec, curSec.nextSibling);
|
|
2392
|
+
} else {
|
|
2393
|
+
parent.appendChild(nextSec);
|
|
2394
|
+
}
|
|
2395
|
+
finalSections.push(nextSec);
|
|
2396
|
+
curSec = nextSec;
|
|
2397
|
+
curContent = nextArticle;
|
|
2398
|
+
curH = 0;
|
|
2166
2399
|
}
|
|
2167
|
-
parent.appendChild(nextSec);
|
|
2168
|
-
newSections.push(nextSec);
|
|
2169
|
-
curContent = nextArticle;
|
|
2170
|
-
curH = 0;
|
|
2171
2400
|
}
|
|
2401
|
+
} else {
|
|
2402
|
+
finalSections.push(singleSec);
|
|
2172
2403
|
}
|
|
2173
|
-
sections = newSections;
|
|
2174
2404
|
}
|
|
2405
|
+
sections = finalSections;
|
|
2175
2406
|
}
|
|
2176
2407
|
const pageElements = sections.length > 0 ? sections : cards;
|
|
2177
2408
|
const totalPages = Math.max(1, pageElements.length);
|
|
@@ -2563,6 +2794,88 @@ var DocxPlugin = class {
|
|
|
2563
2794
|
}
|
|
2564
2795
|
return result;
|
|
2565
2796
|
}
|
|
2797
|
+
parseAndRenderChartSvg(xmlStr, width = 500, height = 260) {
|
|
2798
|
+
const catMatches = [...xmlStr.matchAll(/<c:cat>[\s\S]*?<c:strCache>([\s\S]*?)<\/c:strCache>/g)];
|
|
2799
|
+
let categories = [];
|
|
2800
|
+
if (catMatches.length > 0) {
|
|
2801
|
+
categories = [...catMatches[0][1].matchAll(/<c:v>([^<]+)<\/c:v>/g)].map((m) => m[1]);
|
|
2802
|
+
}
|
|
2803
|
+
if (categories.length === 0) {
|
|
2804
|
+
categories = ["Category 1", "Category 2", "Category 3", "Category 4"];
|
|
2805
|
+
}
|
|
2806
|
+
const defaultColors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021", "#83caff"];
|
|
2807
|
+
const sers = [...xmlStr.matchAll(/<c:ser>([\s\S]*?)<\/c:ser>/g)];
|
|
2808
|
+
const series = [];
|
|
2809
|
+
sers.forEach((s, sIdx) => {
|
|
2810
|
+
const titleMatch = s[1].match(/<c:tx>[\s\S]*?<c:v>([^<]+)<\/c:v>/);
|
|
2811
|
+
const title = titleMatch ? titleMatch[1] : `Series ${sIdx + 1}`;
|
|
2812
|
+
const clrMatch = s[1].match(/<a:srgbClr\s+val="([^"]+)"/);
|
|
2813
|
+
const color = clrMatch ? "#" + clrMatch[1] : defaultColors[sIdx % defaultColors.length];
|
|
2814
|
+
const valMatch = s[1].match(/<c:val>[\s\S]*?<c:numCache>([\s\S]*?)<\/c:numCache>/);
|
|
2815
|
+
let values = [];
|
|
2816
|
+
if (valMatch) {
|
|
2817
|
+
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);
|
|
2818
|
+
}
|
|
2819
|
+
series.push({ title, color, values });
|
|
2820
|
+
});
|
|
2821
|
+
if (series.length === 0) return "";
|
|
2822
|
+
let maxVal = 10;
|
|
2823
|
+
series.forEach((s) => s.values.forEach((v) => {
|
|
2824
|
+
if (v > maxVal) maxVal = v;
|
|
2825
|
+
}));
|
|
2826
|
+
maxVal = Math.ceil(maxVal * 1.15);
|
|
2827
|
+
if (maxVal % 2 !== 0) maxVal++;
|
|
2828
|
+
const padLeft = 45;
|
|
2829
|
+
const padBottom = 55;
|
|
2830
|
+
const padTop = 20;
|
|
2831
|
+
const padRight = 20;
|
|
2832
|
+
const plotW = width - padLeft - padRight;
|
|
2833
|
+
const plotH = height - padTop - padBottom;
|
|
2834
|
+
const yTicks = 5;
|
|
2835
|
+
let gridLines = "";
|
|
2836
|
+
for (let i = 0; i <= yTicks; i++) {
|
|
2837
|
+
const val = maxVal / yTicks * i;
|
|
2838
|
+
const y = padTop + plotH - val / maxVal * plotH;
|
|
2839
|
+
gridLines += `<line x1="${padLeft}" y1="${y}" x2="${padLeft + plotW}" y2="${y}" stroke="#e2e8f0" stroke-width="1" />`;
|
|
2840
|
+
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>`;
|
|
2841
|
+
}
|
|
2842
|
+
const numCats = categories.length;
|
|
2843
|
+
const numSers = series.length;
|
|
2844
|
+
const groupW = plotW / numCats;
|
|
2845
|
+
const barW = Math.max(8, Math.min(28, groupW * 0.7 / numSers));
|
|
2846
|
+
const groupPad = (groupW - barW * numSers) / 2;
|
|
2847
|
+
let bars = "";
|
|
2848
|
+
let catLabels = "";
|
|
2849
|
+
for (let c = 0; c < numCats; c++) {
|
|
2850
|
+
const catX = padLeft + c * groupW;
|
|
2851
|
+
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>`;
|
|
2852
|
+
for (let s = 0; s < numSers; s++) {
|
|
2853
|
+
const val = series[s].values[c] ?? 0;
|
|
2854
|
+
const bH = Math.max(0, val / maxVal * plotH);
|
|
2855
|
+
const bX = catX + groupPad + s * barW;
|
|
2856
|
+
const bY = padTop + plotH - bH;
|
|
2857
|
+
bars += `<rect x="${bX}" y="${bY}" width="${barW - 2}" height="${bH}" fill="${series[s].color}" rx="1" />`;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
let legend = "";
|
|
2861
|
+
const legY = height - 12;
|
|
2862
|
+
let legX = padLeft + (plotW - numSers * 100) / 2;
|
|
2863
|
+
series.forEach((s) => {
|
|
2864
|
+
legend += `<rect x="${legX}" y="${legY - 9}" width="10" height="10" fill="${s.color}" rx="2" />`;
|
|
2865
|
+
legend += `<text x="${legX + 15}" y="${legY}" font-size="11" fill="#475569" font-family="Calibri, sans-serif">${s.title}</text>`;
|
|
2866
|
+
legX += 95;
|
|
2867
|
+
});
|
|
2868
|
+
return `
|
|
2869
|
+
<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">
|
|
2870
|
+
${gridLines}
|
|
2871
|
+
<line x1="${padLeft}" y1="${padTop + plotH}" x2="${padLeft + plotW}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
|
|
2872
|
+
<line x1="${padLeft}" y1="${padTop}" x2="${padLeft}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
|
|
2873
|
+
${bars}
|
|
2874
|
+
${catLabels}
|
|
2875
|
+
${legend}
|
|
2876
|
+
</svg>
|
|
2877
|
+
`.trim();
|
|
2878
|
+
}
|
|
2566
2879
|
};
|
|
2567
2880
|
function docxPlugin() {
|
|
2568
2881
|
return new DocxPlugin();
|
|
@@ -3129,6 +3442,16 @@ var CodePlugin = class {
|
|
|
3129
3442
|
execute: () => {
|
|
3130
3443
|
instance.print?.();
|
|
3131
3444
|
}
|
|
3445
|
+
},
|
|
3446
|
+
{
|
|
3447
|
+
id: "open-window",
|
|
3448
|
+
icon: "open-window",
|
|
3449
|
+
label: "Open in Separate Full Window",
|
|
3450
|
+
type: "button",
|
|
3451
|
+
group: "actions",
|
|
3452
|
+
execute: () => {
|
|
3453
|
+
instance.openInSeparateWindow?.();
|
|
3454
|
+
}
|
|
3132
3455
|
}
|
|
3133
3456
|
);
|
|
3134
3457
|
return actions;
|
|
@@ -4297,6 +4620,14 @@ var RtfPlugin = class {
|
|
|
4297
4620
|
type: "button",
|
|
4298
4621
|
group: "actions",
|
|
4299
4622
|
execute: () => instance.print?.()
|
|
4623
|
+
},
|
|
4624
|
+
{
|
|
4625
|
+
id: "open-window",
|
|
4626
|
+
icon: "open-window",
|
|
4627
|
+
label: "Open in Separate Full Window",
|
|
4628
|
+
type: "button",
|
|
4629
|
+
group: "actions",
|
|
4630
|
+
execute: () => instance.openInSeparateWindow?.()
|
|
4300
4631
|
}
|
|
4301
4632
|
);
|
|
4302
4633
|
return actions;
|
|
@@ -4349,59 +4680,54 @@ var RtfPlugin = class {
|
|
|
4349
4680
|
}
|
|
4350
4681
|
const doc = new RTFJS.Document(ctx.buffer, {});
|
|
4351
4682
|
const htmlElements = await doc.render();
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
const secH = singleEl.offsetHeight || singleEl.scrollHeight;
|
|
4357
|
-
if (secH > 1300 && children.length > 1) {
|
|
4358
|
-
const childHeights = children.map((c) => {
|
|
4359
|
-
const rectH = c.getBoundingClientRect().height;
|
|
4360
|
-
const offH = c.offsetHeight;
|
|
4361
|
-
const textLen = c.textContent?.trim().length || 0;
|
|
4362
|
-
const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
|
|
4363
|
-
return Math.max(rectH, offH, estH);
|
|
4364
|
-
});
|
|
4365
|
-
wrapper.innerHTML = "";
|
|
4366
|
-
const createRtfCard = () => {
|
|
4367
|
-
const card = document.createElement("div");
|
|
4368
|
-
card.className = "fp-rtf-page-card";
|
|
4369
|
-
card.style.backgroundColor = "#ffffff";
|
|
4370
|
-
card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
|
|
4371
|
-
card.style.borderRadius = "4px";
|
|
4372
|
-
card.style.padding = "72px 56px";
|
|
4373
|
-
card.style.width = "816px";
|
|
4374
|
-
card.style.minHeight = "1056px";
|
|
4375
|
-
card.style.boxSizing = "border-box";
|
|
4376
|
-
card.style.marginBottom = "24px";
|
|
4377
|
-
return card;
|
|
4378
|
-
};
|
|
4379
|
-
let curCard = createRtfCard();
|
|
4380
|
-
wrapper.appendChild(curCard);
|
|
4381
|
-
pageElements = [curCard];
|
|
4382
|
-
let curH = 0;
|
|
4383
|
-
const maxH = 920;
|
|
4384
|
-
for (let i = 0; i < children.length; i++) {
|
|
4385
|
-
const child = children[i];
|
|
4386
|
-
const chH = childHeights[i];
|
|
4387
|
-
curCard.appendChild(child);
|
|
4388
|
-
curH += chH;
|
|
4389
|
-
if (curH >= maxH && i < children.length - 1) {
|
|
4390
|
-
curCard = createRtfCard();
|
|
4391
|
-
wrapper.appendChild(curCard);
|
|
4392
|
-
pageElements.push(curCard);
|
|
4393
|
-
curH = 0;
|
|
4394
|
-
}
|
|
4395
|
-
}
|
|
4683
|
+
const contentNodes = [];
|
|
4684
|
+
for (const item of htmlElements) {
|
|
4685
|
+
if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
|
|
4686
|
+
contentNodes.push(...Array.from(item.children));
|
|
4396
4687
|
} else {
|
|
4397
|
-
|
|
4688
|
+
contentNodes.push(item);
|
|
4398
4689
|
}
|
|
4399
|
-
}
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4690
|
+
}
|
|
4691
|
+
wrapper.innerHTML = "";
|
|
4692
|
+
contentNodes.forEach((node) => wrapper.appendChild(node));
|
|
4693
|
+
const childHeights = contentNodes.map((c) => {
|
|
4694
|
+
const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
|
|
4695
|
+
const offH = c.offsetHeight || 0;
|
|
4696
|
+
const textLen = c.textContent?.trim().length || 0;
|
|
4697
|
+
const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
|
|
4698
|
+
return Math.max(rectH, offH, estH);
|
|
4699
|
+
});
|
|
4700
|
+
wrapper.innerHTML = "";
|
|
4701
|
+
const createRtfCard = () => {
|
|
4702
|
+
const card = document.createElement("div");
|
|
4703
|
+
card.className = "fp-rtf-page-card";
|
|
4704
|
+
card.style.backgroundColor = "#ffffff";
|
|
4705
|
+
card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
|
|
4706
|
+
card.style.borderRadius = "4px";
|
|
4707
|
+
card.style.padding = "72px 56px";
|
|
4708
|
+
card.style.width = "816px";
|
|
4709
|
+
card.style.minHeight = "1056px";
|
|
4710
|
+
card.style.boxSizing = "border-box";
|
|
4711
|
+
card.style.marginBottom = "24px";
|
|
4712
|
+
card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
|
|
4713
|
+
card.style.lineHeight = "1.6";
|
|
4714
|
+
return card;
|
|
4715
|
+
};
|
|
4716
|
+
let curCard = createRtfCard();
|
|
4717
|
+
wrapper.appendChild(curCard);
|
|
4718
|
+
pageElements = [curCard];
|
|
4719
|
+
let curH = 0;
|
|
4720
|
+
const maxH = 912;
|
|
4721
|
+
for (let i = 0; i < contentNodes.length; i++) {
|
|
4722
|
+
const child = contentNodes[i];
|
|
4723
|
+
const chH = childHeights[i];
|
|
4724
|
+
curCard.appendChild(child);
|
|
4725
|
+
curH += chH;
|
|
4726
|
+
if (curH >= maxH && i < contentNodes.length - 1) {
|
|
4727
|
+
curCard = createRtfCard();
|
|
4728
|
+
wrapper.appendChild(curCard);
|
|
4729
|
+
pageElements.push(curCard);
|
|
4730
|
+
curH = 0;
|
|
4405
4731
|
}
|
|
4406
4732
|
}
|
|
4407
4733
|
} catch (err) {
|
|
@@ -4770,6 +5096,14 @@ var OpenDocumentPlugin = class {
|
|
|
4770
5096
|
type: "button",
|
|
4771
5097
|
group: "actions",
|
|
4772
5098
|
execute: () => instance.print?.()
|
|
5099
|
+
},
|
|
5100
|
+
{
|
|
5101
|
+
id: "open-window",
|
|
5102
|
+
icon: "open-window",
|
|
5103
|
+
label: "Open in Separate Full Window",
|
|
5104
|
+
type: "button",
|
|
5105
|
+
group: "actions",
|
|
5106
|
+
execute: () => instance.openInSeparateWindow?.()
|
|
4773
5107
|
}
|
|
4774
5108
|
);
|
|
4775
5109
|
return actions;
|
|
@@ -5358,6 +5692,14 @@ var DocPlugin = class {
|
|
|
5358
5692
|
type: "button",
|
|
5359
5693
|
group: "actions",
|
|
5360
5694
|
execute: () => instance.print?.()
|
|
5695
|
+
},
|
|
5696
|
+
{
|
|
5697
|
+
id: "open-window",
|
|
5698
|
+
icon: "open-window",
|
|
5699
|
+
label: "Open in Separate Full Window",
|
|
5700
|
+
type: "button",
|
|
5701
|
+
group: "actions",
|
|
5702
|
+
execute: () => instance.openInSeparateWindow?.()
|
|
5361
5703
|
}
|
|
5362
5704
|
);
|
|
5363
5705
|
return actions;
|
|
@@ -5377,8 +5719,17 @@ var DocPlugin = class {
|
|
|
5377
5719
|
let scale = 1;
|
|
5378
5720
|
let extractedRawText = "";
|
|
5379
5721
|
let isFallback = false;
|
|
5722
|
+
let chartSvg = "";
|
|
5380
5723
|
try {
|
|
5381
5724
|
const cfbf = new CfbfReader(ctx.buffer);
|
|
5725
|
+
try {
|
|
5726
|
+
const pkg = cfbf.readStream("package_stream");
|
|
5727
|
+
if (pkg && pkg.length > 100) {
|
|
5728
|
+
chartSvg = this.parseOdfChartToSvg(pkg);
|
|
5729
|
+
}
|
|
5730
|
+
} catch (chartErr) {
|
|
5731
|
+
console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
|
|
5732
|
+
}
|
|
5382
5733
|
const wordDocStream = cfbf.readStream("WordDocument");
|
|
5383
5734
|
if (!wordDocStream || wordDocStream.length < 512) {
|
|
5384
5735
|
throw new Error("WordDocument stream not found or invalid in CFBF archive");
|
|
@@ -5396,7 +5747,7 @@ var DocPlugin = class {
|
|
|
5396
5747
|
extractedRawText = fallback;
|
|
5397
5748
|
isFallback = true;
|
|
5398
5749
|
}
|
|
5399
|
-
const rawPages = this.splitIntoPages(extractedRawText);
|
|
5750
|
+
const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
|
|
5400
5751
|
const totalPages = Math.max(1, rawPages.length);
|
|
5401
5752
|
let currentPage = 1;
|
|
5402
5753
|
const pageCards = [];
|
|
@@ -5638,7 +5989,7 @@ var DocPlugin = class {
|
|
|
5638
5989
|
for (const run of [...ansiRuns, ...utf16Runs]) {
|
|
5639
5990
|
const trimmed = run.trim();
|
|
5640
5991
|
if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
|
|
5641
|
-
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)) {
|
|
5992
|
+
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)) {
|
|
5642
5993
|
seen.add(trimmed);
|
|
5643
5994
|
candidateLines.push(trimmed);
|
|
5644
5995
|
}
|
|
@@ -5649,12 +6000,138 @@ var DocPlugin = class {
|
|
|
5649
6000
|
heuristicTextExtraction(buffer) {
|
|
5650
6001
|
return this.extractStringsFromBytes(new Uint8Array(buffer));
|
|
5651
6002
|
}
|
|
5652
|
-
|
|
6003
|
+
/**
|
|
6004
|
+
* Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
|
|
6005
|
+
*/
|
|
6006
|
+
parseOdfChartToSvg(zipBytes) {
|
|
6007
|
+
try {
|
|
6008
|
+
const unzipped = fflate.unzipSync(zipBytes);
|
|
6009
|
+
const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
|
|
6010
|
+
if (!contentXml) return "";
|
|
6011
|
+
const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
|
|
6012
|
+
if (rowsMatch.length < 2) return "";
|
|
6013
|
+
const headers = [];
|
|
6014
|
+
const firstRow = rowsMatch[0];
|
|
6015
|
+
const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
|
|
6016
|
+
for (const h of headerCells) {
|
|
6017
|
+
headers.push(h.replace(/<\/?text:p>/g, "").trim());
|
|
6018
|
+
}
|
|
6019
|
+
const categories = [];
|
|
6020
|
+
const seriesValues = headers.map(() => []);
|
|
6021
|
+
for (let r = 1; r < rowsMatch.length; r++) {
|
|
6022
|
+
const rowStr = rowsMatch[r];
|
|
6023
|
+
if (!rowStr) continue;
|
|
6024
|
+
const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
|
|
6025
|
+
if (cells.length > 0 && cells[0]) {
|
|
6026
|
+
const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
|
|
6027
|
+
categories.push(catMatch ? catMatch[1] : "Row " + r);
|
|
6028
|
+
for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
|
|
6029
|
+
const cellStr = cells[c];
|
|
6030
|
+
if (!cellStr) continue;
|
|
6031
|
+
const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
|
|
6032
|
+
const series = seriesValues[c - 1];
|
|
6033
|
+
if (series) {
|
|
6034
|
+
series.push(valMatch ? parseFloat(valMatch[1]) : 0);
|
|
6035
|
+
}
|
|
6036
|
+
}
|
|
6037
|
+
}
|
|
6038
|
+
}
|
|
6039
|
+
const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
|
|
6040
|
+
const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
|
|
6041
|
+
let cIdx = 0;
|
|
6042
|
+
for (const cm of colorMatches) {
|
|
6043
|
+
if (cIdx < colors.length) colors[cIdx] = cm[1];
|
|
6044
|
+
cIdx++;
|
|
6045
|
+
}
|
|
6046
|
+
let maxVal = 10;
|
|
6047
|
+
for (const s of seriesValues) {
|
|
6048
|
+
for (const v of s) {
|
|
6049
|
+
if (v > maxVal) maxVal = v;
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
maxVal = Math.ceil(maxVal * 1.15);
|
|
6053
|
+
const width = 560;
|
|
6054
|
+
const height = 280;
|
|
6055
|
+
const padLeft = 45;
|
|
6056
|
+
const padRight = 100;
|
|
6057
|
+
const padTop = 20;
|
|
6058
|
+
const padBottom = 40;
|
|
6059
|
+
const chartW = width - padLeft - padRight;
|
|
6060
|
+
const chartH = height - padTop - padBottom;
|
|
6061
|
+
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);">`;
|
|
6062
|
+
for (let step = 0; step <= 4; step++) {
|
|
6063
|
+
const yVal = (maxVal / 4 * step).toFixed(1);
|
|
6064
|
+
const yPos = padTop + chartH - step / 4 * chartH;
|
|
6065
|
+
svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
|
|
6066
|
+
svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
|
|
6067
|
+
}
|
|
6068
|
+
const numCats = categories.length;
|
|
6069
|
+
const numSeries = headers.length;
|
|
6070
|
+
const groupW = chartW / numCats;
|
|
6071
|
+
const barW = Math.max(8, groupW * 0.7 / numSeries);
|
|
6072
|
+
const groupPad = (groupW - barW * numSeries) / 2;
|
|
6073
|
+
for (let catIdx = 0; catIdx < numCats; catIdx++) {
|
|
6074
|
+
const groupX = padLeft + catIdx * groupW + groupPad;
|
|
6075
|
+
for (let sIdx = 0; sIdx < numSeries; sIdx++) {
|
|
6076
|
+
const val = seriesValues[sIdx][catIdx] || 0;
|
|
6077
|
+
const barH = val / maxVal * chartH;
|
|
6078
|
+
const barX = groupX + sIdx * barW;
|
|
6079
|
+
const barY = padTop + chartH - barH;
|
|
6080
|
+
const col = colors[sIdx % colors.length];
|
|
6081
|
+
svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
|
|
6082
|
+
}
|
|
6083
|
+
const catX = padLeft + catIdx * groupW + groupW / 2;
|
|
6084
|
+
svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
|
|
6085
|
+
}
|
|
6086
|
+
let legendY = padTop + 20;
|
|
6087
|
+
for (let sIdx = 0; sIdx < numSeries; sIdx++) {
|
|
6088
|
+
const col = colors[sIdx % colors.length];
|
|
6089
|
+
svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
|
|
6090
|
+
svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
|
|
6091
|
+
legendY += 20;
|
|
6092
|
+
}
|
|
6093
|
+
svg += "</svg>";
|
|
6094
|
+
return svg;
|
|
6095
|
+
} catch (e) {
|
|
6096
|
+
console.warn("[DocPlugin] Error generating chart SVG:", e);
|
|
6097
|
+
return "";
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
cleanWordDocFields(text, chartSvg = "") {
|
|
6101
|
+
if (!text) return "";
|
|
6102
|
+
let cleaned = text.replace(
|
|
6103
|
+
/\x13\s*EMBED\b[\s\S]*?\x15/gi,
|
|
6104
|
+
() => chartSvg ? `
|
|
6105
|
+
|
|
6106
|
+
${chartSvg}
|
|
6107
|
+
|
|
6108
|
+
` : ""
|
|
6109
|
+
);
|
|
6110
|
+
cleaned = cleaned.replace(
|
|
6111
|
+
/\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
|
|
6112
|
+
(_match, url, label) => {
|
|
6113
|
+
const cleanUrl = url.trim();
|
|
6114
|
+
const cleanLabel = label.trim() || cleanUrl;
|
|
6115
|
+
return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
|
|
6116
|
+
}
|
|
6117
|
+
);
|
|
6118
|
+
cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
|
|
6119
|
+
if (/[\x00-\x1F]/.test(res)) return "";
|
|
6120
|
+
return res.trim();
|
|
6121
|
+
});
|
|
6122
|
+
cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
|
|
6123
|
+
cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
|
|
6124
|
+
cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
|
|
6125
|
+
cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
|
|
6126
|
+
return cleaned;
|
|
6127
|
+
}
|
|
6128
|
+
splitIntoPages(text, chartSvg = "") {
|
|
5653
6129
|
if (!text) return [""];
|
|
5654
|
-
const
|
|
6130
|
+
const cleanedText = this.cleanWordDocFields(text, chartSvg);
|
|
6131
|
+
const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
|
|
5655
6132
|
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);
|
|
5656
6133
|
if (explicitParts.length === 0) explicitParts.push(normalized);
|
|
5657
|
-
const maxLinesPerPage =
|
|
6134
|
+
const maxLinesPerPage = 34;
|
|
5658
6135
|
const charsPerLine = 80;
|
|
5659
6136
|
const finalPages = [];
|
|
5660
6137
|
for (const part of explicitParts) {
|
|
@@ -5662,7 +6139,8 @@ var DocPlugin = class {
|
|
|
5662
6139
|
let currentLines = [];
|
|
5663
6140
|
let count = 0;
|
|
5664
6141
|
for (const line of lines) {
|
|
5665
|
-
const
|
|
6142
|
+
const isSvg = line.includes("<svg");
|
|
6143
|
+
const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
|
|
5666
6144
|
if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
|
|
5667
6145
|
finalPages.push(currentLines.join("\n"));
|
|
5668
6146
|
currentLines = [];
|
|
@@ -5702,9 +6180,44 @@ var DocPlugin = class {
|
|
|
5702
6180
|
tableLines = [];
|
|
5703
6181
|
}
|
|
5704
6182
|
};
|
|
6183
|
+
const sanitizeOptions = {
|
|
6184
|
+
ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
|
|
6185
|
+
ADD_ATTR: [
|
|
6186
|
+
"href",
|
|
6187
|
+
"target",
|
|
6188
|
+
"rel",
|
|
6189
|
+
"style",
|
|
6190
|
+
"viewBox",
|
|
6191
|
+
"width",
|
|
6192
|
+
"height",
|
|
6193
|
+
"x",
|
|
6194
|
+
"y",
|
|
6195
|
+
"x1",
|
|
6196
|
+
"y1",
|
|
6197
|
+
"x2",
|
|
6198
|
+
"y2",
|
|
6199
|
+
"fill",
|
|
6200
|
+
"stroke",
|
|
6201
|
+
"stroke-width",
|
|
6202
|
+
"stroke-dasharray",
|
|
6203
|
+
"rx",
|
|
6204
|
+
"font-size",
|
|
6205
|
+
"text-anchor"
|
|
6206
|
+
]
|
|
6207
|
+
};
|
|
5705
6208
|
let i = 0;
|
|
5706
6209
|
while (i < lines.length) {
|
|
5707
6210
|
let line = lines[i];
|
|
6211
|
+
if (line.includes("<svg")) {
|
|
6212
|
+
if (inList) {
|
|
6213
|
+
html += "</ul>";
|
|
6214
|
+
inList = false;
|
|
6215
|
+
}
|
|
6216
|
+
flushTable();
|
|
6217
|
+
html += line;
|
|
6218
|
+
i++;
|
|
6219
|
+
continue;
|
|
6220
|
+
}
|
|
5708
6221
|
let tabCount = (line.match(/\t/g) || []).length;
|
|
5709
6222
|
if (tabCount > 0) {
|
|
5710
6223
|
let j = i;
|
|
@@ -5743,20 +6256,20 @@ var DocPlugin = class {
|
|
|
5743
6256
|
html += "</ul>";
|
|
5744
6257
|
inList = false;
|
|
5745
6258
|
}
|
|
5746
|
-
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>`;
|
|
6259
|
+
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>`;
|
|
5747
6260
|
} else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
|
|
5748
6261
|
if (!inList) {
|
|
5749
6262
|
html += '<ul style="margin: 8px 0; padding-left: 24px;">';
|
|
5750
6263
|
inList = true;
|
|
5751
6264
|
}
|
|
5752
6265
|
const bulletText = line.replace(/^[•\-\*]\s*/, "");
|
|
5753
|
-
html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText)}</li>`;
|
|
6266
|
+
html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
|
|
5754
6267
|
} else {
|
|
5755
6268
|
if (inList) {
|
|
5756
6269
|
html += "</ul>";
|
|
5757
6270
|
inList = false;
|
|
5758
6271
|
}
|
|
5759
|
-
html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
|
|
6272
|
+
html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
|
|
5760
6273
|
}
|
|
5761
6274
|
i++;
|
|
5762
6275
|
}
|