@files-preview-app/preview-file 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/angular.cjs CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var core = require('@angular/core');
6
6
  var common = require('@angular/common');
7
- var DOMPurify2 = require('dompurify');
7
+ var DOMPurify6 = require('dompurify');
8
8
  var pdfjsLib = require('pdfjs-dist');
9
9
  var docx = require('docx-preview');
10
10
  var fflate = require('fflate');
@@ -38,7 +38,7 @@ function _interopNamespace(e) {
38
38
  return Object.freeze(n);
39
39
  }
40
40
 
41
- var DOMPurify2__default = /*#__PURE__*/_interopDefault(DOMPurify2);
41
+ var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
42
42
  var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
43
43
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
44
44
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
@@ -437,7 +437,7 @@ async function sourceToArrayBuffer(source, signal) {
437
437
  if (magicMime === "application/zip") {
438
438
  const ooxmlMime = detectOoxmlType(buffer);
439
439
  if (ooxmlMime && ooxmlMime !== "application/zip") {
440
- metadata.mimeType = ooxmlMime;
440
+ metadata.mimeType = metadata.mimeType ?? ooxmlMime;
441
441
  if (ooxmlMime.includes("wordprocessing")) metadata.extension = metadata.extension ?? ".docx";
442
442
  else if (ooxmlMime.includes("spreadsheet")) metadata.extension = metadata.extension ?? ".xlsx";
443
443
  else if (ooxmlMime.includes("presentation")) metadata.extension = metadata.extension ?? ".pptx";
@@ -466,7 +466,7 @@ async function sourceToArrayBuffer(source, signal) {
466
466
  return { buffer, metadata };
467
467
  }
468
468
  function sanitizeSVG(svg) {
469
- return DOMPurify2__default.default.sanitize(svg, {
469
+ return DOMPurify6__default.default.sanitize(svg, {
470
470
  USE_PROFILES: { svg: true, svgFilters: true }
471
471
  });
472
472
  }
@@ -785,6 +785,8 @@ var FilePreviewViewer = class {
785
785
  keyHandler = null;
786
786
  currentContainer = null;
787
787
  currentOptions = {};
788
+ resizeObserver = null;
789
+ fullscreenHandler = null;
788
790
  /**
789
791
  * Register a preview plugin.
790
792
  */
@@ -857,12 +859,31 @@ var FilePreviewViewer = class {
857
859
  label: "Toggle Fullscreen",
858
860
  type: "button",
859
861
  group: "view",
860
- execute: () => {
861
- if (!document.fullscreenElement) {
862
- this.wrapperEl?.requestFullscreen?.();
863
- } else {
864
- document.exitFullscreen?.();
862
+ execute: async () => {
863
+ try {
864
+ const isNativeFs = !!document.fullscreenElement;
865
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
866
+ if (!isNativeFs && !isCssFs) {
867
+ if (this.wrapperEl?.requestFullscreen) {
868
+ await this.wrapperEl.requestFullscreen().catch(() => {
869
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
870
+ });
871
+ } else {
872
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
873
+ }
874
+ } else {
875
+ if (document.fullscreenElement) {
876
+ await document.exitFullscreen().catch(() => {
877
+ });
878
+ }
879
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
880
+ }
881
+ } catch {
882
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
865
883
  }
884
+ setTimeout(() => {
885
+ this.activeInstance?.fitToPage?.();
886
+ }, 120);
866
887
  }
867
888
  });
868
889
  }
@@ -912,6 +933,15 @@ var FilePreviewViewer = class {
912
933
  window.removeEventListener("keydown", this.keyHandler);
913
934
  this.keyHandler = null;
914
935
  }
936
+ if (this.resizeObserver) {
937
+ this.resizeObserver.disconnect();
938
+ this.resizeObserver = null;
939
+ }
940
+ if (this.fullscreenHandler) {
941
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
942
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
943
+ this.fullscreenHandler = null;
944
+ }
915
945
  this.abort();
916
946
  this.destroyInstance();
917
947
  this.toolbar?.destroy();
@@ -991,6 +1021,25 @@ var FilePreviewViewer = class {
991
1021
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
992
1022
  this.setupKeyboardShortcuts();
993
1023
  this.setupDragAndDrop(container, options);
1024
+ this.setupResizeAndFullscreenListeners();
1025
+ }
1026
+ setupResizeAndFullscreenListeners() {
1027
+ if (this.fullscreenHandler) return;
1028
+ this.fullscreenHandler = () => {
1029
+ setTimeout(() => {
1030
+ this.activeInstance?.fitToPage?.();
1031
+ }, 100);
1032
+ };
1033
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
1034
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
1035
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1036
+ this.resizeObserver = new ResizeObserver(() => {
1037
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1038
+ this.activeInstance.fitToPage?.();
1039
+ }
1040
+ });
1041
+ this.resizeObserver.observe(this.contentEl);
1042
+ }
994
1043
  }
995
1044
  setupKeyboardShortcuts() {
996
1045
  if (this.keyHandler) return;
@@ -1382,7 +1431,8 @@ var PdfPlugin = class {
1382
1431
  container.style.display = "flex";
1383
1432
  container.style.flexDirection = "column";
1384
1433
  container.style.alignItems = "center";
1385
- container.style.padding = "24px 16px";
1434
+ container.style.justifyContent = "flex-start";
1435
+ container.style.padding = "20px 16px";
1386
1436
  container.style.backgroundColor = "#0f172a";
1387
1437
  container.style.boxSizing = "border-box";
1388
1438
  container.style.position = "relative";
@@ -1395,7 +1445,8 @@ var PdfPlugin = class {
1395
1445
  pageCard.style.lineHeight = "0";
1396
1446
  pageCard.style.transition = "transform 0.15s ease";
1397
1447
  pageCard.style.position = "relative";
1398
- const canvas = document.createElement("canvas");
1448
+ pageCard.style.flexShrink = "0";
1449
+ let canvas = document.createElement("canvas");
1399
1450
  pageCard.appendChild(canvas);
1400
1451
  container.appendChild(pageCard);
1401
1452
  const indicator = document.createElement("div");
@@ -1437,14 +1488,22 @@ var PdfPlugin = class {
1437
1488
  }
1438
1489
  currentRenderTask = null;
1439
1490
  }
1491
+ const newCanvas = document.createElement("canvas");
1492
+ pageCard.replaceChild(newCanvas, canvas);
1493
+ canvas = newCanvas;
1440
1494
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1441
1495
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1442
1496
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1443
1497
  const page = await pdfDoc.getPage(currentPage);
1444
1498
  const containerWidth = container.clientWidth || 900;
1499
+ const containerHeight = container.clientHeight || 700;
1445
1500
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1446
- const baseScale = Math.min((containerWidth - 64) / unscaledVp.width, 1.6);
1447
- const effectiveScale = (baseScale > 0 ? baseScale : 1) * zoomScale;
1501
+ const availWidth = Math.max(100, containerWidth - 48);
1502
+ const availHeight = Math.max(100, containerHeight - 88);
1503
+ const scaleW = availWidth / unscaledVp.width;
1504
+ const scaleH = availHeight / unscaledVp.height;
1505
+ const fitScale = Math.min(scaleW, scaleH);
1506
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1448
1507
  const pixelRatio = window.devicePixelRatio || 1;
1449
1508
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
1450
1509
  canvas.width = Math.floor(viewport.width * pixelRatio);
@@ -1469,7 +1528,19 @@ var PdfPlugin = class {
1469
1528
  }
1470
1529
  };
1471
1530
  await renderPage(1);
1531
+ let resizeTimer = null;
1532
+ const resizeObserver = new ResizeObserver(() => {
1533
+ if (resizeTimer) clearTimeout(resizeTimer);
1534
+ resizeTimer = setTimeout(() => {
1535
+ if (Math.abs(zoomScale - 1) < 0.05) {
1536
+ renderPage(currentPage);
1537
+ }
1538
+ }, 150);
1539
+ });
1540
+ resizeObserver.observe(container);
1472
1541
  const cleanup = () => {
1542
+ resizeObserver.disconnect();
1543
+ if (resizeTimer) clearTimeout(resizeTimer);
1473
1544
  if (currentRenderTask) {
1474
1545
  try {
1475
1546
  currentRenderTask.cancel();
@@ -1501,6 +1572,7 @@ var PdfPlugin = class {
1501
1572
  },
1502
1573
  fitToPage: () => {
1503
1574
  zoomScale = 1;
1575
+ rotation = 0;
1504
1576
  renderPage(currentPage);
1505
1577
  },
1506
1578
  rotateCW: () => {
@@ -1951,6 +2023,14 @@ var DocxPlugin = class {
1951
2023
  group: "zoom",
1952
2024
  execute: () => instance.fitToPage?.()
1953
2025
  },
2026
+ {
2027
+ id: "rotate-cw",
2028
+ icon: "rotate-cw",
2029
+ label: "Rotate",
2030
+ type: "button",
2031
+ group: "view",
2032
+ execute: () => instance.rotateCW?.()
2033
+ },
1954
2034
  {
1955
2035
  id: "download",
1956
2036
  icon: "download",
@@ -2087,6 +2167,26 @@ var DocxPlugin = class {
2087
2167
  if (totalPages > 1) {
2088
2168
  showPage(1);
2089
2169
  }
2170
+ scale = 1;
2171
+ let rotation = 0;
2172
+ const calculateFitScale = () => {
2173
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2174
+ const elW = activeEl.offsetWidth || 816;
2175
+ const elH = activeEl.offsetHeight || 1056;
2176
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2177
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2178
+ const sW = availW / elW;
2179
+ const sH = availH / elH;
2180
+ return Math.min(1.1, Math.min(sW, sH));
2181
+ };
2182
+ const applyTransform = () => {
2183
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2184
+ wrapper.style.transformOrigin = "top center";
2185
+ };
2186
+ setTimeout(() => {
2187
+ scale = calculateFitScale();
2188
+ applyTransform();
2189
+ }, 60);
2090
2190
  const cleanup = () => {
2091
2191
  indicator?.remove();
2092
2192
  for (const url of createdBlobUrls) {
@@ -2105,21 +2205,30 @@ var DocxPlugin = class {
2105
2205
  showPage(page);
2106
2206
  },
2107
2207
  zoomIn: () => {
2108
- scale += 0.1;
2109
- wrapper.style.transform = `scale(${scale})`;
2208
+ scale += 0.15;
2209
+ applyTransform();
2110
2210
  },
2111
2211
  zoomOut: () => {
2112
- scale = Math.max(0.2, scale - 0.1);
2113
- wrapper.style.transform = `scale(${scale})`;
2212
+ scale = Math.max(0.2, scale - 0.15);
2213
+ applyTransform();
2114
2214
  },
2115
2215
  getZoom: () => scale,
2116
2216
  setZoom: (level) => {
2117
2217
  scale = level;
2118
- wrapper.style.transform = `scale(${scale})`;
2218
+ applyTransform();
2119
2219
  },
2120
2220
  fitToPage: () => {
2121
- scale = 1;
2122
- wrapper.style.transform = "scale(1)";
2221
+ scale = calculateFitScale();
2222
+ rotation = 0;
2223
+ applyTransform();
2224
+ },
2225
+ rotateCW: () => {
2226
+ rotation = (rotation + 90) % 360;
2227
+ applyTransform();
2228
+ },
2229
+ rotateCCW: () => {
2230
+ rotation = (rotation - 90 + 360) % 360;
2231
+ applyTransform();
2123
2232
  },
2124
2233
  download: () => {
2125
2234
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2179,90 +2288,149 @@ var DocxPlugin = class {
2179
2288
  console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
2180
2289
  }
2181
2290
  }
2182
- const card = document.createElement("div");
2183
- card.className = "fp-docx-page-card";
2184
- card.style.backgroundColor = "#ffffff";
2185
- card.style.borderRadius = "6px";
2186
- card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2187
- card.style.padding = "48px 56px";
2188
- card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2189
- card.style.color = "#1e293b";
2190
- card.style.lineHeight = "1.6";
2291
+ const sectPrs = Array.from(doc.getElementsByTagNameNS("*", "sectPr"));
2292
+ const lastSectPr = sectPrs[sectPrs.length - 1];
2293
+ let defaultW = 12240;
2294
+ let defaultH = 15840;
2295
+ let margins = { top: 1440, right: 1440, bottom: 1440, left: 1440 };
2296
+ if (lastSectPr) {
2297
+ const pgSz = lastSectPr.getElementsByTagNameNS("*", "pgSz")[0];
2298
+ if (pgSz) {
2299
+ defaultW = parseInt(pgSz.getAttribute("w:w") || pgSz.getAttribute("w") || "12240", 10);
2300
+ defaultH = parseInt(pgSz.getAttribute("w:h") || pgSz.getAttribute("h") || "15840", 10);
2301
+ }
2302
+ const pgMar = lastSectPr.getElementsByTagNameNS("*", "pgMar")[0];
2303
+ if (pgMar) {
2304
+ margins.top = parseInt(pgMar.getAttribute("w:top") || pgMar.getAttribute("top") || "1440", 10);
2305
+ margins.bottom = parseInt(pgMar.getAttribute("w:bottom") || pgMar.getAttribute("bottom") || "1440", 10);
2306
+ margins.left = parseInt(pgMar.getAttribute("w:left") || pgMar.getAttribute("left") || "1440", 10);
2307
+ margins.right = parseInt(pgMar.getAttribute("w:right") || pgMar.getAttribute("right") || "1440", 10);
2308
+ }
2309
+ }
2310
+ const createPageCard = () => {
2311
+ const card = document.createElement("div");
2312
+ card.className = "fp-docx-page-card";
2313
+ card.style.backgroundColor = "#ffffff";
2314
+ card.style.borderRadius = "4px";
2315
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2316
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2317
+ card.style.color = "#1e293b";
2318
+ card.style.lineHeight = "1.6";
2319
+ card.style.boxSizing = "border-box";
2320
+ card.style.overflow = "hidden";
2321
+ card.style.position = "relative";
2322
+ card.style.marginBottom = "24px";
2323
+ card.style.width = `${defaultW / 15}px`;
2324
+ card.style.height = `${defaultH / 15}px`;
2325
+ card.style.padding = `${margins.top / 15}px ${margins.right / 15}px ${margins.bottom / 15}px ${margins.left / 15}px`;
2326
+ return card;
2327
+ };
2328
+ let currentCard = createPageCard();
2329
+ let currentHtml = "";
2330
+ const pages = [{ card: currentCard, html: "" }];
2331
+ const flushHtml = () => {
2332
+ pages[pages.length - 1].html += currentHtml;
2333
+ currentHtml = "";
2334
+ };
2335
+ const newPage = () => {
2336
+ flushHtml();
2337
+ currentCard = createPageCard();
2338
+ pages.push({ card: currentCard, html: "" });
2339
+ };
2191
2340
  const body = doc.getElementsByTagNameNS("*", "body")[0] || doc.documentElement;
2192
- let html = "";
2193
2341
  for (const child of Array.from(body.children)) {
2194
2342
  const tag = child.localName || child.nodeName.split(":").pop();
2195
2343
  if (tag === "p") {
2196
2344
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
2197
2345
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
2198
2346
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
2199
- const textContent = this.extractParagraphHtml(child, imageMap);
2200
- if (!textContent.trim()) {
2201
- html += '<div style="height: 10px;"></div>';
2202
- continue;
2203
- }
2204
- const lowerStyle = styleVal.toLowerCase();
2205
- if (lowerStyle.includes("title")) {
2206
- html += `<h1 style="font-size: 28px; font-weight: 700; color: #1e3a8a; margin: 24px 0 12px; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px;">${textContent}</h1>`;
2207
- } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2208
- html += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2209
- } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2210
- html += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2211
- } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2212
- html += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2213
- } else if (numPr) {
2214
- html += `<div style="display: flex; gap: 8px; margin: 4px 0 4px 20px;"><span style="color: #2563eb; font-weight: bold;">\u2022</span><span>${textContent}</span></div>`;
2215
- } else {
2216
- html += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2347
+ const chunks = this.extractParagraphChunks(child, imageMap);
2348
+ for (let i = 0; i < chunks.length; i++) {
2349
+ if (i > 0) {
2350
+ newPage();
2351
+ }
2352
+ const textContent = chunks[i];
2353
+ if (!textContent.trim() && !textContent.includes("<img")) {
2354
+ currentHtml += '<div style="height: 10px;"></div>';
2355
+ continue;
2356
+ }
2357
+ const lowerStyle = styleVal.toLowerCase();
2358
+ if (lowerStyle.includes("title")) {
2359
+ currentHtml += `<h1 style="font-size: 28px; font-weight: 700; color: #1e3a8a; margin: 24px 0 12px; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px;">${textContent}</h1>`;
2360
+ } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2361
+ currentHtml += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2362
+ } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2363
+ currentHtml += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2364
+ } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2365
+ currentHtml += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2366
+ } else if (numPr) {
2367
+ currentHtml += `<div style="display: flex; gap: 8px; margin: 4px 0 4px 20px;"><span style="color: #2563eb; font-weight: bold;">\u2022</span><span>${textContent}</span></div>`;
2368
+ } else {
2369
+ currentHtml += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2370
+ }
2217
2371
  }
2218
2372
  } else if (tag === "tbl") {
2219
- html += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2373
+ currentHtml += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2220
2374
  const rows = Array.from(child.getElementsByTagNameNS("*", "tr"));
2221
2375
  rows.forEach((tr, rIdx) => {
2222
- html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2376
+ currentHtml += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2223
2377
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
2224
2378
  cells.forEach((tc) => {
2225
2379
  const cellText = this.extractParagraphHtml(tc, imageMap);
2226
- html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2380
+ currentHtml += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2227
2381
  });
2228
- html += "</tr>";
2382
+ currentHtml += "</tr>";
2229
2383
  });
2230
- html += "</table>";
2384
+ currentHtml += "</table>";
2231
2385
  }
2232
2386
  }
2233
- card.innerHTML = DOMPurify2__default.default.sanitize(html, {
2234
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2235
- ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2236
- });
2237
- wrapper.appendChild(card);
2387
+ flushHtml();
2388
+ for (const page of pages) {
2389
+ page.card.innerHTML = DOMPurify6__default.default.sanitize(page.html, {
2390
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2391
+ ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2392
+ });
2393
+ wrapper.appendChild(page.card);
2394
+ }
2238
2395
  }
2239
2396
  extractParagraphHtml(pElement, imageMap = {}) {
2240
- let result = "";
2397
+ return this.extractParagraphChunks(pElement, imageMap).join("<br/>");
2398
+ }
2399
+ extractParagraphChunks(pElement, imageMap = {}) {
2400
+ const chunks = [""];
2401
+ let currentChunkIndex = 0;
2241
2402
  const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
2242
2403
  for (const drawing of drawings) {
2243
2404
  const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
2244
2405
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2245
2406
  if (rId && imageMap[rId]) {
2246
- result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2407
+ chunks[currentChunkIndex] += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2247
2408
  }
2248
2409
  }
2249
2410
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
2250
- if (runs.length === 0 && !result) {
2251
- return DOMPurify2__default.default.sanitize(pElement.textContent || "");
2411
+ if (runs.length === 0 && !chunks[currentChunkIndex]) {
2412
+ chunks[currentChunkIndex] = DOMPurify6__default.default.sanitize(pElement.textContent || "");
2413
+ return chunks;
2252
2414
  }
2253
2415
  for (const r of runs) {
2254
2416
  const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
2255
2417
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2256
2418
  if (rId && imageMap[rId]) {
2257
- result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2419
+ chunks[currentChunkIndex] += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2258
2420
  }
2259
- const brs = r.getElementsByTagNameNS("*", "br");
2260
- for (let i = 0; i < brs.length; i++) {
2261
- result += "<br/>";
2421
+ const brs = Array.from(r.getElementsByTagNameNS("*", "br"));
2422
+ for (const br of brs) {
2423
+ const type = br.getAttribute("w:type") || br.getAttribute("type");
2424
+ if (type === "page") {
2425
+ chunks.push("");
2426
+ currentChunkIndex++;
2427
+ } else {
2428
+ chunks[currentChunkIndex] += "<br/>";
2429
+ }
2262
2430
  }
2263
2431
  const tabs = r.getElementsByTagNameNS("*", "tab");
2264
2432
  if (tabs.length > 0) {
2265
- result += "&emsp;";
2433
+ chunks[currentChunkIndex] += "&emsp;";
2266
2434
  }
2267
2435
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
2268
2436
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
@@ -2276,7 +2444,7 @@ var DocxPlugin = class {
2276
2444
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
2277
2445
  let text = texts.map((t) => t.textContent || "").join("");
2278
2446
  if (!text) continue;
2279
- text = DOMPurify2__default.default.sanitize(text);
2447
+ text = DOMPurify6__default.default.sanitize(text);
2280
2448
  let styles = "";
2281
2449
  if (isBold) styles += "font-weight: bold; ";
2282
2450
  if (isItalic) styles += "font-style: italic; ";
@@ -2288,12 +2456,12 @@ var DocxPlugin = class {
2288
2456
  if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2289
2457
  }
2290
2458
  if (styles) {
2291
- result += `<span style="${styles}">${text}</span>`;
2459
+ chunks[currentChunkIndex] += `<span style="${styles}">${text}</span>`;
2292
2460
  } else {
2293
- result += text;
2461
+ chunks[currentChunkIndex] += text;
2294
2462
  }
2295
2463
  }
2296
- return result;
2464
+ return chunks;
2297
2465
  }
2298
2466
  renderBinaryDocFallback(ctx, wrapper) {
2299
2467
  const card = document.createElement("div");
@@ -2310,14 +2478,14 @@ var DocxPlugin = class {
2310
2478
  const wordDocStream = cfbf.readStream("WordDocument");
2311
2479
  if (wordDocStream && wordDocStream.length >= 512) {
2312
2480
  const text2 = this.extractReadableStrings(wordDocStream);
2313
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text2)}</div>`;
2481
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6__default.default.sanitize(text2)}</div>`;
2314
2482
  wrapper.appendChild(card);
2315
2483
  return;
2316
2484
  }
2317
2485
  } catch {
2318
2486
  }
2319
2487
  const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2320
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text)}</div>`;
2488
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6__default.default.sanitize(text)}</div>`;
2321
2489
  wrapper.appendChild(card);
2322
2490
  }
2323
2491
  extractReadableStrings(bytes) {
@@ -2413,6 +2581,26 @@ var ExcelPlugin = class {
2413
2581
  instance.zoomIn?.();
2414
2582
  }
2415
2583
  },
2584
+ {
2585
+ id: "fit-page",
2586
+ icon: "fit-page",
2587
+ label: "Fit to View",
2588
+ type: "button",
2589
+ group: "zoom",
2590
+ execute: () => {
2591
+ instance.fitToPage?.();
2592
+ }
2593
+ },
2594
+ {
2595
+ id: "rotate-cw",
2596
+ icon: "rotate-cw",
2597
+ label: "Rotate",
2598
+ type: "button",
2599
+ group: "view",
2600
+ execute: () => {
2601
+ instance.rotateCW?.();
2602
+ }
2603
+ },
2416
2604
  {
2417
2605
  id: "download",
2418
2606
  icon: "download",
@@ -2463,9 +2651,23 @@ var ExcelPlugin = class {
2463
2651
  container.appendChild(tabsArea);
2464
2652
  ctx.container.appendChild(container);
2465
2653
  let scale = 1;
2654
+ let rotation = 0;
2466
2655
  let currentSheetIndex = 1;
2467
2656
  let sheetNames = [];
2468
2657
  let wb = null;
2658
+ const applyTransform = () => {
2659
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2660
+ contentArea.style.transformOrigin = "top left";
2661
+ };
2662
+ const calculateFitScale = () => {
2663
+ const table = contentArea.querySelector("table");
2664
+ if (!table) return 1;
2665
+ const availW = Math.max(200, container.clientWidth - 48);
2666
+ const availH = Math.max(200, container.clientHeight - 80);
2667
+ const tW = table.offsetWidth || 800;
2668
+ const tH = table.offsetHeight || 600;
2669
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2670
+ };
2469
2671
  try {
2470
2672
  wb = XLSX__namespace.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2471
2673
  sheetNames = wb.SheetNames || [];
@@ -2553,17 +2755,30 @@ var ExcelPlugin = class {
2553
2755
  return {
2554
2756
  destroy: cleanup,
2555
2757
  zoomIn: () => {
2556
- scale += 0.1;
2557
- contentArea.style.transform = `scale(${scale})`;
2758
+ scale += 0.15;
2759
+ applyTransform();
2558
2760
  },
2559
2761
  zoomOut: () => {
2560
- scale = Math.max(0.2, scale - 0.1);
2561
- contentArea.style.transform = `scale(${scale})`;
2762
+ scale = Math.max(0.2, scale - 0.15);
2763
+ applyTransform();
2562
2764
  },
2563
2765
  getZoom: () => scale,
2564
2766
  setZoom: (level) => {
2565
2767
  scale = level;
2566
- contentArea.style.transform = `scale(${scale})`;
2768
+ applyTransform();
2769
+ },
2770
+ fitToPage: () => {
2771
+ scale = calculateFitScale();
2772
+ rotation = 0;
2773
+ applyTransform();
2774
+ },
2775
+ rotateCW: () => {
2776
+ rotation = (rotation + 90) % 360;
2777
+ applyTransform();
2778
+ },
2779
+ rotateCCW: () => {
2780
+ rotation = (rotation - 90 + 360) % 360;
2781
+ applyTransform();
2567
2782
  },
2568
2783
  goToPage: (page) => {
2569
2784
  if (page > 0 && page <= sheetNames.length) {
@@ -2813,6 +3028,26 @@ var CodePlugin = class {
2813
3028
  instance.zoomIn?.();
2814
3029
  }
2815
3030
  },
3031
+ {
3032
+ id: "fit-page",
3033
+ icon: "fit-page",
3034
+ label: "Fit to View",
3035
+ type: "button",
3036
+ group: "zoom",
3037
+ execute: () => {
3038
+ instance.fitToPage?.();
3039
+ }
3040
+ },
3041
+ {
3042
+ id: "rotate-cw",
3043
+ icon: "rotate-cw",
3044
+ label: "Rotate",
3045
+ type: "button",
3046
+ group: "view",
3047
+ execute: () => {
3048
+ instance.rotateCW?.();
3049
+ }
3050
+ },
2816
3051
  {
2817
3052
  id: "copy",
2818
3053
  icon: "copy",
@@ -2849,40 +3084,94 @@ var CodePlugin = class {
2849
3084
  async render(ctx) {
2850
3085
  const decoder = new TextDecoder("utf-8");
2851
3086
  const fullText = decoder.decode(ctx.buffer);
2852
- const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2853
- const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3087
+ const extRaw = ctx.metadata.extension?.toLowerCase();
3088
+ const mimeRaw = ctx.metadata.mimeType?.toLowerCase();
3089
+ const isTxt = extRaw === ".txt" || mimeRaw === "text/plain" && (!extRaw || !this.extensions.filter((e) => e !== ".txt").includes(extRaw));
3090
+ let rawPages = [];
3091
+ if (isTxt) {
3092
+ const explicitPages = fullText.split(/(?:\f|\x0C)/);
3093
+ for (const ep of explicitPages) {
3094
+ const lines = ep.split(/\r?\n/);
3095
+ let currentChunk = [];
3096
+ for (let i = 0; i < lines.length; i++) {
3097
+ currentChunk.push(lines[i]);
3098
+ if (currentChunk.length >= 46) {
3099
+ rawPages.push(currentChunk.join("\n"));
3100
+ currentChunk = [];
3101
+ }
3102
+ }
3103
+ if (currentChunk.length > 0 || lines.length === 0) {
3104
+ rawPages.push(currentChunk.join("\n"));
3105
+ }
3106
+ }
3107
+ if (rawPages.length === 0) rawPages = [""];
3108
+ } else {
3109
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
3110
+ rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3111
+ }
2854
3112
  const totalPages = Math.max(1, rawPages.length);
2855
3113
  let currentPage = 1;
2856
3114
  const container = document.createElement("div");
2857
3115
  container.style.width = "100%";
2858
3116
  container.style.height = "100%";
2859
3117
  container.style.overflow = "auto";
2860
- container.style.backgroundColor = "#1e1e1e";
2861
- container.style.color = "#d4d4d4";
2862
- container.style.padding = "16px";
2863
- container.style.boxSizing = "border-box";
2864
3118
  let fontSize = 13;
3119
+ let rotation = 0;
3120
+ let zoomLevel = 1;
2865
3121
  const pre = document.createElement("pre");
2866
- pre.style.margin = "0";
2867
- pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
2868
- pre.style.fontSize = `${fontSize}px`;
2869
- pre.style.lineHeight = "1.5";
2870
- pre.style.whiteSpace = "pre-wrap";
2871
- pre.style.wordBreak = "break-all";
2872
3122
  const code = document.createElement("code");
3123
+ if (isTxt) {
3124
+ container.style.backgroundColor = "#f1f5f9";
3125
+ container.style.padding = "32px";
3126
+ container.style.boxSizing = "border-box";
3127
+ container.style.display = "flex";
3128
+ container.style.flexDirection = "column";
3129
+ container.style.alignItems = "center";
3130
+ pre.style.margin = "0";
3131
+ pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3132
+ pre.style.fontSize = "13px";
3133
+ pre.style.color = "#1e293b";
3134
+ pre.style.lineHeight = "1.5";
3135
+ pre.style.whiteSpace = "pre-wrap";
3136
+ pre.style.wordBreak = "break-word";
3137
+ pre.style.backgroundColor = "#ffffff";
3138
+ pre.style.width = "816px";
3139
+ pre.style.minHeight = "1056px";
3140
+ pre.style.padding = "72px 56px";
3141
+ pre.style.boxSizing = "border-box";
3142
+ pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
3143
+ pre.style.borderRadius = "4px";
3144
+ pre.style.transformOrigin = "top center";
3145
+ } else {
3146
+ container.style.backgroundColor = "#1e1e1e";
3147
+ container.style.color = "#d4d4d4";
3148
+ container.style.padding = "16px";
3149
+ container.style.boxSizing = "border-box";
3150
+ pre.style.margin = "0";
3151
+ pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
3152
+ pre.style.fontSize = `${fontSize}px`;
3153
+ pre.style.lineHeight = "1.5";
3154
+ pre.style.whiteSpace = "pre-wrap";
3155
+ pre.style.wordBreak = "break-all";
3156
+ pre.style.transformOrigin = "top left";
3157
+ }
2873
3158
  const ext = (ctx.metadata.extension || "").replace(".", "");
2874
3159
  const renderCodePage = (text) => {
2875
- try {
2876
- if (ext && hljs__default.default.getLanguage(ext)) {
2877
- code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
2878
- } else {
2879
- code.innerHTML = hljs__default.default.highlightAuto(text).value;
2880
- }
2881
- } catch {
3160
+ if (isTxt) {
2882
3161
  code.textContent = text;
3162
+ } else {
3163
+ try {
3164
+ if (ext && hljs__default.default.getLanguage(ext)) {
3165
+ code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
3166
+ } else {
3167
+ code.innerHTML = hljs__default.default.highlightAuto(text).value;
3168
+ }
3169
+ } catch {
3170
+ code.textContent = text;
3171
+ }
2883
3172
  }
2884
3173
  };
2885
- renderCodePage(rawPages[0] || fullText);
3174
+ renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
2886
3175
  pre.appendChild(code);
2887
3176
  container.appendChild(pre);
2888
3177
  let indicator = null;
@@ -2891,15 +3180,22 @@ var CodePlugin = class {
2891
3180
  indicator.className = "fp-code-page-indicator";
2892
3181
  indicator.style.position = "sticky";
2893
3182
  indicator.style.bottom = "16px";
2894
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2895
- indicator.style.backdropFilter = "blur(8px)";
2896
- indicator.style.color = "#f8fafc";
3183
+ if (isTxt) {
3184
+ indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3185
+ indicator.style.color = "#334155";
3186
+ indicator.style.border = "1px solid #e2e8f0";
3187
+ indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3188
+ } else {
3189
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3190
+ indicator.style.color = "#f8fafc";
3191
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3192
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3193
+ indicator.style.backdropFilter = "blur(8px)";
3194
+ }
2897
3195
  indicator.style.fontSize = "12px";
2898
3196
  indicator.style.fontWeight = "600";
2899
3197
  indicator.style.padding = "5px 14px";
2900
3198
  indicator.style.borderRadius = "20px";
2901
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2902
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2903
3199
  indicator.style.zIndex = "10";
2904
3200
  indicator.style.userSelect = "none";
2905
3201
  indicator.style.pointerEvents = "none";
@@ -2910,7 +3206,7 @@ var CodePlugin = class {
2910
3206
  }
2911
3207
  const showPage = (pageNum) => {
2912
3208
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2913
- renderCodePage(rawPages[currentPage - 1] || fullText);
3209
+ renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
2914
3210
  if (indicator) {
2915
3211
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2916
3212
  }
@@ -2926,23 +3222,57 @@ var CodePlugin = class {
2926
3222
  ctx.container.innerHTML = "";
2927
3223
  };
2928
3224
  ctx.signal.addEventListener("abort", cleanup);
3225
+ const updateTransform = () => {
3226
+ if (isTxt) {
3227
+ pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
3228
+ } else {
3229
+ pre.style.transform = `rotate(${rotation}deg)`;
3230
+ pre.style.fontSize = `${fontSize}px`;
3231
+ }
3232
+ };
2929
3233
  return {
2930
3234
  destroy: cleanup,
2931
3235
  getPageCount: () => totalPages,
2932
3236
  getCurrentPage: () => currentPage,
2933
3237
  goToPage: (page) => showPage(page),
2934
3238
  zoomIn: () => {
2935
- fontSize = Math.min(32, fontSize + 2);
2936
- pre.style.fontSize = `${fontSize}px`;
3239
+ if (isTxt) {
3240
+ zoomLevel = Math.min(3, zoomLevel + 0.1);
3241
+ } else {
3242
+ fontSize = Math.min(32, fontSize + 2);
3243
+ }
3244
+ updateTransform();
2937
3245
  },
2938
3246
  zoomOut: () => {
2939
- fontSize = Math.max(8, fontSize - 2);
2940
- pre.style.fontSize = `${fontSize}px`;
3247
+ if (isTxt) {
3248
+ zoomLevel = Math.max(0.1, zoomLevel - 0.1);
3249
+ } else {
3250
+ fontSize = Math.max(8, fontSize - 2);
3251
+ }
3252
+ updateTransform();
2941
3253
  },
2942
- getZoom: () => fontSize / 13,
3254
+ getZoom: () => isTxt ? zoomLevel : fontSize / 13,
2943
3255
  setZoom: (level) => {
2944
- fontSize = Math.round(13 * level);
2945
- pre.style.fontSize = `${fontSize}px`;
3256
+ if (isTxt) {
3257
+ zoomLevel = level;
3258
+ } else {
3259
+ fontSize = Math.round(13 * level);
3260
+ }
3261
+ updateTransform();
3262
+ },
3263
+ fitToPage: () => {
3264
+ fontSize = 13;
3265
+ rotation = 0;
3266
+ zoomLevel = 1;
3267
+ updateTransform();
3268
+ },
3269
+ rotateCW: () => {
3270
+ rotation = (rotation + 90) % 360;
3271
+ updateTransform();
3272
+ },
3273
+ rotateCCW: () => {
3274
+ rotation = (rotation - 90 + 360) % 360;
3275
+ updateTransform();
2946
3276
  },
2947
3277
  download: () => {
2948
3278
  const mimeType = ctx.metadata.mimeType || "text/plain";
@@ -3241,7 +3571,7 @@ var MarkdownPlugin = class {
3241
3571
  gfm: true,
3242
3572
  breaks: true
3243
3573
  });
3244
- const cleanHtml = DOMPurify2__default.default.sanitize(rawHtml, {
3574
+ const cleanHtml = DOMPurify6__default.default.sanitize(rawHtml, {
3245
3575
  USE_PROFILES: { html: true }
3246
3576
  });
3247
3577
  const wrapper = document.createElement("div");
@@ -3455,6 +3785,14 @@ var PptxPlugin = class {
3455
3785
  group: "zoom",
3456
3786
  execute: () => instance.fitToPage?.()
3457
3787
  },
3788
+ {
3789
+ id: "rotate-cw",
3790
+ icon: "rotate-cw",
3791
+ label: "Rotate",
3792
+ type: "button",
3793
+ group: "view",
3794
+ execute: () => instance.rotateCW?.()
3795
+ },
3458
3796
  {
3459
3797
  id: "download",
3460
3798
  icon: "download",
@@ -3479,6 +3817,7 @@ var PptxPlugin = class {
3479
3817
  const slideCount = renderer.slidePaths?.length || 1;
3480
3818
  let currentSlide = 1;
3481
3819
  let scale = 1;
3820
+ let rotation = 0;
3482
3821
  const wrapper = document.createElement("div");
3483
3822
  wrapper.className = "fp-pptx-wrapper";
3484
3823
  wrapper.style.cssText = `
@@ -3501,6 +3840,8 @@ var PptxPlugin = class {
3501
3840
  background: #ffffff;
3502
3841
  transform-origin: top center;
3503
3842
  transition: transform 0.2s ease;
3843
+ max-width: calc(100% - 32px);
3844
+ max-height: calc(100% - 48px);
3504
3845
  `;
3505
3846
  const canvas = document.createElement("canvas");
3506
3847
  slideContainer.appendChild(canvas);
@@ -3508,10 +3849,22 @@ var PptxPlugin = class {
3508
3849
  ctx.container.innerHTML = "";
3509
3850
  ctx.container.style.overflow = "auto";
3510
3851
  ctx.container.appendChild(wrapper);
3852
+ const calculateFitScale = () => {
3853
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3854
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3855
+ const cW = canvas.offsetWidth || 1280;
3856
+ const cH = canvas.offsetHeight || 720;
3857
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3858
+ };
3859
+ const applyTransform = () => {
3860
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3861
+ };
3511
3862
  const renderCurrentSlide = async () => {
3512
3863
  try {
3513
3864
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3514
3865
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3866
+ scale = calculateFitScale();
3867
+ applyTransform();
3515
3868
  } catch (err) {
3516
3869
  console.error("[PptxPlugin] Failed to render slide:", err);
3517
3870
  }
@@ -3534,21 +3887,30 @@ var PptxPlugin = class {
3534
3887
  getPageCount: () => slideCount,
3535
3888
  getCurrentPage: () => currentSlide,
3536
3889
  zoomIn: () => {
3537
- scale += 0.1;
3538
- slideContainer.style.transform = `scale(${scale})`;
3890
+ scale += 0.15;
3891
+ applyTransform();
3539
3892
  },
3540
3893
  zoomOut: () => {
3541
- scale = Math.max(0.2, scale - 0.1);
3542
- slideContainer.style.transform = `scale(${scale})`;
3894
+ scale = Math.max(0.2, scale - 0.15);
3895
+ applyTransform();
3543
3896
  },
3544
3897
  getZoom: () => scale,
3545
3898
  setZoom: (level) => {
3546
3899
  scale = level;
3547
- slideContainer.style.transform = `scale(${scale})`;
3900
+ applyTransform();
3548
3901
  },
3549
3902
  fitToPage: () => {
3550
- scale = 1;
3551
- slideContainer.style.transform = "scale(1)";
3903
+ scale = calculateFitScale();
3904
+ rotation = 0;
3905
+ applyTransform();
3906
+ },
3907
+ rotateCW: () => {
3908
+ rotation = (rotation + 90) % 360;
3909
+ applyTransform();
3910
+ },
3911
+ rotateCCW: () => {
3912
+ rotation = (rotation - 90 + 360) % 360;
3913
+ applyTransform();
3552
3914
  },
3553
3915
  getThumbnails: () => {
3554
3916
  const list = [];
@@ -3829,6 +4191,14 @@ var RtfPlugin = class {
3829
4191
  group: "zoom",
3830
4192
  execute: () => instance.fitToPage?.()
3831
4193
  },
4194
+ {
4195
+ id: "rotate-cw",
4196
+ icon: "rotate-cw",
4197
+ label: "Rotate",
4198
+ type: "button",
4199
+ group: "view",
4200
+ execute: () => instance.rotateCW?.()
4201
+ },
3832
4202
  {
3833
4203
  id: "download",
3834
4204
  icon: "download",
@@ -3837,6 +4207,14 @@ var RtfPlugin = class {
3837
4207
  group: "actions",
3838
4208
  execute: () => instance.download?.()
3839
4209
  },
4210
+ {
4211
+ id: "copy",
4212
+ icon: "copy",
4213
+ label: "Copy Text",
4214
+ type: "button",
4215
+ group: "actions",
4216
+ execute: () => instance.copy?.()
4217
+ },
3840
4218
  {
3841
4219
  id: "print",
3842
4220
  icon: "print",
@@ -3865,7 +4243,24 @@ var RtfPlugin = class {
3865
4243
  ctx.container.style.backgroundColor = "#f1f5f9";
3866
4244
  ctx.container.appendChild(wrapper);
3867
4245
  let scale = 1;
4246
+ let rotation = 0;
3868
4247
  let pageElements = [];
4248
+ const calculateFitScale = () => {
4249
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4250
+ const elW = activeEl.offsetWidth || 850;
4251
+ const elH = activeEl.offsetHeight || 1e3;
4252
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4253
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4254
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4255
+ };
4256
+ const applyTransform = () => {
4257
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4258
+ wrapper.style.transformOrigin = "top center";
4259
+ };
4260
+ setTimeout(() => {
4261
+ scale = calculateFitScale();
4262
+ applyTransform();
4263
+ }, 60);
3869
4264
  try {
3870
4265
  if (typeof RTFJS__namespace.loggingEnabled === "function") {
3871
4266
  RTFJS__namespace.loggingEnabled(false);
@@ -3881,14 +4276,31 @@ var RtfPlugin = class {
3881
4276
  } catch (err) {
3882
4277
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3883
4278
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3884
- const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3885
- const pre = document.createElement("pre");
3886
- pre.style.whiteSpace = "pre-wrap";
3887
- pre.style.fontFamily = "serif";
3888
- pre.style.color = "#333";
3889
- pre.textContent = clean;
3890
- wrapper.appendChild(pre);
3891
- pageElements = [pre];
4279
+ const rawPages = text.split(/\\page\b/).map((segment) => {
4280
+ return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
4281
+ }).filter((p) => p.length > 0);
4282
+ const pages = rawPages.length > 0 ? rawPages : [text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim()];
4283
+ for (let i = 0; i < pages.length; i++) {
4284
+ const pageCard = document.createElement("div");
4285
+ pageCard.className = "fp-rtf-page-card";
4286
+ pageCard.style.backgroundColor = "#ffffff";
4287
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4288
+ pageCard.style.borderRadius = "4px";
4289
+ pageCard.style.padding = "72px 56px";
4290
+ pageCard.style.width = "816px";
4291
+ pageCard.style.minHeight = "1056px";
4292
+ pageCard.style.maxWidth = "100%";
4293
+ pageCard.style.boxSizing = "border-box";
4294
+ pageCard.style.fontFamily = "serif";
4295
+ pageCard.style.fontSize = "12pt";
4296
+ pageCard.style.lineHeight = "1.6";
4297
+ pageCard.style.color = "#1e293b";
4298
+ pageCard.style.whiteSpace = "pre-wrap";
4299
+ pageCard.style.display = i === 0 ? "block" : "none";
4300
+ pageCard.textContent = pages[i];
4301
+ wrapper.appendChild(pageCard);
4302
+ pageElements.push(pageCard);
4303
+ }
3892
4304
  }
3893
4305
  const totalPages = Math.max(1, pageElements.length);
3894
4306
  let currentPage = 1;
@@ -3942,21 +4354,30 @@ var RtfPlugin = class {
3942
4354
  getCurrentPage: () => currentPage,
3943
4355
  goToPage: (page) => showPage(page),
3944
4356
  zoomIn: () => {
3945
- scale += 0.1;
3946
- wrapper.style.transform = `scale(${scale})`;
4357
+ scale += 0.15;
4358
+ applyTransform();
3947
4359
  },
3948
4360
  zoomOut: () => {
3949
- scale = Math.max(0.2, scale - 0.1);
3950
- wrapper.style.transform = `scale(${scale})`;
4361
+ scale = Math.max(0.2, scale - 0.15);
4362
+ applyTransform();
3951
4363
  },
3952
4364
  getZoom: () => scale,
3953
4365
  setZoom: (level) => {
3954
4366
  scale = level;
3955
- wrapper.style.transform = `scale(${scale})`;
4367
+ applyTransform();
3956
4368
  },
3957
4369
  fitToPage: () => {
3958
- scale = 1;
3959
- wrapper.style.transform = "scale(1)";
4370
+ scale = calculateFitScale();
4371
+ rotation = 0;
4372
+ applyTransform();
4373
+ },
4374
+ rotateCW: () => {
4375
+ rotation = (rotation + 90) % 360;
4376
+ applyTransform();
4377
+ },
4378
+ rotateCCW: () => {
4379
+ rotation = (rotation - 90 + 360) % 360;
4380
+ applyTransform();
3960
4381
  },
3961
4382
  download: () => {
3962
4383
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3967,6 +4388,10 @@ var RtfPlugin = class {
3967
4388
  a.click();
3968
4389
  URL.revokeObjectURL(url);
3969
4390
  },
4391
+ copy: () => {
4392
+ const text = wrapper.textContent || "";
4393
+ navigator.clipboard?.writeText(text);
4394
+ },
3970
4395
  print: () => {
3971
4396
  window.print();
3972
4397
  }
@@ -4041,7 +4466,7 @@ var HtmlPreviewPlugin = class {
4041
4466
  }
4042
4467
  async render(ctx) {
4043
4468
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
4044
- const sanitized = DOMPurify2__default.default.sanitize(rawHtml, {
4469
+ const sanitized = DOMPurify6__default.default.sanitize(rawHtml, {
4045
4470
  WHOLE_DOCUMENT: true,
4046
4471
  ADD_TAGS: ["style", "link"],
4047
4472
  ADD_ATTR: ["target", "rel"]
@@ -4184,11 +4609,19 @@ var OpenDocumentPlugin = class {
4184
4609
  {
4185
4610
  id: "fit-page",
4186
4611
  icon: "fit-page",
4187
- label: "Fit to Page",
4612
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
4188
4613
  type: "button",
4189
4614
  group: "zoom",
4190
4615
  execute: () => instance.fitToPage?.()
4191
4616
  },
4617
+ {
4618
+ id: "rotate-cw",
4619
+ icon: "rotate-cw",
4620
+ label: "Rotate",
4621
+ type: "button",
4622
+ group: "view",
4623
+ execute: () => instance.rotateCW?.()
4624
+ },
4192
4625
  {
4193
4626
  id: "download",
4194
4627
  icon: "download",
@@ -4252,6 +4685,22 @@ var OpenDocumentPlugin = class {
4252
4685
  container.appendChild(wrapper);
4253
4686
  ctx.container.appendChild(container);
4254
4687
  let scale = 1;
4688
+ let rotation = 0;
4689
+ const calculateFitScale = () => {
4690
+ const elW = wrapper.offsetWidth || 850;
4691
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4692
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4693
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4694
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4695
+ };
4696
+ const applyTransform = () => {
4697
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4698
+ wrapper.style.transformOrigin = "top center";
4699
+ };
4700
+ setTimeout(() => {
4701
+ scale = calculateFitScale();
4702
+ applyTransform();
4703
+ }, 60);
4255
4704
  let currentPage = 1;
4256
4705
  let totalPages = 1;
4257
4706
  let slides = [];
@@ -4283,14 +4732,97 @@ var OpenDocumentPlugin = class {
4283
4732
  wrapper.textContent = contentDoc.documentElement.textContent || "Formula content";
4284
4733
  }
4285
4734
  } else {
4286
- wrapper.style.maxWidth = "850px";
4287
- wrapper.style.padding = "48px";
4735
+ wrapper.style.maxWidth = "none";
4736
+ wrapper.style.padding = "0";
4288
4737
  wrapper.style.minHeight = "100%";
4738
+ wrapper.style.backgroundColor = "transparent";
4739
+ wrapper.style.boxShadow = "none";
4740
+ wrapper.style.position = "relative";
4741
+ const dims = this.getPageDimensions(stylesDoc, contentDoc);
4289
4742
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
4290
- wrapper.innerHTML = DOMPurify2__default.default.sanitize(bodyHtml, {
4291
- ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
4743
+ const tempDiv = document.createElement("div");
4744
+ tempDiv.style.width = `${dims.width - dims.marginLeft - dims.marginRight}px`;
4745
+ tempDiv.style.position = "absolute";
4746
+ tempDiv.style.visibility = "hidden";
4747
+ tempDiv.innerHTML = DOMPurify6__default.default.sanitize(bodyHtml, {
4748
+ ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub", "hr"],
4292
4749
  ADD_ATTR: ["style", "colspan", "rowspan"]
4293
4750
  });
4751
+ document.body.appendChild(tempDiv);
4752
+ const contentHeight = dims.height - dims.marginTop - dims.marginBottom;
4753
+ const pageElements = [[]];
4754
+ let currentHeight = 0;
4755
+ let currentPageIdx = 0;
4756
+ Array.from(tempDiv.children).forEach((child) => {
4757
+ const el = child;
4758
+ const style = el.getAttribute("style") || "";
4759
+ const isBreakBefore = style.includes("page-break-before: always");
4760
+ const isBreakAfter = style.includes("page-break-after: always");
4761
+ const isSoftBreak = el.classList.contains("odf-page-break");
4762
+ if (isBreakBefore) {
4763
+ if (pageElements[currentPageIdx].length > 0) {
4764
+ currentPageIdx++;
4765
+ pageElements.push([]);
4766
+ currentHeight = 0;
4767
+ }
4768
+ }
4769
+ const h = el.offsetHeight || 0;
4770
+ if (currentHeight + h > contentHeight && pageElements[currentPageIdx].length > 0 && !isSoftBreak) {
4771
+ currentPageIdx++;
4772
+ pageElements.push([]);
4773
+ currentHeight = 0;
4774
+ }
4775
+ if (!isSoftBreak) {
4776
+ pageElements[currentPageIdx].push(el.cloneNode(true));
4777
+ currentHeight += h;
4778
+ }
4779
+ if (isBreakAfter || isSoftBreak) {
4780
+ currentPageIdx++;
4781
+ pageElements.push([]);
4782
+ currentHeight = 0;
4783
+ }
4784
+ });
4785
+ document.body.removeChild(tempDiv);
4786
+ if (pageElements.length > 1 && pageElements[pageElements.length - 1].length === 0) {
4787
+ pageElements.pop();
4788
+ }
4789
+ totalPages = Math.max(1, pageElements.length);
4790
+ pageElements.forEach((elements, idx) => {
4791
+ const page = document.createElement("div");
4792
+ page.className = `fp-odt-page fp-odt-page-${idx + 1}`;
4793
+ page.style.width = `${dims.width}px`;
4794
+ page.style.minHeight = `${dims.height}px`;
4795
+ page.style.padding = `${dims.marginTop}px ${dims.marginRight}px ${dims.marginBottom}px ${dims.marginLeft}px`;
4796
+ page.style.margin = "0 auto";
4797
+ page.style.backgroundColor = "#ffffff";
4798
+ page.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
4799
+ page.style.borderRadius = "4px";
4800
+ page.style.boxSizing = "border-box";
4801
+ page.style.display = idx === 0 ? "block" : "none";
4802
+ page.style.position = "absolute";
4803
+ page.style.top = "0";
4804
+ page.style.left = "50%";
4805
+ page.style.transform = "translateX(-50%)";
4806
+ elements.forEach((el) => page.appendChild(el));
4807
+ wrapper.appendChild(page);
4808
+ slides.push(page);
4809
+ });
4810
+ const pageIndicator = document.createElement("div");
4811
+ pageIndicator.className = "fp-odt-page-indicator";
4812
+ pageIndicator.style.position = "sticky";
4813
+ pageIndicator.style.bottom = "16px";
4814
+ pageIndicator.style.left = "50%";
4815
+ pageIndicator.style.transform = "translateX(-50%)";
4816
+ pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
4817
+ pageIndicator.style.color = "#fff";
4818
+ pageIndicator.style.padding = "6px 12px";
4819
+ pageIndicator.style.borderRadius = "16px";
4820
+ pageIndicator.style.fontSize = "12px";
4821
+ pageIndicator.style.zIndex = "100";
4822
+ pageIndicator.style.display = "inline-block";
4823
+ pageIndicator.style.width = "fit-content";
4824
+ pageIndicator.textContent = `Page 1 of ${totalPages}`;
4825
+ container.appendChild(pageIndicator);
4294
4826
  }
4295
4827
  const cleanup = () => {
4296
4828
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
@@ -4299,32 +4831,45 @@ var OpenDocumentPlugin = class {
4299
4831
  };
4300
4832
  ctx.signal.addEventListener("abort", cleanup);
4301
4833
  const goToPage = (page) => {
4302
- if (!isPresentation || page < 1 || page > totalPages) return;
4834
+ if (page < 1 || page > totalPages) return;
4303
4835
  currentPage = page;
4304
4836
  slides.forEach((s, idx) => {
4305
4837
  s.style.display = idx === page - 1 ? "block" : "none";
4306
4838
  });
4839
+ const indicator = container.querySelector(".fp-odt-page-indicator");
4840
+ if (indicator) {
4841
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4842
+ }
4307
4843
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4308
4844
  };
4309
4845
  return {
4310
4846
  destroy: cleanup,
4311
4847
  isPresentation,
4312
4848
  zoomIn: () => {
4313
- scale += 0.1;
4314
- wrapper.style.transform = `scale(${scale})`;
4849
+ scale += 0.15;
4850
+ applyTransform();
4315
4851
  },
4316
4852
  zoomOut: () => {
4317
- scale = Math.max(0.2, scale - 0.1);
4318
- wrapper.style.transform = `scale(${scale})`;
4853
+ scale = Math.max(0.2, scale - 0.15);
4854
+ applyTransform();
4319
4855
  },
4320
4856
  getZoom: () => scale,
4321
4857
  setZoom: (level) => {
4322
4858
  scale = level;
4323
- wrapper.style.transform = `scale(${scale})`;
4859
+ applyTransform();
4324
4860
  },
4325
4861
  fitToPage: () => {
4326
- scale = 1;
4327
- wrapper.style.transform = "scale(1)";
4862
+ scale = calculateFitScale();
4863
+ rotation = 0;
4864
+ applyTransform();
4865
+ },
4866
+ rotateCW: () => {
4867
+ rotation = (rotation + 90) % 360;
4868
+ applyTransform();
4869
+ },
4870
+ rotateCCW: () => {
4871
+ rotation = (rotation - 90 + 360) % 360;
4872
+ applyTransform();
4328
4873
  },
4329
4874
  goToPage,
4330
4875
  getPageCount: () => totalPages,
@@ -4363,6 +4908,43 @@ var OpenDocumentPlugin = class {
4363
4908
  }
4364
4909
  };
4365
4910
  }
4911
+ getPageDimensions(stylesDoc, contentDoc) {
4912
+ let width = 850;
4913
+ let height = 1123;
4914
+ let marginTop = 48;
4915
+ let marginBottom = 48;
4916
+ let marginLeft = 48;
4917
+ let marginRight = 48;
4918
+ const parseUnit = (val) => {
4919
+ if (!val) return null;
4920
+ if (val.endsWith("cm")) return parseFloat(val) * 37.8;
4921
+ if (val.endsWith("mm")) return parseFloat(val) * 3.78;
4922
+ if (val.endsWith("in")) return parseFloat(val) * 96;
4923
+ if (val.endsWith("pt")) return parseFloat(val) * 1.33;
4924
+ if (val.endsWith("px")) return parseFloat(val);
4925
+ return parseFloat(val);
4926
+ };
4927
+ const docs = [stylesDoc, contentDoc].filter(Boolean);
4928
+ for (const doc of docs) {
4929
+ const pageLayout = doc.querySelector("page-layout-properties, [page-width]");
4930
+ if (pageLayout) {
4931
+ const w = parseUnit(pageLayout.getAttribute("fo:page-width") || pageLayout.getAttribute("page-width"));
4932
+ const h = parseUnit(pageLayout.getAttribute("fo:page-height") || pageLayout.getAttribute("page-height"));
4933
+ const mt = parseUnit(pageLayout.getAttribute("fo:margin-top") || pageLayout.getAttribute("margin-top"));
4934
+ const mb = parseUnit(pageLayout.getAttribute("fo:margin-bottom") || pageLayout.getAttribute("margin-bottom"));
4935
+ const ml = parseUnit(pageLayout.getAttribute("fo:margin-left") || pageLayout.getAttribute("margin-left"));
4936
+ const mr = parseUnit(pageLayout.getAttribute("fo:margin-right") || pageLayout.getAttribute("margin-right"));
4937
+ if (w !== null) width = w;
4938
+ if (h !== null) height = h;
4939
+ if (mt !== null) marginTop = mt;
4940
+ if (mb !== null) marginBottom = mb;
4941
+ if (ml !== null) marginLeft = ml;
4942
+ if (mr !== null) marginRight = mr;
4943
+ break;
4944
+ }
4945
+ }
4946
+ return { width, height, marginTop, marginBottom, marginLeft, marginRight };
4947
+ }
4366
4948
  extractStyles(stylesDoc, contentDoc) {
4367
4949
  const map = /* @__PURE__ */ new Map();
4368
4950
  const styleNodes = [];
@@ -4385,14 +4967,21 @@ var OpenDocumentPlugin = class {
4385
4967
  if (color) css += `color: ${color}; `;
4386
4968
  if (size) css += `font-size: ${size}; `;
4387
4969
  }
4388
- const paraProp = node.querySelector("paragraph-properties, [text-align]");
4970
+ let paraProp = node.querySelector("paragraph-properties, [text-align]");
4971
+ if (!paraProp) {
4972
+ paraProp = Array.from(node.children).find((c) => c.tagName.includes("paragraph-properties")) || null;
4973
+ }
4389
4974
  if (paraProp) {
4390
4975
  const align = paraProp.getAttribute("fo:text-align") || paraProp.getAttribute("text-align");
4391
4976
  const mt = paraProp.getAttribute("fo:margin-top") || paraProp.getAttribute("margin-top");
4392
4977
  const mb = paraProp.getAttribute("fo:margin-bottom") || paraProp.getAttribute("margin-bottom");
4978
+ const breakBefore = paraProp.getAttribute("fo:break-before") || paraProp.getAttribute("break-before");
4979
+ const breakAfter = paraProp.getAttribute("fo:break-after") || paraProp.getAttribute("break-after");
4393
4980
  if (align) css += `text-align: ${align}; `;
4394
4981
  if (mt) css += `margin-top: ${mt}; `;
4395
4982
  if (mb) css += `margin-bottom: ${mb}; `;
4983
+ if (breakBefore === "page") css += "page-break-before: always; ";
4984
+ if (breakAfter === "page") css += "page-break-after: always; ";
4396
4985
  }
4397
4986
  if (css) map.set(name, css);
4398
4987
  }
@@ -4477,6 +5066,8 @@ var OpenDocumentPlugin = class {
4477
5066
  result += "&emsp;";
4478
5067
  } else if (tag === "line-break") {
4479
5068
  result += "<br/>";
5069
+ } else if (tag === "soft-page-break") {
5070
+ result += '<hr class="odf-page-break" style="page-break-after: always; border: none; margin: 0; padding: 0; height: 0;" />';
4480
5071
  } else {
4481
5072
  result += el.textContent || "";
4482
5073
  }
@@ -4594,6 +5185,14 @@ var DocPlugin = class {
4594
5185
  group: "zoom",
4595
5186
  execute: () => instance.fitToPage?.()
4596
5187
  },
5188
+ {
5189
+ id: "rotate-cw",
5190
+ icon: "rotate-cw",
5191
+ label: "Rotate",
5192
+ type: "button",
5193
+ group: "view",
5194
+ execute: () => instance.rotateCW?.()
5195
+ },
4597
5196
  {
4598
5197
  id: "copy",
4599
5198
  icon: "copy",
@@ -4629,20 +5228,9 @@ var DocPlugin = class {
4629
5228
  container.style.overflow = "auto";
4630
5229
  container.style.padding = "32px 16px";
4631
5230
  container.style.backgroundColor = "#f1f5f9";
4632
- const wrapper = document.createElement("div");
4633
- wrapper.className = "fp-doc-wrapper";
4634
- wrapper.style.maxWidth = "850px";
4635
- wrapper.style.margin = "0 auto";
4636
- wrapper.style.backgroundColor = "#ffffff";
4637
- wrapper.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4638
- wrapper.style.borderRadius = "4px";
4639
- wrapper.style.padding = "56px 48px";
4640
- wrapper.style.minHeight = "100%";
4641
- wrapper.style.transformOrigin = "top center";
4642
- wrapper.style.transition = "transform 0.2s ease";
4643
- wrapper.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4644
- wrapper.style.color = "#1e293b";
4645
- container.appendChild(wrapper);
5231
+ container.style.display = "flex";
5232
+ container.style.justifyContent = "center";
5233
+ container.style.alignItems = "flex-start";
4646
5234
  ctx.container.appendChild(container);
4647
5235
  let scale = 1;
4648
5236
  let extractedRawText = "";
@@ -4669,21 +5257,27 @@ var DocPlugin = class {
4669
5257
  const rawPages = this.splitIntoPages(extractedRawText);
4670
5258
  const totalPages = Math.max(1, rawPages.length);
4671
5259
  let currentPage = 1;
4672
- wrapper.innerHTML = "";
4673
5260
  const pageCards = [];
4674
5261
  for (let i = 0; i < totalPages; i++) {
4675
5262
  const pageCard = document.createElement("div");
4676
5263
  pageCard.className = "fp-doc-page-card";
5264
+ pageCard.style.width = "816px";
5265
+ pageCard.style.height = "1056px";
4677
5266
  pageCard.style.backgroundColor = "#ffffff";
4678
5267
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4679
5268
  pageCard.style.borderRadius = "4px";
4680
- pageCard.style.padding = "56px 48px";
4681
- pageCard.style.minHeight = "100%";
5269
+ pageCard.style.padding = "96px 72px";
5270
+ pageCard.style.boxSizing = "border-box";
5271
+ pageCard.style.overflow = "hidden";
4682
5272
  pageCard.style.display = i === 0 ? "block" : "none";
5273
+ pageCard.style.transformOrigin = "top center";
5274
+ pageCard.style.transition = "transform 0.2s ease";
5275
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5276
+ pageCard.style.color = "#1e293b";
4683
5277
  if (isFallback && i === 0) {
4684
5278
  pageCard.innerHTML = `
4685
5279
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4686
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2__default.default.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
5280
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify6__default.default.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4687
5281
  <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4688
5282
  </div>
4689
5283
  ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
@@ -4691,15 +5285,17 @@ var DocPlugin = class {
4691
5285
  } else {
4692
5286
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4693
5287
  }
4694
- wrapper.appendChild(pageCard);
5288
+ container.appendChild(pageCard);
4695
5289
  pageCards.push(pageCard);
4696
5290
  }
4697
5291
  let indicator = null;
4698
5292
  if (totalPages > 1) {
4699
5293
  indicator = document.createElement("div");
4700
5294
  indicator.className = "fp-doc-page-indicator";
4701
- indicator.style.position = "sticky";
5295
+ indicator.style.position = "fixed";
4702
5296
  indicator.style.bottom = "16px";
5297
+ indicator.style.left = "50%";
5298
+ indicator.style.transform = "translateX(-50%)";
4703
5299
  indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4704
5300
  indicator.style.backdropFilter = "blur(8px)";
4705
5301
  indicator.style.color = "#f8fafc";
@@ -4713,17 +5309,25 @@ var DocPlugin = class {
4713
5309
  indicator.style.userSelect = "none";
4714
5310
  indicator.style.pointerEvents = "none";
4715
5311
  indicator.style.textAlign = "center";
4716
- indicator.style.width = "fit-content";
4717
- indicator.style.margin = "16px auto 0";
4718
5312
  container.appendChild(indicator);
4719
5313
  }
5314
+ let rotation = 0;
5315
+ const applyTransform = () => {
5316
+ const activeCard = pageCards[currentPage - 1];
5317
+ if (activeCard) {
5318
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5319
+ }
5320
+ };
4720
5321
  const showPage = (pageNum) => {
4721
5322
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4722
- if (totalPages > 1) {
4723
- pageCards.forEach((card, idx) => {
4724
- card.style.display = idx + 1 === currentPage ? "block" : "none";
4725
- });
4726
- }
5323
+ pageCards.forEach((card, idx) => {
5324
+ if (idx + 1 === currentPage) {
5325
+ card.style.display = "block";
5326
+ card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5327
+ } else {
5328
+ card.style.display = "none";
5329
+ }
5330
+ });
4727
5331
  if (indicator) {
4728
5332
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4729
5333
  }
@@ -4732,6 +5336,17 @@ var DocPlugin = class {
4732
5336
  if (totalPages > 1) {
4733
5337
  showPage(1);
4734
5338
  }
5339
+ const calculateFitScale = () => {
5340
+ const elW = 816;
5341
+ const elH = 1056;
5342
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
5343
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
5344
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
5345
+ };
5346
+ setTimeout(() => {
5347
+ scale = calculateFitScale();
5348
+ applyTransform();
5349
+ }, 60);
4735
5350
  const cleanup = () => {
4736
5351
  indicator?.remove();
4737
5352
  container.remove();
@@ -4744,21 +5359,30 @@ var DocPlugin = class {
4744
5359
  getCurrentPage: () => currentPage,
4745
5360
  goToPage: (page) => showPage(page),
4746
5361
  zoomIn: () => {
4747
- scale += 0.1;
4748
- wrapper.style.transform = `scale(${scale})`;
5362
+ scale += 0.15;
5363
+ applyTransform();
4749
5364
  },
4750
5365
  zoomOut: () => {
4751
- scale = Math.max(0.2, scale - 0.1);
4752
- wrapper.style.transform = `scale(${scale})`;
5366
+ scale = Math.max(0.2, scale - 0.15);
5367
+ applyTransform();
4753
5368
  },
4754
5369
  getZoom: () => scale,
4755
5370
  setZoom: (level) => {
4756
5371
  scale = level;
4757
- wrapper.style.transform = `scale(${scale})`;
5372
+ applyTransform();
4758
5373
  },
4759
5374
  fitToPage: () => {
4760
- scale = 1;
4761
- wrapper.style.transform = "scale(1)";
5375
+ scale = calculateFitScale();
5376
+ rotation = 0;
5377
+ applyTransform();
5378
+ },
5379
+ rotateCW: () => {
5380
+ rotation = (rotation + 90) % 360;
5381
+ applyTransform();
5382
+ },
5383
+ rotateCCW: () => {
5384
+ rotation = (rotation - 90 + 360) % 360;
5385
+ applyTransform();
4762
5386
  },
4763
5387
  copy: () => {
4764
5388
  navigator.clipboard.writeText(extractedRawText);
@@ -4876,8 +5500,28 @@ var DocPlugin = class {
4876
5500
  }
4877
5501
  splitIntoPages(text) {
4878
5502
  if (!text) return [""];
4879
- const parts = 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);
4880
- return parts.length > 0 ? parts : [text];
5503
+ 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);
5504
+ if (explicitParts.length === 0) explicitParts.push(text);
5505
+ const maxLinesPerPage = 48;
5506
+ const finalPages = [];
5507
+ for (const part of explicitParts) {
5508
+ const lines = part.split(/\r?\n/);
5509
+ let currentLines = [];
5510
+ let count = 0;
5511
+ for (const line of lines) {
5512
+ currentLines.push(line);
5513
+ count++;
5514
+ if (count >= maxLinesPerPage) {
5515
+ finalPages.push(currentLines.join("\n"));
5516
+ currentLines = [];
5517
+ count = 0;
5518
+ }
5519
+ }
5520
+ if (currentLines.length > 0) {
5521
+ finalPages.push(currentLines.join("\n"));
5522
+ }
5523
+ }
5524
+ return finalPages.length > 0 ? finalPages : [text];
4881
5525
  }
4882
5526
  /**
4883
5527
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
@@ -4886,17 +5530,65 @@ var DocPlugin = class {
4886
5530
  const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4887
5531
  let html = "";
4888
5532
  let inList = false;
4889
- for (const rawLine of lines) {
4890
- const line = rawLine.trim();
5533
+ let tableLines = [];
5534
+ const flushTable = () => {
5535
+ if (tableLines.length > 0) {
5536
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 12px;">';
5537
+ for (const tLine of tableLines) {
5538
+ html += "<tr>";
5539
+ const cols = tLine.split(" ");
5540
+ for (const col of cols) {
5541
+ html += `<td style="border: 1px solid #cbd5e1; padding: 6px 8px;">${DOMPurify6__default.default.sanitize(col.trim())}</td>`;
5542
+ }
5543
+ html += "</tr>";
5544
+ }
5545
+ html += "</table>";
5546
+ tableLines = [];
5547
+ }
5548
+ };
5549
+ let i = 0;
5550
+ while (i < lines.length) {
5551
+ let line = lines[i];
5552
+ let tabCount = (line.match(/\t/g) || []).length;
5553
+ if (tabCount > 0) {
5554
+ let consecutiveTableLines = 1;
5555
+ let j = i + 1;
5556
+ while (j < lines.length) {
5557
+ const nextTabCount = (lines[j].match(/\t/g) || []).length;
5558
+ if (nextTabCount === tabCount) {
5559
+ consecutiveTableLines++;
5560
+ j++;
5561
+ } else {
5562
+ break;
5563
+ }
5564
+ }
5565
+ if (consecutiveTableLines >= 3) {
5566
+ if (inList) {
5567
+ html += "</ul>";
5568
+ inList = false;
5569
+ }
5570
+ tableLines = lines.slice(i, j);
5571
+ flushTable();
5572
+ i = j;
5573
+ continue;
5574
+ }
5575
+ }
5576
+ line = line.trim();
4891
5577
  if (!line) {
4892
5578
  if (inList) {
4893
5579
  html += "</ul>";
4894
5580
  inList = false;
4895
5581
  }
5582
+ html += '<div style="height: 1.15em;"></div>';
5583
+ i++;
4896
5584
  continue;
4897
5585
  }
4898
- if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) continue;
4899
- if (line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman") && line.length < 30) {
5586
+ if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) {
5587
+ i++;
5588
+ continue;
5589
+ }
5590
+ if ((line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman")) && line.length < 30) {
5591
+ i++;
4900
5592
  continue;
4901
5593
  }
4902
5594
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
@@ -4904,24 +5596,26 @@ var DocPlugin = class {
4904
5596
  html += "</ul>";
4905
5597
  inList = false;
4906
5598
  }
4907
- html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify2__default.default.sanitize(line)}</h2>`;
5599
+ html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line)}</h2>`;
4908
5600
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4909
5601
  if (!inList) {
4910
5602
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4911
5603
  inList = true;
4912
5604
  }
4913
5605
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4914
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2__default.default.sanitize(bulletText)}</li>`;
5606
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText)}</li>`;
4915
5607
  } else {
4916
5608
  if (inList) {
4917
5609
  html += "</ul>";
4918
5610
  inList = false;
4919
5611
  }
4920
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2__default.default.sanitize(line)}</p>`;
5612
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line)}</p>`;
4921
5613
  }
5614
+ i++;
4922
5615
  }
4923
5616
  if (inList) html += "</ul>";
4924
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2__default.default.sanitize(filename)})</p>`;
5617
+ flushTable();
5618
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6__default.default.sanitize(filename)})</p>`;
4925
5619
  }
4926
5620
  };
4927
5621
  function docPlugin() {
@@ -4998,6 +5692,14 @@ var PptPlugin = class {
4998
5692
  group: "zoom",
4999
5693
  execute: () => instance.fitToPage?.()
5000
5694
  },
5695
+ {
5696
+ id: "rotate-cw",
5697
+ icon: "rotate-cw",
5698
+ label: "Rotate",
5699
+ type: "button",
5700
+ group: "view",
5701
+ execute: () => instance.rotateCW?.()
5702
+ },
5001
5703
  {
5002
5704
  id: "download",
5003
5705
  icon: "download",
@@ -5031,22 +5733,36 @@ var PptPlugin = class {
5031
5733
  const slideCard = document.createElement("div");
5032
5734
  slideCard.className = "fp-ppt-slide-card";
5033
5735
  slideCard.style.width = "960px";
5034
- slideCard.style.maxWidth = "92%";
5736
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5737
+ slideCard.style.maxHeight = "calc(100% - 48px)";
5035
5738
  slideCard.style.aspectRatio = "16 / 9";
5036
5739
  slideCard.style.backgroundColor = "#ffffff";
5037
5740
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
5038
5741
  slideCard.style.borderRadius = "8px";
5039
5742
  slideCard.style.boxSizing = "border-box";
5040
5743
  slideCard.style.position = "relative";
5041
- slideCard.style.overflow = "hidden";
5042
- slideCard.style.transformOrigin = "center center";
5043
5744
  slideCard.style.transition = "transform 0.2s ease";
5745
+ slideCard.style.transformOrigin = "center center";
5746
+ slideCard.style.flexShrink = "0";
5044
5747
  container.appendChild(slideCard);
5045
5748
  ctx.container.appendChild(container);
5046
5749
  let scale = 1;
5750
+ let rotation = 0;
5047
5751
  let currentSlide = 1;
5048
5752
  let slides = [];
5049
5753
  const createdBlobUrls = [];
5754
+ const calculateFitScale = () => {
5755
+ const availW = Math.max(200, container.clientWidth - 48);
5756
+ const availH = Math.max(200, container.clientHeight - 72);
5757
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5758
+ };
5759
+ const applyTransform = () => {
5760
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5761
+ };
5762
+ setTimeout(() => {
5763
+ scale = calculateFitScale();
5764
+ applyTransform();
5765
+ }, 60);
5050
5766
  try {
5051
5767
  const cfbf = new CfbfReader(ctx.buffer);
5052
5768
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -5077,7 +5793,7 @@ var PptPlugin = class {
5077
5793
  if (s.pictureUrl) {
5078
5794
  contentHtml = `
5079
5795
  <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
5080
- <img src="${s.pictureUrl}" alt="${DOMPurify2__default.default.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
5796
+ <img src="${s.pictureUrl}" alt="${DOMPurify6__default.default.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
5081
5797
  </div>
5082
5798
  `;
5083
5799
  } else if (s.tableColumns.length > 0) {
@@ -5087,7 +5803,7 @@ var PptPlugin = class {
5087
5803
  <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
5088
5804
  <thead>
5089
5805
  <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
5090
- ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2__default.default.sanitize(c)}</th>`).join("")}
5806
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify6__default.default.sanitize(c)}</th>`).join("")}
5091
5807
  </tr>
5092
5808
  </thead>
5093
5809
  <tbody>
@@ -5103,7 +5819,7 @@ var PptPlugin = class {
5103
5819
  } else {
5104
5820
  const pTags = s.paragraphs.map((p) => {
5105
5821
  const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
5106
- return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2__default.default.sanitize(l)}</p>`).join("");
5822
+ return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify6__default.default.sanitize(l)}</p>`).join("");
5107
5823
  }).join("");
5108
5824
  contentHtml = `
5109
5825
  <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
@@ -5116,11 +5832,11 @@ var PptPlugin = class {
5116
5832
  <!-- Header Banner matching PowerPoint design -->
5117
5833
  <div style="background: linear-gradient(90deg, #a3e635 0%, #84cc16 100%); padding: 12px 24px; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); flex-shrink: 0;">
5118
5834
  <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
5119
- ${DOMPurify2__default.default.sanitize(s.title)}
5835
+ ${DOMPurify6__default.default.sanitize(s.title)}
5120
5836
  </h1>
5121
5837
  ${s.subtitle ? `
5122
5838
  <span style="background: #38bdf8; color: #ffffff; padding: 4px 14px; border-radius: 4px; font-weight: 700; font-size: 13px; letter-spacing: 0.5px; box-shadow: 0 1px 4px rgba(0,0,0,0.15);">
5123
- ${DOMPurify2__default.default.sanitize(s.subtitle)}
5839
+ ${DOMPurify6__default.default.sanitize(s.subtitle)}
5124
5840
  </span>
5125
5841
  ` : `
5126
5842
  <span style="font-size: 12px; color: #365314; font-weight: 600;">
@@ -5147,21 +5863,30 @@ var PptPlugin = class {
5147
5863
  return {
5148
5864
  destroy: cleanup,
5149
5865
  zoomIn: () => {
5150
- scale += 0.1;
5151
- slideCard.style.transform = `scale(${scale})`;
5866
+ scale += 0.15;
5867
+ applyTransform();
5152
5868
  },
5153
5869
  zoomOut: () => {
5154
- scale = Math.max(0.3, scale - 0.1);
5155
- slideCard.style.transform = `scale(${scale})`;
5870
+ scale = Math.max(0.2, scale - 0.15);
5871
+ applyTransform();
5156
5872
  },
5157
5873
  getZoom: () => scale,
5158
5874
  setZoom: (level) => {
5159
5875
  scale = level;
5160
- slideCard.style.transform = `scale(${scale})`;
5876
+ applyTransform();
5161
5877
  },
5162
5878
  fitToPage: () => {
5163
- scale = 1;
5164
- slideCard.style.transform = "scale(1)";
5879
+ scale = calculateFitScale();
5880
+ rotation = 0;
5881
+ applyTransform();
5882
+ },
5883
+ rotateCW: () => {
5884
+ rotation = (rotation + 90) % 360;
5885
+ applyTransform();
5886
+ },
5887
+ rotateCCW: () => {
5888
+ rotation = (rotation - 90 + 360) % 360;
5889
+ applyTransform();
5165
5890
  },
5166
5891
  goToPage: (page) => {
5167
5892
  if (page >= 1 && page <= totalSlides) {