@files-preview-app/preview-file 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import DOMPurify2 from 'dompurify';
2
+ import * as pdfjsLib from 'pdfjs-dist';
2
3
  import * as docx from 'docx-preview';
3
4
  import { unzipSync, strFromU8, unzip } from 'fflate';
4
5
  import * as XLSX from 'xlsx';
@@ -522,11 +523,21 @@ var ToolbarController = class {
522
523
  el;
523
524
  toolbarEl;
524
525
  actions = [];
526
+ pageInputEl = null;
527
+ pageLabelEl = null;
525
528
  constructor(container) {
526
529
  this.el = container;
527
530
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
528
531
  this.el.appendChild(this.toolbarEl);
529
532
  }
533
+ setPage(page, max) {
534
+ if (this.pageInputEl) {
535
+ this.pageInputEl.value = page.toString();
536
+ }
537
+ if (max !== void 0 && this.pageLabelEl) {
538
+ this.pageLabelEl.textContent = ` / ${max}`;
539
+ }
540
+ }
530
541
  update(actions) {
531
542
  this.actions = actions;
532
543
  this.render();
@@ -569,6 +580,7 @@ var ToolbarController = class {
569
580
  if (action.type === "separator") {
570
581
  groupEl.appendChild(createElement("div", { className: "fp-toolbar-separator" }));
571
582
  } else if (action.type === "page-nav") {
583
+ const max = action.max ?? 1;
572
584
  const prevBtn = this.createButton(
573
585
  "prev",
574
586
  ICON_PAGE_PREV,
@@ -587,7 +599,6 @@ var ToolbarController = class {
587
599
  "Next Page",
588
600
  () => {
589
601
  const cur = parseInt(input.value, 10) || 1;
590
- const max = action.max ?? 1;
591
602
  if (cur < max) {
592
603
  input.value = (cur + 1).toString();
593
604
  action.execute("next", cur + 1);
@@ -599,15 +610,18 @@ var ToolbarController = class {
599
610
  type: "number",
600
611
  value: (action.value ?? 1).toString(),
601
612
  min: "1",
602
- max: (action.max ?? 1).toString()
613
+ max: max.toString()
603
614
  });
604
615
  input.addEventListener("change", () => {
605
- const val = parseInt(input.value, 10);
606
- if (!isNaN(val)) {
607
- action.execute("go", val);
608
- }
616
+ let val = parseInt(input.value, 10);
617
+ if (isNaN(val)) val = 1;
618
+ val = Math.max(1, Math.min(max, val));
619
+ input.value = val.toString();
620
+ action.execute("go", val);
609
621
  });
610
- const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
622
+ const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
623
+ this.pageInputEl = input;
624
+ this.pageLabelEl = label;
611
625
  groupEl.appendChild(prevBtn);
612
626
  groupEl.appendChild(input);
613
627
  groupEl.appendChild(label);
@@ -734,6 +748,8 @@ var FilePreviewViewer = class {
734
748
  keyHandler = null;
735
749
  currentContainer = null;
736
750
  currentOptions = {};
751
+ resizeObserver = null;
752
+ fullscreenHandler = null;
737
753
  /**
738
754
  * Register a preview plugin.
739
755
  */
@@ -785,7 +801,13 @@ var FilePreviewViewer = class {
785
801
  buffer,
786
802
  options,
787
803
  signal,
788
- emit: (event, payload) => this.eventEmitter.emit(event, payload)
804
+ emit: (event, payload) => {
805
+ if (event === "page-change" && payload && typeof payload.page === "number") {
806
+ const total = payload.total ?? payload.totalPages;
807
+ this.toolbar?.setPage(payload.page, total);
808
+ }
809
+ this.eventEmitter.emit(event, payload);
810
+ }
789
811
  });
790
812
  this.activeInstance = instance;
791
813
  this.hideLoading();
@@ -800,12 +822,31 @@ var FilePreviewViewer = class {
800
822
  label: "Toggle Fullscreen",
801
823
  type: "button",
802
824
  group: "view",
803
- execute: () => {
804
- if (!document.fullscreenElement) {
805
- this.wrapperEl?.requestFullscreen?.();
806
- } else {
807
- document.exitFullscreen?.();
825
+ execute: async () => {
826
+ try {
827
+ const isNativeFs = !!document.fullscreenElement;
828
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
829
+ if (!isNativeFs && !isCssFs) {
830
+ if (this.wrapperEl?.requestFullscreen) {
831
+ await this.wrapperEl.requestFullscreen().catch(() => {
832
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
833
+ });
834
+ } else {
835
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
836
+ }
837
+ } else {
838
+ if (document.fullscreenElement) {
839
+ await document.exitFullscreen().catch(() => {
840
+ });
841
+ }
842
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
843
+ }
844
+ } catch {
845
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
808
846
  }
847
+ setTimeout(() => {
848
+ this.activeInstance?.fitToPage?.();
849
+ }, 120);
809
850
  }
810
851
  });
811
852
  }
@@ -819,6 +860,7 @@ var FilePreviewViewer = class {
819
860
  if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
820
861
  this.thumbnailPanel.update(thumbnails, (index) => {
821
862
  instance.goToPage?.(index + 1);
863
+ this.toolbar?.setPage(index + 1);
822
864
  });
823
865
  if (options.showThumbnails) {
824
866
  this.thumbnailPanel.show();
@@ -854,6 +896,15 @@ var FilePreviewViewer = class {
854
896
  window.removeEventListener("keydown", this.keyHandler);
855
897
  this.keyHandler = null;
856
898
  }
899
+ if (this.resizeObserver) {
900
+ this.resizeObserver.disconnect();
901
+ this.resizeObserver = null;
902
+ }
903
+ if (this.fullscreenHandler) {
904
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
905
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
906
+ this.fullscreenHandler = null;
907
+ }
857
908
  this.abort();
858
909
  this.destroyInstance();
859
910
  this.toolbar?.destroy();
@@ -933,6 +984,25 @@ var FilePreviewViewer = class {
933
984
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
934
985
  this.setupKeyboardShortcuts();
935
986
  this.setupDragAndDrop(container, options);
987
+ this.setupResizeAndFullscreenListeners();
988
+ }
989
+ setupResizeAndFullscreenListeners() {
990
+ if (this.fullscreenHandler) return;
991
+ this.fullscreenHandler = () => {
992
+ setTimeout(() => {
993
+ this.activeInstance?.fitToPage?.();
994
+ }, 100);
995
+ };
996
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
997
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
998
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
999
+ this.resizeObserver = new ResizeObserver(() => {
1000
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1001
+ this.activeInstance.fitToPage?.();
1002
+ }
1003
+ });
1004
+ this.resizeObserver.observe(this.contentEl);
1005
+ }
936
1006
  }
937
1007
  setupKeyboardShortcuts() {
938
1008
  if (this.keyHandler) return;
@@ -944,9 +1014,13 @@ var FilePreviewViewer = class {
944
1014
  if (e.key === "ArrowRight" || e.key === "PageDown") {
945
1015
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
946
1016
  this.activeInstance.goToPage?.(cur + 1);
1017
+ const nextCur = this.activeInstance.getCurrentPage?.() ?? cur + 1;
1018
+ this.toolbar?.setPage(nextCur);
947
1019
  } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
948
1020
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
949
1021
  this.activeInstance.goToPage?.(Math.max(1, cur - 1));
1022
+ const prevCur = this.activeInstance.getCurrentPage?.() ?? Math.max(1, cur - 1);
1023
+ this.toolbar?.setPage(prevCur);
950
1024
  } else if (e.key === "+" || e.key === "=") {
951
1025
  this.activeInstance.zoomIn?.();
952
1026
  } else if (e.key === "-" || e.key === "_") {
@@ -1212,30 +1286,62 @@ var CfbfReader = class {
1212
1286
  return result.subarray(0, targetSize);
1213
1287
  }
1214
1288
  };
1215
-
1216
- // ../plugins/pdf/dist/index.js
1289
+ if (typeof window !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
1290
+ if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
1291
+ pdfjsLib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1292
+ }
1293
+ }
1217
1294
  var PdfPlugin = class {
1218
1295
  id = "pdf";
1219
- name = "PDF Preview";
1296
+ name = "PDF Document Preview";
1220
1297
  extensions = [".pdf"];
1221
1298
  mimeTypes = ["application/pdf"];
1222
1299
  weight = 100;
1223
1300
  supports(file) {
1224
1301
  const ext = file.metadata.extension?.toLowerCase();
1225
1302
  const mime = file.metadata.mimeType?.toLowerCase();
1226
- return ext === ".pdf" || mime === "application/pdf";
1303
+ if (ext) return this.extensions.includes(ext);
1304
+ return this.mimeTypes.includes(mime || "");
1227
1305
  }
1228
1306
  getToolbarActions(instance) {
1307
+ const totalPages = instance.getPageCount?.() ?? 1;
1308
+ const curPage = instance.getCurrentPage?.() ?? 1;
1229
1309
  return [
1310
+ {
1311
+ id: "thumbnails",
1312
+ icon: "thumbnails",
1313
+ label: "Page Thumbnails",
1314
+ type: "button",
1315
+ group: "navigation",
1316
+ execute: () => instance.toggleThumbnails?.()
1317
+ },
1318
+ {
1319
+ id: "page-nav",
1320
+ icon: "",
1321
+ label: "Page Navigation",
1322
+ type: "page-nav",
1323
+ group: "navigation",
1324
+ value: curPage,
1325
+ max: totalPages,
1326
+ execute: (action, page) => {
1327
+ const cur = instance.getCurrentPage?.() ?? 1;
1328
+ const max = instance.getPageCount?.() ?? 1;
1329
+ if (action === "prev") {
1330
+ if (cur > 1) instance.goToPage?.(cur - 1);
1331
+ } else if (action === "next") {
1332
+ if (cur < max) instance.goToPage?.(cur + 1);
1333
+ } else if (typeof page === "number") {
1334
+ instance.goToPage?.(page);
1335
+ }
1336
+ }
1337
+ },
1230
1338
  {
1231
1339
  id: "zoom-out",
1232
1340
  icon: "zoom-out",
1233
1341
  label: "Zoom Out",
1234
1342
  type: "button",
1235
1343
  group: "zoom",
1236
- execute: () => {
1237
- instance.zoomOut?.();
1238
- }
1344
+ execute: () => instance.zoomOut?.()
1239
1345
  },
1240
1346
  {
1241
1347
  id: "zoom-in",
@@ -1243,9 +1349,7 @@ var PdfPlugin = class {
1243
1349
  label: "Zoom In",
1244
1350
  type: "button",
1245
1351
  group: "zoom",
1246
- execute: () => {
1247
- instance.zoomIn?.();
1248
- }
1352
+ execute: () => instance.zoomIn?.()
1249
1353
  },
1250
1354
  {
1251
1355
  id: "fit-page",
@@ -1253,39 +1357,23 @@ var PdfPlugin = class {
1253
1357
  label: "Fit to Page",
1254
1358
  type: "button",
1255
1359
  group: "zoom",
1256
- execute: () => {
1257
- instance.fitToPage?.();
1258
- }
1360
+ execute: () => instance.fitToPage?.()
1259
1361
  },
1260
1362
  {
1261
1363
  id: "rotate-cw",
1262
1364
  icon: "rotate-cw",
1263
- label: "Rotate",
1365
+ label: "Rotate Clockwise",
1264
1366
  type: "button",
1265
1367
  group: "view",
1266
- execute: () => {
1267
- instance.rotateCW?.();
1268
- }
1269
- },
1270
- {
1271
- id: "page-nav",
1272
- icon: "page-nav",
1273
- label: "Page Navigation",
1274
- type: "page-nav",
1275
- group: "navigation",
1276
- execute: (page) => {
1277
- if (typeof page === "number") instance.goToPage?.(page);
1278
- }
1368
+ execute: () => instance.rotateCW?.()
1279
1369
  },
1280
1370
  {
1281
1371
  id: "download",
1282
1372
  icon: "download",
1283
- label: "Download",
1373
+ label: "Download PDF",
1284
1374
  type: "button",
1285
1375
  group: "actions",
1286
- execute: () => {
1287
- instance.download?.();
1288
- }
1376
+ execute: () => instance.download?.()
1289
1377
  },
1290
1378
  {
1291
1379
  id: "print",
@@ -1293,99 +1381,224 @@ var PdfPlugin = class {
1293
1381
  label: "Print",
1294
1382
  type: "button",
1295
1383
  group: "actions",
1296
- execute: () => {
1297
- instance.print?.();
1298
- }
1384
+ execute: () => instance.print?.()
1299
1385
  }
1300
1386
  ];
1301
1387
  }
1302
1388
  async render(ctx) {
1303
- const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1304
- const url = URL.createObjectURL(blob);
1305
- const wrapper = document.createElement("div");
1306
- wrapper.style.width = "100%";
1307
- wrapper.style.height = "100%";
1308
- wrapper.style.overflow = "hidden";
1309
- wrapper.style.display = "flex";
1310
- wrapper.style.justifyContent = "center";
1311
- wrapper.style.alignItems = "center";
1312
- const iframe = document.createElement("iframe");
1313
- iframe.src = url;
1314
- iframe.style.width = "100%";
1315
- iframe.style.height = "100%";
1316
- iframe.style.border = "none";
1317
- wrapper.appendChild(iframe);
1318
- ctx.container.appendChild(wrapper);
1389
+ const container = document.createElement("div");
1390
+ container.className = "fp-pdf-container";
1391
+ container.style.width = "100%";
1392
+ container.style.height = "100%";
1393
+ container.style.overflow = "auto";
1394
+ container.style.display = "flex";
1395
+ container.style.flexDirection = "column";
1396
+ container.style.alignItems = "center";
1397
+ container.style.justifyContent = "flex-start";
1398
+ container.style.padding = "20px 16px";
1399
+ container.style.backgroundColor = "#0f172a";
1400
+ container.style.boxSizing = "border-box";
1401
+ container.style.position = "relative";
1402
+ const pageCard = document.createElement("div");
1403
+ pageCard.className = "fp-pdf-page-card";
1404
+ pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
1405
+ pageCard.style.backgroundColor = "#ffffff";
1406
+ pageCard.style.borderRadius = "4px";
1407
+ pageCard.style.overflow = "hidden";
1408
+ pageCard.style.lineHeight = "0";
1409
+ pageCard.style.transition = "transform 0.15s ease";
1410
+ pageCard.style.position = "relative";
1411
+ pageCard.style.flexShrink = "0";
1412
+ let canvas = document.createElement("canvas");
1413
+ pageCard.appendChild(canvas);
1414
+ container.appendChild(pageCard);
1415
+ const indicator = document.createElement("div");
1416
+ indicator.className = "fp-pdf-page-indicator";
1417
+ indicator.style.position = "sticky";
1418
+ indicator.style.bottom = "16px";
1419
+ indicator.style.marginTop = "16px";
1420
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1421
+ indicator.style.backdropFilter = "blur(8px)";
1422
+ indicator.style.color = "#f8fafc";
1423
+ indicator.style.fontSize = "12px";
1424
+ indicator.style.fontWeight = "600";
1425
+ indicator.style.padding = "5px 14px";
1426
+ indicator.style.borderRadius = "20px";
1427
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1428
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1429
+ indicator.style.zIndex = "10";
1430
+ indicator.style.userSelect = "none";
1431
+ indicator.style.pointerEvents = "none";
1432
+ container.appendChild(indicator);
1433
+ ctx.container.appendChild(container);
1434
+ const loadingTask = pdfjsLib.getDocument({
1435
+ data: new Uint8Array(ctx.buffer),
1436
+ cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/cmaps/`,
1437
+ cMapPacked: true,
1438
+ standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/standard_fonts/`
1439
+ });
1440
+ const pdfDoc = await loadingTask.promise;
1441
+ const totalPages = Math.max(1, pdfDoc.numPages);
1319
1442
  let currentPage = 1;
1320
- let currentZoom = 1;
1443
+ let zoomScale = 1;
1321
1444
  let rotation = 0;
1445
+ let currentRenderTask = null;
1446
+ const renderPage = async (pageNum) => {
1447
+ if (currentRenderTask) {
1448
+ try {
1449
+ currentRenderTask.cancel();
1450
+ } catch {
1451
+ }
1452
+ currentRenderTask = null;
1453
+ }
1454
+ const newCanvas = document.createElement("canvas");
1455
+ pageCard.replaceChild(newCanvas, canvas);
1456
+ canvas = newCanvas;
1457
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
1458
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1459
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
1460
+ const page = await pdfDoc.getPage(currentPage);
1461
+ const containerWidth = container.clientWidth || 900;
1462
+ const containerHeight = container.clientHeight || 700;
1463
+ const unscaledVp = page.getViewport({ scale: 1, rotation });
1464
+ const availWidth = Math.max(100, containerWidth - 48);
1465
+ const availHeight = Math.max(100, containerHeight - 88);
1466
+ const scaleW = availWidth / unscaledVp.width;
1467
+ const scaleH = availHeight / unscaledVp.height;
1468
+ const fitScale = Math.min(scaleW, scaleH);
1469
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1470
+ const pixelRatio = window.devicePixelRatio || 1;
1471
+ const viewport = page.getViewport({ scale: effectiveScale, rotation });
1472
+ canvas.width = Math.floor(viewport.width * pixelRatio);
1473
+ canvas.height = Math.floor(viewport.height * pixelRatio);
1474
+ canvas.style.width = `${Math.floor(viewport.width)}px`;
1475
+ canvas.style.height = `${Math.floor(viewport.height)}px`;
1476
+ const canvasCtx = canvas.getContext("2d");
1477
+ if (!canvasCtx) return;
1478
+ canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
1479
+ currentRenderTask = page.render({
1480
+ canvasContext: canvasCtx,
1481
+ viewport
1482
+ });
1483
+ try {
1484
+ await currentRenderTask.promise;
1485
+ } catch (err) {
1486
+ if (err?.name !== "RenderingCancelledException") {
1487
+ console.warn("[PdfPlugin] Page render warning:", err);
1488
+ }
1489
+ } finally {
1490
+ currentRenderTask = null;
1491
+ }
1492
+ };
1493
+ await renderPage(1);
1322
1494
  const cleanup = () => {
1323
- URL.revokeObjectURL(url);
1324
- wrapper.remove();
1495
+ if (currentRenderTask) {
1496
+ try {
1497
+ currentRenderTask.cancel();
1498
+ } catch {
1499
+ }
1500
+ }
1501
+ try {
1502
+ pdfDoc.destroy();
1503
+ } catch {
1504
+ }
1505
+ container.remove();
1325
1506
  ctx.container.innerHTML = "";
1326
1507
  };
1327
1508
  ctx.signal.addEventListener("abort", cleanup);
1328
- return {
1509
+ const instance = {
1329
1510
  destroy: cleanup,
1330
1511
  zoomIn: () => {
1331
- currentZoom += 0.1;
1332
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1512
+ zoomScale = Math.min(3.5, zoomScale + 0.2);
1513
+ renderPage(currentPage);
1333
1514
  },
1334
1515
  zoomOut: () => {
1335
- currentZoom = Math.max(0.2, currentZoom - 0.1);
1336
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1516
+ zoomScale = Math.max(0.3, zoomScale - 0.2);
1517
+ renderPage(currentPage);
1337
1518
  },
1338
- getZoom: () => currentZoom,
1519
+ getZoom: () => zoomScale,
1339
1520
  setZoom: (level) => {
1340
- currentZoom = level;
1341
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1521
+ zoomScale = Math.max(0.3, Math.min(3.5, level));
1522
+ renderPage(currentPage);
1342
1523
  },
1343
1524
  fitToPage: () => {
1344
- currentZoom = 1;
1345
- iframe.style.transform = `scale(1) rotate(${rotation}deg)`;
1525
+ zoomScale = 1;
1526
+ rotation = 0;
1527
+ renderPage(currentPage);
1346
1528
  },
1347
1529
  rotateCW: () => {
1348
1530
  rotation = (rotation + 90) % 360;
1349
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1531
+ renderPage(currentPage);
1350
1532
  },
1351
1533
  rotateCCW: () => {
1352
1534
  rotation = (rotation - 90 + 360) % 360;
1353
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1535
+ renderPage(currentPage);
1354
1536
  },
1355
1537
  getRotation: () => rotation,
1538
+ getPageCount: () => totalPages,
1539
+ getCurrentPage: () => currentPage,
1356
1540
  goToPage: (page) => {
1357
- currentPage = page;
1358
- iframe.src = `${url}#page=${page}`;
1541
+ renderPage(page);
1359
1542
  },
1360
- getCurrentPage: () => currentPage,
1361
1543
  download: () => {
1544
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1545
+ const url = URL.createObjectURL(blob);
1362
1546
  const a = document.createElement("a");
1363
1547
  a.href = url;
1364
1548
  a.download = ctx.metadata.name || "document.pdf";
1365
1549
  a.click();
1550
+ URL.revokeObjectURL(url);
1366
1551
  },
1367
1552
  print: () => {
1368
- iframe.contentWindow?.print();
1553
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1554
+ const url = URL.createObjectURL(blob);
1555
+ const hiddenIframe = document.createElement("iframe");
1556
+ hiddenIframe.style.position = "fixed";
1557
+ hiddenIframe.style.right = "0";
1558
+ hiddenIframe.style.bottom = "0";
1559
+ hiddenIframe.style.width = "0";
1560
+ hiddenIframe.style.height = "0";
1561
+ hiddenIframe.style.border = "0";
1562
+ document.body.appendChild(hiddenIframe);
1563
+ hiddenIframe.src = url;
1564
+ hiddenIframe.onload = () => {
1565
+ setTimeout(() => {
1566
+ hiddenIframe.contentWindow?.print();
1567
+ setTimeout(() => {
1568
+ hiddenIframe.remove();
1569
+ URL.revokeObjectURL(url);
1570
+ }, 1e3);
1571
+ }, 300);
1572
+ };
1369
1573
  },
1370
1574
  getThumbnails: async () => {
1371
- return [
1372
- {
1373
- index: 1,
1374
- label: "Page 1",
1375
- render: async (canvas) => {
1376
- const context = canvas.getContext("2d");
1377
- if (context) {
1378
- context.fillStyle = "#fff";
1379
- context.fillRect(0, 0, canvas.width, canvas.height);
1380
- context.fillStyle = "#333";
1381
- context.font = "12px sans-serif";
1382
- context.fillText("PDF Preview", 10, 20);
1575
+ const thumbnails = [];
1576
+ const count = Math.min(totalPages, 50);
1577
+ for (let i = 1; i <= count; i++) {
1578
+ thumbnails.push({
1579
+ index: i,
1580
+ label: `Page ${i}`,
1581
+ render: async (thumbCanvas) => {
1582
+ try {
1583
+ const p = await pdfDoc.getPage(i);
1584
+ const baseVp = p.getViewport({ scale: 1 });
1585
+ const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1586
+ const thumbVp = p.getViewport({ scale: thumbScale });
1587
+ thumbCanvas.height = Math.floor(thumbVp.height);
1588
+ const tCtx = thumbCanvas.getContext("2d");
1589
+ if (tCtx) {
1590
+ await p.render({ canvasContext: tCtx, viewport: thumbVp }).promise;
1591
+ }
1592
+ } catch (e) {
1593
+ console.warn(`[PdfPlugin] Error generating thumbnail for page ${i}:`, e);
1383
1594
  }
1384
1595
  }
1385
- }
1386
- ];
1596
+ });
1597
+ }
1598
+ return thumbnails;
1387
1599
  }
1388
1600
  };
1601
+ return instance;
1389
1602
  }
1390
1603
  };
1391
1604
  function pdfPlugin() {
@@ -1712,7 +1925,31 @@ var DocxPlugin = class {
1712
1925
  return this.mimeTypes.includes(mime || "");
1713
1926
  }
1714
1927
  getToolbarActions(instance) {
1715
- return [
1928
+ const totalPages = instance.getPageCount?.() ?? 1;
1929
+ const actions = [];
1930
+ if (totalPages > 1) {
1931
+ actions.push({
1932
+ id: "page-nav",
1933
+ icon: "",
1934
+ label: "Page Navigation",
1935
+ type: "page-nav",
1936
+ group: "navigation",
1937
+ value: instance.getCurrentPage?.() ?? 1,
1938
+ max: totalPages,
1939
+ execute: (action, page) => {
1940
+ const cur = instance.getCurrentPage?.() ?? 1;
1941
+ const max = instance.getPageCount?.() ?? 1;
1942
+ if (action === "prev") {
1943
+ if (cur > 1) instance.goToPage?.(cur - 1);
1944
+ } else if (action === "next") {
1945
+ if (cur < max) instance.goToPage?.(cur + 1);
1946
+ } else if (typeof page === "number") {
1947
+ instance.goToPage?.(page);
1948
+ }
1949
+ }
1950
+ });
1951
+ }
1952
+ actions.push(
1716
1953
  {
1717
1954
  id: "zoom-out",
1718
1955
  icon: "zoom-out",
@@ -1737,6 +1974,14 @@ var DocxPlugin = class {
1737
1974
  group: "zoom",
1738
1975
  execute: () => instance.fitToPage?.()
1739
1976
  },
1977
+ {
1978
+ id: "rotate-cw",
1979
+ icon: "rotate-cw",
1980
+ label: "Rotate",
1981
+ type: "button",
1982
+ group: "view",
1983
+ execute: () => instance.rotateCW?.()
1984
+ },
1740
1985
  {
1741
1986
  id: "download",
1742
1987
  icon: "download",
@@ -1753,7 +1998,8 @@ var DocxPlugin = class {
1753
1998
  group: "actions",
1754
1999
  execute: () => instance.print?.()
1755
2000
  }
1756
- ];
2001
+ );
2002
+ return actions;
1757
2003
  }
1758
2004
  async render(ctx) {
1759
2005
  const wrapper = document.createElement("div");
@@ -1829,7 +2075,71 @@ var DocxPlugin = class {
1829
2075
  }
1830
2076
  }
1831
2077
  }
2078
+ const sections = wrapper.querySelectorAll("section.docx");
2079
+ const cards = wrapper.querySelectorAll(".fp-docx-page-card");
2080
+ const pageElements = sections.length > 0 ? sections : cards;
2081
+ const totalPages = Math.max(1, pageElements.length);
2082
+ let currentPage = 1;
2083
+ let indicator = null;
2084
+ if (totalPages > 1) {
2085
+ indicator = document.createElement("div");
2086
+ indicator.className = "fp-docx-page-indicator";
2087
+ indicator.style.position = "sticky";
2088
+ indicator.style.bottom = "16px";
2089
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2090
+ indicator.style.backdropFilter = "blur(8px)";
2091
+ indicator.style.color = "#f8fafc";
2092
+ indicator.style.fontSize = "12px";
2093
+ indicator.style.fontWeight = "600";
2094
+ indicator.style.padding = "5px 14px";
2095
+ indicator.style.borderRadius = "20px";
2096
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2097
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2098
+ indicator.style.zIndex = "10";
2099
+ indicator.style.userSelect = "none";
2100
+ indicator.style.pointerEvents = "none";
2101
+ indicator.style.textAlign = "center";
2102
+ indicator.style.width = "fit-content";
2103
+ indicator.style.margin = "16px auto 0";
2104
+ ctx.container.appendChild(indicator);
2105
+ }
2106
+ const showPage = (pageNum) => {
2107
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
2108
+ if (pageElements.length > 1) {
2109
+ pageElements.forEach((sec, idx) => {
2110
+ sec.style.display = idx + 1 === currentPage ? "block" : "none";
2111
+ });
2112
+ }
2113
+ if (indicator) {
2114
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2115
+ }
2116
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
2117
+ };
2118
+ if (totalPages > 1) {
2119
+ showPage(1);
2120
+ }
2121
+ scale = 1;
2122
+ let rotation = 0;
2123
+ const calculateFitScale = () => {
2124
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2125
+ const elW = activeEl.offsetWidth || 816;
2126
+ const elH = activeEl.offsetHeight || 1056;
2127
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2128
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2129
+ const sW = availW / elW;
2130
+ const sH = availH / elH;
2131
+ return Math.min(1.1, Math.min(sW, sH));
2132
+ };
2133
+ const applyTransform = () => {
2134
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2135
+ wrapper.style.transformOrigin = "top center";
2136
+ };
2137
+ setTimeout(() => {
2138
+ scale = calculateFitScale();
2139
+ applyTransform();
2140
+ }, 60);
1832
2141
  const cleanup = () => {
2142
+ indicator?.remove();
1833
2143
  for (const url of createdBlobUrls) {
1834
2144
  URL.revokeObjectURL(url);
1835
2145
  }
@@ -1840,22 +2150,36 @@ var DocxPlugin = class {
1840
2150
  ctx.signal.addEventListener("abort", cleanup);
1841
2151
  return {
1842
2152
  destroy: cleanup,
2153
+ getPageCount: () => totalPages,
2154
+ getCurrentPage: () => currentPage,
2155
+ goToPage: (page) => {
2156
+ showPage(page);
2157
+ },
1843
2158
  zoomIn: () => {
1844
- scale += 0.1;
1845
- wrapper.style.transform = `scale(${scale})`;
2159
+ scale += 0.15;
2160
+ applyTransform();
1846
2161
  },
1847
2162
  zoomOut: () => {
1848
- scale = Math.max(0.2, scale - 0.1);
1849
- wrapper.style.transform = `scale(${scale})`;
2163
+ scale = Math.max(0.2, scale - 0.15);
2164
+ applyTransform();
1850
2165
  },
1851
2166
  getZoom: () => scale,
1852
2167
  setZoom: (level) => {
1853
2168
  scale = level;
1854
- wrapper.style.transform = `scale(${scale})`;
2169
+ applyTransform();
1855
2170
  },
1856
2171
  fitToPage: () => {
1857
- scale = 1;
1858
- wrapper.style.transform = "scale(1)";
2172
+ scale = calculateFitScale();
2173
+ rotation = 0;
2174
+ applyTransform();
2175
+ },
2176
+ rotateCW: () => {
2177
+ rotation = (rotation + 90) % 360;
2178
+ applyTransform();
2179
+ },
2180
+ rotateCCW: () => {
2181
+ rotation = (rotation - 90 + 360) % 360;
2182
+ applyTransform();
1859
2183
  },
1860
2184
  download: () => {
1861
2185
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2104,7 +2428,31 @@ var ExcelPlugin = class {
2104
2428
  return this.mimeTypes.includes(mime || "");
2105
2429
  }
2106
2430
  getToolbarActions(instance) {
2107
- return [
2431
+ const totalSheets = instance.getPageCount?.() ?? 1;
2432
+ const actions = [];
2433
+ if (totalSheets > 1) {
2434
+ actions.push({
2435
+ id: "page-nav",
2436
+ icon: "",
2437
+ label: "Sheet Navigation",
2438
+ type: "page-nav",
2439
+ group: "navigation",
2440
+ value: instance.getCurrentPage?.() ?? 1,
2441
+ max: totalSheets,
2442
+ execute: (action, sheet) => {
2443
+ const cur = instance.getCurrentPage?.() ?? 1;
2444
+ const max = instance.getPageCount?.() ?? 1;
2445
+ if (action === "prev") {
2446
+ if (cur > 1) instance.goToPage?.(cur - 1);
2447
+ } else if (action === "next") {
2448
+ if (cur < max) instance.goToPage?.(cur + 1);
2449
+ } else if (typeof sheet === "number") {
2450
+ instance.goToPage?.(sheet);
2451
+ }
2452
+ }
2453
+ });
2454
+ }
2455
+ actions.push(
2108
2456
  {
2109
2457
  id: "zoom-out",
2110
2458
  icon: "zoom-out",
@@ -2126,13 +2474,23 @@ var ExcelPlugin = class {
2126
2474
  }
2127
2475
  },
2128
2476
  {
2129
- id: "page-nav",
2130
- icon: "page-nav",
2131
- label: "Sheet Navigation",
2132
- type: "page-nav",
2133
- group: "navigation",
2134
- execute: (sheet) => {
2135
- if (typeof sheet === "number") instance.goToPage?.(sheet);
2477
+ id: "fit-page",
2478
+ icon: "fit-page",
2479
+ label: "Fit to View",
2480
+ type: "button",
2481
+ group: "zoom",
2482
+ execute: () => {
2483
+ instance.fitToPage?.();
2484
+ }
2485
+ },
2486
+ {
2487
+ id: "rotate-cw",
2488
+ icon: "rotate-cw",
2489
+ label: "Rotate",
2490
+ type: "button",
2491
+ group: "view",
2492
+ execute: () => {
2493
+ instance.rotateCW?.();
2136
2494
  }
2137
2495
  },
2138
2496
  {
@@ -2155,7 +2513,8 @@ var ExcelPlugin = class {
2155
2513
  instance.print?.();
2156
2514
  }
2157
2515
  }
2158
- ];
2516
+ );
2517
+ return actions;
2159
2518
  }
2160
2519
  async render(ctx) {
2161
2520
  const container = document.createElement("div");
@@ -2184,9 +2543,23 @@ var ExcelPlugin = class {
2184
2543
  container.appendChild(tabsArea);
2185
2544
  ctx.container.appendChild(container);
2186
2545
  let scale = 1;
2546
+ let rotation = 0;
2187
2547
  let currentSheetIndex = 1;
2188
2548
  let sheetNames = [];
2189
2549
  let wb = null;
2550
+ const applyTransform = () => {
2551
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2552
+ contentArea.style.transformOrigin = "top left";
2553
+ };
2554
+ const calculateFitScale = () => {
2555
+ const table = contentArea.querySelector("table");
2556
+ if (!table) return 1;
2557
+ const availW = Math.max(200, container.clientWidth - 48);
2558
+ const availH = Math.max(200, container.clientHeight - 80);
2559
+ const tW = table.offsetWidth || 800;
2560
+ const tH = table.offsetHeight || 600;
2561
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2562
+ };
2190
2563
  try {
2191
2564
  wb = XLSX.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2192
2565
  sheetNames = wb.SheetNames || [];
@@ -2239,6 +2612,7 @@ var ExcelPlugin = class {
2239
2612
  b.style.fontWeight = "normal";
2240
2613
  }
2241
2614
  });
2615
+ ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
2242
2616
  };
2243
2617
  if (sheetNames.length > 0) {
2244
2618
  sheetNames.forEach((name, idx) => {
@@ -2273,17 +2647,30 @@ var ExcelPlugin = class {
2273
2647
  return {
2274
2648
  destroy: cleanup,
2275
2649
  zoomIn: () => {
2276
- scale += 0.1;
2277
- contentArea.style.transform = `scale(${scale})`;
2650
+ scale += 0.15;
2651
+ applyTransform();
2278
2652
  },
2279
2653
  zoomOut: () => {
2280
- scale = Math.max(0.2, scale - 0.1);
2281
- contentArea.style.transform = `scale(${scale})`;
2654
+ scale = Math.max(0.2, scale - 0.15);
2655
+ applyTransform();
2282
2656
  },
2283
2657
  getZoom: () => scale,
2284
2658
  setZoom: (level) => {
2285
2659
  scale = level;
2286
- contentArea.style.transform = `scale(${scale})`;
2660
+ applyTransform();
2661
+ },
2662
+ fitToPage: () => {
2663
+ scale = calculateFitScale();
2664
+ rotation = 0;
2665
+ applyTransform();
2666
+ },
2667
+ rotateCW: () => {
2668
+ rotation = (rotation + 90) % 360;
2669
+ applyTransform();
2670
+ },
2671
+ rotateCCW: () => {
2672
+ rotation = (rotation - 90 + 360) % 360;
2673
+ applyTransform();
2287
2674
  },
2288
2675
  goToPage: (page) => {
2289
2676
  if (page > 0 && page <= sheetNames.length) {
@@ -2488,7 +2875,31 @@ var CodePlugin = class {
2488
2875
  return false;
2489
2876
  }
2490
2877
  getToolbarActions(instance) {
2491
- return [
2878
+ const totalPages = instance.getPageCount?.() ?? 1;
2879
+ const actions = [];
2880
+ if (totalPages > 1) {
2881
+ actions.push({
2882
+ id: "page-nav",
2883
+ icon: "",
2884
+ label: "Page Navigation",
2885
+ type: "page-nav",
2886
+ group: "navigation",
2887
+ value: instance.getCurrentPage?.() ?? 1,
2888
+ max: totalPages,
2889
+ execute: (action, page) => {
2890
+ const cur = instance.getCurrentPage?.() ?? 1;
2891
+ const max = instance.getPageCount?.() ?? 1;
2892
+ if (action === "prev") {
2893
+ if (cur > 1) instance.goToPage?.(cur - 1);
2894
+ } else if (action === "next") {
2895
+ if (cur < max) instance.goToPage?.(cur + 1);
2896
+ } else if (typeof page === "number") {
2897
+ instance.goToPage?.(page);
2898
+ }
2899
+ }
2900
+ });
2901
+ }
2902
+ actions.push(
2492
2903
  {
2493
2904
  id: "zoom-out",
2494
2905
  icon: "zoom-out",
@@ -2509,6 +2920,26 @@ var CodePlugin = class {
2509
2920
  instance.zoomIn?.();
2510
2921
  }
2511
2922
  },
2923
+ {
2924
+ id: "fit-page",
2925
+ icon: "fit-page",
2926
+ label: "Fit to View",
2927
+ type: "button",
2928
+ group: "zoom",
2929
+ execute: () => {
2930
+ instance.fitToPage?.();
2931
+ }
2932
+ },
2933
+ {
2934
+ id: "rotate-cw",
2935
+ icon: "rotate-cw",
2936
+ label: "Rotate",
2937
+ type: "button",
2938
+ group: "view",
2939
+ execute: () => {
2940
+ instance.rotateCW?.();
2941
+ }
2942
+ },
2512
2943
  {
2513
2944
  id: "copy",
2514
2945
  icon: "copy",
@@ -2539,11 +2970,16 @@ var CodePlugin = class {
2539
2970
  instance.print?.();
2540
2971
  }
2541
2972
  }
2542
- ];
2973
+ );
2974
+ return actions;
2543
2975
  }
2544
2976
  async render(ctx) {
2545
2977
  const decoder = new TextDecoder("utf-8");
2546
- const text = decoder.decode(ctx.buffer);
2978
+ const fullText = decoder.decode(ctx.buffer);
2979
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2980
+ const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
2981
+ const totalPages = Math.max(1, rawPages.length);
2982
+ let currentPage = 1;
2547
2983
  const container = document.createElement("div");
2548
2984
  container.style.width = "100%";
2549
2985
  container.style.height = "100%";
@@ -2553,6 +2989,7 @@ var CodePlugin = class {
2553
2989
  container.style.padding = "16px";
2554
2990
  container.style.boxSizing = "border-box";
2555
2991
  let fontSize = 13;
2992
+ let rotation = 0;
2556
2993
  const pre = document.createElement("pre");
2557
2994
  pre.style.margin = "0";
2558
2995
  pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
@@ -2562,25 +2999,66 @@ var CodePlugin = class {
2562
2999
  pre.style.wordBreak = "break-all";
2563
3000
  const code = document.createElement("code");
2564
3001
  const ext = (ctx.metadata.extension || "").replace(".", "");
2565
- try {
2566
- if (ext && hljs.getLanguage(ext)) {
2567
- code.innerHTML = hljs.highlight(text, { language: ext }).value;
2568
- } else {
2569
- code.innerHTML = hljs.highlightAuto(text).value;
3002
+ const renderCodePage = (text) => {
3003
+ try {
3004
+ if (ext && hljs.getLanguage(ext)) {
3005
+ code.innerHTML = hljs.highlight(text, { language: ext }).value;
3006
+ } else {
3007
+ code.innerHTML = hljs.highlightAuto(text).value;
3008
+ }
3009
+ } catch {
3010
+ code.textContent = text;
2570
3011
  }
2571
- } catch {
2572
- code.textContent = text;
2573
- }
3012
+ };
3013
+ renderCodePage(rawPages[0] || fullText);
2574
3014
  pre.appendChild(code);
2575
3015
  container.appendChild(pre);
3016
+ let indicator = null;
3017
+ if (totalPages > 1) {
3018
+ indicator = document.createElement("div");
3019
+ indicator.className = "fp-code-page-indicator";
3020
+ indicator.style.position = "sticky";
3021
+ indicator.style.bottom = "16px";
3022
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3023
+ indicator.style.backdropFilter = "blur(8px)";
3024
+ indicator.style.color = "#f8fafc";
3025
+ indicator.style.fontSize = "12px";
3026
+ indicator.style.fontWeight = "600";
3027
+ indicator.style.padding = "5px 14px";
3028
+ indicator.style.borderRadius = "20px";
3029
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3030
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3031
+ indicator.style.zIndex = "10";
3032
+ indicator.style.userSelect = "none";
3033
+ indicator.style.pointerEvents = "none";
3034
+ indicator.style.textAlign = "center";
3035
+ indicator.style.width = "fit-content";
3036
+ indicator.style.margin = "16px auto 0";
3037
+ container.appendChild(indicator);
3038
+ }
3039
+ const showPage = (pageNum) => {
3040
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3041
+ renderCodePage(rawPages[currentPage - 1] || fullText);
3042
+ if (indicator) {
3043
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3044
+ }
3045
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3046
+ };
3047
+ if (totalPages > 1) {
3048
+ showPage(1);
3049
+ }
2576
3050
  ctx.container.appendChild(container);
2577
3051
  const cleanup = () => {
3052
+ indicator?.remove();
2578
3053
  container.remove();
2579
3054
  ctx.container.innerHTML = "";
2580
3055
  };
2581
3056
  ctx.signal.addEventListener("abort", cleanup);
2582
3057
  return {
2583
3058
  destroy: cleanup,
3059
+ getPageCount: () => totalPages,
3060
+ getCurrentPage: () => currentPage,
3061
+ goToPage: (page) => showPage(page),
2584
3062
  zoomIn: () => {
2585
3063
  fontSize = Math.min(32, fontSize + 2);
2586
3064
  pre.style.fontSize = `${fontSize}px`;
@@ -2594,6 +3072,22 @@ var CodePlugin = class {
2594
3072
  fontSize = Math.round(13 * level);
2595
3073
  pre.style.fontSize = `${fontSize}px`;
2596
3074
  },
3075
+ fitToPage: () => {
3076
+ fontSize = 13;
3077
+ rotation = 0;
3078
+ pre.style.fontSize = "13px";
3079
+ pre.style.transform = "none";
3080
+ },
3081
+ rotateCW: () => {
3082
+ rotation = (rotation + 90) % 360;
3083
+ pre.style.transform = `rotate(${rotation}deg)`;
3084
+ pre.style.transformOrigin = "top left";
3085
+ },
3086
+ rotateCCW: () => {
3087
+ rotation = (rotation - 90 + 360) % 360;
3088
+ pre.style.transform = `rotate(${rotation}deg)`;
3089
+ pre.style.transformOrigin = "top left";
3090
+ },
2597
3091
  download: () => {
2598
3092
  const mimeType = ctx.metadata.mimeType || "text/plain";
2599
3093
  const blob = new Blob([ctx.buffer], { type: mimeType });
@@ -2608,7 +3102,7 @@ var CodePlugin = class {
2608
3102
  window.print();
2609
3103
  },
2610
3104
  copy: () => {
2611
- navigator.clipboard?.writeText(text);
3105
+ navigator.clipboard?.writeText(fullText);
2612
3106
  }
2613
3107
  };
2614
3108
  }
@@ -3105,6 +3599,14 @@ var PptxPlugin = class {
3105
3599
  group: "zoom",
3106
3600
  execute: () => instance.fitToPage?.()
3107
3601
  },
3602
+ {
3603
+ id: "rotate-cw",
3604
+ icon: "rotate-cw",
3605
+ label: "Rotate",
3606
+ type: "button",
3607
+ group: "view",
3608
+ execute: () => instance.rotateCW?.()
3609
+ },
3108
3610
  {
3109
3611
  id: "download",
3110
3612
  icon: "download",
@@ -3129,6 +3631,7 @@ var PptxPlugin = class {
3129
3631
  const slideCount = renderer.slidePaths?.length || 1;
3130
3632
  let currentSlide = 1;
3131
3633
  let scale = 1;
3634
+ let rotation = 0;
3132
3635
  const wrapper = document.createElement("div");
3133
3636
  wrapper.className = "fp-pptx-wrapper";
3134
3637
  wrapper.style.cssText = `
@@ -3151,6 +3654,8 @@ var PptxPlugin = class {
3151
3654
  background: #ffffff;
3152
3655
  transform-origin: top center;
3153
3656
  transition: transform 0.2s ease;
3657
+ max-width: calc(100% - 32px);
3658
+ max-height: calc(100% - 48px);
3154
3659
  `;
3155
3660
  const canvas = document.createElement("canvas");
3156
3661
  slideContainer.appendChild(canvas);
@@ -3158,10 +3663,22 @@ var PptxPlugin = class {
3158
3663
  ctx.container.innerHTML = "";
3159
3664
  ctx.container.style.overflow = "auto";
3160
3665
  ctx.container.appendChild(wrapper);
3666
+ const calculateFitScale = () => {
3667
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3668
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3669
+ const cW = canvas.offsetWidth || 1280;
3670
+ const cH = canvas.offsetHeight || 720;
3671
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3672
+ };
3673
+ const applyTransform = () => {
3674
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3675
+ };
3161
3676
  const renderCurrentSlide = async () => {
3162
3677
  try {
3163
3678
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3164
3679
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3680
+ scale = calculateFitScale();
3681
+ applyTransform();
3165
3682
  } catch (err) {
3166
3683
  console.error("[PptxPlugin] Failed to render slide:", err);
3167
3684
  }
@@ -3184,21 +3701,30 @@ var PptxPlugin = class {
3184
3701
  getPageCount: () => slideCount,
3185
3702
  getCurrentPage: () => currentSlide,
3186
3703
  zoomIn: () => {
3187
- scale += 0.1;
3188
- slideContainer.style.transform = `scale(${scale})`;
3704
+ scale += 0.15;
3705
+ applyTransform();
3189
3706
  },
3190
3707
  zoomOut: () => {
3191
- scale = Math.max(0.2, scale - 0.1);
3192
- slideContainer.style.transform = `scale(${scale})`;
3708
+ scale = Math.max(0.2, scale - 0.15);
3709
+ applyTransform();
3193
3710
  },
3194
3711
  getZoom: () => scale,
3195
3712
  setZoom: (level) => {
3196
3713
  scale = level;
3197
- slideContainer.style.transform = `scale(${scale})`;
3714
+ applyTransform();
3198
3715
  },
3199
3716
  fitToPage: () => {
3200
- scale = 1;
3201
- slideContainer.style.transform = "scale(1)";
3717
+ scale = calculateFitScale();
3718
+ rotation = 0;
3719
+ applyTransform();
3720
+ },
3721
+ rotateCW: () => {
3722
+ rotation = (rotation + 90) % 360;
3723
+ applyTransform();
3724
+ },
3725
+ rotateCCW: () => {
3726
+ rotation = (rotation - 90 + 360) % 360;
3727
+ applyTransform();
3202
3728
  },
3203
3729
  getThumbnails: () => {
3204
3730
  const list = [];
@@ -3430,7 +3956,31 @@ var RtfPlugin = class {
3430
3956
  return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3431
3957
  }
3432
3958
  getToolbarActions(instance) {
3433
- return [
3959
+ const totalPages = instance.getPageCount?.() ?? 1;
3960
+ const actions = [];
3961
+ if (totalPages > 1) {
3962
+ actions.push({
3963
+ id: "page-nav",
3964
+ icon: "",
3965
+ label: "Page Navigation",
3966
+ type: "page-nav",
3967
+ group: "navigation",
3968
+ value: instance.getCurrentPage?.() ?? 1,
3969
+ max: totalPages,
3970
+ execute: (action, page) => {
3971
+ const cur = instance.getCurrentPage?.() ?? 1;
3972
+ const max = instance.getPageCount?.() ?? 1;
3973
+ if (action === "prev") {
3974
+ if (cur > 1) instance.goToPage?.(cur - 1);
3975
+ } else if (action === "next") {
3976
+ if (cur < max) instance.goToPage?.(cur + 1);
3977
+ } else if (typeof page === "number") {
3978
+ instance.goToPage?.(page);
3979
+ }
3980
+ }
3981
+ });
3982
+ }
3983
+ actions.push(
3434
3984
  {
3435
3985
  id: "zoom-out",
3436
3986
  icon: "zoom-out",
@@ -3455,6 +4005,14 @@ var RtfPlugin = class {
3455
4005
  group: "zoom",
3456
4006
  execute: () => instance.fitToPage?.()
3457
4007
  },
4008
+ {
4009
+ id: "rotate-cw",
4010
+ icon: "rotate-cw",
4011
+ label: "Rotate",
4012
+ type: "button",
4013
+ group: "view",
4014
+ execute: () => instance.rotateCW?.()
4015
+ },
3458
4016
  {
3459
4017
  id: "download",
3460
4018
  icon: "download",
@@ -3471,7 +4029,8 @@ var RtfPlugin = class {
3471
4029
  group: "actions",
3472
4030
  execute: () => instance.print?.()
3473
4031
  }
3474
- ];
4032
+ );
4033
+ return actions;
3475
4034
  }
3476
4035
  async render(ctx) {
3477
4036
  const wrapper = document.createElement("div");
@@ -3490,44 +4049,124 @@ var RtfPlugin = class {
3490
4049
  ctx.container.style.backgroundColor = "#f1f5f9";
3491
4050
  ctx.container.appendChild(wrapper);
3492
4051
  let scale = 1;
4052
+ let rotation = 0;
4053
+ let pageElements = [];
4054
+ const calculateFitScale = () => {
4055
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4056
+ const elW = activeEl.offsetWidth || 850;
4057
+ const elH = activeEl.offsetHeight || 1e3;
4058
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4059
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4060
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4061
+ };
4062
+ const applyTransform = () => {
4063
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4064
+ wrapper.style.transformOrigin = "top center";
4065
+ };
4066
+ setTimeout(() => {
4067
+ scale = calculateFitScale();
4068
+ applyTransform();
4069
+ }, 60);
3493
4070
  try {
3494
4071
  if (typeof RTFJS.loggingEnabled === "function") {
3495
4072
  RTFJS.loggingEnabled(false);
3496
4073
  }
3497
4074
  const doc = new RTFJS.Document(ctx.buffer, {});
3498
4075
  const htmlElements = await doc.render();
3499
- for (const el of htmlElements) {
4076
+ pageElements = htmlElements;
4077
+ for (let i = 0; i < htmlElements.length; i++) {
4078
+ const el = htmlElements[i];
4079
+ el.style.display = i === 0 ? "block" : "none";
3500
4080
  wrapper.appendChild(el);
3501
4081
  }
3502
4082
  } catch (err) {
3503
4083
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3504
4084
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3505
4085
  const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3506
- wrapper.innerHTML = `<pre style="white-space: pre-wrap; font-family: serif; color: #333;">${clean}</pre>`;
4086
+ const pre = document.createElement("pre");
4087
+ pre.style.whiteSpace = "pre-wrap";
4088
+ pre.style.fontFamily = "serif";
4089
+ pre.style.color = "#333";
4090
+ pre.textContent = clean;
4091
+ wrapper.appendChild(pre);
4092
+ pageElements = [pre];
4093
+ }
4094
+ const totalPages = Math.max(1, pageElements.length);
4095
+ let currentPage = 1;
4096
+ let indicator = null;
4097
+ if (totalPages > 1) {
4098
+ indicator = document.createElement("div");
4099
+ indicator.className = "fp-rtf-page-indicator";
4100
+ indicator.style.position = "sticky";
4101
+ indicator.style.bottom = "16px";
4102
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4103
+ indicator.style.backdropFilter = "blur(8px)";
4104
+ indicator.style.color = "#f8fafc";
4105
+ indicator.style.fontSize = "12px";
4106
+ indicator.style.fontWeight = "600";
4107
+ indicator.style.padding = "5px 14px";
4108
+ indicator.style.borderRadius = "20px";
4109
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4110
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4111
+ indicator.style.zIndex = "10";
4112
+ indicator.style.userSelect = "none";
4113
+ indicator.style.pointerEvents = "none";
4114
+ indicator.style.textAlign = "center";
4115
+ indicator.style.width = "fit-content";
4116
+ indicator.style.margin = "16px auto 0";
4117
+ ctx.container.appendChild(indicator);
4118
+ }
4119
+ const showPage = (pageNum) => {
4120
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4121
+ if (totalPages > 1) {
4122
+ pageElements.forEach((el, idx) => {
4123
+ el.style.display = idx + 1 === currentPage ? "block" : "none";
4124
+ });
4125
+ }
4126
+ if (indicator) {
4127
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4128
+ }
4129
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4130
+ };
4131
+ if (totalPages > 1) {
4132
+ showPage(1);
3507
4133
  }
3508
4134
  const cleanup = () => {
4135
+ indicator?.remove();
3509
4136
  wrapper.remove();
3510
4137
  ctx.container.innerHTML = "";
3511
4138
  };
3512
4139
  ctx.signal.addEventListener("abort", cleanup);
3513
4140
  return {
3514
4141
  destroy: cleanup,
4142
+ getPageCount: () => totalPages,
4143
+ getCurrentPage: () => currentPage,
4144
+ goToPage: (page) => showPage(page),
3515
4145
  zoomIn: () => {
3516
- scale += 0.1;
3517
- wrapper.style.transform = `scale(${scale})`;
4146
+ scale += 0.15;
4147
+ applyTransform();
3518
4148
  },
3519
4149
  zoomOut: () => {
3520
- scale = Math.max(0.2, scale - 0.1);
3521
- wrapper.style.transform = `scale(${scale})`;
4150
+ scale = Math.max(0.2, scale - 0.15);
4151
+ applyTransform();
3522
4152
  },
3523
4153
  getZoom: () => scale,
3524
4154
  setZoom: (level) => {
3525
4155
  scale = level;
3526
- wrapper.style.transform = `scale(${scale})`;
4156
+ applyTransform();
3527
4157
  },
3528
4158
  fitToPage: () => {
3529
- scale = 1;
3530
- wrapper.style.transform = "scale(1)";
4159
+ scale = calculateFitScale();
4160
+ rotation = 0;
4161
+ applyTransform();
4162
+ },
4163
+ rotateCW: () => {
4164
+ rotation = (rotation + 90) % 360;
4165
+ applyTransform();
4166
+ },
4167
+ rotateCCW: () => {
4168
+ rotation = (rotation - 90 + 360) % 360;
4169
+ applyTransform();
3531
4170
  },
3532
4171
  download: () => {
3533
4172
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3699,41 +4338,41 @@ var OpenDocumentPlugin = class {
3699
4338
  }
3700
4339
  getToolbarActions(instance) {
3701
4340
  const isPresentation = instance.isPresentation;
4341
+ const totalPages = instance.getPageCount?.() ?? 1;
3702
4342
  const actions = [];
3703
- if (isPresentation) {
3704
- actions.push(
3705
- {
4343
+ if (isPresentation || totalPages > 1) {
4344
+ if (isPresentation) {
4345
+ actions.push({
3706
4346
  id: "thumbnails",
3707
4347
  icon: "thumbnails",
3708
4348
  label: "Slide Thumbnails",
3709
4349
  type: "button",
3710
4350
  group: "navigation",
3711
4351
  execute: () => instance.toggleThumbnails?.()
3712
- },
3713
- {
3714
- id: "page-nav",
3715
- icon: "",
3716
- label: "Slide Navigation",
3717
- type: "page-nav",
3718
- group: "navigation",
3719
- value: instance.getCurrentPage?.() ?? 1,
3720
- max: instance.getPageCount?.() ?? 1,
3721
- execute: (action, page) => {
3722
- if (action === "prev") {
3723
- const cur = instance.getCurrentPage?.() ?? 1;
3724
- if (cur > 1) instance.goToPage?.(cur - 1);
3725
- } else if (action === "next") {
3726
- const cur = instance.getCurrentPage?.() ?? 1;
3727
- const total = instance.getPageCount?.() ?? 1;
3728
- if (cur < total) instance.goToPage?.(cur + 1);
3729
- } else if (typeof page === "number") {
3730
- instance.goToPage?.(page);
3731
- } else if (typeof action === "number") {
3732
- instance.goToPage?.(action);
3733
- }
4352
+ });
4353
+ }
4354
+ actions.push({
4355
+ id: "page-nav",
4356
+ icon: "",
4357
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
4358
+ type: "page-nav",
4359
+ group: "navigation",
4360
+ value: instance.getCurrentPage?.() ?? 1,
4361
+ max: totalPages,
4362
+ execute: (action, page) => {
4363
+ const cur = instance.getCurrentPage?.() ?? 1;
4364
+ const max = instance.getPageCount?.() ?? 1;
4365
+ if (action === "prev") {
4366
+ if (cur > 1) instance.goToPage?.(cur - 1);
4367
+ } else if (action === "next") {
4368
+ if (cur < max) instance.goToPage?.(cur + 1);
4369
+ } else if (typeof page === "number") {
4370
+ instance.goToPage?.(page);
4371
+ } else if (typeof action === "number") {
4372
+ instance.goToPage?.(action);
3734
4373
  }
3735
4374
  }
3736
- );
4375
+ });
3737
4376
  }
3738
4377
  actions.push(
3739
4378
  {
@@ -3755,11 +4394,19 @@ var OpenDocumentPlugin = class {
3755
4394
  {
3756
4395
  id: "fit-page",
3757
4396
  icon: "fit-page",
3758
- label: "Fit to Page",
4397
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
3759
4398
  type: "button",
3760
4399
  group: "zoom",
3761
4400
  execute: () => instance.fitToPage?.()
3762
4401
  },
4402
+ {
4403
+ id: "rotate-cw",
4404
+ icon: "rotate-cw",
4405
+ label: "Rotate",
4406
+ type: "button",
4407
+ group: "view",
4408
+ execute: () => instance.rotateCW?.()
4409
+ },
3763
4410
  {
3764
4411
  id: "download",
3765
4412
  icon: "download",
@@ -3823,6 +4470,22 @@ var OpenDocumentPlugin = class {
3823
4470
  container.appendChild(wrapper);
3824
4471
  ctx.container.appendChild(container);
3825
4472
  let scale = 1;
4473
+ let rotation = 0;
4474
+ const calculateFitScale = () => {
4475
+ const elW = wrapper.offsetWidth || 850;
4476
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4477
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4478
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4479
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4480
+ };
4481
+ const applyTransform = () => {
4482
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4483
+ wrapper.style.transformOrigin = "top center";
4484
+ };
4485
+ setTimeout(() => {
4486
+ scale = calculateFitScale();
4487
+ applyTransform();
4488
+ }, 60);
3826
4489
  let currentPage = 1;
3827
4490
  let totalPages = 1;
3828
4491
  let slides = [];
@@ -3881,21 +4544,30 @@ var OpenDocumentPlugin = class {
3881
4544
  destroy: cleanup,
3882
4545
  isPresentation,
3883
4546
  zoomIn: () => {
3884
- scale += 0.1;
3885
- wrapper.style.transform = `scale(${scale})`;
4547
+ scale += 0.15;
4548
+ applyTransform();
3886
4549
  },
3887
4550
  zoomOut: () => {
3888
- scale = Math.max(0.2, scale - 0.1);
3889
- wrapper.style.transform = `scale(${scale})`;
4551
+ scale = Math.max(0.2, scale - 0.15);
4552
+ applyTransform();
3890
4553
  },
3891
4554
  getZoom: () => scale,
3892
4555
  setZoom: (level) => {
3893
4556
  scale = level;
3894
- wrapper.style.transform = `scale(${scale})`;
4557
+ applyTransform();
3895
4558
  },
3896
4559
  fitToPage: () => {
3897
- scale = 1;
3898
- wrapper.style.transform = "scale(1)";
4560
+ scale = calculateFitScale();
4561
+ rotation = 0;
4562
+ applyTransform();
4563
+ },
4564
+ rotateCW: () => {
4565
+ rotation = (rotation + 90) % 360;
4566
+ applyTransform();
4567
+ },
4568
+ rotateCCW: () => {
4569
+ rotation = (rotation - 90 + 360) % 360;
4570
+ applyTransform();
3899
4571
  },
3900
4572
  goToPage,
3901
4573
  getPageCount: () => totalPages,
@@ -4116,7 +4788,31 @@ var DocPlugin = class {
4116
4788
  return this.mimeTypes.includes(mime || "");
4117
4789
  }
4118
4790
  getToolbarActions(instance) {
4119
- return [
4791
+ const totalPages = instance.getPageCount?.() ?? 1;
4792
+ const actions = [];
4793
+ if (totalPages > 1) {
4794
+ actions.push({
4795
+ id: "page-nav",
4796
+ icon: "",
4797
+ label: "Page Navigation",
4798
+ type: "page-nav",
4799
+ group: "navigation",
4800
+ value: instance.getCurrentPage?.() ?? 1,
4801
+ max: totalPages,
4802
+ execute: (action, page) => {
4803
+ const cur = instance.getCurrentPage?.() ?? 1;
4804
+ const max = instance.getPageCount?.() ?? 1;
4805
+ if (action === "prev") {
4806
+ if (cur > 1) instance.goToPage?.(cur - 1);
4807
+ } else if (action === "next") {
4808
+ if (cur < max) instance.goToPage?.(cur + 1);
4809
+ } else if (typeof page === "number") {
4810
+ instance.goToPage?.(page);
4811
+ }
4812
+ }
4813
+ });
4814
+ }
4815
+ actions.push(
4120
4816
  {
4121
4817
  id: "zoom-out",
4122
4818
  icon: "zoom-out",
@@ -4141,6 +4837,14 @@ var DocPlugin = class {
4141
4837
  group: "zoom",
4142
4838
  execute: () => instance.fitToPage?.()
4143
4839
  },
4840
+ {
4841
+ id: "rotate-cw",
4842
+ icon: "rotate-cw",
4843
+ label: "Rotate",
4844
+ type: "button",
4845
+ group: "view",
4846
+ execute: () => instance.rotateCW?.()
4847
+ },
4144
4848
  {
4145
4849
  id: "copy",
4146
4850
  icon: "copy",
@@ -4165,7 +4869,8 @@ var DocPlugin = class {
4165
4869
  group: "actions",
4166
4870
  execute: () => instance.print?.()
4167
4871
  }
4168
- ];
4872
+ );
4873
+ return actions;
4169
4874
  }
4170
4875
  async render(ctx) {
4171
4876
  const container = document.createElement("div");
@@ -4192,6 +4897,7 @@ var DocPlugin = class {
4192
4897
  ctx.container.appendChild(container);
4193
4898
  let scale = 1;
4194
4899
  let extractedRawText = "";
4900
+ let isFallback = false;
4195
4901
  try {
4196
4902
  const cfbf = new CfbfReader(ctx.buffer);
4197
4903
  const wordDocStream = cfbf.readStream("WordDocument");
@@ -4205,42 +4911,131 @@ var DocPlugin = class {
4205
4911
  const tableStream = cfbf.readStream(tableName);
4206
4912
  const text = this.extractDocText(wordDocStream, tableStream);
4207
4913
  extractedRawText = text;
4208
- wrapper.innerHTML = this.formatDocToHtml(text, ctx.metadata.name || "Document");
4209
4914
  } catch (err) {
4210
4915
  console.warn("[DocPlugin] Binary parsing error, fallback text:", err);
4211
4916
  const fallback = this.heuristicTextExtraction(ctx.buffer);
4212
4917
  extractedRawText = fallback;
4213
- wrapper.innerHTML = `
4214
- <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4215
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${ctx.metadata.name || "Word Document (.doc)"}</h2>
4216
- <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4217
- </div>
4218
- ${this.formatDocToHtml(fallback, ctx.metadata.name || "Document")}
4219
- `;
4918
+ isFallback = true;
4919
+ }
4920
+ const rawPages = this.splitIntoPages(extractedRawText);
4921
+ const totalPages = Math.max(1, rawPages.length);
4922
+ let currentPage = 1;
4923
+ wrapper.innerHTML = "";
4924
+ const pageCards = [];
4925
+ for (let i = 0; i < totalPages; i++) {
4926
+ const pageCard = document.createElement("div");
4927
+ pageCard.className = "fp-doc-page-card";
4928
+ pageCard.style.backgroundColor = "#ffffff";
4929
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4930
+ pageCard.style.borderRadius = "4px";
4931
+ pageCard.style.padding = "56px 48px";
4932
+ pageCard.style.minHeight = "100%";
4933
+ pageCard.style.display = i === 0 ? "block" : "none";
4934
+ if (isFallback && i === 0) {
4935
+ pageCard.innerHTML = `
4936
+ <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4937
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4938
+ <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4939
+ </div>
4940
+ ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
4941
+ `;
4942
+ } else {
4943
+ pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4944
+ }
4945
+ wrapper.appendChild(pageCard);
4946
+ pageCards.push(pageCard);
4947
+ }
4948
+ let indicator = null;
4949
+ if (totalPages > 1) {
4950
+ indicator = document.createElement("div");
4951
+ indicator.className = "fp-doc-page-indicator";
4952
+ indicator.style.position = "sticky";
4953
+ indicator.style.bottom = "16px";
4954
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4955
+ indicator.style.backdropFilter = "blur(8px)";
4956
+ indicator.style.color = "#f8fafc";
4957
+ indicator.style.fontSize = "12px";
4958
+ indicator.style.fontWeight = "600";
4959
+ indicator.style.padding = "5px 14px";
4960
+ indicator.style.borderRadius = "20px";
4961
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4962
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4963
+ indicator.style.zIndex = "10";
4964
+ indicator.style.userSelect = "none";
4965
+ indicator.style.pointerEvents = "none";
4966
+ indicator.style.textAlign = "center";
4967
+ indicator.style.width = "fit-content";
4968
+ indicator.style.margin = "16px auto 0";
4969
+ container.appendChild(indicator);
4970
+ }
4971
+ const showPage = (pageNum) => {
4972
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4973
+ if (totalPages > 1) {
4974
+ pageCards.forEach((card, idx) => {
4975
+ card.style.display = idx + 1 === currentPage ? "block" : "none";
4976
+ });
4977
+ }
4978
+ if (indicator) {
4979
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4980
+ }
4981
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4982
+ };
4983
+ if (totalPages > 1) {
4984
+ showPage(1);
4220
4985
  }
4986
+ let rotation = 0;
4987
+ const calculateFitScale = () => {
4988
+ const activeCard = pageCards[currentPage - 1] || wrapper;
4989
+ const elW = activeCard.offsetWidth || 850;
4990
+ const elH = activeCard.offsetHeight || 1e3;
4991
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4992
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4993
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4994
+ };
4995
+ const applyTransform = () => {
4996
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4997
+ wrapper.style.transformOrigin = "top center";
4998
+ };
4999
+ setTimeout(() => {
5000
+ scale = calculateFitScale();
5001
+ applyTransform();
5002
+ }, 60);
4221
5003
  const cleanup = () => {
5004
+ indicator?.remove();
4222
5005
  container.remove();
4223
5006
  ctx.container.innerHTML = "";
4224
5007
  };
4225
5008
  ctx.signal.addEventListener("abort", cleanup);
4226
5009
  return {
4227
5010
  destroy: cleanup,
5011
+ getPageCount: () => totalPages,
5012
+ getCurrentPage: () => currentPage,
5013
+ goToPage: (page) => showPage(page),
4228
5014
  zoomIn: () => {
4229
- scale += 0.1;
4230
- wrapper.style.transform = `scale(${scale})`;
5015
+ scale += 0.15;
5016
+ applyTransform();
4231
5017
  },
4232
5018
  zoomOut: () => {
4233
- scale = Math.max(0.2, scale - 0.1);
4234
- wrapper.style.transform = `scale(${scale})`;
5019
+ scale = Math.max(0.2, scale - 0.15);
5020
+ applyTransform();
4235
5021
  },
4236
5022
  getZoom: () => scale,
4237
5023
  setZoom: (level) => {
4238
5024
  scale = level;
4239
- wrapper.style.transform = `scale(${scale})`;
5025
+ applyTransform();
4240
5026
  },
4241
5027
  fitToPage: () => {
4242
- scale = 1;
4243
- wrapper.style.transform = "scale(1)";
5028
+ scale = calculateFitScale();
5029
+ rotation = 0;
5030
+ applyTransform();
5031
+ },
5032
+ rotateCW: () => {
5033
+ rotation = (rotation + 90) % 360;
5034
+ applyTransform();
5035
+ },
5036
+ rotateCCW: () => {
5037
+ rotation = (rotation - 90 + 360) % 360;
5038
+ applyTransform();
4244
5039
  },
4245
5040
  copy: () => {
4246
5041
  navigator.clipboard.writeText(extractedRawText);
@@ -4356,11 +5151,16 @@ var DocPlugin = class {
4356
5151
  heuristicTextExtraction(buffer) {
4357
5152
  return this.extractStringsFromBytes(new Uint8Array(buffer));
4358
5153
  }
5154
+ splitIntoPages(text) {
5155
+ if (!text) return [""];
5156
+ 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);
5157
+ return parts.length > 0 ? parts : [text];
5158
+ }
4359
5159
  /**
4360
5160
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
4361
5161
  */
4362
5162
  formatDocToHtml(text, filename) {
4363
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x0C/g, "\n\n").split("\n");
5163
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4364
5164
  let html = "";
4365
5165
  let inList = false;
4366
5166
  for (const rawLine of lines) {
@@ -4475,6 +5275,14 @@ var PptPlugin = class {
4475
5275
  group: "zoom",
4476
5276
  execute: () => instance.fitToPage?.()
4477
5277
  },
5278
+ {
5279
+ id: "rotate-cw",
5280
+ icon: "rotate-cw",
5281
+ label: "Rotate",
5282
+ type: "button",
5283
+ group: "view",
5284
+ execute: () => instance.rotateCW?.()
5285
+ },
4478
5286
  {
4479
5287
  id: "download",
4480
5288
  icon: "download",
@@ -4508,22 +5316,36 @@ var PptPlugin = class {
4508
5316
  const slideCard = document.createElement("div");
4509
5317
  slideCard.className = "fp-ppt-slide-card";
4510
5318
  slideCard.style.width = "960px";
4511
- slideCard.style.maxWidth = "92%";
5319
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5320
+ slideCard.style.maxHeight = "calc(100% - 48px)";
4512
5321
  slideCard.style.aspectRatio = "16 / 9";
4513
5322
  slideCard.style.backgroundColor = "#ffffff";
4514
5323
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4515
5324
  slideCard.style.borderRadius = "8px";
4516
5325
  slideCard.style.boxSizing = "border-box";
4517
5326
  slideCard.style.position = "relative";
4518
- slideCard.style.overflow = "hidden";
4519
- slideCard.style.transformOrigin = "center center";
4520
5327
  slideCard.style.transition = "transform 0.2s ease";
5328
+ slideCard.style.transformOrigin = "center center";
5329
+ slideCard.style.flexShrink = "0";
4521
5330
  container.appendChild(slideCard);
4522
5331
  ctx.container.appendChild(container);
4523
5332
  let scale = 1;
5333
+ let rotation = 0;
4524
5334
  let currentSlide = 1;
4525
5335
  let slides = [];
4526
5336
  const createdBlobUrls = [];
5337
+ const calculateFitScale = () => {
5338
+ const availW = Math.max(200, container.clientWidth - 48);
5339
+ const availH = Math.max(200, container.clientHeight - 72);
5340
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5341
+ };
5342
+ const applyTransform = () => {
5343
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5344
+ };
5345
+ setTimeout(() => {
5346
+ scale = calculateFitScale();
5347
+ applyTransform();
5348
+ }, 60);
4527
5349
  try {
4528
5350
  const cfbf = new CfbfReader(ctx.buffer);
4529
5351
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -4624,21 +5446,30 @@ var PptPlugin = class {
4624
5446
  return {
4625
5447
  destroy: cleanup,
4626
5448
  zoomIn: () => {
4627
- scale += 0.1;
4628
- slideCard.style.transform = `scale(${scale})`;
5449
+ scale += 0.15;
5450
+ applyTransform();
4629
5451
  },
4630
5452
  zoomOut: () => {
4631
- scale = Math.max(0.3, scale - 0.1);
4632
- slideCard.style.transform = `scale(${scale})`;
5453
+ scale = Math.max(0.2, scale - 0.15);
5454
+ applyTransform();
4633
5455
  },
4634
5456
  getZoom: () => scale,
4635
5457
  setZoom: (level) => {
4636
5458
  scale = level;
4637
- slideCard.style.transform = `scale(${scale})`;
5459
+ applyTransform();
4638
5460
  },
4639
5461
  fitToPage: () => {
4640
- scale = 1;
4641
- slideCard.style.transform = "scale(1)";
5462
+ scale = calculateFitScale();
5463
+ rotation = 0;
5464
+ applyTransform();
5465
+ },
5466
+ rotateCW: () => {
5467
+ rotation = (rotation + 90) % 360;
5468
+ applyTransform();
5469
+ },
5470
+ rotateCCW: () => {
5471
+ rotation = (rotation - 90 + 360) % 360;
5472
+ applyTransform();
4642
5473
  },
4643
5474
  goToPage: (page) => {
4644
5475
  if (page >= 1 && page <= totalSlides) {