@files-preview-app/preview-file 1.2.8 → 1.3.0

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/vue.cjs CHANGED
@@ -17,6 +17,7 @@ var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
17
17
  var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
18
18
  var RTFJS = require('rtf.js/dist/RTFJS.bundle.js');
19
19
 
20
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
20
21
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
22
 
22
23
  function _interopNamespace(e) {
@@ -369,16 +370,21 @@ async function sourceToArrayBuffer(source, signal) {
369
370
  metadata.mimeType = source.type || void 0;
370
371
  metadata.extension = extractExtension(source.name);
371
372
  buffer = await source.arrayBuffer();
372
- } else if (source instanceof Blob) {
373
+ } else if (source instanceof Blob || source && typeof source.arrayBuffer === "function" && typeof source.size === "number") {
373
374
  metadata.size = source.size;
374
375
  metadata.mimeType = source.type || void 0;
376
+ if (source.name) {
377
+ metadata.name = source.name;
378
+ metadata.extension = extractExtension(source.name);
379
+ }
375
380
  buffer = await source.arrayBuffer();
376
- } else if (source instanceof ArrayBuffer) {
381
+ } else if (source instanceof ArrayBuffer || Object.prototype.toString.call(source) === "[object ArrayBuffer]" || source && typeof source.byteLength === "number" && typeof source.slice === "function") {
377
382
  buffer = source;
378
- } else if (source instanceof Uint8Array) {
379
- buffer = source.buffer.slice(
380
- source.byteOffset,
381
- source.byteOffset + source.byteLength
383
+ } else if (source instanceof Uint8Array || ArrayBuffer.isView(source)) {
384
+ const view = source;
385
+ buffer = view.buffer.slice(
386
+ view.byteOffset,
387
+ view.byteOffset + view.byteLength
382
388
  );
383
389
  } else {
384
390
  throw new Error("Unsupported file source type");
@@ -463,6 +469,45 @@ function createElement(tag, attrs, ...children) {
463
469
  }
464
470
  return el;
465
471
  }
472
+ var DB_NAME = "PreviewFileTransferDB";
473
+ var DB_STORE = "transfers";
474
+ function openDB() {
475
+ return new Promise((resolve, reject) => {
476
+ if (typeof indexedDB === "undefined") {
477
+ return reject(new Error("IndexedDB is not available"));
478
+ }
479
+ const req = indexedDB.open(DB_NAME, 1);
480
+ req.onupgradeneeded = () => {
481
+ const db = req.result;
482
+ if (!db.objectStoreNames.contains(DB_STORE)) {
483
+ db.createObjectStore(DB_STORE, { keyPath: "id" });
484
+ }
485
+ };
486
+ req.onsuccess = () => resolve(req.result);
487
+ req.onerror = () => reject(req.error);
488
+ });
489
+ }
490
+ async function saveTransferPayload(id, payload) {
491
+ if (typeof window !== "undefined") {
492
+ try {
493
+ window[id] = payload;
494
+ window.__lastTransfer = payload;
495
+ } catch {
496
+ }
497
+ }
498
+ try {
499
+ const db = await openDB();
500
+ return new Promise((resolve, reject) => {
501
+ const tx = db.transaction(DB_STORE, "readwrite");
502
+ const store = tx.objectStore(DB_STORE);
503
+ store.put({ id, ...payload, timestamp: Date.now() });
504
+ tx.oncomplete = () => resolve();
505
+ tx.onerror = () => reject(tx.error);
506
+ });
507
+ } catch (e) {
508
+ console.warn("[saveTransferPayload] IndexedDB store warning:", e);
509
+ }
510
+ }
466
511
  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>`;
467
512
  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>`;
468
513
  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>`;
@@ -482,6 +527,7 @@ var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" str
482
527
  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>`;
483
528
  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>`;
484
529
  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>`;
530
+ 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>`;
485
531
  var ICON_MAP = {
486
532
  "zoom-in": ICON_ZOOM_IN,
487
533
  "zoom-out": ICON_ZOOM_OUT,
@@ -508,7 +554,9 @@ var ICON_MAP = {
508
554
  "forward-10": ICON_FAST_FORWARD,
509
555
  "rewind": ICON_REWIND,
510
556
  "replay-10": ICON_REWIND,
511
- "speed": ICON_SPEED
557
+ "speed": ICON_SPEED,
558
+ "open-window": ICON_EXTERNAL_WINDOW,
559
+ "external-window": ICON_EXTERNAL_WINDOW
512
560
  };
513
561
  var ToolbarController = class {
514
562
  el;
@@ -725,7 +773,7 @@ var ThumbnailPanel = class {
725
773
  }
726
774
  }
727
775
  };
728
- var FilePreviewViewer = class {
776
+ var FilePreviewViewer = class _FilePreviewViewer {
729
777
  plugins = [];
730
778
  activeInstance = null;
731
779
  abortController = null;
@@ -762,6 +810,7 @@ var FilePreviewViewer = class {
762
810
  * Preview a file in the given container element.
763
811
  */
764
812
  async preview(container, source, options = {}) {
813
+ this.currentOptions = options;
765
814
  this.abort();
766
815
  this.abortController = new AbortController();
767
816
  const { signal } = this.abortController;
@@ -771,7 +820,10 @@ var FilePreviewViewer = class {
771
820
  this.showLoading();
772
821
  try {
773
822
  const { buffer, metadata } = await sourceToArrayBuffer(source, signal);
774
- this.currentBuffer = buffer;
823
+ if (options.metadata) {
824
+ Object.assign(metadata, options.metadata);
825
+ }
826
+ this.currentBuffer = buffer.slice(0);
775
827
  this.currentMetadata = metadata;
776
828
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
777
829
  const fileInfo = { metadata, buffer };
@@ -801,6 +853,7 @@ var FilePreviewViewer = class {
801
853
  }
802
854
  });
803
855
  this.activeInstance = instance;
856
+ instance.openInSeparateWindow = () => this.openInSeparateWindow();
804
857
  this.hideLoading();
805
858
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
806
859
  if (options.showToolbar !== false && this.toolbar) {
@@ -810,26 +863,16 @@ var FilePreviewViewer = class {
810
863
  actions.push({
811
864
  id: "fullscreen",
812
865
  icon: "fullscreen",
813
- label: "Toggle Fullscreen",
866
+ label: "Fullscreen",
814
867
  type: "button",
815
868
  group: "view",
816
- execute: async () => {
869
+ execute: () => {
817
870
  try {
818
- const isNativeFs = !!document.fullscreenElement;
819
- const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
820
- if (!isNativeFs && !isCssFs) {
821
- if (this.wrapperEl?.requestFullscreen) {
822
- await this.wrapperEl.requestFullscreen().catch(() => {
823
- this.wrapperEl?.classList.add("fp-fullscreen-active");
824
- });
825
- } else {
826
- this.wrapperEl?.classList.add("fp-fullscreen-active");
827
- }
871
+ if (!document.fullscreenElement) {
872
+ this.wrapperEl?.requestFullscreen?.();
873
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
828
874
  } else {
829
- if (document.fullscreenElement) {
830
- await document.exitFullscreen().catch(() => {
831
- });
832
- }
875
+ document.exitFullscreen?.();
833
876
  this.wrapperEl?.classList.remove("fp-fullscreen-active");
834
877
  }
835
878
  } catch {
@@ -841,6 +884,28 @@ var FilePreviewViewer = class {
841
884
  }
842
885
  });
843
886
  }
887
+ const openWinAction = actions.find((a) => a.id === "open-window");
888
+ if (openWinAction) {
889
+ if (options?._isSeparateWindow) {
890
+ const idx = actions.indexOf(openWinAction);
891
+ if (idx !== -1) actions.splice(idx, 1);
892
+ } else {
893
+ openWinAction.execute = () => {
894
+ this.openInSeparateWindow();
895
+ };
896
+ }
897
+ } else if (!options?._isSeparateWindow) {
898
+ actions.push({
899
+ id: "open-window",
900
+ icon: "open-window",
901
+ label: "Open in Separate Full Window",
902
+ type: "button",
903
+ group: "actions",
904
+ execute: () => {
905
+ this.openInSeparateWindow();
906
+ }
907
+ });
908
+ }
844
909
  this.toolbar.update(actions);
845
910
  this.toolbar.show();
846
911
  }
@@ -873,6 +938,104 @@ var FilePreviewViewer = class {
873
938
  throw error;
874
939
  }
875
940
  }
941
+ /**
942
+ * Opens the current file preview in a separate full browser window.
943
+ */
944
+ openInSeparateWindow() {
945
+ if (!this.currentBuffer) {
946
+ console.warn("[FilePreviewViewer] No active file buffer to open in separate window");
947
+ return null;
948
+ }
949
+ if (this.currentOptions.onOpenSeparateWindow) {
950
+ return this.currentOptions.onOpenSeparateWindow({
951
+ buffer: this.currentBuffer,
952
+ metadata: this.currentMetadata || { name: "Document" },
953
+ options: this.currentOptions
954
+ });
955
+ }
956
+ const transferId = "fp_win_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
957
+ let clonedBuffer;
958
+ try {
959
+ clonedBuffer = this.currentBuffer.slice(0);
960
+ } catch {
961
+ clonedBuffer = this.currentBuffer;
962
+ }
963
+ const payload = {
964
+ buffer: clonedBuffer,
965
+ metadata: this.currentMetadata ? { ...this.currentMetadata } : void 0,
966
+ options: { ...this.currentOptions, _isSeparateWindow: true }
967
+ };
968
+ if (typeof window !== "undefined") {
969
+ try {
970
+ window[transferId] = payload;
971
+ window.__lastTransfer = payload;
972
+ } catch {
973
+ }
974
+ }
975
+ saveTransferPayload(transferId, payload).catch((err) => {
976
+ console.warn("[FilePreviewViewer] Transfer payload save warning:", err);
977
+ });
978
+ let targetUrl = null;
979
+ if (this.currentOptions.standaloneViewerUrl) {
980
+ const u = new URL(this.currentOptions.standaloneViewerUrl, window.location.href);
981
+ u.searchParams.set("mode", "fullscreen");
982
+ u.searchParams.set("transferId", transferId);
983
+ targetUrl = u.toString();
984
+ } else if (typeof window !== "undefined" && window.location?.href && !window.location.href.startsWith("about:")) {
985
+ const u = new URL(window.location.href);
986
+ u.searchParams.set("mode", "fullscreen");
987
+ u.searchParams.set("transferId", transferId);
988
+ targetUrl = u.toString();
989
+ }
990
+ if (targetUrl) {
991
+ const newWin2 = window.open(targetUrl, "_blank");
992
+ if (!newWin2) {
993
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
994
+ return null;
995
+ }
996
+ return newWin2;
997
+ }
998
+ const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
999
+ const newWin = window.open("", "_blank");
1000
+ if (!newWin) {
1001
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1002
+ return null;
1003
+ }
1004
+ newWin.document.title = title;
1005
+ newWin.document.body.style.margin = "0";
1006
+ newWin.document.body.style.padding = "0";
1007
+ newWin.document.body.style.width = "100vw";
1008
+ newWin.document.body.style.height = "100vh";
1009
+ newWin.document.body.style.overflow = "hidden";
1010
+ newWin.document.body.style.backgroundColor = "#f8fafc";
1011
+ const headNodes = document.querySelectorAll('link[rel="stylesheet"], style');
1012
+ headNodes.forEach((node) => {
1013
+ newWin.document.head.appendChild(node.cloneNode(true));
1014
+ });
1015
+ const root = newWin.document.createElement("div");
1016
+ root.id = "full-window-preview-root";
1017
+ root.style.width = "100%";
1018
+ root.style.height = "100%";
1019
+ root.style.overflow = "hidden";
1020
+ newWin.document.body.appendChild(root);
1021
+ const separateViewer = new _FilePreviewViewer();
1022
+ for (const plugin of this.plugins) {
1023
+ separateViewer.registerPlugin(plugin);
1024
+ }
1025
+ separateViewer.preview(root, this.currentBuffer.slice(0), {
1026
+ ...this.currentOptions,
1027
+ showToolbar: true,
1028
+ toolbarPosition: "top",
1029
+ metadata: this.currentMetadata || void 0,
1030
+ _isSeparateWindow: true
1031
+ }).catch((err) => {
1032
+ console.error("[FilePreviewViewer] Error rendering in separate window:", err);
1033
+ });
1034
+ newWin.addEventListener("beforeunload", () => {
1035
+ separateViewer.destroy();
1036
+ });
1037
+ return newWin;
1038
+ }
876
1039
  /**
877
1040
  * Subscribe to viewer events.
878
1041
  */
@@ -1278,8 +1441,20 @@ var CfbfReader = class {
1278
1441
  }
1279
1442
  };
1280
1443
  if (typeof window !== "undefined" && pdfjsLib__namespace.GlobalWorkerOptions) {
1281
- if (!pdfjsLib__namespace.GlobalWorkerOptions.workerSrc) {
1282
- pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1444
+ if (!pdfjsLib__namespace.GlobalWorkerOptions.workerPort && !pdfjsLib__namespace.GlobalWorkerOptions.workerSrc) {
1445
+ const customWorker = window.__PDF_WORKER_SRC__;
1446
+ if (customWorker) {
1447
+ pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = customWorker;
1448
+ } else {
1449
+ try {
1450
+ pdfjsLib__namespace.GlobalWorkerOptions.workerPort = new Worker(
1451
+ 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('vue.cjs', document.baseURI).href))),
1452
+ { type: "module" }
1453
+ );
1454
+ } catch {
1455
+ pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.mjs";
1456
+ }
1457
+ }
1283
1458
  }
1284
1459
  }
1285
1460
  var PdfPlugin = class {
@@ -1373,6 +1548,14 @@ var PdfPlugin = class {
1373
1548
  type: "button",
1374
1549
  group: "actions",
1375
1550
  execute: () => instance.print?.()
1551
+ },
1552
+ {
1553
+ id: "open-window",
1554
+ icon: "open-window",
1555
+ label: "Open in Separate Full Window",
1556
+ type: "button",
1557
+ group: "actions",
1558
+ execute: () => instance.openInSeparateWindow?.()
1376
1559
  }
1377
1560
  ];
1378
1561
  }
@@ -1422,18 +1605,21 @@ var PdfPlugin = class {
1422
1605
  indicator.style.pointerEvents = "none";
1423
1606
  container.appendChild(indicator);
1424
1607
  ctx.container.appendChild(container);
1608
+ const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1609
+ const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
1425
1610
  const loadingTask = pdfjsLib__namespace.getDocument({
1426
- data: new Uint8Array(ctx.buffer),
1427
- cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/cmaps/`,
1611
+ data: new Uint8Array(ctx.buffer.slice(0)),
1612
+ cMapUrl: cmapsUrl,
1428
1613
  cMapPacked: true,
1429
- standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/standard_fonts/`
1614
+ standardFontDataUrl: standardFontsUrl,
1615
+ verbosity: 0
1430
1616
  });
1431
1617
  const pdfDoc = await loadingTask.promise;
1432
1618
  const totalPages = Math.max(1, pdfDoc.numPages);
1433
1619
  let currentPage = 1;
1434
1620
  let zoomScale = 1;
1435
1621
  let rotation = 0;
1436
- let fitMode = "width";
1622
+ let fitMode = "page";
1437
1623
  let currentRenderTask = null;
1438
1624
  const renderPage = async (pageNum) => {
1439
1625
  if (currentRenderTask) {
@@ -1453,15 +1639,15 @@ var PdfPlugin = class {
1453
1639
  const containerWidth = container.clientWidth || 900;
1454
1640
  const containerHeight = container.clientHeight || 700;
1455
1641
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1456
- const availWidth = Math.max(280, containerWidth - 48);
1457
- const availHeight = Math.max(280, containerHeight - 88);
1642
+ const availWidth = Math.max(320, containerWidth - 48);
1643
+ const availHeight = Math.max(550, containerHeight - 88);
1458
1644
  const scaleW = availWidth / unscaledVp.width;
1459
1645
  const scaleH = availHeight / unscaledVp.height;
1460
1646
  let fitScale;
1461
1647
  if (fitMode === "page") {
1462
- fitScale = Math.max(0.5, Math.min(scaleW, scaleH));
1648
+ fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
1463
1649
  } else {
1464
- fitScale = Math.max(0.65, Math.min(1.15, scaleW));
1650
+ fitScale = Math.max(0.65, Math.min(1.25, scaleW));
1465
1651
  }
1466
1652
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1467
1653
  const pixelRatio = window.devicePixelRatio || 1;
@@ -1487,7 +1673,7 @@ var PdfPlugin = class {
1487
1673
  currentRenderTask = null;
1488
1674
  }
1489
1675
  };
1490
- await renderPage(1);
1676
+ renderPage(1);
1491
1677
  let resizeTimer = null;
1492
1678
  const resizeObserver = new ResizeObserver(() => {
1493
1679
  if (resizeTimer) clearTimeout(resizeTimer);
@@ -1531,7 +1717,7 @@ var PdfPlugin = class {
1531
1717
  renderPage(currentPage);
1532
1718
  },
1533
1719
  fitToPage: () => {
1534
- fitMode = fitMode === "width" ? "page" : "width";
1720
+ fitMode = fitMode === "page" ? "width" : "page";
1535
1721
  zoomScale = 1;
1536
1722
  rotation = 0;
1537
1723
  renderPage(currentPage);
@@ -1548,6 +1734,7 @@ var PdfPlugin = class {
1548
1734
  getPageCount: () => totalPages,
1549
1735
  getCurrentPage: () => currentPage,
1550
1736
  goToPage: (page) => {
1737
+ container.scrollTop = 0;
1551
1738
  renderPage(page);
1552
1739
  },
1553
1740
  download: () => {
@@ -2007,6 +2194,14 @@ var DocxPlugin = class {
2007
2194
  type: "button",
2008
2195
  group: "actions",
2009
2196
  execute: () => instance.print?.()
2197
+ },
2198
+ {
2199
+ id: "open-window",
2200
+ icon: "open-window",
2201
+ label: "Open in Separate Full Window",
2202
+ type: "button",
2203
+ group: "actions",
2204
+ execute: () => instance.openInSeparateWindow?.()
2010
2205
  }
2011
2206
  );
2012
2207
  return actions;
@@ -2052,13 +2247,45 @@ var DocxPlugin = class {
2052
2247
  ignoreFonts: true,
2053
2248
  // Avoid crashes on embedded obfuscated fonts
2054
2249
  breakPages: true,
2055
- experimental: true
2250
+ experimental: true,
2251
+ ignoreLastRenderedPageBreak: false,
2252
+ // Honor Word's exact page breaks!
2253
+ renderHeaders: true,
2254
+ renderFooters: true,
2255
+ renderFootnotes: true,
2256
+ renderEndnotes: true,
2257
+ useBase64URL: true
2056
2258
  });
2057
2259
  if (!ctx.container.contains(wrapper)) {
2058
2260
  ctx.container.appendChild(wrapper);
2059
2261
  }
2060
2262
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2061
2263
  renderedSuccessfully = true;
2264
+ try {
2265
+ const unzipped = fflate.unzipSync(new Uint8Array(ctx.buffer));
2266
+ const chartKeys = Object.keys(unzipped).filter((k) => k.replace(/^[./\\]+/, "").toLowerCase().startsWith("word/charts/chart") && k.endsWith(".xml")).sort();
2267
+ if (chartKeys.length > 0) {
2268
+ const allDivs = Array.from(wrapper.querySelectorAll("div"));
2269
+ const emptyContainers = allDivs.filter((div) => {
2270
+ const st = div.getAttribute("style") || "";
2271
+ return st.includes("width:") && st.includes("height:") && div.children.length === 0 && (div.textContent?.trim().length ?? 0) === 0;
2272
+ });
2273
+ chartKeys.forEach((cKey, idx) => {
2274
+ const target = emptyContainers[idx];
2275
+ if (target) {
2276
+ const xmlStr = fflate.strFromU8(unzipped[cKey]);
2277
+ const svg = this.parseAndRenderChartSvg(xmlStr);
2278
+ if (svg) {
2279
+ target.innerHTML = svg;
2280
+ target.style.display = "block";
2281
+ target.style.margin = "12px auto";
2282
+ }
2283
+ }
2284
+ });
2285
+ }
2286
+ } catch (chartErr) {
2287
+ console.warn("[DocxPlugin] Non-critical error rendering DrawingML charts:", chartErr);
2288
+ }
2062
2289
  }
2063
2290
  } catch (err) {
2064
2291
  console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
@@ -2073,11 +2300,14 @@ var DocxPlugin = class {
2073
2300
  renderedSuccessfully = true;
2074
2301
  } catch (fallbackErr) {
2075
2302
  console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
2303
+ const isCorrupt = fallbackErr?.message?.includes("invalid zip") || fallbackErr?.message?.includes("corrupted");
2076
2304
  wrapper.innerHTML = `
2077
- <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
2078
- <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
2079
- <h3 style="margin: 0 0 8px; color: #1e293b;">${ctx.metadata.name || "Word Document"}</h3>
2080
- <p style="color: #64748b; margin: 0;">Could not parse document content</p>
2305
+ <div style="text-align:center; padding: 48px 32px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06); max-width: 600px; margin: 40px auto;">
2306
+ <div style="font-size:48px; margin-bottom: 16px;">${isCorrupt ? "\u26A0\uFE0F" : "\u{1F4C4}"}</div>
2307
+ <h3 style="margin: 0 0 8px; color: #1e293b; font-size: 18px;">${ctx.metadata.name || "Word Document"}</h3>
2308
+ <p style="color: #64748b; margin: 0 0 16px; font-size: 14px; line-height: 1.5;">
2309
+ ${isCorrupt ? "This document appears to be corrupted or contains invalid archive data and cannot be opened (matches Microsoft Word on Windows)." : "Could not render document content. The file structure may be damaged."}
2310
+ </p>
2081
2311
  </div>
2082
2312
  `;
2083
2313
  if (!ctx.container.contains(wrapper)) {
@@ -2087,50 +2317,74 @@ var DocxPlugin = class {
2087
2317
  }
2088
2318
  let sections = Array.from(wrapper.querySelectorAll("section.docx"));
2089
2319
  const cards = Array.from(wrapper.querySelectorAll(".fp-docx-page-card"));
2090
- if (sections.length === 1 && cards.length === 0) {
2091
- const singleSec = sections[0];
2092
- const pageH = 1056;
2093
- const secH = singleSec.offsetHeight || singleSec.scrollHeight;
2094
- if (secH > pageH * 1.25) {
2095
- const children = Array.from(singleSec.children);
2096
- if (children.length > 1) {
2320
+ if (sections.length > 0 && cards.length === 0) {
2321
+ const finalSections = [];
2322
+ for (const singleSec of sections) {
2323
+ const contentContainer = singleSec.querySelector("article") || singleSec;
2324
+ const children = Array.from(contentContainer.children);
2325
+ const pageH = singleSec.offsetHeight > 1300 ? 1122 : Math.max(1056, singleSec.offsetHeight);
2326
+ const secH = singleSec.scrollHeight || singleSec.offsetHeight;
2327
+ if (secH > pageH * 1.25 && children.length > 1) {
2328
+ const childHeights = children.map((c) => {
2329
+ const rectH = c.getBoundingClientRect().height;
2330
+ const offH = c.offsetHeight;
2331
+ const textLen = c.textContent?.trim().length || 0;
2332
+ const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
2333
+ return Math.max(rectH, offH, estH);
2334
+ });
2097
2335
  const parent = singleSec.parentElement || wrapper;
2098
- const newSections = [singleSec];
2099
- singleSec.innerHTML = "";
2336
+ const headerEl = singleSec.querySelector("header");
2337
+ const footerEl = singleSec.querySelector("footer");
2338
+ contentContainer.innerHTML = "";
2100
2339
  singleSec.style.minHeight = `${pageH}px`;
2101
- singleSec.style.maxHeight = `${pageH}px`;
2102
- singleSec.style.overflow = "hidden";
2103
2340
  singleSec.style.boxSizing = "border-box";
2341
+ let curContent = contentContainer;
2104
2342
  let curSec = singleSec;
2105
2343
  let curH = 0;
2106
- const maxH = pageH - 96;
2344
+ const maxH = pageH - 140;
2345
+ finalSections.push(singleSec);
2107
2346
  for (let i = 0; i < children.length; i++) {
2108
2347
  const child = children[i];
2109
- curSec.appendChild(child);
2110
- const chH = child.offsetHeight || 28;
2348
+ const chH = childHeights[i];
2349
+ curContent.appendChild(child);
2111
2350
  curH += chH;
2112
2351
  if (curH >= maxH && i < children.length - 1) {
2113
2352
  const nextSec = document.createElement("section");
2114
2353
  nextSec.className = singleSec.className;
2115
2354
  nextSec.style.cssText = singleSec.style.cssText;
2116
- nextSec.style.width = singleSec.style.width || "816px";
2117
2355
  nextSec.style.minHeight = `${pageH}px`;
2118
- nextSec.style.maxHeight = `${pageH}px`;
2119
- nextSec.style.overflow = "hidden";
2120
2356
  nextSec.style.boxSizing = "border-box";
2121
2357
  nextSec.style.backgroundColor = "#ffffff";
2122
2358
  nextSec.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
2123
2359
  nextSec.style.borderRadius = "4px";
2124
2360
  nextSec.style.marginBottom = "24px";
2125
- parent.appendChild(nextSec);
2126
- newSections.push(nextSec);
2361
+ if (headerEl) {
2362
+ nextSec.appendChild(headerEl.cloneNode(true));
2363
+ }
2364
+ const nextArticle = document.createElement("article");
2365
+ if (contentContainer.tagName.toLowerCase() === "article") {
2366
+ nextArticle.style.cssText = contentContainer.style.cssText;
2367
+ }
2368
+ nextSec.appendChild(nextArticle);
2369
+ if (footerEl) {
2370
+ nextSec.appendChild(footerEl.cloneNode(true));
2371
+ }
2372
+ if (curSec.nextSibling) {
2373
+ parent.insertBefore(nextSec, curSec.nextSibling);
2374
+ } else {
2375
+ parent.appendChild(nextSec);
2376
+ }
2377
+ finalSections.push(nextSec);
2127
2378
  curSec = nextSec;
2379
+ curContent = nextArticle;
2128
2380
  curH = 0;
2129
2381
  }
2130
2382
  }
2131
- sections = newSections;
2383
+ } else {
2384
+ finalSections.push(singleSec);
2132
2385
  }
2133
2386
  }
2387
+ sections = finalSections;
2134
2388
  }
2135
2389
  const pageElements = sections.length > 0 ? sections : cards;
2136
2390
  const totalPages = Math.max(1, pageElements.length);
@@ -2168,6 +2422,7 @@ var DocxPlugin = class {
2168
2422
  if (indicator) {
2169
2423
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2170
2424
  }
2425
+ ctx.container.scrollTop = 0;
2171
2426
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2172
2427
  };
2173
2428
  if (totalPages > 1) {
@@ -2521,6 +2776,88 @@ var DocxPlugin = class {
2521
2776
  }
2522
2777
  return result;
2523
2778
  }
2779
+ parseAndRenderChartSvg(xmlStr, width = 500, height = 260) {
2780
+ const catMatches = [...xmlStr.matchAll(/<c:cat>[\s\S]*?<c:strCache>([\s\S]*?)<\/c:strCache>/g)];
2781
+ let categories = [];
2782
+ if (catMatches.length > 0) {
2783
+ categories = [...catMatches[0][1].matchAll(/<c:v>([^<]+)<\/c:v>/g)].map((m) => m[1]);
2784
+ }
2785
+ if (categories.length === 0) {
2786
+ categories = ["Category 1", "Category 2", "Category 3", "Category 4"];
2787
+ }
2788
+ const defaultColors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021", "#83caff"];
2789
+ const sers = [...xmlStr.matchAll(/<c:ser>([\s\S]*?)<\/c:ser>/g)];
2790
+ const series = [];
2791
+ sers.forEach((s, sIdx) => {
2792
+ const titleMatch = s[1].match(/<c:tx>[\s\S]*?<c:v>([^<]+)<\/c:v>/);
2793
+ const title = titleMatch ? titleMatch[1] : `Series ${sIdx + 1}`;
2794
+ const clrMatch = s[1].match(/<a:srgbClr\s+val="([^"]+)"/);
2795
+ const color = clrMatch ? "#" + clrMatch[1] : defaultColors[sIdx % defaultColors.length];
2796
+ const valMatch = s[1].match(/<c:val>[\s\S]*?<c:numCache>([\s\S]*?)<\/c:numCache>/);
2797
+ let values = [];
2798
+ if (valMatch) {
2799
+ 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);
2800
+ }
2801
+ series.push({ title, color, values });
2802
+ });
2803
+ if (series.length === 0) return "";
2804
+ let maxVal = 10;
2805
+ series.forEach((s) => s.values.forEach((v) => {
2806
+ if (v > maxVal) maxVal = v;
2807
+ }));
2808
+ maxVal = Math.ceil(maxVal * 1.15);
2809
+ if (maxVal % 2 !== 0) maxVal++;
2810
+ const padLeft = 45;
2811
+ const padBottom = 55;
2812
+ const padTop = 20;
2813
+ const padRight = 20;
2814
+ const plotW = width - padLeft - padRight;
2815
+ const plotH = height - padTop - padBottom;
2816
+ const yTicks = 5;
2817
+ let gridLines = "";
2818
+ for (let i = 0; i <= yTicks; i++) {
2819
+ const val = maxVal / yTicks * i;
2820
+ const y = padTop + plotH - val / maxVal * plotH;
2821
+ gridLines += `<line x1="${padLeft}" y1="${y}" x2="${padLeft + plotW}" y2="${y}" stroke="#e2e8f0" stroke-width="1" />`;
2822
+ 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>`;
2823
+ }
2824
+ const numCats = categories.length;
2825
+ const numSers = series.length;
2826
+ const groupW = plotW / numCats;
2827
+ const barW = Math.max(8, Math.min(28, groupW * 0.7 / numSers));
2828
+ const groupPad = (groupW - barW * numSers) / 2;
2829
+ let bars = "";
2830
+ let catLabels = "";
2831
+ for (let c = 0; c < numCats; c++) {
2832
+ const catX = padLeft + c * groupW;
2833
+ 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>`;
2834
+ for (let s = 0; s < numSers; s++) {
2835
+ const val = series[s].values[c] ?? 0;
2836
+ const bH = Math.max(0, val / maxVal * plotH);
2837
+ const bX = catX + groupPad + s * barW;
2838
+ const bY = padTop + plotH - bH;
2839
+ bars += `<rect x="${bX}" y="${bY}" width="${barW - 2}" height="${bH}" fill="${series[s].color}" rx="1" />`;
2840
+ }
2841
+ }
2842
+ let legend = "";
2843
+ const legY = height - 12;
2844
+ let legX = padLeft + (plotW - numSers * 100) / 2;
2845
+ series.forEach((s) => {
2846
+ legend += `<rect x="${legX}" y="${legY - 9}" width="10" height="10" fill="${s.color}" rx="2" />`;
2847
+ legend += `<text x="${legX + 15}" y="${legY}" font-size="11" fill="#475569" font-family="Calibri, sans-serif">${s.title}</text>`;
2848
+ legX += 95;
2849
+ });
2850
+ return `
2851
+ <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">
2852
+ ${gridLines}
2853
+ <line x1="${padLeft}" y1="${padTop + plotH}" x2="${padLeft + plotW}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2854
+ <line x1="${padLeft}" y1="${padTop}" x2="${padLeft}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2855
+ ${bars}
2856
+ ${catLabels}
2857
+ ${legend}
2858
+ </svg>
2859
+ `.trim();
2860
+ }
2524
2861
  };
2525
2862
  function docxPlugin() {
2526
2863
  return new DocxPlugin();
@@ -3087,6 +3424,16 @@ var CodePlugin = class {
3087
3424
  execute: () => {
3088
3425
  instance.print?.();
3089
3426
  }
3427
+ },
3428
+ {
3429
+ id: "open-window",
3430
+ icon: "open-window",
3431
+ label: "Open in Separate Full Window",
3432
+ type: "button",
3433
+ group: "actions",
3434
+ execute: () => {
3435
+ instance.openInSeparateWindow?.();
3436
+ }
3090
3437
  }
3091
3438
  );
3092
3439
  return actions;
@@ -3100,17 +3447,39 @@ var CodePlugin = class {
3100
3447
  let rawPages = [];
3101
3448
  if (isTxt) {
3102
3449
  const explicitPages = fullText.split(/(?:\f|\x0C)/);
3450
+ const charsPerLine = 85;
3451
+ const maxVisualLines = 45;
3452
+ const charsPerPage = charsPerLine * maxVisualLines;
3103
3453
  for (const ep of explicitPages) {
3104
3454
  const lines = ep.split(/\r?\n/);
3105
3455
  let currentChunk = [];
3456
+ let currentLines = 0;
3106
3457
  for (let i = 0; i < lines.length; i++) {
3107
- currentChunk.push(lines[i]);
3108
- if (currentChunk.length >= 46) {
3458
+ const line = lines[i];
3459
+ const vLines = Math.max(1, Math.ceil((line.length || 1) / charsPerLine));
3460
+ if (currentLines + vLines > maxVisualLines && currentChunk.length > 0) {
3109
3461
  rawPages.push(currentChunk.join("\n"));
3110
3462
  currentChunk = [];
3463
+ currentLines = 0;
3464
+ }
3465
+ if (vLines > maxVisualLines) {
3466
+ let remaining = line;
3467
+ while (remaining.length > charsPerPage) {
3468
+ let splitIdx = remaining.lastIndexOf(" ", charsPerPage);
3469
+ if (splitIdx < charsPerPage * 0.75) splitIdx = charsPerPage;
3470
+ rawPages.push(remaining.slice(0, splitIdx));
3471
+ remaining = remaining.slice(splitIdx).trimStart();
3472
+ }
3473
+ if (remaining.length > 0) {
3474
+ currentChunk.push(remaining);
3475
+ currentLines = Math.ceil(remaining.length / charsPerLine);
3476
+ }
3477
+ } else {
3478
+ currentChunk.push(line);
3479
+ currentLines += vLines;
3111
3480
  }
3112
3481
  }
3113
- if (currentChunk.length > 0 || lines.length === 0) {
3482
+ if (currentChunk.length > 0) {
3114
3483
  rawPages.push(currentChunk.join("\n"));
3115
3484
  }
3116
3485
  }
@@ -3220,6 +3589,7 @@ var CodePlugin = class {
3220
3589
  if (indicator) {
3221
3590
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3222
3591
  }
3592
+ container.scrollTop = 0;
3223
3593
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3224
3594
  };
3225
3595
  if (totalPages > 1) {
@@ -4232,6 +4602,14 @@ var RtfPlugin = class {
4232
4602
  type: "button",
4233
4603
  group: "actions",
4234
4604
  execute: () => instance.print?.()
4605
+ },
4606
+ {
4607
+ id: "open-window",
4608
+ icon: "open-window",
4609
+ label: "Open in Separate Full Window",
4610
+ type: "button",
4611
+ group: "actions",
4612
+ execute: () => instance.openInSeparateWindow?.()
4235
4613
  }
4236
4614
  );
4237
4615
  return actions;
@@ -4284,11 +4662,55 @@ var RtfPlugin = class {
4284
4662
  }
4285
4663
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
4286
4664
  const htmlElements = await doc.render();
4287
- pageElements = htmlElements;
4288
- for (let i = 0; i < htmlElements.length; i++) {
4289
- const el = htmlElements[i];
4290
- el.style.display = i === 0 ? "block" : "none";
4291
- wrapper.appendChild(el);
4665
+ const contentNodes = [];
4666
+ for (const item of htmlElements) {
4667
+ if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4668
+ contentNodes.push(...Array.from(item.children));
4669
+ } else {
4670
+ contentNodes.push(item);
4671
+ }
4672
+ }
4673
+ wrapper.innerHTML = "";
4674
+ contentNodes.forEach((node) => wrapper.appendChild(node));
4675
+ const childHeights = contentNodes.map((c) => {
4676
+ const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4677
+ const offH = c.offsetHeight || 0;
4678
+ const textLen = c.textContent?.trim().length || 0;
4679
+ const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
4680
+ return Math.max(rectH, offH, estH);
4681
+ });
4682
+ wrapper.innerHTML = "";
4683
+ const createRtfCard = () => {
4684
+ const card = document.createElement("div");
4685
+ card.className = "fp-rtf-page-card";
4686
+ card.style.backgroundColor = "#ffffff";
4687
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4688
+ card.style.borderRadius = "4px";
4689
+ card.style.padding = "72px 56px";
4690
+ card.style.width = "816px";
4691
+ card.style.minHeight = "1056px";
4692
+ card.style.boxSizing = "border-box";
4693
+ card.style.marginBottom = "24px";
4694
+ card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4695
+ card.style.lineHeight = "1.6";
4696
+ return card;
4697
+ };
4698
+ let curCard = createRtfCard();
4699
+ wrapper.appendChild(curCard);
4700
+ pageElements = [curCard];
4701
+ let curH = 0;
4702
+ const maxH = 912;
4703
+ for (let i = 0; i < contentNodes.length; i++) {
4704
+ const child = contentNodes[i];
4705
+ const chH = childHeights[i];
4706
+ curCard.appendChild(child);
4707
+ curH += chH;
4708
+ if (curH >= maxH && i < contentNodes.length - 1) {
4709
+ curCard = createRtfCard();
4710
+ wrapper.appendChild(curCard);
4711
+ pageElements.push(curCard);
4712
+ curH = 0;
4713
+ }
4292
4714
  }
4293
4715
  } catch (err) {
4294
4716
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
@@ -4354,6 +4776,7 @@ var RtfPlugin = class {
4354
4776
  if (indicator) {
4355
4777
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4356
4778
  }
4779
+ ctx.container.scrollTop = 0;
4357
4780
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4358
4781
  };
4359
4782
  if (totalPages > 1) {
@@ -4655,6 +5078,14 @@ var OpenDocumentPlugin = class {
4655
5078
  type: "button",
4656
5079
  group: "actions",
4657
5080
  execute: () => instance.print?.()
5081
+ },
5082
+ {
5083
+ id: "open-window",
5084
+ icon: "open-window",
5085
+ label: "Open in Separate Full Window",
5086
+ type: "button",
5087
+ group: "actions",
5088
+ execute: () => instance.openInSeparateWindow?.()
4658
5089
  }
4659
5090
  );
4660
5091
  return actions;
@@ -4825,12 +5256,9 @@ var OpenDocumentPlugin = class {
4825
5256
  page.style.backgroundColor = "#ffffff";
4826
5257
  page.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
4827
5258
  page.style.borderRadius = "4px";
4828
- page.style.boxSizing = "border-box";
4829
5259
  page.style.display = idx === 0 ? "block" : "none";
4830
- page.style.position = "absolute";
4831
- page.style.top = "0";
4832
- page.style.left = "50%";
4833
- page.style.transform = "translateX(-50%)";
5260
+ page.style.margin = "0 auto 24px";
5261
+ page.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
4834
5262
  elements.forEach((el) => page.appendChild(el));
4835
5263
  wrapper.appendChild(page);
4836
5264
  slides.push(page);
@@ -4868,6 +5296,7 @@ var OpenDocumentPlugin = class {
4868
5296
  if (indicator) {
4869
5297
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4870
5298
  }
5299
+ container.scrollTop = 0;
4871
5300
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4872
5301
  };
4873
5302
  return {
@@ -5245,6 +5674,14 @@ var DocPlugin = class {
5245
5674
  type: "button",
5246
5675
  group: "actions",
5247
5676
  execute: () => instance.print?.()
5677
+ },
5678
+ {
5679
+ id: "open-window",
5680
+ icon: "open-window",
5681
+ label: "Open in Separate Full Window",
5682
+ type: "button",
5683
+ group: "actions",
5684
+ execute: () => instance.openInSeparateWindow?.()
5248
5685
  }
5249
5686
  );
5250
5687
  return actions;
@@ -5360,6 +5797,7 @@ var DocPlugin = class {
5360
5797
  if (indicator) {
5361
5798
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5362
5799
  }
5800
+ container.scrollTop = 0;
5363
5801
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5364
5802
  };
5365
5803
  if (totalPages > 1) {
@@ -5535,47 +5973,69 @@ var DocPlugin = class {
5535
5973
  heuristicTextExtraction(buffer) {
5536
5974
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5537
5975
  }
5976
+ cleanWordDocFields(text) {
5977
+ if (!text) return "";
5978
+ let cleaned = text.replace(
5979
+ /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
5980
+ (_match, url, label) => {
5981
+ const cleanUrl = url.trim();
5982
+ const cleanLabel = label.trim() || cleanUrl;
5983
+ return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
5984
+ }
5985
+ );
5986
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
5987
+ cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
5988
+ cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
5989
+ return cleaned;
5990
+ }
5538
5991
  splitIntoPages(text) {
5539
5992
  if (!text) return [""];
5540
- const explicitParts = text.split(/[\x0C\f]|\r?\n\s*[-=_]{3,}\s*(?:PAGE|Page|page break)[\s\d\w-]*[-=_]{3,}\s*\r?\n/i).map((p) => p.trim()).filter((p) => p.length > 0);
5541
- if (explicitParts.length === 0) explicitParts.push(text);
5542
- const maxLinesPerPage = 48;
5993
+ const cleanedText = this.cleanWordDocFields(text);
5994
+ const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
5995
+ 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);
5996
+ if (explicitParts.length === 0) explicitParts.push(normalized);
5997
+ const maxLinesPerPage = 32;
5998
+ const charsPerLine = 80;
5543
5999
  const finalPages = [];
5544
6000
  for (const part of explicitParts) {
5545
- const lines = part.split(/\r?\n/);
6001
+ const lines = part.split("\n");
5546
6002
  let currentLines = [];
5547
6003
  let count = 0;
5548
6004
  for (const line of lines) {
5549
- currentLines.push(line);
5550
- count++;
5551
- if (count >= maxLinesPerPage) {
6005
+ const plainLine = line.replace(/<[^>]+>/g, "");
6006
+ const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
6007
+ if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
5552
6008
  finalPages.push(currentLines.join("\n"));
5553
6009
  currentLines = [];
5554
6010
  count = 0;
5555
6011
  }
6012
+ currentLines.push(line);
6013
+ count += vLines;
5556
6014
  }
5557
6015
  if (currentLines.length > 0) {
5558
6016
  finalPages.push(currentLines.join("\n"));
5559
6017
  }
5560
6018
  }
5561
- return finalPages.length > 0 ? finalPages : [text];
6019
+ return finalPages.length > 0 ? finalPages : [normalized];
5562
6020
  }
5563
6021
  /**
5564
6022
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
5565
6023
  */
5566
6024
  formatDocToHtml(text, filename) {
5567
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
6025
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ").split("\n");
5568
6026
  let html = "";
5569
6027
  let inList = false;
5570
6028
  let tableLines = [];
5571
6029
  const flushTable = () => {
5572
6030
  if (tableLines.length > 0) {
5573
- html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 12px;">';
5574
- for (const tLine of tableLines) {
5575
- html += "<tr>";
5576
- const cols = tLine.split(" ");
6031
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; font-family: Calibri, sans-serif;">';
6032
+ for (let rIdx = 0; rIdx < tableLines.length; rIdx++) {
6033
+ const tLine = tableLines[rIdx];
6034
+ const isHeader = rIdx === 0;
6035
+ html += `<tr style="${isHeader ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
6036
+ const cols = tLine.split(" ").filter((c) => c.trim().length > 0);
5577
6037
  for (const col of cols) {
5578
- html += `<td style="border: 1px solid #cbd5e1; padding: 6px 8px;">${DOMPurify6__default.default.sanitize(col.trim())}</td>`;
6038
+ html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px;">${DOMPurify6__default.default.sanitize(col.trim())}</td>`;
5579
6039
  }
5580
6040
  html += "</tr>";
5581
6041
  }
@@ -5588,27 +6048,18 @@ var DocPlugin = class {
5588
6048
  let line = lines[i];
5589
6049
  let tabCount = (line.match(/\t/g) || []).length;
5590
6050
  if (tabCount > 0) {
5591
- let consecutiveTableLines = 1;
5592
- let j = i + 1;
5593
- while (j < lines.length) {
5594
- const nextTabCount = (lines[j].match(/\t/g) || []).length;
5595
- if (nextTabCount === tabCount) {
5596
- consecutiveTableLines++;
5597
- j++;
5598
- } else {
5599
- break;
5600
- }
6051
+ let j = i;
6052
+ while (j < lines.length && (lines[j].match(/\t/g) || []).length > 0) {
6053
+ j++;
5601
6054
  }
5602
- if (consecutiveTableLines >= 3) {
5603
- if (inList) {
5604
- html += "</ul>";
5605
- inList = false;
5606
- }
5607
- tableLines = lines.slice(i, j);
5608
- flushTable();
5609
- i = j;
5610
- continue;
6055
+ if (inList) {
6056
+ html += "</ul>";
6057
+ inList = false;
5611
6058
  }
6059
+ tableLines = lines.slice(i, j);
6060
+ flushTable();
6061
+ i = j;
6062
+ continue;
5612
6063
  }
5613
6064
  line = line.trim();
5614
6065
  if (!line) {
@@ -5628,25 +6079,26 @@ var DocPlugin = class {
5628
6079
  i++;
5629
6080
  continue;
5630
6081
  }
6082
+ const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
5631
6083
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
5632
6084
  if (inList) {
5633
6085
  html += "</ul>";
5634
6086
  inList = false;
5635
6087
  }
5636
- 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>`;
6088
+ 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>`;
5637
6089
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
5638
6090
  if (!inList) {
5639
6091
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
5640
6092
  inList = true;
5641
6093
  }
5642
6094
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
5643
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText)}</li>`;
6095
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
5644
6096
  } else {
5645
6097
  if (inList) {
5646
6098
  html += "</ul>";
5647
6099
  inList = false;
5648
6100
  }
5649
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line)}</p>`;
6101
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
5650
6102
  }
5651
6103
  i++;
5652
6104
  }