@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.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var DOMPurify2 = require('dompurify');
3
+ var DOMPurify6 = require('dompurify');
4
4
  var pdfjsLib = require('pdfjs-dist');
5
5
  var docx = require('docx-preview');
6
6
  var fflate = require('fflate');
@@ -34,7 +34,7 @@ function _interopNamespace(e) {
34
34
  return Object.freeze(n);
35
35
  }
36
36
 
37
- var DOMPurify2__default = /*#__PURE__*/_interopDefault(DOMPurify2);
37
+ var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
38
38
  var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
39
39
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
40
40
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
@@ -386,7 +386,7 @@ async function sourceToArrayBuffer(source, signal) {
386
386
  if (magicMime === "application/zip") {
387
387
  const ooxmlMime = detectOoxmlType(buffer);
388
388
  if (ooxmlMime && ooxmlMime !== "application/zip") {
389
- metadata.mimeType = ooxmlMime;
389
+ metadata.mimeType = metadata.mimeType ?? ooxmlMime;
390
390
  if (ooxmlMime.includes("wordprocessing")) metadata.extension = metadata.extension ?? ".docx";
391
391
  else if (ooxmlMime.includes("spreadsheet")) metadata.extension = metadata.extension ?? ".xlsx";
392
392
  else if (ooxmlMime.includes("presentation")) metadata.extension = metadata.extension ?? ".pptx";
@@ -415,12 +415,12 @@ async function sourceToArrayBuffer(source, signal) {
415
415
  return { buffer, metadata };
416
416
  }
417
417
  function sanitizeHTML(html) {
418
- return DOMPurify2__default.default.sanitize(html, {
418
+ return DOMPurify6__default.default.sanitize(html, {
419
419
  USE_PROFILES: { html: true }
420
420
  });
421
421
  }
422
422
  function sanitizeSVG(svg) {
423
- return DOMPurify2__default.default.sanitize(svg, {
423
+ return DOMPurify6__default.default.sanitize(svg, {
424
424
  USE_PROFILES: { svg: true, svgFilters: true }
425
425
  });
426
426
  }
@@ -778,6 +778,8 @@ var FilePreviewViewer = class {
778
778
  keyHandler = null;
779
779
  currentContainer = null;
780
780
  currentOptions = {};
781
+ resizeObserver = null;
782
+ fullscreenHandler = null;
781
783
  /**
782
784
  * Register a preview plugin.
783
785
  */
@@ -850,12 +852,31 @@ var FilePreviewViewer = class {
850
852
  label: "Toggle Fullscreen",
851
853
  type: "button",
852
854
  group: "view",
853
- execute: () => {
854
- if (!document.fullscreenElement) {
855
- this.wrapperEl?.requestFullscreen?.();
856
- } else {
857
- document.exitFullscreen?.();
855
+ execute: async () => {
856
+ try {
857
+ const isNativeFs = !!document.fullscreenElement;
858
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
859
+ if (!isNativeFs && !isCssFs) {
860
+ if (this.wrapperEl?.requestFullscreen) {
861
+ await this.wrapperEl.requestFullscreen().catch(() => {
862
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
863
+ });
864
+ } else {
865
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
866
+ }
867
+ } else {
868
+ if (document.fullscreenElement) {
869
+ await document.exitFullscreen().catch(() => {
870
+ });
871
+ }
872
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
873
+ }
874
+ } catch {
875
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
858
876
  }
877
+ setTimeout(() => {
878
+ this.activeInstance?.fitToPage?.();
879
+ }, 120);
859
880
  }
860
881
  });
861
882
  }
@@ -905,6 +926,15 @@ var FilePreviewViewer = class {
905
926
  window.removeEventListener("keydown", this.keyHandler);
906
927
  this.keyHandler = null;
907
928
  }
929
+ if (this.resizeObserver) {
930
+ this.resizeObserver.disconnect();
931
+ this.resizeObserver = null;
932
+ }
933
+ if (this.fullscreenHandler) {
934
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
935
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
936
+ this.fullscreenHandler = null;
937
+ }
908
938
  this.abort();
909
939
  this.destroyInstance();
910
940
  this.toolbar?.destroy();
@@ -984,6 +1014,25 @@ var FilePreviewViewer = class {
984
1014
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
985
1015
  this.setupKeyboardShortcuts();
986
1016
  this.setupDragAndDrop(container, options);
1017
+ this.setupResizeAndFullscreenListeners();
1018
+ }
1019
+ setupResizeAndFullscreenListeners() {
1020
+ if (this.fullscreenHandler) return;
1021
+ this.fullscreenHandler = () => {
1022
+ setTimeout(() => {
1023
+ this.activeInstance?.fitToPage?.();
1024
+ }, 100);
1025
+ };
1026
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
1027
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
1028
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1029
+ this.resizeObserver = new ResizeObserver(() => {
1030
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1031
+ this.activeInstance.fitToPage?.();
1032
+ }
1033
+ });
1034
+ this.resizeObserver.observe(this.contentEl);
1035
+ }
987
1036
  }
988
1037
  setupKeyboardShortcuts() {
989
1038
  if (this.keyHandler) return;
@@ -1375,7 +1424,8 @@ var PdfPlugin = class {
1375
1424
  container.style.display = "flex";
1376
1425
  container.style.flexDirection = "column";
1377
1426
  container.style.alignItems = "center";
1378
- container.style.padding = "24px 16px";
1427
+ container.style.justifyContent = "flex-start";
1428
+ container.style.padding = "20px 16px";
1379
1429
  container.style.backgroundColor = "#0f172a";
1380
1430
  container.style.boxSizing = "border-box";
1381
1431
  container.style.position = "relative";
@@ -1388,7 +1438,8 @@ var PdfPlugin = class {
1388
1438
  pageCard.style.lineHeight = "0";
1389
1439
  pageCard.style.transition = "transform 0.15s ease";
1390
1440
  pageCard.style.position = "relative";
1391
- const canvas = document.createElement("canvas");
1441
+ pageCard.style.flexShrink = "0";
1442
+ let canvas = document.createElement("canvas");
1392
1443
  pageCard.appendChild(canvas);
1393
1444
  container.appendChild(pageCard);
1394
1445
  const indicator = document.createElement("div");
@@ -1430,14 +1481,22 @@ var PdfPlugin = class {
1430
1481
  }
1431
1482
  currentRenderTask = null;
1432
1483
  }
1484
+ const newCanvas = document.createElement("canvas");
1485
+ pageCard.replaceChild(newCanvas, canvas);
1486
+ canvas = newCanvas;
1433
1487
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1434
1488
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1435
1489
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1436
1490
  const page = await pdfDoc.getPage(currentPage);
1437
1491
  const containerWidth = container.clientWidth || 900;
1492
+ const containerHeight = container.clientHeight || 700;
1438
1493
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1439
- const baseScale = Math.min((containerWidth - 64) / unscaledVp.width, 1.6);
1440
- const effectiveScale = (baseScale > 0 ? baseScale : 1) * zoomScale;
1494
+ const availWidth = Math.max(100, containerWidth - 48);
1495
+ const availHeight = Math.max(100, containerHeight - 88);
1496
+ const scaleW = availWidth / unscaledVp.width;
1497
+ const scaleH = availHeight / unscaledVp.height;
1498
+ const fitScale = Math.min(scaleW, scaleH);
1499
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1441
1500
  const pixelRatio = window.devicePixelRatio || 1;
1442
1501
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
1443
1502
  canvas.width = Math.floor(viewport.width * pixelRatio);
@@ -1462,7 +1521,19 @@ var PdfPlugin = class {
1462
1521
  }
1463
1522
  };
1464
1523
  await renderPage(1);
1524
+ let resizeTimer = null;
1525
+ const resizeObserver = new ResizeObserver(() => {
1526
+ if (resizeTimer) clearTimeout(resizeTimer);
1527
+ resizeTimer = setTimeout(() => {
1528
+ if (Math.abs(zoomScale - 1) < 0.05) {
1529
+ renderPage(currentPage);
1530
+ }
1531
+ }, 150);
1532
+ });
1533
+ resizeObserver.observe(container);
1465
1534
  const cleanup = () => {
1535
+ resizeObserver.disconnect();
1536
+ if (resizeTimer) clearTimeout(resizeTimer);
1466
1537
  if (currentRenderTask) {
1467
1538
  try {
1468
1539
  currentRenderTask.cancel();
@@ -1494,6 +1565,7 @@ var PdfPlugin = class {
1494
1565
  },
1495
1566
  fitToPage: () => {
1496
1567
  zoomScale = 1;
1568
+ rotation = 0;
1497
1569
  renderPage(currentPage);
1498
1570
  },
1499
1571
  rotateCW: () => {
@@ -1944,6 +2016,14 @@ var DocxPlugin = class {
1944
2016
  group: "zoom",
1945
2017
  execute: () => instance.fitToPage?.()
1946
2018
  },
2019
+ {
2020
+ id: "rotate-cw",
2021
+ icon: "rotate-cw",
2022
+ label: "Rotate",
2023
+ type: "button",
2024
+ group: "view",
2025
+ execute: () => instance.rotateCW?.()
2026
+ },
1947
2027
  {
1948
2028
  id: "download",
1949
2029
  icon: "download",
@@ -2080,6 +2160,26 @@ var DocxPlugin = class {
2080
2160
  if (totalPages > 1) {
2081
2161
  showPage(1);
2082
2162
  }
2163
+ scale = 1;
2164
+ let rotation = 0;
2165
+ const calculateFitScale = () => {
2166
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2167
+ const elW = activeEl.offsetWidth || 816;
2168
+ const elH = activeEl.offsetHeight || 1056;
2169
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2170
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2171
+ const sW = availW / elW;
2172
+ const sH = availH / elH;
2173
+ return Math.min(1.1, Math.min(sW, sH));
2174
+ };
2175
+ const applyTransform = () => {
2176
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2177
+ wrapper.style.transformOrigin = "top center";
2178
+ };
2179
+ setTimeout(() => {
2180
+ scale = calculateFitScale();
2181
+ applyTransform();
2182
+ }, 60);
2083
2183
  const cleanup = () => {
2084
2184
  indicator?.remove();
2085
2185
  for (const url of createdBlobUrls) {
@@ -2098,21 +2198,30 @@ var DocxPlugin = class {
2098
2198
  showPage(page);
2099
2199
  },
2100
2200
  zoomIn: () => {
2101
- scale += 0.1;
2102
- wrapper.style.transform = `scale(${scale})`;
2201
+ scale += 0.15;
2202
+ applyTransform();
2103
2203
  },
2104
2204
  zoomOut: () => {
2105
- scale = Math.max(0.2, scale - 0.1);
2106
- wrapper.style.transform = `scale(${scale})`;
2205
+ scale = Math.max(0.2, scale - 0.15);
2206
+ applyTransform();
2107
2207
  },
2108
2208
  getZoom: () => scale,
2109
2209
  setZoom: (level) => {
2110
2210
  scale = level;
2111
- wrapper.style.transform = `scale(${scale})`;
2211
+ applyTransform();
2112
2212
  },
2113
2213
  fitToPage: () => {
2114
- scale = 1;
2115
- wrapper.style.transform = "scale(1)";
2214
+ scale = calculateFitScale();
2215
+ rotation = 0;
2216
+ applyTransform();
2217
+ },
2218
+ rotateCW: () => {
2219
+ rotation = (rotation + 90) % 360;
2220
+ applyTransform();
2221
+ },
2222
+ rotateCCW: () => {
2223
+ rotation = (rotation - 90 + 360) % 360;
2224
+ applyTransform();
2116
2225
  },
2117
2226
  download: () => {
2118
2227
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2172,90 +2281,149 @@ var DocxPlugin = class {
2172
2281
  console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
2173
2282
  }
2174
2283
  }
2175
- const card = document.createElement("div");
2176
- card.className = "fp-docx-page-card";
2177
- card.style.backgroundColor = "#ffffff";
2178
- card.style.borderRadius = "6px";
2179
- card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2180
- card.style.padding = "48px 56px";
2181
- card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2182
- card.style.color = "#1e293b";
2183
- card.style.lineHeight = "1.6";
2284
+ const sectPrs = Array.from(doc.getElementsByTagNameNS("*", "sectPr"));
2285
+ const lastSectPr = sectPrs[sectPrs.length - 1];
2286
+ let defaultW = 12240;
2287
+ let defaultH = 15840;
2288
+ let margins = { top: 1440, right: 1440, bottom: 1440, left: 1440 };
2289
+ if (lastSectPr) {
2290
+ const pgSz = lastSectPr.getElementsByTagNameNS("*", "pgSz")[0];
2291
+ if (pgSz) {
2292
+ defaultW = parseInt(pgSz.getAttribute("w:w") || pgSz.getAttribute("w") || "12240", 10);
2293
+ defaultH = parseInt(pgSz.getAttribute("w:h") || pgSz.getAttribute("h") || "15840", 10);
2294
+ }
2295
+ const pgMar = lastSectPr.getElementsByTagNameNS("*", "pgMar")[0];
2296
+ if (pgMar) {
2297
+ margins.top = parseInt(pgMar.getAttribute("w:top") || pgMar.getAttribute("top") || "1440", 10);
2298
+ margins.bottom = parseInt(pgMar.getAttribute("w:bottom") || pgMar.getAttribute("bottom") || "1440", 10);
2299
+ margins.left = parseInt(pgMar.getAttribute("w:left") || pgMar.getAttribute("left") || "1440", 10);
2300
+ margins.right = parseInt(pgMar.getAttribute("w:right") || pgMar.getAttribute("right") || "1440", 10);
2301
+ }
2302
+ }
2303
+ const createPageCard = () => {
2304
+ const card = document.createElement("div");
2305
+ card.className = "fp-docx-page-card";
2306
+ card.style.backgroundColor = "#ffffff";
2307
+ card.style.borderRadius = "4px";
2308
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2309
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2310
+ card.style.color = "#1e293b";
2311
+ card.style.lineHeight = "1.6";
2312
+ card.style.boxSizing = "border-box";
2313
+ card.style.overflow = "hidden";
2314
+ card.style.position = "relative";
2315
+ card.style.marginBottom = "24px";
2316
+ card.style.width = `${defaultW / 15}px`;
2317
+ card.style.height = `${defaultH / 15}px`;
2318
+ card.style.padding = `${margins.top / 15}px ${margins.right / 15}px ${margins.bottom / 15}px ${margins.left / 15}px`;
2319
+ return card;
2320
+ };
2321
+ let currentCard = createPageCard();
2322
+ let currentHtml = "";
2323
+ const pages = [{ card: currentCard, html: "" }];
2324
+ const flushHtml = () => {
2325
+ pages[pages.length - 1].html += currentHtml;
2326
+ currentHtml = "";
2327
+ };
2328
+ const newPage = () => {
2329
+ flushHtml();
2330
+ currentCard = createPageCard();
2331
+ pages.push({ card: currentCard, html: "" });
2332
+ };
2184
2333
  const body = doc.getElementsByTagNameNS("*", "body")[0] || doc.documentElement;
2185
- let html = "";
2186
2334
  for (const child of Array.from(body.children)) {
2187
2335
  const tag = child.localName || child.nodeName.split(":").pop();
2188
2336
  if (tag === "p") {
2189
2337
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
2190
2338
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
2191
2339
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
2192
- const textContent = this.extractParagraphHtml(child, imageMap);
2193
- if (!textContent.trim()) {
2194
- html += '<div style="height: 10px;"></div>';
2195
- continue;
2196
- }
2197
- const lowerStyle = styleVal.toLowerCase();
2198
- if (lowerStyle.includes("title")) {
2199
- 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>`;
2200
- } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2201
- html += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2202
- } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2203
- html += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2204
- } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2205
- html += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2206
- } else if (numPr) {
2207
- 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>`;
2208
- } else {
2209
- html += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2340
+ const chunks = this.extractParagraphChunks(child, imageMap);
2341
+ for (let i = 0; i < chunks.length; i++) {
2342
+ if (i > 0) {
2343
+ newPage();
2344
+ }
2345
+ const textContent = chunks[i];
2346
+ if (!textContent.trim() && !textContent.includes("<img")) {
2347
+ currentHtml += '<div style="height: 10px;"></div>';
2348
+ continue;
2349
+ }
2350
+ const lowerStyle = styleVal.toLowerCase();
2351
+ if (lowerStyle.includes("title")) {
2352
+ 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>`;
2353
+ } else if (lowerStyle.includes("heading1") || styleVal === "1") {
2354
+ currentHtml += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
2355
+ } else if (lowerStyle.includes("heading2") || styleVal === "2") {
2356
+ currentHtml += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
2357
+ } else if (lowerStyle.includes("heading3") || styleVal === "3") {
2358
+ currentHtml += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
2359
+ } else if (numPr) {
2360
+ 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>`;
2361
+ } else {
2362
+ currentHtml += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
2363
+ }
2210
2364
  }
2211
2365
  } else if (tag === "tbl") {
2212
- html += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2366
+ currentHtml += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
2213
2367
  const rows = Array.from(child.getElementsByTagNameNS("*", "tr"));
2214
2368
  rows.forEach((tr, rIdx) => {
2215
- html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2369
+ currentHtml += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
2216
2370
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
2217
2371
  cells.forEach((tc) => {
2218
2372
  const cellText = this.extractParagraphHtml(tc, imageMap);
2219
- html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2373
+ currentHtml += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
2220
2374
  });
2221
- html += "</tr>";
2375
+ currentHtml += "</tr>";
2222
2376
  });
2223
- html += "</table>";
2377
+ currentHtml += "</table>";
2224
2378
  }
2225
2379
  }
2226
- card.innerHTML = DOMPurify2__default.default.sanitize(html, {
2227
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2228
- ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2229
- });
2230
- wrapper.appendChild(card);
2380
+ flushHtml();
2381
+ for (const page of pages) {
2382
+ page.card.innerHTML = DOMPurify6__default.default.sanitize(page.html, {
2383
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
2384
+ ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
2385
+ });
2386
+ wrapper.appendChild(page.card);
2387
+ }
2231
2388
  }
2232
2389
  extractParagraphHtml(pElement, imageMap = {}) {
2233
- let result = "";
2390
+ return this.extractParagraphChunks(pElement, imageMap).join("<br/>");
2391
+ }
2392
+ extractParagraphChunks(pElement, imageMap = {}) {
2393
+ const chunks = [""];
2394
+ let currentChunkIndex = 0;
2234
2395
  const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
2235
2396
  for (const drawing of drawings) {
2236
2397
  const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
2237
2398
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2238
2399
  if (rId && imageMap[rId]) {
2239
- result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2400
+ chunks[currentChunkIndex] += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2240
2401
  }
2241
2402
  }
2242
2403
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
2243
- if (runs.length === 0 && !result) {
2244
- return DOMPurify2__default.default.sanitize(pElement.textContent || "");
2404
+ if (runs.length === 0 && !chunks[currentChunkIndex]) {
2405
+ chunks[currentChunkIndex] = DOMPurify6__default.default.sanitize(pElement.textContent || "");
2406
+ return chunks;
2245
2407
  }
2246
2408
  for (const r of runs) {
2247
2409
  const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
2248
2410
  const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2249
2411
  if (rId && imageMap[rId]) {
2250
- result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2412
+ chunks[currentChunkIndex] += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2251
2413
  }
2252
- const brs = r.getElementsByTagNameNS("*", "br");
2253
- for (let i = 0; i < brs.length; i++) {
2254
- result += "<br/>";
2414
+ const brs = Array.from(r.getElementsByTagNameNS("*", "br"));
2415
+ for (const br of brs) {
2416
+ const type = br.getAttribute("w:type") || br.getAttribute("type");
2417
+ if (type === "page") {
2418
+ chunks.push("");
2419
+ currentChunkIndex++;
2420
+ } else {
2421
+ chunks[currentChunkIndex] += "<br/>";
2422
+ }
2255
2423
  }
2256
2424
  const tabs = r.getElementsByTagNameNS("*", "tab");
2257
2425
  if (tabs.length > 0) {
2258
- result += "&emsp;";
2426
+ chunks[currentChunkIndex] += "&emsp;";
2259
2427
  }
2260
2428
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
2261
2429
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
@@ -2269,7 +2437,7 @@ var DocxPlugin = class {
2269
2437
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
2270
2438
  let text = texts.map((t) => t.textContent || "").join("");
2271
2439
  if (!text) continue;
2272
- text = DOMPurify2__default.default.sanitize(text);
2440
+ text = DOMPurify6__default.default.sanitize(text);
2273
2441
  let styles = "";
2274
2442
  if (isBold) styles += "font-weight: bold; ";
2275
2443
  if (isItalic) styles += "font-style: italic; ";
@@ -2281,12 +2449,12 @@ var DocxPlugin = class {
2281
2449
  if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2282
2450
  }
2283
2451
  if (styles) {
2284
- result += `<span style="${styles}">${text}</span>`;
2452
+ chunks[currentChunkIndex] += `<span style="${styles}">${text}</span>`;
2285
2453
  } else {
2286
- result += text;
2454
+ chunks[currentChunkIndex] += text;
2287
2455
  }
2288
2456
  }
2289
- return result;
2457
+ return chunks;
2290
2458
  }
2291
2459
  renderBinaryDocFallback(ctx, wrapper) {
2292
2460
  const card = document.createElement("div");
@@ -2303,14 +2471,14 @@ var DocxPlugin = class {
2303
2471
  const wordDocStream = cfbf.readStream("WordDocument");
2304
2472
  if (wordDocStream && wordDocStream.length >= 512) {
2305
2473
  const text2 = this.extractReadableStrings(wordDocStream);
2306
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text2)}</div>`;
2474
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6__default.default.sanitize(text2)}</div>`;
2307
2475
  wrapper.appendChild(card);
2308
2476
  return;
2309
2477
  }
2310
2478
  } catch {
2311
2479
  }
2312
2480
  const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2313
- card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text)}</div>`;
2481
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify6__default.default.sanitize(text)}</div>`;
2314
2482
  wrapper.appendChild(card);
2315
2483
  }
2316
2484
  extractReadableStrings(bytes) {
@@ -2406,6 +2574,26 @@ var ExcelPlugin = class {
2406
2574
  instance.zoomIn?.();
2407
2575
  }
2408
2576
  },
2577
+ {
2578
+ id: "fit-page",
2579
+ icon: "fit-page",
2580
+ label: "Fit to View",
2581
+ type: "button",
2582
+ group: "zoom",
2583
+ execute: () => {
2584
+ instance.fitToPage?.();
2585
+ }
2586
+ },
2587
+ {
2588
+ id: "rotate-cw",
2589
+ icon: "rotate-cw",
2590
+ label: "Rotate",
2591
+ type: "button",
2592
+ group: "view",
2593
+ execute: () => {
2594
+ instance.rotateCW?.();
2595
+ }
2596
+ },
2409
2597
  {
2410
2598
  id: "download",
2411
2599
  icon: "download",
@@ -2456,9 +2644,23 @@ var ExcelPlugin = class {
2456
2644
  container.appendChild(tabsArea);
2457
2645
  ctx.container.appendChild(container);
2458
2646
  let scale = 1;
2647
+ let rotation = 0;
2459
2648
  let currentSheetIndex = 1;
2460
2649
  let sheetNames = [];
2461
2650
  let wb = null;
2651
+ const applyTransform = () => {
2652
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2653
+ contentArea.style.transformOrigin = "top left";
2654
+ };
2655
+ const calculateFitScale = () => {
2656
+ const table = contentArea.querySelector("table");
2657
+ if (!table) return 1;
2658
+ const availW = Math.max(200, container.clientWidth - 48);
2659
+ const availH = Math.max(200, container.clientHeight - 80);
2660
+ const tW = table.offsetWidth || 800;
2661
+ const tH = table.offsetHeight || 600;
2662
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2663
+ };
2462
2664
  try {
2463
2665
  wb = XLSX__namespace.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2464
2666
  sheetNames = wb.SheetNames || [];
@@ -2546,17 +2748,30 @@ var ExcelPlugin = class {
2546
2748
  return {
2547
2749
  destroy: cleanup,
2548
2750
  zoomIn: () => {
2549
- scale += 0.1;
2550
- contentArea.style.transform = `scale(${scale})`;
2751
+ scale += 0.15;
2752
+ applyTransform();
2551
2753
  },
2552
2754
  zoomOut: () => {
2553
- scale = Math.max(0.2, scale - 0.1);
2554
- contentArea.style.transform = `scale(${scale})`;
2755
+ scale = Math.max(0.2, scale - 0.15);
2756
+ applyTransform();
2555
2757
  },
2556
2758
  getZoom: () => scale,
2557
2759
  setZoom: (level) => {
2558
2760
  scale = level;
2559
- contentArea.style.transform = `scale(${scale})`;
2761
+ applyTransform();
2762
+ },
2763
+ fitToPage: () => {
2764
+ scale = calculateFitScale();
2765
+ rotation = 0;
2766
+ applyTransform();
2767
+ },
2768
+ rotateCW: () => {
2769
+ rotation = (rotation + 90) % 360;
2770
+ applyTransform();
2771
+ },
2772
+ rotateCCW: () => {
2773
+ rotation = (rotation - 90 + 360) % 360;
2774
+ applyTransform();
2560
2775
  },
2561
2776
  goToPage: (page) => {
2562
2777
  if (page > 0 && page <= sheetNames.length) {
@@ -2806,6 +3021,26 @@ var CodePlugin = class {
2806
3021
  instance.zoomIn?.();
2807
3022
  }
2808
3023
  },
3024
+ {
3025
+ id: "fit-page",
3026
+ icon: "fit-page",
3027
+ label: "Fit to View",
3028
+ type: "button",
3029
+ group: "zoom",
3030
+ execute: () => {
3031
+ instance.fitToPage?.();
3032
+ }
3033
+ },
3034
+ {
3035
+ id: "rotate-cw",
3036
+ icon: "rotate-cw",
3037
+ label: "Rotate",
3038
+ type: "button",
3039
+ group: "view",
3040
+ execute: () => {
3041
+ instance.rotateCW?.();
3042
+ }
3043
+ },
2809
3044
  {
2810
3045
  id: "copy",
2811
3046
  icon: "copy",
@@ -2842,40 +3077,94 @@ var CodePlugin = class {
2842
3077
  async render(ctx) {
2843
3078
  const decoder = new TextDecoder("utf-8");
2844
3079
  const fullText = decoder.decode(ctx.buffer);
2845
- const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2846
- const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3080
+ const extRaw = ctx.metadata.extension?.toLowerCase();
3081
+ const mimeRaw = ctx.metadata.mimeType?.toLowerCase();
3082
+ const isTxt = extRaw === ".txt" || mimeRaw === "text/plain" && (!extRaw || !this.extensions.filter((e) => e !== ".txt").includes(extRaw));
3083
+ let rawPages = [];
3084
+ if (isTxt) {
3085
+ const explicitPages = fullText.split(/(?:\f|\x0C)/);
3086
+ for (const ep of explicitPages) {
3087
+ const lines = ep.split(/\r?\n/);
3088
+ let currentChunk = [];
3089
+ for (let i = 0; i < lines.length; i++) {
3090
+ currentChunk.push(lines[i]);
3091
+ if (currentChunk.length >= 46) {
3092
+ rawPages.push(currentChunk.join("\n"));
3093
+ currentChunk = [];
3094
+ }
3095
+ }
3096
+ if (currentChunk.length > 0 || lines.length === 0) {
3097
+ rawPages.push(currentChunk.join("\n"));
3098
+ }
3099
+ }
3100
+ if (rawPages.length === 0) rawPages = [""];
3101
+ } else {
3102
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
3103
+ rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3104
+ }
2847
3105
  const totalPages = Math.max(1, rawPages.length);
2848
3106
  let currentPage = 1;
2849
3107
  const container = document.createElement("div");
2850
3108
  container.style.width = "100%";
2851
3109
  container.style.height = "100%";
2852
3110
  container.style.overflow = "auto";
2853
- container.style.backgroundColor = "#1e1e1e";
2854
- container.style.color = "#d4d4d4";
2855
- container.style.padding = "16px";
2856
- container.style.boxSizing = "border-box";
2857
3111
  let fontSize = 13;
3112
+ let rotation = 0;
3113
+ let zoomLevel = 1;
2858
3114
  const pre = document.createElement("pre");
2859
- pre.style.margin = "0";
2860
- pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
2861
- pre.style.fontSize = `${fontSize}px`;
2862
- pre.style.lineHeight = "1.5";
2863
- pre.style.whiteSpace = "pre-wrap";
2864
- pre.style.wordBreak = "break-all";
2865
3115
  const code = document.createElement("code");
3116
+ if (isTxt) {
3117
+ container.style.backgroundColor = "#f1f5f9";
3118
+ container.style.padding = "32px";
3119
+ container.style.boxSizing = "border-box";
3120
+ container.style.display = "flex";
3121
+ container.style.flexDirection = "column";
3122
+ container.style.alignItems = "center";
3123
+ pre.style.margin = "0";
3124
+ pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3125
+ pre.style.fontSize = "13px";
3126
+ pre.style.color = "#1e293b";
3127
+ pre.style.lineHeight = "1.5";
3128
+ pre.style.whiteSpace = "pre-wrap";
3129
+ pre.style.wordBreak = "break-word";
3130
+ pre.style.backgroundColor = "#ffffff";
3131
+ pre.style.width = "816px";
3132
+ pre.style.minHeight = "1056px";
3133
+ pre.style.padding = "72px 56px";
3134
+ pre.style.boxSizing = "border-box";
3135
+ pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
3136
+ pre.style.borderRadius = "4px";
3137
+ pre.style.transformOrigin = "top center";
3138
+ } else {
3139
+ container.style.backgroundColor = "#1e1e1e";
3140
+ container.style.color = "#d4d4d4";
3141
+ container.style.padding = "16px";
3142
+ container.style.boxSizing = "border-box";
3143
+ pre.style.margin = "0";
3144
+ pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
3145
+ pre.style.fontSize = `${fontSize}px`;
3146
+ pre.style.lineHeight = "1.5";
3147
+ pre.style.whiteSpace = "pre-wrap";
3148
+ pre.style.wordBreak = "break-all";
3149
+ pre.style.transformOrigin = "top left";
3150
+ }
2866
3151
  const ext = (ctx.metadata.extension || "").replace(".", "");
2867
3152
  const renderCodePage = (text) => {
2868
- try {
2869
- if (ext && hljs__default.default.getLanguage(ext)) {
2870
- code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
2871
- } else {
2872
- code.innerHTML = hljs__default.default.highlightAuto(text).value;
2873
- }
2874
- } catch {
3153
+ if (isTxt) {
2875
3154
  code.textContent = text;
3155
+ } else {
3156
+ try {
3157
+ if (ext && hljs__default.default.getLanguage(ext)) {
3158
+ code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
3159
+ } else {
3160
+ code.innerHTML = hljs__default.default.highlightAuto(text).value;
3161
+ }
3162
+ } catch {
3163
+ code.textContent = text;
3164
+ }
2876
3165
  }
2877
3166
  };
2878
- renderCodePage(rawPages[0] || fullText);
3167
+ renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
2879
3168
  pre.appendChild(code);
2880
3169
  container.appendChild(pre);
2881
3170
  let indicator = null;
@@ -2884,15 +3173,22 @@ var CodePlugin = class {
2884
3173
  indicator.className = "fp-code-page-indicator";
2885
3174
  indicator.style.position = "sticky";
2886
3175
  indicator.style.bottom = "16px";
2887
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2888
- indicator.style.backdropFilter = "blur(8px)";
2889
- indicator.style.color = "#f8fafc";
3176
+ if (isTxt) {
3177
+ indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3178
+ indicator.style.color = "#334155";
3179
+ indicator.style.border = "1px solid #e2e8f0";
3180
+ indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3181
+ } else {
3182
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3183
+ indicator.style.color = "#f8fafc";
3184
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3185
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3186
+ indicator.style.backdropFilter = "blur(8px)";
3187
+ }
2890
3188
  indicator.style.fontSize = "12px";
2891
3189
  indicator.style.fontWeight = "600";
2892
3190
  indicator.style.padding = "5px 14px";
2893
3191
  indicator.style.borderRadius = "20px";
2894
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2895
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2896
3192
  indicator.style.zIndex = "10";
2897
3193
  indicator.style.userSelect = "none";
2898
3194
  indicator.style.pointerEvents = "none";
@@ -2903,7 +3199,7 @@ var CodePlugin = class {
2903
3199
  }
2904
3200
  const showPage = (pageNum) => {
2905
3201
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2906
- renderCodePage(rawPages[currentPage - 1] || fullText);
3202
+ renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
2907
3203
  if (indicator) {
2908
3204
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2909
3205
  }
@@ -2919,23 +3215,57 @@ var CodePlugin = class {
2919
3215
  ctx.container.innerHTML = "";
2920
3216
  };
2921
3217
  ctx.signal.addEventListener("abort", cleanup);
3218
+ const updateTransform = () => {
3219
+ if (isTxt) {
3220
+ pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
3221
+ } else {
3222
+ pre.style.transform = `rotate(${rotation}deg)`;
3223
+ pre.style.fontSize = `${fontSize}px`;
3224
+ }
3225
+ };
2922
3226
  return {
2923
3227
  destroy: cleanup,
2924
3228
  getPageCount: () => totalPages,
2925
3229
  getCurrentPage: () => currentPage,
2926
3230
  goToPage: (page) => showPage(page),
2927
3231
  zoomIn: () => {
2928
- fontSize = Math.min(32, fontSize + 2);
2929
- pre.style.fontSize = `${fontSize}px`;
3232
+ if (isTxt) {
3233
+ zoomLevel = Math.min(3, zoomLevel + 0.1);
3234
+ } else {
3235
+ fontSize = Math.min(32, fontSize + 2);
3236
+ }
3237
+ updateTransform();
2930
3238
  },
2931
3239
  zoomOut: () => {
2932
- fontSize = Math.max(8, fontSize - 2);
2933
- pre.style.fontSize = `${fontSize}px`;
3240
+ if (isTxt) {
3241
+ zoomLevel = Math.max(0.1, zoomLevel - 0.1);
3242
+ } else {
3243
+ fontSize = Math.max(8, fontSize - 2);
3244
+ }
3245
+ updateTransform();
2934
3246
  },
2935
- getZoom: () => fontSize / 13,
3247
+ getZoom: () => isTxt ? zoomLevel : fontSize / 13,
2936
3248
  setZoom: (level) => {
2937
- fontSize = Math.round(13 * level);
2938
- pre.style.fontSize = `${fontSize}px`;
3249
+ if (isTxt) {
3250
+ zoomLevel = level;
3251
+ } else {
3252
+ fontSize = Math.round(13 * level);
3253
+ }
3254
+ updateTransform();
3255
+ },
3256
+ fitToPage: () => {
3257
+ fontSize = 13;
3258
+ rotation = 0;
3259
+ zoomLevel = 1;
3260
+ updateTransform();
3261
+ },
3262
+ rotateCW: () => {
3263
+ rotation = (rotation + 90) % 360;
3264
+ updateTransform();
3265
+ },
3266
+ rotateCCW: () => {
3267
+ rotation = (rotation - 90 + 360) % 360;
3268
+ updateTransform();
2939
3269
  },
2940
3270
  download: () => {
2941
3271
  const mimeType = ctx.metadata.mimeType || "text/plain";
@@ -3234,7 +3564,7 @@ var MarkdownPlugin = class {
3234
3564
  gfm: true,
3235
3565
  breaks: true
3236
3566
  });
3237
- const cleanHtml = DOMPurify2__default.default.sanitize(rawHtml, {
3567
+ const cleanHtml = DOMPurify6__default.default.sanitize(rawHtml, {
3238
3568
  USE_PROFILES: { html: true }
3239
3569
  });
3240
3570
  const wrapper = document.createElement("div");
@@ -3448,6 +3778,14 @@ var PptxPlugin = class {
3448
3778
  group: "zoom",
3449
3779
  execute: () => instance.fitToPage?.()
3450
3780
  },
3781
+ {
3782
+ id: "rotate-cw",
3783
+ icon: "rotate-cw",
3784
+ label: "Rotate",
3785
+ type: "button",
3786
+ group: "view",
3787
+ execute: () => instance.rotateCW?.()
3788
+ },
3451
3789
  {
3452
3790
  id: "download",
3453
3791
  icon: "download",
@@ -3472,6 +3810,7 @@ var PptxPlugin = class {
3472
3810
  const slideCount = renderer.slidePaths?.length || 1;
3473
3811
  let currentSlide = 1;
3474
3812
  let scale = 1;
3813
+ let rotation = 0;
3475
3814
  const wrapper = document.createElement("div");
3476
3815
  wrapper.className = "fp-pptx-wrapper";
3477
3816
  wrapper.style.cssText = `
@@ -3494,6 +3833,8 @@ var PptxPlugin = class {
3494
3833
  background: #ffffff;
3495
3834
  transform-origin: top center;
3496
3835
  transition: transform 0.2s ease;
3836
+ max-width: calc(100% - 32px);
3837
+ max-height: calc(100% - 48px);
3497
3838
  `;
3498
3839
  const canvas = document.createElement("canvas");
3499
3840
  slideContainer.appendChild(canvas);
@@ -3501,10 +3842,22 @@ var PptxPlugin = class {
3501
3842
  ctx.container.innerHTML = "";
3502
3843
  ctx.container.style.overflow = "auto";
3503
3844
  ctx.container.appendChild(wrapper);
3845
+ const calculateFitScale = () => {
3846
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3847
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3848
+ const cW = canvas.offsetWidth || 1280;
3849
+ const cH = canvas.offsetHeight || 720;
3850
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3851
+ };
3852
+ const applyTransform = () => {
3853
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3854
+ };
3504
3855
  const renderCurrentSlide = async () => {
3505
3856
  try {
3506
3857
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3507
3858
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3859
+ scale = calculateFitScale();
3860
+ applyTransform();
3508
3861
  } catch (err) {
3509
3862
  console.error("[PptxPlugin] Failed to render slide:", err);
3510
3863
  }
@@ -3527,21 +3880,30 @@ var PptxPlugin = class {
3527
3880
  getPageCount: () => slideCount,
3528
3881
  getCurrentPage: () => currentSlide,
3529
3882
  zoomIn: () => {
3530
- scale += 0.1;
3531
- slideContainer.style.transform = `scale(${scale})`;
3883
+ scale += 0.15;
3884
+ applyTransform();
3532
3885
  },
3533
3886
  zoomOut: () => {
3534
- scale = Math.max(0.2, scale - 0.1);
3535
- slideContainer.style.transform = `scale(${scale})`;
3887
+ scale = Math.max(0.2, scale - 0.15);
3888
+ applyTransform();
3536
3889
  },
3537
3890
  getZoom: () => scale,
3538
3891
  setZoom: (level) => {
3539
3892
  scale = level;
3540
- slideContainer.style.transform = `scale(${scale})`;
3893
+ applyTransform();
3541
3894
  },
3542
3895
  fitToPage: () => {
3543
- scale = 1;
3544
- slideContainer.style.transform = "scale(1)";
3896
+ scale = calculateFitScale();
3897
+ rotation = 0;
3898
+ applyTransform();
3899
+ },
3900
+ rotateCW: () => {
3901
+ rotation = (rotation + 90) % 360;
3902
+ applyTransform();
3903
+ },
3904
+ rotateCCW: () => {
3905
+ rotation = (rotation - 90 + 360) % 360;
3906
+ applyTransform();
3545
3907
  },
3546
3908
  getThumbnails: () => {
3547
3909
  const list = [];
@@ -3822,6 +4184,14 @@ var RtfPlugin = class {
3822
4184
  group: "zoom",
3823
4185
  execute: () => instance.fitToPage?.()
3824
4186
  },
4187
+ {
4188
+ id: "rotate-cw",
4189
+ icon: "rotate-cw",
4190
+ label: "Rotate",
4191
+ type: "button",
4192
+ group: "view",
4193
+ execute: () => instance.rotateCW?.()
4194
+ },
3825
4195
  {
3826
4196
  id: "download",
3827
4197
  icon: "download",
@@ -3830,6 +4200,14 @@ var RtfPlugin = class {
3830
4200
  group: "actions",
3831
4201
  execute: () => instance.download?.()
3832
4202
  },
4203
+ {
4204
+ id: "copy",
4205
+ icon: "copy",
4206
+ label: "Copy Text",
4207
+ type: "button",
4208
+ group: "actions",
4209
+ execute: () => instance.copy?.()
4210
+ },
3833
4211
  {
3834
4212
  id: "print",
3835
4213
  icon: "print",
@@ -3858,7 +4236,24 @@ var RtfPlugin = class {
3858
4236
  ctx.container.style.backgroundColor = "#f1f5f9";
3859
4237
  ctx.container.appendChild(wrapper);
3860
4238
  let scale = 1;
4239
+ let rotation = 0;
3861
4240
  let pageElements = [];
4241
+ const calculateFitScale = () => {
4242
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4243
+ const elW = activeEl.offsetWidth || 850;
4244
+ const elH = activeEl.offsetHeight || 1e3;
4245
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4246
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4247
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4248
+ };
4249
+ const applyTransform = () => {
4250
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4251
+ wrapper.style.transformOrigin = "top center";
4252
+ };
4253
+ setTimeout(() => {
4254
+ scale = calculateFitScale();
4255
+ applyTransform();
4256
+ }, 60);
3862
4257
  try {
3863
4258
  if (typeof RTFJS__namespace.loggingEnabled === "function") {
3864
4259
  RTFJS__namespace.loggingEnabled(false);
@@ -3874,14 +4269,31 @@ var RtfPlugin = class {
3874
4269
  } catch (err) {
3875
4270
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3876
4271
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3877
- const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3878
- const pre = document.createElement("pre");
3879
- pre.style.whiteSpace = "pre-wrap";
3880
- pre.style.fontFamily = "serif";
3881
- pre.style.color = "#333";
3882
- pre.textContent = clean;
3883
- wrapper.appendChild(pre);
3884
- pageElements = [pre];
4272
+ const rawPages = text.split(/\\page\b/).map((segment) => {
4273
+ return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
4274
+ }).filter((p) => p.length > 0);
4275
+ const pages = rawPages.length > 0 ? rawPages : [text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim()];
4276
+ for (let i = 0; i < pages.length; i++) {
4277
+ const pageCard = document.createElement("div");
4278
+ pageCard.className = "fp-rtf-page-card";
4279
+ pageCard.style.backgroundColor = "#ffffff";
4280
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4281
+ pageCard.style.borderRadius = "4px";
4282
+ pageCard.style.padding = "72px 56px";
4283
+ pageCard.style.width = "816px";
4284
+ pageCard.style.minHeight = "1056px";
4285
+ pageCard.style.maxWidth = "100%";
4286
+ pageCard.style.boxSizing = "border-box";
4287
+ pageCard.style.fontFamily = "serif";
4288
+ pageCard.style.fontSize = "12pt";
4289
+ pageCard.style.lineHeight = "1.6";
4290
+ pageCard.style.color = "#1e293b";
4291
+ pageCard.style.whiteSpace = "pre-wrap";
4292
+ pageCard.style.display = i === 0 ? "block" : "none";
4293
+ pageCard.textContent = pages[i];
4294
+ wrapper.appendChild(pageCard);
4295
+ pageElements.push(pageCard);
4296
+ }
3885
4297
  }
3886
4298
  const totalPages = Math.max(1, pageElements.length);
3887
4299
  let currentPage = 1;
@@ -3935,21 +4347,30 @@ var RtfPlugin = class {
3935
4347
  getCurrentPage: () => currentPage,
3936
4348
  goToPage: (page) => showPage(page),
3937
4349
  zoomIn: () => {
3938
- scale += 0.1;
3939
- wrapper.style.transform = `scale(${scale})`;
4350
+ scale += 0.15;
4351
+ applyTransform();
3940
4352
  },
3941
4353
  zoomOut: () => {
3942
- scale = Math.max(0.2, scale - 0.1);
3943
- wrapper.style.transform = `scale(${scale})`;
4354
+ scale = Math.max(0.2, scale - 0.15);
4355
+ applyTransform();
3944
4356
  },
3945
4357
  getZoom: () => scale,
3946
4358
  setZoom: (level) => {
3947
4359
  scale = level;
3948
- wrapper.style.transform = `scale(${scale})`;
4360
+ applyTransform();
3949
4361
  },
3950
4362
  fitToPage: () => {
3951
- scale = 1;
3952
- wrapper.style.transform = "scale(1)";
4363
+ scale = calculateFitScale();
4364
+ rotation = 0;
4365
+ applyTransform();
4366
+ },
4367
+ rotateCW: () => {
4368
+ rotation = (rotation + 90) % 360;
4369
+ applyTransform();
4370
+ },
4371
+ rotateCCW: () => {
4372
+ rotation = (rotation - 90 + 360) % 360;
4373
+ applyTransform();
3953
4374
  },
3954
4375
  download: () => {
3955
4376
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3960,6 +4381,10 @@ var RtfPlugin = class {
3960
4381
  a.click();
3961
4382
  URL.revokeObjectURL(url);
3962
4383
  },
4384
+ copy: () => {
4385
+ const text = wrapper.textContent || "";
4386
+ navigator.clipboard?.writeText(text);
4387
+ },
3963
4388
  print: () => {
3964
4389
  window.print();
3965
4390
  }
@@ -4034,7 +4459,7 @@ var HtmlPreviewPlugin = class {
4034
4459
  }
4035
4460
  async render(ctx) {
4036
4461
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
4037
- const sanitized = DOMPurify2__default.default.sanitize(rawHtml, {
4462
+ const sanitized = DOMPurify6__default.default.sanitize(rawHtml, {
4038
4463
  WHOLE_DOCUMENT: true,
4039
4464
  ADD_TAGS: ["style", "link"],
4040
4465
  ADD_ATTR: ["target", "rel"]
@@ -4177,11 +4602,19 @@ var OpenDocumentPlugin = class {
4177
4602
  {
4178
4603
  id: "fit-page",
4179
4604
  icon: "fit-page",
4180
- label: "Fit to Page",
4605
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
4181
4606
  type: "button",
4182
4607
  group: "zoom",
4183
4608
  execute: () => instance.fitToPage?.()
4184
4609
  },
4610
+ {
4611
+ id: "rotate-cw",
4612
+ icon: "rotate-cw",
4613
+ label: "Rotate",
4614
+ type: "button",
4615
+ group: "view",
4616
+ execute: () => instance.rotateCW?.()
4617
+ },
4185
4618
  {
4186
4619
  id: "download",
4187
4620
  icon: "download",
@@ -4245,6 +4678,22 @@ var OpenDocumentPlugin = class {
4245
4678
  container.appendChild(wrapper);
4246
4679
  ctx.container.appendChild(container);
4247
4680
  let scale = 1;
4681
+ let rotation = 0;
4682
+ const calculateFitScale = () => {
4683
+ const elW = wrapper.offsetWidth || 850;
4684
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4685
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4686
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4687
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4688
+ };
4689
+ const applyTransform = () => {
4690
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4691
+ wrapper.style.transformOrigin = "top center";
4692
+ };
4693
+ setTimeout(() => {
4694
+ scale = calculateFitScale();
4695
+ applyTransform();
4696
+ }, 60);
4248
4697
  let currentPage = 1;
4249
4698
  let totalPages = 1;
4250
4699
  let slides = [];
@@ -4276,14 +4725,97 @@ var OpenDocumentPlugin = class {
4276
4725
  wrapper.textContent = contentDoc.documentElement.textContent || "Formula content";
4277
4726
  }
4278
4727
  } else {
4279
- wrapper.style.maxWidth = "850px";
4280
- wrapper.style.padding = "48px";
4728
+ wrapper.style.maxWidth = "none";
4729
+ wrapper.style.padding = "0";
4281
4730
  wrapper.style.minHeight = "100%";
4731
+ wrapper.style.backgroundColor = "transparent";
4732
+ wrapper.style.boxShadow = "none";
4733
+ wrapper.style.position = "relative";
4734
+ const dims = this.getPageDimensions(stylesDoc, contentDoc);
4282
4735
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
4283
- wrapper.innerHTML = DOMPurify2__default.default.sanitize(bodyHtml, {
4284
- ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
4736
+ const tempDiv = document.createElement("div");
4737
+ tempDiv.style.width = `${dims.width - dims.marginLeft - dims.marginRight}px`;
4738
+ tempDiv.style.position = "absolute";
4739
+ tempDiv.style.visibility = "hidden";
4740
+ tempDiv.innerHTML = DOMPurify6__default.default.sanitize(bodyHtml, {
4741
+ ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub", "hr"],
4285
4742
  ADD_ATTR: ["style", "colspan", "rowspan"]
4286
4743
  });
4744
+ document.body.appendChild(tempDiv);
4745
+ const contentHeight = dims.height - dims.marginTop - dims.marginBottom;
4746
+ const pageElements = [[]];
4747
+ let currentHeight = 0;
4748
+ let currentPageIdx = 0;
4749
+ Array.from(tempDiv.children).forEach((child) => {
4750
+ const el = child;
4751
+ const style = el.getAttribute("style") || "";
4752
+ const isBreakBefore = style.includes("page-break-before: always");
4753
+ const isBreakAfter = style.includes("page-break-after: always");
4754
+ const isSoftBreak = el.classList.contains("odf-page-break");
4755
+ if (isBreakBefore) {
4756
+ if (pageElements[currentPageIdx].length > 0) {
4757
+ currentPageIdx++;
4758
+ pageElements.push([]);
4759
+ currentHeight = 0;
4760
+ }
4761
+ }
4762
+ const h = el.offsetHeight || 0;
4763
+ if (currentHeight + h > contentHeight && pageElements[currentPageIdx].length > 0 && !isSoftBreak) {
4764
+ currentPageIdx++;
4765
+ pageElements.push([]);
4766
+ currentHeight = 0;
4767
+ }
4768
+ if (!isSoftBreak) {
4769
+ pageElements[currentPageIdx].push(el.cloneNode(true));
4770
+ currentHeight += h;
4771
+ }
4772
+ if (isBreakAfter || isSoftBreak) {
4773
+ currentPageIdx++;
4774
+ pageElements.push([]);
4775
+ currentHeight = 0;
4776
+ }
4777
+ });
4778
+ document.body.removeChild(tempDiv);
4779
+ if (pageElements.length > 1 && pageElements[pageElements.length - 1].length === 0) {
4780
+ pageElements.pop();
4781
+ }
4782
+ totalPages = Math.max(1, pageElements.length);
4783
+ pageElements.forEach((elements, idx) => {
4784
+ const page = document.createElement("div");
4785
+ page.className = `fp-odt-page fp-odt-page-${idx + 1}`;
4786
+ page.style.width = `${dims.width}px`;
4787
+ page.style.minHeight = `${dims.height}px`;
4788
+ page.style.padding = `${dims.marginTop}px ${dims.marginRight}px ${dims.marginBottom}px ${dims.marginLeft}px`;
4789
+ page.style.margin = "0 auto";
4790
+ page.style.backgroundColor = "#ffffff";
4791
+ page.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
4792
+ page.style.borderRadius = "4px";
4793
+ page.style.boxSizing = "border-box";
4794
+ page.style.display = idx === 0 ? "block" : "none";
4795
+ page.style.position = "absolute";
4796
+ page.style.top = "0";
4797
+ page.style.left = "50%";
4798
+ page.style.transform = "translateX(-50%)";
4799
+ elements.forEach((el) => page.appendChild(el));
4800
+ wrapper.appendChild(page);
4801
+ slides.push(page);
4802
+ });
4803
+ const pageIndicator = document.createElement("div");
4804
+ pageIndicator.className = "fp-odt-page-indicator";
4805
+ pageIndicator.style.position = "sticky";
4806
+ pageIndicator.style.bottom = "16px";
4807
+ pageIndicator.style.left = "50%";
4808
+ pageIndicator.style.transform = "translateX(-50%)";
4809
+ pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
4810
+ pageIndicator.style.color = "#fff";
4811
+ pageIndicator.style.padding = "6px 12px";
4812
+ pageIndicator.style.borderRadius = "16px";
4813
+ pageIndicator.style.fontSize = "12px";
4814
+ pageIndicator.style.zIndex = "100";
4815
+ pageIndicator.style.display = "inline-block";
4816
+ pageIndicator.style.width = "fit-content";
4817
+ pageIndicator.textContent = `Page 1 of ${totalPages}`;
4818
+ container.appendChild(pageIndicator);
4287
4819
  }
4288
4820
  const cleanup = () => {
4289
4821
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
@@ -4292,32 +4824,45 @@ var OpenDocumentPlugin = class {
4292
4824
  };
4293
4825
  ctx.signal.addEventListener("abort", cleanup);
4294
4826
  const goToPage = (page) => {
4295
- if (!isPresentation || page < 1 || page > totalPages) return;
4827
+ if (page < 1 || page > totalPages) return;
4296
4828
  currentPage = page;
4297
4829
  slides.forEach((s, idx) => {
4298
4830
  s.style.display = idx === page - 1 ? "block" : "none";
4299
4831
  });
4832
+ const indicator = container.querySelector(".fp-odt-page-indicator");
4833
+ if (indicator) {
4834
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4835
+ }
4300
4836
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4301
4837
  };
4302
4838
  return {
4303
4839
  destroy: cleanup,
4304
4840
  isPresentation,
4305
4841
  zoomIn: () => {
4306
- scale += 0.1;
4307
- wrapper.style.transform = `scale(${scale})`;
4842
+ scale += 0.15;
4843
+ applyTransform();
4308
4844
  },
4309
4845
  zoomOut: () => {
4310
- scale = Math.max(0.2, scale - 0.1);
4311
- wrapper.style.transform = `scale(${scale})`;
4846
+ scale = Math.max(0.2, scale - 0.15);
4847
+ applyTransform();
4312
4848
  },
4313
4849
  getZoom: () => scale,
4314
4850
  setZoom: (level) => {
4315
4851
  scale = level;
4316
- wrapper.style.transform = `scale(${scale})`;
4852
+ applyTransform();
4317
4853
  },
4318
4854
  fitToPage: () => {
4319
- scale = 1;
4320
- wrapper.style.transform = "scale(1)";
4855
+ scale = calculateFitScale();
4856
+ rotation = 0;
4857
+ applyTransform();
4858
+ },
4859
+ rotateCW: () => {
4860
+ rotation = (rotation + 90) % 360;
4861
+ applyTransform();
4862
+ },
4863
+ rotateCCW: () => {
4864
+ rotation = (rotation - 90 + 360) % 360;
4865
+ applyTransform();
4321
4866
  },
4322
4867
  goToPage,
4323
4868
  getPageCount: () => totalPages,
@@ -4356,6 +4901,43 @@ var OpenDocumentPlugin = class {
4356
4901
  }
4357
4902
  };
4358
4903
  }
4904
+ getPageDimensions(stylesDoc, contentDoc) {
4905
+ let width = 850;
4906
+ let height = 1123;
4907
+ let marginTop = 48;
4908
+ let marginBottom = 48;
4909
+ let marginLeft = 48;
4910
+ let marginRight = 48;
4911
+ const parseUnit = (val) => {
4912
+ if (!val) return null;
4913
+ if (val.endsWith("cm")) return parseFloat(val) * 37.8;
4914
+ if (val.endsWith("mm")) return parseFloat(val) * 3.78;
4915
+ if (val.endsWith("in")) return parseFloat(val) * 96;
4916
+ if (val.endsWith("pt")) return parseFloat(val) * 1.33;
4917
+ if (val.endsWith("px")) return parseFloat(val);
4918
+ return parseFloat(val);
4919
+ };
4920
+ const docs = [stylesDoc, contentDoc].filter(Boolean);
4921
+ for (const doc of docs) {
4922
+ const pageLayout = doc.querySelector("page-layout-properties, [page-width]");
4923
+ if (pageLayout) {
4924
+ const w = parseUnit(pageLayout.getAttribute("fo:page-width") || pageLayout.getAttribute("page-width"));
4925
+ const h = parseUnit(pageLayout.getAttribute("fo:page-height") || pageLayout.getAttribute("page-height"));
4926
+ const mt = parseUnit(pageLayout.getAttribute("fo:margin-top") || pageLayout.getAttribute("margin-top"));
4927
+ const mb = parseUnit(pageLayout.getAttribute("fo:margin-bottom") || pageLayout.getAttribute("margin-bottom"));
4928
+ const ml = parseUnit(pageLayout.getAttribute("fo:margin-left") || pageLayout.getAttribute("margin-left"));
4929
+ const mr = parseUnit(pageLayout.getAttribute("fo:margin-right") || pageLayout.getAttribute("margin-right"));
4930
+ if (w !== null) width = w;
4931
+ if (h !== null) height = h;
4932
+ if (mt !== null) marginTop = mt;
4933
+ if (mb !== null) marginBottom = mb;
4934
+ if (ml !== null) marginLeft = ml;
4935
+ if (mr !== null) marginRight = mr;
4936
+ break;
4937
+ }
4938
+ }
4939
+ return { width, height, marginTop, marginBottom, marginLeft, marginRight };
4940
+ }
4359
4941
  extractStyles(stylesDoc, contentDoc) {
4360
4942
  const map = /* @__PURE__ */ new Map();
4361
4943
  const styleNodes = [];
@@ -4378,14 +4960,21 @@ var OpenDocumentPlugin = class {
4378
4960
  if (color) css += `color: ${color}; `;
4379
4961
  if (size) css += `font-size: ${size}; `;
4380
4962
  }
4381
- const paraProp = node.querySelector("paragraph-properties, [text-align]");
4963
+ let paraProp = node.querySelector("paragraph-properties, [text-align]");
4964
+ if (!paraProp) {
4965
+ paraProp = Array.from(node.children).find((c) => c.tagName.includes("paragraph-properties")) || null;
4966
+ }
4382
4967
  if (paraProp) {
4383
4968
  const align = paraProp.getAttribute("fo:text-align") || paraProp.getAttribute("text-align");
4384
4969
  const mt = paraProp.getAttribute("fo:margin-top") || paraProp.getAttribute("margin-top");
4385
4970
  const mb = paraProp.getAttribute("fo:margin-bottom") || paraProp.getAttribute("margin-bottom");
4971
+ const breakBefore = paraProp.getAttribute("fo:break-before") || paraProp.getAttribute("break-before");
4972
+ const breakAfter = paraProp.getAttribute("fo:break-after") || paraProp.getAttribute("break-after");
4386
4973
  if (align) css += `text-align: ${align}; `;
4387
4974
  if (mt) css += `margin-top: ${mt}; `;
4388
4975
  if (mb) css += `margin-bottom: ${mb}; `;
4976
+ if (breakBefore === "page") css += "page-break-before: always; ";
4977
+ if (breakAfter === "page") css += "page-break-after: always; ";
4389
4978
  }
4390
4979
  if (css) map.set(name, css);
4391
4980
  }
@@ -4470,6 +5059,8 @@ var OpenDocumentPlugin = class {
4470
5059
  result += "&emsp;";
4471
5060
  } else if (tag === "line-break") {
4472
5061
  result += "<br/>";
5062
+ } else if (tag === "soft-page-break") {
5063
+ result += '<hr class="odf-page-break" style="page-break-after: always; border: none; margin: 0; padding: 0; height: 0;" />';
4473
5064
  } else {
4474
5065
  result += el.textContent || "";
4475
5066
  }
@@ -4587,6 +5178,14 @@ var DocPlugin = class {
4587
5178
  group: "zoom",
4588
5179
  execute: () => instance.fitToPage?.()
4589
5180
  },
5181
+ {
5182
+ id: "rotate-cw",
5183
+ icon: "rotate-cw",
5184
+ label: "Rotate",
5185
+ type: "button",
5186
+ group: "view",
5187
+ execute: () => instance.rotateCW?.()
5188
+ },
4590
5189
  {
4591
5190
  id: "copy",
4592
5191
  icon: "copy",
@@ -4622,20 +5221,9 @@ var DocPlugin = class {
4622
5221
  container.style.overflow = "auto";
4623
5222
  container.style.padding = "32px 16px";
4624
5223
  container.style.backgroundColor = "#f1f5f9";
4625
- const wrapper = document.createElement("div");
4626
- wrapper.className = "fp-doc-wrapper";
4627
- wrapper.style.maxWidth = "850px";
4628
- wrapper.style.margin = "0 auto";
4629
- wrapper.style.backgroundColor = "#ffffff";
4630
- wrapper.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4631
- wrapper.style.borderRadius = "4px";
4632
- wrapper.style.padding = "56px 48px";
4633
- wrapper.style.minHeight = "100%";
4634
- wrapper.style.transformOrigin = "top center";
4635
- wrapper.style.transition = "transform 0.2s ease";
4636
- wrapper.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4637
- wrapper.style.color = "#1e293b";
4638
- container.appendChild(wrapper);
5224
+ container.style.display = "flex";
5225
+ container.style.justifyContent = "center";
5226
+ container.style.alignItems = "flex-start";
4639
5227
  ctx.container.appendChild(container);
4640
5228
  let scale = 1;
4641
5229
  let extractedRawText = "";
@@ -4662,21 +5250,27 @@ var DocPlugin = class {
4662
5250
  const rawPages = this.splitIntoPages(extractedRawText);
4663
5251
  const totalPages = Math.max(1, rawPages.length);
4664
5252
  let currentPage = 1;
4665
- wrapper.innerHTML = "";
4666
5253
  const pageCards = [];
4667
5254
  for (let i = 0; i < totalPages; i++) {
4668
5255
  const pageCard = document.createElement("div");
4669
5256
  pageCard.className = "fp-doc-page-card";
5257
+ pageCard.style.width = "816px";
5258
+ pageCard.style.height = "1056px";
4670
5259
  pageCard.style.backgroundColor = "#ffffff";
4671
5260
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4672
5261
  pageCard.style.borderRadius = "4px";
4673
- pageCard.style.padding = "56px 48px";
4674
- pageCard.style.minHeight = "100%";
5262
+ pageCard.style.padding = "96px 72px";
5263
+ pageCard.style.boxSizing = "border-box";
5264
+ pageCard.style.overflow = "hidden";
4675
5265
  pageCard.style.display = i === 0 ? "block" : "none";
5266
+ pageCard.style.transformOrigin = "top center";
5267
+ pageCard.style.transition = "transform 0.2s ease";
5268
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5269
+ pageCard.style.color = "#1e293b";
4676
5270
  if (isFallback && i === 0) {
4677
5271
  pageCard.innerHTML = `
4678
5272
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4679
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2__default.default.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
5273
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify6__default.default.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4680
5274
  <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4681
5275
  </div>
4682
5276
  ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
@@ -4684,15 +5278,17 @@ var DocPlugin = class {
4684
5278
  } else {
4685
5279
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4686
5280
  }
4687
- wrapper.appendChild(pageCard);
5281
+ container.appendChild(pageCard);
4688
5282
  pageCards.push(pageCard);
4689
5283
  }
4690
5284
  let indicator = null;
4691
5285
  if (totalPages > 1) {
4692
5286
  indicator = document.createElement("div");
4693
5287
  indicator.className = "fp-doc-page-indicator";
4694
- indicator.style.position = "sticky";
5288
+ indicator.style.position = "fixed";
4695
5289
  indicator.style.bottom = "16px";
5290
+ indicator.style.left = "50%";
5291
+ indicator.style.transform = "translateX(-50%)";
4696
5292
  indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4697
5293
  indicator.style.backdropFilter = "blur(8px)";
4698
5294
  indicator.style.color = "#f8fafc";
@@ -4706,17 +5302,25 @@ var DocPlugin = class {
4706
5302
  indicator.style.userSelect = "none";
4707
5303
  indicator.style.pointerEvents = "none";
4708
5304
  indicator.style.textAlign = "center";
4709
- indicator.style.width = "fit-content";
4710
- indicator.style.margin = "16px auto 0";
4711
5305
  container.appendChild(indicator);
4712
5306
  }
5307
+ let rotation = 0;
5308
+ const applyTransform = () => {
5309
+ const activeCard = pageCards[currentPage - 1];
5310
+ if (activeCard) {
5311
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5312
+ }
5313
+ };
4713
5314
  const showPage = (pageNum) => {
4714
5315
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4715
- if (totalPages > 1) {
4716
- pageCards.forEach((card, idx) => {
4717
- card.style.display = idx + 1 === currentPage ? "block" : "none";
4718
- });
4719
- }
5316
+ pageCards.forEach((card, idx) => {
5317
+ if (idx + 1 === currentPage) {
5318
+ card.style.display = "block";
5319
+ card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5320
+ } else {
5321
+ card.style.display = "none";
5322
+ }
5323
+ });
4720
5324
  if (indicator) {
4721
5325
  indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4722
5326
  }
@@ -4725,6 +5329,17 @@ var DocPlugin = class {
4725
5329
  if (totalPages > 1) {
4726
5330
  showPage(1);
4727
5331
  }
5332
+ const calculateFitScale = () => {
5333
+ const elW = 816;
5334
+ const elH = 1056;
5335
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
5336
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
5337
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
5338
+ };
5339
+ setTimeout(() => {
5340
+ scale = calculateFitScale();
5341
+ applyTransform();
5342
+ }, 60);
4728
5343
  const cleanup = () => {
4729
5344
  indicator?.remove();
4730
5345
  container.remove();
@@ -4737,21 +5352,30 @@ var DocPlugin = class {
4737
5352
  getCurrentPage: () => currentPage,
4738
5353
  goToPage: (page) => showPage(page),
4739
5354
  zoomIn: () => {
4740
- scale += 0.1;
4741
- wrapper.style.transform = `scale(${scale})`;
5355
+ scale += 0.15;
5356
+ applyTransform();
4742
5357
  },
4743
5358
  zoomOut: () => {
4744
- scale = Math.max(0.2, scale - 0.1);
4745
- wrapper.style.transform = `scale(${scale})`;
5359
+ scale = Math.max(0.2, scale - 0.15);
5360
+ applyTransform();
4746
5361
  },
4747
5362
  getZoom: () => scale,
4748
5363
  setZoom: (level) => {
4749
5364
  scale = level;
4750
- wrapper.style.transform = `scale(${scale})`;
5365
+ applyTransform();
4751
5366
  },
4752
5367
  fitToPage: () => {
4753
- scale = 1;
4754
- wrapper.style.transform = "scale(1)";
5368
+ scale = calculateFitScale();
5369
+ rotation = 0;
5370
+ applyTransform();
5371
+ },
5372
+ rotateCW: () => {
5373
+ rotation = (rotation + 90) % 360;
5374
+ applyTransform();
5375
+ },
5376
+ rotateCCW: () => {
5377
+ rotation = (rotation - 90 + 360) % 360;
5378
+ applyTransform();
4755
5379
  },
4756
5380
  copy: () => {
4757
5381
  navigator.clipboard.writeText(extractedRawText);
@@ -4869,8 +5493,28 @@ var DocPlugin = class {
4869
5493
  }
4870
5494
  splitIntoPages(text) {
4871
5495
  if (!text) return [""];
4872
- 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);
4873
- return parts.length > 0 ? parts : [text];
5496
+ 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);
5497
+ if (explicitParts.length === 0) explicitParts.push(text);
5498
+ const maxLinesPerPage = 48;
5499
+ const finalPages = [];
5500
+ for (const part of explicitParts) {
5501
+ const lines = part.split(/\r?\n/);
5502
+ let currentLines = [];
5503
+ let count = 0;
5504
+ for (const line of lines) {
5505
+ currentLines.push(line);
5506
+ count++;
5507
+ if (count >= maxLinesPerPage) {
5508
+ finalPages.push(currentLines.join("\n"));
5509
+ currentLines = [];
5510
+ count = 0;
5511
+ }
5512
+ }
5513
+ if (currentLines.length > 0) {
5514
+ finalPages.push(currentLines.join("\n"));
5515
+ }
5516
+ }
5517
+ return finalPages.length > 0 ? finalPages : [text];
4874
5518
  }
4875
5519
  /**
4876
5520
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
@@ -4879,17 +5523,65 @@ var DocPlugin = class {
4879
5523
  const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4880
5524
  let html = "";
4881
5525
  let inList = false;
4882
- for (const rawLine of lines) {
4883
- const line = rawLine.trim();
5526
+ let tableLines = [];
5527
+ const flushTable = () => {
5528
+ if (tableLines.length > 0) {
5529
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 12px;">';
5530
+ for (const tLine of tableLines) {
5531
+ html += "<tr>";
5532
+ const cols = tLine.split(" ");
5533
+ for (const col of cols) {
5534
+ html += `<td style="border: 1px solid #cbd5e1; padding: 6px 8px;">${DOMPurify6__default.default.sanitize(col.trim())}</td>`;
5535
+ }
5536
+ html += "</tr>";
5537
+ }
5538
+ html += "</table>";
5539
+ tableLines = [];
5540
+ }
5541
+ };
5542
+ let i = 0;
5543
+ while (i < lines.length) {
5544
+ let line = lines[i];
5545
+ let tabCount = (line.match(/\t/g) || []).length;
5546
+ if (tabCount > 0) {
5547
+ let consecutiveTableLines = 1;
5548
+ let j = i + 1;
5549
+ while (j < lines.length) {
5550
+ const nextTabCount = (lines[j].match(/\t/g) || []).length;
5551
+ if (nextTabCount === tabCount) {
5552
+ consecutiveTableLines++;
5553
+ j++;
5554
+ } else {
5555
+ break;
5556
+ }
5557
+ }
5558
+ if (consecutiveTableLines >= 3) {
5559
+ if (inList) {
5560
+ html += "</ul>";
5561
+ inList = false;
5562
+ }
5563
+ tableLines = lines.slice(i, j);
5564
+ flushTable();
5565
+ i = j;
5566
+ continue;
5567
+ }
5568
+ }
5569
+ line = line.trim();
4884
5570
  if (!line) {
4885
5571
  if (inList) {
4886
5572
  html += "</ul>";
4887
5573
  inList = false;
4888
5574
  }
5575
+ html += '<div style="height: 1.15em;"></div>';
5576
+ i++;
4889
5577
  continue;
4890
5578
  }
4891
- if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) continue;
4892
- if (line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman") && line.length < 30) {
5579
+ if (/^[\x00-\x1F\x7F-\x9F]+$/.test(line)) {
5580
+ i++;
5581
+ continue;
5582
+ }
5583
+ if ((line.includes("Normal.dot") || line.includes("Microsoft Word") || line.includes("Times New Roman")) && line.length < 30) {
5584
+ i++;
4893
5585
  continue;
4894
5586
  }
4895
5587
  if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
@@ -4897,24 +5589,26 @@ var DocPlugin = class {
4897
5589
  html += "</ul>";
4898
5590
  inList = false;
4899
5591
  }
4900
- html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify2__default.default.sanitize(line)}</h2>`;
5592
+ html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line)}</h2>`;
4901
5593
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4902
5594
  if (!inList) {
4903
5595
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4904
5596
  inList = true;
4905
5597
  }
4906
5598
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4907
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2__default.default.sanitize(bulletText)}</li>`;
5599
+ html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText)}</li>`;
4908
5600
  } else {
4909
5601
  if (inList) {
4910
5602
  html += "</ul>";
4911
5603
  inList = false;
4912
5604
  }
4913
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2__default.default.sanitize(line)}</p>`;
5605
+ html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line)}</p>`;
4914
5606
  }
5607
+ i++;
4915
5608
  }
4916
5609
  if (inList) html += "</ul>";
4917
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2__default.default.sanitize(filename)})</p>`;
5610
+ flushTable();
5611
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6__default.default.sanitize(filename)})</p>`;
4918
5612
  }
4919
5613
  };
4920
5614
  function docPlugin() {
@@ -4991,6 +5685,14 @@ var PptPlugin = class {
4991
5685
  group: "zoom",
4992
5686
  execute: () => instance.fitToPage?.()
4993
5687
  },
5688
+ {
5689
+ id: "rotate-cw",
5690
+ icon: "rotate-cw",
5691
+ label: "Rotate",
5692
+ type: "button",
5693
+ group: "view",
5694
+ execute: () => instance.rotateCW?.()
5695
+ },
4994
5696
  {
4995
5697
  id: "download",
4996
5698
  icon: "download",
@@ -5024,22 +5726,36 @@ var PptPlugin = class {
5024
5726
  const slideCard = document.createElement("div");
5025
5727
  slideCard.className = "fp-ppt-slide-card";
5026
5728
  slideCard.style.width = "960px";
5027
- slideCard.style.maxWidth = "92%";
5729
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5730
+ slideCard.style.maxHeight = "calc(100% - 48px)";
5028
5731
  slideCard.style.aspectRatio = "16 / 9";
5029
5732
  slideCard.style.backgroundColor = "#ffffff";
5030
5733
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
5031
5734
  slideCard.style.borderRadius = "8px";
5032
5735
  slideCard.style.boxSizing = "border-box";
5033
5736
  slideCard.style.position = "relative";
5034
- slideCard.style.overflow = "hidden";
5035
- slideCard.style.transformOrigin = "center center";
5036
5737
  slideCard.style.transition = "transform 0.2s ease";
5738
+ slideCard.style.transformOrigin = "center center";
5739
+ slideCard.style.flexShrink = "0";
5037
5740
  container.appendChild(slideCard);
5038
5741
  ctx.container.appendChild(container);
5039
5742
  let scale = 1;
5743
+ let rotation = 0;
5040
5744
  let currentSlide = 1;
5041
5745
  let slides = [];
5042
5746
  const createdBlobUrls = [];
5747
+ const calculateFitScale = () => {
5748
+ const availW = Math.max(200, container.clientWidth - 48);
5749
+ const availH = Math.max(200, container.clientHeight - 72);
5750
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5751
+ };
5752
+ const applyTransform = () => {
5753
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5754
+ };
5755
+ setTimeout(() => {
5756
+ scale = calculateFitScale();
5757
+ applyTransform();
5758
+ }, 60);
5043
5759
  try {
5044
5760
  const cfbf = new CfbfReader(ctx.buffer);
5045
5761
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -5070,7 +5786,7 @@ var PptPlugin = class {
5070
5786
  if (s.pictureUrl) {
5071
5787
  contentHtml = `
5072
5788
  <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
5073
- <img src="${s.pictureUrl}" alt="${DOMPurify2__default.default.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
5789
+ <img src="${s.pictureUrl}" alt="${DOMPurify6__default.default.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
5074
5790
  </div>
5075
5791
  `;
5076
5792
  } else if (s.tableColumns.length > 0) {
@@ -5080,7 +5796,7 @@ var PptPlugin = class {
5080
5796
  <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
5081
5797
  <thead>
5082
5798
  <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
5083
- ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2__default.default.sanitize(c)}</th>`).join("")}
5799
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify6__default.default.sanitize(c)}</th>`).join("")}
5084
5800
  </tr>
5085
5801
  </thead>
5086
5802
  <tbody>
@@ -5096,7 +5812,7 @@ var PptPlugin = class {
5096
5812
  } else {
5097
5813
  const pTags = s.paragraphs.map((p) => {
5098
5814
  const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
5099
- return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2__default.default.sanitize(l)}</p>`).join("");
5815
+ return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify6__default.default.sanitize(l)}</p>`).join("");
5100
5816
  }).join("");
5101
5817
  contentHtml = `
5102
5818
  <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
@@ -5109,11 +5825,11 @@ var PptPlugin = class {
5109
5825
  <!-- Header Banner matching PowerPoint design -->
5110
5826
  <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;">
5111
5827
  <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
5112
- ${DOMPurify2__default.default.sanitize(s.title)}
5828
+ ${DOMPurify6__default.default.sanitize(s.title)}
5113
5829
  </h1>
5114
5830
  ${s.subtitle ? `
5115
5831
  <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);">
5116
- ${DOMPurify2__default.default.sanitize(s.subtitle)}
5832
+ ${DOMPurify6__default.default.sanitize(s.subtitle)}
5117
5833
  </span>
5118
5834
  ` : `
5119
5835
  <span style="font-size: 12px; color: #365314; font-weight: 600;">
@@ -5140,21 +5856,30 @@ var PptPlugin = class {
5140
5856
  return {
5141
5857
  destroy: cleanup,
5142
5858
  zoomIn: () => {
5143
- scale += 0.1;
5144
- slideCard.style.transform = `scale(${scale})`;
5859
+ scale += 0.15;
5860
+ applyTransform();
5145
5861
  },
5146
5862
  zoomOut: () => {
5147
- scale = Math.max(0.3, scale - 0.1);
5148
- slideCard.style.transform = `scale(${scale})`;
5863
+ scale = Math.max(0.2, scale - 0.15);
5864
+ applyTransform();
5149
5865
  },
5150
5866
  getZoom: () => scale,
5151
5867
  setZoom: (level) => {
5152
5868
  scale = level;
5153
- slideCard.style.transform = `scale(${scale})`;
5869
+ applyTransform();
5154
5870
  },
5155
5871
  fitToPage: () => {
5156
- scale = 1;
5157
- slideCard.style.transform = "scale(1)";
5872
+ scale = calculateFitScale();
5873
+ rotation = 0;
5874
+ applyTransform();
5875
+ },
5876
+ rotateCW: () => {
5877
+ rotation = (rotation + 90) % 360;
5878
+ applyTransform();
5879
+ },
5880
+ rotateCCW: () => {
5881
+ rotation = (rotation - 90 + 360) % 360;
5882
+ applyTransform();
5158
5883
  },
5159
5884
  goToPage: (page) => {
5160
5885
  if (page >= 1 && page <= totalSlides) {