@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/angular.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Component, ChangeDetectionStrategy, Input, Output, ViewChild, EventEmitter as EventEmitter$1 } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
- import DOMPurify2 from 'dompurify';
3
+ import DOMPurify6 from 'dompurify';
4
4
  import * as pdfjsLib from 'pdfjs-dist';
5
5
  import * as docx from 'docx-preview';
6
6
  import { unzipSync, strFromU8, unzip } from 'fflate';
@@ -405,7 +405,7 @@ async function sourceToArrayBuffer(source, signal) {
405
405
  if (magicMime === "application/zip") {
406
406
  const ooxmlMime = detectOoxmlType(buffer);
407
407
  if (ooxmlMime && ooxmlMime !== "application/zip") {
408
- metadata.mimeType = ooxmlMime;
408
+ metadata.mimeType = metadata.mimeType ?? ooxmlMime;
409
409
  if (ooxmlMime.includes("wordprocessing")) metadata.extension = metadata.extension ?? ".docx";
410
410
  else if (ooxmlMime.includes("spreadsheet")) metadata.extension = metadata.extension ?? ".xlsx";
411
411
  else if (ooxmlMime.includes("presentation")) metadata.extension = metadata.extension ?? ".pptx";
@@ -434,7 +434,7 @@ async function sourceToArrayBuffer(source, signal) {
434
434
  return { buffer, metadata };
435
435
  }
436
436
  function sanitizeSVG(svg) {
437
- return DOMPurify2.sanitize(svg, {
437
+ return DOMPurify6.sanitize(svg, {
438
438
  USE_PROFILES: { svg: true, svgFilters: true }
439
439
  });
440
440
  }
@@ -753,6 +753,8 @@ var FilePreviewViewer = class {
753
753
  keyHandler = null;
754
754
  currentContainer = null;
755
755
  currentOptions = {};
756
+ resizeObserver = null;
757
+ fullscreenHandler = null;
756
758
  /**
757
759
  * Register a preview plugin.
758
760
  */
@@ -825,12 +827,31 @@ var FilePreviewViewer = class {
825
827
  label: "Toggle Fullscreen",
826
828
  type: "button",
827
829
  group: "view",
828
- execute: () => {
829
- if (!document.fullscreenElement) {
830
- this.wrapperEl?.requestFullscreen?.();
831
- } else {
832
- document.exitFullscreen?.();
830
+ execute: async () => {
831
+ try {
832
+ const isNativeFs = !!document.fullscreenElement;
833
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
834
+ if (!isNativeFs && !isCssFs) {
835
+ if (this.wrapperEl?.requestFullscreen) {
836
+ await this.wrapperEl.requestFullscreen().catch(() => {
837
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
838
+ });
839
+ } else {
840
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
841
+ }
842
+ } else {
843
+ if (document.fullscreenElement) {
844
+ await document.exitFullscreen().catch(() => {
845
+ });
846
+ }
847
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
848
+ }
849
+ } catch {
850
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
833
851
  }
852
+ setTimeout(() => {
853
+ this.activeInstance?.fitToPage?.();
854
+ }, 120);
834
855
  }
835
856
  });
836
857
  }
@@ -880,6 +901,15 @@ var FilePreviewViewer = class {
880
901
  window.removeEventListener("keydown", this.keyHandler);
881
902
  this.keyHandler = null;
882
903
  }
904
+ if (this.resizeObserver) {
905
+ this.resizeObserver.disconnect();
906
+ this.resizeObserver = null;
907
+ }
908
+ if (this.fullscreenHandler) {
909
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
910
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
911
+ this.fullscreenHandler = null;
912
+ }
883
913
  this.abort();
884
914
  this.destroyInstance();
885
915
  this.toolbar?.destroy();
@@ -959,6 +989,25 @@ var FilePreviewViewer = class {
959
989
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
960
990
  this.setupKeyboardShortcuts();
961
991
  this.setupDragAndDrop(container, options);
992
+ this.setupResizeAndFullscreenListeners();
993
+ }
994
+ setupResizeAndFullscreenListeners() {
995
+ if (this.fullscreenHandler) return;
996
+ this.fullscreenHandler = () => {
997
+ setTimeout(() => {
998
+ this.activeInstance?.fitToPage?.();
999
+ }, 100);
1000
+ };
1001
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
1002
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
1003
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1004
+ this.resizeObserver = new ResizeObserver(() => {
1005
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1006
+ this.activeInstance.fitToPage?.();
1007
+ }
1008
+ });
1009
+ this.resizeObserver.observe(this.contentEl);
1010
+ }
962
1011
  }
963
1012
  setupKeyboardShortcuts() {
964
1013
  if (this.keyHandler) return;
@@ -1350,7 +1399,8 @@ var PdfPlugin = class {
1350
1399
  container.style.display = "flex";
1351
1400
  container.style.flexDirection = "column";
1352
1401
  container.style.alignItems = "center";
1353
- container.style.padding = "24px 16px";
1402
+ container.style.justifyContent = "flex-start";
1403
+ container.style.padding = "20px 16px";
1354
1404
  container.style.backgroundColor = "#0f172a";
1355
1405
  container.style.boxSizing = "border-box";
1356
1406
  container.style.position = "relative";
@@ -1363,7 +1413,8 @@ var PdfPlugin = class {
1363
1413
  pageCard.style.lineHeight = "0";
1364
1414
  pageCard.style.transition = "transform 0.15s ease";
1365
1415
  pageCard.style.position = "relative";
1366
- const canvas = document.createElement("canvas");
1416
+ pageCard.style.flexShrink = "0";
1417
+ let canvas = document.createElement("canvas");
1367
1418
  pageCard.appendChild(canvas);
1368
1419
  container.appendChild(pageCard);
1369
1420
  const indicator = document.createElement("div");
@@ -1405,14 +1456,22 @@ var PdfPlugin = class {
1405
1456
  }
1406
1457
  currentRenderTask = null;
1407
1458
  }
1459
+ const newCanvas = document.createElement("canvas");
1460
+ pageCard.replaceChild(newCanvas, canvas);
1461
+ canvas = newCanvas;
1408
1462
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1409
1463
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1410
1464
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1411
1465
  const page = await pdfDoc.getPage(currentPage);
1412
1466
  const containerWidth = container.clientWidth || 900;
1467
+ const containerHeight = container.clientHeight || 700;
1413
1468
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1414
- const baseScale = Math.min((containerWidth - 64) / unscaledVp.width, 1.6);
1415
- const effectiveScale = (baseScale > 0 ? baseScale : 1) * zoomScale;
1469
+ const availWidth = Math.max(100, containerWidth - 48);
1470
+ const availHeight = Math.max(100, containerHeight - 88);
1471
+ const scaleW = availWidth / unscaledVp.width;
1472
+ const scaleH = availHeight / unscaledVp.height;
1473
+ const fitScale = Math.min(scaleW, scaleH);
1474
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1416
1475
  const pixelRatio = window.devicePixelRatio || 1;
1417
1476
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
1418
1477
  canvas.width = Math.floor(viewport.width * pixelRatio);
@@ -1437,7 +1496,19 @@ var PdfPlugin = class {
1437
1496
  }
1438
1497
  };
1439
1498
  await renderPage(1);
1499
+ let resizeTimer = null;
1500
+ const resizeObserver = new ResizeObserver(() => {
1501
+ if (resizeTimer) clearTimeout(resizeTimer);
1502
+ resizeTimer = setTimeout(() => {
1503
+ if (Math.abs(zoomScale - 1) < 0.05) {
1504
+ renderPage(currentPage);
1505
+ }
1506
+ }, 150);
1507
+ });
1508
+ resizeObserver.observe(container);
1440
1509
  const cleanup = () => {
1510
+ resizeObserver.disconnect();
1511
+ if (resizeTimer) clearTimeout(resizeTimer);
1441
1512
  if (currentRenderTask) {
1442
1513
  try {
1443
1514
  currentRenderTask.cancel();
@@ -1469,6 +1540,7 @@ var PdfPlugin = class {
1469
1540
  },
1470
1541
  fitToPage: () => {
1471
1542
  zoomScale = 1;
1543
+ rotation = 0;
1472
1544
  renderPage(currentPage);
1473
1545
  },
1474
1546
  rotateCW: () => {
@@ -1919,6 +1991,14 @@ var DocxPlugin = class {
1919
1991
  group: "zoom",
1920
1992
  execute: () => instance.fitToPage?.()
1921
1993
  },
1994
+ {
1995
+ id: "rotate-cw",
1996
+ icon: "rotate-cw",
1997
+ label: "Rotate",
1998
+ type: "button",
1999
+ group: "view",
2000
+ execute: () => instance.rotateCW?.()
2001
+ },
1922
2002
  {
1923
2003
  id: "download",
1924
2004
  icon: "download",
@@ -2055,6 +2135,26 @@ var DocxPlugin = class {
2055
2135
  if (totalPages > 1) {
2056
2136
  showPage(1);
2057
2137
  }
2138
+ scale = 1;
2139
+ let rotation = 0;
2140
+ const calculateFitScale = () => {
2141
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2142
+ const elW = activeEl.offsetWidth || 816;
2143
+ const elH = activeEl.offsetHeight || 1056;
2144
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2145
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2146
+ const sW = availW / elW;
2147
+ const sH = availH / elH;
2148
+ return Math.min(1.1, Math.min(sW, sH));
2149
+ };
2150
+ const applyTransform = () => {
2151
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2152
+ wrapper.style.transformOrigin = "top center";
2153
+ };
2154
+ setTimeout(() => {
2155
+ scale = calculateFitScale();
2156
+ applyTransform();
2157
+ }, 60);
2058
2158
  const cleanup = () => {
2059
2159
  indicator?.remove();
2060
2160
  for (const url of createdBlobUrls) {
@@ -2073,21 +2173,30 @@ var DocxPlugin = class {
2073
2173
  showPage(page);
2074
2174
  },
2075
2175
  zoomIn: () => {
2076
- scale += 0.1;
2077
- wrapper.style.transform = `scale(${scale})`;
2176
+ scale += 0.15;
2177
+ applyTransform();
2078
2178
  },
2079
2179
  zoomOut: () => {
2080
- scale = Math.max(0.2, scale - 0.1);
2081
- wrapper.style.transform = `scale(${scale})`;
2180
+ scale = Math.max(0.2, scale - 0.15);
2181
+ applyTransform();
2082
2182
  },
2083
2183
  getZoom: () => scale,
2084
2184
  setZoom: (level) => {
2085
2185
  scale = level;
2086
- wrapper.style.transform = `scale(${scale})`;
2186
+ applyTransform();
2087
2187
  },
2088
2188
  fitToPage: () => {
2089
- scale = 1;
2090
- wrapper.style.transform = "scale(1)";
2189
+ scale = calculateFitScale();
2190
+ rotation = 0;
2191
+ applyTransform();
2192
+ },
2193
+ rotateCW: () => {
2194
+ rotation = (rotation + 90) % 360;
2195
+ applyTransform();
2196
+ },
2197
+ rotateCCW: () => {
2198
+ rotation = (rotation - 90 + 360) % 360;
2199
+ applyTransform();
2091
2200
  },
2092
2201
  download: () => {
2093
2202
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2147,90 +2256,149 @@ var DocxPlugin = class {
2147
2256
  console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
2148
2257
  }
2149
2258
  }
2150
- const card = document.createElement("div");
2151
- card.className = "fp-docx-page-card";
2152
- card.style.backgroundColor = "#ffffff";
2153
- card.style.borderRadius = "6px";
2154
- card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2155
- card.style.padding = "48px 56px";
2156
- card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2157
- card.style.color = "#1e293b";
2158
- card.style.lineHeight = "1.6";
2259
+ const sectPrs = Array.from(doc.getElementsByTagNameNS("*", "sectPr"));
2260
+ const lastSectPr = sectPrs[sectPrs.length - 1];
2261
+ let defaultW = 12240;
2262
+ let defaultH = 15840;
2263
+ let margins = { top: 1440, right: 1440, bottom: 1440, left: 1440 };
2264
+ if (lastSectPr) {
2265
+ const pgSz = lastSectPr.getElementsByTagNameNS("*", "pgSz")[0];
2266
+ if (pgSz) {
2267
+ defaultW = parseInt(pgSz.getAttribute("w:w") || pgSz.getAttribute("w") || "12240", 10);
2268
+ defaultH = parseInt(pgSz.getAttribute("w:h") || pgSz.getAttribute("h") || "15840", 10);
2269
+ }
2270
+ const pgMar = lastSectPr.getElementsByTagNameNS("*", "pgMar")[0];
2271
+ if (pgMar) {
2272
+ margins.top = parseInt(pgMar.getAttribute("w:top") || pgMar.getAttribute("top") || "1440", 10);
2273
+ margins.bottom = parseInt(pgMar.getAttribute("w:bottom") || pgMar.getAttribute("bottom") || "1440", 10);
2274
+ margins.left = parseInt(pgMar.getAttribute("w:left") || pgMar.getAttribute("left") || "1440", 10);
2275
+ margins.right = parseInt(pgMar.getAttribute("w:right") || pgMar.getAttribute("right") || "1440", 10);
2276
+ }
2277
+ }
2278
+ const createPageCard = () => {
2279
+ const card = document.createElement("div");
2280
+ card.className = "fp-docx-page-card";
2281
+ card.style.backgroundColor = "#ffffff";
2282
+ card.style.borderRadius = "4px";
2283
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2284
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2285
+ card.style.color = "#1e293b";
2286
+ card.style.lineHeight = "1.6";
2287
+ card.style.boxSizing = "border-box";
2288
+ card.style.overflow = "hidden";
2289
+ card.style.position = "relative";
2290
+ card.style.marginBottom = "24px";
2291
+ card.style.width = `${defaultW / 15}px`;
2292
+ card.style.height = `${defaultH / 15}px`;
2293
+ card.style.padding = `${margins.top / 15}px ${margins.right / 15}px ${margins.bottom / 15}px ${margins.left / 15}px`;
2294
+ return card;
2295
+ };
2296
+ let currentCard = createPageCard();
2297
+ let currentHtml = "";
2298
+ const pages = [{ card: currentCard, html: "" }];
2299
+ const flushHtml = () => {
2300
+ pages[pages.length - 1].html += currentHtml;
2301
+ currentHtml = "";
2302
+ };
2303
+ const newPage = () => {
2304
+ flushHtml();
2305
+ currentCard = createPageCard();
2306
+ pages.push({ card: currentCard, html: "" });
2307
+ };
2159
2308
  const body = doc.getElementsByTagNameNS("*", "body")[0] || doc.documentElement;
2160
- let html = "";
2161
2309
  for (const child of Array.from(body.children)) {
2162
2310
  const tag = child.localName || child.nodeName.split(":").pop();
2163
2311
  if (tag === "p") {
2164
2312
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
2165
2313
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
2166
2314
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
2167
- const textContent = this.extractParagraphHtml(child, imageMap);
2168
- if (!textContent.trim()) {
2169
- html += '<div style="height: 10px;"></div>';
2170
- continue;
2171
- }
2172
- const lowerStyle = styleVal.toLowerCase();
2173
- if (lowerStyle.includes("title")) {
2174
- 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>`;
2175
- } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2176
- html += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2177
- } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2178
- html += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2179
- } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2180
- html += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2181
- } else if (numPr) {
2182
- 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>`;
2183
- } else {
2184
- html += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2315
+ const chunks = this.extractParagraphChunks(child, imageMap);
2316
+ for (let i = 0; i < chunks.length; i++) {
2317
+ if (i > 0) {
2318
+ newPage();
2319
+ }
2320
+ const textContent = chunks[i];
2321
+ if (!textContent.trim() && !textContent.includes("<img")) {
2322
+ currentHtml += '<div style="height: 10px;"></div>';
2323
+ continue;
2324
+ }
2325
+ const lowerStyle = styleVal.toLowerCase();
2326
+ if (lowerStyle.includes("title")) {
2327
+ 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>`;
2328
+ } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2329
+ currentHtml += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2330
+ } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2331
+ currentHtml += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2332
+ } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2333
+ currentHtml += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2334
+ } else if (numPr) {
2335
+ 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>`;
2336
+ } else {
2337
+ currentHtml += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2338
+ }
2185
2339
  }
2186
2340
  } else if (tag === "tbl") {
2187
- html += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2341
+ currentHtml += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2188
2342
  const rows = Array.from(child.getElementsByTagNameNS("*", "tr"));
2189
2343
  rows.forEach((tr, rIdx) => {
2190
- html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2344
+ currentHtml += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2191
2345
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
2192
2346
  cells.forEach((tc) => {
2193
2347
  const cellText = this.extractParagraphHtml(tc, imageMap);
2194
- html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2348
+ currentHtml += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2195
2349
  });
2196
- html += "</tr>";
2350
+ currentHtml += "</tr>";
2197
2351
  });
2198
- html += "</table>";
2352
+ currentHtml += "</table>";
2199
2353
  }
2200
2354
  }
2201
- card.innerHTML = DOMPurify2.sanitize(html, {
2202
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2203
- ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2204
- });
2205
- wrapper.appendChild(card);
2355
+ flushHtml();
2356
+ for (const page of pages) {
2357
+ page.card.innerHTML = DOMPurify6.sanitize(page.html, {
2358
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2359
+ ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2360
+ });
2361
+ wrapper.appendChild(page.card);
2362
+ }
2206
2363
  }
2207
2364
  extractParagraphHtml(pElement, imageMap = {}) {
2208
- let result = "";
2365
+ return this.extractParagraphChunks(pElement, imageMap).join("<br/>");
2366
+ }
2367
+ extractParagraphChunks(pElement, imageMap = {}) {
2368
+ const chunks = [""];
2369
+ let currentChunkIndex = 0;
2209
2370
  const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
2210
2371
  for (const drawing of drawings) {
2211
2372
  const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
2212
2373
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2213
2374
  if (rId && imageMap[rId]) {
2214
- result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2375
+ chunks[currentChunkIndex] += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2215
2376
  }
2216
2377
  }
2217
2378
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
2218
- if (runs.length === 0 && !result) {
2219
- return DOMPurify2.sanitize(pElement.textContent || "");
2379
+ if (runs.length === 0 && !chunks[currentChunkIndex]) {
2380
+ chunks[currentChunkIndex] = DOMPurify6.sanitize(pElement.textContent || "");
2381
+ return chunks;
2220
2382
  }
2221
2383
  for (const r of runs) {
2222
2384
  const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
2223
2385
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2224
2386
  if (rId && imageMap[rId]) {
2225
- result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2387
+ chunks[currentChunkIndex] += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2226
2388
  }
2227
- const brs = r.getElementsByTagNameNS("*", "br");
2228
- for (let i = 0; i < brs.length; i++) {
2229
- result += "<br/>";
2389
+ const brs = Array.from(r.getElementsByTagNameNS("*", "br"));
2390
+ for (const br of brs) {
2391
+ const type = br.getAttribute("w:type") || br.getAttribute("type");
2392
+ if (type === "page") {
2393
+ chunks.push("");
2394
+ currentChunkIndex++;
2395
+ } else {
2396
+ chunks[currentChunkIndex] += "<br/>";
2397
+ }
2230
2398
  }
2231
2399
  const tabs = r.getElementsByTagNameNS("*", "tab");
2232
2400
  if (tabs.length > 0) {
2233
- result += "&emsp;";
2401
+ chunks[currentChunkIndex] += "&emsp;";
2234
2402
  }
2235
2403
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
2236
2404
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
@@ -2244,7 +2412,7 @@ var DocxPlugin = class {
2244
2412
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
2245
2413
  let text = texts.map((t) => t.textContent || "").join("");
2246
2414
  if (!text) continue;
2247
- text = DOMPurify2.sanitize(text);
2415
+ text = DOMPurify6.sanitize(text);
2248
2416
  let styles = "";
2249
2417
  if (isBold) styles += "font-weight: bold; ";
2250
2418
  if (isItalic) styles += "font-style: italic; ";
@@ -2256,12 +2424,12 @@ var DocxPlugin = class {
2256
2424
  if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2257
2425
  }
2258
2426
  if (styles) {
2259
- result += `<span style="${styles}">${text}</span>`;
2427
+ chunks[currentChunkIndex] += `<span style="${styles}">${text}</span>`;
2260
2428
  } else {
2261
- result += text;
2429
+ chunks[currentChunkIndex] += text;
2262
2430
  }
2263
2431
  }
2264
- return result;
2432
+ return chunks;
2265
2433
  }
2266
2434
  renderBinaryDocFallback(ctx, wrapper) {
2267
2435
  const card = document.createElement("div");
@@ -2278,14 +2446,14 @@ var DocxPlugin = class {
2278
2446
  const wordDocStream = cfbf.readStream("WordDocument");
2279
2447
  if (wordDocStream && wordDocStream.length >= 512) {
2280
2448
  const text2 = this.extractReadableStrings(wordDocStream);
2281
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text2)}</div>`;
2449
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6.sanitize(text2)}</div>`;
2282
2450
  wrapper.appendChild(card);
2283
2451
  return;
2284
2452
  }
2285
2453
  } catch {
2286
2454
  }
2287
2455
  const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2288
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text)}</div>`;
2456
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6.sanitize(text)}</div>`;
2289
2457
  wrapper.appendChild(card);
2290
2458
  }
2291
2459
  extractReadableStrings(bytes) {
@@ -2381,6 +2549,26 @@ var ExcelPlugin = class {
2381
2549
  instance.zoomIn?.();
2382
2550
  }
2383
2551
  },
2552
+ {
2553
+ id: "fit-page",
2554
+ icon: "fit-page",
2555
+ label: "Fit to View",
2556
+ type: "button",
2557
+ group: "zoom",
2558
+ execute: () => {
2559
+ instance.fitToPage?.();
2560
+ }
2561
+ },
2562
+ {
2563
+ id: "rotate-cw",
2564
+ icon: "rotate-cw",
2565
+ label: "Rotate",
2566
+ type: "button",
2567
+ group: "view",
2568
+ execute: () => {
2569
+ instance.rotateCW?.();
2570
+ }
2571
+ },
2384
2572
  {
2385
2573
  id: "download",
2386
2574
  icon: "download",
@@ -2431,9 +2619,23 @@ var ExcelPlugin = class {
2431
2619
  container.appendChild(tabsArea);
2432
2620
  ctx.container.appendChild(container);
2433
2621
  let scale = 1;
2622
+ let rotation = 0;
2434
2623
  let currentSheetIndex = 1;
2435
2624
  let sheetNames = [];
2436
2625
  let wb = null;
2626
+ const applyTransform = () => {
2627
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2628
+ contentArea.style.transformOrigin = "top left";
2629
+ };
2630
+ const calculateFitScale = () => {
2631
+ const table = contentArea.querySelector("table");
2632
+ if (!table) return 1;
2633
+ const availW = Math.max(200, container.clientWidth - 48);
2634
+ const availH = Math.max(200, container.clientHeight - 80);
2635
+ const tW = table.offsetWidth || 800;
2636
+ const tH = table.offsetHeight || 600;
2637
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2638
+ };
2437
2639
  try {
2438
2640
  wb = XLSX.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2439
2641
  sheetNames = wb.SheetNames || [];
@@ -2521,17 +2723,30 @@ var ExcelPlugin = class {
2521
2723
  return {
2522
2724
  destroy: cleanup,
2523
2725
  zoomIn: () => {
2524
- scale += 0.1;
2525
- contentArea.style.transform = `scale(${scale})`;
2726
+ scale += 0.15;
2727
+ applyTransform();
2526
2728
  },
2527
2729
  zoomOut: () => {
2528
- scale = Math.max(0.2, scale - 0.1);
2529
- contentArea.style.transform = `scale(${scale})`;
2730
+ scale = Math.max(0.2, scale - 0.15);
2731
+ applyTransform();
2530
2732
  },
2531
2733
  getZoom: () => scale,
2532
2734
  setZoom: (level) => {
2533
2735
  scale = level;
2534
- contentArea.style.transform = `scale(${scale})`;
2736
+ applyTransform();
2737
+ },
2738
+ fitToPage: () => {
2739
+ scale = calculateFitScale();
2740
+ rotation = 0;
2741
+ applyTransform();
2742
+ },
2743
+ rotateCW: () => {
2744
+ rotation = (rotation + 90) % 360;
2745
+ applyTransform();
2746
+ },
2747
+ rotateCCW: () => {
2748
+ rotation = (rotation - 90 + 360) % 360;
2749
+ applyTransform();
2535
2750
  },
2536
2751
  goToPage: (page) => {
2537
2752
  if (page > 0 && page <= sheetNames.length) {
@@ -2781,6 +2996,26 @@ var CodePlugin = class {
2781
2996
  instance.zoomIn?.();
2782
2997
  }
2783
2998
  },
2999
+ {
3000
+ id: "fit-page",
3001
+ icon: "fit-page",
3002
+ label: "Fit to View",
3003
+ type: "button",
3004
+ group: "zoom",
3005
+ execute: () => {
3006
+ instance.fitToPage?.();
3007
+ }
3008
+ },
3009
+ {
3010
+ id: "rotate-cw",
3011
+ icon: "rotate-cw",
3012
+ label: "Rotate",
3013
+ type: "button",
3014
+ group: "view",
3015
+ execute: () => {
3016
+ instance.rotateCW?.();
3017
+ }
3018
+ },
2784
3019
  {
2785
3020
  id: "copy",
2786
3021
  icon: "copy",
@@ -2817,40 +3052,94 @@ var CodePlugin = class {
2817
3052
  async render(ctx) {
2818
3053
  const decoder = new TextDecoder("utf-8");
2819
3054
  const fullText = decoder.decode(ctx.buffer);
2820
- const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2821
- const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3055
+ const extRaw = ctx.metadata.extension?.toLowerCase();
3056
+ const mimeRaw = ctx.metadata.mimeType?.toLowerCase();
3057
+ const isTxt = extRaw === ".txt" || mimeRaw === "text/plain" && (!extRaw || !this.extensions.filter((e) => e !== ".txt").includes(extRaw));
3058
+ let rawPages = [];
3059
+ if (isTxt) {
3060
+ const explicitPages = fullText.split(/(?:\f|\x0C)/);
3061
+ for (const ep of explicitPages) {
3062
+ const lines = ep.split(/\r?\n/);
3063
+ let currentChunk = [];
3064
+ for (let i = 0; i < lines.length; i++) {
3065
+ currentChunk.push(lines[i]);
3066
+ if (currentChunk.length >= 46) {
3067
+ rawPages.push(currentChunk.join("\n"));
3068
+ currentChunk = [];
3069
+ }
3070
+ }
3071
+ if (currentChunk.length > 0 || lines.length === 0) {
3072
+ rawPages.push(currentChunk.join("\n"));
3073
+ }
3074
+ }
3075
+ if (rawPages.length === 0) rawPages = [""];
3076
+ } else {
3077
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
3078
+ rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3079
+ }
2822
3080
  const totalPages = Math.max(1, rawPages.length);
2823
3081
  let currentPage = 1;
2824
3082
  const container = document.createElement("div");
2825
3083
  container.style.width = "100%";
2826
3084
  container.style.height = "100%";
2827
3085
  container.style.overflow = "auto";
2828
- container.style.backgroundColor = "#1e1e1e";
2829
- container.style.color = "#d4d4d4";
2830
- container.style.padding = "16px";
2831
- container.style.boxSizing = "border-box";
2832
3086
  let fontSize = 13;
3087
+ let rotation = 0;
3088
+ let zoomLevel = 1;
2833
3089
  const pre = document.createElement("pre");
2834
- pre.style.margin = "0";
2835
- pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
2836
- pre.style.fontSize = `${fontSize}px`;
2837
- pre.style.lineHeight = "1.5";
2838
- pre.style.whiteSpace = "pre-wrap";
2839
- pre.style.wordBreak = "break-all";
2840
3090
  const code = document.createElement("code");
3091
+ if (isTxt) {
3092
+ container.style.backgroundColor = "#f1f5f9";
3093
+ container.style.padding = "32px";
3094
+ container.style.boxSizing = "border-box";
3095
+ container.style.display = "flex";
3096
+ container.style.flexDirection = "column";
3097
+ container.style.alignItems = "center";
3098
+ pre.style.margin = "0";
3099
+ pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3100
+ pre.style.fontSize = "13px";
3101
+ pre.style.color = "#1e293b";
3102
+ pre.style.lineHeight = "1.5";
3103
+ pre.style.whiteSpace = "pre-wrap";
3104
+ pre.style.wordBreak = "break-word";
3105
+ pre.style.backgroundColor = "#ffffff";
3106
+ pre.style.width = "816px";
3107
+ pre.style.minHeight = "1056px";
3108
+ pre.style.padding = "72px 56px";
3109
+ pre.style.boxSizing = "border-box";
3110
+ pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
3111
+ pre.style.borderRadius = "4px";
3112
+ pre.style.transformOrigin = "top center";
3113
+ } else {
3114
+ container.style.backgroundColor = "#1e1e1e";
3115
+ container.style.color = "#d4d4d4";
3116
+ container.style.padding = "16px";
3117
+ container.style.boxSizing = "border-box";
3118
+ pre.style.margin = "0";
3119
+ pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
3120
+ pre.style.fontSize = `${fontSize}px`;
3121
+ pre.style.lineHeight = "1.5";
3122
+ pre.style.whiteSpace = "pre-wrap";
3123
+ pre.style.wordBreak = "break-all";
3124
+ pre.style.transformOrigin = "top left";
3125
+ }
2841
3126
  const ext = (ctx.metadata.extension || "").replace(".", "");
2842
3127
  const renderCodePage = (text) => {
2843
- try {
2844
- if (ext && hljs.getLanguage(ext)) {
2845
- code.innerHTML = hljs.highlight(text, { language: ext }).value;
2846
- } else {
2847
- code.innerHTML = hljs.highlightAuto(text).value;
2848
- }
2849
- } catch {
3128
+ if (isTxt) {
2850
3129
  code.textContent = text;
3130
+ } else {
3131
+ try {
3132
+ if (ext && hljs.getLanguage(ext)) {
3133
+ code.innerHTML = hljs.highlight(text, { language: ext }).value;
3134
+ } else {
3135
+ code.innerHTML = hljs.highlightAuto(text).value;
3136
+ }
3137
+ } catch {
3138
+ code.textContent = text;
3139
+ }
2851
3140
  }
2852
3141
  };
2853
- renderCodePage(rawPages[0] || fullText);
3142
+ renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
2854
3143
  pre.appendChild(code);
2855
3144
  container.appendChild(pre);
2856
3145
  let indicator = null;
@@ -2859,15 +3148,22 @@ var CodePlugin = class {
2859
3148
  indicator.className = "fp-code-page-indicator";
2860
3149
  indicator.style.position = "sticky";
2861
3150
  indicator.style.bottom = "16px";
2862
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2863
- indicator.style.backdropFilter = "blur(8px)";
2864
- indicator.style.color = "#f8fafc";
3151
+ if (isTxt) {
3152
+ indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3153
+ indicator.style.color = "#334155";
3154
+ indicator.style.border = "1px solid #e2e8f0";
3155
+ indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3156
+ } else {
3157
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3158
+ indicator.style.color = "#f8fafc";
3159
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3160
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3161
+ indicator.style.backdropFilter = "blur(8px)";
3162
+ }
2865
3163
  indicator.style.fontSize = "12px";
2866
3164
  indicator.style.fontWeight = "600";
2867
3165
  indicator.style.padding = "5px 14px";
2868
3166
  indicator.style.borderRadius = "20px";
2869
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2870
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2871
3167
  indicator.style.zIndex = "10";
2872
3168
  indicator.style.userSelect = "none";
2873
3169
  indicator.style.pointerEvents = "none";
@@ -2878,7 +3174,7 @@ var CodePlugin = class {
2878
3174
  }
2879
3175
  const showPage = (pageNum) => {
2880
3176
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2881
- renderCodePage(rawPages[currentPage - 1] || fullText);
3177
+ renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
2882
3178
  if (indicator) {
2883
3179
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2884
3180
  }
@@ -2894,23 +3190,57 @@ var CodePlugin = class {
2894
3190
  ctx.container.innerHTML = "";
2895
3191
  };
2896
3192
  ctx.signal.addEventListener("abort", cleanup);
3193
+ const updateTransform = () => {
3194
+ if (isTxt) {
3195
+ pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
3196
+ } else {
3197
+ pre.style.transform = `rotate(${rotation}deg)`;
3198
+ pre.style.fontSize = `${fontSize}px`;
3199
+ }
3200
+ };
2897
3201
  return {
2898
3202
  destroy: cleanup,
2899
3203
  getPageCount: () => totalPages,
2900
3204
  getCurrentPage: () => currentPage,
2901
3205
  goToPage: (page) => showPage(page),
2902
3206
  zoomIn: () => {
2903
- fontSize = Math.min(32, fontSize + 2);
2904
- pre.style.fontSize = `${fontSize}px`;
3207
+ if (isTxt) {
3208
+ zoomLevel = Math.min(3, zoomLevel + 0.1);
3209
+ } else {
3210
+ fontSize = Math.min(32, fontSize + 2);
3211
+ }
3212
+ updateTransform();
2905
3213
  },
2906
3214
  zoomOut: () => {
2907
- fontSize = Math.max(8, fontSize - 2);
2908
- pre.style.fontSize = `${fontSize}px`;
3215
+ if (isTxt) {
3216
+ zoomLevel = Math.max(0.1, zoomLevel - 0.1);
3217
+ } else {
3218
+ fontSize = Math.max(8, fontSize - 2);
3219
+ }
3220
+ updateTransform();
2909
3221
  },
2910
- getZoom: () => fontSize / 13,
3222
+ getZoom: () => isTxt ? zoomLevel : fontSize / 13,
2911
3223
  setZoom: (level) => {
2912
- fontSize = Math.round(13 * level);
2913
- pre.style.fontSize = `${fontSize}px`;
3224
+ if (isTxt) {
3225
+ zoomLevel = level;
3226
+ } else {
3227
+ fontSize = Math.round(13 * level);
3228
+ }
3229
+ updateTransform();
3230
+ },
3231
+ fitToPage: () => {
3232
+ fontSize = 13;
3233
+ rotation = 0;
3234
+ zoomLevel = 1;
3235
+ updateTransform();
3236
+ },
3237
+ rotateCW: () => {
3238
+ rotation = (rotation + 90) % 360;
3239
+ updateTransform();
3240
+ },
3241
+ rotateCCW: () => {
3242
+ rotation = (rotation - 90 + 360) % 360;
3243
+ updateTransform();
2914
3244
  },
2915
3245
  download: () => {
2916
3246
  const mimeType = ctx.metadata.mimeType || "text/plain";
@@ -3209,7 +3539,7 @@ var MarkdownPlugin = class {
3209
3539
  gfm: true,
3210
3540
  breaks: true
3211
3541
  });
3212
- const cleanHtml = DOMPurify2.sanitize(rawHtml, {
3542
+ const cleanHtml = DOMPurify6.sanitize(rawHtml, {
3213
3543
  USE_PROFILES: { html: true }
3214
3544
  });
3215
3545
  const wrapper = document.createElement("div");
@@ -3423,6 +3753,14 @@ var PptxPlugin = class {
3423
3753
  group: "zoom",
3424
3754
  execute: () => instance.fitToPage?.()
3425
3755
  },
3756
+ {
3757
+ id: "rotate-cw",
3758
+ icon: "rotate-cw",
3759
+ label: "Rotate",
3760
+ type: "button",
3761
+ group: "view",
3762
+ execute: () => instance.rotateCW?.()
3763
+ },
3426
3764
  {
3427
3765
  id: "download",
3428
3766
  icon: "download",
@@ -3447,6 +3785,7 @@ var PptxPlugin = class {
3447
3785
  const slideCount = renderer.slidePaths?.length || 1;
3448
3786
  let currentSlide = 1;
3449
3787
  let scale = 1;
3788
+ let rotation = 0;
3450
3789
  const wrapper = document.createElement("div");
3451
3790
  wrapper.className = "fp-pptx-wrapper";
3452
3791
  wrapper.style.cssText = `
@@ -3469,6 +3808,8 @@ var PptxPlugin = class {
3469
3808
  background: #ffffff;
3470
3809
  transform-origin: top center;
3471
3810
  transition: transform 0.2s ease;
3811
+ max-width: calc(100% - 32px);
3812
+ max-height: calc(100% - 48px);
3472
3813
  `;
3473
3814
  const canvas = document.createElement("canvas");
3474
3815
  slideContainer.appendChild(canvas);
@@ -3476,10 +3817,22 @@ var PptxPlugin = class {
3476
3817
  ctx.container.innerHTML = "";
3477
3818
  ctx.container.style.overflow = "auto";
3478
3819
  ctx.container.appendChild(wrapper);
3820
+ const calculateFitScale = () => {
3821
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3822
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3823
+ const cW = canvas.offsetWidth || 1280;
3824
+ const cH = canvas.offsetHeight || 720;
3825
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3826
+ };
3827
+ const applyTransform = () => {
3828
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3829
+ };
3479
3830
  const renderCurrentSlide = async () => {
3480
3831
  try {
3481
3832
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3482
3833
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3834
+ scale = calculateFitScale();
3835
+ applyTransform();
3483
3836
  } catch (err) {
3484
3837
  console.error("[PptxPlugin] Failed to render slide:", err);
3485
3838
  }
@@ -3502,21 +3855,30 @@ var PptxPlugin = class {
3502
3855
  getPageCount: () => slideCount,
3503
3856
  getCurrentPage: () => currentSlide,
3504
3857
  zoomIn: () => {
3505
- scale += 0.1;
3506
- slideContainer.style.transform = `scale(${scale})`;
3858
+ scale += 0.15;
3859
+ applyTransform();
3507
3860
  },
3508
3861
  zoomOut: () => {
3509
- scale = Math.max(0.2, scale - 0.1);
3510
- slideContainer.style.transform = `scale(${scale})`;
3862
+ scale = Math.max(0.2, scale - 0.15);
3863
+ applyTransform();
3511
3864
  },
3512
3865
  getZoom: () => scale,
3513
3866
  setZoom: (level) => {
3514
3867
  scale = level;
3515
- slideContainer.style.transform = `scale(${scale})`;
3868
+ applyTransform();
3516
3869
  },
3517
3870
  fitToPage: () => {
3518
- scale = 1;
3519
- slideContainer.style.transform = "scale(1)";
3871
+ scale = calculateFitScale();
3872
+ rotation = 0;
3873
+ applyTransform();
3874
+ },
3875
+ rotateCW: () => {
3876
+ rotation = (rotation + 90) % 360;
3877
+ applyTransform();
3878
+ },
3879
+ rotateCCW: () => {
3880
+ rotation = (rotation - 90 + 360) % 360;
3881
+ applyTransform();
3520
3882
  },
3521
3883
  getThumbnails: () => {
3522
3884
  const list = [];
@@ -3797,6 +4159,14 @@ var RtfPlugin = class {
3797
4159
  group: "zoom",
3798
4160
  execute: () => instance.fitToPage?.()
3799
4161
  },
4162
+ {
4163
+ id: "rotate-cw",
4164
+ icon: "rotate-cw",
4165
+ label: "Rotate",
4166
+ type: "button",
4167
+ group: "view",
4168
+ execute: () => instance.rotateCW?.()
4169
+ },
3800
4170
  {
3801
4171
  id: "download",
3802
4172
  icon: "download",
@@ -3805,6 +4175,14 @@ var RtfPlugin = class {
3805
4175
  group: "actions",
3806
4176
  execute: () => instance.download?.()
3807
4177
  },
4178
+ {
4179
+ id: "copy",
4180
+ icon: "copy",
4181
+ label: "Copy Text",
4182
+ type: "button",
4183
+ group: "actions",
4184
+ execute: () => instance.copy?.()
4185
+ },
3808
4186
  {
3809
4187
  id: "print",
3810
4188
  icon: "print",
@@ -3833,7 +4211,24 @@ var RtfPlugin = class {
3833
4211
  ctx.container.style.backgroundColor = "#f1f5f9";
3834
4212
  ctx.container.appendChild(wrapper);
3835
4213
  let scale = 1;
4214
+ let rotation = 0;
3836
4215
  let pageElements = [];
4216
+ const calculateFitScale = () => {
4217
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4218
+ const elW = activeEl.offsetWidth || 850;
4219
+ const elH = activeEl.offsetHeight || 1e3;
4220
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4221
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4222
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4223
+ };
4224
+ const applyTransform = () => {
4225
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4226
+ wrapper.style.transformOrigin = "top center";
4227
+ };
4228
+ setTimeout(() => {
4229
+ scale = calculateFitScale();
4230
+ applyTransform();
4231
+ }, 60);
3837
4232
  try {
3838
4233
  if (typeof RTFJS.loggingEnabled === "function") {
3839
4234
  RTFJS.loggingEnabled(false);
@@ -3849,14 +4244,31 @@ var RtfPlugin = class {
3849
4244
  } catch (err) {
3850
4245
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3851
4246
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3852
- const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3853
- const pre = document.createElement("pre");
3854
- pre.style.whiteSpace = "pre-wrap";
3855
- pre.style.fontFamily = "serif";
3856
- pre.style.color = "#333";
3857
- pre.textContent = clean;
3858
- wrapper.appendChild(pre);
3859
- pageElements = [pre];
4247
+ const rawPages = text.split(/\\page\b/).map((segment) => {
4248
+ return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
4249
+ }).filter((p) => p.length > 0);
4250
+ const pages = rawPages.length > 0 ? rawPages : [text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim()];
4251
+ for (let i = 0; i < pages.length; i++) {
4252
+ const pageCard = document.createElement("div");
4253
+ pageCard.className = "fp-rtf-page-card";
4254
+ pageCard.style.backgroundColor = "#ffffff";
4255
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4256
+ pageCard.style.borderRadius = "4px";
4257
+ pageCard.style.padding = "72px 56px";
4258
+ pageCard.style.width = "816px";
4259
+ pageCard.style.minHeight = "1056px";
4260
+ pageCard.style.maxWidth = "100%";
4261
+ pageCard.style.boxSizing = "border-box";
4262
+ pageCard.style.fontFamily = "serif";
4263
+ pageCard.style.fontSize = "12pt";
4264
+ pageCard.style.lineHeight = "1.6";
4265
+ pageCard.style.color = "#1e293b";
4266
+ pageCard.style.whiteSpace = "pre-wrap";
4267
+ pageCard.style.display = i === 0 ? "block" : "none";
4268
+ pageCard.textContent = pages[i];
4269
+ wrapper.appendChild(pageCard);
4270
+ pageElements.push(pageCard);
4271
+ }
3860
4272
  }
3861
4273
  const totalPages = Math.max(1, pageElements.length);
3862
4274
  let currentPage = 1;
@@ -3910,21 +4322,30 @@ var RtfPlugin = class {
3910
4322
  getCurrentPage: () => currentPage,
3911
4323
  goToPage: (page) => showPage(page),
3912
4324
  zoomIn: () => {
3913
- scale += 0.1;
3914
- wrapper.style.transform = `scale(${scale})`;
4325
+ scale += 0.15;
4326
+ applyTransform();
3915
4327
  },
3916
4328
  zoomOut: () => {
3917
- scale = Math.max(0.2, scale - 0.1);
3918
- wrapper.style.transform = `scale(${scale})`;
4329
+ scale = Math.max(0.2, scale - 0.15);
4330
+ applyTransform();
3919
4331
  },
3920
4332
  getZoom: () => scale,
3921
4333
  setZoom: (level) => {
3922
4334
  scale = level;
3923
- wrapper.style.transform = `scale(${scale})`;
4335
+ applyTransform();
3924
4336
  },
3925
4337
  fitToPage: () => {
3926
- scale = 1;
3927
- wrapper.style.transform = "scale(1)";
4338
+ scale = calculateFitScale();
4339
+ rotation = 0;
4340
+ applyTransform();
4341
+ },
4342
+ rotateCW: () => {
4343
+ rotation = (rotation + 90) % 360;
4344
+ applyTransform();
4345
+ },
4346
+ rotateCCW: () => {
4347
+ rotation = (rotation - 90 + 360) % 360;
4348
+ applyTransform();
3928
4349
  },
3929
4350
  download: () => {
3930
4351
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3935,6 +4356,10 @@ var RtfPlugin = class {
3935
4356
  a.click();
3936
4357
  URL.revokeObjectURL(url);
3937
4358
  },
4359
+ copy: () => {
4360
+ const text = wrapper.textContent || "";
4361
+ navigator.clipboard?.writeText(text);
4362
+ },
3938
4363
  print: () => {
3939
4364
  window.print();
3940
4365
  }
@@ -4009,7 +4434,7 @@ var HtmlPreviewPlugin = class {
4009
4434
  }
4010
4435
  async render(ctx) {
4011
4436
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
4012
- const sanitized = DOMPurify2.sanitize(rawHtml, {
4437
+ const sanitized = DOMPurify6.sanitize(rawHtml, {
4013
4438
  WHOLE_DOCUMENT: true,
4014
4439
  ADD_TAGS: ["style", "link"],
4015
4440
  ADD_ATTR: ["target", "rel"]
@@ -4152,11 +4577,19 @@ var OpenDocumentPlugin = class {
4152
4577
  {
4153
4578
  id: "fit-page",
4154
4579
  icon: "fit-page",
4155
- label: "Fit to Page",
4580
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
4156
4581
  type: "button",
4157
4582
  group: "zoom",
4158
4583
  execute: () => instance.fitToPage?.()
4159
4584
  },
4585
+ {
4586
+ id: "rotate-cw",
4587
+ icon: "rotate-cw",
4588
+ label: "Rotate",
4589
+ type: "button",
4590
+ group: "view",
4591
+ execute: () => instance.rotateCW?.()
4592
+ },
4160
4593
  {
4161
4594
  id: "download",
4162
4595
  icon: "download",
@@ -4220,6 +4653,22 @@ var OpenDocumentPlugin = class {
4220
4653
  container.appendChild(wrapper);
4221
4654
  ctx.container.appendChild(container);
4222
4655
  let scale = 1;
4656
+ let rotation = 0;
4657
+ const calculateFitScale = () => {
4658
+ const elW = wrapper.offsetWidth || 850;
4659
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4660
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4661
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4662
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4663
+ };
4664
+ const applyTransform = () => {
4665
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4666
+ wrapper.style.transformOrigin = "top center";
4667
+ };
4668
+ setTimeout(() => {
4669
+ scale = calculateFitScale();
4670
+ applyTransform();
4671
+ }, 60);
4223
4672
  let currentPage = 1;
4224
4673
  let totalPages = 1;
4225
4674
  let slides = [];
@@ -4251,14 +4700,97 @@ var OpenDocumentPlugin = class {
4251
4700
  wrapper.textContent = contentDoc.documentElement.textContent || "Formula content";
4252
4701
  }
4253
4702
  } else {
4254
- wrapper.style.maxWidth = "850px";
4255
- wrapper.style.padding = "48px";
4703
+ wrapper.style.maxWidth = "none";
4704
+ wrapper.style.padding = "0";
4256
4705
  wrapper.style.minHeight = "100%";
4706
+ wrapper.style.backgroundColor = "transparent";
4707
+ wrapper.style.boxShadow = "none";
4708
+ wrapper.style.position = "relative";
4709
+ const dims = this.getPageDimensions(stylesDoc, contentDoc);
4257
4710
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
4258
- wrapper.innerHTML = DOMPurify2.sanitize(bodyHtml, {
4259
- ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
4711
+ const tempDiv = document.createElement("div");
4712
+ tempDiv.style.width = `${dims.width - dims.marginLeft - dims.marginRight}px`;
4713
+ tempDiv.style.position = "absolute";
4714
+ tempDiv.style.visibility = "hidden";
4715
+ tempDiv.innerHTML = DOMPurify6.sanitize(bodyHtml, {
4716
+ ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub", "hr"],
4260
4717
  ADD_ATTR: ["style", "colspan", "rowspan"]
4261
4718
  });
4719
+ document.body.appendChild(tempDiv);
4720
+ const contentHeight = dims.height - dims.marginTop - dims.marginBottom;
4721
+ const pageElements = [[]];
4722
+ let currentHeight = 0;
4723
+ let currentPageIdx = 0;
4724
+ Array.from(tempDiv.children).forEach((child) => {
4725
+ const el = child;
4726
+ const style = el.getAttribute("style") || "";
4727
+ const isBreakBefore = style.includes("page-break-before: always");
4728
+ const isBreakAfter = style.includes("page-break-after: always");
4729
+ const isSoftBreak = el.classList.contains("odf-page-break");
4730
+ if (isBreakBefore) {
4731
+ if (pageElements[currentPageIdx].length > 0) {
4732
+ currentPageIdx++;
4733
+ pageElements.push([]);
4734
+ currentHeight = 0;
4735
+ }
4736
+ }
4737
+ const h = el.offsetHeight || 0;
4738
+ if (currentHeight + h > contentHeight && pageElements[currentPageIdx].length > 0 && !isSoftBreak) {
4739
+ currentPageIdx++;
4740
+ pageElements.push([]);
4741
+ currentHeight = 0;
4742
+ }
4743
+ if (!isSoftBreak) {
4744
+ pageElements[currentPageIdx].push(el.cloneNode(true));
4745
+ currentHeight += h;
4746
+ }
4747
+ if (isBreakAfter || isSoftBreak) {
4748
+ currentPageIdx++;
4749
+ pageElements.push([]);
4750
+ currentHeight = 0;
4751
+ }
4752
+ });
4753
+ document.body.removeChild(tempDiv);
4754
+ if (pageElements.length > 1 && pageElements[pageElements.length - 1].length === 0) {
4755
+ pageElements.pop();
4756
+ }
4757
+ totalPages = Math.max(1, pageElements.length);
4758
+ pageElements.forEach((elements, idx) => {
4759
+ const page = document.createElement("div");
4760
+ page.className = `fp-odt-page fp-odt-page-${idx + 1}`;
4761
+ page.style.width = `${dims.width}px`;
4762
+ page.style.minHeight = `${dims.height}px`;
4763
+ page.style.padding = `${dims.marginTop}px ${dims.marginRight}px ${dims.marginBottom}px ${dims.marginLeft}px`;
4764
+ page.style.margin = "0 auto";
4765
+ page.style.backgroundColor = "#ffffff";
4766
+ page.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
4767
+ page.style.borderRadius = "4px";
4768
+ page.style.boxSizing = "border-box";
4769
+ page.style.display = idx === 0 ? "block" : "none";
4770
+ page.style.position = "absolute";
4771
+ page.style.top = "0";
4772
+ page.style.left = "50%";
4773
+ page.style.transform = "translateX(-50%)";
4774
+ elements.forEach((el) => page.appendChild(el));
4775
+ wrapper.appendChild(page);
4776
+ slides.push(page);
4777
+ });
4778
+ const pageIndicator = document.createElement("div");
4779
+ pageIndicator.className = "fp-odt-page-indicator";
4780
+ pageIndicator.style.position = "sticky";
4781
+ pageIndicator.style.bottom = "16px";
4782
+ pageIndicator.style.left = "50%";
4783
+ pageIndicator.style.transform = "translateX(-50%)";
4784
+ pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
4785
+ pageIndicator.style.color = "#fff";
4786
+ pageIndicator.style.padding = "6px 12px";
4787
+ pageIndicator.style.borderRadius = "16px";
4788
+ pageIndicator.style.fontSize = "12px";
4789
+ pageIndicator.style.zIndex = "100";
4790
+ pageIndicator.style.display = "inline-block";
4791
+ pageIndicator.style.width = "fit-content";
4792
+ pageIndicator.textContent = `Page 1 of ${totalPages}`;
4793
+ container.appendChild(pageIndicator);
4262
4794
  }
4263
4795
  const cleanup = () => {
4264
4796
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
@@ -4267,32 +4799,45 @@ var OpenDocumentPlugin = class {
4267
4799
  };
4268
4800
  ctx.signal.addEventListener("abort", cleanup);
4269
4801
  const goToPage = (page) => {
4270
- if (!isPresentation || page < 1 || page > totalPages) return;
4802
+ if (page < 1 || page > totalPages) return;
4271
4803
  currentPage = page;
4272
4804
  slides.forEach((s, idx) => {
4273
4805
  s.style.display = idx === page - 1 ? "block" : "none";
4274
4806
  });
4807
+ const indicator = container.querySelector(".fp-odt-page-indicator");
4808
+ if (indicator) {
4809
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4810
+ }
4275
4811
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4276
4812
  };
4277
4813
  return {
4278
4814
  destroy: cleanup,
4279
4815
  isPresentation,
4280
4816
  zoomIn: () => {
4281
- scale += 0.1;
4282
- wrapper.style.transform = `scale(${scale})`;
4817
+ scale += 0.15;
4818
+ applyTransform();
4283
4819
  },
4284
4820
  zoomOut: () => {
4285
- scale = Math.max(0.2, scale - 0.1);
4286
- wrapper.style.transform = `scale(${scale})`;
4821
+ scale = Math.max(0.2, scale - 0.15);
4822
+ applyTransform();
4287
4823
  },
4288
4824
  getZoom: () => scale,
4289
4825
  setZoom: (level) => {
4290
4826
  scale = level;
4291
- wrapper.style.transform = `scale(${scale})`;
4827
+ applyTransform();
4292
4828
  },
4293
4829
  fitToPage: () => {
4294
- scale = 1;
4295
- wrapper.style.transform = "scale(1)";
4830
+ scale = calculateFitScale();
4831
+ rotation = 0;
4832
+ applyTransform();
4833
+ },
4834
+ rotateCW: () => {
4835
+ rotation = (rotation + 90) % 360;
4836
+ applyTransform();
4837
+ },
4838
+ rotateCCW: () => {
4839
+ rotation = (rotation - 90 + 360) % 360;
4840
+ applyTransform();
4296
4841
  },
4297
4842
  goToPage,
4298
4843
  getPageCount: () => totalPages,
@@ -4331,6 +4876,43 @@ var OpenDocumentPlugin = class {
4331
4876
  }
4332
4877
  };
4333
4878
  }
4879
+ getPageDimensions(stylesDoc, contentDoc) {
4880
+ let width = 850;
4881
+ let height = 1123;
4882
+ let marginTop = 48;
4883
+ let marginBottom = 48;
4884
+ let marginLeft = 48;
4885
+ let marginRight = 48;
4886
+ const parseUnit = (val) => {
4887
+ if (!val) return null;
4888
+ if (val.endsWith("cm")) return parseFloat(val) * 37.8;
4889
+ if (val.endsWith("mm")) return parseFloat(val) * 3.78;
4890
+ if (val.endsWith("in")) return parseFloat(val) * 96;
4891
+ if (val.endsWith("pt")) return parseFloat(val) * 1.33;
4892
+ if (val.endsWith("px")) return parseFloat(val);
4893
+ return parseFloat(val);
4894
+ };
4895
+ const docs = [stylesDoc, contentDoc].filter(Boolean);
4896
+ for (const doc of docs) {
4897
+ const pageLayout = doc.querySelector("page-layout-properties, [page-width]");
4898
+ if (pageLayout) {
4899
+ const w = parseUnit(pageLayout.getAttribute("fo:page-width") || pageLayout.getAttribute("page-width"));
4900
+ const h = parseUnit(pageLayout.getAttribute("fo:page-height") || pageLayout.getAttribute("page-height"));
4901
+ const mt = parseUnit(pageLayout.getAttribute("fo:margin-top") || pageLayout.getAttribute("margin-top"));
4902
+ const mb = parseUnit(pageLayout.getAttribute("fo:margin-bottom") || pageLayout.getAttribute("margin-bottom"));
4903
+ const ml = parseUnit(pageLayout.getAttribute("fo:margin-left") || pageLayout.getAttribute("margin-left"));
4904
+ const mr = parseUnit(pageLayout.getAttribute("fo:margin-right") || pageLayout.getAttribute("margin-right"));
4905
+ if (w !== null) width = w;
4906
+ if (h !== null) height = h;
4907
+ if (mt !== null) marginTop = mt;
4908
+ if (mb !== null) marginBottom = mb;
4909
+ if (ml !== null) marginLeft = ml;
4910
+ if (mr !== null) marginRight = mr;
4911
+ break;
4912
+ }
4913
+ }
4914
+ return { width, height, marginTop, marginBottom, marginLeft, marginRight };
4915
+ }
4334
4916
  extractStyles(stylesDoc, contentDoc) {
4335
4917
  const map = /* @__PURE__ */ new Map();
4336
4918
  const styleNodes = [];
@@ -4353,14 +4935,21 @@ var OpenDocumentPlugin = class {
4353
4935
  if (color) css += `color: ${color}; `;
4354
4936
  if (size) css += `font-size: ${size}; `;
4355
4937
  }
4356
- const paraProp = node.querySelector("paragraph-properties, [text-align]");
4938
+ let paraProp = node.querySelector("paragraph-properties, [text-align]");
4939
+ if (!paraProp) {
4940
+ paraProp = Array.from(node.children).find((c) => c.tagName.includes("paragraph-properties")) || null;
4941
+ }
4357
4942
  if (paraProp) {
4358
4943
  const align = paraProp.getAttribute("fo:text-align") || paraProp.getAttribute("text-align");
4359
4944
  const mt = paraProp.getAttribute("fo:margin-top") || paraProp.getAttribute("margin-top");
4360
4945
  const mb = paraProp.getAttribute("fo:margin-bottom") || paraProp.getAttribute("margin-bottom");
4946
+ const breakBefore = paraProp.getAttribute("fo:break-before") || paraProp.getAttribute("break-before");
4947
+ const breakAfter = paraProp.getAttribute("fo:break-after") || paraProp.getAttribute("break-after");
4361
4948
  if (align) css += `text-align: ${align}; `;
4362
4949
  if (mt) css += `margin-top: ${mt}; `;
4363
4950
  if (mb) css += `margin-bottom: ${mb}; `;
4951
+ if (breakBefore === "page") css += "page-break-before: always; ";
4952
+ if (breakAfter === "page") css += "page-break-after: always; ";
4364
4953
  }
4365
4954
  if (css) map.set(name, css);
4366
4955
  }
@@ -4445,6 +5034,8 @@ var OpenDocumentPlugin = class {
4445
5034
  result += "&emsp;";
4446
5035
  } else if (tag === "line-break") {
4447
5036
  result += "<br/>";
5037
+ } else if (tag === "soft-page-break") {
5038
+ result += '<hr class="odf-page-break" style="page-break-after: always; border: none; margin: 0; padding: 0; height: 0;" />';
4448
5039
  } else {
4449
5040
  result += el.textContent || "";
4450
5041
  }
@@ -4562,6 +5153,14 @@ var DocPlugin = class {
4562
5153
  group: "zoom",
4563
5154
  execute: () => instance.fitToPage?.()
4564
5155
  },
5156
+ {
5157
+ id: "rotate-cw",
5158
+ icon: "rotate-cw",
5159
+ label: "Rotate",
5160
+ type: "button",
5161
+ group: "view",
5162
+ execute: () => instance.rotateCW?.()
5163
+ },
4565
5164
  {
4566
5165
  id: "copy",
4567
5166
  icon: "copy",
@@ -4597,20 +5196,9 @@ var DocPlugin = class {
4597
5196
  container.style.overflow = "auto";
4598
5197
  container.style.padding = "32px 16px";
4599
5198
  container.style.backgroundColor = "#f1f5f9";
4600
- const wrapper = document.createElement("div");
4601
- wrapper.className = "fp-doc-wrapper";
4602
- wrapper.style.maxWidth = "850px";
4603
- wrapper.style.margin = "0 auto";
4604
- wrapper.style.backgroundColor = "#ffffff";
4605
- wrapper.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4606
- wrapper.style.borderRadius = "4px";
4607
- wrapper.style.padding = "56px 48px";
4608
- wrapper.style.minHeight = "100%";
4609
- wrapper.style.transformOrigin = "top center";
4610
- wrapper.style.transition = "transform 0.2s ease";
4611
- wrapper.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4612
- wrapper.style.color = "#1e293b";
4613
- container.appendChild(wrapper);
5199
+ container.style.display = "flex";
5200
+ container.style.justifyContent = "center";
5201
+ container.style.alignItems = "flex-start";
4614
5202
  ctx.container.appendChild(container);
4615
5203
  let scale = 1;
4616
5204
  let extractedRawText = "";
@@ -4637,21 +5225,27 @@ var DocPlugin = class {
4637
5225
  const rawPages = this.splitIntoPages(extractedRawText);
4638
5226
  const totalPages = Math.max(1, rawPages.length);
4639
5227
  let currentPage = 1;
4640
- wrapper.innerHTML = "";
4641
5228
  const pageCards = [];
4642
5229
  for (let i = 0; i < totalPages; i++) {
4643
5230
  const pageCard = document.createElement("div");
4644
5231
  pageCard.className = "fp-doc-page-card";
5232
+ pageCard.style.width = "816px";
5233
+ pageCard.style.height = "1056px";
4645
5234
  pageCard.style.backgroundColor = "#ffffff";
4646
5235
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4647
5236
  pageCard.style.borderRadius = "4px";
4648
- pageCard.style.padding = "56px 48px";
4649
- pageCard.style.minHeight = "100%";
5237
+ pageCard.style.padding = "96px 72px";
5238
+ pageCard.style.boxSizing = "border-box";
5239
+ pageCard.style.overflow = "hidden";
4650
5240
  pageCard.style.display = i === 0 ? "block" : "none";
5241
+ pageCard.style.transformOrigin = "top center";
5242
+ pageCard.style.transition = "transform 0.2s ease";
5243
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5244
+ pageCard.style.color = "#1e293b";
4651
5245
  if (isFallback && i === 0) {
4652
5246
  pageCard.innerHTML = `
4653
5247
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4654
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
5248
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify6.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4655
5249
  <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4656
5250
  </div>
4657
5251
  ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
@@ -4659,15 +5253,17 @@ var DocPlugin = class {
4659
5253
  } else {
4660
5254
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4661
5255
  }
4662
- wrapper.appendChild(pageCard);
5256
+ container.appendChild(pageCard);
4663
5257
  pageCards.push(pageCard);
4664
5258
  }
4665
5259
  let indicator = null;
4666
5260
  if (totalPages > 1) {
4667
5261
  indicator = document.createElement("div");
4668
5262
  indicator.className = "fp-doc-page-indicator";
4669
- indicator.style.position = "sticky";
5263
+ indicator.style.position = "fixed";
4670
5264
  indicator.style.bottom = "16px";
5265
+ indicator.style.left = "50%";
5266
+ indicator.style.transform = "translateX(-50%)";
4671
5267
  indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4672
5268
  indicator.style.backdropFilter = "blur(8px)";
4673
5269
  indicator.style.color = "#f8fafc";
@@ -4681,17 +5277,25 @@ var DocPlugin = class {
4681
5277
  indicator.style.userSelect = "none";
4682
5278
  indicator.style.pointerEvents = "none";
4683
5279
  indicator.style.textAlign = "center";
4684
- indicator.style.width = "fit-content";
4685
- indicator.style.margin = "16px auto 0";
4686
5280
  container.appendChild(indicator);
4687
5281
  }
5282
+ let rotation = 0;
5283
+ const applyTransform = () => {
5284
+ const activeCard = pageCards[currentPage - 1];
5285
+ if (activeCard) {
5286
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5287
+ }
5288
+ };
4688
5289
  const showPage = (pageNum) => {
4689
5290
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4690
- if (totalPages > 1) {
4691
- pageCards.forEach((card, idx) => {
4692
- card.style.display = idx + 1 === currentPage ? "block" : "none";
4693
- });
4694
- }
5291
+ pageCards.forEach((card, idx) => {
5292
+ if (idx + 1 === currentPage) {
5293
+ card.style.display = "block";
5294
+ card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5295
+ } else {
5296
+ card.style.display = "none";
5297
+ }
5298
+ });
4695
5299
  if (indicator) {
4696
5300
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4697
5301
  }
@@ -4700,6 +5304,17 @@ var DocPlugin = class {
4700
5304
  if (totalPages > 1) {
4701
5305
  showPage(1);
4702
5306
  }
5307
+ const calculateFitScale = () => {
5308
+ const elW = 816;
5309
+ const elH = 1056;
5310
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
5311
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
5312
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
5313
+ };
5314
+ setTimeout(() => {
5315
+ scale = calculateFitScale();
5316
+ applyTransform();
5317
+ }, 60);
4703
5318
  const cleanup = () => {
4704
5319
  indicator?.remove();
4705
5320
  container.remove();
@@ -4712,21 +5327,30 @@ var DocPlugin = class {
4712
5327
  getCurrentPage: () => currentPage,
4713
5328
  goToPage: (page) => showPage(page),
4714
5329
  zoomIn: () => {
4715
- scale += 0.1;
4716
- wrapper.style.transform = `scale(${scale})`;
5330
+ scale += 0.15;
5331
+ applyTransform();
4717
5332
  },
4718
5333
  zoomOut: () => {
4719
- scale = Math.max(0.2, scale - 0.1);
4720
- wrapper.style.transform = `scale(${scale})`;
5334
+ scale = Math.max(0.2, scale - 0.15);
5335
+ applyTransform();
4721
5336
  },
4722
5337
  getZoom: () => scale,
4723
5338
  setZoom: (level) => {
4724
5339
  scale = level;
4725
- wrapper.style.transform = `scale(${scale})`;
5340
+ applyTransform();
4726
5341
  },
4727
5342
  fitToPage: () => {
4728
- scale = 1;
4729
- wrapper.style.transform = "scale(1)";
5343
+ scale = calculateFitScale();
5344
+ rotation = 0;
5345
+ applyTransform();
5346
+ },
5347
+ rotateCW: () => {
5348
+ rotation = (rotation + 90) % 360;
5349
+ applyTransform();
5350
+ },
5351
+ rotateCCW: () => {
5352
+ rotation = (rotation - 90 + 360) % 360;
5353
+ applyTransform();
4730
5354
  },
4731
5355
  copy: () => {
4732
5356
  navigator.clipboard.writeText(extractedRawText);
@@ -4844,8 +5468,28 @@ var DocPlugin = class {
4844
5468
  }
4845
5469
  splitIntoPages(text) {
4846
5470
  if (!text) return [""];
4847
- 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);
4848
- return parts.length > 0 ? parts : [text];
5471
+ 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);
5472
+ if (explicitParts.length === 0) explicitParts.push(text);
5473
+ const maxLinesPerPage = 48;
5474
+ const finalPages = [];
5475
+ for (const part of explicitParts) {
5476
+ const lines = part.split(/\r?\n/);
5477
+ let currentLines = [];
5478
+ let count = 0;
5479
+ for (const line of lines) {
5480
+ currentLines.push(line);
5481
+ count++;
5482
+ if (count >= maxLinesPerPage) {
5483
+ finalPages.push(currentLines.join("\n"));
5484
+ currentLines = [];
5485
+ count = 0;
5486
+ }
5487
+ }
5488
+ if (currentLines.length > 0) {
5489
+ finalPages.push(currentLines.join("\n"));
5490
+ }
5491
+ }
5492
+ return finalPages.length > 0 ? finalPages : [text];
4849
5493
  }
4850
5494
  /**
4851
5495
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
@@ -4854,17 +5498,65 @@ var DocPlugin = class {
4854
5498
  const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4855
5499
  let html = "";
4856
5500
  let inList = false;
4857
- for (const rawLine of lines) {
4858
- const line = rawLine.trim();
5501
+ let tableLines = [];
5502
+ const flushTable = () => {
5503
+ if (tableLines.length > 0) {
5504
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 12px;">';
5505
+ for (const tLine of tableLines) {
5506
+ html += "<tr>";
5507
+ const cols = tLine.split(" ");
5508
+ for (const col of cols) {
5509
+ html += `<td style="border: 1px solid #cbd5e1; padding: 6px 8px;">${DOMPurify6.sanitize(col.trim())}</td>`;
5510
+ }
5511
+ html += "</tr>";
5512
+ }
5513
+ html += "</table>";
5514
+ tableLines = [];
5515
+ }
5516
+ };
5517
+ let i = 0;
5518
+ while (i < lines.length) {
5519
+ let line = lines[i];
5520
+ let tabCount = (line.match(/\t/g) || []).length;
5521
+ if (tabCount > 0) {
5522
+ let consecutiveTableLines = 1;
5523
+ let j = i + 1;
5524
+ while (j < lines.length) {
5525
+ const nextTabCount = (lines[j].match(/\t/g) || []).length;
5526
+ if (nextTabCount === tabCount) {
5527
+ consecutiveTableLines++;
5528
+ j++;
5529
+ } else {
5530
+ break;
5531
+ }
5532
+ }
5533
+ if (consecutiveTableLines >= 3) {
5534
+ if (inList) {
5535
+ html += "</ul>";
5536
+ inList = false;
5537
+ }
5538
+ tableLines = lines.slice(i, j);
5539
+ flushTable();
5540
+ i = j;
5541
+ continue;
5542
+ }
5543
+ }
5544
+ line = line.trim();
4859
5545
  if (!line) {
4860
5546
  if (inList) {
4861
5547
  html += "</ul>";
4862
5548
  inList = false;
4863
5549
  }
5550
+ html += '<div style="height: 1.15em;"></div>';
5551
+ i++;
4864
5552
  continue;
4865
5553
  }
4866
- if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) continue;
4867
- if (line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman") && line.length < 30) {
5554
+ if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) {
5555
+ i++;
5556
+ continue;
5557
+ }
5558
+ if ((line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman")) && line.length < 30) {
5559
+ i++;
4868
5560
  continue;
4869
5561
  }
4870
5562
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
@@ -4872,24 +5564,26 @@ var DocPlugin = class {
4872
5564
  html += "</ul>";
4873
5565
  inList = false;
4874
5566
  }
4875
- 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>`;
5567
+ 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>`;
4876
5568
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4877
5569
  if (!inList) {
4878
5570
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4879
5571
  inList = true;
4880
5572
  }
4881
5573
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4882
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2.sanitize(bulletText)}</li>`;
5574
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText)}</li>`;
4883
5575
  } else {
4884
5576
  if (inList) {
4885
5577
  html += "</ul>";
4886
5578
  inList = false;
4887
5579
  }
4888
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2.sanitize(line)}</p>`;
5580
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
4889
5581
  }
5582
+ i++;
4890
5583
  }
4891
5584
  if (inList) html += "</ul>";
4892
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2.sanitize(filename)})</p>`;
5585
+ flushTable();
5586
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6.sanitize(filename)})</p>`;
4893
5587
  }
4894
5588
  };
4895
5589
  function docPlugin() {
@@ -4966,6 +5660,14 @@ var PptPlugin = class {
4966
5660
  group: "zoom",
4967
5661
  execute: () => instance.fitToPage?.()
4968
5662
  },
5663
+ {
5664
+ id: "rotate-cw",
5665
+ icon: "rotate-cw",
5666
+ label: "Rotate",
5667
+ type: "button",
5668
+ group: "view",
5669
+ execute: () => instance.rotateCW?.()
5670
+ },
4969
5671
  {
4970
5672
  id: "download",
4971
5673
  icon: "download",
@@ -4999,22 +5701,36 @@ var PptPlugin = class {
4999
5701
  const slideCard = document.createElement("div");
5000
5702
  slideCard.className = "fp-ppt-slide-card";
5001
5703
  slideCard.style.width = "960px";
5002
- slideCard.style.maxWidth = "92%";
5704
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5705
+ slideCard.style.maxHeight = "calc(100% - 48px)";
5003
5706
  slideCard.style.aspectRatio = "16 / 9";
5004
5707
  slideCard.style.backgroundColor = "#ffffff";
5005
5708
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
5006
5709
  slideCard.style.borderRadius = "8px";
5007
5710
  slideCard.style.boxSizing = "border-box";
5008
5711
  slideCard.style.position = "relative";
5009
- slideCard.style.overflow = "hidden";
5010
- slideCard.style.transformOrigin = "center center";
5011
5712
  slideCard.style.transition = "transform 0.2s ease";
5713
+ slideCard.style.transformOrigin = "center center";
5714
+ slideCard.style.flexShrink = "0";
5012
5715
  container.appendChild(slideCard);
5013
5716
  ctx.container.appendChild(container);
5014
5717
  let scale = 1;
5718
+ let rotation = 0;
5015
5719
  let currentSlide = 1;
5016
5720
  let slides = [];
5017
5721
  const createdBlobUrls = [];
5722
+ const calculateFitScale = () => {
5723
+ const availW = Math.max(200, container.clientWidth - 48);
5724
+ const availH = Math.max(200, container.clientHeight - 72);
5725
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5726
+ };
5727
+ const applyTransform = () => {
5728
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5729
+ };
5730
+ setTimeout(() => {
5731
+ scale = calculateFitScale();
5732
+ applyTransform();
5733
+ }, 60);
5018
5734
  try {
5019
5735
  const cfbf = new CfbfReader(ctx.buffer);
5020
5736
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -5045,7 +5761,7 @@ var PptPlugin = class {
5045
5761
  if (s.pictureUrl) {
5046
5762
  contentHtml = `
5047
5763
  <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
5048
- <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;" />
5764
+ <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;" />
5049
5765
  </div>
5050
5766
  `;
5051
5767
  } else if (s.tableColumns.length > 0) {
@@ -5055,7 +5771,7 @@ var PptPlugin = class {
5055
5771
  <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
5056
5772
  <thead>
5057
5773
  <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
5058
- ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2.sanitize(c)}</th>`).join("")}
5774
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify6.sanitize(c)}</th>`).join("")}
5059
5775
  </tr>
5060
5776
  </thead>
5061
5777
  <tbody>
@@ -5071,7 +5787,7 @@ var PptPlugin = class {
5071
5787
  } else {
5072
5788
  const pTags = s.paragraphs.map((p) => {
5073
5789
  const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
5074
- 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("");
5790
+ 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("");
5075
5791
  }).join("");
5076
5792
  contentHtml = `
5077
5793
  <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
@@ -5084,11 +5800,11 @@ var PptPlugin = class {
5084
5800
  <!-- Header Banner matching PowerPoint design -->
5085
5801
  <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;">
5086
5802
  <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
5087
- ${DOMPurify2.sanitize(s.title)}
5803
+ ${DOMPurify6.sanitize(s.title)}
5088
5804
  </h1>
5089
5805
  ${s.subtitle ? `
5090
5806
  <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);">
5091
- ${DOMPurify2.sanitize(s.subtitle)}
5807
+ ${DOMPurify6.sanitize(s.subtitle)}
5092
5808
  </span>
5093
5809
  ` : `
5094
5810
  <span style="font-size: 12px; color: #365314; font-weight: 600;">
@@ -5115,21 +5831,30 @@ var PptPlugin = class {
5115
5831
  return {
5116
5832
  destroy: cleanup,
5117
5833
  zoomIn: () => {
5118
- scale += 0.1;
5119
- slideCard.style.transform = `scale(${scale})`;
5834
+ scale += 0.15;
5835
+ applyTransform();
5120
5836
  },
5121
5837
  zoomOut: () => {
5122
- scale = Math.max(0.3, scale - 0.1);
5123
- slideCard.style.transform = `scale(${scale})`;
5838
+ scale = Math.max(0.2, scale - 0.15);
5839
+ applyTransform();
5124
5840
  },
5125
5841
  getZoom: () => scale,
5126
5842
  setZoom: (level) => {
5127
5843
  scale = level;
5128
- slideCard.style.transform = `scale(${scale})`;
5844
+ applyTransform();
5129
5845
  },
5130
5846
  fitToPage: () => {
5131
- scale = 1;
5132
- slideCard.style.transform = "scale(1)";
5847
+ scale = calculateFitScale();
5848
+ rotation = 0;
5849
+ applyTransform();
5850
+ },
5851
+ rotateCW: () => {
5852
+ rotation = (rotation + 90) % 360;
5853
+ applyTransform();
5854
+ },
5855
+ rotateCCW: () => {
5856
+ rotation = (rotation - 90 + 360) % 360;
5857
+ applyTransform();
5133
5858
  },
5134
5859
  goToPage: (page) => {
5135
5860
  if (page >= 1 && page <= totalSlides) {