@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/index.cjs CHANGED
@@ -14,6 +14,7 @@ var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
14
14
  var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
15
15
  var RTFJS = require('rtf.js/dist/RTFJS.bundle.js');
16
16
 
17
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
17
18
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
18
19
 
19
20
  function _interopNamespace(e) {
@@ -37,6 +38,7 @@ function _interopNamespace(e) {
37
38
  var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
38
39
  var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
39
40
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
41
+ var fflate__namespace = /*#__PURE__*/_interopNamespace(fflate);
40
42
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
41
43
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
42
44
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
@@ -366,16 +368,21 @@ async function sourceToArrayBuffer(source, signal) {
366
368
  metadata.mimeType = source.type || void 0;
367
369
  metadata.extension = extractExtension(source.name);
368
370
  buffer = await source.arrayBuffer();
369
- } else if (source instanceof Blob) {
371
+ } else if (source instanceof Blob || source && typeof source.arrayBuffer === "function" && typeof source.size === "number") {
370
372
  metadata.size = source.size;
371
373
  metadata.mimeType = source.type || void 0;
374
+ if (source.name) {
375
+ metadata.name = source.name;
376
+ metadata.extension = extractExtension(source.name);
377
+ }
372
378
  buffer = await source.arrayBuffer();
373
- } else if (source instanceof ArrayBuffer) {
379
+ } else if (source instanceof ArrayBuffer || Object.prototype.toString.call(source) === "[object ArrayBuffer]" || source && typeof source.byteLength === "number" && typeof source.slice === "function") {
374
380
  buffer = source;
375
- } else if (source instanceof Uint8Array) {
376
- buffer = source.buffer.slice(
377
- source.byteOffset,
378
- source.byteOffset + source.byteLength
381
+ } else if (source instanceof Uint8Array || ArrayBuffer.isView(source)) {
382
+ const view = source;
383
+ buffer = view.buffer.slice(
384
+ view.byteOffset,
385
+ view.byteOffset + view.byteLength
379
386
  );
380
387
  } else {
381
388
  throw new Error("Unsupported file source type");
@@ -504,6 +511,86 @@ function createElement(tag, attrs, ...children) {
504
511
  }
505
512
  return el;
506
513
  }
514
+ var DB_NAME = "PreviewFileTransferDB";
515
+ var DB_STORE = "transfers";
516
+ function openDB() {
517
+ return new Promise((resolve, reject) => {
518
+ if (typeof indexedDB === "undefined") {
519
+ return reject(new Error("IndexedDB is not available"));
520
+ }
521
+ const req = indexedDB.open(DB_NAME, 1);
522
+ req.onupgradeneeded = () => {
523
+ const db = req.result;
524
+ if (!db.objectStoreNames.contains(DB_STORE)) {
525
+ db.createObjectStore(DB_STORE, { keyPath: "id" });
526
+ }
527
+ };
528
+ req.onsuccess = () => resolve(req.result);
529
+ req.onerror = () => reject(req.error);
530
+ });
531
+ }
532
+ async function saveTransferPayload(id, payload) {
533
+ if (typeof window !== "undefined") {
534
+ try {
535
+ window[id] = payload;
536
+ window.__lastTransfer = payload;
537
+ } catch {
538
+ }
539
+ }
540
+ try {
541
+ const db = await openDB();
542
+ return new Promise((resolve, reject) => {
543
+ const tx = db.transaction(DB_STORE, "readwrite");
544
+ const store = tx.objectStore(DB_STORE);
545
+ store.put({ id, ...payload, timestamp: Date.now() });
546
+ tx.oncomplete = () => resolve();
547
+ tx.onerror = () => reject(tx.error);
548
+ });
549
+ } catch (e) {
550
+ console.warn("[saveTransferPayload] IndexedDB store warning:", e);
551
+ }
552
+ }
553
+ async function getTransferPayload(id) {
554
+ if (typeof window !== "undefined") {
555
+ try {
556
+ if (window.opener && window.opener[id]) {
557
+ return window.opener[id];
558
+ }
559
+ if (window[id]) {
560
+ return window[id];
561
+ }
562
+ if (window.opener && window.opener.__lastTransfer) {
563
+ return window.opener.__lastTransfer;
564
+ }
565
+ if (window.__lastTransfer) {
566
+ return window.__lastTransfer;
567
+ }
568
+ } catch {
569
+ }
570
+ }
571
+ try {
572
+ const db = await openDB();
573
+ return new Promise((resolve) => {
574
+ const tx = db.transaction(DB_STORE, "readonly");
575
+ const store = tx.objectStore(DB_STORE);
576
+ const req = store.get(id);
577
+ req.onsuccess = () => {
578
+ if (req.result && req.result.buffer) {
579
+ resolve({
580
+ buffer: req.result.buffer,
581
+ metadata: req.result.metadata,
582
+ options: req.result.options
583
+ });
584
+ } else {
585
+ resolve(null);
586
+ }
587
+ };
588
+ req.onerror = () => resolve(null);
589
+ });
590
+ } catch {
591
+ return null;
592
+ }
593
+ }
507
594
  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>`;
508
595
  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>`;
509
596
  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>`;
@@ -523,6 +610,7 @@ var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" str
523
610
  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>`;
524
611
  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>`;
525
612
  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>`;
613
+ 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>`;
526
614
  var ICON_MAP = {
527
615
  "zoom-in": ICON_ZOOM_IN,
528
616
  "zoom-out": ICON_ZOOM_OUT,
@@ -549,7 +637,9 @@ var ICON_MAP = {
549
637
  "forward-10": ICON_FAST_FORWARD,
550
638
  "rewind": ICON_REWIND,
551
639
  "replay-10": ICON_REWIND,
552
- "speed": ICON_SPEED
640
+ "speed": ICON_SPEED,
641
+ "open-window": ICON_EXTERNAL_WINDOW,
642
+ "external-window": ICON_EXTERNAL_WINDOW
553
643
  };
554
644
  var ToolbarController = class {
555
645
  el;
@@ -692,9 +782,11 @@ var ToolbarController = class {
692
782
  type: "button",
693
783
  "data-action-id": id
694
784
  });
695
- const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
696
- if (svg) {
697
- btn.innerHTML = sanitizeSVG(svg);
785
+ const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
786
+ if (internalSvg) {
787
+ btn.innerHTML = internalSvg;
788
+ } else if (iconHtml && iconHtml.startsWith("<svg")) {
789
+ btn.innerHTML = sanitizeSVG(iconHtml);
698
790
  } else {
699
791
  btn.textContent = title || id;
700
792
  }
@@ -766,7 +858,7 @@ var ThumbnailPanel = class {
766
858
  }
767
859
  }
768
860
  };
769
- var FilePreviewViewer = class {
861
+ var FilePreviewViewer = class _FilePreviewViewer {
770
862
  plugins = [];
771
863
  activeInstance = null;
772
864
  abortController = null;
@@ -803,6 +895,7 @@ var FilePreviewViewer = class {
803
895
  * Preview a file in the given container element.
804
896
  */
805
897
  async preview(container, source, options = {}) {
898
+ this.currentOptions = options;
806
899
  this.abort();
807
900
  this.abortController = new AbortController();
808
901
  const { signal } = this.abortController;
@@ -812,7 +905,10 @@ var FilePreviewViewer = class {
812
905
  this.showLoading();
813
906
  try {
814
907
  const { buffer, metadata } = await sourceToArrayBuffer(source, signal);
815
- this.currentBuffer = buffer;
908
+ if (options.metadata) {
909
+ Object.assign(metadata, options.metadata);
910
+ }
911
+ this.currentBuffer = buffer.slice(0);
816
912
  this.currentMetadata = metadata;
817
913
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
818
914
  const fileInfo = { metadata, buffer };
@@ -842,6 +938,7 @@ var FilePreviewViewer = class {
842
938
  }
843
939
  });
844
940
  this.activeInstance = instance;
941
+ instance.openInSeparateWindow = () => this.openInSeparateWindow();
845
942
  this.hideLoading();
846
943
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
847
944
  if (options.showToolbar !== false && this.toolbar) {
@@ -851,26 +948,16 @@ var FilePreviewViewer = class {
851
948
  actions.push({
852
949
  id: "fullscreen",
853
950
  icon: "fullscreen",
854
- label: "Toggle Fullscreen",
951
+ label: "Fullscreen",
855
952
  type: "button",
856
953
  group: "view",
857
- execute: async () => {
954
+ execute: () => {
858
955
  try {
859
- const isNativeFs = !!document.fullscreenElement;
860
- const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
861
- if (!isNativeFs && !isCssFs) {
862
- if (this.wrapperEl?.requestFullscreen) {
863
- await this.wrapperEl.requestFullscreen().catch(() => {
864
- this.wrapperEl?.classList.add("fp-fullscreen-active");
865
- });
866
- } else {
867
- this.wrapperEl?.classList.add("fp-fullscreen-active");
868
- }
956
+ if (!document.fullscreenElement) {
957
+ this.wrapperEl?.requestFullscreen?.();
958
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
869
959
  } else {
870
- if (document.fullscreenElement) {
871
- await document.exitFullscreen().catch(() => {
872
- });
873
- }
960
+ document.exitFullscreen?.();
874
961
  this.wrapperEl?.classList.remove("fp-fullscreen-active");
875
962
  }
876
963
  } catch {
@@ -882,6 +969,28 @@ var FilePreviewViewer = class {
882
969
  }
883
970
  });
884
971
  }
972
+ const openWinAction = actions.find((a) => a.id === "open-window");
973
+ if (openWinAction) {
974
+ if (options?._isSeparateWindow) {
975
+ const idx = actions.indexOf(openWinAction);
976
+ if (idx !== -1) actions.splice(idx, 1);
977
+ } else {
978
+ openWinAction.execute = () => {
979
+ this.openInSeparateWindow();
980
+ };
981
+ }
982
+ } else if (!options?._isSeparateWindow) {
983
+ actions.push({
984
+ id: "open-window",
985
+ icon: "open-window",
986
+ label: "Open in Separate Full Window",
987
+ type: "button",
988
+ group: "actions",
989
+ execute: () => {
990
+ this.openInSeparateWindow();
991
+ }
992
+ });
993
+ }
885
994
  this.toolbar.update(actions);
886
995
  this.toolbar.show();
887
996
  }
@@ -914,6 +1023,104 @@ var FilePreviewViewer = class {
914
1023
  throw error;
915
1024
  }
916
1025
  }
1026
+ /**
1027
+ * Opens the current file preview in a separate full browser window.
1028
+ */
1029
+ openInSeparateWindow() {
1030
+ if (!this.currentBuffer) {
1031
+ console.warn("[FilePreviewViewer] No active file buffer to open in separate window");
1032
+ return null;
1033
+ }
1034
+ if (this.currentOptions.onOpenSeparateWindow) {
1035
+ return this.currentOptions.onOpenSeparateWindow({
1036
+ buffer: this.currentBuffer,
1037
+ metadata: this.currentMetadata || { name: "Document" },
1038
+ options: this.currentOptions
1039
+ });
1040
+ }
1041
+ const transferId = "fp_win_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
1042
+ let clonedBuffer;
1043
+ try {
1044
+ clonedBuffer = this.currentBuffer.slice(0);
1045
+ } catch {
1046
+ clonedBuffer = this.currentBuffer;
1047
+ }
1048
+ const payload = {
1049
+ buffer: clonedBuffer,
1050
+ metadata: this.currentMetadata ? { ...this.currentMetadata } : void 0,
1051
+ options: { ...this.currentOptions, _isSeparateWindow: true }
1052
+ };
1053
+ if (typeof window !== "undefined") {
1054
+ try {
1055
+ window[transferId] = payload;
1056
+ window.__lastTransfer = payload;
1057
+ } catch {
1058
+ }
1059
+ }
1060
+ saveTransferPayload(transferId, payload).catch((err) => {
1061
+ console.warn("[FilePreviewViewer] Transfer payload save warning:", err);
1062
+ });
1063
+ let targetUrl = null;
1064
+ if (this.currentOptions.standaloneViewerUrl) {
1065
+ const u = new URL(this.currentOptions.standaloneViewerUrl, window.location.href);
1066
+ u.searchParams.set("mode", "fullscreen");
1067
+ u.searchParams.set("transferId", transferId);
1068
+ targetUrl = u.toString();
1069
+ } else if (typeof window !== "undefined" && window.location?.href && !window.location.href.startsWith("about:")) {
1070
+ const u = new URL(window.location.href);
1071
+ u.searchParams.set("mode", "fullscreen");
1072
+ u.searchParams.set("transferId", transferId);
1073
+ targetUrl = u.toString();
1074
+ }
1075
+ if (targetUrl) {
1076
+ const newWin2 = window.open(targetUrl, "_blank");
1077
+ if (!newWin2) {
1078
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1079
+ return null;
1080
+ }
1081
+ return newWin2;
1082
+ }
1083
+ const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
1084
+ const newWin = window.open("", "_blank");
1085
+ if (!newWin) {
1086
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1087
+ return null;
1088
+ }
1089
+ newWin.document.title = title;
1090
+ newWin.document.body.style.margin = "0";
1091
+ newWin.document.body.style.padding = "0";
1092
+ newWin.document.body.style.width = "100vw";
1093
+ newWin.document.body.style.height = "100vh";
1094
+ newWin.document.body.style.overflow = "hidden";
1095
+ newWin.document.body.style.backgroundColor = "#f8fafc";
1096
+ const headNodes = document.querySelectorAll('link[rel="stylesheet"], style');
1097
+ headNodes.forEach((node) => {
1098
+ newWin.document.head.appendChild(node.cloneNode(true));
1099
+ });
1100
+ const root = newWin.document.createElement("div");
1101
+ root.id = "full-window-preview-root";
1102
+ root.style.width = "100%";
1103
+ root.style.height = "100%";
1104
+ root.style.overflow = "hidden";
1105
+ newWin.document.body.appendChild(root);
1106
+ const separateViewer = new _FilePreviewViewer();
1107
+ for (const plugin of this.plugins) {
1108
+ separateViewer.registerPlugin(plugin);
1109
+ }
1110
+ separateViewer.preview(root, this.currentBuffer.slice(0), {
1111
+ ...this.currentOptions,
1112
+ showToolbar: true,
1113
+ toolbarPosition: "top",
1114
+ metadata: this.currentMetadata || void 0,
1115
+ _isSeparateWindow: true
1116
+ }).catch((err) => {
1117
+ console.error("[FilePreviewViewer] Error rendering in separate window:", err);
1118
+ });
1119
+ newWin.addEventListener("beforeunload", () => {
1120
+ separateViewer.destroy();
1121
+ });
1122
+ return newWin;
1123
+ }
917
1124
  /**
918
1125
  * Subscribe to viewer events.
919
1126
  */
@@ -1319,8 +1526,20 @@ var CfbfReader = class {
1319
1526
  }
1320
1527
  };
1321
1528
  if (typeof window !== "undefined" && pdfjsLib__namespace.GlobalWorkerOptions) {
1322
- if (!pdfjsLib__namespace.GlobalWorkerOptions.workerSrc) {
1323
- pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1529
+ if (!pdfjsLib__namespace.GlobalWorkerOptions.workerPort && !pdfjsLib__namespace.GlobalWorkerOptions.workerSrc) {
1530
+ const customWorker = window.__PDF_WORKER_SRC__;
1531
+ if (customWorker) {
1532
+ pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = customWorker;
1533
+ } else {
1534
+ try {
1535
+ pdfjsLib__namespace.GlobalWorkerOptions.workerPort = new Worker(
1536
+ new URL("pdfjs-dist/build/pdf.worker.min.mjs", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))),
1537
+ { type: "module" }
1538
+ );
1539
+ } catch {
1540
+ pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.mjs";
1541
+ }
1542
+ }
1324
1543
  }
1325
1544
  }
1326
1545
  var PdfPlugin = class {
@@ -1414,6 +1633,14 @@ var PdfPlugin = class {
1414
1633
  type: "button",
1415
1634
  group: "actions",
1416
1635
  execute: () => instance.print?.()
1636
+ },
1637
+ {
1638
+ id: "open-window",
1639
+ icon: "open-window",
1640
+ label: "Open in Separate Full Window",
1641
+ type: "button",
1642
+ group: "actions",
1643
+ execute: () => instance.openInSeparateWindow?.()
1417
1644
  }
1418
1645
  ];
1419
1646
  }
@@ -1463,18 +1690,21 @@ var PdfPlugin = class {
1463
1690
  indicator.style.pointerEvents = "none";
1464
1691
  container.appendChild(indicator);
1465
1692
  ctx.container.appendChild(container);
1693
+ const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1694
+ const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
1466
1695
  const loadingTask = pdfjsLib__namespace.getDocument({
1467
- data: new Uint8Array(ctx.buffer),
1468
- cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/cmaps/`,
1696
+ data: new Uint8Array(ctx.buffer.slice(0)),
1697
+ cMapUrl: cmapsUrl,
1469
1698
  cMapPacked: true,
1470
- standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/standard_fonts/`
1699
+ standardFontDataUrl: standardFontsUrl,
1700
+ verbosity: 0
1471
1701
  });
1472
1702
  const pdfDoc = await loadingTask.promise;
1473
1703
  const totalPages = Math.max(1, pdfDoc.numPages);
1474
1704
  let currentPage = 1;
1475
1705
  let zoomScale = 1;
1476
1706
  let rotation = 0;
1477
- let fitMode = "width";
1707
+ let fitMode = "page";
1478
1708
  let currentRenderTask = null;
1479
1709
  const renderPage = async (pageNum) => {
1480
1710
  if (currentRenderTask) {
@@ -1494,15 +1724,15 @@ var PdfPlugin = class {
1494
1724
  const containerWidth = container.clientWidth || 900;
1495
1725
  const containerHeight = container.clientHeight || 700;
1496
1726
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1497
- const availWidth = Math.max(280, containerWidth - 48);
1498
- const availHeight = Math.max(280, containerHeight - 88);
1727
+ const availWidth = Math.max(320, containerWidth - 48);
1728
+ const availHeight = Math.max(550, containerHeight - 88);
1499
1729
  const scaleW = availWidth / unscaledVp.width;
1500
1730
  const scaleH = availHeight / unscaledVp.height;
1501
1731
  let fitScale;
1502
1732
  if (fitMode === "page") {
1503
- fitScale = Math.max(0.5, Math.min(scaleW, scaleH));
1733
+ fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
1504
1734
  } else {
1505
- fitScale = Math.max(0.65, Math.min(1.15, scaleW));
1735
+ fitScale = Math.max(0.65, Math.min(1.25, scaleW));
1506
1736
  }
1507
1737
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1508
1738
  const pixelRatio = window.devicePixelRatio || 1;
@@ -1528,7 +1758,7 @@ var PdfPlugin = class {
1528
1758
  currentRenderTask = null;
1529
1759
  }
1530
1760
  };
1531
- await renderPage(1);
1761
+ renderPage(1);
1532
1762
  let resizeTimer = null;
1533
1763
  const resizeObserver = new ResizeObserver(() => {
1534
1764
  if (resizeTimer) clearTimeout(resizeTimer);
@@ -1572,7 +1802,7 @@ var PdfPlugin = class {
1572
1802
  renderPage(currentPage);
1573
1803
  },
1574
1804
  fitToPage: () => {
1575
- fitMode = fitMode === "width" ? "page" : "width";
1805
+ fitMode = fitMode === "page" ? "width" : "page";
1576
1806
  zoomScale = 1;
1577
1807
  rotation = 0;
1578
1808
  renderPage(currentPage);
@@ -2049,6 +2279,14 @@ var DocxPlugin = class {
2049
2279
  type: "button",
2050
2280
  group: "actions",
2051
2281
  execute: () => instance.print?.()
2282
+ },
2283
+ {
2284
+ id: "open-window",
2285
+ icon: "open-window",
2286
+ label: "Open in Separate Full Window",
2287
+ type: "button",
2288
+ group: "actions",
2289
+ execute: () => instance.openInSeparateWindow?.()
2052
2290
  }
2053
2291
  );
2054
2292
  return actions;
@@ -2108,6 +2346,31 @@ var DocxPlugin = class {
2108
2346
  }
2109
2347
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2110
2348
  renderedSuccessfully = true;
2349
+ try {
2350
+ const unzipped = fflate.unzipSync(new Uint8Array(ctx.buffer));
2351
+ const chartKeys = Object.keys(unzipped).filter((k) => k.replace(/^[./\\]+/, "").toLowerCase().startsWith("word/charts/chart") && k.endsWith(".xml")).sort();
2352
+ if (chartKeys.length > 0) {
2353
+ const allDivs = Array.from(wrapper.querySelectorAll("div"));
2354
+ const emptyContainers = allDivs.filter((div) => {
2355
+ const st = div.getAttribute("style") || "";
2356
+ return st.includes("width:") && st.includes("height:") && div.children.length === 0 && (div.textContent?.trim().length ?? 0) === 0;
2357
+ });
2358
+ chartKeys.forEach((cKey, idx) => {
2359
+ const target = emptyContainers[idx];
2360
+ if (target) {
2361
+ const xmlStr = fflate.strFromU8(unzipped[cKey]);
2362
+ const svg = this.parseAndRenderChartSvg(xmlStr);
2363
+ if (svg) {
2364
+ target.innerHTML = svg;
2365
+ target.style.display = "block";
2366
+ target.style.margin = "12px auto";
2367
+ }
2368
+ }
2369
+ });
2370
+ }
2371
+ } catch (chartErr) {
2372
+ console.warn("[DocxPlugin] Non-critical error rendering DrawingML charts:", chartErr);
2373
+ }
2111
2374
  }
2112
2375
  } catch (err) {
2113
2376
  console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
@@ -2139,64 +2402,74 @@ var DocxPlugin = class {
2139
2402
  }
2140
2403
  let sections = Array.from(wrapper.querySelectorAll("section.docx"));
2141
2404
  const cards = Array.from(wrapper.querySelectorAll(".fp-docx-page-card"));
2142
- if (sections.length === 1 && cards.length === 0) {
2143
- const singleSec = sections[0];
2144
- const contentContainer = singleSec.querySelector("article") || singleSec;
2145
- const children = Array.from(contentContainer.children);
2146
- const pageH = singleSec.offsetHeight > 1300 ? 1122 : Math.max(1056, singleSec.offsetHeight);
2147
- const secH = singleSec.offsetHeight || singleSec.scrollHeight;
2148
- if (secH > pageH * 1.25 && children.length > 1) {
2149
- const childHeights = children.map((c) => {
2150
- const rectH = c.getBoundingClientRect().height;
2151
- const offH = c.offsetHeight;
2152
- const textLen = c.textContent?.trim().length || 0;
2153
- const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
2154
- return Math.max(rectH, offH, estH);
2155
- });
2156
- const parent = singleSec.parentElement || wrapper;
2157
- const newSections = [singleSec];
2158
- const headerEl = singleSec.querySelector("header");
2159
- const footerEl = singleSec.querySelector("footer");
2160
- contentContainer.innerHTML = "";
2161
- singleSec.style.minHeight = `${pageH}px`;
2162
- singleSec.style.boxSizing = "border-box";
2163
- let curContent = contentContainer;
2164
- let curH = 0;
2165
- const maxH = pageH - 140;
2166
- for (let i = 0; i < children.length; i++) {
2167
- const child = children[i];
2168
- const chH = childHeights[i];
2169
- curContent.appendChild(child);
2170
- curH += chH;
2171
- if (curH >= maxH && i < children.length - 1) {
2172
- const nextSec = document.createElement("section");
2173
- nextSec.className = singleSec.className;
2174
- nextSec.style.cssText = singleSec.style.cssText;
2175
- nextSec.style.minHeight = `${pageH}px`;
2176
- nextSec.style.boxSizing = "border-box";
2177
- nextSec.style.backgroundColor = "#ffffff";
2178
- nextSec.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
2179
- nextSec.style.borderRadius = "4px";
2180
- nextSec.style.marginBottom = "24px";
2181
- if (headerEl) {
2182
- nextSec.appendChild(headerEl.cloneNode(true));
2183
- }
2184
- const nextArticle = document.createElement("article");
2185
- if (contentContainer.tagName.toLowerCase() === "article") {
2186
- nextArticle.style.cssText = contentContainer.style.cssText;
2187
- }
2188
- nextSec.appendChild(nextArticle);
2189
- if (footerEl) {
2190
- nextSec.appendChild(footerEl.cloneNode(true));
2405
+ if (sections.length > 0 && cards.length === 0) {
2406
+ const finalSections = [];
2407
+ for (const singleSec of sections) {
2408
+ const contentContainer = singleSec.querySelector("article") || singleSec;
2409
+ const children = Array.from(contentContainer.children);
2410
+ const pageH = singleSec.offsetHeight > 1300 ? 1122 : Math.max(1056, singleSec.offsetHeight);
2411
+ const secH = singleSec.scrollHeight || singleSec.offsetHeight;
2412
+ if (secH > pageH * 1.25 && children.length > 1) {
2413
+ const childHeights = children.map((c) => {
2414
+ const rectH = c.getBoundingClientRect().height;
2415
+ const offH = c.offsetHeight;
2416
+ const textLen = c.textContent?.trim().length || 0;
2417
+ const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
2418
+ return Math.max(rectH, offH, estH);
2419
+ });
2420
+ const parent = singleSec.parentElement || wrapper;
2421
+ const headerEl = singleSec.querySelector("header");
2422
+ const footerEl = singleSec.querySelector("footer");
2423
+ contentContainer.innerHTML = "";
2424
+ singleSec.style.minHeight = `${pageH}px`;
2425
+ singleSec.style.boxSizing = "border-box";
2426
+ let curContent = contentContainer;
2427
+ let curSec = singleSec;
2428
+ let curH = 0;
2429
+ const maxH = pageH - 140;
2430
+ finalSections.push(singleSec);
2431
+ for (let i = 0; i < children.length; i++) {
2432
+ const child = children[i];
2433
+ const chH = childHeights[i];
2434
+ curContent.appendChild(child);
2435
+ curH += chH;
2436
+ if (curH >= maxH && i < children.length - 1) {
2437
+ const nextSec = document.createElement("section");
2438
+ nextSec.className = singleSec.className;
2439
+ nextSec.style.cssText = singleSec.style.cssText;
2440
+ nextSec.style.minHeight = `${pageH}px`;
2441
+ nextSec.style.boxSizing = "border-box";
2442
+ nextSec.style.backgroundColor = "#ffffff";
2443
+ nextSec.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
2444
+ nextSec.style.borderRadius = "4px";
2445
+ nextSec.style.marginBottom = "24px";
2446
+ if (headerEl) {
2447
+ nextSec.appendChild(headerEl.cloneNode(true));
2448
+ }
2449
+ const nextArticle = document.createElement("article");
2450
+ if (contentContainer.tagName.toLowerCase() === "article") {
2451
+ nextArticle.style.cssText = contentContainer.style.cssText;
2452
+ }
2453
+ nextSec.appendChild(nextArticle);
2454
+ if (footerEl) {
2455
+ nextSec.appendChild(footerEl.cloneNode(true));
2456
+ }
2457
+ if (curSec.nextSibling) {
2458
+ parent.insertBefore(nextSec, curSec.nextSibling);
2459
+ } else {
2460
+ parent.appendChild(nextSec);
2461
+ }
2462
+ finalSections.push(nextSec);
2463
+ curSec = nextSec;
2464
+ curContent = nextArticle;
2465
+ curH = 0;
2191
2466
  }
2192
- parent.appendChild(nextSec);
2193
- newSections.push(nextSec);
2194
- curContent = nextArticle;
2195
- curH = 0;
2196
2467
  }
2468
+ } else {
2469
+ finalSections.push(singleSec);
2197
2470
  }
2198
- sections = newSections;
2199
2471
  }
2472
+ sections = finalSections;
2200
2473
  }
2201
2474
  const pageElements = sections.length > 0 ? sections : cards;
2202
2475
  const totalPages = Math.max(1, pageElements.length);
@@ -2588,6 +2861,88 @@ var DocxPlugin = class {
2588
2861
  }
2589
2862
  return result;
2590
2863
  }
2864
+ parseAndRenderChartSvg(xmlStr, width = 500, height = 260) {
2865
+ const catMatches = [...xmlStr.matchAll(/<c:cat>[\s\S]*?<c:strCache>([\s\S]*?)<\/c:strCache>/g)];
2866
+ let categories = [];
2867
+ if (catMatches.length > 0) {
2868
+ categories = [...catMatches[0][1].matchAll(/<c:v>([^<]+)<\/c:v>/g)].map((m) => m[1]);
2869
+ }
2870
+ if (categories.length === 0) {
2871
+ categories = ["Category 1", "Category 2", "Category 3", "Category 4"];
2872
+ }
2873
+ const defaultColors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021", "#83caff"];
2874
+ const sers = [...xmlStr.matchAll(/<c:ser>([\s\S]*?)<\/c:ser>/g)];
2875
+ const series = [];
2876
+ sers.forEach((s, sIdx) => {
2877
+ const titleMatch = s[1].match(/<c:tx>[\s\S]*?<c:v>([^<]+)<\/c:v>/);
2878
+ const title = titleMatch ? titleMatch[1] : `Series ${sIdx + 1}`;
2879
+ const clrMatch = s[1].match(/<a:srgbClr\s+val="([^"]+)"/);
2880
+ const color = clrMatch ? "#" + clrMatch[1] : defaultColors[sIdx % defaultColors.length];
2881
+ const valMatch = s[1].match(/<c:val>[\s\S]*?<c:numCache>([\s\S]*?)<\/c:numCache>/);
2882
+ let values = [];
2883
+ if (valMatch) {
2884
+ 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);
2885
+ }
2886
+ series.push({ title, color, values });
2887
+ });
2888
+ if (series.length === 0) return "";
2889
+ let maxVal = 10;
2890
+ series.forEach((s) => s.values.forEach((v) => {
2891
+ if (v > maxVal) maxVal = v;
2892
+ }));
2893
+ maxVal = Math.ceil(maxVal * 1.15);
2894
+ if (maxVal % 2 !== 0) maxVal++;
2895
+ const padLeft = 45;
2896
+ const padBottom = 55;
2897
+ const padTop = 20;
2898
+ const padRight = 20;
2899
+ const plotW = width - padLeft - padRight;
2900
+ const plotH = height - padTop - padBottom;
2901
+ const yTicks = 5;
2902
+ let gridLines = "";
2903
+ for (let i = 0; i <= yTicks; i++) {
2904
+ const val = maxVal / yTicks * i;
2905
+ const y = padTop + plotH - val / maxVal * plotH;
2906
+ gridLines += `<line x1="${padLeft}" y1="${y}" x2="${padLeft + plotW}" y2="${y}" stroke="#e2e8f0" stroke-width="1" />`;
2907
+ 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>`;
2908
+ }
2909
+ const numCats = categories.length;
2910
+ const numSers = series.length;
2911
+ const groupW = plotW / numCats;
2912
+ const barW = Math.max(8, Math.min(28, groupW * 0.7 / numSers));
2913
+ const groupPad = (groupW - barW * numSers) / 2;
2914
+ let bars = "";
2915
+ let catLabels = "";
2916
+ for (let c = 0; c < numCats; c++) {
2917
+ const catX = padLeft + c * groupW;
2918
+ 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>`;
2919
+ for (let s = 0; s < numSers; s++) {
2920
+ const val = series[s].values[c] ?? 0;
2921
+ const bH = Math.max(0, val / maxVal * plotH);
2922
+ const bX = catX + groupPad + s * barW;
2923
+ const bY = padTop + plotH - bH;
2924
+ bars += `<rect x="${bX}" y="${bY}" width="${barW - 2}" height="${bH}" fill="${series[s].color}" rx="1" />`;
2925
+ }
2926
+ }
2927
+ let legend = "";
2928
+ const legY = height - 12;
2929
+ let legX = padLeft + (plotW - numSers * 100) / 2;
2930
+ series.forEach((s) => {
2931
+ legend += `<rect x="${legX}" y="${legY - 9}" width="10" height="10" fill="${s.color}" rx="2" />`;
2932
+ legend += `<text x="${legX + 15}" y="${legY}" font-size="11" fill="#475569" font-family="Calibri, sans-serif">${s.title}</text>`;
2933
+ legX += 95;
2934
+ });
2935
+ return `
2936
+ <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">
2937
+ ${gridLines}
2938
+ <line x1="${padLeft}" y1="${padTop + plotH}" x2="${padLeft + plotW}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2939
+ <line x1="${padLeft}" y1="${padTop}" x2="${padLeft}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2940
+ ${bars}
2941
+ ${catLabels}
2942
+ ${legend}
2943
+ </svg>
2944
+ `.trim();
2945
+ }
2591
2946
  };
2592
2947
  function docxPlugin() {
2593
2948
  return new DocxPlugin();
@@ -3154,6 +3509,16 @@ var CodePlugin = class {
3154
3509
  execute: () => {
3155
3510
  instance.print?.();
3156
3511
  }
3512
+ },
3513
+ {
3514
+ id: "open-window",
3515
+ icon: "open-window",
3516
+ label: "Open in Separate Full Window",
3517
+ type: "button",
3518
+ group: "actions",
3519
+ execute: () => {
3520
+ instance.openInSeparateWindow?.();
3521
+ }
3157
3522
  }
3158
3523
  );
3159
3524
  return actions;
@@ -4322,6 +4687,14 @@ var RtfPlugin = class {
4322
4687
  type: "button",
4323
4688
  group: "actions",
4324
4689
  execute: () => instance.print?.()
4690
+ },
4691
+ {
4692
+ id: "open-window",
4693
+ icon: "open-window",
4694
+ label: "Open in Separate Full Window",
4695
+ type: "button",
4696
+ group: "actions",
4697
+ execute: () => instance.openInSeparateWindow?.()
4325
4698
  }
4326
4699
  );
4327
4700
  return actions;
@@ -4374,59 +4747,54 @@ var RtfPlugin = class {
4374
4747
  }
4375
4748
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
4376
4749
  const htmlElements = await doc.render();
4377
- if (htmlElements.length === 1) {
4378
- const singleEl = htmlElements[0];
4379
- wrapper.appendChild(singleEl);
4380
- const children = Array.from(singleEl.children);
4381
- const secH = singleEl.offsetHeight || singleEl.scrollHeight;
4382
- if (secH > 1300 && children.length > 1) {
4383
- const childHeights = children.map((c) => {
4384
- const rectH = c.getBoundingClientRect().height;
4385
- const offH = c.offsetHeight;
4386
- const textLen = c.textContent?.trim().length || 0;
4387
- const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
4388
- return Math.max(rectH, offH, estH);
4389
- });
4390
- wrapper.innerHTML = "";
4391
- const createRtfCard = () => {
4392
- const card = document.createElement("div");
4393
- card.className = "fp-rtf-page-card";
4394
- card.style.backgroundColor = "#ffffff";
4395
- card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4396
- card.style.borderRadius = "4px";
4397
- card.style.padding = "72px 56px";
4398
- card.style.width = "816px";
4399
- card.style.minHeight = "1056px";
4400
- card.style.boxSizing = "border-box";
4401
- card.style.marginBottom = "24px";
4402
- return card;
4403
- };
4404
- let curCard = createRtfCard();
4405
- wrapper.appendChild(curCard);
4406
- pageElements = [curCard];
4407
- let curH = 0;
4408
- const maxH = 920;
4409
- for (let i = 0; i < children.length; i++) {
4410
- const child = children[i];
4411
- const chH = childHeights[i];
4412
- curCard.appendChild(child);
4413
- curH += chH;
4414
- if (curH >= maxH && i < children.length - 1) {
4415
- curCard = createRtfCard();
4416
- wrapper.appendChild(curCard);
4417
- pageElements.push(curCard);
4418
- curH = 0;
4419
- }
4420
- }
4750
+ const contentNodes = [];
4751
+ for (const item of htmlElements) {
4752
+ if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4753
+ contentNodes.push(...Array.from(item.children));
4421
4754
  } else {
4422
- pageElements = [singleEl];
4755
+ contentNodes.push(item);
4423
4756
  }
4424
- } else {
4425
- pageElements = htmlElements;
4426
- for (let i = 0; i < htmlElements.length; i++) {
4427
- const el = htmlElements[i];
4428
- el.style.display = i === 0 ? "block" : "none";
4429
- wrapper.appendChild(el);
4757
+ }
4758
+ wrapper.innerHTML = "";
4759
+ contentNodes.forEach((node) => wrapper.appendChild(node));
4760
+ const childHeights = contentNodes.map((c) => {
4761
+ const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4762
+ const offH = c.offsetHeight || 0;
4763
+ const textLen = c.textContent?.trim().length || 0;
4764
+ const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
4765
+ return Math.max(rectH, offH, estH);
4766
+ });
4767
+ wrapper.innerHTML = "";
4768
+ const createRtfCard = () => {
4769
+ const card = document.createElement("div");
4770
+ card.className = "fp-rtf-page-card";
4771
+ card.style.backgroundColor = "#ffffff";
4772
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4773
+ card.style.borderRadius = "4px";
4774
+ card.style.padding = "72px 56px";
4775
+ card.style.width = "816px";
4776
+ card.style.minHeight = "1056px";
4777
+ card.style.boxSizing = "border-box";
4778
+ card.style.marginBottom = "24px";
4779
+ card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4780
+ card.style.lineHeight = "1.6";
4781
+ return card;
4782
+ };
4783
+ let curCard = createRtfCard();
4784
+ wrapper.appendChild(curCard);
4785
+ pageElements = [curCard];
4786
+ let curH = 0;
4787
+ const maxH = 912;
4788
+ for (let i = 0; i < contentNodes.length; i++) {
4789
+ const child = contentNodes[i];
4790
+ const chH = childHeights[i];
4791
+ curCard.appendChild(child);
4792
+ curH += chH;
4793
+ if (curH >= maxH && i < contentNodes.length - 1) {
4794
+ curCard = createRtfCard();
4795
+ wrapper.appendChild(curCard);
4796
+ pageElements.push(curCard);
4797
+ curH = 0;
4430
4798
  }
4431
4799
  }
4432
4800
  } catch (err) {
@@ -4795,6 +5163,14 @@ var OpenDocumentPlugin = class {
4795
5163
  type: "button",
4796
5164
  group: "actions",
4797
5165
  execute: () => instance.print?.()
5166
+ },
5167
+ {
5168
+ id: "open-window",
5169
+ icon: "open-window",
5170
+ label: "Open in Separate Full Window",
5171
+ type: "button",
5172
+ group: "actions",
5173
+ execute: () => instance.openInSeparateWindow?.()
4798
5174
  }
4799
5175
  );
4800
5176
  return actions;
@@ -5383,6 +5759,14 @@ var DocPlugin = class {
5383
5759
  type: "button",
5384
5760
  group: "actions",
5385
5761
  execute: () => instance.print?.()
5762
+ },
5763
+ {
5764
+ id: "open-window",
5765
+ icon: "open-window",
5766
+ label: "Open in Separate Full Window",
5767
+ type: "button",
5768
+ group: "actions",
5769
+ execute: () => instance.openInSeparateWindow?.()
5386
5770
  }
5387
5771
  );
5388
5772
  return actions;
@@ -5402,8 +5786,17 @@ var DocPlugin = class {
5402
5786
  let scale = 1;
5403
5787
  let extractedRawText = "";
5404
5788
  let isFallback = false;
5789
+ let chartSvg = "";
5405
5790
  try {
5406
5791
  const cfbf = new CfbfReader(ctx.buffer);
5792
+ try {
5793
+ const pkg = cfbf.readStream("package_stream");
5794
+ if (pkg && pkg.length > 100) {
5795
+ chartSvg = this.parseOdfChartToSvg(pkg);
5796
+ }
5797
+ } catch (chartErr) {
5798
+ console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
5799
+ }
5407
5800
  const wordDocStream = cfbf.readStream("WordDocument");
5408
5801
  if (!wordDocStream || wordDocStream.length < 512) {
5409
5802
  throw new Error("WordDocument stream not found or invalid in CFBF archive");
@@ -5421,7 +5814,7 @@ var DocPlugin = class {
5421
5814
  extractedRawText = fallback;
5422
5815
  isFallback = true;
5423
5816
  }
5424
- const rawPages = this.splitIntoPages(extractedRawText);
5817
+ const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
5425
5818
  const totalPages = Math.max(1, rawPages.length);
5426
5819
  let currentPage = 1;
5427
5820
  const pageCards = [];
@@ -5663,7 +6056,7 @@ var DocPlugin = class {
5663
6056
  for (const run of [...ansiRuns, ...utf16Runs]) {
5664
6057
  const trimmed = run.trim();
5665
6058
  if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
5666
- 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)) {
6059
+ 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)) {
5667
6060
  seen.add(trimmed);
5668
6061
  candidateLines.push(trimmed);
5669
6062
  }
@@ -5674,12 +6067,138 @@ var DocPlugin = class {
5674
6067
  heuristicTextExtraction(buffer) {
5675
6068
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5676
6069
  }
5677
- splitIntoPages(text) {
6070
+ /**
6071
+ * Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
6072
+ */
6073
+ parseOdfChartToSvg(zipBytes) {
6074
+ try {
6075
+ const unzipped = fflate__namespace.unzipSync(zipBytes);
6076
+ const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
6077
+ if (!contentXml) return "";
6078
+ const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
6079
+ if (rowsMatch.length < 2) return "";
6080
+ const headers = [];
6081
+ const firstRow = rowsMatch[0];
6082
+ const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
6083
+ for (const h of headerCells) {
6084
+ headers.push(h.replace(/<\/?text:p>/g, "").trim());
6085
+ }
6086
+ const categories = [];
6087
+ const seriesValues = headers.map(() => []);
6088
+ for (let r = 1; r < rowsMatch.length; r++) {
6089
+ const rowStr = rowsMatch[r];
6090
+ if (!rowStr) continue;
6091
+ const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
6092
+ if (cells.length > 0 && cells[0]) {
6093
+ const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
6094
+ categories.push(catMatch ? catMatch[1] : "Row " + r);
6095
+ for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
6096
+ const cellStr = cells[c];
6097
+ if (!cellStr) continue;
6098
+ const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
6099
+ const series = seriesValues[c - 1];
6100
+ if (series) {
6101
+ series.push(valMatch ? parseFloat(valMatch[1]) : 0);
6102
+ }
6103
+ }
6104
+ }
6105
+ }
6106
+ const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
6107
+ const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
6108
+ let cIdx = 0;
6109
+ for (const cm of colorMatches) {
6110
+ if (cIdx < colors.length) colors[cIdx] = cm[1];
6111
+ cIdx++;
6112
+ }
6113
+ let maxVal = 10;
6114
+ for (const s of seriesValues) {
6115
+ for (const v of s) {
6116
+ if (v > maxVal) maxVal = v;
6117
+ }
6118
+ }
6119
+ maxVal = Math.ceil(maxVal * 1.15);
6120
+ const width = 560;
6121
+ const height = 280;
6122
+ const padLeft = 45;
6123
+ const padRight = 100;
6124
+ const padTop = 20;
6125
+ const padBottom = 40;
6126
+ const chartW = width - padLeft - padRight;
6127
+ const chartH = height - padTop - padBottom;
6128
+ 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);">`;
6129
+ for (let step = 0; step <= 4; step++) {
6130
+ const yVal = (maxVal / 4 * step).toFixed(1);
6131
+ const yPos = padTop + chartH - step / 4 * chartH;
6132
+ svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
6133
+ svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
6134
+ }
6135
+ const numCats = categories.length;
6136
+ const numSeries = headers.length;
6137
+ const groupW = chartW / numCats;
6138
+ const barW = Math.max(8, groupW * 0.7 / numSeries);
6139
+ const groupPad = (groupW - barW * numSeries) / 2;
6140
+ for (let catIdx = 0; catIdx < numCats; catIdx++) {
6141
+ const groupX = padLeft + catIdx * groupW + groupPad;
6142
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6143
+ const val = seriesValues[sIdx][catIdx] || 0;
6144
+ const barH = val / maxVal * chartH;
6145
+ const barX = groupX + sIdx * barW;
6146
+ const barY = padTop + chartH - barH;
6147
+ const col = colors[sIdx % colors.length];
6148
+ svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
6149
+ }
6150
+ const catX = padLeft + catIdx * groupW + groupW / 2;
6151
+ svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
6152
+ }
6153
+ let legendY = padTop + 20;
6154
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6155
+ const col = colors[sIdx % colors.length];
6156
+ svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
6157
+ svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
6158
+ legendY += 20;
6159
+ }
6160
+ svg += "</svg>";
6161
+ return svg;
6162
+ } catch (e) {
6163
+ console.warn("[DocPlugin] Error generating chart SVG:", e);
6164
+ return "";
6165
+ }
6166
+ }
6167
+ cleanWordDocFields(text, chartSvg = "") {
6168
+ if (!text) return "";
6169
+ let cleaned = text.replace(
6170
+ /\x13\s*EMBED\b[\s\S]*?\x15/gi,
6171
+ () => chartSvg ? `
6172
+
6173
+ ${chartSvg}
6174
+
6175
+ ` : ""
6176
+ );
6177
+ cleaned = cleaned.replace(
6178
+ /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
6179
+ (_match, url, label) => {
6180
+ const cleanUrl = url.trim();
6181
+ const cleanLabel = label.trim() || cleanUrl;
6182
+ return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
6183
+ }
6184
+ );
6185
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
6186
+ if (/[\x00-\x1F]/.test(res)) return "";
6187
+ return res.trim();
6188
+ });
6189
+ cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
6190
+ cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6191
+ cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
6192
+ cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
6193
+ return cleaned;
6194
+ }
6195
+ splitIntoPages(text, chartSvg = "") {
5678
6196
  if (!text) return [""];
5679
- const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
6197
+ const cleanedText = this.cleanWordDocFields(text, chartSvg);
6198
+ const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
5680
6199
  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);
