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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vue.js CHANGED
@@ -337,16 +337,21 @@ async function sourceToArrayBuffer(source, signal) {
337
337
  metadata.mimeType = source.type || void 0;
338
338
  metadata.extension = extractExtension(source.name);
339
339
  buffer = await source.arrayBuffer();
340
- } else if (source instanceof Blob) {
340
+ } else if (source instanceof Blob || source && typeof source.arrayBuffer === "function" && typeof source.size === "number") {
341
341
  metadata.size = source.size;
342
342
  metadata.mimeType = source.type || void 0;
343
+ if (source.name) {
344
+ metadata.name = source.name;
345
+ metadata.extension = extractExtension(source.name);
346
+ }
343
347
  buffer = await source.arrayBuffer();
344
- } else if (source instanceof ArrayBuffer) {
348
+ } else if (source instanceof ArrayBuffer || Object.prototype.toString.call(source) === "[object ArrayBuffer]" || source && typeof source.byteLength === "number" && typeof source.slice === "function") {
345
349
  buffer = source;
346
- } else if (source instanceof Uint8Array) {
347
- buffer = source.buffer.slice(
348
- source.byteOffset,
349
- source.byteOffset + source.byteLength
350
+ } else if (source instanceof Uint8Array || ArrayBuffer.isView(source)) {
351
+ const view = source;
352
+ buffer = view.buffer.slice(
353
+ view.byteOffset,
354
+ view.byteOffset + view.byteLength
350
355
  );
351
356
  } else {
352
357
  throw new Error("Unsupported file source type");
@@ -431,6 +436,45 @@ function createElement(tag, attrs, ...children) {
431
436
  }
432
437
  return el;
433
438
  }
439
+ var DB_NAME = "PreviewFileTransferDB";
440
+ var DB_STORE = "transfers";
441
+ function openDB() {
442
+ return new Promise((resolve, reject) => {
443
+ if (typeof indexedDB === "undefined") {
444
+ return reject(new Error("IndexedDB is not available"));
445
+ }
446
+ const req = indexedDB.open(DB_NAME, 1);
447
+ req.onupgradeneeded = () => {
448
+ const db = req.result;
449
+ if (!db.objectStoreNames.contains(DB_STORE)) {
450
+ db.createObjectStore(DB_STORE, { keyPath: "id" });
451
+ }
452
+ };
453
+ req.onsuccess = () => resolve(req.result);
454
+ req.onerror = () => reject(req.error);
455
+ });
456
+ }
457
+ async function saveTransferPayload(id, payload) {
458
+ if (typeof window !== "undefined") {
459
+ try {
460
+ window[id] = payload;
461
+ window.__lastTransfer = payload;
462
+ } catch {
463
+ }
464
+ }
465
+ try {
466
+ const db = await openDB();
467
+ return new Promise((resolve, reject) => {
468
+ const tx = db.transaction(DB_STORE, "readwrite");
469
+ const store = tx.objectStore(DB_STORE);
470
+ store.put({ id, ...payload, timestamp: Date.now() });
471
+ tx.oncomplete = () => resolve();
472
+ tx.onerror = () => reject(tx.error);
473
+ });
474
+ } catch (e) {
475
+ console.warn("[saveTransferPayload] IndexedDB store warning:", e);
476
+ }
477
+ }
434
478
  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>`;
435
479
  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>`;
436
480
  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>`;
@@ -450,6 +494,7 @@ var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" str
450
494
  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>`;
451
495
  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>`;
452
496
  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>`;
497
+ 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>`;
453
498
  var ICON_MAP = {
454
499
  "zoom-in": ICON_ZOOM_IN,
455
500
  "zoom-out": ICON_ZOOM_OUT,
@@ -476,7 +521,9 @@ var ICON_MAP = {
476
521
  "forward-10": ICON_FAST_FORWARD,
477
522
  "rewind": ICON_REWIND,
478
523
  "replay-10": ICON_REWIND,
479
- "speed": ICON_SPEED
524
+ "speed": ICON_SPEED,
525
+ "open-window": ICON_EXTERNAL_WINDOW,
526
+ "external-window": ICON_EXTERNAL_WINDOW
480
527
  };
481
528
  var ToolbarController = class {
482
529
  el;
@@ -693,7 +740,7 @@ var ThumbnailPanel = class {
693
740
  }
694
741
  }
695
742
  };
696
- var FilePreviewViewer = class {
743
+ var FilePreviewViewer = class _FilePreviewViewer {
697
744
  plugins = [];
698
745
  activeInstance = null;
699
746
  abortController = null;
@@ -730,6 +777,7 @@ var FilePreviewViewer = class {
730
777
  * Preview a file in the given container element.
731
778
  */
732
779
  async preview(container, source, options = {}) {
780
+ this.currentOptions = options;
733
781
  this.abort();
734
782
  this.abortController = new AbortController();
735
783
  const { signal } = this.abortController;
@@ -739,7 +787,10 @@ var FilePreviewViewer = class {
739
787
  this.showLoading();
740
788
  try {
741
789
  const { buffer, metadata } = await sourceToArrayBuffer(source, signal);
742
- this.currentBuffer = buffer;
790
+ if (options.metadata) {
791
+ Object.assign(metadata, options.metadata);
792
+ }
793
+ this.currentBuffer = buffer.slice(0);
743
794
  this.currentMetadata = metadata;
744
795
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
745
796
  const fileInfo = { metadata, buffer };
@@ -769,6 +820,7 @@ var FilePreviewViewer = class {
769
820
  }
770
821
  });
771
822
  this.activeInstance = instance;
823
+ instance.openInSeparateWindow = () => this.openInSeparateWindow();
772
824
  this.hideLoading();
773
825
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
774
826
  if (options.showToolbar !== false && this.toolbar) {
@@ -778,26 +830,16 @@ var FilePreviewViewer = class {
778
830
  actions.push({
779
831
  id: "fullscreen",
780
832
  icon: "fullscreen",
781
- label: "Toggle Fullscreen",
833
+ label: "Fullscreen",
782
834
  type: "button",
783
835
  group: "view",
784
- execute: async () => {
836
+ execute: () => {
785
837
  try {
786
- const isNativeFs = !!document.fullscreenElement;
787
- const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
788
- if (!isNativeFs && !isCssFs) {
789
- if (this.wrapperEl?.requestFullscreen) {
790
- await this.wrapperEl.requestFullscreen().catch(() => {
791
- this.wrapperEl?.classList.add("fp-fullscreen-active");
792
- });
793
- } else {
794
- this.wrapperEl?.classList.add("fp-fullscreen-active");
795
- }
838
+ if (!document.fullscreenElement) {
839
+ this.wrapperEl?.requestFullscreen?.();
840
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
796
841
  } else {
797
- if (document.fullscreenElement) {
798
- await document.exitFullscreen().catch(() => {
799
- });
800
- }
842
+ document.exitFullscreen?.();
801
843
  this.wrapperEl?.classList.remove("fp-fullscreen-active");
802
844
  }
803
845
  } catch {
@@ -809,6 +851,28 @@ var FilePreviewViewer = class {
809
851
  }
810
852
  });
811
853
  }
854
+ const openWinAction = actions.find((a) => a.id === "open-window");
855
+ if (openWinAction) {
856
+ if (options?._isSeparateWindow) {
857
+ const idx = actions.indexOf(openWinAction);
858
+ if (idx !== -1) actions.splice(idx, 1);
859
+ } else {
860
+ openWinAction.execute = () => {
861
+ this.openInSeparateWindow();
862
+ };
863
+ }
864
+ } else if (!options?._isSeparateWindow) {
865
+ actions.push({
866
+ id: "open-window",
867
+ icon: "open-window",
868
+ label: "Open in Separate Full Window",
869
+ type: "button",
870
+ group: "actions",
871
+ execute: () => {
872
+ this.openInSeparateWindow();
873
+ }
874
+ });
875
+ }
812
876
  this.toolbar.update(actions);
813
877
  this.toolbar.show();
814
878
  }
@@ -841,6 +905,104 @@ var FilePreviewViewer = class {
841
905
  throw error;
842
906
  }
843
907
  }
908
+ /**
909
+ * Opens the current file preview in a separate full browser window.
910
+ */
911
+ openInSeparateWindow() {
912
+ if (!this.currentBuffer) {
913
+ console.warn("[FilePreviewViewer] No active file buffer to open in separate window");
914
+ return null;
915
+ }
916
+ if (this.currentOptions.onOpenSeparateWindow) {
917
+ return this.currentOptions.onOpenSeparateWindow({
918
+ buffer: this.currentBuffer,
919
+ metadata: this.currentMetadata || { name: "Document" },
920
+ options: this.currentOptions
921
+ });
922
+ }
923
+ const transferId = "fp_win_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
924
+ let clonedBuffer;
925
+ try {
926
+ clonedBuffer = this.currentBuffer.slice(0);
927
+ } catch {
928
+ clonedBuffer = this.currentBuffer;
929
+ }
930
+ const payload = {
931
+ buffer: clonedBuffer,
932
+ metadata: this.currentMetadata ? { ...this.currentMetadata } : void 0,
933
+ options: { ...this.currentOptions, _isSeparateWindow: true }
934
+ };
935
+ if (typeof window !== "undefined") {
936
+ try {
937
+ window[transferId] = payload;
938
+ window.__lastTransfer = payload;
939
+ } catch {
940
+ }
941
+ }
942
+ saveTransferPayload(transferId, payload).catch((err) => {
943
+ console.warn("[FilePreviewViewer] Transfer payload save warning:", err);
944
+ });
945
+ let targetUrl = null;
946
+ if (this.currentOptions.standaloneViewerUrl) {
947
+ const u = new URL(this.currentOptions.standaloneViewerUrl, window.location.href);
948
+ u.searchParams.set("mode", "fullscreen");
949
+ u.searchParams.set("transferId", transferId);
950
+ targetUrl = u.toString();
951
+ } else if (typeof window !== "undefined" && window.location?.href && !window.location.href.startsWith("about:")) {
952
+ const u = new URL(window.location.href);
953
+ u.searchParams.set("mode", "fullscreen");
954
+ u.searchParams.set("transferId", transferId);
955
+ targetUrl = u.toString();
956
+ }
957
+ if (targetUrl) {
958
+ const newWin2 = window.open(targetUrl, "_blank");
959
+ if (!newWin2) {
960
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
961
+ return null;
962
+ }
963
+ return newWin2;
964
+ }
965
+ const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
966
+ const newWin = window.open("", "_blank");
967
+ if (!newWin) {
968
+ alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
969
+ return null;
970
+ }
971
+ newWin.document.title = title;
972
+ newWin.document.body.style.margin = "0";
973
+ newWin.document.body.style.padding = "0";
974
+ newWin.document.body.style.width = "100vw";
975
+ newWin.document.body.style.height = "100vh";
976
+ newWin.document.body.style.overflow = "hidden";
977
+ newWin.document.body.style.backgroundColor = "#f8fafc";
978
+ const headNodes = document.querySelectorAll('link[rel="stylesheet"], style');
979
+ headNodes.forEach((node) => {
980
+ newWin.document.head.appendChild(node.cloneNode(true));
981
+ });
982
+ const root = newWin.document.createElement("div");
983
+ root.id = "full-window-preview-root";
984
+ root.style.width = "100%";
985
+ root.style.height = "100%";
986
+ root.style.overflow = "hidden";
987
+ newWin.document.body.appendChild(root);
988
+ const separateViewer = new _FilePreviewViewer();
989
+ for (const plugin of this.plugins) {
990
+ separateViewer.registerPlugin(plugin);
991
+ }
992
+ separateViewer.preview(root, this.currentBuffer.slice(0), {
993
+ ...this.currentOptions,
994
+ showToolbar: true,
995
+ toolbarPosition: "top",
996
+ metadata: this.currentMetadata || void 0,
997
+ _isSeparateWindow: true
998
+ }).catch((err) => {
999
+ console.error("[FilePreviewViewer] Error rendering in separate window:", err);
1000
+ });
1001
+ newWin.addEventListener("beforeunload", () => {
1002
+ separateViewer.destroy();
1003
+ });
1004
+ return newWin;
1005
+ }
844
1006
  /**
845
1007
  * Subscribe to viewer events.
846
1008
  */
@@ -1246,8 +1408,20 @@ var CfbfReader = class {
1246
1408
  }
1247
1409
  };
1248
1410
  if (typeof window !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
1249
- if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
1250
- pdfjsLib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1411
+ if (!pdfjsLib.GlobalWorkerOptions.workerPort && !pdfjsLib.GlobalWorkerOptions.workerSrc) {
1412
+ const customWorker = window.__PDF_WORKER_SRC__;
1413
+ if (customWorker) {
1414
+ pdfjsLib.GlobalWorkerOptions.workerSrc = customWorker;
1415
+ } else {
1416
+ try {
1417
+ pdfjsLib.GlobalWorkerOptions.workerPort = new Worker(
1418
+ new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url),
1419
+ { type: "module" }
1420
+ );
1421
+ } catch {
1422
+ pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.mjs";
1423
+ }
1424
+ }
1251
1425
  }
1252
1426
  }
1253
1427
  var PdfPlugin = class {
@@ -1341,6 +1515,14 @@ var PdfPlugin = class {
1341
1515
  type: "button",
1342
1516
  group: "actions",
1343
1517
  execute: () => instance.print?.()
1518
+ },
1519
+ {
1520
+ id: "open-window",
1521
+ icon: "open-window",
1522
+ label: "Open in Separate Full Window",
1523
+ type: "button",
1524
+ group: "actions",
1525
+ execute: () => instance.openInSeparateWindow?.()
1344
1526
  }
1345
1527
  ];
1346
1528
  }
@@ -1390,18 +1572,21 @@ var PdfPlugin = class {
1390
1572
  indicator.style.pointerEvents = "none";
1391
1573
  container.appendChild(indicator);
1392
1574
  ctx.container.appendChild(container);
1575
+ const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1576
+ const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
1393
1577
  const loadingTask = pdfjsLib.getDocument({
1394
- data: new Uint8Array(ctx.buffer),
1395
- cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/cmaps/`,
1578
+ data: new Uint8Array(ctx.buffer.slice(0)),
1579
+ cMapUrl: cmapsUrl,
1396
1580
  cMapPacked: true,
1397
- standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/standard_fonts/`
1581
+ standardFontDataUrl: standardFontsUrl,
1582
+ verbosity: 0
1398
1583
  });
1399
1584
  const pdfDoc = await loadingTask.promise;
1400
1585
  const totalPages = Math.max(1, pdfDoc.numPages);
1401
1586
  let currentPage = 1;
1402
1587
  let zoomScale = 1;
1403
1588
  let rotation = 0;
1404
- let fitMode = "width";
1589
+ let fitMode = "page";
1405
1590
  let currentRenderTask = null;
1406
1591
  const renderPage = async (pageNum) => {
1407
1592
  if (currentRenderTask) {
@@ -1421,15 +1606,15 @@ var PdfPlugin = class {
1421
1606
  const containerWidth = container.clientWidth || 900;
1422
1607
  const containerHeight = container.clientHeight || 700;
1423
1608
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1424
- const availWidth = Math.max(280, containerWidth - 48);
1425
- const availHeight = Math.max(280, containerHeight - 88);
1609
+ const availWidth = Math.max(320, containerWidth - 48);
1610
+ const availHeight = Math.max(550, containerHeight - 88);
1426
1611
  const scaleW = availWidth / unscaledVp.width;
1427
1612
  const scaleH = availHeight / unscaledVp.height;
1428
1613
  let fitScale;
1429
1614
  if (fitMode === "page") {
1430
- fitScale = Math.max(0.5, Math.min(scaleW, scaleH));
1615
+ fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
1431
1616
  } else {
1432
- fitScale = Math.max(0.65, Math.min(1.15, scaleW));
1617
+ fitScale = Math.max(0.65, Math.min(1.25, scaleW));
1433
1618
  }
1434
1619
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1435
1620
  const pixelRatio = window.devicePixelRatio || 1;
@@ -1455,7 +1640,7 @@ var PdfPlugin = class {
1455
1640
  currentRenderTask = null;
1456
1641
  }
1457
1642
  };
1458
- await renderPage(1);
1643
+ renderPage(1);
1459
1644
  let resizeTimer = null;
1460
1645
  const resizeObserver = new ResizeObserver(() => {
1461
1646
  if (resizeTimer) clearTimeout(resizeTimer);
@@ -1499,7 +1684,7 @@ var PdfPlugin = class {
1499
1684
  renderPage(currentPage);
1500
1685
  },
1501
1686
  fitToPage: () => {
1502
- fitMode = fitMode === "width" ? "page" : "width";
1687
+ fitMode = fitMode === "page" ? "width" : "page";
1503
1688
  zoomScale = 1;
1504
1689
  rotation = 0;
1505
1690
  renderPage(currentPage);
@@ -1976,6 +2161,14 @@ var DocxPlugin = class {
1976
2161
  type: "button",
1977
2162
  group: "actions",
1978
2163
  execute: () => instance.print?.()
2164
+ },
2165
+ {
2166
+ id: "open-window",
2167
+ icon: "open-window",
2168
+ label: "Open in Separate Full Window",
2169
+ type: "button",
2170
+ group: "actions",
2171
+ execute: () => instance.openInSeparateWindow?.()
1979
2172
  }
1980
2173
  );
1981
2174
  return actions;
@@ -2035,6 +2228,31 @@ var DocxPlugin = class {
2035
2228
  }
2036
2229
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2037
2230
  renderedSuccessfully = true;
2231
+ try {
2232
+ const unzipped = unzipSync(new Uint8Array(ctx.buffer));
2233
+ const chartKeys = Object.keys(unzipped).filter((k) => k.replace(/^[./\\]+/, "").toLowerCase().startsWith("word/charts/chart") && k.endsWith(".xml")).sort();
2234
+ if (chartKeys.length > 0) {
2235
+ const allDivs = Array.from(wrapper.querySelectorAll("div"));
2236
+ const emptyContainers = allDivs.filter((div) => {
2237
+ const st = div.getAttribute("style") || "";
2238
+ return st.includes("width:") && st.includes("height:") && div.children.length === 0 && (div.textContent?.trim().length ?? 0) === 0;
2239
+ });
2240
+ chartKeys.forEach((cKey, idx) => {
2241
+ const target = emptyContainers[idx];
2242
+ if (target) {
2243
+ const xmlStr = strFromU8(unzipped[cKey]);
2244
+ const svg = this.parseAndRenderChartSvg(xmlStr);
2245
+ if (svg) {
2246
+ target.innerHTML = svg;
2247
+ target.style.display = "block";
2248
+ target.style.margin = "12px auto";
2249
+ }
2250
+ }
2251
+ });
2252
+ }
2253
+ } catch (chartErr) {
2254
+ console.warn("[DocxPlugin] Non-critical error rendering DrawingML charts:", chartErr);
2255
+ }
2038
2256
  }
2039
2257
  } catch (err) {
2040
2258
  console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
@@ -2066,64 +2284,74 @@ var DocxPlugin = class {
2066
2284
  }
2067
2285
  let sections = Array.from(wrapper.querySelectorAll("section.docx"));
2068
2286
  const cards = Array.from(wrapper.querySelectorAll(".fp-docx-page-card"));
2069
- if (sections.length === 1 && cards.length === 0) {
2070
- const singleSec = sections[0];
2071
- const contentContainer = singleSec.querySelector("article") || singleSec;
2072
- const children = Array.from(contentContainer.children);
2073
- const pageH = singleSec.offsetHeight > 1300 ? 1122 : Math.max(1056, singleSec.offsetHeight);
2074
- const secH = singleSec.offsetHeight || singleSec.scrollHeight;
2075
- if (secH > pageH * 1.25 && children.length > 1) {
2076
- const childHeights = children.map((c) => {
2077
- const rectH = c.getBoundingClientRect().height;
2078
- const offH = c.offsetHeight;
2079
- const textLen = c.textContent?.trim().length || 0;
2080
- const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
2081
- return Math.max(rectH, offH, estH);
2082
- });
2083
- const parent = singleSec.parentElement || wrapper;
2084
- const newSections = [singleSec];
2085
- const headerEl = singleSec.querySelector("header");
2086
- const footerEl = singleSec.querySelector("footer");
2087
- contentContainer.innerHTML = "";
2088
- singleSec.style.minHeight = `${pageH}px`;
2089
- singleSec.style.boxSizing = "border-box";
2090
- let curContent = contentContainer;
2091
- let curH = 0;
2092
- const maxH = pageH - 140;
2093
- for (let i = 0; i < children.length; i++) {
2094
- const child = children[i];
2095
- const chH = childHeights[i];
2096
- curContent.appendChild(child);
2097
- curH += chH;
2098
- if (curH >= maxH && i < children.length - 1) {
2099
- const nextSec = document.createElement("section");
2100
- nextSec.className = singleSec.className;
2101
- nextSec.style.cssText = singleSec.style.cssText;
2102
- nextSec.style.minHeight = `${pageH}px`;
2103
- nextSec.style.boxSizing = "border-box";
2104
- nextSec.style.backgroundColor = "#ffffff";
2105
- nextSec.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
2106
- nextSec.style.borderRadius = "4px";
2107
- nextSec.style.marginBottom = "24px";
2108
- if (headerEl) {
2109
- nextSec.appendChild(headerEl.cloneNode(true));
2110
- }
2111
- const nextArticle = document.createElement("article");
2112
- if (contentContainer.tagName.toLowerCase() === "article") {
2113
- nextArticle.style.cssText = contentContainer.style.cssText;
2114
- }
2115
- nextSec.appendChild(nextArticle);
2116
- if (footerEl) {
2117
- nextSec.appendChild(footerEl.cloneNode(true));
2287
+ if (sections.length > 0 && cards.length === 0) {
2288
+ const finalSections = [];
2289
+ for (const singleSec of sections) {
2290
+ const contentContainer = singleSec.querySelector("article") || singleSec;
2291
+ const children = Array.from(contentContainer.children);
2292
+ const pageH = singleSec.offsetHeight > 1300 ? 1122 : Math.max(1056, singleSec.offsetHeight);
2293
+ const secH = singleSec.scrollHeight || singleSec.offsetHeight;
2294
+ if (secH > pageH * 1.25 && children.length > 1) {
2295
+ const childHeights = children.map((c) => {
2296
+ const rectH = c.getBoundingClientRect().height;
2297
+ const offH = c.offsetHeight;
2298
+ const textLen = c.textContent?.trim().length || 0;
2299
+ const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
2300
+ return Math.max(rectH, offH, estH);
2301
+ });
2302
+ const parent = singleSec.parentElement || wrapper;
2303
+ const headerEl = singleSec.querySelector("header");
2304
+ const footerEl = singleSec.querySelector("footer");
2305
+ contentContainer.innerHTML = "";
2306
+ singleSec.style.minHeight = `${pageH}px`;
2307
+ singleSec.style.boxSizing = "border-box";
2308
+ let curContent = contentContainer;
2309
+ let curSec = singleSec;
2310
+ let curH = 0;
2311
+ const maxH = pageH - 140;
2312
+ finalSections.push(singleSec);
2313
+ for (let i = 0; i < children.length; i++) {
2314
+ const child = children[i];
2315
+ const chH = childHeights[i];
2316
+ curContent.appendChild(child);
2317
+ curH += chH;
2318
+ if (curH >= maxH && i < children.length - 1) {
2319
+ const nextSec = document.createElement("section");
2320
+ nextSec.className = singleSec.className;
2321
+ nextSec.style.cssText = singleSec.style.cssText;
2322
+ nextSec.style.minHeight = `${pageH}px`;
2323
+ nextSec.style.boxSizing = "border-box";
2324
+ nextSec.style.backgroundColor = "#ffffff";
2325
+ nextSec.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
2326
+ nextSec.style.borderRadius = "4px";
2327
+ nextSec.style.marginBottom = "24px";
2328
+ if (headerEl) {
2329
+ nextSec.appendChild(headerEl.cloneNode(true));
2330
+ }
2331
+ const nextArticle = document.createElement("article");
2332
+ if (contentContainer.tagName.toLowerCase() === "article") {
2333
+ nextArticle.style.cssText = contentContainer.style.cssText;
2334
+ }
2335
+ nextSec.appendChild(nextArticle);
2336
+ if (footerEl) {
2337
+ nextSec.appendChild(footerEl.cloneNode(true));
2338
+ }
2339
+ if (curSec.nextSibling) {
2340
+ parent.insertBefore(nextSec, curSec.nextSibling);
2341
+ } else {
2342
+ parent.appendChild(nextSec);
2343
+ }
2344
+ finalSections.push(nextSec);
2345
+ curSec = nextSec;
2346
+ curContent = nextArticle;
2347
+ curH = 0;
2118
2348
  }
2119
- parent.appendChild(nextSec);
2120
- newSections.push(nextSec);
2121
- curContent = nextArticle;
2122
- curH = 0;
2123
2349
  }
2350
+ } else {
2351
+ finalSections.push(singleSec);
2124
2352
  }
2125
- sections = newSections;
2126
2353
  }
2354
+ sections = finalSections;
2127
2355
  }
2128
2356
  const pageElements = sections.length > 0 ? sections : cards;
2129
2357
  const totalPages = Math.max(1, pageElements.length);
@@ -2515,6 +2743,88 @@ var DocxPlugin = class {
2515
2743
  }
2516
2744
  return result;
2517
2745
  }
2746
+ parseAndRenderChartSvg(xmlStr, width = 500, height = 260) {
2747
+ const catMatches = [...xmlStr.matchAll(/<c:cat>[\s\S]*?<c:strCache>([\s\S]*?)<\/c:strCache>/g)];
2748
+ let categories = [];
2749
+ if (catMatches.length > 0) {
2750
+ categories = [...catMatches[0][1].matchAll(/<c:v>([^<]+)<\/c:v>/g)].map((m) => m[1]);
2751
+ }
2752
+ if (categories.length === 0) {
2753
+ categories = ["Category 1", "Category 2", "Category 3", "Category 4"];
2754
+ }
2755
+ const defaultColors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021", "#83caff"];
2756
+ const sers = [...xmlStr.matchAll(/<c:ser>([\s\S]*?)<\/c:ser>/g)];
2757
+ const series = [];
2758
+ sers.forEach((s, sIdx) => {
2759
+ const titleMatch = s[1].match(/<c:tx>[\s\S]*?<c:v>([^<]+)<\/c:v>/);
2760
+ const title = titleMatch ? titleMatch[1] : `Series ${sIdx + 1}`;
2761
+ const clrMatch = s[1].match(/<a:srgbClr\s+val="([^"]+)"/);
2762
+ const color = clrMatch ? "#" + clrMatch[1] : defaultColors[sIdx % defaultColors.length];
2763
+ const valMatch = s[1].match(/<c:val>[\s\S]*?<c:numCache>([\s\S]*?)<\/c:numCache>/);
2764
+ let values = [];
2765
+ if (valMatch) {
2766
+ 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);
2767
+ }
2768
+ series.push({ title, color, values });
2769
+ });
2770
+ if (series.length === 0) return "";
2771
+ let maxVal = 10;
2772
+ series.forEach((s) => s.values.forEach((v) => {
2773
+ if (v > maxVal) maxVal = v;
2774
+ }));
2775
+ maxVal = Math.ceil(maxVal * 1.15);
2776
+ if (maxVal % 2 !== 0) maxVal++;
2777
+ const padLeft = 45;
2778
+ const padBottom = 55;
2779
+ const padTop = 20;
2780
+ const padRight = 20;
2781
+ const plotW = width - padLeft - padRight;
2782
+ const plotH = height - padTop - padBottom;
2783
+ const yTicks = 5;
2784
+ let gridLines = "";
2785
+ for (let i = 0; i <= yTicks; i++) {
2786
+ const val = maxVal / yTicks * i;
2787
+ const y = padTop + plotH - val / maxVal * plotH;
2788
+ gridLines += `<line x1="${padLeft}" y1="${y}" x2="${padLeft + plotW}" y2="${y}" stroke="#e2e8f0" stroke-width="1" />`;
2789
+ 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>`;
2790
+ }
2791
+ const numCats = categories.length;
2792
+ const numSers = series.length;
2793
+ const groupW = plotW / numCats;
2794
+ const barW = Math.max(8, Math.min(28, groupW * 0.7 / numSers));
2795
+ const groupPad = (groupW - barW * numSers) / 2;
2796
+ let bars = "";
2797
+ let catLabels = "";
2798
+ for (let c = 0; c < numCats; c++) {
2799
+ const catX = padLeft + c * groupW;
2800
+ 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>`;
2801
+ for (let s = 0; s < numSers; s++) {
2802
+ const val = series[s].values[c] ?? 0;
2803
+ const bH = Math.max(0, val / maxVal * plotH);
2804
+ const bX = catX + groupPad + s * barW;
2805
+ const bY = padTop + plotH - bH;
2806
+ bars += `<rect x="${bX}" y="${bY}" width="${barW - 2}" height="${bH}" fill="${series[s].color}" rx="1" />`;
2807
+ }
2808
+ }
2809
+ let legend = "";
2810
+ const legY = height - 12;
2811
+ let legX = padLeft + (plotW - numSers * 100) / 2;
2812
+ series.forEach((s) => {
2813
+ legend += `<rect x="${legX}" y="${legY - 9}" width="10" height="10" fill="${s.color}" rx="2" />`;
2814
+ legend += `<text x="${legX + 15}" y="${legY}" font-size="11" fill="#475569" font-family="Calibri, sans-serif">${s.title}</text>`;
2815
+ legX += 95;
2816
+ });
2817
+ return `
2818
+ <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">
2819
+ ${gridLines}
2820
+ <line x1="${padLeft}" y1="${padTop + plotH}" x2="${padLeft + plotW}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2821
+ <line x1="${padLeft}" y1="${padTop}" x2="${padLeft}" y2="${padTop + plotH}" stroke="#94a3b8" stroke-width="1.5" />
2822
+ ${bars}
2823
+ ${catLabels}
2824
+ ${legend}
2825
+ </svg>
2826
+ `.trim();
2827
+ }
2518
2828
  };
2519
2829
  function docxPlugin() {
2520
2830
  return new DocxPlugin();
@@ -3081,6 +3391,16 @@ var CodePlugin = class {
3081
3391
  execute: () => {
3082
3392
  instance.print?.();
3083
3393
  }
3394
+ },
3395
+ {
3396
+ id: "open-window",
3397
+ icon: "open-window",
3398
+ label: "Open in Separate Full Window",
3399
+ type: "button",
3400
+ group: "actions",
3401
+ execute: () => {
3402
+ instance.openInSeparateWindow?.();
3403
+ }
3084
3404
  }
3085
3405
  );
3086
3406
  return actions;
@@ -4249,6 +4569,14 @@ var RtfPlugin = class {
4249
4569
  type: "button",
4250
4570
  group: "actions",
4251
4571
  execute: () => instance.print?.()
4572
+ },
4573
+ {
4574
+ id: "open-window",
4575
+ icon: "open-window",
4576
+ label: "Open in Separate Full Window",
4577
+ type: "button",
4578
+ group: "actions",
4579
+ execute: () => instance.openInSeparateWindow?.()
4252
4580
  }
4253
4581
  );
4254
4582
  return actions;
@@ -4301,59 +4629,54 @@ var RtfPlugin = class {
4301
4629
  }
4302
4630
  const doc = new RTFJS.Document(ctx.buffer, {});
4303
4631
  const htmlElements = await doc.render();
4304
- if (htmlElements.length === 1) {
4305
- const singleEl = htmlElements[0];
4306
- wrapper.appendChild(singleEl);
4307
- const children = Array.from(singleEl.children);
4308
- const secH = singleEl.offsetHeight || singleEl.scrollHeight;
4309
- if (secH > 1300 && children.length > 1) {
4310
- const childHeights = children.map((c) => {
4311
- const rectH = c.getBoundingClientRect().height;
4312
- const offH = c.offsetHeight;
4313
- const textLen = c.textContent?.trim().length || 0;
4314
- const estH = Math.max(24, Math.ceil(textLen / 80) * 22 + 16);
4315
- return Math.max(rectH, offH, estH);
4316
- });
4317
- wrapper.innerHTML = "";
4318
- const createRtfCard = () => {
4319
- const card = document.createElement("div");
4320
- card.className = "fp-rtf-page-card";
4321
- card.style.backgroundColor = "#ffffff";
4322
- card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4323
- card.style.borderRadius = "4px";
4324
- card.style.padding = "72px 56px";
4325
- card.style.width = "816px";
4326
- card.style.minHeight = "1056px";
4327
- card.style.boxSizing = "border-box";
4328
- card.style.marginBottom = "24px";
4329
- return card;
4330
- };
4331
- let curCard = createRtfCard();
4332
- wrapper.appendChild(curCard);
4333
- pageElements = [curCard];
4334
- let curH = 0;
4335
- const maxH = 920;
4336
- for (let i = 0; i < children.length; i++) {
4337
- const child = children[i];
4338
- const chH = childHeights[i];
4339
- curCard.appendChild(child);
4340
- curH += chH;
4341
- if (curH >= maxH && i < children.length - 1) {
4342
- curCard = createRtfCard();
4343
- wrapper.appendChild(curCard);
4344
- pageElements.push(curCard);
4345
- curH = 0;
4346
- }
4347
- }
4632
+ const contentNodes = [];
4633
+ for (const item of htmlElements) {
4634
+ if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4635
+ contentNodes.push(...Array.from(item.children));
4348
4636
  } else {
4349
- pageElements = [singleEl];
4637
+ contentNodes.push(item);
4350
4638
  }
4351
- } else {
4352
- pageElements = htmlElements;
4353
- for (let i = 0; i < htmlElements.length; i++) {
4354
- const el = htmlElements[i];
4355
- el.style.display = i === 0 ? "block" : "none";
4356
- wrapper.appendChild(el);
4639
+ }
4640
+ wrapper.innerHTML = "";
4641
+ contentNodes.forEach((node) => wrapper.appendChild(node));
4642
+ const childHeights = contentNodes.map((c) => {
4643
+ const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4644
+ const offH = c.offsetHeight || 0;
4645
+ const textLen = c.textContent?.trim().length || 0;
4646
+ const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
4647
+ return Math.max(rectH, offH, estH);
4648
+ });
4649
+ wrapper.innerHTML = "";
4650
+ const createRtfCard = () => {
4651
+ const card = document.createElement("div");
4652
+ card.className = "fp-rtf-page-card";
4653
+ card.style.backgroundColor = "#ffffff";
4654
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4655
+ card.style.borderRadius = "4px";
4656
+ card.style.padding = "72px 56px";
4657
+ card.style.width = "816px";
4658
+ card.style.minHeight = "1056px";
4659
+ card.style.boxSizing = "border-box";
4660
+ card.style.marginBottom = "24px";
4661
+ card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4662
+ card.style.lineHeight = "1.6";
4663
+ return card;
4664
+ };
4665
+ let curCard = createRtfCard();
4666
+ wrapper.appendChild(curCard);
4667
+ pageElements = [curCard];
4668
+ let curH = 0;
4669
+ const maxH = 912;
4670
+ for (let i = 0; i < contentNodes.length; i++) {
4671
+ const child = contentNodes[i];
4672
+ const chH = childHeights[i];
4673
+ curCard.appendChild(child);
4674
+ curH += chH;
4675
+ if (curH >= maxH && i < contentNodes.length - 1) {
4676
+ curCard = createRtfCard();
4677
+ wrapper.appendChild(curCard);
4678
+ pageElements.push(curCard);
4679
+ curH = 0;
4357
4680
  }
4358
4681
  }
4359
4682
  } catch (err) {
@@ -4722,6 +5045,14 @@ var OpenDocumentPlugin = class {
4722
5045
  type: "button",
4723
5046
  group: "actions",
4724
5047
  execute: () => instance.print?.()
5048
+ },
5049
+ {
5050
+ id: "open-window",
5051
+ icon: "open-window",
5052
+ label: "Open in Separate Full Window",
5053
+ type: "button",
5054
+ group: "actions",
5055
+ execute: () => instance.openInSeparateWindow?.()
4725
5056
  }
4726
5057
  );
4727
5058
  return actions;
@@ -5310,6 +5641,14 @@ var DocPlugin = class {
5310
5641
  type: "button",
5311
5642
  group: "actions",
5312
5643
  execute: () => instance.print?.()
5644
+ },
5645
+ {
5646
+ id: "open-window",
5647
+ icon: "open-window",
5648
+ label: "Open in Separate Full Window",
5649
+ type: "button",
5650
+ group: "actions",
5651
+ execute: () => instance.openInSeparateWindow?.()
5313
5652
  }
5314
5653
  );
5315
5654
  return actions;
@@ -5601,9 +5940,25 @@ var DocPlugin = class {
5601
5940
  heuristicTextExtraction(buffer) {
5602
5941
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5603
5942
  }
5943
+ cleanWordDocFields(text) {
5944
+ if (!text) return "";
5945
+ let cleaned = text.replace(
5946
+ /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
5947
+ (_match, url, label) => {
5948
+ const cleanUrl = url.trim();
5949
+ const cleanLabel = label.trim() || cleanUrl;
5950
+ return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
5951
+ }
5952
+ );
5953
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
5954
+ cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
5955
+ cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
5956
+ return cleaned;
5957
+ }
5604
5958
  splitIntoPages(text) {
5605
5959
  if (!text) return [""];
5606
- const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
5960
+ const cleanedText = this.cleanWordDocFields(text);
5961
+ const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
5607
5962
  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);
5608
5963
  if (explicitParts.length === 0) explicitParts.push(normalized);
5609
5964
  const maxLinesPerPage = 32;
@@ -5614,7 +5969,8 @@ var DocPlugin = class {
5614
5969
  let currentLines = [];
5615
5970
  let count = 0;
5616
5971
  for (const line of lines) {
5617
- const vLines = Math.max(1, Math.ceil((line.length || 1) / charsPerLine));
5972
+ const plainLine = line.replace(/<[^>]+>/g, "");
5973
+ const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
5618
5974
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
5619
5975
  finalPages.push(currentLines.join("\n"));
5620
5976
  currentLines = [];
@@ -5690,25 +6046,26 @@ var DocPlugin = class {
5690
6046
  i++;
5691
6047
  continue;
5692
6048
  }
6049
+ const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
5693
6050
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
5694
6051
  if (inList) {
5695
6052
  html += "</ul>";
5696
6053
  inList = false;
5697
6054
  }
5698
- 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>`;
6055
+ 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>`;
5699
6056
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
5700
6057
  if (!inList) {
5701
6058
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
5702
6059
  inList = true;
5703
6060
  }
5704
6061
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
5705
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText)}</li>`;
6062
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
5706
6063
  } else {
5707
6064
  if (inList) {
5708
6065
  html += "</ul>";
5709
6066
  inList = false;
5710
6067
  }
5711
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
6068
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
5712
6069
  }
5713
6070
  i++;
5714
6071
  }