@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.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var react = require('react');
6
- var DOMPurify2 = require('dompurify');
6
+ var DOMPurify6 = require('dompurify');
7
7
  var pdfjsLib = require('pdfjs-dist');
8
8
  var docx = require('docx-preview');
9
9
  var fflate = require('fflate');
@@ -38,7 +38,7 @@ function _interopNamespace(e) {
38
38
  return Object.freeze(n);
39
39
  }
40
40
 
41
- var DOMPurify2__default = /*#__PURE__*/_interopDefault(DOMPurify2);
41
+ var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
42
42
  var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
43
43
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
44
44
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
@@ -390,7 +390,7 @@ async function sourceToArrayBuffer(source, signal) {
390
390
  if (magicMime === "application/zip") {
391
391
  const ooxmlMime = detectOoxmlType(buffer);
392
392
  if (ooxmlMime && ooxmlMime !== "application/zip") {
393
- metadata.mimeType = ooxmlMime;
393
+ metadata.mimeType = metadata.mimeType ?? ooxmlMime;
394
394
  if (ooxmlMime.includes("wordprocessing")) metadata.extension = metadata.extension ?? ".docx";
395
395
  else if (ooxmlMime.includes("spreadsheet")) metadata.extension = metadata.extension ?? ".xlsx";
396
396
  else if (ooxmlMime.includes("presentation")) metadata.extension = metadata.extension ?? ".pptx";
@@ -419,7 +419,7 @@ async function sourceToArrayBuffer(source, signal) {
419
419
  return { buffer, metadata };
420
420
  }
421
421
  function sanitizeSVG(svg) {
422
- return DOMPurify2__default.default.sanitize(svg, {
422
+ return DOMPurify6__default.default.sanitize(svg, {
423
423
  USE_PROFILES: { svg: true, svgFilters: true }
424
424
  });
425
425
  }
@@ -738,6 +738,8 @@ var FilePreviewViewer = class {
738
738
  keyHandler = null;
739
739
  currentContainer = null;
740
740
  currentOptions = {};
741
+ resizeObserver = null;
742
+ fullscreenHandler = null;
741
743
  /**
742
744
  * Register a preview plugin.
743
745
  */
@@ -810,12 +812,31 @@ var FilePreviewViewer = class {
810
812
  label: "Toggle Fullscreen",
811
813
  type: "button",
812
814
  group: "view",
813
- execute: () => {
814
- if (!document.fullscreenElement) {
815
- this.wrapperEl?.requestFullscreen?.();
816
- } else {
817
- document.exitFullscreen?.();
815
+ execute: async () => {
816
+ try {
817
+ const isNativeFs = !!document.fullscreenElement;
818
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
819
+ if (!isNativeFs && !isCssFs) {
820
+ if (this.wrapperEl?.requestFullscreen) {
821
+ await this.wrapperEl.requestFullscreen().catch(() => {
822
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
823
+ });
824
+ } else {
825
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
826
+ }
827
+ } else {
828
+ if (document.fullscreenElement) {
829
+ await document.exitFullscreen().catch(() => {
830
+ });
831
+ }
832
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
833
+ }
834
+ } catch {
835
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
818
836
  }
837
+ setTimeout(() => {
838
+ this.activeInstance?.fitToPage?.();
839
+ }, 120);
819
840
  }
820
841
  });
821
842
  }
@@ -865,6 +886,15 @@ var FilePreviewViewer = class {
865
886
  window.removeEventListener("keydown", this.keyHandler);
866
887
  this.keyHandler = null;
867
888
  }
889
+ if (this.resizeObserver) {
890
+ this.resizeObserver.disconnect();
891
+ this.resizeObserver = null;
892
+ }
893
+ if (this.fullscreenHandler) {
894
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
895
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
896
+ this.fullscreenHandler = null;
897
+ }
868
898
  this.abort();
869
899
  this.destroyInstance();
870
900
  this.toolbar?.destroy();
@@ -944,6 +974,25 @@ var FilePreviewViewer = class {
944
974
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
945
975
  this.setupKeyboardShortcuts();
946
976
  this.setupDragAndDrop(container, options);
977
+ this.setupResizeAndFullscreenListeners();
978
+ }
979
+ setupResizeAndFullscreenListeners() {
980
+ if (this.fullscreenHandler) return;
981
+ this.fullscreenHandler = () => {
982
+ setTimeout(() => {
983
+ this.activeInstance?.fitToPage?.();
984
+ }, 100);
985
+ };
986
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
987
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
988
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
989
+ this.resizeObserver = new ResizeObserver(() => {
990
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
991
+ this.activeInstance.fitToPage?.();
992
+ }
993
+ });
994
+ this.resizeObserver.observe(this.contentEl);
995
+ }
947
996
  }
948
997
  setupKeyboardShortcuts() {
949
998
  if (this.keyHandler) return;
@@ -1335,7 +1384,8 @@ var PdfPlugin = class {
1335
1384
  container.style.display = "flex";
1336
1385
  container.style.flexDirection = "column";
1337
1386
  container.style.alignItems = "center";
1338
- container.style.padding = "24px 16px";
1387
+ container.style.justifyContent = "flex-start";
1388
+ container.style.padding = "20px 16px";
1339
1389
  container.style.backgroundColor = "#0f172a";
1340
1390
  container.style.boxSizing = "border-box";
1341
1391
  container.style.position = "relative";
@@ -1348,7 +1398,8 @@ var PdfPlugin = class {
1348
1398
  pageCard.style.lineHeight = "0";
1349
1399
  pageCard.style.transition = "transform 0.15s ease";
1350
1400
  pageCard.style.position = "relative";
1351
- const canvas = document.createElement("canvas");
1401
+ pageCard.style.flexShrink = "0";
1402
+ let canvas = document.createElement("canvas");
1352
1403
  pageCard.appendChild(canvas);
1353
1404
  container.appendChild(pageCard);
1354
1405
  const indicator = document.createElement("div");
@@ -1390,14 +1441,22 @@ var PdfPlugin = class {
1390
1441
  }
1391
1442
  currentRenderTask = null;
1392
1443
  }
1444
+ const newCanvas = document.createElement("canvas");
1445
+ pageCard.replaceChild(newCanvas, canvas);
1446
+ canvas = newCanvas;
1393
1447
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1394
1448
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1395
1449
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1396
1450
  const page = await pdfDoc.getPage(currentPage);
1397
1451
  const containerWidth = container.clientWidth || 900;
1452
+ const containerHeight = container.clientHeight || 700;
1398
1453
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1399
- const baseScale = Math.min((containerWidth - 64) / unscaledVp.width, 1.6);
1400
- const effectiveScale = (baseScale > 0 ? baseScale : 1) * zoomScale;
1454
+ const availWidth = Math.max(100, containerWidth - 48);
1455
+ const availHeight = Math.max(100, containerHeight - 88);
1456
+ const scaleW = availWidth / unscaledVp.width;
1457
+ const scaleH = availHeight / unscaledVp.height;
1458
+ const fitScale = Math.min(scaleW, scaleH);
1459
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1401
1460
  const pixelRatio = window.devicePixelRatio || 1;
1402
1461
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
1403
1462
  canvas.width = Math.floor(viewport.width * pixelRatio);
@@ -1422,7 +1481,19 @@ var PdfPlugin = class {
1422
1481
  }
1423
1482
  };
1424
1483
  await renderPage(1);
1484
+ let resizeTimer = null;
1485
+ const resizeObserver = new ResizeObserver(() => {
1486
+ if (resizeTimer) clearTimeout(resizeTimer);
1487
+ resizeTimer = setTimeout(() => {
1488
+ if (Math.abs(zoomScale - 1) < 0.05) {
1489
+ renderPage(currentPage);
1490
+ }
1491
+ }, 150);
1492
+ });
1493
+ resizeObserver.observe(container);
1425
1494
  const cleanup = () => {
1495
+ resizeObserver.disconnect();
1496
+ if (resizeTimer) clearTimeout(resizeTimer);
1426
1497
  if (currentRenderTask) {
1427
1498
  try {
1428
1499
  currentRenderTask.cancel();
@@ -1454,6 +1525,7 @@ var PdfPlugin = class {
1454
1525
  },
1455
1526
  fitToPage: () => {
1456
1527
  zoomScale = 1;
1528
+ rotation = 0;
1457
1529
  renderPage(currentPage);
1458
1530
  },
1459
1531
  rotateCW: () => {
@@ -1904,6 +1976,14 @@ var DocxPlugin = class {
1904
1976
  group: "zoom",
1905
1977
  execute: () => instance.fitToPage?.()
1906
1978
  },
1979
+ {
1980
+ id: "rotate-cw",
1981
+ icon: "rotate-cw",
1982
+ label: "Rotate",
1983
+ type: "button",
1984
+ group: "view",
1985
+ execute: () => instance.rotateCW?.()
1986
+ },
1907
1987
  {
1908
1988
  id: "download",
1909
1989
  icon: "download",
@@ -2040,6 +2120,26 @@ var DocxPlugin = class {
2040
2120
  if (totalPages > 1) {
2041
2121
  showPage(1);
2042
2122
  }
2123
+ scale = 1;
2124
+ let rotation = 0;
2125
+ const calculateFitScale = () => {
2126
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2127
+ const elW = activeEl.offsetWidth || 816;
2128
+ const elH = activeEl.offsetHeight || 1056;
2129
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2130
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2131
+ const sW = availW / elW;
2132
+ const sH = availH / elH;
2133
+ return Math.min(1.1, Math.min(sW, sH));
2134
+ };
2135
+ const applyTransform = () => {
2136
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2137
+ wrapper.style.transformOrigin = "top center";
2138
+ };
2139
+ setTimeout(() => {
2140
+ scale = calculateFitScale();
2141
+ applyTransform();
2142
+ }, 60);
2043
2143
  const cleanup = () => {
2044
2144
  indicator?.remove();
2045
2145
  for (const url of createdBlobUrls) {
@@ -2058,21 +2158,30 @@ var DocxPlugin = class {
2058
2158
  showPage(page);
2059
2159
  },
2060
2160
  zoomIn: () => {
2061
- scale += 0.1;
2062
- wrapper.style.transform = `scale(${scale})`;
2161
+ scale += 0.15;
2162
+ applyTransform();
2063
2163
  },
2064
2164
  zoomOut: () => {
2065
- scale = Math.max(0.2, scale - 0.1);
2066
- wrapper.style.transform = `scale(${scale})`;
2165
+ scale = Math.max(0.2, scale - 0.15);
2166
+ applyTransform();
2067
2167
  },
2068
2168
  getZoom: () => scale,
2069
2169
  setZoom: (level) => {
2070
2170
  scale = level;
2071
- wrapper.style.transform = `scale(${scale})`;
2171
+ applyTransform();
2072
2172
  },
2073
2173
  fitToPage: () => {
2074
- scale = 1;
2075
- wrapper.style.transform = "scale(1)";
2174
+ scale = calculateFitScale();
2175
+ rotation = 0;
2176
+ applyTransform();
2177
+ },
2178
+ rotateCW: () => {
2179
+ rotation = (rotation + 90) % 360;
2180
+ applyTransform();
2181
+ },
2182
+ rotateCCW: () => {
2183
+ rotation = (rotation - 90 + 360) % 360;
2184
+ applyTransform();
2076
2185
  },
2077
2186
  download: () => {
2078
2187
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2132,90 +2241,149 @@ var DocxPlugin = class {
2132
2241
  console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
2133
2242
  }
2134
2243
  }
2135
- const card = document.createElement("div");
2136
- card.className = "fp-docx-page-card";
2137
- card.style.backgroundColor = "#ffffff";
2138
- card.style.borderRadius = "6px";
2139
- card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2140
- card.style.padding = "48px 56px";
2141
- card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2142
- card.style.color = "#1e293b";
2143
- card.style.lineHeight = "1.6";
2244
+ const sectPrs = Array.from(doc.getElementsByTagNameNS("*", "sectPr"));
2245
+ const lastSectPr = sectPrs[sectPrs.length - 1];
2246
+ let defaultW = 12240;
2247
+ let defaultH = 15840;
2248
+ let margins = { top: 1440, right: 1440, bottom: 1440, left: 1440 };
2249
+ if (lastSectPr) {
2250
+ const pgSz = lastSectPr.getElementsByTagNameNS("*", "pgSz")[0];
2251
+ if (pgSz) {
2252
+ defaultW = parseInt(pgSz.getAttribute("w:w") || pgSz.getAttribute("w") || "12240", 10);
2253
+ defaultH = parseInt(pgSz.getAttribute("w:h") || pgSz.getAttribute("h") || "15840", 10);
2254
+ }
2255
+ const pgMar = lastSectPr.getElementsByTagNameNS("*", "pgMar")[0];
2256
+ if (pgMar) {
2257
+ margins.top = parseInt(pgMar.getAttribute("w:top") || pgMar.getAttribute("top") || "1440", 10);
2258
+ margins.bottom = parseInt(pgMar.getAttribute("w:bottom") || pgMar.getAttribute("bottom") || "1440", 10);
2259
+ margins.left = parseInt(pgMar.getAttribute("w:left") || pgMar.getAttribute("left") || "1440", 10);
2260
+ margins.right = parseInt(pgMar.getAttribute("w:right") || pgMar.getAttribute("right") || "1440", 10);
2261
+ }
2262
+ }
2263
+ const createPageCard = () => {
2264
+ const card = document.createElement("div");
2265
+ card.className = "fp-docx-page-card";
2266
+ card.style.backgroundColor = "#ffffff";
2267
+ card.style.borderRadius = "4px";
2268
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2269
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2270
+ card.style.color = "#1e293b";
2271
+ card.style.lineHeight = "1.6";
2272
+ card.style.boxSizing = "border-box";
2273
+ card.style.overflow = "hidden";
2274
+ card.style.position = "relative";
2275
+ card.style.marginBottom = "24px";
2276
+ card.style.width = `${defaultW / 15}px`;
2277
+ card.style.height = `${defaultH / 15}px`;
2278
+ card.style.padding = `${margins.top / 15}px ${margins.right / 15}px ${margins.bottom / 15}px ${margins.left / 15}px`;
2279
+ return card;
2280
+ };
2281
+ let currentCard = createPageCard();
2282
+ let currentHtml = "";
2283
+ const pages = [{ card: currentCard, html: "" }];
2284
+ const flushHtml = () => {
2285
+ pages[pages.length - 1].html += currentHtml;
2286
+ currentHtml = "";
2287
+ };
2288
+ const newPage = () => {
2289
+ flushHtml();
2290
+ currentCard = createPageCard();
2291
+ pages.push({ card: currentCard, html: "" });
2292
+ };
2144
2293
  const body = doc.getElementsByTagNameNS("*", "body")[0] || doc.documentElement;
2145
- let html = "";
2146
2294
  for (const child of Array.from(body.children)) {
2147
2295
  const tag = child.localName || child.nodeName.split(":").pop();
2148
2296
  if (tag === "p") {
2149
2297
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
2150
2298
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
2151
2299
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
2152
- const textContent = this.extractParagraphHtml(child, imageMap);
2153
- if (!textContent.trim()) {
2154
- html += '<div style="height: 10px;"></div>';
2155
- continue;
2156
- }
2157
- const lowerStyle = styleVal.toLowerCase();
2158
- if (lowerStyle.includes("title")) {
2159
- 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>`;
2160
- } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2161
- html += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2162
- } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2163
- html += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2164
- } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2165
- html += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2166
- } else if (numPr) {
2167
- 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>`;
2168
- } else {
2169
- html += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2300
+ const chunks = this.extractParagraphChunks(child, imageMap);
2301
+ for (let i = 0; i < chunks.length; i++) {
2302
+ if (i > 0) {
2303
+ newPage();
2304
+ }
2305
+ const textContent = chunks[i];
2306
+ if (!textContent.trim() && !textContent.includes("<img")) {
2307
+ currentHtml += '<div style="height: 10px;"></div>';
2308
+ continue;
2309
+ }
2310
+ const lowerStyle = styleVal.toLowerCase();
2311
+ if (lowerStyle.includes("title")) {
2312
+ 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>`;
2313
+ } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2314
+ currentHtml += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2315
+ } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2316
+ currentHtml += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2317
+ } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2318
+ currentHtml += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2319
+ } else if (numPr) {
2320
+ 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>`;
2321
+ } else {
2322
+ currentHtml += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2323
+ }
2170
2324
  }
2171
2325
  } else if (tag === "tbl") {
2172
- html += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2326
+ currentHtml += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2173
2327
  const rows = Array.from(child.getElementsByTagNameNS("*", "tr"));
2174
2328
  rows.forEach((tr, rIdx) => {
2175
- html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2329
+ currentHtml += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2176
2330
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
2177
2331
  cells.forEach((tc) => {
2178
2332
  const cellText = this.extractParagraphHtml(tc, imageMap);
2179
- html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2333
+ currentHtml += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2180
2334
  });
2181
- html += "</tr>";
2335
+ currentHtml += "</tr>";
2182
2336
  });
2183
- html += "</table>";
2337
+ currentHtml += "</table>";
2184
2338
  }
2185
2339
  }
2186
- card.innerHTML = DOMPurify2__default.default.sanitize(html, {
2187
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2188
- ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2189
- });
2190
- wrapper.appendChild(card);
2340
+ flushHtml();
2341
+ for (const page of pages) {
2342
+ page.card.innerHTML = DOMPurify6__default.default.sanitize(page.html, {
2343
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2344
+ ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2345
+ });
2346
+ wrapper.appendChild(page.card);
2347
+ }
2191
2348
  }
2192
2349
  extractParagraphHtml(pElement, imageMap = {}) {
2193
- let result = "";
2350
+ return this.extractParagraphChunks(pElement, imageMap).join("<br/>");
2351
+ }
2352
+ extractParagraphChunks(pElement, imageMap = {}) {
2353
+ const chunks = [""];
2354
+ let currentChunkIndex = 0;
2194
2355
  const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
2195
2356
  for (const drawing of drawings) {
2196
2357
  const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
2197
2358
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2198
2359
  if (rId && imageMap[rId]) {
2199
- result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2360
+ chunks[currentChunkIndex] += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2200
2361
  }
2201
2362
  }
2202
2363
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
2203
- if (runs.length === 0 && !result) {
2204
- return DOMPurify2__default.default.sanitize(pElement.textContent || "");
2364
+ if (runs.length === 0 && !chunks[currentChunkIndex]) {
2365
+ chunks[currentChunkIndex] = DOMPurify6__default.default.sanitize(pElement.textContent || "");
2366
+ return chunks;
2205
2367
  }
2206
2368
  for (const r of runs) {
2207
2369
  const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
2208
2370
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2209
2371
  if (rId && imageMap[rId]) {
2210
- result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2372
+ chunks[currentChunkIndex] += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2211
2373
  }
2212
- const brs = r.getElementsByTagNameNS("*", "br");
2213
- for (let i = 0; i < brs.length; i++) {
2214
- result += "<br/>";
2374
+ const brs = Array.from(r.getElementsByTagNameNS("*", "br"));
2375
+ for (const br of brs) {
2376
+ const type = br.getAttribute("w:type") || br.getAttribute("type");
2377
+ if (type === "page") {
2378
+ chunks.push("");
2379
+ currentChunkIndex++;
2380
+ } else {
2381
+ chunks[currentChunkIndex] += "<br/>";
2382
+ }
2215
2383
  }
2216
2384
  const tabs = r.getElementsByTagNameNS("*", "tab");
2217
2385
  if (tabs.length > 0) {
2218
- result += "&emsp;";
2386
+ chunks[currentChunkIndex] += "&emsp;";
2219
2387
  }
2220
2388
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
2221
2389
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
@@ -2229,7 +2397,7 @@ var DocxPlugin = class {
2229
2397
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
2230
2398
  let text = texts.map((t) => t.textContent || "").join("");
2231
2399
  if (!text) continue;
2232
- text = DOMPurify2__default.default.sanitize(text);
2400
+ text = DOMPurify6__default.default.sanitize(text);
2233
2401
  let styles = "";
2234
2402
  if (isBold) styles += "font-weight: bold; ";
2235
2403
  if (isItalic) styles += "font-style: italic; ";
@@ -2241,12 +2409,12 @@ var DocxPlugin = class {
2241
2409
  if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2242
2410
  }
2243
2411
  if (styles) {
2244
- result += `<span style="${styles}">${text}</span>`;
2412
+ chunks[currentChunkIndex] += `<span style="${styles}">${text}</span>`;
2245
2413
  } else {
2246
- result += text;
2414
+ chunks[currentChunkIndex] += text;
2247
2415
  }
2248
2416
  }
2249
- return result;
2417
+ return chunks;
2250
2418
  }
2251
2419
  renderBinaryDocFallback(ctx, wrapper) {
2252
2420
  const card = document.createElement("div");
@@ -2263,14 +2431,14 @@ var DocxPlugin = class {
2263
2431
  const wordDocStream = cfbf.readStream("WordDocument");
2264
2432
  if (wordDocStream && wordDocStream.length >= 512) {
2265
2433
  const text2 = this.extractReadableStrings(wordDocStream);
2266
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text2)}</div>`;
2434
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6__default.default.sanitize(text2)}</div>`;
2267
2435
  wrapper.appendChild(card);
2268
2436
  return;
2269
2437
  }
2270
2438
  } catch {
2271
2439
  }
2272
2440
  const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2273
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text)}</div>`;
2441
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6__default.default.sanitize(text)}</div>`;
2274
2442
  wrapper.appendChild(card);
2275
2443
  }
2276
2444
  extractReadableStrings(bytes) {
@@ -2366,6 +2534,26 @@ var ExcelPlugin = class {
2366
2534
  instance.zoomIn?.();
2367
2535
  }
2368
2536
  },
2537
+ {
2538
+ id: "fit-page",
2539
+ icon: "fit-page",
2540
+ label: "Fit to View",
2541
+ type: "button",
2542
+ group: "zoom",
2543
+ execute: () => {
2544
+ instance.fitToPage?.();
2545
+ }
2546
+ },
2547
+ {
2548
+ id: "rotate-cw",
2549
+ icon: "rotate-cw",
2550
+ label: "Rotate",
2551
+ type: "button",
2552
+ group: "view",
2553
+ execute: () => {
2554
+ instance.rotateCW?.();
2555
+ }
2556
+ },
2369
2557
  {
2370
2558
  id: "download",
2371
2559
  icon: "download",
@@ -2416,9 +2604,23 @@ var ExcelPlugin = class {
2416
2604
  container.appendChild(tabsArea);
2417
2605
  ctx.container.appendChild(container);
2418
2606
  let scale = 1;
2607
+ let rotation = 0;
2419
2608
  let currentSheetIndex = 1;
2420
2609
  let sheetNames = [];
2421
2610
  let wb = null;
2611
+ const applyTransform = () => {
2612
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2613
+ contentArea.style.transformOrigin = "top left";
2614
+ };
2615
+ const calculateFitScale = () => {
2616
+ const table = contentArea.querySelector("table");
2617
+ if (!table) return 1;
2618
+ const availW = Math.max(200, container.clientWidth - 48);
2619
+ const availH = Math.max(200, container.clientHeight - 80);
2620
+ const tW = table.offsetWidth || 800;
2621
+ const tH = table.offsetHeight || 600;
2622
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2623
+ };
2422
2624
  try {
2423
2625
  wb = XLSX__namespace.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2424
2626
  sheetNames = wb.SheetNames || [];
@@ -2506,17 +2708,30 @@ var ExcelPlugin = class {
2506
2708
  return {
2507
2709
  destroy: cleanup,
2508
2710
  zoomIn: () => {
2509
- scale += 0.1;
2510
- contentArea.style.transform = `scale(${scale})`;
2711
+ scale += 0.15;
2712
+ applyTransform();
2511
2713
  },
2512
2714
  zoomOut: () => {
2513
- scale = Math.max(0.2, scale - 0.1);
2514
- contentArea.style.transform = `scale(${scale})`;
2715
+ scale = Math.max(0.2, scale - 0.15);
2716
+ applyTransform();
2515
2717
  },
2516
2718
  getZoom: () => scale,
2517
2719
  setZoom: (level) => {
2518
2720
  scale = level;
2519
- contentArea.style.transform = `scale(${scale})`;
2721
+ applyTransform();
2722
+ },
2723
+ fitToPage: () => {
2724
+ scale = calculateFitScale();
2725
+ rotation = 0;
2726
+ applyTransform();
2727
+ },
2728
+ rotateCW: () => {
2729
+ rotation = (rotation + 90) % 360;
2730
+ applyTransform();
2731
+ },
2732
+ rotateCCW: () => {
2733
+ rotation = (rotation - 90 + 360) % 360;
2734
+ applyTransform();
2520
2735
  },
2521
2736
  goToPage: (page) => {
2522
2737
  if (page > 0 && page <= sheetNames.length) {
@@ -2766,6 +2981,26 @@ var CodePlugin = class {
2766
2981
  instance.zoomIn?.();
2767
2982
  }
2768
2983
  },
2984
+ {
2985
+ id: "fit-page",
2986
+ icon: "fit-page",
2987
+ label: "Fit to View",
2988
+ type: "button",
2989
+ group: "zoom",
2990
+ execute: () => {
2991
+ instance.fitToPage?.();
2992
+ }
2993
+ },
2994
+ {
2995
+ id: "rotate-cw",
2996
+ icon: "rotate-cw",
2997
+ label: "Rotate",
2998
+ type: "button",
2999
+ group: "view",
3000
+ execute: () => {
3001
+ instance.rotateCW?.();
3002
+ }
3003
+ },
2769
3004
  {
2770
3005
  id: "copy",
2771
3006
  icon: "copy",
@@ -2802,40 +3037,94 @@ var CodePlugin = class {
2802
3037
  async render(ctx) {
2803
3038
  const decoder = new TextDecoder("utf-8");
2804
3039
  const fullText = decoder.decode(ctx.buffer);
2805
- const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2806
- const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3040
+ const extRaw = ctx.metadata.extension?.toLowerCase();
3041
+ const mimeRaw = ctx.metadata.mimeType?.toLowerCase();
3042
+ const isTxt = extRaw === ".txt" || mimeRaw === "text/plain" && (!extRaw || !this.extensions.filter((e) => e !== ".txt").includes(extRaw));
3043
+ let rawPages = [];
3044
+ if (isTxt) {
3045
+ const explicitPages = fullText.split(/(?:\f|\x0C)/);
3046
+ for (const ep of explicitPages) {
3047
+ const lines = ep.split(/\r?\n/);
3048
+ let currentChunk = [];
3049
+ for (let i = 0; i < lines.length; i++) {
3050
+ currentChunk.push(lines[i]);
3051
+ if (currentChunk.length >= 46) {
3052
+ rawPages.push(currentChunk.join("\n"));
3053
+ currentChunk = [];
3054
+ }
3055
+ }
3056
+ if (currentChunk.length > 0 || lines.length === 0) {
3057
+ rawPages.push(currentChunk.join("\n"));
3058
+ }
3059
+ }
3060
+ if (rawPages.length === 0) rawPages = [""];
3061
+ } else {
3062
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
3063
+ rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3064
+ }
2807
3065
  const totalPages = Math.max(1, rawPages.length);
2808
3066
  let currentPage = 1;
2809
3067
  const container = document.createElement("div");
2810
3068
  container.style.width = "100%";
2811
3069
  container.style.height = "100%";
2812
3070
  container.style.overflow = "auto";
2813
- container.style.backgroundColor = "#1e1e1e";
2814
- container.style.color = "#d4d4d4";
2815
- container.style.padding = "16px";
2816
- container.style.boxSizing = "border-box";
2817
3071
  let fontSize = 13;
3072
+ let rotation = 0;
3073
+ let zoomLevel = 1;
2818
3074
  const pre = document.createElement("pre");
2819
- pre.style.margin = "0";
2820
- pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
2821
- pre.style.fontSize = `${fontSize}px`;
2822
- pre.style.lineHeight = "1.5";
2823
- pre.style.whiteSpace = "pre-wrap";
2824
- pre.style.wordBreak = "break-all";
2825
3075
  const code = document.createElement("code");
3076
+ if (isTxt) {
3077
+ container.style.backgroundColor = "#f1f5f9";
3078
+ container.style.padding = "32px";
3079
+ container.style.boxSizing = "border-box";
3080
+ container.style.display = "flex";
3081
+ container.style.flexDirection = "column";
3082
+ container.style.alignItems = "center";
3083
+ pre.style.margin = "0";
3084
+ pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3085
+ pre.style.fontSize = "13px";
3086
+ pre.style.color = "#1e293b";
3087
+ pre.style.lineHeight = "1.5";
3088
+ pre.style.whiteSpace = "pre-wrap";
3089
+ pre.style.wordBreak = "break-word";
3090
+ pre.style.backgroundColor = "#ffffff";
3091
+ pre.style.width = "816px";
3092
+ pre.style.minHeight = "1056px";
3093
+ pre.style.padding = "72px 56px";
3094
+ pre.style.boxSizing = "border-box";
3095
+ pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
3096
+ pre.style.borderRadius = "4px";
3097
+ pre.style.transformOrigin = "top center";
3098
+ } else {
3099
+ container.style.backgroundColor = "#1e1e1e";
3100
+ container.style.color = "#d4d4d4";
3101
+ container.style.padding = "16px";
3102
+ container.style.boxSizing = "border-box";
3103
+ pre.style.margin = "0";
3104
+ pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
3105
+ pre.style.fontSize = `${fontSize}px`;
3106
+ pre.style.lineHeight = "1.5";
3107
+ pre.style.whiteSpace = "pre-wrap";
3108
+ pre.style.wordBreak = "break-all";
3109
+ pre.style.transformOrigin = "top left";
3110
+ }
2826
3111
  const ext = (ctx.metadata.extension || "").replace(".", "");
2827
3112
  const renderCodePage = (text) => {
2828
- try {
2829
- if (ext && hljs__default.default.getLanguage(ext)) {
2830
- code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
2831
- } else {
2832
- code.innerHTML = hljs__default.default.highlightAuto(text).value;
2833
- }
2834
- } catch {
3113
+ if (isTxt) {
2835
3114
  code.textContent = text;
3115
+ } else {
3116
+ try {
3117
+ if (ext && hljs__default.default.getLanguage(ext)) {
3118
+ code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
3119
+ } else {
3120
+ code.innerHTML = hljs__default.default.highlightAuto(text).value;
3121
+ }
3122
+ } catch {
3123
+ code.textContent = text;
3124
+ }
2836
3125
  }
2837
3126
  };
2838
- renderCodePage(rawPages[0] || fullText);
3127
+ renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
2839
3128
  pre.appendChild(code);
2840
3129
  container.appendChild(pre);
2841
3130
  let indicator = null;
@@ -2844,15 +3133,22 @@ var CodePlugin = class {
2844
3133
  indicator.className = "fp-code-page-indicator";
2845
3134
  indicator.style.position = "sticky";
2846
3135
  indicator.style.bottom = "16px";
2847
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2848
- indicator.style.backdropFilter = "blur(8px)";
2849
- indicator.style.color = "#f8fafc";
3136
+ if (isTxt) {
3137
+ indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3138
+ indicator.style.color = "#334155";
3139
+ indicator.style.border = "1px solid #e2e8f0";
3140
+ indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3141
+ } else {
3142
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3143
+ indicator.style.color = "#f8fafc";
3144
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3145
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3146
+ indicator.style.backdropFilter = "blur(8px)";
3147
+ }
2850
3148
  indicator.style.fontSize = "12px";
2851
3149
  indicator.style.fontWeight = "600";
2852
3150
  indicator.style.padding = "5px 14px";
2853
3151
  indicator.style.borderRadius = "20px";
2854
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2855
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2856
3152
  indicator.style.zIndex = "10";
2857
3153
  indicator.style.userSelect = "none";
2858
3154
  indicator.style.pointerEvents = "none";
@@ -2863,7 +3159,7 @@ var CodePlugin = class {
2863
3159
  }
2864
3160
  const showPage = (pageNum) => {
2865
3161
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2866
- renderCodePage(rawPages[currentPage - 1] || fullText);
3162
+ renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
2867
3163
  if (indicator) {
2868
3164
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2869
3165
  }
@@ -2879,23 +3175,57 @@ var CodePlugin = class {
2879
3175
  ctx.container.innerHTML = "";
2880
3176
  };
2881
3177
  ctx.signal.addEventListener("abort", cleanup);
3178
+ const updateTransform = () => {
3179
+ if (isTxt) {
3180
+ pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
3181
+ } else {
3182
+ pre.style.transform = `rotate(${rotation}deg)`;
3183
+ pre.style.fontSize = `${fontSize}px`;
3184
+ }
3185
+ };
2882
3186
  return {
2883
3187
  destroy: cleanup,
2884
3188
  getPageCount: () => totalPages,
2885
3189
  getCurrentPage: () => currentPage,
2886
3190
  goToPage: (page) => showPage(page),
2887
3191
  zoomIn: () => {
2888
- fontSize = Math.min(32, fontSize + 2);
2889
- pre.style.fontSize = `${fontSize}px`;
3192
+ if (isTxt) {
3193
+ zoomLevel = Math.min(3, zoomLevel + 0.1);
3194
+ } else {
3195
+ fontSize = Math.min(32, fontSize + 2);
3196
+ }
3197
+ updateTransform();
2890
3198
  },
2891
3199
  zoomOut: () => {
2892
- fontSize = Math.max(8, fontSize - 2);
2893
- pre.style.fontSize = `${fontSize}px`;
3200
+ if (isTxt) {
3201
+ zoomLevel = Math.max(0.1, zoomLevel - 0.1);
3202
+ } else {
3203
+ fontSize = Math.max(8, fontSize - 2);
3204
+ }
3205
+ updateTransform();
2894
3206
  },
2895
- getZoom: () => fontSize / 13,
3207
+ getZoom: () => isTxt ? zoomLevel : fontSize / 13,
2896
3208
  setZoom: (level) => {
2897
- fontSize = Math.round(13 * level);
2898
- pre.style.fontSize = `${fontSize}px`;
3209
+ if (isTxt) {
3210
+ zoomLevel = level;
3211
+ } else {
3212
+ fontSize = Math.round(13 * level);
3213
+ }
3214
+ updateTransform();
3215
+ },
3216
+ fitToPage: () => {
3217
+ fontSize = 13;
3218
+ rotation = 0;
3219
+ zoomLevel = 1;
3220
+ updateTransform();
3221
+ },
3222
+ rotateCW: () => {
3223
+ rotation = (rotation + 90) % 360;
3224
+ updateTransform();
3225
+ },
3226
+ rotateCCW: () => {
3227
+ rotation = (rotation - 90 + 360) % 360;
3228
+ updateTransform();
2899
3229
  },
2900
3230
  download: () => {
2901
3231
  const mimeType = ctx.metadata.mimeType || "text/plain";
@@ -3194,7 +3524,7 @@ var MarkdownPlugin = class {
3194
3524
  gfm: true,
3195
3525
  breaks: true
3196
3526
  });
3197
- const cleanHtml = DOMPurify2__default.default.sanitize(rawHtml, {
3527
+ const cleanHtml = DOMPurify6__default.default.sanitize(rawHtml, {
3198
3528
  USE_PROFILES: { html: true }
3199
3529
  });
3200
3530
  const wrapper = document.createElement("div");
@@ -3408,6 +3738,14 @@ var PptxPlugin = class {
3408
3738
  group: "zoom",
3409
3739
  execute: () => instance.fitToPage?.()
3410
3740
  },
3741
+ {
3742
+ id: "rotate-cw",
3743
+ icon: "rotate-cw",
3744
+ label: "Rotate",
3745
+ type: "button",
3746
+ group: "view",
3747
+ execute: () => instance.rotateCW?.()
3748
+ },
3411
3749
  {
3412
3750
  id: "download",
3413
3751
  icon: "download",
@@ -3432,6 +3770,7 @@ var PptxPlugin = class {
3432
3770
  const slideCount = renderer.slidePaths?.length || 1;
3433
3771
  let currentSlide = 1;
3434
3772
  let scale = 1;
3773
+ let rotation = 0;
3435
3774
  const wrapper = document.createElement("div");
3436
3775
  wrapper.className = "fp-pptx-wrapper";
3437
3776
  wrapper.style.cssText = `
@@ -3454,6 +3793,8 @@ var PptxPlugin = class {
3454
3793
  background: #ffffff;
3455
3794
  transform-origin: top center;
3456
3795
  transition: transform 0.2s ease;
3796
+ max-width: calc(100% - 32px);
3797
+ max-height: calc(100% - 48px);
3457
3798
  `;
3458
3799
  const canvas = document.createElement("canvas");
3459
3800
  slideContainer.appendChild(canvas);
@@ -3461,10 +3802,22 @@ var PptxPlugin = class {
3461
3802
  ctx.container.innerHTML = "";
3462
3803
  ctx.container.style.overflow = "auto";
3463
3804
  ctx.container.appendChild(wrapper);
3805
+ const calculateFitScale = () => {
3806
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3807
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3808
+ const cW = canvas.offsetWidth || 1280;
3809
+ const cH = canvas.offsetHeight || 720;
3810
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3811
+ };
3812
+ const applyTransform = () => {
3813
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3814
+ };
3464
3815
  const renderCurrentSlide = async () => {
3465
3816
  try {
3466
3817
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3467
3818
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3819
+ scale = calculateFitScale();
3820
+ applyTransform();
3468
3821
  } catch (err) {
3469
3822
  console.error("[PptxPlugin] Failed to render slide:", err);
3470
3823
  }
@@ -3487,21 +3840,30 @@ var PptxPlugin = class {
3487
3840
  getPageCount: () => slideCount,
3488
3841
  getCurrentPage: () => currentSlide,
3489
3842
  zoomIn: () => {
3490
- scale += 0.1;
3491
- slideContainer.style.transform = `scale(${scale})`;
3843
+ scale += 0.15;
3844
+ applyTransform();
3492
3845
  },
3493
3846
  zoomOut: () => {
3494
- scale = Math.max(0.2, scale - 0.1);
3495
- slideContainer.style.transform = `scale(${scale})`;
3847
+ scale = Math.max(0.2, scale - 0.15);
3848
+ applyTransform();
3496
3849
  },
3497
3850
  getZoom: () => scale,
3498
3851
  setZoom: (level) => {
3499
3852
  scale = level;
3500
- slideContainer.style.transform = `scale(${scale})`;
3853
+ applyTransform();
3501
3854
  },
3502
3855
  fitToPage: () => {
3503
- scale = 1;
3504
- slideContainer.style.transform = "scale(1)";
3856
+ scale = calculateFitScale();
3857
+ rotation = 0;
3858
+ applyTransform();
3859
+ },
3860
+ rotateCW: () => {
3861
+ rotation = (rotation + 90) % 360;
3862
+ applyTransform();
3863
+ },
3864
+ rotateCCW: () => {
3865
+ rotation = (rotation - 90 + 360) % 360;
3866
+ applyTransform();
3505
3867
  },
3506
3868
  getThumbnails: () => {
3507
3869
  const list = [];
@@ -3782,6 +4144,14 @@ var RtfPlugin = class {
3782
4144
  group: "zoom",
3783
4145
  execute: () => instance.fitToPage?.()
3784
4146
  },
4147
+ {
4148
+ id: "rotate-cw",
4149
+ icon: "rotate-cw",
4150
+ label: "Rotate",
4151
+ type: "button",
4152
+ group: "view",
4153
+ execute: () => instance.rotateCW?.()
4154
+ },
3785
4155
  {
3786
4156
  id: "download",
3787
4157
  icon: "download",
@@ -3790,6 +4160,14 @@ var RtfPlugin = class {
3790
4160
  group: "actions",
3791
4161
  execute: () => instance.download?.()
3792
4162
  },
4163
+ {
4164
+ id: "copy",
4165
+ icon: "copy",
4166
+ label: "Copy Text",
4167
+ type: "button",
4168
+ group: "actions",
4169
+ execute: () => instance.copy?.()
4170
+ },
3793
4171
  {
3794
4172
  id: "print",
3795
4173
  icon: "print",
@@ -3818,7 +4196,24 @@ var RtfPlugin = class {
3818
4196
  ctx.container.style.backgroundColor = "#f1f5f9";
3819
4197
  ctx.container.appendChild(wrapper);
3820
4198
  let scale = 1;
4199
+ let rotation = 0;
3821
4200
  let pageElements = [];
4201
+ const calculateFitScale = () => {
4202
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4203
+ const elW = activeEl.offsetWidth || 850;
4204
+ const elH = activeEl.offsetHeight || 1e3;
4205
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4206
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4207
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4208
+ };
4209
+ const applyTransform = () => {
4210
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4211
+ wrapper.style.transformOrigin = "top center";
4212
+ };
4213
+ setTimeout(() => {
4214
+ scale = calculateFitScale();
4215
+ applyTransform();
4216
+ }, 60);
3822
4217
  try {
3823
4218
  if (typeof RTFJS__namespace.loggingEnabled === "function") {
3824
4219
  RTFJS__namespace.loggingEnabled(false);
@@ -3834,14 +4229,31 @@ var RtfPlugin = class {
3834
4229
  } catch (err) {
3835
4230
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3836
4231
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3837
- const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3838
- const pre = document.createElement("pre");
3839
- pre.style.whiteSpace = "pre-wrap";
3840
- pre.style.fontFamily = "serif";
3841
- pre.style.color = "#333";
3842
- pre.textContent = clean;
3843
- wrapper.appendChild(pre);
3844
- pageElements = [pre];
4232
+ const rawPages = text.split(/\\page\b/).map((segment) => {
4233
+ return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
4234
+ }).filter((p) => p.length > 0);
4235
+ const pages = rawPages.length > 0 ? rawPages : [text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim()];
4236
+ for (let i = 0; i < pages.length; i++) {
4237
+ const pageCard = document.createElement("div");
4238
+ pageCard.className = "fp-rtf-page-card";
4239
+ pageCard.style.backgroundColor = "#ffffff";
4240
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4241
+ pageCard.style.borderRadius = "4px";
4242
+ pageCard.style.padding = "72px 56px";
4243
+ pageCard.style.width = "816px";
4244
+ pageCard.style.minHeight = "1056px";
4245
+ pageCard.style.maxWidth = "100%";
4246
+ pageCard.style.boxSizing = "border-box";
4247
+ pageCard.style.fontFamily = "serif";
4248
+ pageCard.style.fontSize = "12pt";
4249
+ pageCard.style.lineHeight = "1.6";
4250
+ pageCard.style.color = "#1e293b";
4251
+ pageCard.style.whiteSpace = "pre-wrap";
4252
+ pageCard.style.display = i === 0 ? "block" : "none";
4253
+ pageCard.textContent = pages[i];
4254
+ wrapper.appendChild(pageCard);
4255
+ pageElements.push(pageCard);
4256
+ }
3845
4257
  }
3846
4258
  const totalPages = Math.max(1, pageElements.length);
3847
4259
  let currentPage = 1;
@@ -3895,21 +4307,30 @@ var RtfPlugin = class {
3895
4307
  getCurrentPage: () => currentPage,
3896
4308
  goToPage: (page) => showPage(page),
3897
4309
  zoomIn: () => {
3898
- scale += 0.1;
3899
- wrapper.style.transform = `scale(${scale})`;
4310
+ scale += 0.15;
4311
+ applyTransform();
3900
4312
  },
3901
4313
  zoomOut: () => {
3902
- scale = Math.max(0.2, scale - 0.1);
3903
- wrapper.style.transform = `scale(${scale})`;
4314
+ scale = Math.max(0.2, scale - 0.15);
4315
+ applyTransform();
3904
4316
  },
3905
4317
  getZoom: () => scale,
3906
4318
  setZoom: (level) => {
3907
4319
  scale = level;
3908
- wrapper.style.transform = `scale(${scale})`;
4320
+ applyTransform();
3909
4321
  },
3910
4322
  fitToPage: () => {
3911
- scale = 1;
3912
- wrapper.style.transform = "scale(1)";
4323
+ scale = calculateFitScale();
4324
+ rotation = 0;
4325
+ applyTransform();
4326
+ },
4327
+ rotateCW: () => {
4328
+ rotation = (rotation + 90) % 360;
4329
+ applyTransform();
4330
+ },
4331
+ rotateCCW: () => {
4332
+ rotation = (rotation - 90 + 360) % 360;
4333
+ applyTransform();
3913
4334
  },
3914
4335
  download: () => {
3915
4336
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3920,6 +4341,10 @@ var RtfPlugin = class {
3920
4341
  a.click();
3921
4342
  URL.revokeObjectURL(url);
3922
4343
  },
4344
+ copy: () => {
4345
+ const text = wrapper.textContent || "";
4346
+ navigator.clipboard?.writeText(text);
4347
+ },
3923
4348
  print: () => {
3924
4349
  window.print();
3925
4350
  }
@@ -3994,7 +4419,7 @@ var HtmlPreviewPlugin = class {
3994
4419
  }
3995
4420
  async render(ctx) {
3996
4421
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3997
- const sanitized = DOMPurify2__default.default.sanitize(rawHtml, {
4422
+ const sanitized = DOMPurify6__default.default.sanitize(rawHtml, {
3998
4423
  WHOLE_DOCUMENT: true,
3999
4424
  ADD_TAGS: ["style", "link"],
4000
4425
  ADD_ATTR: ["target", "rel"]
@@ -4137,11 +4562,19 @@ var OpenDocumentPlugin = class {
4137
4562
  {
4138
4563
  id: "fit-page",
4139
4564
  icon: "fit-page",
4140
- label: "Fit to Page",
4565
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
4141
4566
  type: "button",
4142
4567
  group: "zoom",
4143
4568
  execute: () => instance.fitToPage?.()
4144
4569
  },
4570
+ {
4571
+ id: "rotate-cw",
4572
+ icon: "rotate-cw",
4573
+ label: "Rotate",
4574
+ type: "button",
4575
+ group: "view",
4576
+ execute: () => instance.rotateCW?.()
4577
+ },
4145
4578
  {
4146
4579
  id: "download",
4147
4580
  icon: "download",
@@ -4205,6 +4638,22 @@ var OpenDocumentPlugin = class {
4205
4638
  container.appendChild(wrapper);
4206
4639
  ctx.container.appendChild(container);
4207
4640
  let scale = 1;
4641
+ let rotation = 0;
4642
+ const calculateFitScale = () => {
4643
+ const elW = wrapper.offsetWidth || 850;
4644
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4645
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4646
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4647
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4648
+ };
4649
+ const applyTransform = () => {
4650
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4651
+ wrapper.style.transformOrigin = "top center";
4652
+ };
4653
+ setTimeout(() => {
4654
+ scale = calculateFitScale();
4655
+ applyTransform();
4656
+ }, 60);
4208
4657
  let currentPage = 1;
4209
4658
  let totalPages = 1;
4210
4659
  let slides = [];
@@ -4236,14 +4685,97 @@ var OpenDocumentPlugin = class {
4236
4685
  wrapper.textContent = contentDoc.documentElement.textContent || "Formula content";
4237
4686
  }
4238
4687
  } else {
4239
- wrapper.style.maxWidth = "850px";
4240
- wrapper.style.padding = "48px";
4688
+ wrapper.style.maxWidth = "none";
4689
+ wrapper.style.padding = "0";
4241
4690
  wrapper.style.minHeight = "100%";
4691
+ wrapper.style.backgroundColor = "transparent";
4692
+ wrapper.style.boxShadow = "none";
4693
+ wrapper.style.position = "relative";
4694
+ const dims = this.getPageDimensions(stylesDoc, contentDoc);
4242
4695
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
4243
- wrapper.innerHTML = DOMPurify2__default.default.sanitize(bodyHtml, {
4244
- ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
4696
+ const tempDiv = document.createElement("div");
4697
+ tempDiv.style.width = `${dims.width - dims.marginLeft - dims.marginRight}px`;
4698
+ tempDiv.style.position = "absolute";
4699
+ tempDiv.style.visibility = "hidden";
4700
+ tempDiv.innerHTML = DOMPurify6__default.default.sanitize(bodyHtml, {
4701
+ ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub", "hr"],
4245
4702
  ADD_ATTR: ["style", "colspan", "rowspan"]
4246
4703
  });
4704
+ document.body.appendChild(tempDiv);
4705
+ const contentHeight = dims.height - dims.marginTop - dims.marginBottom;
4706
+ const pageElements = [[]];
4707
+ let currentHeight = 0;
4708
+ let currentPageIdx = 0;
4709
+ Array.from(tempDiv.children).forEach((child) => {
4710
+ const el = child;
4711
+ const style = el.getAttribute("style") || "";
4712
+ const isBreakBefore = style.includes("page-break-before: always");
4713
+ const isBreakAfter = style.includes("page-break-after: always");
4714
+ const isSoftBreak = el.classList.contains("odf-page-break");
4715
+ if (isBreakBefore) {
4716
+ if (pageElements[currentPageIdx].length > 0) {
4717
+ currentPageIdx++;
4718
+ pageElements.push([]);
4719
+ currentHeight = 0;
4720
+ }
4721
+ }
4722
+ const h = el.offsetHeight || 0;
4723
+ if (currentHeight + h > contentHeight && pageElements[currentPageIdx].length > 0 && !isSoftBreak) {
4724
+ currentPageIdx++;
4725
+ pageElements.push([]);
4726
+ currentHeight = 0;
4727
+ }
4728
+ if (!isSoftBreak) {
4729
+ pageElements[currentPageIdx].push(el.cloneNode(true));
4730
+ currentHeight += h;
4731
+ }
4732
+ if (isBreakAfter || isSoftBreak) {
4733
+ currentPageIdx++;
4734
+ pageElements.push([]);
4735
+ currentHeight = 0;
4736
+ }
4737
+ });
4738
+ document.body.removeChild(tempDiv);
4739
+ if (pageElements.length > 1 && pageElements[pageElements.length - 1].length === 0) {
4740
+ pageElements.pop();
4741
+ }
4742
+ totalPages = Math.max(1, pageElements.length);
4743
+ pageElements.forEach((elements, idx) => {
4744
+ const page = document.createElement("div");
4745
+ page.className = `fp-odt-page fp-odt-page-${idx + 1}`;
4746
+ page.style.width = `${dims.width}px`;
4747
+ page.style.minHeight = `${dims.height}px`;
4748
+ page.style.padding = `${dims.marginTop}px ${dims.marginRight}px ${dims.marginBottom}px ${dims.marginLeft}px`;
4749
+ page.style.margin = "0 auto";
4750
+ page.style.backgroundColor = "#ffffff";
4751
+ page.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
4752
+ page.style.borderRadius = "4px";
4753
+ page.style.boxSizing = "border-box";
4754
+ page.style.display = idx === 0 ? "block" : "none";
4755
+ page.style.position = "absolute";
4756
+ page.style.top = "0";
4757
+ page.style.left = "50%";
4758
+ page.style.transform = "translateX(-50%)";
4759
+ elements.forEach((el) => page.appendChild(el));
4760
+ wrapper.appendChild(page);
4761
+ slides.push(page);
4762
+ });
4763
+ const pageIndicator = document.createElement("div");
4764
+ pageIndicator.className = "fp-odt-page-indicator";
4765
+ pageIndicator.style.position = "sticky";
4766
+ pageIndicator.style.bottom = "16px";
4767
+ pageIndicator.style.left = "50%";
4768
+ pageIndicator.style.transform = "translateX(-50%)";
4769
+ pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
4770
+ pageIndicator.style.color = "#fff";
4771
+ pageIndicator.style.padding = "6px 12px";
4772
+ pageIndicator.style.borderRadius = "16px";
4773
+ pageIndicator.style.fontSize = "12px";
4774
+ pageIndicator.style.zIndex = "100";
4775
+ pageIndicator.style.display = "inline-block";
4776
+ pageIndicator.style.width = "fit-content";
4777
+ pageIndicator.textContent = `Page 1 of ${totalPages}`;
4778
+ container.appendChild(pageIndicator);
4247
4779
  }
4248
4780
  const cleanup = () => {
4249
4781
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
@@ -4252,32 +4784,45 @@ var OpenDocumentPlugin = class {
4252
4784
  };
4253
4785
  ctx.signal.addEventListener("abort", cleanup);
4254
4786
  const goToPage = (page) => {
4255
- if (!isPresentation || page < 1 || page > totalPages) return;
4787
+ if (page < 1 || page > totalPages) return;
4256
4788
  currentPage = page;
4257
4789
  slides.forEach((s, idx) => {
4258
4790
  s.style.display = idx === page - 1 ? "block" : "none";
4259
4791
  });
4792
+ const indicator = container.querySelector(".fp-odt-page-indicator");
4793
+ if (indicator) {
4794
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4795
+ }
4260
4796
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4261
4797
  };
4262
4798
  return {
4263
4799
  destroy: cleanup,
4264
4800
  isPresentation,
4265
4801
  zoomIn: () => {
4266
- scale += 0.1;
4267
- wrapper.style.transform = `scale(${scale})`;
4802
+ scale += 0.15;
4803
+ applyTransform();
4268
4804
  },
4269
4805
  zoomOut: () => {
4270
- scale = Math.max(0.2, scale - 0.1);
4271
- wrapper.style.transform = `scale(${scale})`;
4806
+ scale = Math.max(0.2, scale - 0.15);
4807
+ applyTransform();
4272
4808
  },
4273
4809
  getZoom: () => scale,
4274
4810
  setZoom: (level) => {
4275
4811
  scale = level;
4276
- wrapper.style.transform = `scale(${scale})`;
4812
+ applyTransform();
4277
4813
  },
4278
4814
  fitToPage: () => {
4279
- scale = 1;
4280
- wrapper.style.transform = "scale(1)";
4815
+ scale = calculateFitScale();
4816
+ rotation = 0;
4817
+ applyTransform();
4818
+ },
4819
+ rotateCW: () => {
4820
+ rotation = (rotation + 90) % 360;
4821
+ applyTransform();
4822
+ },
4823
+ rotateCCW: () => {
4824
+ rotation = (rotation - 90 + 360) % 360;
4825
+ applyTransform();
4281
4826
  },
4282
4827
  goToPage,
4283
4828
  getPageCount: () => totalPages,
@@ -4316,6 +4861,43 @@ var OpenDocumentPlugin = class {
4316
4861
  }
4317
4862
  };
4318
4863
  }
4864
+ getPageDimensions(stylesDoc, contentDoc) {
4865
+ let width = 850;
4866
+ let height = 1123;
4867
+ let marginTop = 48;
4868
+ let marginBottom = 48;
4869
+ let marginLeft = 48;
4870
+ let marginRight = 48;
4871
+ const parseUnit = (val) => {
4872
+ if (!val) return null;
4873
+ if (val.endsWith("cm")) return parseFloat(val) * 37.8;
4874
+ if (val.endsWith("mm")) return parseFloat(val) * 3.78;
4875
+ if (val.endsWith("in")) return parseFloat(val) * 96;
4876
+ if (val.endsWith("pt")) return parseFloat(val) * 1.33;
4877
+ if (val.endsWith("px")) return parseFloat(val);
4878
+ return parseFloat(val);
4879
+ };
4880
+ const docs = [stylesDoc, contentDoc].filter(Boolean);
4881
+ for (const doc of docs) {
4882
+ const pageLayout = doc.querySelector("page-layout-properties, [page-width]");
4883
+ if (pageLayout) {
4884
+ const w = parseUnit(pageLayout.getAttribute("fo:page-width") || pageLayout.getAttribute("page-width"));
4885
+ const h = parseUnit(pageLayout.getAttribute("fo:page-height") || pageLayout.getAttribute("page-height"));
4886
+ const mt = parseUnit(pageLayout.getAttribute("fo:margin-top") || pageLayout.getAttribute("margin-top"));
4887
+ const mb = parseUnit(pageLayout.getAttribute("fo:margin-bottom") || pageLayout.getAttribute("margin-bottom"));
4888
+ const ml = parseUnit(pageLayout.getAttribute("fo:margin-left") || pageLayout.getAttribute("margin-left"));
4889
+ const mr = parseUnit(pageLayout.getAttribute("fo:margin-right") || pageLayout.getAttribute("margin-right"));
4890
+ if (w !== null) width = w;
4891
+ if (h !== null) height = h;
4892
+ if (mt !== null) marginTop = mt;
4893
+ if (mb !== null) marginBottom = mb;
4894
+ if (ml !== null) marginLeft = ml;
4895
+ if (mr !== null) marginRight = mr;
4896
+ break;
4897
+ }
4898
+ }
4899
+ return { width, height, marginTop, marginBottom, marginLeft, marginRight };
4900
+ }
4319
4901
  extractStyles(stylesDoc, contentDoc) {
4320
4902
  const map = /* @__PURE__ */ new Map();
4321
4903
  const styleNodes = [];
@@ -4338,14 +4920,21 @@ var OpenDocumentPlugin = class {
4338
4920
  if (color) css += `color: ${color}; `;
4339
4921
  if (size) css += `font-size: ${size}; `;
4340
4922
  }
4341
- const paraProp = node.querySelector("paragraph-properties, [text-align]");
4923
+ let paraProp = node.querySelector("paragraph-properties, [text-align]");
4924
+ if (!paraProp) {
4925
+ paraProp = Array.from(node.children).find((c) => c.tagName.includes("paragraph-properties")) || null;
4926
+ }
4342
4927
  if (paraProp) {
4343
4928
  const align = paraProp.getAttribute("fo:text-align") || paraProp.getAttribute("text-align");
4344
4929
  const mt = paraProp.getAttribute("fo:margin-top") || paraProp.getAttribute("margin-top");
4345
4930
  const mb = paraProp.getAttribute("fo:margin-bottom") || paraProp.getAttribute("margin-bottom");
4931
+ const breakBefore = paraProp.getAttribute("fo:break-before") || paraProp.getAttribute("break-before");
4932
+ const breakAfter = paraProp.getAttribute("fo:break-after") || paraProp.getAttribute("break-after");
4346
4933
  if (align) css += `text-align: ${align}; `;
4347
4934
  if (mt) css += `margin-top: ${mt}; `;
4348
4935
  if (mb) css += `margin-bottom: ${mb}; `;
4936
+ if (breakBefore === "page") css += "page-break-before: always; ";
4937
+ if (breakAfter === "page") css += "page-break-after: always; ";
4349
4938
  }
4350
4939
  if (css) map.set(name, css);
4351
4940
  }
@@ -4430,6 +5019,8 @@ var OpenDocumentPlugin = class {
4430
5019
  result += "&emsp;";
4431
5020
  } else if (tag === "line-break") {
4432
5021
  result += "<br/>";
5022
+ } else if (tag === "soft-page-break") {
5023
+ result += '<hr class="odf-page-break" style="page-break-after: always; border: none; margin: 0; padding: 0; height: 0;" />';
4433
5024
  } else {
4434
5025
  result += el.textContent || "";
4435
5026
  }
@@ -4547,6 +5138,14 @@ var DocPlugin = class {
4547
5138
  group: "zoom",
4548
5139
  execute: () => instance.fitToPage?.()
4549
5140
  },
5141
+ {
5142
+ id: "rotate-cw",
5143
+ icon: "rotate-cw",
5144
+ label: "Rotate",
5145
+ type: "button",
5146
+ group: "view",
5147
+ execute: () => instance.rotateCW?.()
5148
+ },
4550
5149
  {
4551
5150
  id: "copy",
4552
5151
  icon: "copy",
@@ -4582,20 +5181,9 @@ var DocPlugin = class {
4582
5181
  container.style.overflow = "auto";
4583
5182
  container.style.padding = "32px 16px";
4584
5183
  container.style.backgroundColor = "#f1f5f9";
4585
- const wrapper = document.createElement("div");
4586
- wrapper.className = "fp-doc-wrapper";
4587
- wrapper.style.maxWidth = "850px";
4588
- wrapper.style.margin = "0 auto";
4589
- wrapper.style.backgroundColor = "#ffffff";
4590
- wrapper.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4591
- wrapper.style.borderRadius = "4px";
4592
- wrapper.style.padding = "56px 48px";
4593
- wrapper.style.minHeight = "100%";
4594
- wrapper.style.transformOrigin = "top center";
4595
- wrapper.style.transition = "transform 0.2s ease";
4596
- wrapper.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4597
- wrapper.style.color = "#1e293b";
4598
- container.appendChild(wrapper);
5184
+ container.style.display = "flex";
5185
+ container.style.justifyContent = "center";
5186
+ container.style.alignItems = "flex-start";
4599
5187
  ctx.container.appendChild(container);
4600
5188
  let scale = 1;
4601
5189
  let extractedRawText = "";
@@ -4622,21 +5210,27 @@ var DocPlugin = class {
4622
5210
  const rawPages = this.splitIntoPages(extractedRawText);
4623
5211
  const totalPages = Math.max(1, rawPages.length);
4624
5212
  let currentPage = 1;
4625
- wrapper.innerHTML = "";
4626
5213
  const pageCards = [];
4627
5214
  for (let i = 0; i < totalPages; i++) {
4628
5215
  const pageCard = document.createElement("div");
4629
5216
  pageCard.className = "fp-doc-page-card";
5217
+ pageCard.style.width = "816px";
5218
+ pageCard.style.height = "1056px";
4630
5219
  pageCard.style.backgroundColor = "#ffffff";
4631
5220
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4632
5221
  pageCard.style.borderRadius = "4px";
4633
- pageCard.style.padding = "56px 48px";
4634
- pageCard.style.minHeight = "100%";
5222
+ pageCard.style.padding = "96px 72px";
5223
+ pageCard.style.boxSizing = "border-box";
5224
+ pageCard.style.overflow = "hidden";
4635
5225
  pageCard.style.display = i === 0 ? "block" : "none";
5226
+ pageCard.style.transformOrigin = "top center";
5227
+ pageCard.style.transition = "transform 0.2s ease";
5228
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5229
+ pageCard.style.color = "#1e293b";
4636
5230
  if (isFallback && i === 0) {
4637
5231
  pageCard.innerHTML = `
4638
5232
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4639
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2__default.default.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
5233
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify6__default.default.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4640
5234
  <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4641
5235
  </div>
4642
5236
  ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
@@ -4644,15 +5238,17 @@ var DocPlugin = class {
4644
5238
  } else {
4645
5239
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4646
5240
  }
4647
- wrapper.appendChild(pageCard);
5241
+ container.appendChild(pageCard);
4648
5242
  pageCards.push(pageCard);
4649
5243
  }
4650
5244
  let indicator = null;
4651
5245
  if (totalPages > 1) {
4652
5246
  indicator = document.createElement("div");
4653
5247
  indicator.className = "fp-doc-page-indicator";
4654
- indicator.style.position = "sticky";
5248
+ indicator.style.position = "fixed";
4655
5249
  indicator.style.bottom = "16px";
5250
+ indicator.style.left = "50%";
5251
+ indicator.style.transform = "translateX(-50%)";
4656
5252
  indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4657
5253
  indicator.style.backdropFilter = "blur(8px)";
4658
5254
  indicator.style.color = "#f8fafc";
@@ -4666,17 +5262,25 @@ var DocPlugin = class {
4666
5262
  indicator.style.userSelect = "none";
4667
5263
  indicator.style.pointerEvents = "none";
4668
5264
  indicator.style.textAlign = "center";
4669
- indicator.style.width = "fit-content";
4670
- indicator.style.margin = "16px auto 0";
4671
5265
  container.appendChild(indicator);
4672
5266
  }
5267
+ let rotation = 0;
5268
+ const applyTransform = () => {
5269
+ const activeCard = pageCards[currentPage - 1];
5270
+ if (activeCard) {
5271
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5272
+ }
5273
+ };
4673
5274
  const showPage = (pageNum) => {
4674
5275
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4675
- if (totalPages > 1) {
4676
- pageCards.forEach((card, idx) => {
4677
- card.style.display = idx + 1 === currentPage ? "block" : "none";
4678
- });
4679
- }
5276
+ pageCards.forEach((card, idx) => {
5277
+ if (idx + 1 === currentPage) {
5278
+ card.style.display = "block";
5279
+ card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5280
+ } else {
5281
+ card.style.display = "none";
5282
+ }
5283
+ });
4680
5284
  if (indicator) {
4681
5285
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4682
5286
  }
@@ -4685,6 +5289,17 @@ var DocPlugin = class {
4685
5289
  if (totalPages > 1) {
4686
5290
  showPage(1);
4687
5291
  }
5292
+ const calculateFitScale = () => {
5293
+ const elW = 816;
5294
+ const elH = 1056;
5295
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
5296
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
5297
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
5298
+ };
5299
+ setTimeout(() => {
5300
+ scale = calculateFitScale();
5301
+ applyTransform();
5302
+ }, 60);
4688
5303
  const cleanup = () => {
4689
5304
  indicator?.remove();
4690
5305
  container.remove();
@@ -4697,21 +5312,30 @@ var DocPlugin = class {
4697
5312
  getCurrentPage: () => currentPage,
4698
5313
  goToPage: (page) => showPage(page),
4699
5314
  zoomIn: () => {
4700
- scale += 0.1;
4701
- wrapper.style.transform = `scale(${scale})`;
5315
+ scale += 0.15;
5316
+ applyTransform();
4702
5317
  },
4703
5318
  zoomOut: () => {
4704
- scale = Math.max(0.2, scale - 0.1);
4705
- wrapper.style.transform = `scale(${scale})`;
5319
+ scale = Math.max(0.2, scale - 0.15);
5320
+ applyTransform();
4706
5321
  },
4707
5322
  getZoom: () => scale,
4708
5323
  setZoom: (level) => {
4709
5324
  scale = level;
4710
- wrapper.style.transform = `scale(${scale})`;
5325
+ applyTransform();
4711
5326
  },
4712
5327
  fitToPage: () => {
4713
- scale = 1;
4714
- wrapper.style.transform = "scale(1)";
5328
+ scale = calculateFitScale();
5329
+ rotation = 0;
5330
+ applyTransform();
5331
+ },
5332
+ rotateCW: () => {
5333
+ rotation = (rotation + 90) % 360;
5334
+ applyTransform();
5335
+ },
5336
+ rotateCCW: () => {
5337
+ rotation = (rotation - 90 + 360) % 360;
5338
+ applyTransform();
4715
5339
  },
4716
5340
  copy: () => {
4717
5341
  navigator.clipboard.writeText(extractedRawText);
@@ -4829,8 +5453,28 @@ var DocPlugin = class {
4829
5453
  }
4830
5454
  splitIntoPages(text) {
4831
5455
  if (!text) return [""];
4832
- 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);
4833
- return parts.length > 0 ? parts : [text];
5456
+ 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);
5457
+ if (explicitParts.length === 0) explicitParts.push(text);
5458
+ const maxLinesPerPage = 48;
5459
+ const finalPages = [];
5460
+ for (const part of explicitParts) {
5461
+ const lines = part.split(/\r?\n/);
5462
+ let currentLines = [];
5463
+ let count = 0;
5464
+ for (const line of lines) {
5465
+ currentLines.push(line);
5466
+ count++;
5467
+ if (count >= maxLinesPerPage) {
5468
+ finalPages.push(currentLines.join("\n"));
5469
+ currentLines = [];
5470
+ count = 0;
5471
+ }
5472
+ }
5473
+ if (currentLines.length > 0) {
5474
+ finalPages.push(currentLines.join("\n"));
5475
+ }
5476
+ }
5477
+ return finalPages.length > 0 ? finalPages : [text];
4834
5478
  }
4835
5479
  /**
4836
5480
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
@@ -4839,17 +5483,65 @@ var DocPlugin = class {
4839
5483
  const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4840
5484
  let html = "";
4841
5485
  let inList = false;
4842
- for (const rawLine of lines) {
4843
- const line = rawLine.trim();
5486
+ let tableLines = [];
5487
+ const flushTable = () => {
5488
+ if (tableLines.length > 0) {
5489
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 12px;">';
5490
+ for (const tLine of tableLines) {
5491
+ html += "<tr>";
5492
+ const cols = tLine.split(" ");
5493
+ for (const col of cols) {
5494
+ html += `<td style="border: 1px solid #cbd5e1; padding: 6px 8px;">${DOMPurify6__default.default.sanitize(col.trim())}</td>`;
5495
+ }
5496
+ html += "</tr>";
5497
+ }
5498
+ html += "</table>";
5499
+ tableLines = [];
5500
+ }
5501
+ };
5502
+ let i = 0;
5503
+ while (i < lines.length) {
5504
+ let line = lines[i];
5505
+ let tabCount = (line.match(/\t/g) || []).length;
5506
+ if (tabCount > 0) {
5507
+ let consecutiveTableLines = 1;
5508
+ let j = i + 1;
5509
+ while (j < lines.length) {
5510
+ const nextTabCount = (lines[j].match(/\t/g) || []).length;
5511
+ if (nextTabCount === tabCount) {
5512
+ consecutiveTableLines++;
5513
+ j++;
5514
+ } else {
5515
+ break;
5516
+ }
5517
+ }
5518
+ if (consecutiveTableLines >= 3) {
5519
+ if (inList) {
5520
+ html += "</ul>";
5521
+ inList = false;
5522
+ }
5523
+ tableLines = lines.slice(i, j);
5524
+ flushTable();
5525
+ i = j;
5526
+ continue;
5527
+ }
5528
+ }
5529
+ line = line.trim();
4844
5530
  if (!line) {
4845
5531
  if (inList) {
4846
5532
  html += "</ul>";
4847
5533
  inList = false;
4848
5534
  }
5535
+ html += '<div style="height: 1.15em;"></div>';
5536
+ i++;
4849
5537
  continue;
4850
5538
  }
4851
- if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) continue;
4852
- if (line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman") && line.length < 30) {
5539
+ if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) {
5540
+ i++;
5541
+ continue;
5542
+ }
5543
+ if ((line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman")) && line.length < 30) {
5544
+ i++;
4853
5545
  continue;
4854
5546
  }
4855
5547
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
@@ -4857,24 +5549,26 @@ var DocPlugin = class {
4857
5549
  html += "</ul>";
4858
5550
  inList = false;
4859
5551
  }
4860
- html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify2__default.default.sanitize(line)}</h2>`;
5552
+ html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line)}</h2>`;
4861
5553
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4862
5554
  if (!inList) {
4863
5555
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4864
5556
  inList = true;
4865
5557
  }
4866
5558
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4867
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2__default.default.sanitize(bulletText)}</li>`;
5559
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText)}</li>`;
4868
5560
  } else {
4869
5561
  if (inList) {
4870
5562
  html += "</ul>";
4871
5563
  inList = false;
4872
5564
  }
4873
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2__default.default.sanitize(line)}</p>`;
5565
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line)}</p>`;
4874
5566
  }
5567
+ i++;
4875
5568
  }
4876
5569
  if (inList) html += "</ul>";
4877
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2__default.default.sanitize(filename)})</p>`;
5570
+ flushTable();
5571
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6__default.default.sanitize(filename)})</p>`;
4878
5572
  }
4879
5573
  };
4880
5574
  function docPlugin() {
@@ -4951,6 +5645,14 @@ var PptPlugin = class {
4951
5645
  group: "zoom",
4952
5646
  execute: () => instance.fitToPage?.()
4953
5647
  },
5648
+ {
5649
+ id: "rotate-cw",
5650
+ icon: "rotate-cw",
5651
+ label: "Rotate",
5652
+ type: "button",
5653
+ group: "view",
5654
+ execute: () => instance.rotateCW?.()
5655
+ },
4954
5656
  {
4955
5657
  id: "download",
4956
5658
  icon: "download",
@@ -4984,22 +5686,36 @@ var PptPlugin = class {
4984
5686
  const slideCard = document.createElement("div");
4985
5687
  slideCard.className = "fp-ppt-slide-card";
4986
5688
  slideCard.style.width = "960px";
4987
- slideCard.style.maxWidth = "92%";
5689
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5690
+ slideCard.style.maxHeight = "calc(100% - 48px)";
4988
5691
  slideCard.style.aspectRatio = "16 / 9";
4989
5692
  slideCard.style.backgroundColor = "#ffffff";
4990
5693
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4991
5694
  slideCard.style.borderRadius = "8px";
4992
5695
  slideCard.style.boxSizing = "border-box";
4993
5696
  slideCard.style.position = "relative";
4994
- slideCard.style.overflow = "hidden";
4995
- slideCard.style.transformOrigin = "center center";
4996
5697
  slideCard.style.transition = "transform 0.2s ease";
5698
+ slideCard.style.transformOrigin = "center center";
5699
+ slideCard.style.flexShrink = "0";
4997
5700
  container.appendChild(slideCard);
4998
5701
  ctx.container.appendChild(container);
4999
5702
  let scale = 1;
5703
+ let rotation = 0;
5000
5704
  let currentSlide = 1;
5001
5705
  let slides = [];
5002
5706
  const createdBlobUrls = [];
5707
+ const calculateFitScale = () => {
5708
+ const availW = Math.max(200, container.clientWidth - 48);
5709
+ const availH = Math.max(200, container.clientHeight - 72);
5710
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5711
+ };
5712
+ const applyTransform = () => {
5713
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5714
+ };
5715
+ setTimeout(() => {
5716
+ scale = calculateFitScale();
5717
+ applyTransform();
5718
+ }, 60);
5003
5719
  try {
5004
5720
  const cfbf = new CfbfReader(ctx.buffer);
5005
5721
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -5030,7 +5746,7 @@ var PptPlugin = class {
5030
5746
  if (s.pictureUrl) {
5031
5747
  contentHtml = `
5032
5748
  <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
5033
- <img src="${s.pictureUrl}" alt="${DOMPurify2__default.default.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
5749
+ <img src="${s.pictureUrl}" alt="${DOMPurify6__default.default.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
5034
5750
  </div>
5035
5751
  `;
5036
5752
  } else if (s.tableColumns.length > 0) {
@@ -5040,7 +5756,7 @@ var PptPlugin = class {
5040
5756
  <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
5041
5757
  <thead>
5042
5758
  <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
5043
- ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2__default.default.sanitize(c)}</th>`).join("")}
5759
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify6__default.default.sanitize(c)}</th>`).join("")}
5044
5760
  </tr>
5045
5761
  </thead>
5046
5762
  <tbody>
@@ -5056,7 +5772,7 @@ var PptPlugin = class {
5056
5772
  } else {
5057
5773
  const pTags = s.paragraphs.map((p) => {
5058
5774
  const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
5059
- return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2__default.default.sanitize(l)}</p>`).join("");
5775
+ return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify6__default.default.sanitize(l)}</p>`).join("");
5060
5776
  }).join("");
5061
5777
  contentHtml = `
5062
5778
  <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
@@ -5069,11 +5785,11 @@ var PptPlugin = class {
5069
5785
  <!-- Header Banner matching PowerPoint design -->
5070
5786
  <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;">
5071
5787
  <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
5072
- ${DOMPurify2__default.default.sanitize(s.title)}
5788
+ ${DOMPurify6__default.default.sanitize(s.title)}
5073
5789
  </h1>
5074
5790
  ${s.subtitle ? `
5075
5791
  <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);">
5076
- ${DOMPurify2__default.default.sanitize(s.subtitle)}
5792
+ ${DOMPurify6__default.default.sanitize(s.subtitle)}
5077
5793
  </span>
5078
5794
  ` : `
5079
5795
  <span style="font-size: 12px; color: #365314; font-weight: 600;">
@@ -5100,21 +5816,30 @@ var PptPlugin = class {
5100
5816
  return {
5101
5817
  destroy: cleanup,
5102
5818
  zoomIn: () => {
5103
- scale += 0.1;
5104
- slideCard.style.transform = `scale(${scale})`;
5819
+ scale += 0.15;
5820
+ applyTransform();
5105
5821
  },
5106
5822
  zoomOut: () => {
5107
- scale = Math.max(0.3, scale - 0.1);
5108
- slideCard.style.transform = `scale(${scale})`;
5823
+ scale = Math.max(0.2, scale - 0.15);
5824
+ applyTransform();
5109
5825
  },
5110
5826
  getZoom: () => scale,
5111
5827
  setZoom: (level) => {
5112
5828
  scale = level;
5113
- slideCard.style.transform = `scale(${scale})`;
5829
+ applyTransform();
5114
5830
  },
5115
5831
  fitToPage: () => {
5116
- scale = 1;
5117
- slideCard.style.transform = "scale(1)";
5832
+ scale = calculateFitScale();
5833
+ rotation = 0;
5834
+ applyTransform();
5835
+ },
5836
+ rotateCW: () => {
5837
+ rotation = (rotation + 90) % 360;
5838
+ applyTransform();
5839
+ },
5840
+ rotateCCW: () => {
5841
+ rotation = (rotation - 90 + 360) % 360;
5842
+ applyTransform();
5118
5843
  },
5119
5844
  goToPage: (page) => {
5120
5845
  if (page >= 1 && page <= totalSlides) {