@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.js CHANGED
@@ -385,16 +385,21 @@ async function sourceToArrayBuffer(source, signal) {
385
385
  metadata.mimeType = source.type || void 0;
386
386
  metadata.extension = extractExtension(source.name);
387
387
  buffer = await source.arrayBuffer();
388
- } else if (source instanceof Blob) {
388
+ } else if (source instanceof Blob || source && typeof source.arrayBuffer === "function" && typeof source.size === "number") {
389
389
  metadata.size = source.size;
390
390
  metadata.mimeType = source.type || void 0;
391
+ if (source.name) {
392
+ metadata.name = source.name;
393
+ metadata.extension = extractExtension(source.name);
394
+ }
391
395
  buffer = await source.arrayBuffer();
392
- } else if (source instanceof ArrayBuffer) {
396
+ } else if (source instanceof ArrayBuffer || Object.prototype.toString.call(source) === "[object ArrayBuffer]" || source && typeof source.byteLength === "number" && typeof source.slice === "function") {
393
397
  buffer = source;
394
- } else if (source instanceof Uint8Array) {
395
- buffer = source.buffer.slice(
396
- source.byteOffset,
397
- source.byteOffset + source.byteLength
398
+ } else if (source instanceof Uint8Array || ArrayBuffer.isView(source)) {
399
+ const view = source;
400
+ buffer = view.buffer.slice(
401
+ view.byteOffset,
402
+ view.byteOffset + view.byteLength
398
403
  );
399
404
  } else {
400
405
  throw new Error("Unsupported file source type");
@@ -479,6 +484,45 @@ function createElement(tag, attrs, ...children) {
479
484
  }
480
485
  return el;
481
486
  }
487
+ var DB_NAME = "PreviewFileTransferDB";
488
+ var DB_STORE = "transfers";
489
+ function openDB() {
490
+ return new Promise((resolve, reject) => {
491
+ if (typeof indexedDB === "undefined") {
492
+ return reject(new Error("IndexedDB is not available"));
493
+ }
494
+ const req = indexedDB.open(DB_NAME, 1);
495
+ req.onupgradeneeded = () => {
496
+ const db = req.result;
497
+ if (!db.objectStoreNames.contains(DB_STORE)) {
498
+ db.createObjectStore(DB_STORE, { keyPath: "id" });
499
+ }
500
+ };
501
+ req.onsuccess = () => resolve(req.result);
502
+ req.onerror = () => reject(req.error);
503
+ });
504
+ }
505
+ async function saveTransferPayload(id, payload) {
506
+ if (typeof window !== "undefined") {
507
+ try {
508
+ window[id] = payload;
509
+ window.__lastTransfer = payload;
510
+ } catch {
511
+ }
512
+ }
513
+ try {
514
+ const db = await openDB();
515
+ return new Promise((resolve, reject) => {
516
+ const tx = db.transaction(DB_STORE, "readwrite");
517
+ const store = tx.objectStore(DB_STORE);
518
+ store.put({ id, ...payload, timestamp: Date.now() });
519
+ tx.oncomplete = () => resolve();
520
+ tx.onerror = () => reject(tx.error);
521
+ });
522
+ } catch (e) {
523
+ console.warn("[saveTransferPayload] IndexedDB store warning:", e);
524
+ }
525
+ }
482
526
  var ICON_ZOOM_IN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>`;
483
527
  var ICON_ZOOM_OUT = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>`;
484
528
  var ICON_FIT_PAGE = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><line x1="8" y1="12" x2="16" y2="12"></line><polyline points="11 9 8 12 11 15"></polyline><polyline points="13 9 16 12 13 15"></polyline></svg>`;
@@ -498,6 +542,7 @@ var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" str
498
542
  var ICON_FAST_FORWARD = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon></svg>`;
499
543
  var ICON_REWIND = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon></svg>`;
500
544
  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>`;
545
+ var ICON_EXTERNAL_WINDOW = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`;
501
546
  var ICON_MAP = {
502
547
  "zoom-in": ICON_ZOOM_IN,
503
548
  "zoom-out": ICON_ZOOM_OUT,
@@ -524,7 +569,9 @@ var ICON_MAP = {
524
569
  "forward-10": ICON_FAST_FORWARD,
525
570
  "rewind": ICON_REWIND,
526
571
  "replay-10": ICON_REWIND,
527
- "speed": ICON_SPEED
572
+ "speed": ICON_SPEED,
573
+ "open-window": ICON_EXTERNAL_WINDOW,
574
+ "external-window": ICON_EXTERNAL_WINDOW
528
575
  };
529
576
  var ToolbarController = class {
530
577
  el;
@@ -741,7 +788,7 @@ var ThumbnailPanel = class {
741
788
  }
742
789
  }
743
790
  };
744
- var FilePreviewViewer = class {
791
+ var FilePreviewViewer = class _FilePreviewViewer {
745
792
  plugins = [];
746
793
  activeInstance = null;
747
794
  abortController = null;
@@ -778,6 +825,7 @@ var FilePreviewViewer = class {
778
825
  * Preview a file in the given container element.
779
826
  */
780
827
  async preview(container, source, options = {}) {
828
+ this.currentOptions = options;
781
829
  this.abort();
782
830
  this.abortController = new AbortController();
783
831
  const { signal } = this.abortController;
@@ -787,7 +835,10 @@ var FilePreviewViewer = class {
787
835
  this.showLoading();
788
836
  try {
789
837
  const { buffer, metadata } = await sourceToArrayBuffer(source, signal);
790
- this.currentBuffer = buffer;
838
+ if (options.metadata) {
839
+ Object.assign(metadata, options.metadata);
840
+ }
841
+ this.currentBuffer = buffer.slice(0);
791
842
  this.currentMetadata = metadata;
792
843
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
793
844
  const fileInfo = { metadata, buffer };
@@ -817,6 +868,7 @@ var FilePreviewViewer = class {
817
868
  }
818
869
  });
819
870
  this.activeInstance = instance;
871
+ instance.openInSeparateWindow = () => this.openInSeparateWindow();
820
872
  this.hideLoading();
821
873
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
822
874
  if (options.showToolbar !== false && this.toolbar) {
@@ -826,26 +878,16 @@ var FilePreviewViewer = class {
826
878
  actions.push({
827
879
  id: "fullscreen",
828
880
  icon: "fullscreen",
829
- label: "Toggle Fullscreen",
881
+ label: "Fullscreen",
830
882
  type: "button",
831
883
  group: "view",
832
- execute: async () => {
884
+ execute: () => {
833
885
  try {
834
- const isNativeFs = !!document.fullscreenElement;
835
- const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
836
- if (!isNativeFs && !isCssFs) {
837
- if (this.wrapperEl?.requestFullscreen) {
838
- await this.wrapperEl.requestFullscreen().catch(() => {
839
- this.wrapperEl?.classList.add("fp-fullscreen-active");
840
- });
841
- } else {
842
- this.wrapperEl?.classList.add("fp-fullscreen-active");
843
- }
886
+ if (!document.fullscreenElement) {
887
+ this.wrapperEl?.requestFullscreen?.();
888
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
844
889
  } else {
845
- if (document.fullscreenElement) {
846
- await document.exitFullscreen().catch(() => {
847
- });
848
- }
890
+ document.exitFullscreen?.();
849
891
  this.wrapperEl?.classList.remove("fp-fullscreen-active");
850
892
  }
851
893
  } catch {
@@ -857,6 +899,28 @@ var FilePreviewViewer = class {
857
899
  }
858
900
  });
859
901
  }
902
+ const openWinAction = actions.find((a) => a.id === "open-window");
903
+ if (openWinAction) {
904
+ if (options?._isSeparateWindow) {
905
+ const idx = actions.indexOf(openWinAction);
906
+ if (idx !== -1) actions.splice(idx, 1);
907
+ } else {
908
+ openWinAction.execute = () => {
909
+ this.openInSeparateWindow();
910
+ };
911
+ }
912
+ } else if (!options?._isSeparateWindow) {
913
+ actions.push({
914
+ id: "open-window",
915
+ icon: "open-window",
916
+ label: "Open in Separate Full Window",
917
+ type: "button",
918
+ group: "actions",
919
+ execute: () => {
920
+ this.openInSeparateWindow();
921
+ }
922
+ });
923
+ }
860
924
  this.toolbar.update(actions);
861
925
  this.toolbar.show();
862
926
  }
@@ -889,6 +953,104 @@ var FilePreviewViewer = class {
889
953
  throw error;
890
954
  }
891
955
  }
956
+ /**
957
+ * Opens the current file preview in a separate full browser window.
958
+ */
959
+ openInSeparateWindow() {
960
+ if (!this.currentBuffer) {
961
+ console.warn("[FilePreviewViewer] No active file buffer to open in separate window");
962
+ return null;
963
+ }
964
+ if (this.currentOptions.onOpenSeparateWindow) {
965
+ return this.currentOptions.onOpenSeparateWindow({
966
+ buffer: this.currentBuffer,
967
+ metadata: this.currentMetadata || { name: "Document" },
968
+ options: this.currentOptions
969
+ });
970
+ }
971
+ const transferId = "fp_win_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
972
+ let clonedBuffer;
973
+ try {
974
+ clonedBuffer = this.currentBuffer.slice(0);
975
+ } catch {
976
+ clonedBuffer = this.currentBuffer;
977
+ }
978
+ const payload = {
979
+ buffer: clonedBuffer,
980
+ metadata: this.currentMetadata ? { ...this.currentMetadata } : void 0,
981
+ options: { ...this.currentOptions, _isSeparateWindow: true }
982
+ };
983
+ if (typeof window !== "undefined") {
984
+ try {
985
+ window[transferId] = payload;
986
+ window.__lastTransfer = payload;
987
+ } catch {
988
+ }
989
+ }
990
+ saveTransferPayload(transferId, payload).catch((err) => {
991
+ console.warn("[FilePreviewViewer] Transfer payload save warning:", err);
992
+ });
993
+ let targetUrl = null;
994
+ if (this.currentOptions.standaloneViewerUrl) {
995
+ const u = new URL(this.currentOptions.standaloneViewerUrl, window.location.href);
996
+ u.searchParams.set("mode", "fullscreen");
997
+ u.searchParams.set("transferId", transferId);
998
+ targetUrl = u.toString();
999
+ } else if (typeof window !== "undefined" && window.location?.href && !window.location.href.startsWith("about:")) {
1000
+ const u = new URL(window.location.href);
1001
+ u.searchParams.set("mode", "fullscreen");
1002
+ u.searchParams.set("transferId", transferId);
1003
+ targetUrl = u.toString();
1004
+ }
1005
+ if (targetUrl) {
1006
+ const newWin2 = window.open(targetUrl, "_blank");
1007
+ if (!newWin2) {
1008
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1009
+ return null;
1010
+ }
1011
+ return newWin2;
1012
+ }
1013
+ const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
1014
+ const newWin = window.open("", "_blank");
1015
+ if (!newWin) {
1016
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1017
+ return null;
1018
+ }
1019
+ newWin.document.title = title;
1020
+ newWin.document.body.style.margin = "0";
1021
+ newWin.document.body.style.padding = "0";
1022
+ newWin.document.body.style.width = "100vw";
1023
+ newWin.document.body.style.height = "100vh";
1024
+ newWin.document.body.style.overflow = "hidden";
1025
+ newWin.document.body.style.backgroundColor = "#f8fafc";
1026
+ const headNodes = document.querySelectorAll('link[rel="stylesheet"], style');
1027
+ headNodes.forEach((node) => {
1028
+ newWin.document.head.appendChild(node.cloneNode(true));
1029
+ });
1030
+ const root = newWin.document.createElement("div");
1031
+ root.id = "full-window-preview-root";
1032
+ root.style.width = "100%";
1033
+ root.style.height = "100%";
1034
+ root.style.overflow = "hidden";
1035
+ newWin.document.body.appendChild(root);
1036
+ const separateViewer = new _FilePreviewViewer();
1037
+ for (const plugin of this.plugins) {
1038
+ separateViewer.registerPlugin(plugin);
1039
+ }
1040
+ separateViewer.preview(root, this.currentBuffer.slice(0), {
1041
+ ...this.currentOptions,
1042
+ showToolbar: true,
1043
+ toolbarPosition: "top",
1044
+ metadata: this.currentMetadata || void 0,
1045
+ _isSeparateWindow: true
1046
+ }).catch((err) => {
1047
+ console.error("[FilePreviewViewer] Error rendering in separate window:", err);
1048
+ });
1049
+ newWin.addEventListener("beforeunload", () => {
1050
+ separateViewer.destroy();
1051
+ });
1052
+ return newWin;
1053
+ }
892
1054
  /**
893
1055
  * Subscribe to viewer events.
894
1056
  */
@@ -1294,8 +1456,20 @@ var CfbfReader = class {
1294
1456
  }
1295
1457
  };
1296
1458
  if (typeof window !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
1297
- if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
1298
- pdfjsLib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1459
+ if (!pdfjsLib.GlobalWorkerOptions.workerPort && !pdfjsLib.GlobalWorkerOptions.workerSrc) {
1460
+ const customWorker = window.__PDF_WORKER_SRC__;
1461
+ if (customWorker) {
1462
+ pdfjsLib.GlobalWorkerOptions.workerSrc = customWorker;
1463
+ } else {
1464
+ try {
1465
+ pdfjsLib.GlobalWorkerOptions.workerPort = new Worker(
1466
+ new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url),
1467
+ { type: "module" }
1468
+ );
1469
+ } catch {
1470
+ pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.mjs";
1471
+ }
1472
+ }
1299
1473
  }
1300
1474
  }
1301
1475
  var PdfPlugin = class {
@@ -1389,6 +1563,14 @@ var PdfPlugin = class {
1389
1563
  type: "button",
1390
1564
  group: "actions",
1391
1565
  execute: () => instance.print?.()
1566
+ },
1567
+ {
1568
+ id: "open-window",
1569
+ icon: "open-window",
1570
+ label: "Open in Separate Full Window",
1571
+ type: "button",
1572
+ group: "actions",
1573
+ execute: () => instance.openInSeparateWindow?.()
1392
1574
  }
1393
1575
  ];
1394
1576
  }
@@ -1438,18 +1620,21 @@ var PdfPlugin = class {
1438
1620
  indicator.style.pointerEvents = "none";
1439
1621
  container.appendChild(indicator);
1440
1622
  ctx.container.appendChild(container);
1623
+ const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1624
+ const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
1441
1625
  const loadingTask = pdfjsLib.getDocument({
1442
- data: new Uint8Array(ctx.buffer),
1443
- cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/cmaps/`,
1626
+ data: new Uint8Array(ctx.buffer.slice(0)),
1627
+ cMapUrl: cmapsUrl,
1444
1628
  cMapPacked: true,
1445
- standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/standard_fonts/`
1629
+ standardFontDataUrl: standardFontsUrl,
1630
+ verbosity: 0
1446
1631
  });
1447
1632
  const pdfDoc = await loadingTask.promise;
1448
1633
  const totalPages = Math.max(1, pdfDoc.numPages);
1449
1634
  let currentPage = 1;
1450
1635
  let zoomScale = 1;
1451
1636
  let rotation = 0;
1452
- let fitMode = "width";
1637
+ let fitMode = "page";
1453
1638
  let currentRenderTask = null;
1454
1639
  const renderPage = async (pageNum) => {
1455
1640
  if (currentRenderTask) {
@@ -1469,15 +1654,15 @@ var PdfPlugin = class {
1469
1654
  const containerWidth = container.clientWidth || 900;
1470
1655
  const containerHeight = container.clientHeight || 700;
1471
1656
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1472
- const availWidth = Math.max(280, containerWidth - 48);
1473
- const availHeight = Math.max(280, containerHeight - 88);
1657
+ const availWidth = Math.max(320, containerWidth - 48);
1658
+ const availHeight = Math.max(550, containerHeight - 88);
1474
1659
  const scaleW = availWidth / unscaledVp.width;
1475
1660
  const scaleH = availHeight / unscaledVp.height;
1476
1661
  let fitScale;
1477
1662
  if (fitMode === "page") {
1478
- fitScale = Math.max(0.5, Math.min(scaleW, scaleH));
1663
+ fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
1479
1664
  } else {
1480
- fitScale = Math.max(0.65, Math.min(1.15, scaleW));
1665
+ fitScale = Math.max(0.65, Math.min(1.25, scaleW));
1481
1666
  }
1482
1667
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1483
1668
  const pixelRatio = window.devicePixelRatio || 1;
@@ -1503,7 +1688,7 @@ var PdfPlugin = class {
1503
1688
  currentRenderTask = null;
1504
1689
  }
1505
1690
  };
1506
- await renderPage(1);
1691
+ renderPage(1);
1507
1692
  let resizeTimer = null;
1508
1693
  const resizeObserver = new ResizeObserver(() => {
1509
1694
  if (resizeTimer) clearTimeout(resizeTimer);
@@ -1547,7 +1732,7 @@ var PdfPlugin = class {
1547
1732
  renderPage(currentPage);
1548
1733
  },
1549
1734
  fitToPage: () => {
1550
- fitMode = fitMode === "width" ? "page" : "width";
1735
+ fitMode = fitMode === "page" ? "width" : "page";
1551
1736
  zoomScale = 1;
1552
1737
  rotation = 0;
1553
1738
  renderPage(currentPage);
@@ -1564,6 +1749,7 @@ var PdfPlugin = class {
1564
1749
  getPageCount: () => totalPages,
1565
1750
  getCurrentPage: () => currentPage,
1566
1751
  goToPage: (page) => {
1752
+ container.scrollTop = 0;
1567
1753
  renderPage(page);
1568
1754
  },
1569
1755
  download: () => {
@@ -2023,6 +2209,14 @@ var DocxPlugin = class {
2023
2209
  type: "button",
2024
2210
  group: "actions",
2025
2211
  execute: () => instance.print?.()
2212
+ },
2213
+ {
2214
+ id: "open-window",
2215
+ icon: "open-window",
2216
+ label: "Open in Separate Full Window",
2217
+ type: "button",
2218
+ group: "actions",
2219
+ execute: () => instance.openInSeparateWindow?.()
2026
2220
  }
2027
2221
  );
2028
2222
  return actions;
@@ -2068,13 +2262,45 @@ var DocxPlugin = class {
2068
2262
  ignoreFonts: true,
2069
2263
  // Avoid crashes on embedded obfuscated fonts
2070
2264
  breakPages: true,
2071
- experimental: true
2265
+ experimental: true,
2266
+ ignoreLastRenderedPageBreak: false,
2267
+ // Honor Word's exact page breaks!
2268
+ renderHeaders: true,
2269
+ renderFooters: true,
2270
+ renderFootnotes: true,
2271
+ renderEndnotes: true,
2272
+ useBase64URL: true
2072
2273
  });
2073
2274
  if (!ctx.container.contains(wrapper)) {
2074
2275
  ctx.container.appendChild(wrapper);
2075
2276
  }
2076
2277
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2077
2278
  renderedSuccessfully = true;
2279
+ try {
2280
+ const unzipped = unzipSync(new Uint8Array(ctx.buffer));
2281
+ const chartKeys = Object.keys(unzipped).filter((k) => k.replace(/^[./\\]+/, "").toLowerCase().startsWith("word/charts/chart") && k.endsWith(".xml")).sort();
2282
+ if (chartKeys.length > 0) {
2283
+ const allDivs = Array.from(wrapper.querySelectorAll("div"));
2284
+ const emptyContainers = allDivs.filter((div) => {
2285
+ const st = div.getAttribute("style") || "";
2286
+ return st.includes("width:") && st.includes("height:") && div.children.length === 0 && (div.textContent?.trim().length ?? 0) === 0;
2287
+ });
2288
+ chartKeys.forEach((cKey, idx) => {
2289
+ const target = emptyContainers[idx];
2290
+ if (target) {
2291
+ const xmlStr = strFromU8(unzipped[cKey]);
2292
+ const svg = this.parseAndRenderChartSvg(xmlStr);
2293
+ if (svg) {
2294
+ target.innerHTML = svg;
2295
+ target.style.display = "block";
2296
+ target.style.margin = "12px auto";
2297
+ }
2298
+ }
2299
+ });
2300
+ }
2301
+ } catch (chartErr) {
2302
+ console.warn("[DocxPlugin] Non-critical error rendering DrawingML charts:", chartErr);
2303
+ }
2078
2304
  }
2079
2305
  } catch (err) {
2080
2306
  console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
@@ -2089,11 +2315,14 @@ var DocxPlugin = class {
2089
2315
  renderedSuccessfully = true;
2090
2316
  } catch (fallbackErr) {
2091
2317
  console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
2318
+ const isCorrupt = fallbackErr?.message?.includes("invalid zip") || fallbackErr?.message?.includes("corrupted");
2092
2319
  wrapper.innerHTML = `
2093
- <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
2094
- <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
2095
- <h3 style="margin: 0 0 8px; color: #1e293b;">${ctx.metadata.name || "Word Document"}</h3>
2096
- <p style="color: #64748b; margin: 0;">Could not parse document content</p>
2320
+ <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;">
2321
+ <div style="font-size:48px; margin-bottom: 16px;">${isCorrupt ? "\u26A0\uFE0F" : "\u{1F4C4}"}</div>
2322
+ <h3 style="margin: 0 0 8px; color: #1e293b; font-size: 18px;">${ctx.metadata.name || "Word Document"}</h3>
2323
+ <p style="color: #64748b; margin: 0 0 16px; font-size: 14px; line-height: 1.5;">
2324
+ ${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."}
2325
+ </p>
2097
2326
  </div>
2098
2327
  `;
2099
2328
  if (!ctx.container.contains(wrapper)) {
@@ -2103,50 +2332,74 @@ var DocxPlugin = class {
2103
2332
  }
2104
2333
  let sections = Array.from(wrapper.querySelectorAll("section.docx"));
2105
2334
  const cards = Array.from(wrapper.querySelectorAll(".fp-docx-page-card"));
2106
- if (sections.length === 1 && cards.length === 0) {
2107
- const singleSec = sections[0];
2108
- const pageH = 1056;
2109
- const secH = singleSec.offsetHeight || singleSec.scrollHeight;
2110
- if (secH > pageH * 1.25) {
2111
- const children = Array.from(singleSec.children);
2112
- if (children.length > 1) {
2335
+ if (sections.length > 0 && cards.length === 0) {
2336
+ const finalSections = [];
2337
+ for (const singleSec of sections) {
2338
+ const contentContainer = singleSec.querySelector("article") || singleSec;
2339
+ const children = Array.from(contentContainer.children);
2340
+ const pageH = singleSec.offsetHeight > 1300 ? 1122 : Math.max(1056, singleSec.offsetHeight);
2341
+ const secH = singleSec.scrollHeight || singleSec.offsetHeight;
2342
+ if (secH > pageH * 1.25 && children.length > 1) {
2343
+ const childHeights = children.map((c) => {
2344
+ const rectH = c.getBoundingClientRect().height;
2345
+ const offH = c.offsetHeight;
2346
+ const textLen = c.textContent?.trim().length || 0;
2347
+ const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
2348
+ return Math.max(rectH, offH, estH);
2349
+ });
2113
2350
  const parent = singleSec.parentElement || wrapper;
2114
- const newSections = [singleSec];
2115
- singleSec.innerHTML = "";
2351
+ const headerEl = singleSec.querySelector("header");
2352
+ const footerEl = singleSec.querySelector("footer");
2353
+ contentContainer.innerHTML = "";
2116
2354
  singleSec.style.minHeight = `${pageH}px`;
2117
- singleSec.style.maxHeight = `${pageH}px`;
2118
- singleSec.style.overflow = "hidden";
2119
2355
  singleSec.style.boxSizing = "border-box";
2356
+ let curContent = contentContainer;
2120
2357
  let curSec = singleSec;
2121
2358
  let curH = 0;
2122
- const maxH = pageH - 96;
2359
+ const maxH = pageH - 140;
2360
+ finalSections.push(singleSec);
2123
2361
  for (let i = 0; i < children.length; i++) {
2124
2362
  const child = children[i];
2125
- curSec.appendChild(child);
2126
- const chH = child.offsetHeight || 28;
2363
+ const chH = childHeights[i];
2364
+ curContent.appendChild(child);
2127
2365
  curH += chH;
2128
2366
  if (curH >= maxH && i < children.length - 1) {
2129
2367
  const nextSec = document.createElement("section");
2130
2368
  nextSec.className = singleSec.className;
2131
2369
  nextSec.style.cssText = singleSec.style.cssText;
2132
- nextSec.style.width = singleSec.style.width || "816px";
2133
2370
  nextSec.style.minHeight = `${pageH}px`;
2134
- nextSec.style.maxHeight = `${pageH}px`;
2135
- nextSec.style.overflow = "hidden";
2136
2371
  nextSec.style.boxSizing = "border-box";
2137
2372
  nextSec.style.backgroundColor = "#ffffff";
2138
2373
  nextSec.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
2139
2374
  nextSec.style.borderRadius = "4px";
2140
2375
  nextSec.style.marginBottom = "24px";
2141
- parent.appendChild(nextSec);
2142
- newSections.push(nextSec);
2376
+ if (headerEl) {
2377
+ nextSec.appendChild(headerEl.cloneNode(true));
2378
+ }
2379
+ const nextArticle = document.createElement("article");
2380
+ if (contentContainer.tagName.toLowerCase() === "article") {
2381
+ nextArticle.style.cssText = contentContainer.style.cssText;
2382
+ }
2383
+ nextSec.appendChild(nextArticle);
2384
+ if (footerEl) {
2385
+ nextSec.appendChild(footerEl.cloneNode(true));
2386
+ }
2387
+ if (curSec.nextSibling) {
2388
+ parent.insertBefore(nextSec, curSec.nextSibling);
2389
+ } else {
2390
+ parent.appendChild(nextSec);
2391
+ }
2392
+ finalSections.push(nextSec);
2143
2393
  curSec = nextSec;
2394
+ curContent = nextArticle;
2144
2395
  curH = 0;
2145
2396
  }
2146
2397
  }
2147
- sections = newSections;
2398
+ } else {
2399
+ finalSections.push(singleSec);
2148
2400
  }
2149
2401
  }
2402
+ sections = finalSections;
2150
2403
  }
2151
2404
  const pageElements = sections.length > 0 ? sections : cards;
2152
2405
  const totalPages = Math.max(1, pageElements.length);
@@ -2184,6 +2437,7 @@ var DocxPlugin = class {
2184
2437
  if (indicator) {
2185
2438
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2186
2439
  }
2440
+ ctx.container.scrollTop = 0;
2187
2441
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2188
2442
  };
2189
2443
  if (totalPages > 1) {
@@ -2537,6 +2791,88 @@ var DocxPlugin = class {
2537
2791
  }
2538
2792
  return result;
2539
2793
  }
2794
+ parseAndRenderChartSvg(xmlStr, width = 500, height = 260) {
2795
+ const catMatches = [...xmlStr.matchAll(/<c:cat>[\s\S]*?<c:strCache>([\s\S]*?)<\/c:strCache>/g)];
2796
+ let categories = [];
2797
+ if (catMatches.length > 0) {
2798
+ categories = [...catMatches[0][1].matchAll(/<c:v>([^<]+)<\/c:v>/g)].map((m) => m[1]);
2799
+ }
2800
+ if (categories.length === 0) {
2801
+ categories = ["Category 1", "Category 2", "Category 3", "Category 4"];
2802
+ }
2803
+ const defaultColors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021", "#83caff"];
2804
+ const sers = [...xmlStr.matchAll(/<c:ser>([\s\S]*?)<\/c:ser>/g)];
2805
+ const series = [];
2806
+ sers.forEach((s, sIdx) => {
2807
+ const titleMatch = s[1].match(/<c:tx>[\s\S]*?<c:v>([^<]+)<\/c:v>/);
2808
+ const title = titleMatch ? titleMatch[1] : `Series ${sIdx + 1}`;
2809
+ const clrMatch = s[1].match(/<a:srgbClr\s+val="([^"]+)"/);
2810
+ const color = clrMatch ? "#" + clrMatch[1] : defaultColors[sIdx % defaultColors.length];
2811
+ const valMatch = s[1].match(/<c:val>[\s\S]*?<c:numCache>([\s\S]*?)<\/c:numCache>/);
2812
+ let values = [];
2813
+ if (valMatch) {
2814
+ 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);
2815
+ }
2816
+ series.push({ title, color, values });
2817
+ });
2818
+ if (series.length === 0) return "";
2819
+ let maxVal = 10;
2820
+ series.forEach((s) => s.values.forEach((v) => {
2821
+ if (v > maxVal) maxVal = v;
2822
+ }));
2823
+ maxVal = Math.ceil(maxVal * 1.15);
2824
+ if (maxVal % 2 !== 0) maxVal++;
2825
+ const padLeft = 45;
2826
+ const padBottom = 55;
2827
+ const padTop = 20;
2828
+ const padRight = 20;
2829
+ const plotW = width - padLeft - padRight;
2830
+ const plotH = height - padTop - padBottom;
2831
+ const yTicks = 5;
2832
+ let gridLines = "";
2833
+ for (let i = 0; i <= yTicks; i++) {
2834
+ const val = maxVal / yTicks * i;
2835
+ const y = padTop + plotH - val / maxVal * plotH;
2836
+ gridLines += `<line x1="${padLeft}" y1="${y}" x2="${padLeft + plotW}" y2="${y}" stroke="#e2e8f0" stroke-width="1" />`;
2837
+ 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>`;
2838
+ }
2839
+ const numCats = categories.length;
2840
+ const numSers = series.length;
2841
+ const groupW = plotW / numCats;
2842
+ const barW = Math.max(8, Math.min(28, groupW * 0.7 / numSers));
2843
+ const groupPad = (groupW - barW * numSers) / 2;
2844
+ let bars = "";
2845
+ let catLabels = "";
2846
+ for (let c = 0; c < numCats; c++) {
2847
+ const catX = padLeft + c * groupW;
2848
+ 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>`;
2849
+ for (let s = 0; s < numSers; s++) {
2850
+ const val = series[s].values[c] ?? 0;
2851
+ const bH = Math.max(0, val / maxVal * plotH);
2852
+ const bX = catX + groupPad + s * barW;
2853
+ const bY = padTop + plotH - bH;
2854
+ bars += `<rect x="${bX}" y="${bY}" width="${barW - 2}" height="${bH}" fill="${series[s].color}" rx="1" />`;
2855
+ }
2856
+ }
2857
+ let legend = "";
2858
+ const legY = height - 12;
2859
+ let legX = padLeft + (plotW - numSers * 100) / 2;
2860
+ series.forEach((s) => {
2861
+ legend += `<rect x="${legX}" y="${legY - 9}" width="10" height="10" fill="${s.color}" rx="2" />`;
2862
+ legend += `<text x="${legX + 15}" y="${legY}" font-size="11" fill="#475569" font-family="Calibri, sans-serif">${s.title}</text>`;
2863
+ legX += 95;
2864
+ });
2865
+ return `
2866
+ <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">
2867
+ ${gridLines}
2868
+ <line x1="${padLeft}" y1="${padTop + plotH}" x2="${padLeft + plotW}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2869
+ <line x1="${padLeft}" y1="${padTop}" x2="${padLeft}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2870
+ ${bars}
2871
+ ${catLabels}
2872
+ ${legend}
2873
+ </svg>
2874
+ `.trim();
2875
+ }
2540
2876
  };
2541
2877
  function docxPlugin() {
2542
2878
  return new DocxPlugin();
@@ -3103,6 +3439,16 @@ var CodePlugin = class {
3103
3439
  execute: () => {
3104
3440
  instance.print?.();
3105
3441
  }
3442
+ },
3443
+ {
3444
+ id: "open-window",
3445
+ icon: "open-window",
3446
+ label: "Open in Separate Full Window",
3447
+ type: "button",
3448
+ group: "actions",
3449
+ execute: () => {
3450
+ instance.openInSeparateWindow?.();
3451
+ }
3106
3452
  }
3107
3453
  );
3108
3454
  return actions;
@@ -3116,17 +3462,39 @@ var CodePlugin = class {
3116
3462
  let rawPages = [];
3117
3463
  if (isTxt) {
3118
3464
  const explicitPages = fullText.split(/(?:\f|\x0C)/);
3465
+ const charsPerLine = 85;
3466
+ const maxVisualLines = 45;
3467
+ const charsPerPage = charsPerLine * maxVisualLines;
3119
3468
  for (const ep of explicitPages) {
3120
3469
  const lines = ep.split(/\r?\n/);
3121
3470
  let currentChunk = [];
3471
+ let currentLines = 0;
3122
3472
  for (let i = 0; i < lines.length; i++) {
3123
- currentChunk.push(lines[i]);
3124
- if (currentChunk.length >= 46) {
3473
+ const line = lines[i];
3474
+ const vLines = Math.max(1, Math.ceil((line.length || 1) / charsPerLine));
3475
+ if (currentLines + vLines > maxVisualLines && currentChunk.length > 0) {
3125
3476
  rawPages.push(currentChunk.join("\n"));
3126
3477
  currentChunk = [];
3478
+ currentLines = 0;
3479
+ }
3480
+ if (vLines > maxVisualLines) {
3481
+ let remaining = line;
3482
+ while (remaining.length > charsPerPage) {
3483
+ let splitIdx = remaining.lastIndexOf(" ", charsPerPage);
3484
+ if (splitIdx < charsPerPage * 0.75) splitIdx = charsPerPage;
3485
+ rawPages.push(remaining.slice(0, splitIdx));
3486
+ remaining = remaining.slice(splitIdx).trimStart();
3487
+ }
3488
+ if (remaining.length > 0) {
3489
+ currentChunk.push(remaining);
3490
+ currentLines = Math.ceil(remaining.length / charsPerLine);
3491
+ }
3492
+ } else {
3493
+ currentChunk.push(line);
3494
+ currentLines += vLines;
3127
3495
  }
3128
3496
  }
3129
- if (currentChunk.length > 0 || lines.length === 0) {
3497
+ if (currentChunk.length > 0) {
3130
3498
  rawPages.push(currentChunk.join("\n"));
3131
3499
  }
3132
3500
  }
@@ -3236,6 +3604,7 @@ var CodePlugin = class {
3236
3604
  if (indicator) {
3237
3605
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3238
3606
  }
3607
+ container.scrollTop = 0;
3239
3608
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3240
3609
  };
3241
3610
  if (totalPages > 1) {
@@ -4248,6 +4617,14 @@ var RtfPlugin = class {
4248
4617
  type: "button",
4249
4618
  group: "actions",
4250
4619
  execute: () => instance.print?.()
4620
+ },
4621
+ {
4622
+ id: "open-window",
4623
+ icon: "open-window",
4624
+ label: "Open in Separate Full Window",
4625
+ type: "button",
4626
+ group: "actions",
4627
+ execute: () => instance.openInSeparateWindow?.()
4251
4628
  }
4252
4629
  );
4253
4630
  return actions;
@@ -4300,11 +4677,55 @@ var RtfPlugin = class {
4300
4677
  }
4301
4678
  const doc = new RTFJS.Document(ctx.buffer, {});
4302
4679
  const htmlElements = await doc.render();
4303
- pageElements = htmlElements;
4304
- for (let i = 0; i < htmlElements.length; i++) {
4305
- const el = htmlElements[i];
4306
- el.style.display = i === 0 ? "block" : "none";
4307
- wrapper.appendChild(el);
4680
+ const contentNodes = [];
4681
+ for (const item of htmlElements) {
4682
+ if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4683
+ contentNodes.push(...Array.from(item.children));
4684
+ } else {
4685
+ contentNodes.push(item);
4686
+ }
4687
+ }
4688
+ wrapper.innerHTML = "";
4689
+ contentNodes.forEach((node) => wrapper.appendChild(node));
4690
+ const childHeights = contentNodes.map((c) => {
4691
+ const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4692
+ const offH = c.offsetHeight || 0;
4693
+ const textLen = c.textContent?.trim().length || 0;
4694
+ const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
4695
+ return Math.max(rectH, offH, estH);
4696
+ });
4697
+ wrapper.innerHTML = "";
4698
+ const createRtfCard = () => {
4699
+ const card = document.createElement("div");
4700
+ card.className = "fp-rtf-page-card";
4701
+ card.style.backgroundColor = "#ffffff";
4702
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4703
+ card.style.borderRadius = "4px";
4704
+ card.style.padding = "72px 56px";
4705
+ card.style.width = "816px";
4706
+ card.style.minHeight = "1056px";
4707
+ card.style.boxSizing = "border-box";
4708
+ card.style.marginBottom = "24px";
4709
+ card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4710
+ card.style.lineHeight = "1.6";
4711
+ return card;
4712
+ };
4713
+ let curCard = createRtfCard();
4714
+ wrapper.appendChild(curCard);
4715
+ pageElements = [curCard];
4716
+ let curH = 0;
4717
+ const maxH = 912;
4718
+ for (let i = 0; i < contentNodes.length; i++) {
4719
+ const child = contentNodes[i];
4720
+ const chH = childHeights[i];
4721
+ curCard.appendChild(child);
4722
+ curH += chH;
4723
+ if (curH >= maxH && i < contentNodes.length - 1) {
4724
+ curCard = createRtfCard();
4725
+ wrapper.appendChild(curCard);
4726
+ pageElements.push(curCard);
4727
+ curH = 0;
4728
+ }
4308
4729
  }
4309
4730
  } catch (err) {
4310
4731
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
@@ -4370,6 +4791,7 @@ var RtfPlugin = class {
4370
4791
  if (indicator) {
4371
4792
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4372
4793
  }
4794
+ ctx.container.scrollTop = 0;
4373
4795
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4374
4796
  };
4375
4797
  if (totalPages > 1) {
@@ -4671,6 +5093,14 @@ var OpenDocumentPlugin = class {
4671
5093
  type: "button",
4672
5094
  group: "actions",
4673
5095
  execute: () => instance.print?.()
5096
+ },
5097
+ {
5098
+ id: "open-window",
5099
+ icon: "open-window",
5100
+ label: "Open in Separate Full Window",
5101
+ type: "button",
5102
+ group: "actions",
5103
+ execute: () => instance.openInSeparateWindow?.()
4674
5104
  }
4675
5105
  );
4676
5106
  return actions;
@@ -4841,12 +5271,9 @@ var OpenDocumentPlugin = class {
4841
5271
  page.style.backgroundColor = "#ffffff";
4842
5272
  page.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
4843
5273
  page.style.borderRadius = "4px";
4844
- page.style.boxSizing = "border-box";
4845
5274
  page.style.display = idx === 0 ? "block" : "none";
4846
- page.style.position = "absolute";
4847
- page.style.top = "0";
4848
- page.style.left = "50%";
4849
- page.style.transform = "translateX(-50%)";
5275
+ page.style.margin = "0 auto 24px";
5276
+ page.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
4850
5277
  elements.forEach((el) => page.appendChild(el));
4851
5278
  wrapper.appendChild(page);
4852
5279
  slides.push(page);
@@ -4884,6 +5311,7 @@ var OpenDocumentPlugin = class {
4884
5311
  if (indicator) {
4885
5312
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4886
5313
  }
5314
+ container.scrollTop = 0;
4887
5315
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4888
5316
  };
4889
5317
  return {
@@ -5261,6 +5689,14 @@ var DocPlugin = class {
5261
5689
  type: "button",
5262
5690
  group: "actions",
5263
5691
  execute: () => instance.print?.()
5692
+ },
5693
+ {
5694
+ id: "open-window",
5695
+ icon: "open-window",
5696
+ label: "Open in Separate Full Window",
5697
+ type: "button",
5698
+ group: "actions",
5699
+ execute: () => instance.openInSeparateWindow?.()
5264
5700
  }
5265
5701
  );
5266
5702
  return actions;
@@ -5376,6 +5812,7 @@ var DocPlugin = class {
5376
5812
  if (indicator) {
5377
5813
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5378
5814
  }
5815
+ container.scrollTop = 0;
5379
5816
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5380
5817
  };
5381
5818
  if (totalPages > 1) {
@@ -5551,47 +5988,69 @@ var DocPlugin = class {
5551
5988
  heuristicTextExtraction(buffer) {
5552
5989
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5553
5990
  }
5991
+ cleanWordDocFields(text) {
5992
+ if (!text) return "";
5993
+ let cleaned = text.replace(
5994
+ /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
5995
+ (_match, url, label) => {
5996
+ const cleanUrl = url.trim();
5997
+ const cleanLabel = label.trim() || cleanUrl;
5998
+ return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
5999
+ }
6000
+ );
6001
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
6002
+ cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
6003
+ cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6004
+ return cleaned;
6005
+ }
5554
6006
  splitIntoPages(text) {
5555
6007
  if (!text) return [""];
5556
- 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);
5557
- if (explicitParts.length === 0) explicitParts.push(text);
5558
- const maxLinesPerPage = 48;
6008
+ const cleanedText = this.cleanWordDocFields(text);
6009
+ const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
6010
+ 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);
6011
+ if (explicitParts.length === 0) explicitParts.push(normalized);
6012
+ const maxLinesPerPage = 32;
6013
+ const charsPerLine = 80;
5559
6014
  const finalPages = [];
5560
6015
  for (const part of explicitParts) {
5561
- const lines = part.split(/\r?\n/);
6016
+ const lines = part.split("\n");
5562
6017
  let currentLines = [];
5563
6018
  let count = 0;
5564
6019
  for (const line of lines) {
5565
- currentLines.push(line);
5566
- count++;
5567
- if (count >= maxLinesPerPage) {
6020
+ const plainLine = line.replace(/<[^>]+>/g, "");
6021
+ const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
6022
+ if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
5568
6023
  finalPages.push(currentLines.join("\n"));
5569
6024
  currentLines = [];
5570
6025
  count = 0;
5571
6026
  }
6027
+ currentLines.push(line);
6028
+ count += vLines;
5572
6029
  }
5573
6030
  if (currentLines.length > 0) {
5574
6031
  finalPages.push(currentLines.join("\n"));
5575
6032
  }
5576
6033
  }
5577
- return finalPages.length > 0 ? finalPages : [text];
6034
+ return finalPages.length > 0 ? finalPages : [normalized];
5578
6035
  }
5579
6036
  /**
5580
6037
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
5581
6038
  */
5582
6039
  formatDocToHtml(text, filename) {
5583
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
6040
+ 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");
5584
6041
  let html = "";
5585
6042
  let inList = false;
5586
6043
  let tableLines = [];
5587
6044
  const flushTable = () => {
5588
6045
  if (tableLines.length > 0) {
5589
- html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 12px;">';
5590
- for (const tLine of tableLines) {
5591
- html += "<tr>";
5592
- const cols = tLine.split(" ");
6046
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; font-family: Calibri, sans-serif;">';
6047
+ for (let rIdx = 0; rIdx < tableLines.length; rIdx++) {
6048
+ const tLine = tableLines[rIdx];
6049
+ const isHeader = rIdx === 0;
6050
+ html += `<tr style="${isHeader ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
6051
+ const cols = tLine.split(" ").filter((c) => c.trim().length > 0);
5593
6052
  for (const col of cols) {
5594
- html += `<td style="border: 1px solid #cbd5e1; padding: 6px 8px;">${DOMPurify6.sanitize(col.trim())}</td>`;
6053
+ html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px;">${DOMPurify6.sanitize(col.trim())}</td>`;
5595
6054
  }
5596
6055
  html += "</tr>";
5597
6056
  }
@@ -5604,27 +6063,18 @@ var DocPlugin = class {
5604
6063
  let line = lines[i];
5605
6064
  let tabCount = (line.match(/\t/g) || []).length;
5606
6065
  if (tabCount > 0) {
5607
- let consecutiveTableLines = 1;
5608
- let j = i + 1;
5609
- while (j < lines.length) {
5610
- const nextTabCount = (lines[j].match(/\t/g) || []).length;
5611
- if (nextTabCount === tabCount) {
5612
- consecutiveTableLines++;
5613
- j++;
5614
- } else {
5615
- break;
5616
- }
6066
+ let j = i;
6067
+ while (j < lines.length && (lines[j].match(/\t/g) || []).length > 0) {
6068
+ j++;
5617
6069
  }
5618
- if (consecutiveTableLines >= 3) {
5619
- if (inList) {
5620
- html += "</ul>";
5621
- inList = false;
5622
- }
5623
- tableLines = lines.slice(i, j);
5624
- flushTable();
5625
- i = j;
5626
- continue;
6070
+ if (inList) {
6071
+ html += "</ul>";
6072
+ inList = false;
5627
6073
  }
6074
+ tableLines = lines.slice(i, j);
6075
+ flushTable();
6076
+ i = j;
6077
+ continue;
5628
6078
  }
5629
6079
  line = line.trim();
5630
6080
  if (!line) {
@@ -5644,25 +6094,26 @@ var DocPlugin = class {
5644
6094
  i++;
5645
6095
  continue;
5646
6096
  }
6097
+ const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
5647
6098
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
5648
6099
  if (inList) {
5649
6100
  html += "</ul>";
5650
6101
  inList = false;
5651
6102
  }
5652
- html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line)}</h2>`;
6103
+ html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line, sanitizeOptions)}</h2>`;
5653
6104
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
5654
6105
  if (!inList) {
5655
6106
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
5656
6107
  inList = true;
5657
6108
  }
5658
6109
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
5659
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText)}</li>`;
6110
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
5660
6111
  } else {
5661
6112
  if (inList) {
5662
6113
  html += "</ul>";
5663
6114
  inList = false;
5664
6115
  }
5665
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
6116
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
5666
6117
  }
5667
6118
  i++;
5668
6119
  }