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

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