5681
6200
  if (explicitParts.length === 0) explicitParts.push(normalized);
5682
- const maxLinesPerPage = 32;
6201
+ const maxLinesPerPage = 34;
5683
6202
  const charsPerLine = 80;
5684
6203
  const finalPages = [];
5685
6204
  for (const part of explicitParts) {
@@ -5687,7 +6206,8 @@ var DocPlugin = class {
5687
6206
  let currentLines = [];
5688
6207
  let count = 0;
5689
6208
  for (const line of lines) {
5690
- const vLines = Math.max(1, Math.ceil((line.length || 1) / charsPerLine));
6209
+ const isSvg = line.includes("<svg");
6210
+ const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
5691
6211
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
5692
6212
  finalPages.push(currentLines.join("\n"));
5693
6213
  currentLines = [];
@@ -5727,9 +6247,44 @@ var DocPlugin = class {
5727
6247
  tableLines = [];
5728
6248
  }
5729
6249
  };
6250
+ const sanitizeOptions = {
6251
+ ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
6252
+ ADD_ATTR: [
6253
+ "href",
6254
+ "target",
6255
+ "rel",
6256
+ "style",
6257
+ "viewBox",
6258
+ "width",
6259
+ "height",
6260
+ "x",
6261
+ "y",
6262
+ "x1",
6263
+ "y1",
6264
+ "x2",
6265
+ "y2",
6266
+ "fill",
6267
+ "stroke",
6268
+ "stroke-width",
6269
+ "stroke-dasharray",
6270
+ "rx",
6271
+ "font-size",
6272
+ "text-anchor"
6273
+ ]
6274
+ };
5730
6275
  let i = 0;
5731
6276
  while (i < lines.length) {
5732
6277
  let line = lines[i];
6278
+ if (line.includes("<svg")) {
6279
+ if (inList) {
6280
+ html += "</ul>";
6281
+ inList = false;
6282
+ }
6283
+ flushTable();
6284
+ html += line;
6285
+ i++;
6286
+ continue;
6287
+ }
5733
6288
  let tabCount = (line.match(/\t/g) || []).length;
5734
6289
  if (tabCount > 0) {
5735
6290
  let j = i;
@@ -5768,20 +6323,20 @@ var DocPlugin = class {
5768
6323
  html += "</ul>";
5769
6324
  inList = false;
5770
6325
  }
5771
- html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line)}</h2>`;
6326
+ html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</h2>`;
5772
6327
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
5773
6328
  if (!inList) {
5774
6329
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
5775
6330
  inList = true;
5776
6331
  }
5777
6332
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
5778
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText)}</li>`;
6333
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
5779
6334
  } else {
5780
6335
  if (inList) {
5781
6336
  html += "</ul>";
5782
6337
  inList = false;
5783
6338
  }
5784
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line)}</p>`;
6339
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
5785
6340
  }
5786
6341
  i++;
5787
6342
  }
@@ -6354,6 +6909,7 @@ exports.excelPlugin = excelPlugin;
6354
6909
  exports.extractExtension = extractExtension;
6355
6910
  exports.formatFileSize = formatFileSize;
6356
6911
  exports.getDefaultPlugins = getDefaultPlugins;
6912
+ exports.getTransferPayload = getTransferPayload;
6357
6913
  exports.htmlPreviewPlugin = htmlPreviewPlugin;
6358
6914
  exports.markdownPlugin = markdownPlugin;
6359
6915
  exports.mediaPlugin = mediaPlugin;
@@ -6366,6 +6922,7 @@ exports.printElement = printElement;
6366
6922
  exports.rtfPlugin = rtfPlugin;
6367
6923
  exports.sanitizeHTML = sanitizeHTML;
6368
6924
  exports.sanitizeSVG = sanitizeSVG;
6925
+ exports.saveTransferPayload = saveTransferPayload;
6369
6926
  exports.sourceToArrayBuffer = sourceToArrayBuffer;
6370
6927
  exports.threeDPlugin = threeDPlugin;
6371
6928
  //# sourceMappingURL=index.cjs.map