@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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import DOMPurify2 from 'dompurify';
1
+ import DOMPurify6 from 'dompurify';
2
2
  import * as pdfjsLib from 'pdfjs-dist';
3
3
  import * as docx from 'docx-preview';
4
4
  import { unzipSync, strFromU8, unzip } from 'fflate';
@@ -356,7 +356,7 @@ async function sourceToArrayBuffer(source, signal) {
356
356
  if (magicMime === "application/zip") {
357
357
  const ooxmlMime = detectOoxmlType(buffer);
358
358
  if (ooxmlMime && ooxmlMime !== "application/zip") {
359
- metadata.mimeType = ooxmlMime;
359
+ metadata.mimeType = metadata.mimeType ?? ooxmlMime;
360
360
  if (ooxmlMime.includes("wordprocessing")) metadata.extension = metadata.extension ?? ".docx";
361
361
  else if (ooxmlMime.includes("spreadsheet")) metadata.extension = metadata.extension ?? ".xlsx";
362
362
  else if (ooxmlMime.includes("presentation")) metadata.extension = metadata.extension ?? ".pptx";
@@ -385,12 +385,12 @@ async function sourceToArrayBuffer(source, signal) {
385
385
  return { buffer, metadata };
386
386
  }
387
387
  function sanitizeHTML(html) {
388
- return DOMPurify2.sanitize(html, {
388
+ return DOMPurify6.sanitize(html, {
389
389
  USE_PROFILES: { html: true }
390
390
  });
391
391
  }
392
392
  function sanitizeSVG(svg) {
393
- return DOMPurify2.sanitize(svg, {
393
+ return DOMPurify6.sanitize(svg, {
394
394
  USE_PROFILES: { svg: true, svgFilters: true }
395
395
  });
396
396
  }
@@ -748,6 +748,8 @@ var FilePreviewViewer = class {
748
748
  keyHandler = null;
749
749
  currentContainer = null;
750
750
  currentOptions = {};
751
+ resizeObserver = null;
752
+ fullscreenHandler = null;
751
753
  /**
752
754
  * Register a preview plugin.
753
755
  */
@@ -820,12 +822,31 @@ var FilePreviewViewer = class {
820
822
  label: "Toggle Fullscreen",
821
823
  type: "button",
822
824
  group: "view",
823
- execute: () => {
824
- if (!document.fullscreenElement) {
825
- this.wrapperEl?.requestFullscreen?.();
826
- } else {
827
- document.exitFullscreen?.();
825
+ execute: async () => {
826
+ try {
827
+ const isNativeFs = !!document.fullscreenElement;
828
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
829
+ if (!isNativeFs && !isCssFs) {
830
+ if (this.wrapperEl?.requestFullscreen) {
831
+ await this.wrapperEl.requestFullscreen().catch(() => {
832
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
833
+ });
834
+ } else {
835
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
836
+ }
837
+ } else {
838
+ if (document.fullscreenElement) {
839
+ await document.exitFullscreen().catch(() => {
840
+ });
841
+ }
842
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
843
+ }
844
+ } catch {
845
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
828
846
  }
847
+ setTimeout(() => {
848
+ this.activeInstance?.fitToPage?.();
849
+ }, 120);
829
850
  }
830
851
  });
831
852
  }
@@ -875,6 +896,15 @@ var FilePreviewViewer = class {
875
896
  window.removeEventListener("keydown", this.keyHandler);
876
897
  this.keyHandler = null;
877
898
  }
899
+ if (this.resizeObserver) {
900
+ this.resizeObserver.disconnect();
901
+ this.resizeObserver = null;
902
+ }
903
+ if (this.fullscreenHandler) {
904
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
905
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
906
+ this.fullscreenHandler = null;
907
+ }
878
908
  this.abort();
879
909
  this.destroyInstance();
880
910
  this.toolbar?.destroy();
@@ -954,6 +984,25 @@ var FilePreviewViewer = class {
954
984
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
955
985
  this.setupKeyboardShortcuts();
956
986
  this.setupDragAndDrop(container, options);
987
+ this.setupResizeAndFullscreenListeners();
988
+ }
989
+ setupResizeAndFullscreenListeners() {
990
+ if (this.fullscreenHandler) return;
991
+ this.fullscreenHandler = () => {
992
+ setTimeout(() => {
993
+ this.activeInstance?.fitToPage?.();
994
+ }, 100);
995
+ };
996
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
997
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
998
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
999
+ this.resizeObserver = new ResizeObserver(() => {
1000
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1001
+ this.activeInstance.fitToPage?.();
1002
+ }
1003
+ });
1004
+ this.resizeObserver.observe(this.contentEl);
1005
+ }
957
1006
  }
958
1007
  setupKeyboardShortcuts() {
959
1008
  if (this.keyHandler) return;
@@ -1345,7 +1394,8 @@ var PdfPlugin = class {
1345
1394
  container.style.display = "flex";
1346
1395
  container.style.flexDirection = "column";
1347
1396
  container.style.alignItems = "center";
1348
- container.style.padding = "24px 16px";
1397
+ container.style.justifyContent = "flex-start";
1398
+ container.style.padding = "20px 16px";
1349
1399
  container.style.backgroundColor = "#0f172a";
1350
1400
  container.style.boxSizing = "border-box";
1351
1401
  container.style.position = "relative";
@@ -1358,7 +1408,8 @@ var PdfPlugin = class {
1358
1408
  pageCard.style.lineHeight = "0";
1359
1409
  pageCard.style.transition = "transform 0.15s ease";
1360
1410
  pageCard.style.position = "relative";
1361
- const canvas = document.createElement("canvas");
1411
+ pageCard.style.flexShrink = "0";
1412
+ let canvas = document.createElement("canvas");
1362
1413
  pageCard.appendChild(canvas);
1363
1414
  container.appendChild(pageCard);
1364
1415
  const indicator = document.createElement("div");
@@ -1400,14 +1451,22 @@ var PdfPlugin = class {
1400
1451
  }
1401
1452
  currentRenderTask = null;
1402
1453
  }
1454
+ const newCanvas = document.createElement("canvas");
1455
+ pageCard.replaceChild(newCanvas, canvas);
1456
+ canvas = newCanvas;
1403
1457
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1404
1458
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1405
1459
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1406
1460
  const page = await pdfDoc.getPage(currentPage);
1407
1461
  const containerWidth = container.clientWidth || 900;
1462
+ const containerHeight = container.clientHeight || 700;
1408
1463
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1409
- const baseScale = Math.min((containerWidth - 64) / unscaledVp.width, 1.6);
1410
- const effectiveScale = (baseScale > 0 ? baseScale : 1) * zoomScale;
1464
+ const availWidth = Math.max(100, containerWidth - 48);
1465
+ const availHeight = Math.max(100, containerHeight - 88);
1466
+ const scaleW = availWidth / unscaledVp.width;
1467
+ const scaleH = availHeight / unscaledVp.height;
1468
+ const fitScale = Math.min(scaleW, scaleH);
1469
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1411
1470
  const pixelRatio = window.devicePixelRatio || 1;
1412
1471
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
1413
1472
  canvas.width = Math.floor(viewport.width * pixelRatio);
@@ -1432,7 +1491,19 @@ var PdfPlugin = class {
1432
1491
  }
1433
1492
  };
1434
1493
  await renderPage(1);
1494
+ let resizeTimer = null;
1495
+ const resizeObserver = new ResizeObserver(() => {
1496
+ if (resizeTimer) clearTimeout(resizeTimer);
1497
+ resizeTimer = setTimeout(() => {
1498
+ if (Math.abs(zoomScale - 1) < 0.05) {
1499
+ renderPage(currentPage);
1500
+ }
1501
+ }, 150);
1502
+ });
1503
+ resizeObserver.observe(container);
1435
1504
  const cleanup = () => {
1505
+ resizeObserver.disconnect();
1506
+ if (resizeTimer) clearTimeout(resizeTimer);
1436
1507
  if (currentRenderTask) {
1437
1508
  try {
1438
1509
  currentRenderTask.cancel();
@@ -1464,6 +1535,7 @@ var PdfPlugin = class {
1464
1535
  },
1465
1536
  fitToPage: () => {
1466
1537
  zoomScale = 1;
1538
+ rotation = 0;
1467
1539
  renderPage(currentPage);
1468
1540
  },
1469
1541
  rotateCW: () => {
@@ -1914,6 +1986,14 @@ var DocxPlugin = class {
1914
1986
  group: "zoom",
1915
1987
  execute: () => instance.fitToPage?.()
1916
1988
  },
1989
+ {
1990
+ id: "rotate-cw",
1991
+ icon: "rotate-cw",
1992
+ label: "Rotate",
1993
+ type: "button",
1994
+ group: "view",
1995
+ execute: () => instance.rotateCW?.()
1996
+ },
1917
1997
  {
1918
1998
  id: "download",
1919
1999
  icon: "download",
@@ -2050,6 +2130,26 @@ var DocxPlugin = class {
2050
2130
  if (totalPages > 1) {
2051
2131
  showPage(1);
2052
2132
  }
2133
+ scale = 1;
2134
+ let rotation = 0;
2135
+ const calculateFitScale = () => {
2136
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2137
+ const elW = activeEl.offsetWidth || 816;
2138
+ const elH = activeEl.offsetHeight || 1056;
2139
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2140
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2141
+ const sW = availW / elW;
2142
+ const sH = availH / elH;
2143
+ return Math.min(1.1, Math.min(sW, sH));
2144
+ };
2145
+ const applyTransform = () => {
2146
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2147
+ wrapper.style.transformOrigin = "top center";
2148
+ };
2149
+ setTimeout(() => {
2150
+ scale = calculateFitScale();
2151
+ applyTransform();
2152
+ }, 60);
2053
2153
  const cleanup = () => {
2054
2154
  indicator?.remove();
2055
2155
  for (const url of createdBlobUrls) {
@@ -2068,21 +2168,30 @@ var DocxPlugin = class {
2068
2168
  showPage(page);
2069
2169
  },
2070
2170
  zoomIn: () => {
2071
- scale += 0.1;
2072
- wrapper.style.transform = `scale(${scale})`;
2171
+ scale += 0.15;
2172
+ applyTransform();
2073
2173
  },
2074
2174
  zoomOut: () => {
2075
- scale = Math.max(0.2, scale - 0.1);
2076
- wrapper.style.transform = `scale(${scale})`;
2175
+ scale = Math.max(0.2, scale - 0.15);
2176
+ applyTransform();
2077
2177
  },
2078
2178
  getZoom: () => scale,
2079
2179
  setZoom: (level) => {
2080
2180
  scale = level;
2081
- wrapper.style.transform = `scale(${scale})`;
2181
+ applyTransform();
2082
2182
  },
2083
2183
  fitToPage: () => {
2084
- scale = 1;
2085
- wrapper.style.transform = "scale(1)";
2184
+ scale = calculateFitScale();
2185
+ rotation = 0;
2186
+ applyTransform();
2187
+ },
2188
+ rotateCW: () => {
2189
+ rotation = (rotation + 90) % 360;
2190
+ applyTransform();
2191
+ },
2192
+ rotateCCW: () => {
2193
+ rotation = (rotation - 90 + 360) % 360;
2194
+ applyTransform();
2086
2195
  },
2087
2196
  download: () => {
2088
2197
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2142,90 +2251,149 @@ var DocxPlugin = class {
2142
2251
  console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
2143
2252
  }
2144
2253
  }
2145
- const card = document.createElement("div");
2146
- card.className = "fp-docx-page-card";
2147
- card.style.backgroundColor = "#ffffff";
2148
- card.style.borderRadius = "6px";
2149
- card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2150
- card.style.padding = "48px 56px";
2151
- card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2152
- card.style.color = "#1e293b";
2153
- card.style.lineHeight = "1.6";
2254
+ const sectPrs = Array.from(doc.getElementsByTagNameNS("*", "sectPr"));
2255
+ const lastSectPr = sectPrs[sectPrs.length - 1];
2256
+ let defaultW = 12240;
2257
+ let defaultH = 15840;
2258
+ let margins = { top: 1440, right: 1440, bottom: 1440, left: 1440 };
2259
+ if (lastSectPr) {
2260
+ const pgSz = lastSectPr.getElementsByTagNameNS("*", "pgSz")[0];
2261
+ if (pgSz) {
2262
+ defaultW = parseInt(pgSz.getAttribute("w:w") || pgSz.getAttribute("w") || "12240", 10);
2263
+ defaultH = parseInt(pgSz.getAttribute("w:h") || pgSz.getAttribute("h") || "15840", 10);
2264
+ }
2265
+ const pgMar = lastSectPr.getElementsByTagNameNS("*", "pgMar")[0];
2266
+ if (pgMar) {
2267
+ margins.top = parseInt(pgMar.getAttribute("w:top") || pgMar.getAttribute("top") || "1440", 10);
2268
+ margins.bottom = parseInt(pgMar.getAttribute("w:bottom") || pgMar.getAttribute("bottom") || "1440", 10);
2269
+ margins.left = parseInt(pgMar.getAttribute("w:left") || pgMar.getAttribute("left") || "1440", 10);
2270
+ margins.right = parseInt(pgMar.getAttribute("w:right") || pgMar.getAttribute("right") || "1440", 10);
2271
+ }
2272
+ }
2273
+ const createPageCard = () => {
2274
+ const card = document.createElement("div");
2275
+ card.className = "fp-docx-page-card";
2276
+ card.style.backgroundColor = "#ffffff";
2277
+ card.style.borderRadius = "4px";
2278
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2279
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2280
+ card.style.color = "#1e293b";
2281
+ card.style.lineHeight = "1.6";
2282
+ card.style.boxSizing = "border-box";
2283
+ card.style.overflow = "hidden";
2284
+ card.style.position = "relative";
2285
+ card.style.marginBottom = "24px";
2286
+ card.style.width = `${defaultW / 15}px`;
2287
+ card.style.height = `${defaultH / 15}px`;
2288
+ card.style.padding = `${margins.top / 15}px ${margins.right / 15}px ${margins.bottom / 15}px ${margins.left / 15}px`;
2289
+ return card;
2290
+ };
2291
+ let currentCard = createPageCard();
2292
+ let currentHtml = "";
2293
+ const pages = [{ card: currentCard, html: "" }];
2294
+ const flushHtml = () => {
2295
+ pages[pages.length - 1].html += currentHtml;
2296
+ currentHtml = "";
2297
+ };
2298
+ const newPage = () => {
2299
+ flushHtml();
2300
+ currentCard = createPageCard();
2301
+ pages.push({ card: currentCard, html: "" });
2302
+ };
2154
2303
  const body = doc.getElementsByTagNameNS("*", "body")[0] || doc.documentElement;
2155
- let html = "";
2156
2304
  for (const child of Array.from(body.children)) {
2157
2305
  const tag = child.localName || child.nodeName.split(":").pop();
2158
2306
  if (tag === "p") {
2159
2307
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
2160
2308
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
2161
2309
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
2162
- const textContent = this.extractParagraphHtml(child, imageMap);
2163
- if (!textContent.trim()) {
2164
- html += '<div style="height: 10px;"></div>';
2165
- continue;
2166
- }
2167
- const lowerStyle = styleVal.toLowerCase();
2168
- if (lowerStyle.includes("title")) {
2169
- 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>`;
2170
- } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2171
- html += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2172
- } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2173
- html += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2174
- } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2175
- html += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2176
- } else if (numPr) {
2177
- 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>`;
2178
- } else {
2179
- html += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2310
+ const chunks = this.extractParagraphChunks(child, imageMap);
2311
+ for (let i = 0; i < chunks.length; i++) {
2312
+ if (i > 0) {
2313
+ newPage();
2314
+ }
2315
+ const textContent = chunks[i];
2316
+ if (!textContent.trim() && !textContent.includes("<img")) {
2317
+ currentHtml += '<div style="height: 10px;"></div>';
2318
+ continue;
2319
+ }
2320
+ const lowerStyle = styleVal.toLowerCase();
2321
+ if (lowerStyle.includes("title")) {
2322
+ 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>`;
2323
+ } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2324
+ currentHtml += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2325
+ } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2326
+ currentHtml += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2327
+ } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2328
+ currentHtml += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2329
+ } else if (numPr) {
2330
+ 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>`;
2331
+ } else {
2332
+ currentHtml += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2333
+ }
2180
2334
  }
2181
2335
  } else if (tag === "tbl") {
2182
- html += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2336
+ currentHtml += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2183
2337
  const rows = Array.from(child.getElementsByTagNameNS("*", "tr"));
2184
2338
  rows.forEach((tr, rIdx) => {
2185
- html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2339
+ currentHtml += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2186
2340
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
2187
2341
  cells.forEach((tc) => {
2188
2342
  const cellText = this.extractParagraphHtml(tc, imageMap);
2189
- html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2343
+ currentHtml += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2190
2344
  });
2191
- html += "</tr>";
2345
+ currentHtml += "</tr>";
2192
2346
  });
2193
- html += "</table>";
2347
+ currentHtml += "</table>";
2194
2348
  }
2195
2349
  }
2196
- card.innerHTML = DOMPurify2.sanitize(html, {
2197
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2198
- ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2199
- });
2200
- wrapper.appendChild(card);
2350
+ flushHtml();
2351
+ for (const page of pages) {
2352
+ page.card.innerHTML = DOMPurify6.sanitize(page.html, {
2353
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2354
+ ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2355
+ });
2356
+ wrapper.appendChild(page.card);
2357
+ }
2201
2358
  }
2202
2359
  extractParagraphHtml(pElement, imageMap = {}) {
2203
- let result = "";
2360
+ return this.extractParagraphChunks(pElement, imageMap).join("<br/>");
2361
+ }
2362
+ extractParagraphChunks(pElement, imageMap = {}) {
2363
+ const chunks = [""];
2364
+ let currentChunkIndex = 0;
2204
2365
  const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
2205
2366
  for (const drawing of drawings) {
2206
2367
  const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
2207
2368
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2208
2369
  if (rId && imageMap[rId]) {
2209
- result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2370
+ chunks[currentChunkIndex] += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2210
2371
  }
2211
2372
  }
2212
2373
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
2213
- if (runs.length === 0 && !result) {
2214
- return DOMPurify2.sanitize(pElement.textContent || "");
2374
+ if (runs.length === 0 && !chunks[currentChunkIndex]) {
2375
+ chunks[currentChunkIndex] = DOMPurify6.sanitize(pElement.textContent || "");
2376
+ return chunks;
2215
2377
  }
2216
2378
  for (const r of runs) {
2217
2379
  const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
2218
2380
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2219
2381
  if (rId && imageMap[rId]) {
2220
- result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2382
+ chunks[currentChunkIndex] += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2221
2383
  }
2222
- const brs = r.getElementsByTagNameNS("*", "br");
2223
- for (let i = 0; i < brs.length; i++) {
2224
- result += "<br/>";
2384
+ const brs = Array.from(r.getElementsByTagNameNS("*", "br"));
2385
+ for (const br of brs) {
2386
+ const type = br.getAttribute("w:type") || br.getAttribute("type");
2387
+ if (type === "page") {
2388
+ chunks.push("");
2389
+ currentChunkIndex++;
2390
+ } else {
2391
+ chunks[currentChunkIndex] += "<br/>";
2392
+ }
2225
2393
  }
2226
2394
  const tabs = r.getElementsByTagNameNS("*", "tab");
2227
2395
  if (tabs.length > 0) {
2228
- result += "&emsp;";
2396
+ chunks[currentChunkIndex] += "&emsp;";
2229
2397
  }
2230
2398
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
2231
2399
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
@@ -2239,7 +2407,7 @@ var DocxPlugin = class {
2239
2407
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
2240
2408
  let text = texts.map((t) => t.textContent || "").join("");
2241
2409
  if (!text) continue;
2242
- text = DOMPurify2.sanitize(text);
2410
+ text = DOMPurify6.sanitize(text);
2243
2411
  let styles = "";
2244
2412
  if (isBold) styles += "font-weight: bold; ";
2245
2413
  if (isItalic) styles += "font-style: italic; ";
@@ -2251,12 +2419,12 @@ var DocxPlugin = class {
2251
2419
  if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2252
2420
  }
2253
2421
  if (styles) {
2254
- result += `<span style="${styles}">${text}</span>`;
2422
+ chunks[currentChunkIndex] += `<span style="${styles}">${text}</span>`;
2255
2423
  } else {
2256
- result += text;
2424
+ chunks[currentChunkIndex] += text;
2257
2425
  }
2258
2426
  }
2259
- return result;
2427
+ return chunks;
2260
2428
  }
2261
2429
  renderBinaryDocFallback(ctx, wrapper) {
2262
2430
  const card = document.createElement("div");
@@ -2273,14 +2441,14 @@ var DocxPlugin = class {
2273
2441
  const wordDocStream = cfbf.readStream("WordDocument");
2274
2442
  if (wordDocStream && wordDocStream.length >= 512) {
2275
2443
  const text2 = this.extractReadableStrings(wordDocStream);
2276
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text2)}</div>`;
2444
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6.sanitize(text2)}</div>`;
2277
2445
  wrapper.appendChild(card);
2278
2446
  return;
2279
2447
  }
2280
2448
  } catch {
2281
2449
  }
2282
2450
  const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2283
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text)}</div>`;
2451
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6.sanitize(text)}</div>`;
2284
2452
  wrapper.appendChild(card);
2285
2453
  }
2286
2454
  extractReadableStrings(bytes) {
@@ -2376,6 +2544,26 @@ var ExcelPlugin = class {
2376
2544
  instance.zoomIn?.();
2377
2545
  }
2378
2546
  },
2547
+ {
2548
+ id: "fit-page",
2549
+ icon: "fit-page",
2550
+ label: "Fit to View",
2551
+ type: "button",
2552
+ group: "zoom",
2553
+ execute: () => {
2554
+ instance.fitToPage?.();
2555
+ }
2556
+ },
2557
+ {
2558
+ id: "rotate-cw",
2559
+ icon: "rotate-cw",
2560
+ label: "Rotate",
2561
+ type: "button",
2562
+ group: "view",
2563
+ execute: () => {
2564
+ instance.rotateCW?.();
2565
+ }
2566
+ },
2379
2567
  {
2380
2568
  id: "download",
2381
2569
  icon: "download",
@@ -2426,9 +2614,23 @@ var ExcelPlugin = class {
2426
2614
  container.appendChild(tabsArea);
2427
2615
  ctx.container.appendChild(container);
2428
2616
  let scale = 1;
2617
+ let rotation = 0;
2429
2618
  let currentSheetIndex = 1;
2430
2619
  let sheetNames = [];
2431
2620
  let wb = null;
2621
+ const applyTransform = () => {
2622
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2623
+ contentArea.style.transformOrigin = "top left";
2624
+ };
2625
+ const calculateFitScale = () => {
2626
+ const table = contentArea.querySelector("table");
2627
+ if (!table) return 1;
2628
+ const availW = Math.max(200, container.clientWidth - 48);
2629
+ const availH = Math.max(200, container.clientHeight - 80);
2630
+ const tW = table.offsetWidth || 800;
2631
+ const tH = table.offsetHeight || 600;
2632
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2633
+ };
2432
2634
  try {
2433
2635
  wb = XLSX.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2434
2636
  sheetNames = wb.SheetNames || [];
@@ -2516,17 +2718,30 @@ var ExcelPlugin = class {
2516
2718
  return {
2517
2719
  destroy: cleanup,
2518
2720
  zoomIn: () => {
2519
- scale += 0.1;
2520
- contentArea.style.transform = `scale(${scale})`;
2721
+ scale += 0.15;
2722
+ applyTransform();
2521
2723
  },
2522
2724
  zoomOut: () => {
2523
- scale = Math.max(0.2, scale - 0.1);
2524
- contentArea.style.transform = `scale(${scale})`;
2725
+ scale = Math.max(0.2, scale - 0.15);
2726
+ applyTransform();
2525
2727
  },
2526
2728
  getZoom: () => scale,
2527
2729
  setZoom: (level) => {
2528
2730
  scale = level;
2529
- contentArea.style.transform = `scale(${scale})`;
2731
+ applyTransform();
2732
+ },
2733
+ fitToPage: () => {
2734
+ scale = calculateFitScale();
2735
+ rotation = 0;
2736
+ applyTransform();
2737
+ },
2738
+ rotateCW: () => {
2739
+ rotation = (rotation + 90) % 360;
2740
+ applyTransform();
2741
+ },
2742
+ rotateCCW: () => {
2743
+ rotation = (rotation - 90 + 360) % 360;
2744
+ applyTransform();
2530
2745
  },
2531
2746
  goToPage: (page) => {
2532
2747
  if (page > 0 && page <= sheetNames.length) {
@@ -2776,6 +2991,26 @@ var CodePlugin = class {
2776
2991
  instance.zoomIn?.();
2777
2992
  }
2778
2993
  },
2994
+ {
2995
+ id: "fit-page",
2996
+ icon: "fit-page",
2997
+ label: "Fit to View",
2998
+ type: "button",
2999
+ group: "zoom",
3000
+ execute: () => {
3001
+ instance.fitToPage?.();
3002
+ }
3003
+ },
3004
+ {
3005
+ id: "rotate-cw",
3006
+ icon: "rotate-cw",
3007
+ label: "Rotate",
3008
+ type: "button",
3009
+ group: "view",
3010
+ execute: () => {
3011
+ instance.rotateCW?.();
3012
+ }
3013
+ },
2779
3014
  {
2780
3015
  id: "copy",
2781
3016
  icon: "copy",
@@ -2812,40 +3047,94 @@ var CodePlugin = class {
2812
3047
  async render(ctx) {
2813
3048
  const decoder = new TextDecoder("utf-8");
2814
3049
  const fullText = decoder.decode(ctx.buffer);
2815
- const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2816
- const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3050
+ const extRaw = ctx.metadata.extension?.toLowerCase();
3051
+ const mimeRaw = ctx.metadata.mimeType?.toLowerCase();
3052
+ const isTxt = extRaw === ".txt" || mimeRaw === "text/plain" && (!extRaw || !this.extensions.filter((e) => e !== ".txt").includes(extRaw));
3053
+ let rawPages = [];
3054
+ if (isTxt) {
3055
+ const explicitPages = fullText.split(/(?:\f|\x0C)/);
3056
+ for (const ep of explicitPages) {
3057
+ const lines = ep.split(/\r?\n/);
3058
+ let currentChunk = [];
3059
+ for (let i = 0; i < lines.length; i++) {
3060
+ currentChunk.push(lines[i]);
3061
+ if (currentChunk.length >= 46) {
3062
+ rawPages.push(currentChunk.join("\n"));
3063
+ currentChunk = [];
3064
+ }
3065
+ }
3066
+ if (currentChunk.length > 0 || lines.length === 0) {
3067
+ rawPages.push(currentChunk.join("\n"));
3068
+ }
3069
+ }
3070
+ if (rawPages.length === 0) rawPages = [""];
3071
+ } else {
3072
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
3073
+ rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3074
+ }
2817
3075
  const totalPages = Math.max(1, rawPages.length);
2818
3076
  let currentPage = 1;
2819
3077
  const container = document.createElement("div");
2820
3078
  container.style.width = "100%";
2821
3079
  container.style.height = "100%";
2822
3080
  container.style.overflow = "auto";
2823
- container.style.backgroundColor = "#1e1e1e";
2824
- container.style.color = "#d4d4d4";
2825
- container.style.padding = "16px";
2826
- container.style.boxSizing = "border-box";
2827
3081
  let fontSize = 13;
3082
+ let rotation = 0;
3083
+ let zoomLevel = 1;
2828
3084
  const pre = document.createElement("pre");
2829
- pre.style.margin = "0";
2830
- pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
2831
- pre.style.fontSize = `${fontSize}px`;
2832
- pre.style.lineHeight = "1.5";
2833
- pre.style.whiteSpace = "pre-wrap";
2834
- pre.style.wordBreak = "break-all";
2835
3085
  const code = document.createElement("code");
3086
+ if (isTxt) {
3087
+ container.style.backgroundColor = "#f1f5f9";
3088
+ container.style.padding = "32px";
3089
+ container.style.boxSizing = "border-box";
3090
+ container.style.display = "flex";
3091
+ container.style.flexDirection = "column";
3092
+ container.style.alignItems = "center";
3093
+ pre.style.margin = "0";
3094
+ pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3095
+ pre.style.fontSize = "13px";
3096
+ pre.style.color = "#1e293b";
3097
+ pre.style.lineHeight = "1.5";
3098
+ pre.style.whiteSpace = "pre-wrap";
3099
+ pre.style.wordBreak = "break-word";
3100
+ pre.style.backgroundColor = "#ffffff";
3101
+ pre.style.width = "816px";
3102
+ pre.style.minHeight = "1056px";
3103
+ pre.style.padding = "72px 56px";
3104
+ pre.style.boxSizing = "border-box";
3105
+ pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
3106
+ pre.style.borderRadius = "4px";
3107
+ pre.style.transformOrigin = "top center";
3108
+ } else {
3109
+ container.style.backgroundColor = "#1e1e1e";
3110
+ container.style.color = "#d4d4d4";
3111
+ container.style.padding = "16px";
3112
+ container.style.boxSizing = "border-box";
3113
+ pre.style.margin = "0";
3114
+ pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
3115
+ pre.style.fontSize = `${fontSize}px`;
3116
+ pre.style.lineHeight = "1.5";
3117
+ pre.style.whiteSpace = "pre-wrap";
3118
+ pre.style.wordBreak = "break-all";
3119
+ pre.style.transformOrigin = "top left";
3120
+ }
2836
3121
  const ext = (ctx.metadata.extension || "").replace(".", "");
2837
3122
  const renderCodePage = (text) => {
2838
- try {
2839
- if (ext && hljs.getLanguage(ext)) {
2840
- code.innerHTML = hljs.highlight(text, { language: ext }).value;
2841
- } else {
2842
- code.innerHTML = hljs.highlightAuto(text).value;
2843
- }
2844
- } catch {
3123
+ if (isTxt) {
2845
3124
  code.textContent = text;
3125
+ } else {
3126
+ try {
3127
+ if (ext && hljs.getLanguage(ext)) {
3128
+ code.innerHTML = hljs.highlight(text, { language: ext }).value;
3129
+ } else {
3130
+ code.innerHTML = hljs.highlightAuto(text).value;
3131
+ }
3132
+ } catch {
3133
+ code.textContent = text;
3134
+ }
2846
3135
  }
2847
3136
  };
2848
- renderCodePage(rawPages[0] || fullText);
3137
+ renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
2849
3138
  pre.appendChild(code);
2850
3139
  container.appendChild(pre);
2851
3140
  let indicator = null;
@@ -2854,15 +3143,22 @@ var CodePlugin = class {
2854
3143
  indicator.className = "fp-code-page-indicator";
2855
3144
  indicator.style.position = "sticky";
2856
3145
  indicator.style.bottom = "16px";
2857
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2858
- indicator.style.backdropFilter = "blur(8px)";
2859
- indicator.style.color = "#f8fafc";
3146
+ if (isTxt) {
3147
+ indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3148
+ indicator.style.color = "#334155";
3149
+ indicator.style.border = "1px solid #e2e8f0";
3150
+ indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3151
+ } else {
3152
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3153
+ indicator.style.color = "#f8fafc";
3154
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3155
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3156
+ indicator.style.backdropFilter = "blur(8px)";
3157
+ }
2860
3158
  indicator.style.fontSize = "12px";
2861
3159
  indicator.style.fontWeight = "600";
2862
3160
  indicator.style.padding = "5px 14px";
2863
3161
  indicator.style.borderRadius = "20px";
2864
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2865
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2866
3162
  indicator.style.zIndex = "10";
2867
3163
  indicator.style.userSelect = "none";
2868
3164
  indicator.style.pointerEvents = "none";
@@ -2873,7 +3169,7 @@ var CodePlugin = class {
2873
3169
  }
2874
3170
  const showPage = (pageNum) => {
2875
3171
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2876
- renderCodePage(rawPages[currentPage - 1] || fullText);
3172
+ renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
2877
3173
  if (indicator) {
2878
3174
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2879
3175
  }
@@ -2889,23 +3185,57 @@ var CodePlugin = class {
2889
3185
  ctx.container.innerHTML = "";
2890
3186
  };
2891
3187
  ctx.signal.addEventListener("abort", cleanup);
3188
+ const updateTransform = () => {
3189
+ if (isTxt) {
3190
+ pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
3191
+ } else {
3192
+ pre.style.transform = `rotate(${rotation}deg)`;
3193
+ pre.style.fontSize = `${fontSize}px`;
3194
+ }
3195
+ };
2892
3196
  return {
2893
3197
  destroy: cleanup,
2894
3198
  getPageCount: () => totalPages,
2895
3199
  getCurrentPage: () => currentPage,
2896
3200
  goToPage: (page) => showPage(page),
2897
3201
  zoomIn: () => {
2898
- fontSize = Math.min(32, fontSize + 2);
2899
- pre.style.fontSize = `${fontSize}px`;
3202
+ if (isTxt) {
3203
+ zoomLevel = Math.min(3, zoomLevel + 0.1);
3204
+ } else {
3205
+ fontSize = Math.min(32, fontSize + 2);
3206
+ }
3207
+ updateTransform();
2900
3208
  },
2901
3209
  zoomOut: () => {
2902
- fontSize = Math.max(8, fontSize - 2);
2903
- pre.style.fontSize = `${fontSize}px`;
3210
+ if (isTxt) {
3211
+ zoomLevel = Math.max(0.1, zoomLevel - 0.1);
3212
+ } else {
3213
+ fontSize = Math.max(8, fontSize - 2);
3214
+ }
3215
+ updateTransform();
2904
3216
  },
2905
- getZoom: () => fontSize / 13,
3217
+ getZoom: () => isTxt ? zoomLevel : fontSize / 13,
2906
3218
  setZoom: (level) => {
2907
- fontSize = Math.round(13 * level);
2908
- pre.style.fontSize = `${fontSize}px`;
3219
+ if (isTxt) {
3220
+ zoomLevel = level;
3221
+ } else {
3222
+ fontSize = Math.round(13 * level);
3223
+ }
3224
+ updateTransform();
3225
+ },
3226
+ fitToPage: () => {
3227
+ fontSize = 13;
3228
+ rotation = 0;
3229
+ zoomLevel = 1;
3230
+ updateTransform();
3231
+ },
3232
+ rotateCW: () => {
3233
+ rotation = (rotation + 90) % 360;
3234
+ updateTransform();
3235
+ },
3236
+ rotateCCW: () => {
3237
+ rotation = (rotation - 90 + 360) % 360;
3238
+ updateTransform();
2909
3239
  },
2910
3240
  download: () => {
2911
3241
  const mimeType = ctx.metadata.mimeType || "text/plain";
@@ -3204,7 +3534,7 @@ var MarkdownPlugin = class {
3204
3534
  gfm: true,
3205
3535
  breaks: true
3206
3536
  });
3207
- const cleanHtml = DOMPurify2.sanitize(rawHtml, {
3537
+ const cleanHtml = DOMPurify6.sanitize(rawHtml, {
3208
3538
  USE_PROFILES: { html: true }
3209
3539
  });
3210
3540
  const wrapper = document.createElement("div");
@@ -3418,6 +3748,14 @@ var PptxPlugin = class {
3418
3748
  group: "zoom",
3419
3749
  execute: () => instance.fitToPage?.()
3420
3750
  },
3751
+ {
3752
+ id: "rotate-cw",
3753
+ icon: "rotate-cw",
3754
+ label: "Rotate",
3755
+ type: "button",
3756
+ group: "view",
3757
+ execute: () => instance.rotateCW?.()
3758
+ },
3421
3759
  {
3422
3760
  id: "download",
3423
3761
  icon: "download",
@@ -3442,6 +3780,7 @@ var PptxPlugin = class {
3442
3780
  const slideCount = renderer.slidePaths?.length || 1;
3443
3781
  let currentSlide = 1;
3444
3782
  let scale = 1;
3783
+ let rotation = 0;
3445
3784
  const wrapper = document.createElement("div");
3446
3785
  wrapper.className = "fp-pptx-wrapper";
3447
3786
  wrapper.style.cssText = `
@@ -3464,6 +3803,8 @@ var PptxPlugin = class {
3464
3803
  background: #ffffff;
3465
3804
  transform-origin: top center;
3466
3805
  transition: transform 0.2s ease;
3806
+ max-width: calc(100% - 32px);
3807
+ max-height: calc(100% - 48px);
3467
3808
  `;
3468
3809
  const canvas = document.createElement("canvas");
3469
3810
  slideContainer.appendChild(canvas);
@@ -3471,10 +3812,22 @@ var PptxPlugin = class {
3471
3812
  ctx.container.innerHTML = "";
3472
3813
  ctx.container.style.overflow = "auto";
3473
3814
  ctx.container.appendChild(wrapper);
3815
+ const calculateFitScale = () => {
3816
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3817
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3818
+ const cW = canvas.offsetWidth || 1280;
3819
+ const cH = canvas.offsetHeight || 720;
3820
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3821
+ };
3822
+ const applyTransform = () => {
3823
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3824
+ };
3474
3825
  const renderCurrentSlide = async () => {
3475
3826
  try {
3476
3827
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3477
3828
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3829
+ scale = calculateFitScale();
3830
+ applyTransform();
3478
3831
  } catch (err) {
3479
3832
  console.error("[PptxPlugin] Failed to render slide:", err);
3480
3833
  }
@@ -3497,21 +3850,30 @@ var PptxPlugin = class {
3497
3850
  getPageCount: () => slideCount,
3498
3851
  getCurrentPage: () => currentSlide,
3499
3852
  zoomIn: () => {
3500
- scale += 0.1;
3501
- slideContainer.style.transform = `scale(${scale})`;
3853
+ scale += 0.15;
3854
+ applyTransform();
3502
3855
  },
3503
3856
  zoomOut: () => {
3504
- scale = Math.max(0.2, scale - 0.1);
3505
- slideContainer.style.transform = `scale(${scale})`;
3857
+ scale = Math.max(0.2, scale - 0.15);
3858
+ applyTransform();
3506
3859
  },
3507
3860
  getZoom: () => scale,
3508
3861
  setZoom: (level) => {
3509
3862
  scale = level;
3510
- slideContainer.style.transform = `scale(${scale})`;
3863
+ applyTransform();
3511
3864
  },
3512
3865
  fitToPage: () => {
3513
- scale = 1;
3514
- slideContainer.style.transform = "scale(1)";
3866
+ scale = calculateFitScale();
3867
+ rotation = 0;
3868
+ applyTransform();
3869
+ },
3870
+ rotateCW: () => {
3871
+ rotation = (rotation + 90) % 360;
3872
+ applyTransform();
3873
+ },
3874
+ rotateCCW: () => {
3875
+ rotation = (rotation - 90 + 360) % 360;
3876
+ applyTransform();
3515
3877
  },
3516
3878
  getThumbnails: () => {
3517
3879
  const list = [];
@@ -3792,6 +4154,14 @@ var RtfPlugin = class {
3792
4154
  group: "zoom",
3793
4155
  execute: () => instance.fitToPage?.()
3794
4156
  },
4157
+ {
4158
+ id: "rotate-cw",
4159
+ icon: "rotate-cw",
4160
+ label: "Rotate",
4161
+ type: "button",
4162
+ group: "view",
4163
+ execute: () => instance.rotateCW?.()
4164
+ },
3795
4165
  {
3796
4166
  id: "download",
3797
4167
  icon: "download",
@@ -3800,6 +4170,14 @@ var RtfPlugin = class {
3800
4170
  group: "actions",
3801
4171
  execute: () => instance.download?.()
3802
4172
  },
4173
+ {
4174
+ id: "copy",
4175
+ icon: "copy",
4176
+ label: "Copy Text",
4177
+ type: "button",
4178
+ group: "actions",
4179
+ execute: () => instance.copy?.()
4180
+ },
3803
4181
  {
3804
4182
  id: "print",
3805
4183
  icon: "print",
@@ -3828,7 +4206,24 @@ var RtfPlugin = class {
3828
4206
  ctx.container.style.backgroundColor = "#f1f5f9";
3829
4207
  ctx.container.appendChild(wrapper);
3830
4208
  let scale = 1;
4209
+ let rotation = 0;
3831
4210
  let pageElements = [];
4211
+ const calculateFitScale = () => {
4212
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4213
+ const elW = activeEl.offsetWidth || 850;
4214
+ const elH = activeEl.offsetHeight || 1e3;
4215
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4216
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4217
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4218
+ };
4219
+ const applyTransform = () => {
4220
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4221
+ wrapper.style.transformOrigin = "top center";
4222
+ };
4223
+ setTimeout(() => {
4224
+ scale = calculateFitScale();
4225
+ applyTransform();
4226
+ }, 60);
3832
4227
  try {
3833
4228
  if (typeof RTFJS.loggingEnabled === "function") {
3834
4229
  RTFJS.loggingEnabled(false);
@@ -3844,14 +4239,31 @@ var RtfPlugin = class {
3844
4239
  } catch (err) {
3845
4240
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3846
4241
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3847
- const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3848
- const pre = document.createElement("pre");
3849
- pre.style.whiteSpace = "pre-wrap";
3850
- pre.style.fontFamily = "serif";
3851
- pre.style.color = "#333";
3852
- pre.textContent = clean;
3853
- wrapper.appendChild(pre);
3854
- pageElements = [pre];
4242
+ const rawPages = text.split(/\\page\b/).map((segment) => {
4243
+ return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
4244
+ }).filter((p) => p.length > 0);
4245
+ const pages = rawPages.length > 0 ? rawPages : [text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim()];
4246
+ for (let i = 0; i < pages.length; i++) {
4247
+ const pageCard = document.createElement("div");
4248
+ pageCard.className = "fp-rtf-page-card";
4249
+ pageCard.style.backgroundColor = "#ffffff";
4250
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4251
+ pageCard.style.borderRadius = "4px";
4252
+ pageCard.style.padding = "72px 56px";
4253
+ pageCard.style.width = "816px";
4254
+ pageCard.style.minHeight = "1056px";
4255
+ pageCard.style.maxWidth = "100%";
4256
+ pageCard.style.boxSizing = "border-box";
4257
+ pageCard.style.fontFamily = "serif";
4258
+ pageCard.style.fontSize = "12pt";
4259
+ pageCard.style.lineHeight = "1.6";
4260
+ pageCard.style.color = "#1e293b";
4261
+ pageCard.style.whiteSpace = "pre-wrap";
4262
+ pageCard.style.display = i === 0 ? "block" : "none";
4263
+ pageCard.textContent = pages[i];
4264
+ wrapper.appendChild(pageCard);
4265
+ pageElements.push(pageCard);
4266
+ }
3855
4267
  }
3856
4268
  const totalPages = Math.max(1, pageElements.length);
3857
4269
  let currentPage = 1;
@@ -3905,21 +4317,30 @@ var RtfPlugin = class {
3905
4317
  getCurrentPage: () => currentPage,
3906
4318
  goToPage: (page) => showPage(page),
3907
4319
  zoomIn: () => {
3908
- scale += 0.1;
3909
- wrapper.style.transform = `scale(${scale})`;
4320
+ scale += 0.15;
4321
+ applyTransform();
3910
4322
  },
3911
4323
  zoomOut: () => {
3912
- scale = Math.max(0.2, scale - 0.1);
3913
- wrapper.style.transform = `scale(${scale})`;
4324
+ scale = Math.max(0.2, scale - 0.15);
4325
+ applyTransform();
3914
4326
  },
3915
4327
  getZoom: () => scale,
3916
4328
  setZoom: (level) => {
3917
4329
  scale = level;
3918
- wrapper.style.transform = `scale(${scale})`;
4330
+ applyTransform();
3919
4331
  },
3920
4332
  fitToPage: () => {
3921
- scale = 1;
3922
- wrapper.style.transform = "scale(1)";
4333
+ scale = calculateFitScale();
4334
+ rotation = 0;
4335
+ applyTransform();
4336
+ },
4337
+ rotateCW: () => {
4338
+ rotation = (rotation + 90) % 360;
4339
+ applyTransform();
4340
+ },
4341
+ rotateCCW: () => {
4342
+ rotation = (rotation - 90 + 360) % 360;
4343
+ applyTransform();
3923
4344
  },
3924
4345
  download: () => {
3925
4346
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3930,6 +4351,10 @@ var RtfPlugin = class {
3930
4351
  a.click();
3931
4352
  URL.revokeObjectURL(url);
3932
4353
  },
4354
+ copy: () => {
4355
+ const text = wrapper.textContent || "";
4356
+ navigator.clipboard?.writeText(text);
4357
+ },
3933
4358
  print: () => {
3934
4359
  window.print();
3935
4360
  }
@@ -4004,7 +4429,7 @@ var HtmlPreviewPlugin = class {
4004
4429
  }
4005
4430
  async render(ctx) {
4006
4431
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
4007
- const sanitized = DOMPurify2.sanitize(rawHtml, {
4432
+ const sanitized = DOMPurify6.sanitize(rawHtml, {
4008
4433
  WHOLE_DOCUMENT: true,
4009
4434
  ADD_TAGS: ["style", "link"],
4010
4435
  ADD_ATTR: ["target", "rel"]
@@ -4147,11 +4572,19 @@ var OpenDocumentPlugin = class {
4147
4572
  {
4148
4573
  id: "fit-page",
4149
4574
  icon: "fit-page",
4150
- label: "Fit to Page",
4575
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
4151
4576
  type: "button",
4152
4577
  group: "zoom",
4153
4578
  execute: () => instance.fitToPage?.()
4154
4579
  },
4580
+ {
4581
+ id: "rotate-cw",
4582
+ icon: "rotate-cw",
4583
+ label: "Rotate",
4584
+ type: "button",
4585
+ group: "view",
4586
+ execute: () => instance.rotateCW?.()
4587
+ },
4155
4588
  {
4156
4589
  id: "download",
4157
4590
  icon: "download",
@@ -4215,6 +4648,22 @@ var OpenDocumentPlugin = class {
4215
4648
  container.appendChild(wrapper);
4216
4649
  ctx.container.appendChild(container);
4217
4650
  let scale = 1;
4651
+ let rotation = 0;
4652
+ const calculateFitScale = () => {
4653
+ const elW = wrapper.offsetWidth || 850;
4654
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4655
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4656
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4657
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4658
+ };
4659
+ const applyTransform = () => {
4660
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4661
+ wrapper.style.transformOrigin = "top center";
4662
+ };
4663
+ setTimeout(() => {
4664
+ scale = calculateFitScale();
4665
+ applyTransform();
4666
+ }, 60);
4218
4667
  let currentPage = 1;
4219
4668
  let totalPages = 1;
4220
4669
  let slides = [];
@@ -4246,14 +4695,97 @@ var OpenDocumentPlugin = class {
4246
4695
  wrapper.textContent = contentDoc.documentElement.textContent || "Formula content";
4247
4696
  }
4248
4697
  } else {
4249
- wrapper.style.maxWidth = "850px";
4250
- wrapper.style.padding = "48px";
4698
+ wrapper.style.maxWidth = "none";
4699
+ wrapper.style.padding = "0";
4251
4700
  wrapper.style.minHeight = "100%";
4701
+ wrapper.style.backgroundColor = "transparent";
4702
+ wrapper.style.boxShadow = "none";
4703
+ wrapper.style.position = "relative";
4704
+ const dims = this.getPageDimensions(stylesDoc, contentDoc);
4252
4705
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
4253
- wrapper.innerHTML = DOMPurify2.sanitize(bodyHtml, {
4254
- ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
4706
+ const tempDiv = document.createElement("div");
4707
+ tempDiv.style.width = `${dims.width - dims.marginLeft - dims.marginRight}px`;
4708
+ tempDiv.style.position = "absolute";
4709
+ tempDiv.style.visibility = "hidden";
4710
+ tempDiv.innerHTML = DOMPurify6.sanitize(bodyHtml, {
4711
+ ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub", "hr"],
4255
4712
  ADD_ATTR: ["style", "colspan", "rowspan"]
4256
4713
  });
4714
+ document.body.appendChild(tempDiv);
4715
+ const contentHeight = dims.height - dims.marginTop - dims.marginBottom;
4716
+ const pageElements = [[]];
4717
+ let currentHeight = 0;
4718
+ let currentPageIdx = 0;
4719
+ Array.from(tempDiv.children).forEach((child) => {
4720
+ const el = child;
4721
+ const style = el.getAttribute("style") || "";
4722
+ const isBreakBefore = style.includes("page-break-before: always");
4723
+ const isBreakAfter = style.includes("page-break-after: always");
4724
+ const isSoftBreak = el.classList.contains("odf-page-break");
4725
+ if (isBreakBefore) {
4726
+ if (pageElements[currentPageIdx].length > 0) {
4727
+ currentPageIdx++;
4728
+ pageElements.push([]);
4729
+ currentHeight = 0;
4730
+ }
4731
+ }
4732
+ const h = el.offsetHeight || 0;
4733
+ if (currentHeight + h > contentHeight && pageElements[currentPageIdx].length > 0 && !isSoftBreak) {
4734
+ currentPageIdx++;
4735
+ pageElements.push([]);
4736
+ currentHeight = 0;
4737
+ }
4738
+ if (!isSoftBreak) {
4739
+ pageElements[currentPageIdx].push(el.cloneNode(true));
4740
+ currentHeight += h;
4741
+ }
4742
+ if (isBreakAfter || isSoftBreak) {
4743
+ currentPageIdx++;
4744
+ pageElements.push([]);
4745
+ currentHeight = 0;
4746
+ }
4747
+ });
4748
+ document.body.removeChild(tempDiv);
4749
+ if (pageElements.length > 1 && pageElements[pageElements.length - 1].length === 0) {
4750
+ pageElements.pop();
4751
+ }
4752
+ totalPages = Math.max(1, pageElements.length);
4753
+ pageElements.forEach((elements, idx) => {
4754
+ const page = document.createElement("div");
4755
+ page.className = `fp-odt-page fp-odt-page-${idx + 1}`;
4756
+ page.style.width = `${dims.width}px`;
4757
+ page.style.minHeight = `${dims.height}px`;
4758
+ page.style.padding = `${dims.marginTop}px ${dims.marginRight}px ${dims.marginBottom}px ${dims.marginLeft}px`;
4759
+ page.style.margin = "0 auto";
4760
+ page.style.backgroundColor = "#ffffff";
4761
+ page.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
4762
+ page.style.borderRadius = "4px";
4763
+ page.style.boxSizing = "border-box";
4764
+ page.style.display = idx === 0 ? "block" : "none";
4765
+ page.style.position = "absolute";
4766
+ page.style.top = "0";
4767
+ page.style.left = "50%";
4768
+ page.style.transform = "translateX(-50%)";
4769
+ elements.forEach((el) => page.appendChild(el));
4770
+ wrapper.appendChild(page);
4771
+ slides.push(page);
4772
+ });
4773
+ const pageIndicator = document.createElement("div");
4774
+ pageIndicator.className = "fp-odt-page-indicator";
4775
+ pageIndicator.style.position = "sticky";
4776
+ pageIndicator.style.bottom = "16px";
4777
+ pageIndicator.style.left = "50%";
4778
+ pageIndicator.style.transform = "translateX(-50%)";
4779
+ pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
4780
+ pageIndicator.style.color = "#fff";
4781
+ pageIndicator.style.padding = "6px 12px";
4782
+ pageIndicator.style.borderRadius = "16px";
4783
+ pageIndicator.style.fontSize = "12px";
4784
+ pageIndicator.style.zIndex = "100";
4785
+ pageIndicator.style.display = "inline-block";
4786
+ pageIndicator.style.width = "fit-content";
4787
+ pageIndicator.textContent = `Page 1 of ${totalPages}`;
4788
+ container.appendChild(pageIndicator);
4257
4789
  }
4258
4790
  const cleanup = () => {
4259
4791
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
@@ -4262,32 +4794,45 @@ var OpenDocumentPlugin = class {
4262
4794
  };
4263
4795
  ctx.signal.addEventListener("abort", cleanup);
4264
4796
  const goToPage = (page) => {
4265
- if (!isPresentation || page < 1 || page > totalPages) return;
4797
+ if (page < 1 || page > totalPages) return;
4266
4798
  currentPage = page;
4267
4799
  slides.forEach((s, idx) => {
4268
4800
  s.style.display = idx === page - 1 ? "block" : "none";
4269
4801
  });
4802
+ const indicator = container.querySelector(".fp-odt-page-indicator");
4803
+ if (indicator) {
4804
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4805
+ }
4270
4806
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4271
4807
  };
4272
4808
  return {
4273
4809
  destroy: cleanup,
4274
4810
  isPresentation,
4275
4811
  zoomIn: () => {
4276
- scale += 0.1;
4277
- wrapper.style.transform = `scale(${scale})`;
4812
+ scale += 0.15;
4813
+ applyTransform();
4278
4814
  },
4279
4815
  zoomOut: () => {
4280
- scale = Math.max(0.2, scale - 0.1);
4281
- wrapper.style.transform = `scale(${scale})`;
4816
+ scale = Math.max(0.2, scale - 0.15);
4817
+ applyTransform();
4282
4818
  },
4283
4819
  getZoom: () => scale,
4284
4820
  setZoom: (level) => {
4285
4821
  scale = level;
4286
- wrapper.style.transform = `scale(${scale})`;
4822
+ applyTransform();
4287
4823
  },
4288
4824
  fitToPage: () => {
4289
- scale = 1;
4290
- wrapper.style.transform = "scale(1)";
4825
+ scale = calculateFitScale();
4826
+ rotation = 0;
4827
+ applyTransform();
4828
+ },
4829
+ rotateCW: () => {
4830
+ rotation = (rotation + 90) % 360;
4831
+ applyTransform();
4832
+ },
4833
+ rotateCCW: () => {
4834
+ rotation = (rotation - 90 + 360) % 360;
4835
+ applyTransform();
4291
4836
  },
4292
4837
  goToPage,
4293
4838
  getPageCount: () => totalPages,
@@ -4326,6 +4871,43 @@ var OpenDocumentPlugin = class {
4326
4871
  }
4327
4872
  };
4328
4873
  }
4874
+ getPageDimensions(stylesDoc, contentDoc) {
4875
+ let width = 850;
4876
+ let height = 1123;
4877
+ let marginTop = 48;
4878
+ let marginBottom = 48;
4879
+ let marginLeft = 48;
4880
+ let marginRight = 48;
4881
+ const parseUnit = (val) => {
4882
+ if (!val) return null;
4883
+ if (val.endsWith("cm")) return parseFloat(val) * 37.8;
4884
+ if (val.endsWith("mm")) return parseFloat(val) * 3.78;
4885
+ if (val.endsWith("in")) return parseFloat(val) * 96;
4886
+ if (val.endsWith("pt")) return parseFloat(val) * 1.33;
4887
+ if (val.endsWith("px")) return parseFloat(val);
4888
+ return parseFloat(val);
4889
+ };
4890
+ const docs = [stylesDoc, contentDoc].filter(Boolean);
4891
+ for (const doc of docs) {
4892
+ const pageLayout = doc.querySelector("page-layout-properties, [page-width]");
4893
+ if (pageLayout) {
4894
+ const w = parseUnit(pageLayout.getAttribute("fo:page-width") || pageLayout.getAttribute("page-width"));
4895
+ const h = parseUnit(pageLayout.getAttribute("fo:page-height") || pageLayout.getAttribute("page-height"));
4896
+ const mt = parseUnit(pageLayout.getAttribute("fo:margin-top") || pageLayout.getAttribute("margin-top"));
4897
+ const mb = parseUnit(pageLayout.getAttribute("fo:margin-bottom") || pageLayout.getAttribute("margin-bottom"));
4898
+ const ml = parseUnit(pageLayout.getAttribute("fo:margin-left") || pageLayout.getAttribute("margin-left"));
4899
+ const mr = parseUnit(pageLayout.getAttribute("fo:margin-right") || pageLayout.getAttribute("margin-right"));
4900
+ if (w !== null) width = w;
4901
+ if (h !== null) height = h;
4902
+ if (mt !== null) marginTop = mt;
4903
+ if (mb !== null) marginBottom = mb;
4904
+ if (ml !== null) marginLeft = ml;
4905
+ if (mr !== null) marginRight = mr;
4906
+ break;
4907
+ }
4908
+ }
4909
+ return { width, height, marginTop, marginBottom, marginLeft, marginRight };
4910
+ }
4329
4911
  extractStyles(stylesDoc, contentDoc) {
4330
4912
  const map = /* @__PURE__ */ new Map();
4331
4913
  const styleNodes = [];
@@ -4348,14 +4930,21 @@ var OpenDocumentPlugin = class {
4348
4930
  if (color) css += `color: ${color}; `;
4349
4931
  if (size) css += `font-size: ${size}; `;
4350
4932
  }
4351
- const paraProp = node.querySelector("paragraph-properties, [text-align]");
4933
+ let paraProp = node.querySelector("paragraph-properties, [text-align]");
4934
+ if (!paraProp) {
4935
+ paraProp = Array.from(node.children).find((c) => c.tagName.includes("paragraph-properties")) || null;
4936
+ }
4352
4937
  if (paraProp) {
4353
4938
  const align = paraProp.getAttribute("fo:text-align") || paraProp.getAttribute("text-align");
4354
4939
  const mt = paraProp.getAttribute("fo:margin-top") || paraProp.getAttribute("margin-top");
4355
4940
  const mb = paraProp.getAttribute("fo:margin-bottom") || paraProp.getAttribute("margin-bottom");
4941
+ const breakBefore = paraProp.getAttribute("fo:break-before") || paraProp.getAttribute("break-before");
4942
+ const breakAfter = paraProp.getAttribute("fo:break-after") || paraProp.getAttribute("break-after");
4356
4943
  if (align) css += `text-align: ${align}; `;
4357
4944
  if (mt) css += `margin-top: ${mt}; `;
4358
4945
  if (mb) css += `margin-bottom: ${mb}; `;
4946
+ if (breakBefore === "page") css += "page-break-before: always; ";
4947
+ if (breakAfter === "page") css += "page-break-after: always; ";
4359
4948
  }
4360
4949
  if (css) map.set(name, css);
4361
4950
  }
@@ -4440,6 +5029,8 @@ var OpenDocumentPlugin = class {
4440
5029
  result += "&emsp;";
4441
5030
  } else if (tag === "line-break") {
4442
5031
  result += "<br/>";
5032
+ } else if (tag === "soft-page-break") {
5033
+ result += '<hr class="odf-page-break" style="page-break-after: always; border: none; margin: 0; padding: 0; height: 0;" />';
4443
5034
  } else {
4444
5035
  result += el.textContent || "";
4445
5036
  }
@@ -4557,6 +5148,14 @@ var DocPlugin = class {
4557
5148
  group: "zoom",
4558
5149
  execute: () => instance.fitToPage?.()
4559
5150
  },
5151
+ {
5152
+ id: "rotate-cw",
5153
+ icon: "rotate-cw",
5154
+ label: "Rotate",
5155
+ type: "button",
5156
+ group: "view",
5157
+ execute: () => instance.rotateCW?.()
5158
+ },
4560
5159
  {
4561
5160
  id: "copy",
4562
5161
  icon: "copy",
@@ -4592,20 +5191,9 @@ var DocPlugin = class {
4592
5191
  container.style.overflow = "auto";
4593
5192
  container.style.padding = "32px 16px";
4594
5193
  container.style.backgroundColor = "#f1f5f9";
4595
- const wrapper = document.createElement("div");
4596
- wrapper.className = "fp-doc-wrapper";
4597
- wrapper.style.maxWidth = "850px";
4598
- wrapper.style.margin = "0 auto";
4599
- wrapper.style.backgroundColor = "#ffffff";
4600
- wrapper.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4601
- wrapper.style.borderRadius = "4px";
4602
- wrapper.style.padding = "56px 48px";
4603
- wrapper.style.minHeight = "100%";
4604
- wrapper.style.transformOrigin = "top center";
4605
- wrapper.style.transition = "transform 0.2s ease";
4606
- wrapper.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4607
- wrapper.style.color = "#1e293b";
4608
- container.appendChild(wrapper);
5194
+ container.style.display = "flex";
5195
+ container.style.justifyContent = "center";
5196
+ container.style.alignItems = "flex-start";
4609
5197
  ctx.container.appendChild(container);
4610
5198
  let scale = 1;
4611
5199
  let extractedRawText = "";
@@ -4632,21 +5220,27 @@ var DocPlugin = class {
4632
5220
  const rawPages = this.splitIntoPages(extractedRawText);
4633
5221
  const totalPages = Math.max(1, rawPages.length);
4634
5222
  let currentPage = 1;
4635
- wrapper.innerHTML = "";
4636
5223
  const pageCards = [];
4637
5224
  for (let i = 0; i < totalPages; i++) {
4638
5225
  const pageCard = document.createElement("div");
4639
5226
  pageCard.className = "fp-doc-page-card";
5227
+ pageCard.style.width = "816px";
5228
+ pageCard.style.height = "1056px";
4640
5229
  pageCard.style.backgroundColor = "#ffffff";
4641
5230
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4642
5231
  pageCard.style.borderRadius = "4px";
4643
- pageCard.style.padding = "56px 48px";
4644
- pageCard.style.minHeight = "100%";
5232
+ pageCard.style.padding = "96px 72px";
5233
+ pageCard.style.boxSizing = "border-box";
5234
+ pageCard.style.overflow = "hidden";
4645
5235
  pageCard.style.display = i === 0 ? "block" : "none";
5236
+ pageCard.style.transformOrigin = "top center";
5237
+ pageCard.style.transition = "transform 0.2s ease";
5238
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5239
+ pageCard.style.color = "#1e293b";
4646
5240
  if (isFallback && i === 0) {
4647
5241
  pageCard.innerHTML = `
4648
5242
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4649
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
5243
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify6.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4650
5244
  <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4651
5245
  </div>
4652
5246
  ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
@@ -4654,15 +5248,17 @@ var DocPlugin = class {
4654
5248
  } else {
4655
5249
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4656
5250
  }
4657
- wrapper.appendChild(pageCard);
5251
+ container.appendChild(pageCard);
4658
5252
  pageCards.push(pageCard);
4659
5253
  }
4660
5254
  let indicator = null;
4661
5255
  if (totalPages > 1) {
4662
5256
  indicator = document.createElement("div");
4663
5257
  indicator.className = "fp-doc-page-indicator";
4664
- indicator.style.position = "sticky";
5258
+ indicator.style.position = "fixed";
4665
5259
  indicator.style.bottom = "16px";
5260
+ indicator.style.left = "50%";
5261
+ indicator.style.transform = "translateX(-50%)";
4666
5262
  indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4667
5263
  indicator.style.backdropFilter = "blur(8px)";
4668
5264
  indicator.style.color = "#f8fafc";
@@ -4676,17 +5272,25 @@ var DocPlugin = class {
4676
5272
  indicator.style.userSelect = "none";
4677
5273
  indicator.style.pointerEvents = "none";
4678
5274
  indicator.style.textAlign = "center";
4679
- indicator.style.width = "fit-content";
4680
- indicator.style.margin = "16px auto 0";
4681
5275
  container.appendChild(indicator);
4682
5276
  }
5277
+ let rotation = 0;
5278
+ const applyTransform = () => {
5279
+ const activeCard = pageCards[currentPage - 1];
5280
+ if (activeCard) {
5281
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5282
+ }
5283
+ };
4683
5284
  const showPage = (pageNum) => {
4684
5285
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4685
- if (totalPages > 1) {
4686
- pageCards.forEach((card, idx) => {
4687
- card.style.display = idx + 1 === currentPage ? "block" : "none";
4688
- });
4689
- }
5286
+ pageCards.forEach((card, idx) => {
5287
+ if (idx + 1 === currentPage) {
5288
+ card.style.display = "block";
5289
+ card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5290
+ } else {
5291
+ card.style.display = "none";
5292
+ }
5293
+ });
4690
5294
  if (indicator) {
4691
5295
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4692
5296
  }
@@ -4695,6 +5299,17 @@ var DocPlugin = class {
4695
5299
  if (totalPages > 1) {
4696
5300
  showPage(1);
4697
5301
  }
5302
+ const calculateFitScale = () => {
5303
+ const elW = 816;
5304
+ const elH = 1056;
5305
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
5306
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
5307
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
5308
+ };
5309
+ setTimeout(() => {
5310
+ scale = calculateFitScale();
5311
+ applyTransform();
5312
+ }, 60);
4698
5313
  const cleanup = () => {
4699
5314
  indicator?.remove();
4700
5315
  container.remove();
@@ -4707,21 +5322,30 @@ var DocPlugin = class {
4707
5322
  getCurrentPage: () => currentPage,
4708
5323
  goToPage: (page) => showPage(page),
4709
5324
  zoomIn: () => {
4710
- scale += 0.1;
4711
- wrapper.style.transform = `scale(${scale})`;
5325
+ scale += 0.15;
5326
+ applyTransform();
4712
5327
  },
4713
5328
  zoomOut: () => {
4714
- scale = Math.max(0.2, scale - 0.1);
4715
- wrapper.style.transform = `scale(${scale})`;
5329
+ scale = Math.max(0.2, scale - 0.15);
5330
+ applyTransform();
4716
5331
  },
4717
5332
  getZoom: () => scale,
4718
5333
  setZoom: (level) => {
4719
5334
  scale = level;
4720
- wrapper.style.transform = `scale(${scale})`;
5335
+ applyTransform();
4721
5336
  },
4722
5337
  fitToPage: () => {
4723
- scale = 1;
4724
- wrapper.style.transform = "scale(1)";
5338
+ scale = calculateFitScale();
5339
+ rotation = 0;
5340
+ applyTransform();
5341
+ },
5342
+ rotateCW: () => {
5343
+ rotation = (rotation + 90) % 360;
5344
+ applyTransform();
5345
+ },
5346
+ rotateCCW: () => {
5347
+ rotation = (rotation - 90 + 360) % 360;
5348
+ applyTransform();
4725
5349
  },
4726
5350
  copy: () => {
4727
5351
  navigator.clipboard.writeText(extractedRawText);
@@ -4839,8 +5463,28 @@ var DocPlugin = class {
4839
5463
  }
4840
5464
  splitIntoPages(text) {
4841
5465
  if (!text) return [""];
4842
- 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);
4843
- return parts.length > 0 ? parts : [text];
5466
+ 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);
5467
+ if (explicitParts.length === 0) explicitParts.push(text);
5468
+ const maxLinesPerPage = 48;
5469
+ const finalPages = [];
5470
+ for (const part of explicitParts) {
5471
+ const lines = part.split(/\r?\n/);
5472
+ let currentLines = [];
5473
+ let count = 0;
5474
+ for (const line of lines) {
5475
+ currentLines.push(line);
5476
+ count++;
5477
+ if (count >= maxLinesPerPage) {
5478
+ finalPages.push(currentLines.join("\n"));
5479
+ currentLines = [];
5480
+ count = 0;
5481
+ }
5482
+ }
5483
+ if (currentLines.length > 0) {
5484
+ finalPages.push(currentLines.join("\n"));
5485
+ }
5486
+ }
5487
+ return finalPages.length > 0 ? finalPages : [text];
4844
5488
  }
4845
5489
  /**
4846
5490
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
@@ -4849,17 +5493,65 @@ var DocPlugin = class {
4849
5493
  const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4850
5494
  let html = "";
4851
5495
  let inList = false;
4852
- for (const rawLine of lines) {
4853
- const line = rawLine.trim();
5496
+ let tableLines = [];
5497
+ const flushTable = () => {
5498
+ if (tableLines.length > 0) {
5499
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 12px;">';
5500
+ for (const tLine of tableLines) {
5501
+ html += "<tr>";
5502
+ const cols = tLine.split(" ");
5503
+ for (const col of cols) {
5504
+ html += `<td style="border: 1px solid #cbd5e1; padding: 6px 8px;">${DOMPurify6.sanitize(col.trim())}</td>`;
5505
+ }
5506
+ html += "</tr>";
5507
+ }
5508
+ html += "</table>";
5509
+ tableLines = [];
5510
+ }
5511
+ };
5512
+ let i = 0;
5513
+ while (i < lines.length) {
5514
+ let line = lines[i];
5515
+ let tabCount = (line.match(/\t/g) || []).length;
5516
+ if (tabCount > 0) {
5517
+ let consecutiveTableLines = 1;
5518
+ let j = i + 1;
5519
+ while (j < lines.length) {
5520
+ const nextTabCount = (lines[j].match(/\t/g) || []).length;
5521
+ if (nextTabCount === tabCount) {
5522
+ consecutiveTableLines++;
5523
+ j++;
5524
+ } else {
5525
+ break;
5526
+ }
5527
+ }
5528
+ if (consecutiveTableLines >= 3) {
5529
+ if (inList) {
5530
+ html += "</ul>";
5531
+ inList = false;
5532
+ }
5533
+ tableLines = lines.slice(i, j);
5534
+ flushTable();
5535
+ i = j;
5536
+ continue;
5537
+ }
5538
+ }
5539
+ line = line.trim();
4854
5540
  if (!line) {
4855
5541
  if (inList) {
4856
5542
  html += "</ul>";
4857
5543
  inList = false;
4858
5544
  }
5545
+ html += '<div style="height: 1.15em;"></div>';
5546
+ i++;
4859
5547
  continue;
4860
5548
  }
4861
- if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) continue;
4862
- if (line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman") && line.length < 30) {
5549
+ if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) {
5550
+ i++;
5551
+ continue;
5552
+ }
5553
+ if ((line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman")) && line.length < 30) {
5554
+ i++;
4863
5555
  continue;
4864
5556
  }
4865
5557
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
@@ -4867,24 +5559,26 @@ var DocPlugin = class {
4867
5559
  html += "</ul>";
4868
5560
  inList = false;
4869
5561
  }
4870
- html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify2.sanitize(line)}</h2>`;
5562
+ html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line)}</h2>`;
4871
5563
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4872
5564
  if (!inList) {
4873
5565
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4874
5566
  inList = true;
4875
5567
  }
4876
5568
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4877
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2.sanitize(bulletText)}</li>`;
5569
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText)}</li>`;
4878
5570
  } else {
4879
5571
  if (inList) {
4880
5572
  html += "</ul>";
4881
5573
  inList = false;
4882
5574
  }
4883
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2.sanitize(line)}</p>`;
5575
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
4884
5576
  }
5577
+ i++;
4885
5578
  }
4886
5579
  if (inList) html += "</ul>";
4887
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2.sanitize(filename)})</p>`;
5580
+ flushTable();
5581
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6.sanitize(filename)})</p>`;
4888
5582
  }
4889
5583
  };
4890
5584
  function docPlugin() {
@@ -4961,6 +5655,14 @@ var PptPlugin = class {
4961
5655
  group: "zoom",
4962
5656
  execute: () => instance.fitToPage?.()
4963
5657
  },
5658
+ {
5659
+ id: "rotate-cw",
5660
+ icon: "rotate-cw",
5661
+ label: "Rotate",
5662
+ type: "button",
5663
+ group: "view",
5664
+ execute: () => instance.rotateCW?.()
5665
+ },
4964
5666
  {
4965
5667
  id: "download",
4966
5668
  icon: "download",
@@ -4994,22 +5696,36 @@ var PptPlugin = class {
4994
5696
  const slideCard = document.createElement("div");
4995
5697
  slideCard.className = "fp-ppt-slide-card";
4996
5698
  slideCard.style.width = "960px";
4997
- slideCard.style.maxWidth = "92%";
5699
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5700
+ slideCard.style.maxHeight = "calc(100% - 48px)";
4998
5701
  slideCard.style.aspectRatio = "16 / 9";
4999
5702
  slideCard.style.backgroundColor = "#ffffff";
5000
5703
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
5001
5704
  slideCard.style.borderRadius = "8px";
5002
5705
  slideCard.style.boxSizing = "border-box";
5003
5706
  slideCard.style.position = "relative";
5004
- slideCard.style.overflow = "hidden";
5005
- slideCard.style.transformOrigin = "center center";
5006
5707
  slideCard.style.transition = "transform 0.2s ease";
5708
+ slideCard.style.transformOrigin = "center center";
5709
+ slideCard.style.flexShrink = "0";
5007
5710
  container.appendChild(slideCard);
5008
5711
  ctx.container.appendChild(container);
5009
5712
  let scale = 1;
5713
+ let rotation = 0;
5010
5714
  let currentSlide = 1;
5011
5715
  let slides = [];
5012
5716
  const createdBlobUrls = [];
5717
+ const calculateFitScale = () => {
5718
+ const availW = Math.max(200, container.clientWidth - 48);
5719
+ const availH = Math.max(200, container.clientHeight - 72);
5720
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5721
+ };
5722
+ const applyTransform = () => {
5723
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5724
+ };
5725
+ setTimeout(() => {
5726
+ scale = calculateFitScale();
5727
+ applyTransform();
5728
+ }, 60);
5013
5729
  try {
5014
5730
  const cfbf = new CfbfReader(ctx.buffer);
5015
5731
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -5040,7 +5756,7 @@ var PptPlugin = class {
5040
5756
  if (s.pictureUrl) {
5041
5757
  contentHtml = `
5042
5758
  <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
5043
- <img src="${s.pictureUrl}" alt="${DOMPurify2.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
5759
+ <img src="${s.pictureUrl}" alt="${DOMPurify6.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
5044
5760
  </div>
5045
5761
  `;
5046
5762
  } else if (s.tableColumns.length > 0) {
@@ -5050,7 +5766,7 @@ var PptPlugin = class {
5050
5766
  <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
5051
5767
  <thead>
5052
5768
  <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
5053
- ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2.sanitize(c)}</th>`).join("")}
5769
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify6.sanitize(c)}</th>`).join("")}
5054
5770
  </tr>
5055
5771
  </thead>
5056
5772
  <tbody>
@@ -5066,7 +5782,7 @@ var PptPlugin = class {
5066
5782
  } else {
5067
5783
  const pTags = s.paragraphs.map((p) => {
5068
5784
  const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
5069
- return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2.sanitize(l)}</p>`).join("");
5785
+ return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify6.sanitize(l)}</p>`).join("");
5070
5786
  }).join("");
5071
5787
  contentHtml = `
5072
5788
  <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
@@ -5079,11 +5795,11 @@ var PptPlugin = class {
5079
5795
  <!-- Header Banner matching PowerPoint design -->
5080
5796
  <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;">
5081
5797
  <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
5082
- ${DOMPurify2.sanitize(s.title)}
5798
+ ${DOMPurify6.sanitize(s.title)}
5083
5799
  </h1>
5084
5800
  ${s.subtitle ? `
5085
5801
  <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);">
5086
- ${DOMPurify2.sanitize(s.subtitle)}
5802
+ ${DOMPurify6.sanitize(s.subtitle)}
5087
5803
  </span>
5088
5804
  ` : `
5089
5805
  <span style="font-size: 12px; color: #365314; font-weight: 600;">
@@ -5110,21 +5826,30 @@ var PptPlugin = class {
5110
5826
  return {
5111
5827
  destroy: cleanup,
5112
5828
  zoomIn: () => {
5113
- scale += 0.1;
5114
- slideCard.style.transform = `scale(${scale})`;
5829
+ scale += 0.15;
5830
+ applyTransform();
5115
5831
  },
5116
5832
  zoomOut: () => {
5117
- scale = Math.max(0.3, scale - 0.1);
5118
- slideCard.style.transform = `scale(${scale})`;
5833
+ scale = Math.max(0.2, scale - 0.15);
5834
+ applyTransform();
5119
5835
  },
5120
5836
  getZoom: () => scale,
5121
5837
  setZoom: (level) => {
5122
5838
  scale = level;
5123
- slideCard.style.transform = `scale(${scale})`;
5839
+ applyTransform();
5124
5840
  },
5125
5841
  fitToPage: () => {
5126
- scale = 1;
5127
- slideCard.style.transform = "scale(1)";
5842
+ scale = calculateFitScale();
5843
+ rotation = 0;
5844
+ applyTransform();
5845
+ },
5846
+ rotateCW: () => {
5847
+ rotation = (rotation + 90) % 360;
5848
+ applyTransform();
5849
+ },
5850
+ rotateCCW: () => {
5851
+ rotation = (rotation - 90 + 360) % 360;
5852
+ applyTransform();
5128
5853
  },
5129
5854
  goToPage: (page) => {
5130
5855
  if (page >= 1 && page <= totalSlides) {