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