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