@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/angular.cjs CHANGED
@@ -18,6 +18,7 @@ var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
18
18
  var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
19
19
  var RTFJS = require('rtf.js/dist/RTFJS.bundle.js');
20
20
 
21
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
21
22
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
22
23
 
23
24
  function _interopNamespace(e) {
@@ -417,16 +418,21 @@ async function sourceToArrayBuffer(source, signal) {
417
418
  metadata.mimeType = source.type || void 0;
418
419
  metadata.extension = extractExtension(source.name);
419
420
  buffer = await source.arrayBuffer();
420
- } else if (source instanceof Blob) {
421
+ } else if (source instanceof Blob || source && typeof source.arrayBuffer === "function" && typeof source.size === "number") {
421
422
  metadata.size = source.size;
422
423
  metadata.mimeType = source.type || void 0;
424
+ if (source.name) {
425
+ metadata.name = source.name;
426
+ metadata.extension = extractExtension(source.name);
427
+ }
423
428
  buffer = await source.arrayBuffer();
424
- } else if (source instanceof ArrayBuffer) {
429
+ } else if (source instanceof ArrayBuffer || Object.prototype.toString.call(source) === "[object ArrayBuffer]" || source && typeof source.byteLength === "number" && typeof source.slice === "function") {
425
430
  buffer = source;
426
- } else if (source instanceof Uint8Array) {
427
- buffer = source.buffer.slice(
428
- source.byteOffset,
429
- source.byteOffset + source.byteLength
431
+ } else if (source instanceof Uint8Array || ArrayBuffer.isView(source)) {
432
+ const view = source;
433
+ buffer = view.buffer.slice(
434
+ view.byteOffset,
435
+ view.byteOffset + view.byteLength
430
436
  );
431
437
  } else {
432
438
  throw new Error("Unsupported file source type");
@@ -511,6 +517,45 @@ function createElement(tag, attrs, ...children) {
511
517
  }
512
518
  return el;
513
519
  }
520
+ var DB_NAME = "PreviewFileTransferDB";
521
+ var DB_STORE = "transfers";
522
+ function openDB() {
523
+ return new Promise((resolve, reject) => {
524
+ if (typeof indexedDB === "undefined") {
525
+ return reject(new Error("IndexedDB is not available"));
526
+ }
527
+ const req = indexedDB.open(DB_NAME, 1);
528
+ req.onupgradeneeded = () => {
529
+ const db = req.result;
530
+ if (!db.objectStoreNames.contains(DB_STORE)) {
531
+ db.createObjectStore(DB_STORE, { keyPath: "id" });
532
+ }
533
+ };
534
+ req.onsuccess = () => resolve(req.result);
535
+ req.onerror = () => reject(req.error);
536
+ });
537
+ }
538
+ async function saveTransferPayload(id, payload) {
539
+ if (typeof window !== "undefined") {
540
+ try {
541
+ window[id] = payload;
542
+ window.__lastTransfer = payload;
543
+ } catch {
544
+ }
545
+ }
546
+ try {
547
+ const db = await openDB();
548
+ return new Promise((resolve, reject) => {
549
+ const tx = db.transaction(DB_STORE, "readwrite");
550
+ const store = tx.objectStore(DB_STORE);
551
+ store.put({ id, ...payload, timestamp: Date.now() });
552
+ tx.oncomplete = () => resolve();
553
+ tx.onerror = () => reject(tx.error);
554
+ });
555
+ } catch (e) {
556
+ console.warn("[saveTransferPayload] IndexedDB store warning:", e);
557
+ }
558
+ }
514
559
  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>`;
515
560
  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>`;
516
561
  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>`;
@@ -530,6 +575,7 @@ var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" str
530
575
  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>`;
531
576
  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>`;
532
577
  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>`;
578
+ 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>`;
533
579
  var ICON_MAP = {
534
580
  "zoom-in": ICON_ZOOM_IN,
535
581
  "zoom-out": ICON_ZOOM_OUT,
@@ -556,7 +602,9 @@ var ICON_MAP = {
556
602
  "forward-10": ICON_FAST_FORWARD,
557
603
  "rewind": ICON_REWIND,
558
604
  "replay-10": ICON_REWIND,
559
- "speed": ICON_SPEED
605
+ "speed": ICON_SPEED,
606
+ "open-window": ICON_EXTERNAL_WINDOW,
607
+ "external-window": ICON_EXTERNAL_WINDOW
560
608
  };
561
609
  var ToolbarController = class {
562
610
  el;
@@ -773,7 +821,7 @@ var ThumbnailPanel = class {
773
821
  }
774
822
  }
775
823
  };
776
- var FilePreviewViewer = class {
824
+ var FilePreviewViewer = class _FilePreviewViewer {
777
825
  plugins = [];
778
826
  activeInstance = null;
779
827
  abortController = null;
@@ -810,6 +858,7 @@ var FilePreviewViewer = class {
810
858
  * Preview a file in the given container element.
811
859
  */
812
860
  async preview(container, source, options = {}) {
861
+ this.currentOptions = options;
813
862
  this.abort();
814
863
  this.abortController = new AbortController();
815
864
  const { signal } = this.abortController;
@@ -819,7 +868,10 @@ var FilePreviewViewer = class {
819
868
  this.showLoading();
820
869
  try {
821
870
  const { buffer, metadata } = await sourceToArrayBuffer(source, signal);
822
- this.currentBuffer = buffer;
871
+ if (options.metadata) {
872
+ Object.assign(metadata, options.metadata);
873
+ }
874
+ this.currentBuffer = buffer.slice(0);
823
875
  this.currentMetadata = metadata;
824
876
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
825
877
  const fileInfo = { metadata, buffer };
@@ -849,6 +901,7 @@ var FilePreviewViewer = class {
849
901
  }
850
902
  });
851
903
  this.activeInstance = instance;
904
+ instance.openInSeparateWindow = () => this.openInSeparateWindow();
852
905
  this.hideLoading();
853
906
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
854
907
  if (options.showToolbar !== false && this.toolbar) {
@@ -858,26 +911,16 @@ var FilePreviewViewer = class {
858
911
  actions.push({
859
912
  id: "fullscreen",
860
913
  icon: "fullscreen",
861
- label: "Toggle Fullscreen",
914
+ label: "Fullscreen",
862
915
  type: "button",
863
916
  group: "view",
864
- execute: async () => {
917
+ execute: () => {
865
918
  try {
866
- const isNativeFs = !!document.fullscreenElement;
867
- const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
868
- if (!isNativeFs && !isCssFs) {
869
- if (this.wrapperEl?.requestFullscreen) {
870
- await this.wrapperEl.requestFullscreen().catch(() => {
871
- this.wrapperEl?.classList.add("fp-fullscreen-active");
872
- });
873
- } else {
874
- this.wrapperEl?.classList.add("fp-fullscreen-active");
875
- }
919
+ if (!document.fullscreenElement) {
920
+ this.wrapperEl?.requestFullscreen?.();
921
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
876
922
  } else {
877
- if (document.fullscreenElement) {
878
- await document.exitFullscreen().catch(() => {
879
- });
880
- }
923
+ document.exitFullscreen?.();
881
924
  this.wrapperEl?.classList.remove("fp-fullscreen-active");
882
925
  }
883
926
  } catch {
@@ -889,6 +932,28 @@ var FilePreviewViewer = class {
889
932
  }
890
933
  });
891
934
  }
935
+ const openWinAction = actions.find((a) => a.id === "open-window");
936
+ if (openWinAction) {
937
+ if (options?._isSeparateWindow) {
938
+ const idx = actions.indexOf(openWinAction);
939
+ if (idx !== -1) actions.splice(idx, 1);
940
+ } else {
941
+ openWinAction.execute = () => {
942
+ this.openInSeparateWindow();
943
+ };
944
+ }
945
+ } else if (!options?._isSeparateWindow) {
946
+ actions.push({
947
+ id: "open-window",
948
+ icon: "open-window",
949
+ label: "Open in Separate Full Window",
950
+ type: "button",
951
+ group: "actions",
952
+ execute: () => {
953
+ this.openInSeparateWindow();
954
+ }
955
+ });
956
+ }
892
957
  this.toolbar.update(actions);
893
958
  this.toolbar.show();
894
959
  }
@@ -921,6 +986,104 @@ var FilePreviewViewer = class {
921
986
  throw error;
922
987
  }
923
988
  }
989
+ /**
990
+ * Opens the current file preview in a separate full browser window.
991
+ */
992
+ openInSeparateWindow() {
993
+ if (!this.currentBuffer) {
994
+ console.warn("[FilePreviewViewer] No active file buffer to open in separate window");
995
+ return null;
996
+ }
997
+ if (this.currentOptions.onOpenSeparateWindow) {
998
+ return this.currentOptions.onOpenSeparateWindow({
999
+ buffer: this.currentBuffer,
1000
+ metadata: this.currentMetadata || { name: "Document" },
1001
+ options: this.currentOptions
1002
+ });
1003
+ }
1004
+ const transferId = "fp_win_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
1005
+ let clonedBuffer;
1006
+ try {
1007
+ clonedBuffer = this.currentBuffer.slice(0);
1008
+ } catch {
1009
+ clonedBuffer = this.currentBuffer;
1010
+ }
1011
+ const payload = {
1012
+ buffer: clonedBuffer,
1013
+ metadata: this.currentMetadata ? { ...this.currentMetadata } : void 0,
1014
+ options: { ...this.currentOptions, _isSeparateWindow: true }
1015
+ };
1016
+ if (typeof window !== "undefined") {
1017
+ try {
1018
+ window[transferId] = payload;
1019
+ window.__lastTransfer = payload;
1020
+ } catch {
1021
+ }
1022
+ }
1023
+ saveTransferPayload(transferId, payload).catch((err) => {
1024
+ console.warn("[FilePreviewViewer] Transfer payload save warning:", err);
1025
+ });
1026
+ let targetUrl = null;
1027
+ if (this.currentOptions.standaloneViewerUrl) {
1028
+ const u = new URL(this.currentOptions.standaloneViewerUrl, window.location.href);
1029
+ u.searchParams.set("mode", "fullscreen");
1030
+ u.searchParams.set("transferId", transferId);
1031
+ targetUrl = u.toString();
1032
+ } else if (typeof window !== "undefined" && window.location?.href && !window.location.href.startsWith("about:")) {
1033
+ const u = new URL(window.location.href);
1034
+ u.searchParams.set("mode", "fullscreen");
1035
+ u.searchParams.set("transferId", transferId);
1036
+ targetUrl = u.toString();
1037
+ }
1038
+ if (targetUrl) {
1039
+ const newWin2 = window.open(targetUrl, "_blank");
1040
+ if (!newWin2) {
1041
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1042
+ return null;
1043
+ }
1044
+ return newWin2;
1045
+ }
1046
+ const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
1047
+ const newWin = window.open("", "_blank");
1048
+ if (!newWin) {
1049
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1050
+ return null;
1051
+ }
1052
+ newWin.document.title = title;
1053
+ newWin.document.body.style.margin = "0";
1054
+ newWin.document.body.style.padding = "0";
1055
+ newWin.document.body.style.width = "100vw";
1056
+ newWin.document.body.style.height = "100vh";
1057
+ newWin.document.body.style.overflow = "hidden";
1058
+ newWin.document.body.style.backgroundColor = "#f8fafc";
1059
+ const headNodes = document.querySelectorAll('link[rel="stylesheet"], style');
1060
+ headNodes.forEach((node) => {
1061
+ newWin.document.head.appendChild(node.cloneNode(true));
1062
+ });
1063
+ const root = newWin.document.createElement("div");
1064
+ root.id = "full-window-preview-root";
1065
+ root.style.width = "100%";
1066
+ root.style.height = "100%";
1067
+ root.style.overflow = "hidden";
1068
+ newWin.document.body.appendChild(root);
1069
+ const separateViewer = new _FilePreviewViewer();
1070
+ for (const plugin of this.plugins) {
1071
+ separateViewer.registerPlugin(plugin);
1072
+ }
1073
+ separateViewer.preview(root, this.currentBuffer.slice(0), {
1074
+ ...this.currentOptions,
1075
+ showToolbar: true,
1076
+ toolbarPosition: "top",
1077
+ metadata: this.currentMetadata || void 0,
1078
+ _isSeparateWindow: true
1079
+ }).catch((err) => {
1080
+ console.error("[FilePreviewViewer] Error rendering in separate window:", err);
1081
+ });
1082
+ newWin.addEventListener("beforeunload", () => {
1083
+ separateViewer.destroy();
1084
+ });
1085
+ return newWin;
1086
+ }
924
1087
  /**
925
1088
  * Subscribe to viewer events.
926
1089
  */
@@ -1326,8 +1489,20 @@ var CfbfReader = class {
1326
1489
  }
1327
1490
  };
1328
1491
  if (typeof window !== "undefined" && pdfjsLib__namespace.GlobalWorkerOptions) {
1329
- if (!pdfjsLib__namespace.GlobalWorkerOptions.workerSrc) {
1330
- pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1492
+ if (!pdfjsLib__namespace.GlobalWorkerOptions.workerPort && !pdfjsLib__namespace.GlobalWorkerOptions.workerSrc) {
1493
+ const customWorker = window.__PDF_WORKER_SRC__;
1494
+ if (customWorker) {
1495
+ pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = customWorker;
1496
+ } else {
1497
+ try {
1498
+ pdfjsLib__namespace.GlobalWorkerOptions.workerPort = new Worker(
1499
+ 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('angular.cjs', document.baseURI).href))),
1500
+ { type: "module" }
1501
+ );
1502
+ } catch {
1503
+ pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.mjs";
1504
+ }
1505
+ }
1331
1506
  }
1332
1507
  }
1333
1508
  var PdfPlugin = class {
@@ -1421,6 +1596,14 @@ var PdfPlugin = class {
1421
1596
  type: "button",
1422
1597
  group: "actions",
1423
1598
  execute: () => instance.print?.()
1599
+ },
1600
+ {
1601
+ id: "open-window",
1602
+ icon: "open-window",
1603
+ label: "Open in Separate Full Window",
1604
+ type: "button",
1605
+ group: "actions",
1606
+ execute: () => instance.openInSeparateWindow?.()
1424
1607
  }
1425
1608
  ];
1426
1609
  }
@@ -1470,18 +1653,21 @@ var PdfPlugin = class {
1470
1653
  indicator.style.pointerEvents = "none";
1471
1654
  container.appendChild(indicator);
1472
1655
  ctx.container.appendChild(container);
1656
+ const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1657
+ const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
1473
1658
  const loadingTask = pdfjsLib__namespace.getDocument({
1474
- data: new Uint8Array(ctx.buffer),
1475
- cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/cmaps/`,
1659
+ data: new Uint8Array(ctx.buffer.slice(0)),
1660
+ cMapUrl: cmapsUrl,
1476
1661
  cMapPacked: true,
1477
- standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/standard_fonts/`
1662
+ standardFontDataUrl: standardFontsUrl,
1663
+ verbosity: 0
1478
1664
  });
1479
1665
  const pdfDoc = await loadingTask.promise;
1480
1666
  const totalPages = Math.max(1, pdfDoc.numPages);
1481
1667
  let currentPage = 1;
1482
1668
  let zoomScale = 1;
1483
1669
  let rotation = 0;
1484
- let fitMode = "width";
1670
+ let fitMode = "page";
1485
1671
  let currentRenderTask = null;
1486
1672
  const renderPage = async (pageNum) => {
1487
1673
  if (currentRenderTask) {
@@ -1501,15 +1687,15 @@ var PdfPlugin = class {
1501
1687
  const containerWidth = container.clientWidth || 900;
1502
1688
  const containerHeight = container.clientHeight || 700;
1503
1689
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1504
- const availWidth = Math.max(280, containerWidth - 48);
1505
- const availHeight = Math.max(280, containerHeight - 88);
1690
+ const availWidth = Math.max(320, containerWidth - 48);
1691
+ const availHeight = Math.max(550, containerHeight - 88);
1506
1692
  const scaleW = availWidth / unscaledVp.width;
1507
1693
  const scaleH = availHeight / unscaledVp.height;
1508
1694
  let fitScale;
1509
1695
  if (fitMode === "page") {
1510
- fitScale = Math.max(0.5, Math.min(scaleW, scaleH));
1696
+ fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
1511
1697
  } else {
1512
- fitScale = Math.max(0.65, Math.min(1.15, scaleW));
1698
+ fitScale = Math.max(0.65, Math.min(1.25, scaleW));
1513
1699
  }
1514
1700
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1515
1701
  const pixelRatio = window.devicePixelRatio || 1;
@@ -1535,7 +1721,7 @@ var PdfPlugin = class {
1535
1721
  currentRenderTask = null;
1536
1722
  }
1537
1723
  };
1538
- await renderPage(1);
1724
+ renderPage(1);
1539
1725
  let resizeTimer = null;
1540
1726
  const resizeObserver = new ResizeObserver(() => {
1541
1727
  if (resizeTimer) clearTimeout(resizeTimer);
@@ -1579,7 +1765,7 @@ var PdfPlugin = class {
1579
1765
  renderPage(currentPage);
1580
1766
  },
1581
1767
  fitToPage: () => {
1582
- fitMode = fitMode === "width" ? "page" : "width";
1768
+ fitMode = fitMode === "page" ? "width" : "page";
1583
1769
  zoomScale = 1;
1584
1770
  rotation = 0;
1585
1771
  renderPage(currentPage);
@@ -1596,6 +1782,7 @@ var PdfPlugin = class {
1596
1782
  getPageCount: () => totalPages,
1597
1783
  getCurrentPage: () => currentPage,
1598
1784
  goToPage: (page) => {
1785
+ container.scrollTop = 0;
1599
1786
  renderPage(page);
1600
1787
  },
1601
1788
  download: () => {
@@ -2055,6 +2242,14 @@ var DocxPlugin = class {
2055
2242
  type: "button",
2056
2243
  group: "actions",
2057
2244
  execute: () => instance.print?.()
2245
+ },
2246
+ {
2247
+ id: "open-window",
2248
+ icon: "open-window",
2249
+ label: "Open in Separate Full Window",
2250
+ type: "button",
2251
+ group: "actions",
2252
+ execute: () => instance.openInSeparateWindow?.()
2058
2253
  }
2059
2254
  );
2060
2255
  return actions;
@@ -2100,13 +2295,45 @@ var DocxPlugin = class {
2100
2295
  ignoreFonts: true,
2101
2296
  // Avoid crashes on embedded obfuscated fonts
2102
2297
  breakPages: true,
2103
- experimental: true
2298
+ experimental: true,
2299
+ ignoreLastRenderedPageBreak: false,
2300
+ // Honor Word's exact page breaks!
2301
+ renderHeaders: true,
2302
+ renderFooters: true,
2303
+ renderFootnotes: true,
2304
+ renderEndnotes: true,
2305
+ useBase64URL: true
2104
2306
  });
2105
2307
  if (!ctx.container.contains(wrapper)) {
2106
2308
  ctx.container.appendChild(wrapper);
2107
2309
  }
2108
2310
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2109
2311
  renderedSuccessfully = true;
2312
+ try {
2313
+ const unzipped = fflate.unzipSync(new Uint8Array(ctx.buffer));
2314
+ const chartKeys = Object.keys(unzipped).filter((k) => k.replace(/^[./\\]+/, "").toLowerCase().startsWith("word/charts/chart") && k.endsWith(".xml")).sort();
2315
+ if (chartKeys.length > 0) {
2316
+ const allDivs = Array.from(wrapper.querySelectorAll("div"));
2317
+ const emptyContainers = allDivs.filter((div) => {
2318
+ const st = div.getAttribute("style") || "";
2319
+ return st.includes("width:") && st.includes("height:") && div.children.length === 0 && (div.textContent?.trim().length ?? 0) === 0;
2320
+ });
2321
+ chartKeys.forEach((cKey, idx) => {
2322
+ const target = emptyContainers[idx];
2323
+ if (target) {
2324
+ const xmlStr = fflate.strFromU8(unzipped[cKey]);
2325
+ const svg = this.parseAndRenderChartSvg(xmlStr);
2326
+ if (svg) {
2327
+ target.innerHTML = svg;
2328
+ target.style.display = "block";
2329
+ target.style.margin = "12px auto";
2330
+ }
2331
+ }
2332
+ });
2333
+ }
2334
+ } catch (chartErr) {
2335
+ console.warn("[DocxPlugin] Non-critical error rendering DrawingML charts:", chartErr);
2336
+ }
2110
2337
  }
2111
2338
  } catch (err) {
2112
2339
  console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
@@ -2121,11 +2348,14 @@ var DocxPlugin = class {
2121
2348
  renderedSuccessfully = true;
2122
2349
  } catch (fallbackErr) {
2123
2350
  console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
2351
+ const isCorrupt = fallbackErr?.message?.includes("invalid zip") || fallbackErr?.message?.includes("corrupted");
2124
2352
  wrapper.innerHTML = `
2125
- <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
2126
- <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
2127
- <h3 style="margin: 0 0 8px; color: #1e293b;">${ctx.metadata.name || "Word Document"}</h3>
2128
- <p style="color: #64748b; margin: 0;">Could not parse document content</p>
2353
+ <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;">
2354
+ <div style="font-size:48px; margin-bottom: 16px;">${isCorrupt ? "\u26A0\uFE0F" : "\u{1F4C4}"}</div>
2355
+ <h3 style="margin: 0 0 8px; color: #1e293b; font-size: 18px;">${ctx.metadata.name || "Word Document"}</h3>
2356
+ <p style="color: #64748b; margin: 0 0 16px; font-size: 14px; line-height: 1.5;">
2357
+ ${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."}
2358
+ </p>
2129
2359
  </div>
2130
2360
  `;
2131
2361
  if (!ctx.container.contains(wrapper)) {
@@ -2135,50 +2365,74 @@ var DocxPlugin = class {
2135
2365
  }
2136
2366
  let sections = Array.from(wrapper.querySelectorAll("section.docx"));
2137
2367
  const cards = Array.from(wrapper.querySelectorAll(".fp-docx-page-card"));
2138
- if (sections.length === 1 && cards.length === 0) {
2139
- const singleSec = sections[0];
2140
- const pageH = 1056;
2141
- const secH = singleSec.offsetHeight || singleSec.scrollHeight;
2142
- if (secH > pageH * 1.25) {
2143
- const children = Array.from(singleSec.children);
2144
- if (children.length > 1) {
2368
+ if (sections.length > 0 && cards.length === 0) {
2369
+ const finalSections = [];
2370
+ for (const singleSec of sections) {
2371
+ const contentContainer = singleSec.querySelector("article") || singleSec;
2372
+ const children = Array.from(contentContainer.children);
2373
+ const pageH = singleSec.offsetHeight > 1300 ? 1122 : Math.max(1056, singleSec.offsetHeight);
2374
+ const secH = singleSec.scrollHeight || singleSec.offsetHeight;
2375
+ if (secH > pageH * 1.25 && children.length > 1) {
2376
+ const childHeights = children.map((c) => {
2377
+ const rectH = c.getBoundingClientRect().height;
2378
+ const offH = c.offsetHeight;
2379
+ const textLen = c.textContent?.trim().length || 0;
2380
+ const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
2381
+ return Math.max(rectH, offH, estH);
2382
+ });
2145
2383
  const parent = singleSec.parentElement || wrapper;
2146
- const newSections = [singleSec];
2147
- singleSec.innerHTML = "";
2384
+ const headerEl = singleSec.querySelector("header");
2385
+ const footerEl = singleSec.querySelector("footer");
2386
+ contentContainer.innerHTML = "";
2148
2387
  singleSec.style.minHeight = `${pageH}px`;
2149
- singleSec.style.maxHeight = `${pageH}px`;
2150
- singleSec.style.overflow = "hidden";
2151
2388
  singleSec.style.boxSizing = "border-box";
2389
+ let curContent = contentContainer;
2152
2390
  let curSec = singleSec;
2153
2391
  let curH = 0;
2154
- const maxH = pageH - 96;
2392
+ const maxH = pageH - 140;
2393
+ finalSections.push(singleSec);
2155
2394
  for (let i = 0; i < children.length; i++) {
2156
2395
  const child = children[i];
2157
- curSec.appendChild(child);
2158
- const chH = child.offsetHeight || 28;
2396
+ const chH = childHeights[i];
2397
+ curContent.appendChild(child);
2159
2398
  curH += chH;
2160
2399
  if (curH >= maxH && i < children.length - 1) {
2161
2400
  const nextSec = document.createElement("section");
2162
2401
  nextSec.className = singleSec.className;
2163
2402
  nextSec.style.cssText = singleSec.style.cssText;
2164
- nextSec.style.width = singleSec.style.width || "816px";
2165
2403
  nextSec.style.minHeight = `${pageH}px`;
2166
- nextSec.style.maxHeight = `${pageH}px`;
2167
- nextSec.style.overflow = "hidden";
2168
2404
  nextSec.style.boxSizing = "border-box";
2169
2405
  nextSec.style.backgroundColor = "#ffffff";
2170
2406
  nextSec.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
2171
2407
  nextSec.style.borderRadius = "4px";
2172
2408
  nextSec.style.marginBottom = "24px";
2173
- parent.appendChild(nextSec);
2174
- newSections.push(nextSec);
2409
+ if (headerEl) {
2410
+ nextSec.appendChild(headerEl.cloneNode(true));
2411
+ }
2412
+ const nextArticle = document.createElement("article");
2413
+ if (contentContainer.tagName.toLowerCase() === "article") {
2414
+ nextArticle.style.cssText = contentContainer.style.cssText;
2415
+ }
2416
+ nextSec.appendChild(nextArticle);
2417
+ if (footerEl) {
2418
+ nextSec.appendChild(footerEl.cloneNode(true));
2419
+ }
2420
+ if (curSec.nextSibling) {
2421
+ parent.insertBefore(nextSec, curSec.nextSibling);
2422
+ } else {
2423
+ parent.appendChild(nextSec);
2424
+ }
2425
+ finalSections.push(nextSec);
2175
2426
  curSec = nextSec;
2427
+ curContent = nextArticle;
2176
2428
  curH = 0;
2177
2429
  }
2178
2430
  }
2179
- sections = newSections;
2431
+ } else {
2432
+ finalSections.push(singleSec);
2180
2433
  }
2181
2434
  }
2435
+ sections = finalSections;
2182
2436
  }
2183
2437
  const pageElements = sections.length > 0 ? sections : cards;
2184
2438
  const totalPages = Math.max(1, pageElements.length);
@@ -2216,6 +2470,7 @@ var DocxPlugin = class {
2216
2470
  if (indicator) {
2217
2471
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2218
2472
  }
2473
+ ctx.container.scrollTop = 0;
2219
2474
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2220
2475
  };
2221
2476
  if (totalPages > 1) {
@@ -2569,6 +2824,88 @@ var DocxPlugin = class {
2569
2824
  }
2570
2825
  return result;
2571
2826
  }
2827
+ parseAndRenderChartSvg(xmlStr, width = 500, height = 260) {
2828
+ const catMatches = [...xmlStr.matchAll(/<c:cat>[\s\S]*?<c:strCache>([\s\S]*?)<\/c:strCache>/g)];
2829
+ let categories = [];
2830
+ if (catMatches.length > 0) {
2831
+ categories = [...catMatches[0][1].matchAll(/<c:v>([^<]+)<\/c:v>/g)].map((m) => m[1]);
2832
+ }
2833
+ if (categories.length === 0) {
2834
+ categories = ["Category 1", "Category 2", "Category 3", "Category 4"];
2835
+ }
2836
+ const defaultColors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021", "#83caff"];
2837
+ const sers = [...xmlStr.matchAll(/<c:ser>([\s\S]*?)<\/c:ser>/g)];
2838
+ const series = [];
2839
+ sers.forEach((s, sIdx) => {
2840
+ const titleMatch = s[1].match(/<c:tx>[\s\S]*?<c:v>([^<]+)<\/c:v>/);
2841
+ const title = titleMatch ? titleMatch[1] : `Series ${sIdx + 1}`;
2842
+ const clrMatch = s[1].match(/<a:srgbClr\s+val="([^"]+)"/);
2843
+ const color = clrMatch ? "#" + clrMatch[1] : defaultColors[sIdx % defaultColors.length];
2844
+ const valMatch = s[1].match(/<c:val>[\s\S]*?<c:numCache>([\s\S]*?)<\/c:numCache>/);
2845
+ let values = [];
2846
+ if (valMatch) {
2847
+ 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);
2848
+ }
2849
+ series.push({ title, color, values });
2850
+ });
2851
+ if (series.length === 0) return "";
2852
+ let maxVal = 10;
2853
+ series.forEach((s) => s.values.forEach((v) => {
2854
+ if (v > maxVal) maxVal = v;
2855
+ }));
2856
+ maxVal = Math.ceil(maxVal * 1.15);
2857
+ if (maxVal % 2 !== 0) maxVal++;
2858
+ const padLeft = 45;
2859
+ const padBottom = 55;
2860
+ const padTop = 20;
2861
+ const padRight = 20;
2862
+ const plotW = width - padLeft - padRight;
2863
+ const plotH = height - padTop - padBottom;
2864
+ const yTicks = 5;
2865
+ let gridLines = "";
2866
+ for (let i = 0; i <= yTicks; i++) {
2867
+ const val = maxVal / yTicks * i;
2868
+ const y = padTop + plotH - val / maxVal * plotH;
2869
+ gridLines += `<line x1="${padLeft}" y1="${y}" x2="${padLeft + plotW}" y2="${y}" stroke="#e2e8f0" stroke-width="1" />`;
2870
+ 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>`;
2871
+ }
2872
+ const numCats = categories.length;
2873
+ const numSers = series.length;
2874
+ const groupW = plotW / numCats;
2875
+ const barW = Math.max(8, Math.min(28, groupW * 0.7 / numSers));
2876
+ const groupPad = (groupW - barW * numSers) / 2;
2877
+ let bars = "";
2878
+ let catLabels = "";
2879
+ for (let c = 0; c < numCats; c++) {
2880
+ const catX = padLeft + c * groupW;
2881
+ 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>`;
2882
+ for (let s = 0; s < numSers; s++) {
2883
+ const val = series[s].values[c] ?? 0;
2884
+ const bH = Math.max(0, val / maxVal * plotH);
2885
+ const bX = catX + groupPad + s * barW;
2886
+ const bY = padTop + plotH - bH;
2887
+ bars += `<rect x="${bX}" y="${bY}" width="${barW - 2}" height="${bH}" fill="${series[s].color}" rx="1" />`;
2888
+ }
2889
+ }
2890
+ let legend = "";
2891
+ const legY = height - 12;
2892
+ let legX = padLeft + (plotW - numSers * 100) / 2;
2893
+ series.forEach((s) => {
2894
+ legend += `<rect x="${legX}" y="${legY - 9}" width="10" height="10" fill="${s.color}" rx="2" />`;
2895
+ legend += `<text x="${legX + 15}" y="${legY}" font-size="11" fill="#475569" font-family="Calibri, sans-serif">${s.title}</text>`;
2896
+ legX += 95;
2897
+ });
2898
+ return `
2899
+ <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">
2900
+ ${gridLines}
2901
+ <line x1="${padLeft}" y1="${padTop + plotH}" x2="${padLeft + plotW}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2902
+ <line x1="${padLeft}" y1="${padTop}" x2="${padLeft}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2903
+ ${bars}
2904
+ ${catLabels}
2905
+ ${legend}
2906
+ </svg>
2907
+ `.trim();
2908
+ }
2572
2909
  };
2573
2910
  function docxPlugin() {
2574
2911
  return new DocxPlugin();
@@ -3135,6 +3472,16 @@ var CodePlugin = class {
3135
3472
  execute: () => {
3136
3473
  instance.print?.();
3137
3474
  }
3475
+ },
3476
+ {
3477
+ id: "open-window",
3478
+ icon: "open-window",
3479
+ label: "Open in Separate Full Window",
3480
+ type: "button",
3481
+ group: "actions",
3482
+ execute: () => {
3483
+ instance.openInSeparateWindow?.();
3484
+ }
3138
3485
  }
3139
3486
  );
3140
3487
  return actions;
@@ -3148,17 +3495,39 @@ var CodePlugin = class {
3148
3495
  let rawPages = [];
3149
3496
  if (isTxt) {
3150
3497
  const explicitPages = fullText.split(/(?:\f|\x0C)/);
3498
+ const charsPerLine = 85;
3499
+ const maxVisualLines = 45;
3500
+ const charsPerPage = charsPerLine * maxVisualLines;
3151
3501
  for (const ep of explicitPages) {
3152
3502
  const lines = ep.split(/\r?\n/);
3153
3503
  let currentChunk = [];
3504
+ let currentLines = 0;
3154
3505
  for (let i = 0; i < lines.length; i++) {
3155
- currentChunk.push(lines[i]);
3156
- if (currentChunk.length >= 46) {
3506
+ const line = lines[i];
3507
+ const vLines = Math.max(1, Math.ceil((line.length || 1) / charsPerLine));
3508
+ if (currentLines + vLines > maxVisualLines && currentChunk.length > 0) {
3157
3509
  rawPages.push(currentChunk.join("\n"));
3158
3510
  currentChunk = [];
3511
+ currentLines = 0;
3512
+ }
3513
+ if (vLines > maxVisualLines) {
3514
+ let remaining = line;
3515
+ while (remaining.length > charsPerPage) {
3516
+ let splitIdx = remaining.lastIndexOf(" ", charsPerPage);
3517
+ if (splitIdx < charsPerPage * 0.75) splitIdx = charsPerPage;
3518
+ rawPages.push(remaining.slice(0, splitIdx));
3519
+ remaining = remaining.slice(splitIdx).trimStart();
3520
+ }
3521
+ if (remaining.length > 0) {
3522
+ currentChunk.push(remaining);
3523
+ currentLines = Math.ceil(remaining.length / charsPerLine);
3524
+ }
3525
+ } else {
3526
+ currentChunk.push(line);
3527
+ currentLines += vLines;
3159
3528
  }
3160
3529
  }
3161
- if (currentChunk.length > 0 || lines.length === 0) {
3530
+ if (currentChunk.length > 0) {
3162
3531
  rawPages.push(currentChunk.join("\n"));
3163
3532
  }
3164
3533
  }
@@ -3268,6 +3637,7 @@ var CodePlugin = class {
3268
3637
  if (indicator) {
3269
3638
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3270
3639
  }
3640
+ container.scrollTop = 0;
3271
3641
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3272
3642
  };
3273
3643
  if (totalPages > 1) {
@@ -4280,6 +4650,14 @@ var RtfPlugin = class {
4280
4650
  type: "button",
4281
4651
  group: "actions",
4282
4652
  execute: () => instance.print?.()
4653
+ },
4654
+ {
4655
+ id: "open-window",
4656
+ icon: "open-window",
4657
+ label: "Open in Separate Full Window",
4658
+ type: "button",
4659
+ group: "actions",
4660
+ execute: () => instance.openInSeparateWindow?.()
4283
4661
  }
4284
4662
  );
4285
4663
  return actions;
@@ -4332,11 +4710,55 @@ var RtfPlugin = class {
4332
4710
  }
4333
4711
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
4334
4712
  const htmlElements = await doc.render();
4335
- pageElements = htmlElements;
4336
- for (let i = 0; i < htmlElements.length; i++) {
4337
- const el = htmlElements[i];
4338
- el.style.display = i === 0 ? "block" : "none";
4339
- wrapper.appendChild(el);
4713
+ const contentNodes = [];
4714
+ for (const item of htmlElements) {
4715
+ if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4716
+ contentNodes.push(...Array.from(item.children));
4717
+ } else {
4718
+ contentNodes.push(item);
4719
+ }
4720
+ }
4721
+ wrapper.innerHTML = "";
4722
+ contentNodes.forEach((node) => wrapper.appendChild(node));
4723
+ const childHeights = contentNodes.map((c) => {
4724
+ const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4725
+ const offH = c.offsetHeight || 0;
4726
+ const textLen = c.textContent?.trim().length || 0;
4727
+ const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
4728
+ return Math.max(rectH, offH, estH);
4729
+ });
4730
+ wrapper.innerHTML = "";
4731
+ const createRtfCard = () => {
4732
+ const card = document.createElement("div");
4733
+ card.className = "fp-rtf-page-card";
4734
+ card.style.backgroundColor = "#ffffff";
4735
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4736
+ card.style.borderRadius = "4px";
4737
+ card.style.padding = "72px 56px";
4738
+ card.style.width = "816px";
4739
+ card.style.minHeight = "1056px";
4740
+ card.style.boxSizing = "border-box";
4741
+ card.style.marginBottom = "24px";
4742
+ card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4743
+ card.style.lineHeight = "1.6";
4744
+ return card;
4745
+ };
4746
+ let curCard = createRtfCard();
4747
+ wrapper.appendChild(curCard);
4748
+ pageElements = [curCard];
4749
+ let curH = 0;
4750
+ const maxH = 912;
4751
+ for (let i = 0; i < contentNodes.length; i++) {
4752
+ const child = contentNodes[i];
4753
+ const chH = childHeights[i];
4754
+ curCard.appendChild(child);
4755
+ curH += chH;
4756
+ if (curH >= maxH && i < contentNodes.length - 1) {
4757
+ curCard = createRtfCard();
4758
+ wrapper.appendChild(curCard);
4759
+ pageElements.push(curCard);
4760
+ curH = 0;
4761
+ }
4340
4762
  }
4341
4763
  } catch (err) {
4342
4764
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
@@ -4402,6 +4824,7 @@ var RtfPlugin = class {
4402
4824
  if (indicator) {
4403
4825
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4404
4826
  }
4827
+ ctx.container.scrollTop = 0;
4405
4828
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4406
4829
  };
4407
4830
  if (totalPages > 1) {
@@ -4703,6 +5126,14 @@ var OpenDocumentPlugin = class {
4703
5126
  type: "button",
4704
5127
  group: "actions",
4705
5128
  execute: () => instance.print?.()
5129
+ },
5130
+ {
5131
+ id: "open-window",
5132
+ icon: "open-window",
5133
+ label: "Open in Separate Full Window",
5134
+ type: "button",
5135
+ group: "actions",
5136
+ execute: () => instance.openInSeparateWindow?.()
4706
5137
  }
4707
5138
  );
4708
5139
  return actions;
@@ -4873,12 +5304,9 @@ var OpenDocumentPlugin = class {
4873
5304
  page.style.backgroundColor = "#ffffff";
4874
5305
  page.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
4875
5306
  page.style.borderRadius = "4px";
4876
- page.style.boxSizing = "border-box";
4877
5307
  page.style.display = idx === 0 ? "block" : "none";
4878
- page.style.position = "absolute";
4879
- page.style.top = "0";
4880
- page.style.left = "50%";
4881
- page.style.transform = "translateX(-50%)";
5308
+ page.style.margin = "0 auto 24px";
5309
+ page.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
4882
5310
  elements.forEach((el) => page.appendChild(el));
4883
5311
  wrapper.appendChild(page);
4884
5312
  slides.push(page);
@@ -4916,6 +5344,7 @@ var OpenDocumentPlugin = class {
4916
5344
  if (indicator) {
4917
5345
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4918
5346
  }
5347
+ container.scrollTop = 0;
4919
5348
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4920
5349
  };
4921
5350
  return {
@@ -5293,6 +5722,14 @@ var DocPlugin = class {
5293
5722
  type: "button",
5294
5723
  group: "actions",
5295
5724
  execute: () => instance.print?.()
5725
+ },
5726
+ {
5727
+ id: "open-window",
5728
+ icon: "open-window",
5729
+ label: "Open in Separate Full Window",
5730
+ type: "button",
5731
+ group: "actions",
5732
+ execute: () => instance.openInSeparateWindow?.()
5296
5733
  }
5297
5734
  );
5298
5735
  return actions;
@@ -5408,6 +5845,7 @@ var DocPlugin = class {
5408
5845
  if (indicator) {
5409
5846
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5410
5847
  }
5848
+ container.scrollTop = 0;
5411
5849
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5412
5850
  };
5413
5851
  if (totalPages > 1) {
@@ -5583,47 +6021,69 @@ var DocPlugin = class {
5583
6021
  heuristicTextExtraction(buffer) {
5584
6022
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5585
6023
  }
6024
+ cleanWordDocFields(text) {
6025
+ if (!text) return "";
6026
+ let cleaned = text.replace(
6027
+ /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
6028
+ (_match, url, label) => {
6029
+ const cleanUrl = url.trim();
6030
+ const cleanLabel = label.trim() || cleanUrl;
6031
+ return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
6032
+ }
6033
+ );
6034
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
6035
+ cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
6036
+ cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6037
+ return cleaned;
6038
+ }
5586
6039
  splitIntoPages(text) {
5587
6040
  if (!text) return [""];
5588
- 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);
5589
- if (explicitParts.length === 0) explicitParts.push(text);
5590
- const maxLinesPerPage = 48;
6041
+ const cleanedText = this.cleanWordDocFields(text);
6042
+ const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
6043
+ 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);
6044
+ if (explicitParts.length === 0) explicitParts.push(normalized);
6045
+ const maxLinesPerPage = 32;
6046
+ const charsPerLine = 80;
5591
6047
  const finalPages = [];
5592
6048
  for (const part of explicitParts) {
5593
- const lines = part.split(/\r?\n/);
6049
+ const lines = part.split("\n");
5594
6050
  let currentLines = [];
5595
6051
  let count = 0;
5596
6052
  for (const line of lines) {
5597
- currentLines.push(line);
5598
- count++;
5599
- if (count >= maxLinesPerPage) {
6053
+ const plainLine = line.replace(/<[^>]+>/g, "");
6054
+ const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
6055
+ if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
5600
6056
  finalPages.push(currentLines.join("\n"));
5601
6057
  currentLines = [];
5602
6058
  count = 0;
5603
6059
  }
6060
+ currentLines.push(line);
6061
+ count += vLines;
5604
6062
  }
5605
6063
  if (currentLines.length > 0) {
5606
6064
  finalPages.push(currentLines.join("\n"));
5607
6065
  }
5608
6066
  }
5609
- return finalPages.length > 0 ? finalPages : [text];
6067
+ return finalPages.length > 0 ? finalPages : [normalized];
5610
6068
  }
5611
6069
  /**
5612
6070
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
5613
6071
  */
5614
6072
  formatDocToHtml(text, filename) {
5615
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
6073
+ 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");
5616
6074
  let html = "";
5617
6075
  let inList = false;
5618
6076
  let tableLines = [];
5619
6077
  const flushTable = () => {
5620
6078
  if (tableLines.length > 0) {
5621
- html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 12px;">';
5622
- for (const tLine of tableLines) {
5623
- html += "<tr>";
5624
- const cols = tLine.split(" ");
6079
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; font-family: Calibri, sans-serif;">';
6080
+ for (let rIdx = 0; rIdx < tableLines.length; rIdx++) {
6081
+ const tLine = tableLines[rIdx];
6082
+ const isHeader = rIdx === 0;
6083
+ html += `<tr style="${isHeader ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
6084
+ const cols = tLine.split(" ").filter((c) => c.trim().length > 0);
5625
6085
  for (const col of cols) {
5626
- html += `<td style="border: 1px solid #cbd5e1; padding: 6px 8px;">${DOMPurify6__default.default.sanitize(col.trim())}</td>`;
6086
+ html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px;">${DOMPurify6__default.default.sanitize(col.trim())}</td>`;
5627
6087
  }
5628
6088
  html += "</tr>";
5629
6089
  }
@@ -5636,27 +6096,18 @@ var DocPlugin = class {
5636
6096
  let line = lines[i];
5637
6097
  let tabCount = (line.match(/\t/g) || []).length;
5638
6098
  if (tabCount > 0) {
5639
- let consecutiveTableLines = 1;
5640
- let j = i + 1;
5641
- while (j < lines.length) {
5642
- const nextTabCount = (lines[j].match(/\t/g) || []).length;
5643
- if (nextTabCount === tabCount) {
5644
- consecutiveTableLines++;
5645
- j++;
5646
- } else {
5647
- break;
5648
- }
6099
+ let j = i;
6100
+ while (j < lines.length && (lines[j].match(/\t/g) || []).length > 0) {
6101
+ j++;
5649
6102
  }
5650
- if (consecutiveTableLines >= 3) {
5651
- if (inList) {
5652
- html += "</ul>";
5653
- inList = false;
5654
- }
5655
- tableLines = lines.slice(i, j);
5656
- flushTable();
5657
- i = j;
5658
- continue;
6103
+ if (inList) {
6104
+ html += "</ul>";
6105
+ inList = false;
5659
6106
  }
6107
+ tableLines = lines.slice(i, j);
6108
+ flushTable();
6109
+ i = j;
6110
+ continue;
5660
6111
  }
5661
6112
  line = line.trim();
5662
6113
  if (!line) {
@@ -5676,25 +6127,26 @@ var DocPlugin = class {
5676
6127
  i++;
5677
6128
  continue;
5678
6129
  }
6130
+ const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
5679
6131
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
5680
6132
  if (inList) {
5681
6133
  html += "</ul>";
5682
6134
  inList = false;
5683
6135
  }
5684
- 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>`;
6136
+ 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>`;
5685
6137
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
5686
6138
  if (!inList) {
5687
6139
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
5688
6140
  inList = true;
5689
6141
  }
5690
6142
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
5691
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText)}</li>`;
6143
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
5692
6144
  } else {
5693
6145
  if (inList) {
5694
6146
  html += "</ul>";
5695
6147
  inList = false;
5696
6148
  }
5697
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line)}</p>`;
6149
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
5698
6150
  }
5699
6151
  i++;
5700
6152
  }