@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.js CHANGED
@@ -338,16 +338,21 @@ async function sourceToArrayBuffer(source, signal) {
338
338
  metadata.mimeType = source.type || void 0;
339
339
  metadata.extension = extractExtension(source.name);
340
340
  buffer = await source.arrayBuffer();
341
- } else if (source instanceof Blob) {
341
+ } else if (source instanceof Blob || source && typeof source.arrayBuffer === "function" && typeof source.size === "number") {
342
342
  metadata.size = source.size;
343
343
  metadata.mimeType = source.type || void 0;
344
+ if (source.name) {
345
+ metadata.name = source.name;
346
+ metadata.extension = extractExtension(source.name);
347
+ }
344
348
  buffer = await source.arrayBuffer();
345
- } else if (source instanceof ArrayBuffer) {
349
+ } else if (source instanceof ArrayBuffer || Object.prototype.toString.call(source) === "[object ArrayBuffer]" || source && typeof source.byteLength === "number" && typeof source.slice === "function") {
346
350
  buffer = source;
347
- } else if (source instanceof Uint8Array) {
348
- buffer = source.buffer.slice(
349
- source.byteOffset,
350
- source.byteOffset + source.byteLength
351
+ } else if (source instanceof Uint8Array || ArrayBuffer.isView(source)) {
352
+ const view = source;
353
+ buffer = view.buffer.slice(
354
+ view.byteOffset,
355
+ view.byteOffset + view.byteLength
351
356
  );
352
357
  } else {
353
358
  throw new Error("Unsupported file source type");
@@ -432,6 +437,45 @@ function createElement(tag, attrs, ...children) {
432
437
  }
433
438
  return el;
434
439
  }
440
+ var DB_NAME = "PreviewFileTransferDB";
441
+ var DB_STORE = "transfers";
442
+ function openDB() {
443
+ return new Promise((resolve, reject) => {
444
+ if (typeof indexedDB === "undefined") {
445
+ return reject(new Error("IndexedDB is not available"));
446
+ }
447
+ const req = indexedDB.open(DB_NAME, 1);
448
+ req.onupgradeneeded = () => {
449
+ const db = req.result;
450
+ if (!db.objectStoreNames.contains(DB_STORE)) {
451
+ db.createObjectStore(DB_STORE, { keyPath: "id" });
452
+ }
453
+ };
454
+ req.onsuccess = () => resolve(req.result);
455
+ req.onerror = () => reject(req.error);
456
+ });
457
+ }
458
+ async function saveTransferPayload(id, payload) {
459
+ if (typeof window !== "undefined") {
460
+ try {
461
+ window[id] = payload;
462
+ window.__lastTransfer = payload;
463
+ } catch {
464
+ }
465
+ }
466
+ try {
467
+ const db = await openDB();
468
+ return new Promise((resolve, reject) => {
469
+ const tx = db.transaction(DB_STORE, "readwrite");
470
+ const store = tx.objectStore(DB_STORE);
471
+ store.put({ id, ...payload, timestamp: Date.now() });
472
+ tx.oncomplete = () => resolve();
473
+ tx.onerror = () => reject(tx.error);
474
+ });
475
+ } catch (e) {
476
+ console.warn("[saveTransferPayload] IndexedDB store warning:", e);
477
+ }
478
+ }
435
479
  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>`;
436
480
  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>`;
437
481
  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>`;
@@ -451,6 +495,7 @@ var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" str
451
495
  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>`;
452
496
  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>`;
453
497
  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>`;
498
+ 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>`;
454
499
  var ICON_MAP = {
455
500
  "zoom-in": ICON_ZOOM_IN,
456
501
  "zoom-out": ICON_ZOOM_OUT,
@@ -477,7 +522,9 @@ var ICON_MAP = {
477
522
  "forward-10": ICON_FAST_FORWARD,
478
523
  "rewind": ICON_REWIND,
479
524
  "replay-10": ICON_REWIND,
480
- "speed": ICON_SPEED
525
+ "speed": ICON_SPEED,
526
+ "open-window": ICON_EXTERNAL_WINDOW,
527
+ "external-window": ICON_EXTERNAL_WINDOW
481
528
  };
482
529
  var ToolbarController = class {
483
530
  el;
@@ -694,7 +741,7 @@ var ThumbnailPanel = class {
694
741
  }
695
742
  }
696
743
  };
697
- var FilePreviewViewer = class {
744
+ var FilePreviewViewer = class _FilePreviewViewer {
698
745
  plugins = [];
699
746
  activeInstance = null;
700
747
  abortController = null;
@@ -731,6 +778,7 @@ var FilePreviewViewer = class {
731
778
  * Preview a file in the given container element.
732
779
  */
733
780
  async preview(container, source, options = {}) {
781
+ this.currentOptions = options;
734
782
  this.abort();
735
783
  this.abortController = new AbortController();
736
784
  const { signal } = this.abortController;
@@ -740,7 +788,10 @@ var FilePreviewViewer = class {
740
788
  this.showLoading();
741
789
  try {
742
790
  const { buffer, metadata } = await sourceToArrayBuffer(source, signal);
743
- this.currentBuffer = buffer;
791
+ if (options.metadata) {
792
+ Object.assign(metadata, options.metadata);
793
+ }
794
+ this.currentBuffer = buffer.slice(0);
744
795
  this.currentMetadata = metadata;
745
796
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
746
797
  const fileInfo = { metadata, buffer };
@@ -770,6 +821,7 @@ var FilePreviewViewer = class {
770
821
  }
771
822
  });
772
823
  this.activeInstance = instance;
824
+ instance.openInSeparateWindow = () => this.openInSeparateWindow();
773
825
  this.hideLoading();
774
826
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
775
827
  if (options.showToolbar !== false && this.toolbar) {
@@ -779,26 +831,16 @@ var FilePreviewViewer = class {
779
831
  actions.push({
780
832
  id: "fullscreen",
781
833
  icon: "fullscreen",
782
- label: "Toggle Fullscreen",
834
+ label: "Fullscreen",
783
835
  type: "button",
784
836
  group: "view",
785
- execute: async () => {
837
+ execute: () => {
786
838
  try {
787
- const isNativeFs = !!document.fullscreenElement;
788
- const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
789
- if (!isNativeFs && !isCssFs) {
790
- if (this.wrapperEl?.requestFullscreen) {
791
- await this.wrapperEl.requestFullscreen().catch(() => {
792
- this.wrapperEl?.classList.add("fp-fullscreen-active");
793
- });
794
- } else {
795
- this.wrapperEl?.classList.add("fp-fullscreen-active");
796
- }
839
+ if (!document.fullscreenElement) {
840
+ this.wrapperEl?.requestFullscreen?.();
841
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
797
842
  } else {
798
- if (document.fullscreenElement) {
799
- await document.exitFullscreen().catch(() => {
800
- });
801
- }
843
+ document.exitFullscreen?.();
802
844
  this.wrapperEl?.classList.remove("fp-fullscreen-active");
803
845
  }
804
846
  } catch {
@@ -810,6 +852,28 @@ var FilePreviewViewer = class {
810
852
  }
811
853
  });
812
854
  }
855
+ const openWinAction = actions.find((a) => a.id === "open-window");
856
+ if (openWinAction) {
857
+ if (options?._isSeparateWindow) {
858
+ const idx = actions.indexOf(openWinAction);
859
+ if (idx !== -1) actions.splice(idx, 1);
860
+ } else {
861
+ openWinAction.execute = () => {
862
+ this.openInSeparateWindow();
863
+ };
864
+ }
865
+ } else if (!options?._isSeparateWindow) {
866
+ actions.push({
867
+ id: "open-window",
868
+ icon: "open-window",
869
+ label: "Open in Separate Full Window",
870
+ type: "button",
871
+ group: "actions",
872
+ execute: () => {
873
+ this.openInSeparateWindow();
874
+ }
875
+ });
876
+ }
813
877
  this.toolbar.update(actions);
814
878
  this.toolbar.show();
815
879
  }
@@ -842,6 +906,104 @@ var FilePreviewViewer = class {
842
906
  throw error;
843
907
  }
844
908
  }
909
+ /**
910
+ * Opens the current file preview in a separate full browser window.
911
+ */
912
+ openInSeparateWindow() {
913
+ if (!this.currentBuffer) {
914
+ console.warn("[FilePreviewViewer] No active file buffer to open in separate window");
915
+ return null;
916
+ }
917
+ if (this.currentOptions.onOpenSeparateWindow) {
918
+ return this.currentOptions.onOpenSeparateWindow({
919
+ buffer: this.currentBuffer,
920
+ metadata: this.currentMetadata || { name: "Document" },
921
+ options: this.currentOptions
922
+ });
923
+ }
924
+ const transferId = "fp_win_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
925
+ let clonedBuffer;
926
+ try {
927
+ clonedBuffer = this.currentBuffer.slice(0);
928
+ } catch {
929
+ clonedBuffer = this.currentBuffer;
930
+ }
931
+ const payload = {
932
+ buffer: clonedBuffer,
933
+ metadata: this.currentMetadata ? { ...this.currentMetadata } : void 0,
934
+ options: { ...this.currentOptions, _isSeparateWindow: true }
935
+ };
936
+ if (typeof window !== "undefined") {
937
+ try {
938
+ window[transferId] = payload;
939
+ window.__lastTransfer = payload;
940
+ } catch {
941
+ }
942
+ }
943
+ saveTransferPayload(transferId, payload).catch((err) => {
944
+ console.warn("[FilePreviewViewer] Transfer payload save warning:", err);
945
+ });
946
+ let targetUrl = null;
947
+ if (this.currentOptions.standaloneViewerUrl) {
948
+ const u = new URL(this.currentOptions.standaloneViewerUrl, window.location.href);
949
+ u.searchParams.set("mode", "fullscreen");
950
+ u.searchParams.set("transferId", transferId);
951
+ targetUrl = u.toString();
952
+ } else if (typeof window !== "undefined" && window.location?.href && !window.location.href.startsWith("about:")) {
953
+ const u = new URL(window.location.href);
954
+ u.searchParams.set("mode", "fullscreen");
955
+ u.searchParams.set("transferId", transferId);
956
+ targetUrl = u.toString();
957
+ }
958
+ if (targetUrl) {
959
+ const newWin2 = window.open(targetUrl, "_blank");
960
+ if (!newWin2) {
961
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
962
+ return null;
963
+ }
964
+ return newWin2;
965
+ }
966
+ const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
967
+ const newWin = window.open("", "_blank");
968
+ if (!newWin) {
969
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
970
+ return null;
971
+ }
972
+ newWin.document.title = title;
973
+ newWin.document.body.style.margin = "0";
974
+ newWin.document.body.style.padding = "0";
975
+ newWin.document.body.style.width = "100vw";
976
+ newWin.document.body.style.height = "100vh";
977
+ newWin.document.body.style.overflow = "hidden";
978
+ newWin.document.body.style.backgroundColor = "#f8fafc";
979
+ const headNodes = document.querySelectorAll('link[rel="stylesheet"], style');
980
+ headNodes.forEach((node) => {
981
+ newWin.document.head.appendChild(node.cloneNode(true));
982
+ });
983
+ const root = newWin.document.createElement("div");
984
+ root.id = "full-window-preview-root";
985
+ root.style.width = "100%";
986
+ root.style.height = "100%";
987
+ root.style.overflow = "hidden";
988
+ newWin.document.body.appendChild(root);
989
+ const separateViewer = new _FilePreviewViewer();
990
+ for (const plugin of this.plugins) {
991
+ separateViewer.registerPlugin(plugin);
992
+ }
993
+ separateViewer.preview(root, this.currentBuffer.slice(0), {
994
+ ...this.currentOptions,
995
+ showToolbar: true,
996
+ toolbarPosition: "top",
997
+ metadata: this.currentMetadata || void 0,
998
+ _isSeparateWindow: true
999
+ }).catch((err) => {
1000
+ console.error("[FilePreviewViewer] Error rendering in separate window:", err);
1001
+ });
1002
+ newWin.addEventListener("beforeunload", () => {
1003
+ separateViewer.destroy();
1004
+ });
1005
+ return newWin;
1006
+ }
845
1007
  /**
846
1008
  * Subscribe to viewer events.
847
1009
  */
@@ -1247,8 +1409,20 @@ var CfbfReader = class {
1247
1409
  }
1248
1410
  };
1249
1411
  if (typeof window !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
1250
- if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
1251
- pdfjsLib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1412
+ if (!pdfjsLib.GlobalWorkerOptions.workerPort && !pdfjsLib.GlobalWorkerOptions.workerSrc) {
1413
+ const customWorker = window.__PDF_WORKER_SRC__;
1414
+ if (customWorker) {
1415
+ pdfjsLib.GlobalWorkerOptions.workerSrc = customWorker;
1416
+ } else {
1417
+ try {
1418
+ pdfjsLib.GlobalWorkerOptions.workerPort = new Worker(
1419
+ new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url),
1420
+ { type: "module" }
1421
+ );
1422
+ } catch {
1423
+ pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.mjs";
1424
+ }
1425
+ }
1252
1426
  }
1253
1427
  }
1254
1428
  var PdfPlugin = class {
@@ -1342,6 +1516,14 @@ var PdfPlugin = class {
1342
1516
  type: "button",
1343
1517
  group: "actions",
1344
1518
  execute: () => instance.print?.()
1519
+ },
1520
+ {
1521
+ id: "open-window",
1522
+ icon: "open-window",
1523
+ label: "Open in Separate Full Window",
1524
+ type: "button",
1525
+ group: "actions",
1526
+ execute: () => instance.openInSeparateWindow?.()
1345
1527
  }
1346
1528
  ];
1347
1529
  }
@@ -1391,18 +1573,21 @@ var PdfPlugin = class {
1391
1573
  indicator.style.pointerEvents = "none";
1392
1574
  container.appendChild(indicator);
1393
1575
  ctx.container.appendChild(container);
1576
+ const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1577
+ const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
1394
1578
  const loadingTask = pdfjsLib.getDocument({
1395
- data: new Uint8Array(ctx.buffer),
1396
- cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/cmaps/`,
1579
+ data: new Uint8Array(ctx.buffer.slice(0)),
1580
+ cMapUrl: cmapsUrl,
1397
1581
  cMapPacked: true,
1398
- standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/standard_fonts/`
1582
+ standardFontDataUrl: standardFontsUrl,
1583
+ verbosity: 0
1399
1584
  });
1400
1585
  const pdfDoc = await loadingTask.promise;
1401
1586
  const totalPages = Math.max(1, pdfDoc.numPages);
1402
1587
  let currentPage = 1;
1403
1588
  let zoomScale = 1;
1404
1589
  let rotation = 0;
1405
- let fitMode = "width";
1590
+ let fitMode = "page";
1406
1591
  let currentRenderTask = null;
1407
1592
  const renderPage = async (pageNum) => {
1408
1593
  if (currentRenderTask) {
@@ -1422,15 +1607,15 @@ var PdfPlugin = class {
1422
1607
  const containerWidth = container.clientWidth || 900;
1423
1608
  const containerHeight = container.clientHeight || 700;
1424
1609
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1425
- const availWidth = Math.max(280, containerWidth - 48);
1426
- const availHeight = Math.max(280, containerHeight - 88);
1610
+ const availWidth = Math.max(320, containerWidth - 48);
1611
+ const availHeight = Math.max(550, containerHeight - 88);
1427
1612
  const scaleW = availWidth / unscaledVp.width;
1428
1613
  const scaleH = availHeight / unscaledVp.height;
1429
1614
  let fitScale;
1430
1615
  if (fitMode === "page") {
1431
- fitScale = Math.max(0.5, Math.min(scaleW, scaleH));
1616
+ fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
1432
1617
  } else {
1433
- fitScale = Math.max(0.65, Math.min(1.15, scaleW));
1618
+ fitScale = Math.max(0.65, Math.min(1.25, scaleW));
1434
1619
  }
1435
1620
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1436
1621
  const pixelRatio = window.devicePixelRatio || 1;
@@ -1456,7 +1641,7 @@ var PdfPlugin = class {
1456
1641
  currentRenderTask = null;
1457
1642
  }
1458
1643
  };
1459
- await renderPage(1);
1644
+ renderPage(1);
1460
1645
  let resizeTimer = null;
1461
1646
  const resizeObserver = new ResizeObserver(() => {
1462
1647
  if (resizeTimer) clearTimeout(resizeTimer);
@@ -1500,7 +1685,7 @@ var PdfPlugin = class {
1500
1685
  renderPage(currentPage);
1501
1686
  },
1502
1687
  fitToPage: () => {
1503
- fitMode = fitMode === "width" ? "page" : "width";
1688
+ fitMode = fitMode === "page" ? "width" : "page";
1504
1689
  zoomScale = 1;
1505
1690
  rotation = 0;
1506
1691
  renderPage(currentPage);
@@ -1517,6 +1702,7 @@ var PdfPlugin = class {
1517
1702
  getPageCount: () => totalPages,
1518
1703
  getCurrentPage: () => currentPage,
1519
1704
  goToPage: (page) => {
1705
+ container.scrollTop = 0;
1520
1706
  renderPage(page);
1521
1707
  },
1522
1708
  download: () => {
@@ -1976,6 +2162,14 @@ var DocxPlugin = class {
1976
2162
  type: "button",
1977
2163
  group: "actions",
1978
2164
  execute: () => instance.print?.()
2165
+ },
2166
+ {
2167
+ id: "open-window",
2168
+ icon: "open-window",
2169
+ label: "Open in Separate Full Window",
2170
+ type: "button",
2171
+ group: "actions",
2172
+ execute: () => instance.openInSeparateWindow?.()
1979
2173
  }
1980
2174
  );
1981
2175
  return actions;
@@ -2021,13 +2215,45 @@ var DocxPlugin = class {
2021
2215
  ignoreFonts: true,
2022
2216
  // Avoid crashes on embedded obfuscated fonts
2023
2217
  breakPages: true,
2024
- experimental: true
2218
+ experimental: true,
2219
+ ignoreLastRenderedPageBreak: false,
2220
+ // Honor Word's exact page breaks!
2221
+ renderHeaders: true,
2222
+ renderFooters: true,
2223
+ renderFootnotes: true,
2224
+ renderEndnotes: true,
2225
+ useBase64URL: true
2025
2226
  });
2026
2227
  if (!ctx.container.contains(wrapper)) {
2027
2228
  ctx.container.appendChild(wrapper);
2028
2229
  }
2029
2230
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2030
2231
  renderedSuccessfully = true;
2232
+ try {
2233
+ const unzipped = unzipSync(new Uint8Array(ctx.buffer));
2234
+ const chartKeys = Object.keys(unzipped).filter((k) => k.replace(/^[./\\]+/, "").toLowerCase().startsWith("word/charts/chart") && k.endsWith(".xml")).sort();
2235
+ if (chartKeys.length > 0) {
2236
+ const allDivs = Array.from(wrapper.querySelectorAll("div"));
2237
+ const emptyContainers = allDivs.filter((div) => {
2238
+ const st = div.getAttribute("style") || "";
2239
+ return st.includes("width:") && st.includes("height:") && div.children.length === 0 && (div.textContent?.trim().length ?? 0) === 0;
2240
+ });
2241
+ chartKeys.forEach((cKey, idx) => {
2242
+ const target = emptyContainers[idx];
2243
+ if (target) {
2244
+ const xmlStr = strFromU8(unzipped[cKey]);
2245
+ const svg = this.parseAndRenderChartSvg(xmlStr);
2246
+ if (svg) {
2247
+ target.innerHTML = svg;
2248
+ target.style.display = "block";
2249
+ target.style.margin = "12px auto";
2250
+ }
2251
+ }
2252
+ });
2253
+ }
2254
+ } catch (chartErr) {
2255
+ console.warn("[DocxPlugin] Non-critical error rendering DrawingML charts:", chartErr);
2256
+ }
2031
2257
  }
2032
2258
  } catch (err) {
2033
2259
  console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
@@ -2042,11 +2268,14 @@ var DocxPlugin = class {
2042
2268
  renderedSuccessfully = true;
2043
2269
  } catch (fallbackErr) {
2044
2270
  console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
2271
+ const isCorrupt = fallbackErr?.message?.includes("invalid zip") || fallbackErr?.message?.includes("corrupted");
2045
2272
  wrapper.innerHTML = `
2046
- <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
2047
- <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
2048
- <h3 style="margin: 0 0 8px; color: #1e293b;">${ctx.metadata.name || "Word Document"}</h3>
2049
- <p style="color: #64748b; margin: 0;">Could not parse document content</p>
2273
+ <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;">
2274
+ <div style="font-size:48px; margin-bottom: 16px;">${isCorrupt ? "\u26A0\uFE0F" : "\u{1F4C4}"}</div>
2275
+ <h3 style="margin: 0 0 8px; color: #1e293b; font-size: 18px;">${ctx.metadata.name || "Word Document"}</h3>
2276
+ <p style="color: #64748b; margin: 0 0 16px; font-size: 14px; line-height: 1.5;">
2277
+ ${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."}
2278
+ </p>
2050
2279
  </div>
2051
2280
  `;
2052
2281
  if (!ctx.container.contains(wrapper)) {
@@ -2056,50 +2285,74 @@ var DocxPlugin = class {
2056
2285
  }
2057
2286
  let sections = Array.from(wrapper.querySelectorAll("section.docx"));
2058
2287
  const cards = Array.from(wrapper.querySelectorAll(".fp-docx-page-card"));
2059
- if (sections.length === 1 && cards.length === 0) {
2060
- const singleSec = sections[0];
2061
- const pageH = 1056;
2062
- const secH = singleSec.offsetHeight || singleSec.scrollHeight;
2063
- if (secH > pageH * 1.25) {
2064
- const children = Array.from(singleSec.children);
2065
- if (children.length > 1) {
2288
+ if (sections.length > 0 && cards.length === 0) {
2289
+ const finalSections = [];
2290
+ for (const singleSec of sections) {
2291
+ const contentContainer = singleSec.querySelector("article") || singleSec;
2292
+ const children = Array.from(contentContainer.children);
2293
+ const pageH = singleSec.offsetHeight > 1300 ? 1122 : Math.max(1056, singleSec.offsetHeight);
2294
+ const secH = singleSec.scrollHeight || singleSec.offsetHeight;
2295
+ if (secH > pageH * 1.25 && children.length > 1) {
2296
+ const childHeights = children.map((c) => {
2297
+ const rectH = c.getBoundingClientRect().height;
2298
+ const offH = c.offsetHeight;
2299
+ const textLen = c.textContent?.trim().length || 0;
2300
+ const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
2301
+ return Math.max(rectH, offH, estH);
2302
+ });
2066
2303
  const parent = singleSec.parentElement || wrapper;
2067
- const newSections = [singleSec];
2068
- singleSec.innerHTML = "";
2304
+ const headerEl = singleSec.querySelector("header");
2305
+ const footerEl = singleSec.querySelector("footer");
2306
+ contentContainer.innerHTML = "";
2069
2307
  singleSec.style.minHeight = `${pageH}px`;
2070
- singleSec.style.maxHeight = `${pageH}px`;
2071
- singleSec.style.overflow = "hidden";
2072
2308
  singleSec.style.boxSizing = "border-box";
2309
+ let curContent = contentContainer;
2073
2310
  let curSec = singleSec;
2074
2311
  let curH = 0;
2075
- const maxH = pageH - 96;
2312
+ const maxH = pageH - 140;
2313
+ finalSections.push(singleSec);
2076
2314
  for (let i = 0; i < children.length; i++) {
2077
2315
  const child = children[i];
2078
- curSec.appendChild(child);
2079
- const chH = child.offsetHeight || 28;
2316
+ const chH = childHeights[i];
2317
+ curContent.appendChild(child);
2080
2318
  curH += chH;
2081
2319
  if (curH >= maxH && i < children.length - 1) {
2082
2320
  const nextSec = document.createElement("section");
2083
2321
  nextSec.className = singleSec.className;
2084
2322
  nextSec.style.cssText = singleSec.style.cssText;
2085
- nextSec.style.width = singleSec.style.width || "816px";
2086
2323
  nextSec.style.minHeight = `${pageH}px`;
2087
- nextSec.style.maxHeight = `${pageH}px`;
2088
- nextSec.style.overflow = "hidden";
2089
2324
  nextSec.style.boxSizing = "border-box";
2090
2325
  nextSec.style.backgroundColor = "#ffffff";
2091
2326
  nextSec.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
2092
2327
  nextSec.style.borderRadius = "4px";
2093
2328
  nextSec.style.marginBottom = "24px";
2094
- parent.appendChild(nextSec);
2095
- newSections.push(nextSec);
2329
+ if (headerEl) {
2330
+ nextSec.appendChild(headerEl.cloneNode(true));
2331
+ }
2332
+ const nextArticle = document.createElement("article");
2333
+ if (contentContainer.tagName.toLowerCase() === "article") {
2334
+ nextArticle.style.cssText = contentContainer.style.cssText;
2335
+ }
2336
+ nextSec.appendChild(nextArticle);
2337
+ if (footerEl) {
2338
+ nextSec.appendChild(footerEl.cloneNode(true));
2339
+ }
2340
+ if (curSec.nextSibling) {
2341
+ parent.insertBefore(nextSec, curSec.nextSibling);
2342
+ } else {
2343
+ parent.appendChild(nextSec);
2344
+ }
2345
+ finalSections.push(nextSec);
2096
2346
  curSec = nextSec;
2347
+ curContent = nextArticle;
2097
2348
  curH = 0;
2098
2349
  }
2099
2350
  }
2100
- sections = newSections;
2351
+ } else {
2352
+ finalSections.push(singleSec);
2101
2353
  }
2102
2354
  }
2355
+ sections = finalSections;
2103
2356
  }
2104
2357
  const pageElements = sections.length > 0 ? sections : cards;
2105
2358
  const totalPages = Math.max(1, pageElements.length);
@@ -2137,6 +2390,7 @@ var DocxPlugin = class {
2137
2390
  if (indicator) {
2138
2391
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2139
2392
  }
2393
+ ctx.container.scrollTop = 0;
2140
2394
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2141
2395
  };
2142
2396
  if (totalPages > 1) {
@@ -2490,6 +2744,88 @@ var DocxPlugin = class {
2490
2744
  }
2491
2745
  return result;
2492
2746
  }
2747
+ parseAndRenderChartSvg(xmlStr, width = 500, height = 260) {
2748
+ const catMatches = [...xmlStr.matchAll(/<c:cat>[\s\S]*?<c:strCache>([\s\S]*?)<\/c:strCache>/g)];
2749
+ let categories = [];
2750
+ if (catMatches.length > 0) {
2751
+ categories = [...catMatches[0][1].matchAll(/<c:v>([^<]+)<\/c:v>/g)].map((m) => m[1]);
2752
+ }
2753
+ if (categories.length === 0) {
2754
+ categories = ["Category 1", "Category 2", "Category 3", "Category 4"];
2755
+ }
2756
+ const defaultColors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021", "#83caff"];
2757
+ const sers = [...xmlStr.matchAll(/<c:ser>([\s\S]*?)<\/c:ser>/g)];
2758
+ const series = [];
2759
+ sers.forEach((s, sIdx) => {
2760
+ const titleMatch = s[1].match(/<c:tx>[\s\S]*?<c:v>([^<]+)<\/c:v>/);
2761
+ const title = titleMatch ? titleMatch[1] : `Series ${sIdx + 1}`;
2762
+ const clrMatch = s[1].match(/<a:srgbClr\s+val="([^"]+)"/);
2763
+ const color = clrMatch ? "#" + clrMatch[1] : defaultColors[sIdx % defaultColors.length];
2764
+ const valMatch = s[1].match(/<c:val>[\s\S]*?<c:numCache>([\s\S]*?)<\/c:numCache>/);
2765
+ let values = [];
2766
+ if (valMatch) {
2767
+ 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);
2768
+ }
2769
+ series.push({ title, color, values });
2770
+ });
2771
+ if (series.length === 0) return "";
2772
+ let maxVal = 10;
2773
+ series.forEach((s) => s.values.forEach((v) => {
2774
+ if (v > maxVal) maxVal = v;
2775
+ }));
2776
+ maxVal = Math.ceil(maxVal * 1.15);
2777
+ if (maxVal % 2 !== 0) maxVal++;
2778
+ const padLeft = 45;
2779
+ const padBottom = 55;
2780
+ const padTop = 20;
2781
+ const padRight = 20;
2782
+ const plotW = width - padLeft - padRight;
2783
+ const plotH = height - padTop - padBottom;
2784
+ const yTicks = 5;
2785
+ let gridLines = "";
2786
+ for (let i = 0; i <= yTicks; i++) {
2787
+ const val = maxVal / yTicks * i;
2788
+ const y = padTop + plotH - val / maxVal * plotH;
2789
+ gridLines += `<line x1="${padLeft}" y1="${y}" x2="${padLeft + plotW}" y2="${y}" stroke="#e2e8f0" stroke-width="1" />`;
2790
+ 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>`;
2791
+ }
2792
+ const numCats = categories.length;
2793
+ const numSers = series.length;
2794
+ const groupW = plotW / numCats;
2795
+ const barW = Math.max(8, Math.min(28, groupW * 0.7 / numSers));
2796
+ const groupPad = (groupW - barW * numSers) / 2;
2797
+ let bars = "";
2798
+ let catLabels = "";
2799
+ for (let c = 0; c < numCats; c++) {
2800
+ const catX = padLeft + c * groupW;
2801
+ 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>`;
2802
+ for (let s = 0; s < numSers; s++) {
2803
+ const val = series[s].values[c] ?? 0;
2804
+ const bH = Math.max(0, val / maxVal * plotH);
2805
+ const bX = catX + groupPad + s * barW;
2806
+ const bY = padTop + plotH - bH;
2807
+ bars += `<rect x="${bX}" y="${bY}" width="${barW - 2}" height="${bH}" fill="${series[s].color}" rx="1" />`;
2808
+ }
2809
+ }
2810
+ let legend = "";
2811
+ const legY = height - 12;
2812
+ let legX = padLeft + (plotW - numSers * 100) / 2;
2813
+ series.forEach((s) => {
2814
+ legend += `<rect x="${legX}" y="${legY - 9}" width="10" height="10" fill="${s.color}" rx="2" />`;
2815
+ legend += `<text x="${legX + 15}" y="${legY}" font-size="11" fill="#475569" font-family="Calibri, sans-serif">${s.title}</text>`;
2816
+ legX += 95;
2817
+ });
2818
+ return `
2819
+ <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">
2820
+ ${gridLines}
2821
+ <line x1="${padLeft}" y1="${padTop + plotH}" x2="${padLeft + plotW}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2822
+ <line x1="${padLeft}" y1="${padTop}" x2="${padLeft}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2823
+ ${bars}
2824
+ ${catLabels}
2825
+ ${legend}
2826
+ </svg>
2827
+ `.trim();
2828
+ }
2493
2829
  };
2494
2830
  function docxPlugin() {
2495
2831
  return new DocxPlugin();
@@ -3056,6 +3392,16 @@ var CodePlugin = class {
3056
3392
  execute: () => {
3057
3393
  instance.print?.();
3058
3394
  }
3395
+ },
3396
+ {
3397
+ id: "open-window",
3398
+ icon: "open-window",
3399
+ label: "Open in Separate Full Window",
3400
+ type: "button",
3401
+ group: "actions",
3402
+ execute: () => {
3403
+ instance.openInSeparateWindow?.();
3404
+ }
3059
3405
  }
3060
3406
  );
3061
3407
  return actions;
@@ -3069,17 +3415,39 @@ var CodePlugin = class {
3069
3415
  let rawPages = [];
3070
3416
  if (isTxt) {
3071
3417
  const explicitPages = fullText.split(/(?:\f|\x0C)/);
3418
+ const charsPerLine = 85;
3419
+ const maxVisualLines = 45;
3420
+ const charsPerPage = charsPerLine * maxVisualLines;
3072
3421
  for (const ep of explicitPages) {
3073
3422
  const lines = ep.split(/\r?\n/);
3074
3423
  let currentChunk = [];
3424
+ let currentLines = 0;
3075
3425
  for (let i = 0; i < lines.length; i++) {
3076
- currentChunk.push(lines[i]);
3077
- if (currentChunk.length >= 46) {
3426
+ const line = lines[i];
3427
+ const vLines = Math.max(1, Math.ceil((line.length || 1) / charsPerLine));
3428
+ if (currentLines + vLines > maxVisualLines && currentChunk.length > 0) {
3078
3429
  rawPages.push(currentChunk.join("\n"));
3079
3430
  currentChunk = [];
3431
+ currentLines = 0;
3432
+ }
3433
+ if (vLines > maxVisualLines) {
3434
+ let remaining = line;
3435
+ while (remaining.length > charsPerPage) {
3436
+ let splitIdx = remaining.lastIndexOf(" ", charsPerPage);
3437
+ if (splitIdx < charsPerPage * 0.75) splitIdx = charsPerPage;
3438
+ rawPages.push(remaining.slice(0, splitIdx));
3439
+ remaining = remaining.slice(splitIdx).trimStart();
3440
+ }
3441
+ if (remaining.length > 0) {
3442
+ currentChunk.push(remaining);
3443
+ currentLines = Math.ceil(remaining.length / charsPerLine);
3444
+ }
3445
+ } else {
3446
+ currentChunk.push(line);
3447
+ currentLines += vLines;
3080
3448
  }
3081
3449
  }
3082
- if (currentChunk.length > 0 || lines.length === 0) {
3450
+ if (currentChunk.length > 0) {
3083
3451
  rawPages.push(currentChunk.join("\n"));
3084
3452
  }
3085
3453
  }
@@ -3189,6 +3557,7 @@ var CodePlugin = class {
3189
3557
  if (indicator) {
3190
3558
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3191
3559
  }
3560
+ container.scrollTop = 0;
3192
3561
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3193
3562
  };
3194
3563
  if (totalPages > 1) {
@@ -4201,6 +4570,14 @@ var RtfPlugin = class {
4201
4570
  type: "button",
4202
4571
  group: "actions",
4203
4572
  execute: () => instance.print?.()
4573
+ },
4574
+ {
4575
+ id: "open-window",
4576
+ icon: "open-window",
4577
+ label: "Open in Separate Full Window",
4578
+ type: "button",
4579
+ group: "actions",
4580
+ execute: () => instance.openInSeparateWindow?.()
4204
4581
  }
4205
4582
  );
4206
4583
  return actions;
@@ -4253,11 +4630,55 @@ var RtfPlugin = class {
4253
4630
  }
4254
4631
  const doc = new RTFJS.Document(ctx.buffer, {});
4255
4632
  const htmlElements = await doc.render();
4256
- pageElements = htmlElements;
4257
- for (let i = 0; i < htmlElements.length; i++) {
4258
- const el = htmlElements[i];
4259
- el.style.display = i === 0 ? "block" : "none";
4260
- wrapper.appendChild(el);
4633
+ const contentNodes = [];
4634
+ for (const item of htmlElements) {
4635
+ if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4636
+ contentNodes.push(...Array.from(item.children));
4637
+ } else {
4638
+ contentNodes.push(item);
4639
+ }
4640
+ }
4641
+ wrapper.innerHTML = "";
4642
+ contentNodes.forEach((node) => wrapper.appendChild(node));
4643
+ const childHeights = contentNodes.map((c) => {
4644
+ const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4645
+ const offH = c.offsetHeight || 0;
4646
+ const textLen = c.textContent?.trim().length || 0;
4647
+ const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
4648
+ return Math.max(rectH, offH, estH);
4649
+ });
4650
+ wrapper.innerHTML = "";
4651
+ const createRtfCard = () => {
4652
+ const card = document.createElement("div");
4653
+ card.className = "fp-rtf-page-card";
4654
+ card.style.backgroundColor = "#ffffff";
4655
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4656
+ card.style.borderRadius = "4px";
4657
+ card.style.padding = "72px 56px";
4658
+ card.style.width = "816px";
4659
+ card.style.minHeight = "1056px";
4660
+ card.style.boxSizing = "border-box";
4661
+ card.style.marginBottom = "24px";
4662
+ card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4663
+ card.style.lineHeight = "1.6";
4664
+ return card;
4665
+ };
4666
+ let curCard = createRtfCard();
4667
+ wrapper.appendChild(curCard);
4668
+ pageElements = [curCard];
4669
+ let curH = 0;
4670
+ const maxH = 912;
4671
+ for (let i = 0; i < contentNodes.length; i++) {
4672
+ const child = contentNodes[i];
4673
+ const chH = childHeights[i];
4674
+ curCard.appendChild(child);
4675
+ curH += chH;
4676
+ if (curH >= maxH && i < contentNodes.length - 1) {
4677
+ curCard = createRtfCard();
4678
+ wrapper.appendChild(curCard);
4679
+ pageElements.push(curCard);
4680
+ curH = 0;
4681
+ }
4261
4682
  }
4262
4683
  } catch (err) {
4263
4684
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
@@ -4323,6 +4744,7 @@ var RtfPlugin = class {
4323
4744
  if (indicator) {
4324
4745
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4325
4746
  }
4747
+ ctx.container.scrollTop = 0;
4326
4748
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4327
4749
  };
4328
4750
  if (totalPages > 1) {
@@ -4624,6 +5046,14 @@ var OpenDocumentPlugin = class {
4624
5046
  type: "button",
4625
5047
  group: "actions",
4626
5048
  execute: () => instance.print?.()
5049
+ },
5050
+ {
5051
+ id: "open-window",
5052
+ icon: "open-window",
5053
+ label: "Open in Separate Full Window",
5054
+ type: "button",
5055
+ group: "actions",
5056
+ execute: () => instance.openInSeparateWindow?.()
4627
5057
  }
4628
5058
  );
4629
5059
  return actions;
@@ -4794,12 +5224,9 @@ var OpenDocumentPlugin = class {
4794
5224
  page.style.backgroundColor = "#ffffff";
4795
5225
  page.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
4796
5226
  page.style.borderRadius = "4px";
4797
- page.style.boxSizing = "border-box";
4798
5227
  page.style.display = idx === 0 ? "block" : "none";
4799
- page.style.position = "absolute";
4800
- page.style.top = "0";
4801
- page.style.left = "50%";
4802
- page.style.transform = "translateX(-50%)";
5228
+ page.style.margin = "0 auto 24px";
5229
+ page.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
4803
5230
  elements.forEach((el) => page.appendChild(el));
4804
5231
  wrapper.appendChild(page);
4805
5232
  slides.push(page);
@@ -4837,6 +5264,7 @@ var OpenDocumentPlugin = class {
4837
5264
  if (indicator) {
4838
5265
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4839
5266
  }
5267
+ container.scrollTop = 0;
4840
5268
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4841
5269
  };
4842
5270
  return {
@@ -5214,6 +5642,14 @@ var DocPlugin = class {
5214
5642
  type: "button",
5215
5643
  group: "actions",
5216
5644
  execute: () => instance.print?.()
5645
+ },
5646
+ {
5647
+ id: "open-window",
5648
+ icon: "open-window",
5649
+ label: "Open in Separate Full Window",
5650
+ type: "button",
5651
+ group: "actions",
5652
+ execute: () => instance.openInSeparateWindow?.()
5217
5653
  }
5218
5654
  );
5219
5655
  return actions;
@@ -5329,6 +5765,7 @@ var DocPlugin = class {
5329
5765
  if (indicator) {
5330
5766
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5331
5767
  }
5768
+ container.scrollTop = 0;
5332
5769
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5333
5770
  };
5334
5771
  if (totalPages > 1) {
@@ -5504,47 +5941,69 @@ var DocPlugin = class {
5504
5941
  heuristicTextExtraction(buffer) {
5505
5942
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5506
5943
  }
5944
+ cleanWordDocFields(text) {
5945
+ if (!text) return "";
5946
+ let cleaned = text.replace(
5947
+ /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
5948
+ (_match, url, label) => {
5949
+ const cleanUrl = url.trim();
5950
+ const cleanLabel = label.trim() || cleanUrl;
5951
+ return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
5952
+ }
5953
+ );
5954
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
5955
+ cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
5956
+ cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
5957
+ return cleaned;
5958
+ }
5507
5959
  splitIntoPages(text) {
5508
5960
  if (!text) return [""];
5509
- 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);
5510
- if (explicitParts.length === 0) explicitParts.push(text);
5511
- const maxLinesPerPage = 48;
5961
+ const cleanedText = this.cleanWordDocFields(text);
5962
+ const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
5963
+ 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);
5964
+ if (explicitParts.length === 0) explicitParts.push(normalized);
5965
+ const maxLinesPerPage = 32;
5966
+ const charsPerLine = 80;
5512
5967
  const finalPages = [];
5513
5968
  for (const part of explicitParts) {
5514
- const lines = part.split(/\r?\n/);
5969
+ const lines = part.split("\n");
5515
5970
  let currentLines = [];
5516
5971
  let count = 0;
5517
5972
  for (const line of lines) {
5518
- currentLines.push(line);
5519
- count++;
5520
- if (count >= maxLinesPerPage) {
5973
+ const plainLine = line.replace(/<[^>]+>/g, "");
5974
+ const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
5975
+ if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
5521
5976
  finalPages.push(currentLines.join("\n"));
5522
5977
  currentLines = [];
5523
5978
  count = 0;
5524
5979
  }
5980
+ currentLines.push(line);
5981
+ count += vLines;
5525
5982
  }
5526
5983
  if (currentLines.length > 0) {
5527
5984
  finalPages.push(currentLines.join("\n"));
5528
5985
  }
5529
5986
  }
5530
- return finalPages.length > 0 ? finalPages : [text];
5987
+ return finalPages.length > 0 ? finalPages : [normalized];
5531
5988
  }
5532
5989
  /**
5533
5990
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
5534
5991
  */
5535
5992
  formatDocToHtml(text, filename) {
5536
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
5993
+ 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");
5537
5994
  let html = "";
5538
5995
  let inList = false;
5539
5996
  let tableLines = [];
5540
5997
  const flushTable = () => {
5541
5998
  if (tableLines.length > 0) {
5542
- html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 12px;">';
5543
- for (const tLine of tableLines) {
5544
- html += "<tr>";
5545
- const cols = tLine.split(" ");
5999
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; font-family: Calibri, sans-serif;">';
6000
+ for (let rIdx = 0; rIdx < tableLines.length; rIdx++) {
6001
+ const tLine = tableLines[rIdx];
6002
+ const isHeader = rIdx === 0;
6003
+ html += `<tr style="${isHeader ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
6004
+ const cols = tLine.split(" ").filter((c) => c.trim().length > 0);
5546
6005
  for (const col of cols) {
5547
- html += `<td style="border: 1px solid #cbd5e1; padding: 6px 8px;">${DOMPurify6.sanitize(col.trim())}</td>`;
6006
+ html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px;">${DOMPurify6.sanitize(col.trim())}</td>`;
5548
6007
  }
5549
6008
  html += "</tr>";
5550
6009
  }
@@ -5557,27 +6016,18 @@ var DocPlugin = class {
5557
6016
  let line = lines[i];
5558
6017
  let tabCount = (line.match(/\t/g) || []).length;
5559
6018
  if (tabCount > 0) {
5560
- let consecutiveTableLines = 1;
5561
- let j = i + 1;
5562
- while (j < lines.length) {
5563
- const nextTabCount = (lines[j].match(/\t/g) || []).length;
5564
- if (nextTabCount === tabCount) {
5565
- consecutiveTableLines++;
5566
- j++;
5567
- } else {
5568
- break;
5569
- }
6019
+ let j = i;
6020
+ while (j < lines.length && (lines[j].match(/\t/g) || []).length > 0) {
6021
+ j++;
5570
6022
  }
5571
- if (consecutiveTableLines >= 3) {
5572
- if (inList) {
5573
- html += "</ul>";
5574
- inList = false;
5575
- }
5576
- tableLines = lines.slice(i, j);
5577
- flushTable();
5578
- i = j;
5579
- continue;
6023
+ if (inList) {
6024
+ html += "</ul>";
6025
+ inList = false;
5580
6026
  }
6027
+ tableLines = lines.slice(i, j);
6028
+ flushTable();
6029
+ i = j;
6030
+ continue;
5581
6031
  }
5582
6032
  line = line.trim();
5583
6033
  if (!line) {
@@ -5597,25 +6047,26 @@ var DocPlugin = class {
5597
6047
  i++;
5598
6048
  continue;
5599
6049
  }
6050
+ const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
5600
6051
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
5601
6052
  if (inList) {
5602
6053
  html += "</ul>";
5603
6054
  inList = false;
5604
6055
  }
5605
- 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>`;
6056
+ 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>`;
5606
6057
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
5607
6058
  if (!inList) {
5608
6059
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
5609
6060
  inList = true;
5610
6061
  }
5611
6062
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
5612
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText)}</li>`;
6063
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
5613
6064
  } else {
5614
6065
  if (inList) {
5615
6066
  html += "</ul>";
5616
6067
  inList = false;
5617
6068
  }
5618
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
6069
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
5619
6070
  }
5620
6071
  i++;
5621
6072
  }