@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/vue.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { defineComponent, ref, onMounted, watch, onBeforeUnmount, h } from 'vue';
2
- import DOMPurify2 from 'dompurify';
2
+ import DOMPurify6 from 'dompurify';
3
3
  import * as pdfjsLib from 'pdfjs-dist';
4
4
  import * as docx from 'docx-preview';
5
5
  import { unzipSync, strFromU8, unzip } from 'fflate';
@@ -357,7 +357,7 @@ async function sourceToArrayBuffer(source, signal) {
357
357
  if (magicMime === "application/zip") {
358
358
  const ooxmlMime = detectOoxmlType(buffer);
359
359
  if (ooxmlMime && ooxmlMime !== "application/zip") {
360
- metadata.mimeType = ooxmlMime;
360
+ metadata.mimeType = metadata.mimeType ?? ooxmlMime;
361
361
  if (ooxmlMime.includes("wordprocessing")) metadata.extension = metadata.extension ?? ".docx";
362
362
  else if (ooxmlMime.includes("spreadsheet")) metadata.extension = metadata.extension ?? ".xlsx";
363
363
  else if (ooxmlMime.includes("presentation")) metadata.extension = metadata.extension ?? ".pptx";
@@ -386,7 +386,7 @@ async function sourceToArrayBuffer(source, signal) {
386
386
  return { buffer, metadata };
387
387
  }
388
388
  function sanitizeSVG(svg) {
389
- return DOMPurify2.sanitize(svg, {
389
+ return DOMPurify6.sanitize(svg, {
390
390
  USE_PROFILES: { svg: true, svgFilters: true }
391
391
  });
392
392
  }
@@ -705,6 +705,8 @@ var FilePreviewViewer = class {
705
705
  keyHandler = null;
706
706
  currentContainer = null;
707
707
  currentOptions = {};
708
+ resizeObserver = null;
709
+ fullscreenHandler = null;
708
710
  /**
709
711
  * Register a preview plugin.
710
712
  */
@@ -777,12 +779,31 @@ var FilePreviewViewer = class {
777
779
  label: "Toggle Fullscreen",
778
780
  type: "button",
779
781
  group: "view",
780
- execute: () => {
781
- if (!document.fullscreenElement) {
782
- this.wrapperEl?.requestFullscreen?.();
783
- } else {
784
- document.exitFullscreen?.();
782
+ execute: async () => {
783
+ try {
784
+ const isNativeFs = !!document.fullscreenElement;
785
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
786
+ if (!isNativeFs && !isCssFs) {
787
+ if (this.wrapperEl?.requestFullscreen) {
788
+ await this.wrapperEl.requestFullscreen().catch(() => {
789
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
790
+ });
791
+ } else {
792
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
793
+ }
794
+ } else {
795
+ if (document.fullscreenElement) {
796
+ await document.exitFullscreen().catch(() => {
797
+ });
798
+ }
799
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
800
+ }
801
+ } catch {
802
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
785
803
  }
804
+ setTimeout(() => {
805
+ this.activeInstance?.fitToPage?.();
806
+ }, 120);
786
807
  }
787
808
  });
788
809
  }
@@ -832,6 +853,15 @@ var FilePreviewViewer = class {
832
853
  window.removeEventListener("keydown", this.keyHandler);
833
854
  this.keyHandler = null;
834
855
  }
856
+ if (this.resizeObserver) {
857
+ this.resizeObserver.disconnect();
858
+ this.resizeObserver = null;
859
+ }
860
+ if (this.fullscreenHandler) {
861
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
862
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
863
+ this.fullscreenHandler = null;
864
+ }
835
865
  this.abort();
836
866
  this.destroyInstance();
837
867
  this.toolbar?.destroy();
@@ -911,6 +941,25 @@ var FilePreviewViewer = class {
911
941
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
912
942
  this.setupKeyboardShortcuts();
913
943
  this.setupDragAndDrop(container, options);
944
+ this.setupResizeAndFullscreenListeners();
945
+ }
946
+ setupResizeAndFullscreenListeners() {
947
+ if (this.fullscreenHandler) return;
948
+ this.fullscreenHandler = () => {
949
+ setTimeout(() => {
950
+ this.activeInstance?.fitToPage?.();
951
+ }, 100);
952
+ };
953
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
954
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
955
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
956
+ this.resizeObserver = new ResizeObserver(() => {
957
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
958
+ this.activeInstance.fitToPage?.();
959
+ }
960
+ });
961
+ this.resizeObserver.observe(this.contentEl);
962
+ }
914
963
  }
915
964
  setupKeyboardShortcuts() {
916
965
  if (this.keyHandler) return;
@@ -1302,7 +1351,8 @@ var PdfPlugin = class {
1302
1351
  container.style.display = "flex";
1303
1352
  container.style.flexDirection = "column";
1304
1353
  container.style.alignItems = "center";
1305
- container.style.padding = "24px 16px";
1354
+ container.style.justifyContent = "flex-start";
1355
+ container.style.padding = "20px 16px";
1306
1356
  container.style.backgroundColor = "#0f172a";
1307
1357
  container.style.boxSizing = "border-box";
1308
1358
  container.style.position = "relative";
@@ -1315,7 +1365,8 @@ var PdfPlugin = class {
1315
1365
  pageCard.style.lineHeight = "0";
1316
1366
  pageCard.style.transition = "transform 0.15s ease";
1317
1367
  pageCard.style.position = "relative";
1318
- const canvas = document.createElement("canvas");
1368
+ pageCard.style.flexShrink = "0";
1369
+ let canvas = document.createElement("canvas");
1319
1370
  pageCard.appendChild(canvas);
1320
1371
  container.appendChild(pageCard);
1321
1372
  const indicator = document.createElement("div");
@@ -1357,14 +1408,22 @@ var PdfPlugin = class {
1357
1408
  }
1358
1409
  currentRenderTask = null;
1359
1410
  }
1411
+ const newCanvas = document.createElement("canvas");
1412
+ pageCard.replaceChild(newCanvas, canvas);
1413
+ canvas = newCanvas;
1360
1414
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1361
1415
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1362
1416
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1363
1417
  const page = await pdfDoc.getPage(currentPage);
1364
1418
  const containerWidth = container.clientWidth || 900;
1419
+ const containerHeight = container.clientHeight || 700;
1365
1420
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1366
- const baseScale = Math.min((containerWidth - 64) / unscaledVp.width, 1.6);
1367
- const effectiveScale = (baseScale > 0 ? baseScale : 1) * zoomScale;
1421
+ const availWidth = Math.max(100, containerWidth - 48);
1422
+ const availHeight = Math.max(100, containerHeight - 88);
1423
+ const scaleW = availWidth / unscaledVp.width;
1424
+ const scaleH = availHeight / unscaledVp.height;
1425
+ const fitScale = Math.min(scaleW, scaleH);
1426
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1368
1427
  const pixelRatio = window.devicePixelRatio || 1;
1369
1428
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
1370
1429
  canvas.width = Math.floor(viewport.width * pixelRatio);
@@ -1389,7 +1448,19 @@ var PdfPlugin = class {
1389
1448
  }
1390
1449
  };
1391
1450
  await renderPage(1);
1451
+ let resizeTimer = null;
1452
+ const resizeObserver = new ResizeObserver(() => {
1453
+ if (resizeTimer) clearTimeout(resizeTimer);
1454
+ resizeTimer = setTimeout(() => {
1455
+ if (Math.abs(zoomScale - 1) < 0.05) {
1456
+ renderPage(currentPage);
1457
+ }
1458
+ }, 150);
1459
+ });
1460
+ resizeObserver.observe(container);
1392
1461
  const cleanup = () => {
1462
+ resizeObserver.disconnect();
1463
+ if (resizeTimer) clearTimeout(resizeTimer);
1393
1464
  if (currentRenderTask) {
1394
1465
  try {
1395
1466
  currentRenderTask.cancel();
@@ -1421,6 +1492,7 @@ var PdfPlugin = class {
1421
1492
  },
1422
1493
  fitToPage: () => {
1423
1494
  zoomScale = 1;
1495
+ rotation = 0;
1424
1496
  renderPage(currentPage);
1425
1497
  },
1426
1498
  rotateCW: () => {
@@ -1871,6 +1943,14 @@ var DocxPlugin = class {
1871
1943
  group: "zoom",
1872
1944
  execute: () => instance.fitToPage?.()
1873
1945
  },
1946
+ {
1947
+ id: "rotate-cw",
1948
+ icon: "rotate-cw",
1949
+ label: "Rotate",
1950
+ type: "button",
1951
+ group: "view",
1952
+ execute: () => instance.rotateCW?.()
1953
+ },
1874
1954
  {
1875
1955
  id: "download",
1876
1956
  icon: "download",
@@ -2007,6 +2087,26 @@ var DocxPlugin = class {
2007
2087
  if (totalPages > 1) {
2008
2088
  showPage(1);
2009
2089
  }
2090
+ scale = 1;
2091
+ let rotation = 0;
2092
+ const calculateFitScale = () => {
2093
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2094
+ const elW = activeEl.offsetWidth || 816;
2095
+ const elH = activeEl.offsetHeight || 1056;
2096
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2097
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2098
+ const sW = availW / elW;
2099
+ const sH = availH / elH;
2100
+ return Math.min(1.1, Math.min(sW, sH));
2101
+ };
2102
+ const applyTransform = () => {
2103
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2104
+ wrapper.style.transformOrigin = "top center";
2105
+ };
2106
+ setTimeout(() => {
2107
+ scale = calculateFitScale();
2108
+ applyTransform();
2109
+ }, 60);
2010
2110
  const cleanup = () => {
2011
2111
  indicator?.remove();
2012
2112
  for (const url of createdBlobUrls) {
@@ -2025,21 +2125,30 @@ var DocxPlugin = class {
2025
2125
  showPage(page);
2026
2126
  },
2027
2127
  zoomIn: () => {
2028
- scale += 0.1;
2029
- wrapper.style.transform = `scale(${scale})`;
2128
+ scale += 0.15;
2129
+ applyTransform();
2030
2130
  },
2031
2131
  zoomOut: () => {
2032
- scale = Math.max(0.2, scale - 0.1);
2033
- wrapper.style.transform = `scale(${scale})`;
2132
+ scale = Math.max(0.2, scale - 0.15);
2133
+ applyTransform();
2034
2134
  },
2035
2135
  getZoom: () => scale,
2036
2136
  setZoom: (level) => {
2037
2137
  scale = level;
2038
- wrapper.style.transform = `scale(${scale})`;
2138
+ applyTransform();
2039
2139
  },
2040
2140
  fitToPage: () => {
2041
- scale = 1;
2042
- wrapper.style.transform = "scale(1)";
2141
+ scale = calculateFitScale();
2142
+ rotation = 0;
2143
+ applyTransform();
2144
+ },
2145
+ rotateCW: () => {
2146
+ rotation = (rotation + 90) % 360;
2147
+ applyTransform();
2148
+ },
2149
+ rotateCCW: () => {
2150
+ rotation = (rotation - 90 + 360) % 360;
2151
+ applyTransform();
2043
2152
  },
2044
2153
  download: () => {
2045
2154
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2099,90 +2208,149 @@ var DocxPlugin = class {
2099
2208
  console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
2100
2209
  }
2101
2210
  }
2102
- const card = document.createElement("div");
2103
- card.className = "fp-docx-page-card";
2104
- card.style.backgroundColor = "#ffffff";
2105
- card.style.borderRadius = "6px";
2106
- card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2107
- card.style.padding = "48px 56px";
2108
- card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2109
- card.style.color = "#1e293b";
2110
- card.style.lineHeight = "1.6";
2211
+ const sectPrs = Array.from(doc.getElementsByTagNameNS("*", "sectPr"));
2212
+ const lastSectPr = sectPrs[sectPrs.length - 1];
2213
+ let defaultW = 12240;
2214
+ let defaultH = 15840;
2215
+ let margins = { top: 1440, right: 1440, bottom: 1440, left: 1440 };
2216
+ if (lastSectPr) {
2217
+ const pgSz = lastSectPr.getElementsByTagNameNS("*", "pgSz")[0];
2218
+ if (pgSz) {
2219
+ defaultW = parseInt(pgSz.getAttribute("w:w") || pgSz.getAttribute("w") || "12240", 10);
2220
+ defaultH = parseInt(pgSz.getAttribute("w:h") || pgSz.getAttribute("h") || "15840", 10);
2221
+ }
2222
+ const pgMar = lastSectPr.getElementsByTagNameNS("*", "pgMar")[0];
2223
+ if (pgMar) {
2224
+ margins.top = parseInt(pgMar.getAttribute("w:top") || pgMar.getAttribute("top") || "1440", 10);
2225
+ margins.bottom = parseInt(pgMar.getAttribute("w:bottom") || pgMar.getAttribute("bottom") || "1440", 10);
2226
+ margins.left = parseInt(pgMar.getAttribute("w:left") || pgMar.getAttribute("left") || "1440", 10);
2227
+ margins.right = parseInt(pgMar.getAttribute("w:right") || pgMar.getAttribute("right") || "1440", 10);
2228
+ }
2229
+ }
2230
+ const createPageCard = () => {
2231
+ const card = document.createElement("div");
2232
+ card.className = "fp-docx-page-card";
2233
+ card.style.backgroundColor = "#ffffff";
2234
+ card.style.borderRadius = "4px";
2235
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2236
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2237
+ card.style.color = "#1e293b";
2238
+ card.style.lineHeight = "1.6";
2239
+ card.style.boxSizing = "border-box";
2240
+ card.style.overflow = "hidden";
2241
+ card.style.position = "relative";
2242
+ card.style.marginBottom = "24px";
2243
+ card.style.width = `${defaultW / 15}px`;
2244
+ card.style.height = `${defaultH / 15}px`;
2245
+ card.style.padding = `${margins.top / 15}px ${margins.right / 15}px ${margins.bottom / 15}px ${margins.left / 15}px`;
2246
+ return card;
2247
+ };
2248
+ let currentCard = createPageCard();
2249
+ let currentHtml = "";
2250
+ const pages = [{ card: currentCard, html: "" }];
2251
+ const flushHtml = () => {
2252
+ pages[pages.length - 1].html += currentHtml;
2253
+ currentHtml = "";
2254
+ };
2255
+ const newPage = () => {
2256
+ flushHtml();
2257
+ currentCard = createPageCard();
2258
+ pages.push({ card: currentCard, html: "" });
2259
+ };
2111
2260
  const body = doc.getElementsByTagNameNS("*", "body")[0] || doc.documentElement;
2112
- let html = "";
2113
2261
  for (const child of Array.from(body.children)) {
2114
2262
  const tag = child.localName || child.nodeName.split(":").pop();
2115
2263
  if (tag === "p") {
2116
2264
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
2117
2265
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
2118
2266
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
2119
- const textContent = this.extractParagraphHtml(child, imageMap);
2120
- if (!textContent.trim()) {
2121
- html += '<div style="height: 10px;"></div>';
2122
- continue;
2123
- }
2124
- const lowerStyle = styleVal.toLowerCase();
2125
- if (lowerStyle.includes("title")) {
2126
- 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>`;
2127
- } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2128
- html += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2129
- } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2130
- html += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2131
- } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2132
- html += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2133
- } else if (numPr) {
2134
- 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>`;
2135
- } else {
2136
- html += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2267
+ const chunks = this.extractParagraphChunks(child, imageMap);
2268
+ for (let i = 0; i < chunks.length; i++) {
2269
+ if (i > 0) {
2270
+ newPage();
2271
+ }
2272
+ const textContent = chunks[i];
2273
+ if (!textContent.trim() && !textContent.includes("<img")) {
2274
+ currentHtml += '<div style="height: 10px;"></div>';
2275
+ continue;
2276
+ }
2277
+ const lowerStyle = styleVal.toLowerCase();
2278
+ if (lowerStyle.includes("title")) {
2279
+ 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>`;
2280
+ } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2281
+ currentHtml += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2282
+ } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2283
+ currentHtml += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2284
+ } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2285
+ currentHtml += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2286
+ } else if (numPr) {
2287
+ 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>`;
2288
+ } else {
2289
+ currentHtml += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2290
+ }
2137
2291
  }
2138
2292
  } else if (tag === "tbl") {
2139
- html += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2293
+ currentHtml += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2140
2294
  const rows = Array.from(child.getElementsByTagNameNS("*", "tr"));
2141
2295
  rows.forEach((tr, rIdx) => {
2142
- html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2296
+ currentHtml += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2143
2297
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
2144
2298
  cells.forEach((tc) => {
2145
2299
  const cellText = this.extractParagraphHtml(tc, imageMap);
2146
- html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2300
+ currentHtml += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2147
2301
  });
2148
- html += "</tr>";
2302
+ currentHtml += "</tr>";
2149
2303
  });
2150
- html += "</table>";
2304
+ currentHtml += "</table>";
2151
2305
  }
2152
2306
  }
2153
- card.innerHTML = DOMPurify2.sanitize(html, {
2154
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2155
- ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2156
- });
2157
- wrapper.appendChild(card);
2307
+ flushHtml();
2308
+ for (const page of pages) {
2309
+ page.card.innerHTML = DOMPurify6.sanitize(page.html, {
2310
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2311
+ ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2312
+ });
2313
+ wrapper.appendChild(page.card);
2314
+ }
2158
2315
  }
2159
2316
  extractParagraphHtml(pElement, imageMap = {}) {
2160
- let result = "";
2317
+ return this.extractParagraphChunks(pElement, imageMap).join("<br/>");
2318
+ }
2319
+ extractParagraphChunks(pElement, imageMap = {}) {
2320
+ const chunks = [""];
2321
+ let currentChunkIndex = 0;
2161
2322
  const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
2162
2323
  for (const drawing of drawings) {
2163
2324
  const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
2164
2325
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2165
2326
  if (rId && imageMap[rId]) {
2166
- result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2327
+ chunks[currentChunkIndex] += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2167
2328
  }
2168
2329
  }
2169
2330
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
2170
- if (runs.length === 0 && !result) {
2171
- return DOMPurify2.sanitize(pElement.textContent || "");
2331
+ if (runs.length === 0 && !chunks[currentChunkIndex]) {
2332
+ chunks[currentChunkIndex] = DOMPurify6.sanitize(pElement.textContent || "");
2333
+ return chunks;
2172
2334
  }
2173
2335
  for (const r of runs) {
2174
2336
  const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
2175
2337
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2176
2338
  if (rId && imageMap[rId]) {
2177
- result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2339
+ chunks[currentChunkIndex] += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2178
2340
  }
2179
- const brs = r.getElementsByTagNameNS("*", "br");
2180
- for (let i = 0; i < brs.length; i++) {
2181
- result += "<br/>";
2341
+ const brs = Array.from(r.getElementsByTagNameNS("*", "br"));
2342
+ for (const br of brs) {
2343
+ const type = br.getAttribute("w:type") || br.getAttribute("type");
2344
+ if (type === "page") {
2345
+ chunks.push("");
2346
+ currentChunkIndex++;
2347
+ } else {
2348
+ chunks[currentChunkIndex] += "<br/>";
2349
+ }
2182
2350
  }
2183
2351
  const tabs = r.getElementsByTagNameNS("*", "tab");
2184
2352
  if (tabs.length > 0) {
2185
- result += "&emsp;";
2353
+ chunks[currentChunkIndex] += "&emsp;";
2186
2354
  }
2187
2355
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
2188
2356
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
@@ -2196,7 +2364,7 @@ var DocxPlugin = class {
2196
2364
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
2197
2365
  let text = texts.map((t) => t.textContent || "").join("");
2198
2366
  if (!text) continue;
2199
- text = DOMPurify2.sanitize(text);
2367
+ text = DOMPurify6.sanitize(text);
2200
2368
  let styles = "";
2201
2369
  if (isBold) styles += "font-weight: bold; ";
2202
2370
  if (isItalic) styles += "font-style: italic; ";
@@ -2208,12 +2376,12 @@ var DocxPlugin = class {
2208
2376
  if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2209
2377
  }
2210
2378
  if (styles) {
2211
- result += `<span style="${styles}">${text}</span>`;
2379
+ chunks[currentChunkIndex] += `<span style="${styles}">${text}</span>`;
2212
2380
  } else {
2213
- result += text;
2381
+ chunks[currentChunkIndex] += text;
2214
2382
  }
2215
2383
  }
2216
- return result;
2384
+ return chunks;
2217
2385
  }
2218
2386
  renderBinaryDocFallback(ctx, wrapper) {
2219
2387
  const card = document.createElement("div");
@@ -2230,14 +2398,14 @@ var DocxPlugin = class {
2230
2398
  const wordDocStream = cfbf.readStream("WordDocument");
2231
2399
  if (wordDocStream && wordDocStream.length >= 512) {
2232
2400
  const text2 = this.extractReadableStrings(wordDocStream);
2233
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text2)}</div>`;
2401
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6.sanitize(text2)}</div>`;
2234
2402
  wrapper.appendChild(card);
2235
2403
  return;
2236
2404
  }
2237
2405
  } catch {
2238
2406
  }
2239
2407
  const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2240
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text)}</div>`;
2408
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6.sanitize(text)}</div>`;
2241
2409
  wrapper.appendChild(card);
2242
2410
  }
2243
2411
  extractReadableStrings(bytes) {
@@ -2333,6 +2501,26 @@ var ExcelPlugin = class {
2333
2501
  instance.zoomIn?.();
2334
2502
  }
2335
2503
  },
2504
+ {
2505
+ id: "fit-page",
2506
+ icon: "fit-page",
2507
+ label: "Fit to View",
2508
+ type: "button",
2509
+ group: "zoom",
2510
+ execute: () => {
2511
+ instance.fitToPage?.();
2512
+ }
2513
+ },
2514
+ {
2515
+ id: "rotate-cw",
2516
+ icon: "rotate-cw",
2517
+ label: "Rotate",
2518
+ type: "button",
2519
+ group: "view",
2520
+ execute: () => {
2521
+ instance.rotateCW?.();
2522
+ }
2523
+ },
2336
2524
  {
2337
2525
  id: "download",
2338
2526
  icon: "download",
@@ -2383,9 +2571,23 @@ var ExcelPlugin = class {
2383
2571
  container.appendChild(tabsArea);
2384
2572
  ctx.container.appendChild(container);
2385
2573
  let scale = 1;
2574
+ let rotation = 0;
2386
2575
  let currentSheetIndex = 1;
2387
2576
  let sheetNames = [];
2388
2577
  let wb = null;
2578
+ const applyTransform = () => {
2579
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2580
+ contentArea.style.transformOrigin = "top left";
2581
+ };
2582
+ const calculateFitScale = () => {
2583
+ const table = contentArea.querySelector("table");
2584
+ if (!table) return 1;
2585
+ const availW = Math.max(200, container.clientWidth - 48);
2586
+ const availH = Math.max(200, container.clientHeight - 80);
2587
+ const tW = table.offsetWidth || 800;
2588
+ const tH = table.offsetHeight || 600;
2589
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2590
+ };
2389
2591
  try {
2390
2592
  wb = XLSX.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2391
2593
  sheetNames = wb.SheetNames || [];
@@ -2473,17 +2675,30 @@ var ExcelPlugin = class {
2473
2675
  return {
2474
2676
  destroy: cleanup,
2475
2677
  zoomIn: () => {
2476
- scale += 0.1;
2477
- contentArea.style.transform = `scale(${scale})`;
2678
+ scale += 0.15;
2679
+ applyTransform();
2478
2680
  },
2479
2681
  zoomOut: () => {
2480
- scale = Math.max(0.2, scale - 0.1);
2481
- contentArea.style.transform = `scale(${scale})`;
2682
+ scale = Math.max(0.2, scale - 0.15);
2683
+ applyTransform();
2482
2684
  },
2483
2685
  getZoom: () => scale,
2484
2686
  setZoom: (level) => {
2485
2687
  scale = level;
2486
- contentArea.style.transform = `scale(${scale})`;
2688
+ applyTransform();
2689
+ },
2690
+ fitToPage: () => {
2691
+ scale = calculateFitScale();
2692
+ rotation = 0;
2693
+ applyTransform();
2694
+ },
2695
+ rotateCW: () => {
2696
+ rotation = (rotation + 90) % 360;
2697
+ applyTransform();
2698
+ },
2699
+ rotateCCW: () => {
2700
+ rotation = (rotation - 90 + 360) % 360;
2701
+ applyTransform();
2487
2702
  },
2488
2703
  goToPage: (page) => {
2489
2704
  if (page > 0 && page <= sheetNames.length) {
@@ -2733,6 +2948,26 @@ var CodePlugin = class {
2733
2948
  instance.zoomIn?.();
2734
2949
  }
2735
2950
  },
2951
+ {
2952
+ id: "fit-page",
2953
+ icon: "fit-page",
2954
+ label: "Fit to View",
2955
+ type: "button",
2956
+ group: "zoom",
2957
+ execute: () => {
2958
+ instance.fitToPage?.();
2959
+ }
2960
+ },
2961
+ {
2962
+ id: "rotate-cw",
2963
+ icon: "rotate-cw",
2964
+ label: "Rotate",
2965
+ type: "button",
2966
+ group: "view",
2967
+ execute: () => {
2968
+ instance.rotateCW?.();
2969
+ }
2970
+ },
2736
2971
  {
2737
2972
  id: "copy",
2738
2973
  icon: "copy",
@@ -2769,40 +3004,94 @@ var CodePlugin = class {
2769
3004
  async render(ctx) {
2770
3005
  const decoder = new TextDecoder("utf-8");
2771
3006
  const fullText = decoder.decode(ctx.buffer);
2772
- const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2773
- const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3007
+ const extRaw = ctx.metadata.extension?.toLowerCase();
3008
+ const mimeRaw = ctx.metadata.mimeType?.toLowerCase();
3009
+ const isTxt = extRaw === ".txt" || mimeRaw === "text/plain" && (!extRaw || !this.extensions.filter((e) => e !== ".txt").includes(extRaw));
3010
+ let rawPages = [];
3011
+ if (isTxt) {
3012
+ const explicitPages = fullText.split(/(?:\f|\x0C)/);
3013
+ for (const ep of explicitPages) {
3014
+ const lines = ep.split(/\r?\n/);
3015
+ let currentChunk = [];
3016
+ for (let i = 0; i < lines.length; i++) {
3017
+ currentChunk.push(lines[i]);
3018
+ if (currentChunk.length >= 46) {
3019
+ rawPages.push(currentChunk.join("\n"));
3020
+ currentChunk = [];
3021
+ }
3022
+ }
3023
+ if (currentChunk.length > 0 || lines.length === 0) {
3024
+ rawPages.push(currentChunk.join("\n"));
3025
+ }
3026
+ }
3027
+ if (rawPages.length === 0) rawPages = [""];
3028
+ } else {
3029
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
3030
+ rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3031
+ }
2774
3032
  const totalPages = Math.max(1, rawPages.length);
2775
3033
  let currentPage = 1;
2776
3034
  const container = document.createElement("div");
2777
3035
  container.style.width = "100%";
2778
3036
  container.style.height = "100%";
2779
3037
  container.style.overflow = "auto";
2780
- container.style.backgroundColor = "#1e1e1e";
2781
- container.style.color = "#d4d4d4";
2782
- container.style.padding = "16px";
2783
- container.style.boxSizing = "border-box";
2784
3038
  let fontSize = 13;
3039
+ let rotation = 0;
3040
+ let zoomLevel = 1;
2785
3041
  const pre = document.createElement("pre");
2786
- pre.style.margin = "0";
2787
- pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
2788
- pre.style.fontSize = `${fontSize}px`;
2789
- pre.style.lineHeight = "1.5";
2790
- pre.style.whiteSpace = "pre-wrap";
2791
- pre.style.wordBreak = "break-all";
2792
3042
  const code = document.createElement("code");
3043
+ if (isTxt) {
3044
+ container.style.backgroundColor = "#f1f5f9";
3045
+ container.style.padding = "32px";
3046
+ container.style.boxSizing = "border-box";
3047
+ container.style.display = "flex";
3048
+ container.style.flexDirection = "column";
3049
+ container.style.alignItems = "center";
3050
+ pre.style.margin = "0";
3051
+ pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3052
+ pre.style.fontSize = "13px";
3053
+ pre.style.color = "#1e293b";
3054
+ pre.style.lineHeight = "1.5";
3055
+ pre.style.whiteSpace = "pre-wrap";
3056
+ pre.style.wordBreak = "break-word";
3057
+ pre.style.backgroundColor = "#ffffff";
3058
+ pre.style.width = "816px";
3059
+ pre.style.minHeight = "1056px";
3060
+ pre.style.padding = "72px 56px";
3061
+ pre.style.boxSizing = "border-box";
3062
+ pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
3063
+ pre.style.borderRadius = "4px";
3064
+ pre.style.transformOrigin = "top center";
3065
+ } else {
3066
+ container.style.backgroundColor = "#1e1e1e";
3067
+ container.style.color = "#d4d4d4";
3068
+ container.style.padding = "16px";
3069
+ container.style.boxSizing = "border-box";
3070
+ pre.style.margin = "0";
3071
+ pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
3072
+ pre.style.fontSize = `${fontSize}px`;
3073
+ pre.style.lineHeight = "1.5";
3074
+ pre.style.whiteSpace = "pre-wrap";
3075
+ pre.style.wordBreak = "break-all";
3076
+ pre.style.transformOrigin = "top left";
3077
+ }
2793
3078
  const ext = (ctx.metadata.extension || "").replace(".", "");
2794
3079
  const renderCodePage = (text) => {
2795
- try {
2796
- if (ext && hljs.getLanguage(ext)) {
2797
- code.innerHTML = hljs.highlight(text, { language: ext }).value;
2798
- } else {
2799
- code.innerHTML = hljs.highlightAuto(text).value;
2800
- }
2801
- } catch {
3080
+ if (isTxt) {
2802
3081
  code.textContent = text;
3082
+ } else {
3083
+ try {
3084
+ if (ext && hljs.getLanguage(ext)) {
3085
+ code.innerHTML = hljs.highlight(text, { language: ext }).value;
3086
+ } else {
3087
+ code.innerHTML = hljs.highlightAuto(text).value;
3088
+ }
3089
+ } catch {
3090
+ code.textContent = text;
3091
+ }
2803
3092
  }
2804
3093
  };
2805
- renderCodePage(rawPages[0] || fullText);
3094
+ renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
2806
3095
  pre.appendChild(code);
2807
3096
  container.appendChild(pre);
2808
3097
  let indicator = null;
@@ -2811,15 +3100,22 @@ var CodePlugin = class {
2811
3100
  indicator.className = "fp-code-page-indicator";
2812
3101
  indicator.style.position = "sticky";
2813
3102
  indicator.style.bottom = "16px";
2814
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2815
- indicator.style.backdropFilter = "blur(8px)";
2816
- indicator.style.color = "#f8fafc";
3103
+ if (isTxt) {
3104
+ indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3105
+ indicator.style.color = "#334155";
3106
+ indicator.style.border = "1px solid #e2e8f0";
3107
+ indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3108
+ } else {
3109
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3110
+ indicator.style.color = "#f8fafc";
3111
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3112
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3113
+ indicator.style.backdropFilter = "blur(8px)";
3114
+ }
2817
3115
  indicator.style.fontSize = "12px";
2818
3116
  indicator.style.fontWeight = "600";
2819
3117
  indicator.style.padding = "5px 14px";
2820
3118
  indicator.style.borderRadius = "20px";
2821
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2822
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2823
3119
  indicator.style.zIndex = "10";
2824
3120
  indicator.style.userSelect = "none";
2825
3121
  indicator.style.pointerEvents = "none";
@@ -2830,7 +3126,7 @@ var CodePlugin = class {
2830
3126
  }
2831
3127
  const showPage = (pageNum) => {
2832
3128
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2833
- renderCodePage(rawPages[currentPage - 1] || fullText);
3129
+ renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
2834
3130
  if (indicator) {
2835
3131
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2836
3132
  }
@@ -2846,23 +3142,57 @@ var CodePlugin = class {
2846
3142
  ctx.container.innerHTML = "";
2847
3143
  };
2848
3144
  ctx.signal.addEventListener("abort", cleanup);
3145
+ const updateTransform = () => {
3146
+ if (isTxt) {
3147
+ pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
3148
+ } else {
3149
+ pre.style.transform = `rotate(${rotation}deg)`;
3150
+ pre.style.fontSize = `${fontSize}px`;
3151
+ }
3152
+ };
2849
3153
  return {
2850
3154
  destroy: cleanup,
2851
3155
  getPageCount: () => totalPages,
2852
3156
  getCurrentPage: () => currentPage,
2853
3157
  goToPage: (page) => showPage(page),
2854
3158
  zoomIn: () => {
2855
- fontSize = Math.min(32, fontSize + 2);
2856
- pre.style.fontSize = `${fontSize}px`;
3159
+ if (isTxt) {
3160
+ zoomLevel = Math.min(3, zoomLevel + 0.1);
3161
+ } else {
3162
+ fontSize = Math.min(32, fontSize + 2);
3163
+ }
3164
+ updateTransform();
2857
3165
  },
2858
3166
  zoomOut: () => {
2859
- fontSize = Math.max(8, fontSize - 2);
2860
- pre.style.fontSize = `${fontSize}px`;
3167
+ if (isTxt) {
3168
+ zoomLevel = Math.max(0.1, zoomLevel - 0.1);
3169
+ } else {
3170
+ fontSize = Math.max(8, fontSize - 2);
3171
+ }
3172
+ updateTransform();
2861
3173
  },
2862
- getZoom: () => fontSize / 13,
3174
+ getZoom: () => isTxt ? zoomLevel : fontSize / 13,
2863
3175
  setZoom: (level) => {
2864
- fontSize = Math.round(13 * level);
2865
- pre.style.fontSize = `${fontSize}px`;
3176
+ if (isTxt) {
3177
+ zoomLevel = level;
3178
+ } else {
3179
+ fontSize = Math.round(13 * level);
3180
+ }
3181
+ updateTransform();
3182
+ },
3183
+ fitToPage: () => {
3184
+ fontSize = 13;
3185
+ rotation = 0;
3186
+ zoomLevel = 1;
3187
+ updateTransform();
3188
+ },
3189
+ rotateCW: () => {
3190
+ rotation = (rotation + 90) % 360;
3191
+ updateTransform();
3192
+ },
3193
+ rotateCCW: () => {
3194
+ rotation = (rotation - 90 + 360) % 360;
3195
+ updateTransform();
2866
3196
  },
2867
3197
  download: () => {
2868
3198
  const mimeType = ctx.metadata.mimeType || "text/plain";
@@ -3161,7 +3491,7 @@ var MarkdownPlugin = class {
3161
3491
  gfm: true,
3162
3492
  breaks: true
3163
3493
  });
3164
- const cleanHtml = DOMPurify2.sanitize(rawHtml, {
3494
+ const cleanHtml = DOMPurify6.sanitize(rawHtml, {
3165
3495
  USE_PROFILES: { html: true }
3166
3496
  });
3167
3497
  const wrapper = document.createElement("div");
@@ -3375,6 +3705,14 @@ var PptxPlugin = class {
3375
3705
  group: "zoom",
3376
3706
  execute: () => instance.fitToPage?.()
3377
3707
  },
3708
+ {
3709
+ id: "rotate-cw",
3710
+ icon: "rotate-cw",
3711
+ label: "Rotate",
3712
+ type: "button",
3713
+ group: "view",
3714
+ execute: () => instance.rotateCW?.()
3715
+ },
3378
3716
  {
3379
3717
  id: "download",
3380
3718
  icon: "download",
@@ -3399,6 +3737,7 @@ var PptxPlugin = class {
3399
3737
  const slideCount = renderer.slidePaths?.length || 1;
3400
3738
  let currentSlide = 1;
3401
3739
  let scale = 1;
3740
+ let rotation = 0;
3402
3741
  const wrapper = document.createElement("div");
3403
3742
  wrapper.className = "fp-pptx-wrapper";
3404
3743
  wrapper.style.cssText = `
@@ -3421,6 +3760,8 @@ var PptxPlugin = class {
3421
3760
  background: #ffffff;
3422
3761
  transform-origin: top center;
3423
3762
  transition: transform 0.2s ease;
3763
+ max-width: calc(100% - 32px);
3764
+ max-height: calc(100% - 48px);
3424
3765
  `;
3425
3766
  const canvas = document.createElement("canvas");
3426
3767
  slideContainer.appendChild(canvas);
@@ -3428,10 +3769,22 @@ var PptxPlugin = class {
3428
3769
  ctx.container.innerHTML = "";
3429
3770
  ctx.container.style.overflow = "auto";
3430
3771
  ctx.container.appendChild(wrapper);
3772
+ const calculateFitScale = () => {
3773
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3774
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3775
+ const cW = canvas.offsetWidth || 1280;
3776
+ const cH = canvas.offsetHeight || 720;
3777
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3778
+ };
3779
+ const applyTransform = () => {
3780
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3781
+ };
3431
3782
  const renderCurrentSlide = async () => {
3432
3783
  try {
3433
3784
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3434
3785
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3786
+ scale = calculateFitScale();
3787
+ applyTransform();
3435
3788
  } catch (err) {
3436
3789
  console.error("[PptxPlugin] Failed to render slide:", err);
3437
3790
  }
@@ -3454,21 +3807,30 @@ var PptxPlugin = class {
3454
3807
  getPageCount: () => slideCount,
3455
3808
  getCurrentPage: () => currentSlide,
3456
3809
  zoomIn: () => {
3457
- scale += 0.1;
3458
- slideContainer.style.transform = `scale(${scale})`;
3810
+ scale += 0.15;
3811
+ applyTransform();
3459
3812
  },
3460
3813
  zoomOut: () => {
3461
- scale = Math.max(0.2, scale - 0.1);
3462
- slideContainer.style.transform = `scale(${scale})`;
3814
+ scale = Math.max(0.2, scale - 0.15);
3815
+ applyTransform();
3463
3816
  },
3464
3817
  getZoom: () => scale,
3465
3818
  setZoom: (level) => {
3466
3819
  scale = level;
3467
- slideContainer.style.transform = `scale(${scale})`;
3820
+ applyTransform();
3468
3821
  },
3469
3822
  fitToPage: () => {
3470
- scale = 1;
3471
- slideContainer.style.transform = "scale(1)";
3823
+ scale = calculateFitScale();
3824
+ rotation = 0;
3825
+ applyTransform();
3826
+ },
3827
+ rotateCW: () => {
3828
+ rotation = (rotation + 90) % 360;
3829
+ applyTransform();
3830
+ },
3831
+ rotateCCW: () => {
3832
+ rotation = (rotation - 90 + 360) % 360;
3833
+ applyTransform();
3472
3834
  },
3473
3835
  getThumbnails: () => {
3474
3836
  const list = [];
@@ -3749,6 +4111,14 @@ var RtfPlugin = class {
3749
4111
  group: "zoom",
3750
4112
  execute: () => instance.fitToPage?.()
3751
4113
  },
4114
+ {
4115
+ id: "rotate-cw",
4116
+ icon: "rotate-cw",
4117
+ label: "Rotate",
4118
+ type: "button",
4119
+ group: "view",
4120
+ execute: () => instance.rotateCW?.()
4121
+ },
3752
4122
  {
3753
4123
  id: "download",
3754
4124
  icon: "download",
@@ -3757,6 +4127,14 @@ var RtfPlugin = class {
3757
4127
  group: "actions",
3758
4128
  execute: () => instance.download?.()
3759
4129
  },
4130
+ {
4131
+ id: "copy",
4132
+ icon: "copy",
4133
+ label: "Copy Text",
4134
+ type: "button",
4135
+ group: "actions",
4136
+ execute: () => instance.copy?.()
4137
+ },
3760
4138
  {
3761
4139
  id: "print",
3762
4140
  icon: "print",
@@ -3785,7 +4163,24 @@ var RtfPlugin = class {
3785
4163
  ctx.container.style.backgroundColor = "#f1f5f9";
3786
4164
  ctx.container.appendChild(wrapper);
3787
4165
  let scale = 1;
4166
+ let rotation = 0;
3788
4167
  let pageElements = [];
4168
+ const calculateFitScale = () => {
4169
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4170
+ const elW = activeEl.offsetWidth || 850;
4171
+ const elH = activeEl.offsetHeight || 1e3;
4172
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4173
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4174
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4175
+ };
4176
+ const applyTransform = () => {
4177
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4178
+ wrapper.style.transformOrigin = "top center";
4179
+ };
4180
+ setTimeout(() => {
4181
+ scale = calculateFitScale();
4182
+ applyTransform();
4183
+ }, 60);
3789
4184
  try {
3790
4185
  if (typeof RTFJS.loggingEnabled === "function") {
3791
4186
  RTFJS.loggingEnabled(false);
@@ -3801,14 +4196,31 @@ var RtfPlugin = class {
3801
4196
  } catch (err) {
3802
4197
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3803
4198
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3804
- const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3805
- const pre = document.createElement("pre");
3806
- pre.style.whiteSpace = "pre-wrap";
3807
- pre.style.fontFamily = "serif";
3808
- pre.style.color = "#333";
3809
- pre.textContent = clean;
3810
- wrapper.appendChild(pre);
3811
- pageElements = [pre];
4199
+ const rawPages = text.split(/\\page\b/).map((segment) => {
4200
+ return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
4201
+ }).filter((p) => p.length > 0);
4202
+ const pages = rawPages.length > 0 ? rawPages : [text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim()];
4203
+ for (let i = 0; i < pages.length; i++) {
4204
+ const pageCard = document.createElement("div");
4205
+ pageCard.className = "fp-rtf-page-card";
4206
+ pageCard.style.backgroundColor = "#ffffff";
4207
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4208
+ pageCard.style.borderRadius = "4px";
4209
+ pageCard.style.padding = "72px 56px";
4210
+ pageCard.style.width = "816px";
4211
+ pageCard.style.minHeight = "1056px";
4212
+ pageCard.style.maxWidth = "100%";
4213
+ pageCard.style.boxSizing = "border-box";
4214
+ pageCard.style.fontFamily = "serif";
4215
+ pageCard.style.fontSize = "12pt";
4216
+ pageCard.style.lineHeight = "1.6";
4217
+ pageCard.style.color = "#1e293b";
4218
+ pageCard.style.whiteSpace = "pre-wrap";
4219
+ pageCard.style.display = i === 0 ? "block" : "none";
4220
+ pageCard.textContent = pages[i];
4221
+ wrapper.appendChild(pageCard);
4222
+ pageElements.push(pageCard);
4223
+ }
3812
4224
  }
3813
4225
  const totalPages = Math.max(1, pageElements.length);
3814
4226
  let currentPage = 1;
@@ -3862,21 +4274,30 @@ var RtfPlugin = class {
3862
4274
  getCurrentPage: () => currentPage,
3863
4275
  goToPage: (page) => showPage(page),
3864
4276
  zoomIn: () => {
3865
- scale += 0.1;
3866
- wrapper.style.transform = `scale(${scale})`;
4277
+ scale += 0.15;
4278
+ applyTransform();
3867
4279
  },
3868
4280
  zoomOut: () => {
3869
- scale = Math.max(0.2, scale - 0.1);
3870
- wrapper.style.transform = `scale(${scale})`;
4281
+ scale = Math.max(0.2, scale - 0.15);
4282
+ applyTransform();
3871
4283
  },
3872
4284
  getZoom: () => scale,
3873
4285
  setZoom: (level) => {
3874
4286
  scale = level;
3875
- wrapper.style.transform = `scale(${scale})`;
4287
+ applyTransform();
3876
4288
  },
3877
4289
  fitToPage: () => {
3878
- scale = 1;
3879
- wrapper.style.transform = "scale(1)";
4290
+ scale = calculateFitScale();
4291
+ rotation = 0;
4292
+ applyTransform();
4293
+ },
4294
+ rotateCW: () => {
4295
+ rotation = (rotation + 90) % 360;
4296
+ applyTransform();
4297
+ },
4298
+ rotateCCW: () => {
4299
+ rotation = (rotation - 90 + 360) % 360;
4300
+ applyTransform();
3880
4301
  },
3881
4302
  download: () => {
3882
4303
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3887,6 +4308,10 @@ var RtfPlugin = class {
3887
4308
  a.click();
3888
4309
  URL.revokeObjectURL(url);
3889
4310
  },
4311
+ copy: () => {
4312
+ const text = wrapper.textContent || "";
4313
+ navigator.clipboard?.writeText(text);
4314
+ },
3890
4315
  print: () => {
3891
4316
  window.print();
3892
4317
  }
@@ -3961,7 +4386,7 @@ var HtmlPreviewPlugin = class {
3961
4386
  }
3962
4387
  async render(ctx) {
3963
4388
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3964
- const sanitized = DOMPurify2.sanitize(rawHtml, {
4389
+ const sanitized = DOMPurify6.sanitize(rawHtml, {
3965
4390
  WHOLE_DOCUMENT: true,
3966
4391
  ADD_TAGS: ["style", "link"],
3967
4392
  ADD_ATTR: ["target", "rel"]
@@ -4104,11 +4529,19 @@ var OpenDocumentPlugin = class {
4104
4529
  {
4105
4530
  id: "fit-page",
4106
4531
  icon: "fit-page",
4107
- label: "Fit to Page",
4532
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
4108
4533
  type: "button",
4109
4534
  group: "zoom",
4110
4535
  execute: () => instance.fitToPage?.()
4111
4536
  },
4537
+ {
4538
+ id: "rotate-cw",
4539
+ icon: "rotate-cw",
4540
+ label: "Rotate",
4541
+ type: "button",
4542
+ group: "view",
4543
+ execute: () => instance.rotateCW?.()
4544
+ },
4112
4545
  {
4113
4546
  id: "download",
4114
4547
  icon: "download",
@@ -4172,6 +4605,22 @@ var OpenDocumentPlugin = class {
4172
4605
  container.appendChild(wrapper);
4173
4606
  ctx.container.appendChild(container);
4174
4607
  let scale = 1;
4608
+ let rotation = 0;
4609
+ const calculateFitScale = () => {
4610
+ const elW = wrapper.offsetWidth || 850;
4611
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4612
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4613
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4614
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4615
+ };
4616
+ const applyTransform = () => {
4617
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4618
+ wrapper.style.transformOrigin = "top center";
4619
+ };
4620
+ setTimeout(() => {
4621
+ scale = calculateFitScale();
4622
+ applyTransform();
4623
+ }, 60);
4175
4624
  let currentPage = 1;
4176
4625
  let totalPages = 1;
4177
4626
  let slides = [];
@@ -4203,14 +4652,97 @@ var OpenDocumentPlugin = class {
4203
4652
  wrapper.textContent = contentDoc.documentElement.textContent || "Formula content";
4204
4653
  }
4205
4654
  } else {
4206
- wrapper.style.maxWidth = "850px";
4207
- wrapper.style.padding = "48px";
4655
+ wrapper.style.maxWidth = "none";
4656
+ wrapper.style.padding = "0";
4208
4657
  wrapper.style.minHeight = "100%";
4658
+ wrapper.style.backgroundColor = "transparent";
4659
+ wrapper.style.boxShadow = "none";
4660
+ wrapper.style.position = "relative";
4661
+ const dims = this.getPageDimensions(stylesDoc, contentDoc);
4209
4662
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
4210
- wrapper.innerHTML = DOMPurify2.sanitize(bodyHtml, {
4211
- ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
4663
+ const tempDiv = document.createElement("div");
4664
+ tempDiv.style.width = `${dims.width - dims.marginLeft - dims.marginRight}px`;
4665
+ tempDiv.style.position = "absolute";
4666
+ tempDiv.style.visibility = "hidden";
4667
+ tempDiv.innerHTML = DOMPurify6.sanitize(bodyHtml, {
4668
+ ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub", "hr"],
4212
4669
  ADD_ATTR: ["style", "colspan", "rowspan"]
4213
4670
  });
4671
+ document.body.appendChild(tempDiv);
4672
+ const contentHeight = dims.height - dims.marginTop - dims.marginBottom;
4673
+ const pageElements = [[]];
4674
+ let currentHeight = 0;
4675
+ let currentPageIdx = 0;
4676
+ Array.from(tempDiv.children).forEach((child) => {
4677
+ const el = child;
4678
+ const style = el.getAttribute("style") || "";
4679
+ const isBreakBefore = style.includes("page-break-before: always");
4680
+ const isBreakAfter = style.includes("page-break-after: always");
4681
+ const isSoftBreak = el.classList.contains("odf-page-break");
4682
+ if (isBreakBefore) {
4683
+ if (pageElements[currentPageIdx].length > 0) {
4684
+ currentPageIdx++;
4685
+ pageElements.push([]);
4686
+ currentHeight = 0;
4687
+ }
4688
+ }
4689
+ const h2 = el.offsetHeight || 0;
4690
+ if (currentHeight + h2 > contentHeight && pageElements[currentPageIdx].length > 0 && !isSoftBreak) {
4691
+ currentPageIdx++;
4692
+ pageElements.push([]);
4693
+ currentHeight = 0;
4694
+ }
4695
+ if (!isSoftBreak) {
4696
+ pageElements[currentPageIdx].push(el.cloneNode(true));
4697
+ currentHeight += h2;
4698
+ }
4699
+ if (isBreakAfter || isSoftBreak) {
4700
+ currentPageIdx++;
4701
+ pageElements.push([]);
4702
+ currentHeight = 0;
4703
+ }
4704
+ });
4705
+ document.body.removeChild(tempDiv);
4706
+ if (pageElements.length > 1 && pageElements[pageElements.length - 1].length === 0) {
4707
+ pageElements.pop();
4708
+ }
4709
+ totalPages = Math.max(1, pageElements.length);
4710
+ pageElements.forEach((elements, idx) => {
4711
+ const page = document.createElement("div");
4712
+ page.className = `fp-odt-page fp-odt-page-${idx + 1}`;
4713
+ page.style.width = `${dims.width}px`;
4714
+ page.style.minHeight = `${dims.height}px`;
4715
+ page.style.padding = `${dims.marginTop}px ${dims.marginRight}px ${dims.marginBottom}px ${dims.marginLeft}px`;
4716
+ page.style.margin = "0 auto";
4717
+ page.style.backgroundColor = "#ffffff";
4718
+ page.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
4719
+ page.style.borderRadius = "4px";
4720
+ page.style.boxSizing = "border-box";
4721
+ page.style.display = idx === 0 ? "block" : "none";
4722
+ page.style.position = "absolute";
4723
+ page.style.top = "0";
4724
+ page.style.left = "50%";
4725
+ page.style.transform = "translateX(-50%)";
4726
+ elements.forEach((el) => page.appendChild(el));
4727
+ wrapper.appendChild(page);
4728
+ slides.push(page);
4729
+ });
4730
+ const pageIndicator = document.createElement("div");
4731
+ pageIndicator.className = "fp-odt-page-indicator";
4732
+ pageIndicator.style.position = "sticky";
4733
+ pageIndicator.style.bottom = "16px";
4734
+ pageIndicator.style.left = "50%";
4735
+ pageIndicator.style.transform = "translateX(-50%)";
4736
+ pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
4737
+ pageIndicator.style.color = "#fff";
4738
+ pageIndicator.style.padding = "6px 12px";
4739
+ pageIndicator.style.borderRadius = "16px";
4740
+ pageIndicator.style.fontSize = "12px";
4741
+ pageIndicator.style.zIndex = "100";
4742
+ pageIndicator.style.display = "inline-block";
4743
+ pageIndicator.style.width = "fit-content";
4744
+ pageIndicator.textContent = `Page 1 of ${totalPages}`;
4745
+ container.appendChild(pageIndicator);
4214
4746
  }
4215
4747
  const cleanup = () => {
4216
4748
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
@@ -4219,32 +4751,45 @@ var OpenDocumentPlugin = class {
4219
4751
  };
4220
4752
  ctx.signal.addEventListener("abort", cleanup);
4221
4753
  const goToPage = (page) => {
4222
- if (!isPresentation || page < 1 || page > totalPages) return;
4754
+ if (page < 1 || page > totalPages) return;
4223
4755
  currentPage = page;
4224
4756
  slides.forEach((s, idx) => {
4225
4757
  s.style.display = idx === page - 1 ? "block" : "none";
4226
4758
  });
4759
+ const indicator = container.querySelector(".fp-odt-page-indicator");
4760
+ if (indicator) {
4761
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4762
+ }
4227
4763
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4228
4764
  };
4229
4765
  return {
4230
4766
  destroy: cleanup,
4231
4767
  isPresentation,
4232
4768
  zoomIn: () => {
4233
- scale += 0.1;
4234
- wrapper.style.transform = `scale(${scale})`;
4769
+ scale += 0.15;
4770
+ applyTransform();
4235
4771
  },
4236
4772
  zoomOut: () => {
4237
- scale = Math.max(0.2, scale - 0.1);
4238
- wrapper.style.transform = `scale(${scale})`;
4773
+ scale = Math.max(0.2, scale - 0.15);
4774
+ applyTransform();
4239
4775
  },
4240
4776
  getZoom: () => scale,
4241
4777
  setZoom: (level) => {
4242
4778
  scale = level;
4243
- wrapper.style.transform = `scale(${scale})`;
4779
+ applyTransform();
4244
4780
  },
4245
4781
  fitToPage: () => {
4246
- scale = 1;
4247
- wrapper.style.transform = "scale(1)";
4782
+ scale = calculateFitScale();
4783
+ rotation = 0;
4784
+ applyTransform();
4785
+ },
4786
+ rotateCW: () => {
4787
+ rotation = (rotation + 90) % 360;
4788
+ applyTransform();
4789
+ },
4790
+ rotateCCW: () => {
4791
+ rotation = (rotation - 90 + 360) % 360;
4792
+ applyTransform();
4248
4793
  },
4249
4794
  goToPage,
4250
4795
  getPageCount: () => totalPages,
@@ -4283,6 +4828,43 @@ var OpenDocumentPlugin = class {
4283
4828
  }
4284
4829
  };
4285
4830
  }
4831
+ getPageDimensions(stylesDoc, contentDoc) {
4832
+ let width = 850;
4833
+ let height = 1123;
4834
+ let marginTop = 48;
4835
+ let marginBottom = 48;
4836
+ let marginLeft = 48;
4837
+ let marginRight = 48;
4838
+ const parseUnit = (val) => {
4839
+ if (!val) return null;
4840
+ if (val.endsWith("cm")) return parseFloat(val) * 37.8;
4841
+ if (val.endsWith("mm")) return parseFloat(val) * 3.78;
4842
+ if (val.endsWith("in")) return parseFloat(val) * 96;
4843
+ if (val.endsWith("pt")) return parseFloat(val) * 1.33;
4844
+ if (val.endsWith("px")) return parseFloat(val);
4845
+ return parseFloat(val);
4846
+ };
4847
+ const docs = [stylesDoc, contentDoc].filter(Boolean);
4848
+ for (const doc of docs) {
4849
+ const pageLayout = doc.querySelector("page-layout-properties, [page-width]");
4850
+ if (pageLayout) {
4851
+ const w = parseUnit(pageLayout.getAttribute("fo:page-width") || pageLayout.getAttribute("page-width"));
4852
+ const h2 = parseUnit(pageLayout.getAttribute("fo:page-height") || pageLayout.getAttribute("page-height"));
4853
+ const mt = parseUnit(pageLayout.getAttribute("fo:margin-top") || pageLayout.getAttribute("margin-top"));
4854
+ const mb = parseUnit(pageLayout.getAttribute("fo:margin-bottom") || pageLayout.getAttribute("margin-bottom"));
4855
+ const ml = parseUnit(pageLayout.getAttribute("fo:margin-left") || pageLayout.getAttribute("margin-left"));
4856
+ const mr = parseUnit(pageLayout.getAttribute("fo:margin-right") || pageLayout.getAttribute("margin-right"));
4857
+ if (w !== null) width = w;
4858
+ if (h2 !== null) height = h2;
4859
+ if (mt !== null) marginTop = mt;
4860
+ if (mb !== null) marginBottom = mb;
4861
+ if (ml !== null) marginLeft = ml;
4862
+ if (mr !== null) marginRight = mr;
4863
+ break;
4864
+ }
4865
+ }
4866
+ return { width, height, marginTop, marginBottom, marginLeft, marginRight };
4867
+ }
4286
4868
  extractStyles(stylesDoc, contentDoc) {
4287
4869
  const map = /* @__PURE__ */ new Map();
4288
4870
  const styleNodes = [];
@@ -4305,14 +4887,21 @@ var OpenDocumentPlugin = class {
4305
4887
  if (color) css += `color: ${color}; `;
4306
4888
  if (size) css += `font-size: ${size}; `;
4307
4889
  }
4308
- const paraProp = node.querySelector("paragraph-properties, [text-align]");
4890
+ let paraProp = node.querySelector("paragraph-properties, [text-align]");
4891
+ if (!paraProp) {
4892
+ paraProp = Array.from(node.children).find((c) => c.tagName.includes("paragraph-properties")) || null;
4893
+ }
4309
4894
  if (paraProp) {
4310
4895
  const align = paraProp.getAttribute("fo:text-align") || paraProp.getAttribute("text-align");
4311
4896
  const mt = paraProp.getAttribute("fo:margin-top") || paraProp.getAttribute("margin-top");
4312
4897
  const mb = paraProp.getAttribute("fo:margin-bottom") || paraProp.getAttribute("margin-bottom");
4898
+ const breakBefore = paraProp.getAttribute("fo:break-before") || paraProp.getAttribute("break-before");
4899
+ const breakAfter = paraProp.getAttribute("fo:break-after") || paraProp.getAttribute("break-after");
4313
4900
  if (align) css += `text-align: ${align}; `;
4314
4901
  if (mt) css += `margin-top: ${mt}; `;
4315
4902
  if (mb) css += `margin-bottom: ${mb}; `;
4903
+ if (breakBefore === "page") css += "page-break-before: always; ";
4904
+ if (breakAfter === "page") css += "page-break-after: always; ";
4316
4905
  }
4317
4906
  if (css) map.set(name, css);
4318
4907
  }
@@ -4397,6 +4986,8 @@ var OpenDocumentPlugin = class {
4397
4986
  result += "&emsp;";
4398
4987
  } else if (tag === "line-break") {
4399
4988
  result += "<br/>";
4989
+ } else if (tag === "soft-page-break") {
4990
+ result += '<hr class="odf-page-break" style="page-break-after: always; border: none; margin: 0; padding: 0; height: 0;" />';
4400
4991
  } else {
4401
4992
  result += el.textContent || "";
4402
4993
  }
@@ -4514,6 +5105,14 @@ var DocPlugin = class {
4514
5105
  group: "zoom",
4515
5106
  execute: () => instance.fitToPage?.()
4516
5107
  },
5108
+ {
5109
+ id: "rotate-cw",
5110
+ icon: "rotate-cw",
5111
+ label: "Rotate",
5112
+ type: "button",
5113
+ group: "view",
5114
+ execute: () => instance.rotateCW?.()
5115
+ },
4517
5116
  {
4518
5117
  id: "copy",
4519
5118
  icon: "copy",
@@ -4549,20 +5148,9 @@ var DocPlugin = class {
4549
5148
  container.style.overflow = "auto";
4550
5149
  container.style.padding = "32px 16px";
4551
5150
  container.style.backgroundColor = "#f1f5f9";
4552
- const wrapper = document.createElement("div");
4553
- wrapper.className = "fp-doc-wrapper";
4554
- wrapper.style.maxWidth = "850px";
4555
- wrapper.style.margin = "0 auto";
4556
- wrapper.style.backgroundColor = "#ffffff";
4557
- wrapper.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4558
- wrapper.style.borderRadius = "4px";
4559
- wrapper.style.padding = "56px 48px";
4560
- wrapper.style.minHeight = "100%";
4561
- wrapper.style.transformOrigin = "top center";
4562
- wrapper.style.transition = "transform 0.2s ease";
4563
- wrapper.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4564
- wrapper.style.color = "#1e293b";
4565
- container.appendChild(wrapper);
5151
+ container.style.display = "flex";
5152
+ container.style.justifyContent = "center";
5153
+ container.style.alignItems = "flex-start";
4566
5154
  ctx.container.appendChild(container);
4567
5155
  let scale = 1;
4568
5156
  let extractedRawText = "";
@@ -4589,21 +5177,27 @@ var DocPlugin = class {
4589
5177
  const rawPages = this.splitIntoPages(extractedRawText);
4590
5178
  const totalPages = Math.max(1, rawPages.length);
4591
5179
  let currentPage = 1;
4592
- wrapper.innerHTML = "";
4593
5180
  const pageCards = [];
4594
5181
  for (let i = 0; i < totalPages; i++) {
4595
5182
  const pageCard = document.createElement("div");
4596
5183
  pageCard.className = "fp-doc-page-card";
5184
+ pageCard.style.width = "816px";
5185
+ pageCard.style.height = "1056px";
4597
5186
  pageCard.style.backgroundColor = "#ffffff";
4598
5187
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4599
5188
  pageCard.style.borderRadius = "4px";
4600
- pageCard.style.padding = "56px 48px";
4601
- pageCard.style.minHeight = "100%";
5189
+ pageCard.style.padding = "96px 72px";
5190
+ pageCard.style.boxSizing = "border-box";
5191
+ pageCard.style.overflow = "hidden";
4602
5192
  pageCard.style.display = i === 0 ? "block" : "none";
5193
+ pageCard.style.transformOrigin = "top center";
5194
+ pageCard.style.transition = "transform 0.2s ease";
5195
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5196
+ pageCard.style.color = "#1e293b";
4603
5197
  if (isFallback && i === 0) {
4604
5198
  pageCard.innerHTML = `
4605
5199
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4606
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
5200
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify6.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4607
5201
  <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4608
5202
  </div>
4609
5203
  ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
@@ -4611,15 +5205,17 @@ var DocPlugin = class {
4611
5205
  } else {
4612
5206
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4613
5207
  }
4614
- wrapper.appendChild(pageCard);
5208
+ container.appendChild(pageCard);
4615
5209
  pageCards.push(pageCard);
4616
5210
  }
4617
5211
  let indicator = null;
4618
5212
  if (totalPages > 1) {
4619
5213
  indicator = document.createElement("div");
4620
5214
  indicator.className = "fp-doc-page-indicator";
4621
- indicator.style.position = "sticky";
5215
+ indicator.style.position = "fixed";
4622
5216
  indicator.style.bottom = "16px";
5217
+ indicator.style.left = "50%";
5218
+ indicator.style.transform = "translateX(-50%)";
4623
5219
  indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4624
5220
  indicator.style.backdropFilter = "blur(8px)";
4625
5221
  indicator.style.color = "#f8fafc";
@@ -4633,17 +5229,25 @@ var DocPlugin = class {
4633
5229
  indicator.style.userSelect = "none";
4634
5230
  indicator.style.pointerEvents = "none";
4635
5231
  indicator.style.textAlign = "center";
4636
- indicator.style.width = "fit-content";
4637
- indicator.style.margin = "16px auto 0";
4638
5232
  container.appendChild(indicator);
4639
5233
  }
5234
+ let rotation = 0;
5235
+ const applyTransform = () => {
5236
+ const activeCard = pageCards[currentPage - 1];
5237
+ if (activeCard) {
5238
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5239
+ }
5240
+ };
4640
5241
  const showPage = (pageNum) => {
4641
5242
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4642
- if (totalPages > 1) {
4643
- pageCards.forEach((card, idx) => {
4644
- card.style.display = idx + 1 === currentPage ? "block" : "none";
4645
- });
4646
- }
5243
+ pageCards.forEach((card, idx) => {
5244
+ if (idx + 1 === currentPage) {
5245
+ card.style.display = "block";
5246
+ card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5247
+ } else {
5248
+ card.style.display = "none";
5249
+ }
5250
+ });
4647
5251
  if (indicator) {
4648
5252
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4649
5253
  }
@@ -4652,6 +5256,17 @@ var DocPlugin = class {
4652
5256
  if (totalPages > 1) {
4653
5257
  showPage(1);
4654
5258
  }
5259
+ const calculateFitScale = () => {
5260
+ const elW = 816;
5261
+ const elH = 1056;
5262
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
5263
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
5264
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
5265
+ };
5266
+ setTimeout(() => {
5267
+ scale = calculateFitScale();
5268
+ applyTransform();
5269
+ }, 60);
4655
5270
  const cleanup = () => {
4656
5271
  indicator?.remove();
4657
5272
  container.remove();
@@ -4664,21 +5279,30 @@ var DocPlugin = class {
4664
5279
  getCurrentPage: () => currentPage,
4665
5280
  goToPage: (page) => showPage(page),
4666
5281
  zoomIn: () => {
4667
- scale += 0.1;
4668
- wrapper.style.transform = `scale(${scale})`;
5282
+ scale += 0.15;
5283
+ applyTransform();
4669
5284
  },
4670
5285
  zoomOut: () => {
4671
- scale = Math.max(0.2, scale - 0.1);
4672
- wrapper.style.transform = `scale(${scale})`;
5286
+ scale = Math.max(0.2, scale - 0.15);
5287
+ applyTransform();
4673
5288
  },
4674
5289
  getZoom: () => scale,
4675
5290
  setZoom: (level) => {
4676
5291
  scale = level;
4677
- wrapper.style.transform = `scale(${scale})`;
5292
+ applyTransform();
4678
5293
  },
4679
5294
  fitToPage: () => {
4680
- scale = 1;
4681
- wrapper.style.transform = "scale(1)";
5295
+ scale = calculateFitScale();
5296
+ rotation = 0;
5297
+ applyTransform();
5298
+ },
5299
+ rotateCW: () => {
5300
+ rotation = (rotation + 90) % 360;
5301
+ applyTransform();
5302
+ },
5303
+ rotateCCW: () => {
5304
+ rotation = (rotation - 90 + 360) % 360;
5305
+ applyTransform();
4682
5306
  },
4683
5307
  copy: () => {
4684
5308
  navigator.clipboard.writeText(extractedRawText);
@@ -4796,8 +5420,28 @@ var DocPlugin = class {
4796
5420
  }
4797
5421
  splitIntoPages(text) {
4798
5422
  if (!text) return [""];
4799
- 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);
4800
- return parts.length > 0 ? parts : [text];
5423
+ 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);
5424
+ if (explicitParts.length === 0) explicitParts.push(text);
5425
+ const maxLinesPerPage = 48;
5426
+ const finalPages = [];
5427
+ for (const part of explicitParts) {
5428
+ const lines = part.split(/\r?\n/);
5429
+ let currentLines = [];
5430
+ let count = 0;
5431
+ for (const line of lines) {
5432
+ currentLines.push(line);
5433
+ count++;
5434
+ if (count >= maxLinesPerPage) {
5435
+ finalPages.push(currentLines.join("\n"));
5436
+ currentLines = [];
5437
+ count = 0;
5438
+ }
5439
+ }
5440
+ if (currentLines.length > 0) {
5441
+ finalPages.push(currentLines.join("\n"));
5442
+ }
5443
+ }
5444
+ return finalPages.length > 0 ? finalPages : [text];
4801
5445
  }
4802
5446
  /**
4803
5447
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
@@ -4806,17 +5450,65 @@ var DocPlugin = class {
4806
5450
  const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4807
5451
  let html = "";
4808
5452
  let inList = false;
4809
- for (const rawLine of lines) {
4810
- const line = rawLine.trim();
5453
+ let tableLines = [];
5454
+ const flushTable = () => {
5455
+ if (tableLines.length > 0) {
5456
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 12px;">';
5457
+ for (const tLine of tableLines) {
5458
+ html += "<tr>";
5459
+ const cols = tLine.split(" ");
5460
+ for (const col of cols) {
5461
+ html += `<td style="border: 1px solid #cbd5e1; padding: 6px 8px;">${DOMPurify6.sanitize(col.trim())}</td>`;
5462
+ }
5463
+ html += "</tr>";
5464
+ }
5465
+ html += "</table>";
5466
+ tableLines = [];
5467
+ }
5468
+ };
5469
+ let i = 0;
5470
+ while (i < lines.length) {
5471
+ let line = lines[i];
5472
+ let tabCount = (line.match(/\t/g) || []).length;
5473
+ if (tabCount > 0) {
5474
+ let consecutiveTableLines = 1;
5475
+ let j = i + 1;
5476
+ while (j < lines.length) {
5477
+ const nextTabCount = (lines[j].match(/\t/g) || []).length;
5478
+ if (nextTabCount === tabCount) {
5479
+ consecutiveTableLines++;
5480
+ j++;
5481
+ } else {
5482
+ break;
5483
+ }
5484
+ }
5485
+ if (consecutiveTableLines >= 3) {
5486
+ if (inList) {
5487
+ html += "</ul>";
5488
+ inList = false;
5489
+ }
5490
+ tableLines = lines.slice(i, j);
5491
+ flushTable();
5492
+ i = j;
5493
+ continue;
5494
+ }
5495
+ }
5496
+ line = line.trim();
4811
5497
  if (!line) {
4812
5498
  if (inList) {
4813
5499
  html += "</ul>";
4814
5500
  inList = false;
4815
5501
  }
5502
+ html += '<div style="height: 1.15em;"></div>';
5503
+ i++;
4816
5504
  continue;
4817
5505
  }
4818
- if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) continue;
4819
- if (line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman") && line.length < 30) {
5506
+ if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) {
5507
+ i++;
5508
+ continue;
5509
+ }
5510
+ if ((line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman")) && line.length < 30) {
5511
+ i++;
4820
5512
  continue;
4821
5513
  }
4822
5514
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
@@ -4824,24 +5516,26 @@ var DocPlugin = class {
4824
5516
  html += "</ul>";
4825
5517
  inList = false;
4826
5518
  }
4827
- html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify2.sanitize(line)}</h2>`;
5519
+ 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>`;
4828
5520
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4829
5521
  if (!inList) {
4830
5522
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4831
5523
  inList = true;
4832
5524
  }
4833
5525
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4834
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2.sanitize(bulletText)}</li>`;
5526
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText)}</li>`;
4835
5527
  } else {
4836
5528
  if (inList) {
4837
5529
  html += "</ul>";
4838
5530
  inList = false;
4839
5531
  }
4840
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2.sanitize(line)}</p>`;
5532
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
4841
5533
  }
5534
+ i++;
4842
5535
  }
4843
5536
  if (inList) html += "</ul>";
4844
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2.sanitize(filename)})</p>`;
5537
+ flushTable();
5538
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6.sanitize(filename)})</p>`;
4845
5539
  }
4846
5540
  };
4847
5541
  function docPlugin() {
@@ -4918,6 +5612,14 @@ var PptPlugin = class {
4918
5612
  group: "zoom",
4919
5613
  execute: () => instance.fitToPage?.()
4920
5614
  },
5615
+ {
5616
+ id: "rotate-cw",
5617
+ icon: "rotate-cw",
5618
+ label: "Rotate",
5619
+ type: "button",
5620
+ group: "view",
5621
+ execute: () => instance.rotateCW?.()
5622
+ },
4921
5623
  {
4922
5624
  id: "download",
4923
5625
  icon: "download",
@@ -4951,22 +5653,36 @@ var PptPlugin = class {
4951
5653
  const slideCard = document.createElement("div");
4952
5654
  slideCard.className = "fp-ppt-slide-card";
4953
5655
  slideCard.style.width = "960px";
4954
- slideCard.style.maxWidth = "92%";
5656
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5657
+ slideCard.style.maxHeight = "calc(100% - 48px)";
4955
5658
  slideCard.style.aspectRatio = "16 / 9";
4956
5659
  slideCard.style.backgroundColor = "#ffffff";
4957
5660
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4958
5661
  slideCard.style.borderRadius = "8px";
4959
5662
  slideCard.style.boxSizing = "border-box";
4960
5663
  slideCard.style.position = "relative";
4961
- slideCard.style.overflow = "hidden";
4962
- slideCard.style.transformOrigin = "center center";
4963
5664
  slideCard.style.transition = "transform 0.2s ease";
5665
+ slideCard.style.transformOrigin = "center center";
5666
+ slideCard.style.flexShrink = "0";
4964
5667
  container.appendChild(slideCard);
4965
5668
  ctx.container.appendChild(container);
4966
5669
  let scale = 1;
5670
+ let rotation = 0;
4967
5671
  let currentSlide = 1;
4968
5672
  let slides = [];
4969
5673
  const createdBlobUrls = [];
5674
+ const calculateFitScale = () => {
5675
+ const availW = Math.max(200, container.clientWidth - 48);
5676
+ const availH = Math.max(200, container.clientHeight - 72);
5677
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5678
+ };
5679
+ const applyTransform = () => {
5680
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5681
+ };
5682
+ setTimeout(() => {
5683
+ scale = calculateFitScale();
5684
+ applyTransform();
5685
+ }, 60);
4970
5686
  try {
4971
5687
  const cfbf = new CfbfReader(ctx.buffer);
4972
5688
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -4997,7 +5713,7 @@ var PptPlugin = class {
4997
5713
  if (s.pictureUrl) {
4998
5714
  contentHtml = `
4999
5715
  <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
5000
- <img src="${s.pictureUrl}" alt="${DOMPurify2.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;" />
5716
+ <img src="${s.pictureUrl}" alt="${DOMPurify6.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;" />
5001
5717
  </div>
5002
5718
  `;
5003
5719
  } else if (s.tableColumns.length > 0) {
@@ -5007,7 +5723,7 @@ var PptPlugin = class {
5007
5723
  <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
5008
5724
  <thead>
5009
5725
  <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
5010
- ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2.sanitize(c)}</th>`).join("")}
5726
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify6.sanitize(c)}</th>`).join("")}
5011
5727
  </tr>
5012
5728
  </thead>
5013
5729
  <tbody>
@@ -5023,7 +5739,7 @@ var PptPlugin = class {
5023
5739
  } else {
5024
5740
  const pTags = s.paragraphs.map((p) => {
5025
5741
  const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
5026
- return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2.sanitize(l)}</p>`).join("");
5742
+ return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify6.sanitize(l)}</p>`).join("");
5027
5743
  }).join("");
5028
5744
  contentHtml = `
5029
5745
  <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
@@ -5036,11 +5752,11 @@ var PptPlugin = class {
5036
5752
  <!-- Header Banner matching PowerPoint design -->
5037
5753
  <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;">
5038
5754
  <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
5039
- ${DOMPurify2.sanitize(s.title)}
5755
+ ${DOMPurify6.sanitize(s.title)}
5040
5756
  </h1>
5041
5757
  ${s.subtitle ? `
5042
5758
  <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);">
5043
- ${DOMPurify2.sanitize(s.subtitle)}
5759
+ ${DOMPurify6.sanitize(s.subtitle)}
5044
5760
  </span>
5045
5761
  ` : `
5046
5762
  <span style="font-size: 12px; color: #365314; font-weight: 600;">
@@ -5067,21 +5783,30 @@ var PptPlugin = class {
5067
5783
  return {
5068
5784
  destroy: cleanup,
5069
5785
  zoomIn: () => {
5070
- scale += 0.1;
5071
- slideCard.style.transform = `scale(${scale})`;
5786
+ scale += 0.15;
5787
+ applyTransform();
5072
5788
  },
5073
5789
  zoomOut: () => {
5074
- scale = Math.max(0.3, scale - 0.1);
5075
- slideCard.style.transform = `scale(${scale})`;
5790
+ scale = Math.max(0.2, scale - 0.15);
5791
+ applyTransform();
5076
5792
  },
5077
5793
  getZoom: () => scale,
5078
5794
  setZoom: (level) => {
5079
5795
  scale = level;
5080
- slideCard.style.transform = `scale(${scale})`;
5796
+ applyTransform();
5081
5797
  },
5082
5798
  fitToPage: () => {
5083
- scale = 1;
5084
- slideCard.style.transform = "scale(1)";
5799
+ scale = calculateFitScale();
5800
+ rotation = 0;
5801
+ applyTransform();
5802
+ },
5803
+ rotateCW: () => {
5804
+ rotation = (rotation + 90) % 360;
5805
+ applyTransform();
5806
+ },
5807
+ rotateCCW: () => {
5808
+ rotation = (rotation - 90 + 360) % 360;
5809
+ applyTransform();
5085
5810
  },
5086
5811
  goToPage: (page) => {
5087
5812
  if (page >= 1 && page <= totalSlides) {