@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/angular.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Component, ChangeDetectionStrategy, Input, Output, ViewChild, EventEmitter as EventEmitter$1 } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import DOMPurify2 from 'dompurify';
4
+ import * as pdfjsLib from 'pdfjs-dist';
4
5
  import * as docx from 'docx-preview';
5
6
  import { unzipSync, strFromU8, unzip } from 'fflate';
6
7
  import * as XLSX from 'xlsx';
@@ -527,11 +528,21 @@ var ToolbarController = class {
527
528
  el;
528
529
  toolbarEl;
529
530
  actions = [];
531
+ pageInputEl = null;
532
+ pageLabelEl = null;
530
533
  constructor(container) {
531
534
  this.el = container;
532
535
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
533
536
  this.el.appendChild(this.toolbarEl);
534
537
  }
538
+ setPage(page, max) {
539
+ if (this.pageInputEl) {
540
+ this.pageInputEl.value = page.toString();
541
+ }
542
+ if (max !== void 0 && this.pageLabelEl) {
543
+ this.pageLabelEl.textContent = ` / ${max}`;
544
+ }
545
+ }
535
546
  update(actions) {
536
547
  this.actions = actions;
537
548
  this.render();
@@ -574,6 +585,7 @@ var ToolbarController = class {
574
585
  if (action.type === "separator") {
575
586
  groupEl.appendChild(createElement("div", { className: "fp-toolbar-separator" }));
576
587
  } else if (action.type === "page-nav") {
588
+ const max = action.max ?? 1;
577
589
  const prevBtn = this.createButton(
578
590
  "prev",
579
591
  ICON_PAGE_PREV,
@@ -592,7 +604,6 @@ var ToolbarController = class {
592
604
  "Next Page",
593
605
  () => {
594
606
  const cur = parseInt(input.value, 10) || 1;
595
- const max = action.max ?? 1;
596
607
  if (cur < max) {
597
608
  input.value = (cur + 1).toString();
598
609
  action.execute("next", cur + 1);
@@ -604,15 +615,18 @@ var ToolbarController = class {
604
615
  type: "number",
605
616
  value: (action.value ?? 1).toString(),
606
617
  min: "1",
607
- max: (action.max ?? 1).toString()
618
+ max: max.toString()
608
619
  });
609
620
  input.addEventListener("change", () => {
610
- const val = parseInt(input.value, 10);
611
- if (!isNaN(val)) {
612
- action.execute("go", val);
613
- }
621
+ let val = parseInt(input.value, 10);
622
+ if (isNaN(val)) val = 1;
623
+ val = Math.max(1, Math.min(max, val));
624
+ input.value = val.toString();
625
+ action.execute("go", val);
614
626
  });
615
- const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
627
+ const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
628
+ this.pageInputEl = input;
629
+ this.pageLabelEl = label;
616
630
  groupEl.appendChild(prevBtn);
617
631
  groupEl.appendChild(input);
618
632
  groupEl.appendChild(label);
@@ -739,6 +753,8 @@ var FilePreviewViewer = class {
739
753
  keyHandler = null;
740
754
  currentContainer = null;
741
755
  currentOptions = {};
756
+ resizeObserver = null;
757
+ fullscreenHandler = null;
742
758
  /**
743
759
  * Register a preview plugin.
744
760
  */
@@ -790,7 +806,13 @@ var FilePreviewViewer = class {
790
806
  buffer,
791
807
  options,
792
808
  signal,
793
- emit: (event, payload) => this.eventEmitter.emit(event, payload)
809
+ emit: (event, payload) => {
810
+ if (event === "page-change" && payload && typeof payload.page === "number") {
811
+ const total = payload.total ?? payload.totalPages;
812
+ this.toolbar?.setPage(payload.page, total);
813
+ }
814
+ this.eventEmitter.emit(event, payload);
815
+ }
794
816
  });
795
817
  this.activeInstance = instance;
796
818
  this.hideLoading();
@@ -805,12 +827,31 @@ var FilePreviewViewer = class {
805
827
  label: "Toggle Fullscreen",
806
828
  type: "button",
807
829
  group: "view",
808
- execute: () => {
809
- if (!document.fullscreenElement) {
810
- this.wrapperEl?.requestFullscreen?.();
811
- } else {
812
- document.exitFullscreen?.();
830
+ execute: async () => {
831
+ try {
832
+ const isNativeFs = !!document.fullscreenElement;
833
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
834
+ if (!isNativeFs && !isCssFs) {
835
+ if (this.wrapperEl?.requestFullscreen) {
836
+ await this.wrapperEl.requestFullscreen().catch(() => {
837
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
838
+ });
839
+ } else {
840
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
841
+ }
842
+ } else {
843
+ if (document.fullscreenElement) {
844
+ await document.exitFullscreen().catch(() => {
845
+ });
846
+ }
847
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
848
+ }
849
+ } catch {
850
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
813
851
  }
852
+ setTimeout(() => {
853
+ this.activeInstance?.fitToPage?.();
854
+ }, 120);
814
855
  }
815
856
  });
816
857
  }
@@ -824,6 +865,7 @@ var FilePreviewViewer = class {
824
865
  if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
825
866
  this.thumbnailPanel.update(thumbnails, (index) => {
826
867
  instance.goToPage?.(index + 1);
868
+ this.toolbar?.setPage(index + 1);
827
869
  });
828
870
  if (options.showThumbnails) {
829
871
  this.thumbnailPanel.show();
@@ -859,6 +901,15 @@ var FilePreviewViewer = class {
859
901
  window.removeEventListener("keydown", this.keyHandler);
860
902
  this.keyHandler = null;
861
903
  }
904
+ if (this.resizeObserver) {
905
+ this.resizeObserver.disconnect();
906
+ this.resizeObserver = null;
907
+ }
908
+ if (this.fullscreenHandler) {
909
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
910
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
911
+ this.fullscreenHandler = null;
912
+ }
862
913
  this.abort();
863
914
  this.destroyInstance();
864
915
  this.toolbar?.destroy();
@@ -938,6 +989,25 @@ var FilePreviewViewer = class {
938
989
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
939
990
  this.setupKeyboardShortcuts();
940
991
  this.setupDragAndDrop(container, options);
992
+ this.setupResizeAndFullscreenListeners();
993
+ }
994
+ setupResizeAndFullscreenListeners() {
995
+ if (this.fullscreenHandler) return;
996
+ this.fullscreenHandler = () => {
997
+ setTimeout(() => {
998
+ this.activeInstance?.fitToPage?.();
999
+ }, 100);
1000
+ };
1001
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
1002
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
1003
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1004
+ this.resizeObserver = new ResizeObserver(() => {
1005
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1006
+ this.activeInstance.fitToPage?.();
1007
+ }
1008
+ });
1009
+ this.resizeObserver.observe(this.contentEl);
1010
+ }
941
1011
  }
942
1012
  setupKeyboardShortcuts() {
943
1013
  if (this.keyHandler) return;
@@ -949,9 +1019,13 @@ var FilePreviewViewer = class {
949
1019
  if (e.key === "ArrowRight" || e.key === "PageDown") {
950
1020
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
951
1021
  this.activeInstance.goToPage?.(cur + 1);
1022
+ const nextCur = this.activeInstance.getCurrentPage?.() ?? cur + 1;
1023
+ this.toolbar?.setPage(nextCur);
952
1024
  } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
953
1025
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
954
1026
  this.activeInstance.goToPage?.(Math.max(1, cur - 1));
1027
+ const prevCur = this.activeInstance.getCurrentPage?.() ?? Math.max(1, cur - 1);
1028
+ this.toolbar?.setPage(prevCur);
955
1029
  } else if (e.key === "+" || e.key === "=") {
956
1030
  this.activeInstance.zoomIn?.();
957
1031
  } else if (e.key === "-" || e.key === "_") {
@@ -1217,30 +1291,62 @@ var CfbfReader = class {
1217
1291
  return result.subarray(0, targetSize);
1218
1292
  }
1219
1293
  };
1220
-
1221
- // ../plugins/pdf/dist/index.js
1294
+ if (typeof window !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
1295
+ if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
1296
+ pdfjsLib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1297
+ }
1298
+ }
1222
1299
  var PdfPlugin = class {
1223
1300
  id = "pdf";
1224
- name = "PDF Preview";
1301
+ name = "PDF Document Preview";
1225
1302
  extensions = [".pdf"];
1226
1303
  mimeTypes = ["application/pdf"];
1227
1304
  weight = 100;
1228
1305
  supports(file) {
1229
1306
  const ext = file.metadata.extension?.toLowerCase();
1230
1307
  const mime = file.metadata.mimeType?.toLowerCase();
1231
- return ext === ".pdf" || mime === "application/pdf";
1308
+ if (ext) return this.extensions.includes(ext);
1309
+ return this.mimeTypes.includes(mime || "");
1232
1310
  }
1233
1311
  getToolbarActions(instance) {
1312
+ const totalPages = instance.getPageCount?.() ?? 1;
1313
+ const curPage = instance.getCurrentPage?.() ?? 1;
1234
1314
  return [
1315
+ {
1316
+ id: "thumbnails",
1317
+ icon: "thumbnails",
1318
+ label: "Page Thumbnails",
1319
+ type: "button",
1320
+ group: "navigation",
1321
+ execute: () => instance.toggleThumbnails?.()
1322
+ },
1323
+ {
1324
+ id: "page-nav",
1325
+ icon: "",
1326
+ label: "Page Navigation",
1327
+ type: "page-nav",
1328
+ group: "navigation",
1329
+ value: curPage,
1330
+ max: totalPages,
1331
+ execute: (action, page) => {
1332
+ const cur = instance.getCurrentPage?.() ?? 1;
1333
+ const max = instance.getPageCount?.() ?? 1;
1334
+ if (action === "prev") {
1335
+ if (cur > 1) instance.goToPage?.(cur - 1);
1336
+ } else if (action === "next") {
1337
+ if (cur < max) instance.goToPage?.(cur + 1);
1338
+ } else if (typeof page === "number") {
1339
+ instance.goToPage?.(page);
1340
+ }
1341
+ }
1342
+ },
1235
1343
  {
1236
1344
  id: "zoom-out",
1237
1345
  icon: "zoom-out",
1238
1346
  label: "Zoom Out",
1239
1347
  type: "button",
1240
1348
  group: "zoom",
1241
- execute: () => {
1242
- instance.zoomOut?.();
1243
- }
1349
+ execute: () => instance.zoomOut?.()
1244
1350
  },
1245
1351
  {
1246
1352
  id: "zoom-in",
@@ -1248,9 +1354,7 @@ var PdfPlugin = class {
1248
1354
  label: "Zoom In",
1249
1355
  type: "button",
1250
1356
  group: "zoom",
1251
- execute: () => {
1252
- instance.zoomIn?.();
1253
- }
1357
+ execute: () => instance.zoomIn?.()
1254
1358
  },
1255
1359
  {
1256
1360
  id: "fit-page",
@@ -1258,39 +1362,23 @@ var PdfPlugin = class {
1258
1362
  label: "Fit to Page",
1259
1363
  type: "button",
1260
1364
  group: "zoom",
1261
- execute: () => {
1262
- instance.fitToPage?.();
1263
- }
1365
+ execute: () => instance.fitToPage?.()
1264
1366
  },
1265
1367
  {
1266
1368
  id: "rotate-cw",
1267
1369
  icon: "rotate-cw",
1268
- label: "Rotate",
1370
+ label: "Rotate Clockwise",
1269
1371
  type: "button",
1270
1372
  group: "view",
1271
- execute: () => {
1272
- instance.rotateCW?.();
1273
- }
1274
- },
1275
- {
1276
- id: "page-nav",
1277
- icon: "page-nav",
1278
- label: "Page Navigation",
1279
- type: "page-nav",
1280
- group: "navigation",
1281
- execute: (page) => {
1282
- if (typeof page === "number") instance.goToPage?.(page);
1283
- }
1373
+ execute: () => instance.rotateCW?.()
1284
1374
  },
1285
1375
  {
1286
1376
  id: "download",
1287
1377
  icon: "download",
1288
- label: "Download",
1378
+ label: "Download PDF",
1289
1379
  type: "button",
1290
1380
  group: "actions",
1291
- execute: () => {
1292
- instance.download?.();
1293
- }
1381
+ execute: () => instance.download?.()
1294
1382
  },
1295
1383
  {
1296
1384
  id: "print",
@@ -1298,99 +1386,224 @@ var PdfPlugin = class {
1298
1386
  label: "Print",
1299
1387
  type: "button",
1300
1388
  group: "actions",
1301
- execute: () => {
1302
- instance.print?.();
1303
- }
1389
+ execute: () => instance.print?.()
1304
1390
  }
1305
1391
  ];
1306
1392
  }
1307
1393
  async render(ctx) {
1308
- const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1309
- const url = URL.createObjectURL(blob);
1310
- const wrapper = document.createElement("div");
1311
- wrapper.style.width = "100%";
1312
- wrapper.style.height = "100%";
1313
- wrapper.style.overflow = "hidden";
1314
- wrapper.style.display = "flex";
1315
- wrapper.style.justifyContent = "center";
1316
- wrapper.style.alignItems = "center";
1317
- const iframe = document.createElement("iframe");
1318
- iframe.src = url;
1319
- iframe.style.width = "100%";
1320
- iframe.style.height = "100%";
1321
- iframe.style.border = "none";
1322
- wrapper.appendChild(iframe);
1323
- ctx.container.appendChild(wrapper);
1394
+ const container = document.createElement("div");
1395
+ container.className = "fp-pdf-container";
1396
+ container.style.width = "100%";
1397
+ container.style.height = "100%";
1398
+ container.style.overflow = "auto";
1399
+ container.style.display = "flex";
1400
+ container.style.flexDirection = "column";
1401
+ container.style.alignItems = "center";
1402
+ container.style.justifyContent = "flex-start";
1403
+ container.style.padding = "20px 16px";
1404
+ container.style.backgroundColor = "#0f172a";
1405
+ container.style.boxSizing = "border-box";
1406
+ container.style.position = "relative";
1407
+ const pageCard = document.createElement("div");
1408
+ pageCard.className = "fp-pdf-page-card";
1409
+ pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
1410
+ pageCard.style.backgroundColor = "#ffffff";
1411
+ pageCard.style.borderRadius = "4px";
1412
+ pageCard.style.overflow = "hidden";
1413
+ pageCard.style.lineHeight = "0";
1414
+ pageCard.style.transition = "transform 0.15s ease";
1415
+ pageCard.style.position = "relative";
1416
+ pageCard.style.flexShrink = "0";
1417
+ let canvas = document.createElement("canvas");
1418
+ pageCard.appendChild(canvas);
1419
+ container.appendChild(pageCard);
1420
+ const indicator = document.createElement("div");
1421
+ indicator.className = "fp-pdf-page-indicator";
1422
+ indicator.style.position = "sticky";
1423
+ indicator.style.bottom = "16px";
1424
+ indicator.style.marginTop = "16px";
1425
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1426
+ indicator.style.backdropFilter = "blur(8px)";
1427
+ indicator.style.color = "#f8fafc";
1428
+ indicator.style.fontSize = "12px";
1429
+ indicator.style.fontWeight = "600";
1430
+ indicator.style.padding = "5px 14px";
1431
+ indicator.style.borderRadius = "20px";
1432
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1433
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1434
+ indicator.style.zIndex = "10";
1435
+ indicator.style.userSelect = "none";
1436
+ indicator.style.pointerEvents = "none";
1437
+ container.appendChild(indicator);
1438
+ ctx.container.appendChild(container);
1439
+ const loadingTask = pdfjsLib.getDocument({
1440
+ data: new Uint8Array(ctx.buffer),
1441
+ cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/cmaps/`,
1442
+ cMapPacked: true,
1443
+ standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/standard_fonts/`
1444
+ });
1445
+ const pdfDoc = await loadingTask.promise;
1446
+ const totalPages = Math.max(1, pdfDoc.numPages);
1324
1447
  let currentPage = 1;
1325
- let currentZoom = 1;
1448
+ let zoomScale = 1;
1326
1449
  let rotation = 0;
1450
+ let currentRenderTask = null;
1451
+ const renderPage = async (pageNum) => {
1452
+ if (currentRenderTask) {
1453
+ try {
1454
+ currentRenderTask.cancel();
1455
+ } catch {
1456
+ }
1457
+ currentRenderTask = null;
1458
+ }
1459
+ const newCanvas = document.createElement("canvas");
1460
+ pageCard.replaceChild(newCanvas, canvas);
1461
+ canvas = newCanvas;
1462
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
1463
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1464
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
1465
+ const page = await pdfDoc.getPage(currentPage);
1466
+ const containerWidth = container.clientWidth || 900;
1467
+ const containerHeight = container.clientHeight || 700;
1468
+ const unscaledVp = page.getViewport({ scale: 1, rotation });
1469
+ const availWidth = Math.max(100, containerWidth - 48);
1470
+ const availHeight = Math.max(100, containerHeight - 88);
1471
+ const scaleW = availWidth / unscaledVp.width;
1472
+ const scaleH = availHeight / unscaledVp.height;
1473
+ const fitScale = Math.min(scaleW, scaleH);
1474
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1475
+ const pixelRatio = window.devicePixelRatio || 1;
1476
+ const viewport = page.getViewport({ scale: effectiveScale, rotation });
1477
+ canvas.width = Math.floor(viewport.width * pixelRatio);
1478
+ canvas.height = Math.floor(viewport.height * pixelRatio);
1479
+ canvas.style.width = `${Math.floor(viewport.width)}px`;
1480
+ canvas.style.height = `${Math.floor(viewport.height)}px`;
1481
+ const canvasCtx = canvas.getContext("2d");
1482
+ if (!canvasCtx) return;
1483
+ canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
1484
+ currentRenderTask = page.render({
1485
+ canvasContext: canvasCtx,
1486
+ viewport
1487
+ });
1488
+ try {
1489
+ await currentRenderTask.promise;
1490
+ } catch (err) {
1491
+ if (err?.name !== "RenderingCancelledException") {
1492
+ console.warn("[PdfPlugin] Page render warning:", err);
1493
+ }
1494
+ } finally {
1495
+ currentRenderTask = null;
1496
+ }
1497
+ };
1498
+ await renderPage(1);
1327
1499
  const cleanup = () => {
1328
- URL.revokeObjectURL(url);
1329
- wrapper.remove();
1500
+ if (currentRenderTask) {
1501
+ try {
1502
+ currentRenderTask.cancel();
1503
+ } catch {
1504
+ }
1505
+ }
1506
+ try {
1507
+ pdfDoc.destroy();
1508
+ } catch {
1509
+ }
1510
+ container.remove();
1330
1511
  ctx.container.innerHTML = "";
1331
1512
  };
1332
1513
  ctx.signal.addEventListener("abort", cleanup);
1333
- return {
1514
+ const instance = {
1334
1515
  destroy: cleanup,
1335
1516
  zoomIn: () => {
1336
- currentZoom += 0.1;
1337
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1517
+ zoomScale = Math.min(3.5, zoomScale + 0.2);
1518
+ renderPage(currentPage);
1338
1519
  },
1339
1520
  zoomOut: () => {
1340
- currentZoom = Math.max(0.2, currentZoom - 0.1);
1341
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1521
+ zoomScale = Math.max(0.3, zoomScale - 0.2);
1522
+ renderPage(currentPage);
1342
1523
  },
1343
- getZoom: () => currentZoom,
1524
+ getZoom: () => zoomScale,
1344
1525
  setZoom: (level) => {
1345
- currentZoom = level;
1346
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1526
+ zoomScale = Math.max(0.3, Math.min(3.5, level));
1527
+ renderPage(currentPage);
1347
1528
  },
1348
1529
  fitToPage: () => {
1349
- currentZoom = 1;
1350
- iframe.style.transform = `scale(1) rotate(${rotation}deg)`;
1530
+ zoomScale = 1;
1531
+ rotation = 0;
1532
+ renderPage(currentPage);
1351
1533
  },
1352
1534
  rotateCW: () => {
1353
1535
  rotation = (rotation + 90) % 360;
1354
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1536
+ renderPage(currentPage);
1355
1537
  },
1356
1538
  rotateCCW: () => {
1357
1539
  rotation = (rotation - 90 + 360) % 360;
1358
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1540
+ renderPage(currentPage);
1359
1541
  },
1360
1542
  getRotation: () => rotation,
1543
+ getPageCount: () => totalPages,
1544
+ getCurrentPage: () => currentPage,
1361
1545
  goToPage: (page) => {
1362
- currentPage = page;
1363
- iframe.src = `${url}#page=${page}`;
1546
+ renderPage(page);
1364
1547
  },
1365
- getCurrentPage: () => currentPage,
1366
1548
  download: () => {
1549
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1550
+ const url = URL.createObjectURL(blob);
1367
1551
  const a = document.createElement("a");
1368
1552
  a.href = url;
1369
1553
  a.download = ctx.metadata.name || "document.pdf";
1370
1554
  a.click();
1555
+ URL.revokeObjectURL(url);
1371
1556
  },
1372
1557
  print: () => {
1373
- iframe.contentWindow?.print();
1558
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1559
+ const url = URL.createObjectURL(blob);
1560
+ const hiddenIframe = document.createElement("iframe");
1561
+ hiddenIframe.style.position = "fixed";
1562
+ hiddenIframe.style.right = "0";
1563
+ hiddenIframe.style.bottom = "0";
1564
+ hiddenIframe.style.width = "0";
1565
+ hiddenIframe.style.height = "0";
1566
+ hiddenIframe.style.border = "0";
1567
+ document.body.appendChild(hiddenIframe);
1568
+ hiddenIframe.src = url;
1569
+ hiddenIframe.onload = () => {
1570
+ setTimeout(() => {
1571
+ hiddenIframe.contentWindow?.print();
1572
+ setTimeout(() => {
1573
+ hiddenIframe.remove();
1574
+ URL.revokeObjectURL(url);
1575
+ }, 1e3);
1576
+ }, 300);
1577
+ };
1374
1578
  },
1375
1579
  getThumbnails: async () => {
1376
- return [
1377
- {
1378
- index: 1,
1379
- label: "Page 1",
1380
- render: async (canvas) => {
1381
- const context = canvas.getContext("2d");
1382
- if (context) {
1383
- context.fillStyle = "#fff";
1384
- context.fillRect(0, 0, canvas.width, canvas.height);
1385
- context.fillStyle = "#333";
1386
- context.font = "12px sans-serif";
1387
- context.fillText("PDF Preview", 10, 20);
1580
+ const thumbnails = [];
1581
+ const count = Math.min(totalPages, 50);
1582
+ for (let i = 1; i <= count; i++) {
1583
+ thumbnails.push({
1584
+ index: i,
1585
+ label: `Page ${i}`,
1586
+ render: async (thumbCanvas) => {
1587
+ try {
1588
+ const p = await pdfDoc.getPage(i);
1589
+ const baseVp = p.getViewport({ scale: 1 });
1590
+ const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1591
+ const thumbVp = p.getViewport({ scale: thumbScale });
1592
+ thumbCanvas.height = Math.floor(thumbVp.height);
1593
+ const tCtx = thumbCanvas.getContext("2d");
1594
+ if (tCtx) {
1595
+ await p.render({ canvasContext: tCtx, viewport: thumbVp }).promise;
1596
+ }
1597
+ } catch (e) {
1598
+ console.warn(`[PdfPlugin] Error generating thumbnail for page ${i}:`, e);
1388
1599
  }
1389
1600
  }
1390
- }
1391
- ];
1601
+ });
1602
+ }
1603
+ return thumbnails;
1392
1604
  }
1393
1605
  };
1606
+ return instance;
1394
1607
  }
1395
1608
  };
1396
1609
  function pdfPlugin() {
@@ -1717,7 +1930,31 @@ var DocxPlugin = class {
1717
1930
  return this.mimeTypes.includes(mime || "");
1718
1931
  }
1719
1932
  getToolbarActions(instance) {
1720
- return [
1933
+ const totalPages = instance.getPageCount?.() ?? 1;
1934
+ const actions = [];
1935
+ if (totalPages > 1) {
1936
+ actions.push({
1937
+ id: "page-nav",
1938
+ icon: "",
1939
+ label: "Page Navigation",
1940
+ type: "page-nav",
1941
+ group: "navigation",
1942
+ value: instance.getCurrentPage?.() ?? 1,
1943
+ max: totalPages,
1944
+ execute: (action, page) => {
1945
+ const cur = instance.getCurrentPage?.() ?? 1;
1946
+ const max = instance.getPageCount?.() ?? 1;
1947
+ if (action === "prev") {
1948
+ if (cur > 1) instance.goToPage?.(cur - 1);
1949
+ } else if (action === "next") {
1950
+ if (cur < max) instance.goToPage?.(cur + 1);
1951
+ } else if (typeof page === "number") {
1952
+ instance.goToPage?.(page);
1953
+ }
1954
+ }
1955
+ });
1956
+ }
1957
+ actions.push(
1721
1958
  {
1722
1959
  id: "zoom-out",
1723
1960
  icon: "zoom-out",
@@ -1742,6 +1979,14 @@ var DocxPlugin = class {
1742
1979
  group: "zoom",
1743
1980
  execute: () => instance.fitToPage?.()
1744
1981
  },
1982
+ {
1983
+ id: "rotate-cw",
1984
+ icon: "rotate-cw",
1985
+ label: "Rotate",
1986
+ type: "button",
1987
+ group: "view",
1988
+ execute: () => instance.rotateCW?.()
1989
+ },
1745
1990
  {
1746
1991
  id: "download",
1747
1992
  icon: "download",
@@ -1758,7 +2003,8 @@ var DocxPlugin = class {
1758
2003
  group: "actions",
1759
2004
  execute: () => instance.print?.()
1760
2005
  }
1761
- ];
2006
+ );
2007
+ return actions;
1762
2008
  }
1763
2009
  async render(ctx) {
1764
2010
  const wrapper = document.createElement("div");
@@ -1834,7 +2080,71 @@ var DocxPlugin = class {
1834
2080
  }
1835
2081
  }
1836
2082
  }
2083
+ const sections = wrapper.querySelectorAll("section.docx");
2084
+ const cards = wrapper.querySelectorAll(".fp-docx-page-card");
2085
+ const pageElements = sections.length > 0 ? sections : cards;
2086
+ const totalPages = Math.max(1, pageElements.length);
2087
+ let currentPage = 1;
2088
+ let indicator = null;
2089
+ if (totalPages > 1) {
2090
+ indicator = document.createElement("div");
2091
+ indicator.className = "fp-docx-page-indicator";
2092
+ indicator.style.position = "sticky";
2093
+ indicator.style.bottom = "16px";
2094
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2095
+ indicator.style.backdropFilter = "blur(8px)";
2096
+ indicator.style.color = "#f8fafc";
2097
+ indicator.style.fontSize = "12px";
2098
+ indicator.style.fontWeight = "600";
2099
+ indicator.style.padding = "5px 14px";
2100
+ indicator.style.borderRadius = "20px";
2101
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2102
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2103
+ indicator.style.zIndex = "10";
2104
+ indicator.style.userSelect = "none";
2105
+ indicator.style.pointerEvents = "none";
2106
+ indicator.style.textAlign = "center";
2107
+ indicator.style.width = "fit-content";
2108
+ indicator.style.margin = "16px auto 0";
2109
+ ctx.container.appendChild(indicator);
2110
+ }
2111
+ const showPage = (pageNum) => {
2112
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
2113
+ if (pageElements.length > 1) {
2114
+ pageElements.forEach((sec, idx) => {
2115
+ sec.style.display = idx + 1 === currentPage ? "block" : "none";
2116
+ });
2117
+ }
2118
+ if (indicator) {
2119
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2120
+ }
2121
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
2122
+ };
2123
+ if (totalPages > 1) {
2124
+ showPage(1);
2125
+ }
2126
+ scale = 1;
2127
+ let rotation = 0;
2128
+ const calculateFitScale = () => {
2129
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2130
+ const elW = activeEl.offsetWidth || 816;
2131
+ const elH = activeEl.offsetHeight || 1056;
2132
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2133
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2134
+ const sW = availW / elW;
2135
+ const sH = availH / elH;
2136
+ return Math.min(1.1, Math.min(sW, sH));
2137
+ };
2138
+ const applyTransform = () => {
2139
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2140
+ wrapper.style.transformOrigin = "top center";
2141
+ };
2142
+ setTimeout(() => {
2143
+ scale = calculateFitScale();
2144
+ applyTransform();
2145
+ }, 60);
1837
2146
  const cleanup = () => {
2147
+ indicator?.remove();
1838
2148
  for (const url of createdBlobUrls) {
1839
2149
  URL.revokeObjectURL(url);
1840
2150
  }
@@ -1845,22 +2155,36 @@ var DocxPlugin = class {
1845
2155
  ctx.signal.addEventListener("abort", cleanup);
1846
2156
  return {
1847
2157
  destroy: cleanup,
2158
+ getPageCount: () => totalPages,
2159
+ getCurrentPage: () => currentPage,
2160
+ goToPage: (page) => {
2161
+ showPage(page);
2162
+ },
1848
2163
  zoomIn: () => {
1849
- scale += 0.1;
1850
- wrapper.style.transform = `scale(${scale})`;
2164
+ scale += 0.15;
2165
+ applyTransform();
1851
2166
  },
1852
2167
  zoomOut: () => {
1853
- scale = Math.max(0.2, scale - 0.1);
1854
- wrapper.style.transform = `scale(${scale})`;
2168
+ scale = Math.max(0.2, scale - 0.15);
2169
+ applyTransform();
1855
2170
  },
1856
2171
  getZoom: () => scale,
1857
2172
  setZoom: (level) => {
1858
2173
  scale = level;
1859
- wrapper.style.transform = `scale(${scale})`;
2174
+ applyTransform();
1860
2175
  },
1861
2176
  fitToPage: () => {
1862
- scale = 1;
1863
- wrapper.style.transform = "scale(1)";
2177
+ scale = calculateFitScale();
2178
+ rotation = 0;
2179
+ applyTransform();
2180
+ },
2181
+ rotateCW: () => {
2182
+ rotation = (rotation + 90) % 360;
2183
+ applyTransform();
2184
+ },
2185
+ rotateCCW: () => {
2186
+ rotation = (rotation - 90 + 360) % 360;
2187
+ applyTransform();
1864
2188
  },
1865
2189
  download: () => {
1866
2190
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2109,7 +2433,31 @@ var ExcelPlugin = class {
2109
2433
  return this.mimeTypes.includes(mime || "");
2110
2434
  }
2111
2435
  getToolbarActions(instance) {
2112
- return [
2436
+ const totalSheets = instance.getPageCount?.() ?? 1;
2437
+ const actions = [];
2438
+ if (totalSheets > 1) {
2439
+ actions.push({
2440
+ id: "page-nav",
2441
+ icon: "",
2442
+ label: "Sheet Navigation",
2443
+ type: "page-nav",
2444
+ group: "navigation",
2445
+ value: instance.getCurrentPage?.() ?? 1,
2446
+ max: totalSheets,
2447
+ execute: (action, sheet) => {
2448
+ const cur = instance.getCurrentPage?.() ?? 1;
2449
+ const max = instance.getPageCount?.() ?? 1;
2450
+ if (action === "prev") {
2451
+ if (cur > 1) instance.goToPage?.(cur - 1);
2452
+ } else if (action === "next") {
2453
+ if (cur < max) instance.goToPage?.(cur + 1);
2454
+ } else if (typeof sheet === "number") {
2455
+ instance.goToPage?.(sheet);
2456
+ }
2457
+ }
2458
+ });
2459
+ }
2460
+ actions.push(
2113
2461
  {
2114
2462
  id: "zoom-out",
2115
2463
  icon: "zoom-out",
@@ -2131,13 +2479,23 @@ var ExcelPlugin = class {
2131
2479
  }
2132
2480
  },
2133
2481
  {
2134
- id: "page-nav",
2135
- icon: "page-nav",
2136
- label: "Sheet Navigation",
2137
- type: "page-nav",
2138
- group: "navigation",
2139
- execute: (sheet) => {
2140
- if (typeof sheet === "number") instance.goToPage?.(sheet);
2482
+ id: "fit-page",
2483
+ icon: "fit-page",
2484
+ label: "Fit to View",
2485
+ type: "button",
2486
+ group: "zoom",
2487
+ execute: () => {
2488
+ instance.fitToPage?.();
2489
+ }
2490
+ },
2491
+ {
2492
+ id: "rotate-cw",
2493
+ icon: "rotate-cw",
2494
+ label: "Rotate",
2495
+ type: "button",
2496
+ group: "view",
2497
+ execute: () => {
2498
+ instance.rotateCW?.();
2141
2499
  }
2142
2500
  },
2143
2501
  {
@@ -2160,7 +2518,8 @@ var ExcelPlugin = class {
2160
2518
  instance.print?.();
2161
2519
  }
2162
2520
  }
2163
- ];
2521
+ );
2522
+ return actions;
2164
2523
  }
2165
2524
  async render(ctx) {
2166
2525
  const container = document.createElement("div");
@@ -2189,9 +2548,23 @@ var ExcelPlugin = class {
2189
2548
  container.appendChild(tabsArea);
2190
2549
  ctx.container.appendChild(container);
2191
2550
  let scale = 1;
2551
+ let rotation = 0;
2192
2552
  let currentSheetIndex = 1;
2193
2553
  let sheetNames = [];
2194
2554
  let wb = null;
2555
+ const applyTransform = () => {
2556
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2557
+ contentArea.style.transformOrigin = "top left";
2558
+ };
2559
+ const calculateFitScale = () => {
2560
+ const table = contentArea.querySelector("table");
2561
+ if (!table) return 1;
2562
+ const availW = Math.max(200, container.clientWidth - 48);
2563
+ const availH = Math.max(200, container.clientHeight - 80);
2564
+ const tW = table.offsetWidth || 800;
2565
+ const tH = table.offsetHeight || 600;
2566
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2567
+ };
2195
2568
  try {
2196
2569
  wb = XLSX.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2197
2570
  sheetNames = wb.SheetNames || [];
@@ -2244,6 +2617,7 @@ var ExcelPlugin = class {
2244
2617
  b.style.fontWeight = "normal";
2245
2618
  }
2246
2619
  });
2620
+ ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
2247
2621
  };
2248
2622
  if (sheetNames.length > 0) {
2249
2623
  sheetNames.forEach((name, idx) => {
@@ -2278,17 +2652,30 @@ var ExcelPlugin = class {
2278
2652
  return {
2279
2653
  destroy: cleanup,
2280
2654
  zoomIn: () => {
2281
- scale += 0.1;
2282
- contentArea.style.transform = `scale(${scale})`;
2655
+ scale += 0.15;
2656
+ applyTransform();
2283
2657
  },
2284
2658
  zoomOut: () => {
2285
- scale = Math.max(0.2, scale - 0.1);
2286
- contentArea.style.transform = `scale(${scale})`;
2659
+ scale = Math.max(0.2, scale - 0.15);
2660
+ applyTransform();
2287
2661
  },
2288
2662
  getZoom: () => scale,
2289
2663
  setZoom: (level) => {
2290
2664
  scale = level;
2291
- contentArea.style.transform = `scale(${scale})`;
2665
+ applyTransform();
2666
+ },
2667
+ fitToPage: () => {
2668
+ scale = calculateFitScale();
2669
+ rotation = 0;
2670
+ applyTransform();
2671
+ },
2672
+ rotateCW: () => {
2673
+ rotation = (rotation + 90) % 360;
2674
+ applyTransform();
2675
+ },
2676
+ rotateCCW: () => {
2677
+ rotation = (rotation - 90 + 360) % 360;
2678
+ applyTransform();
2292
2679
  },
2293
2680
  goToPage: (page) => {
2294
2681
  if (page > 0 && page <= sheetNames.length) {
@@ -2493,7 +2880,31 @@ var CodePlugin = class {
2493
2880
  return false;
2494
2881
  }
2495
2882
  getToolbarActions(instance) {
2496
- return [
2883
+ const totalPages = instance.getPageCount?.() ?? 1;
2884
+ const actions = [];
2885
+ if (totalPages > 1) {
2886
+ actions.push({
2887
+ id: "page-nav",
2888
+ icon: "",
2889
+ label: "Page Navigation",
2890
+ type: "page-nav",
2891
+ group: "navigation",
2892
+ value: instance.getCurrentPage?.() ?? 1,
2893
+ max: totalPages,
2894
+ execute: (action, page) => {
2895
+ const cur = instance.getCurrentPage?.() ?? 1;
2896
+ const max = instance.getPageCount?.() ?? 1;
2897
+ if (action === "prev") {
2898
+ if (cur > 1) instance.goToPage?.(cur - 1);
2899
+ } else if (action === "next") {
2900
+ if (cur < max) instance.goToPage?.(cur + 1);
2901
+ } else if (typeof page === "number") {
2902
+ instance.goToPage?.(page);
2903
+ }
2904
+ }
2905
+ });
2906
+ }
2907
+ actions.push(
2497
2908
  {
2498
2909
  id: "zoom-out",
2499
2910
  icon: "zoom-out",
@@ -2514,6 +2925,26 @@ var CodePlugin = class {
2514
2925
  instance.zoomIn?.();
2515
2926
  }
2516
2927
  },
2928
+ {
2929
+ id: "fit-page",
2930
+ icon: "fit-page",
2931
+ label: "Fit to View",
2932
+ type: "button",
2933
+ group: "zoom",
2934
+ execute: () => {
2935
+ instance.fitToPage?.();
2936
+ }
2937
+ },
2938
+ {
2939
+ id: "rotate-cw",
2940
+ icon: "rotate-cw",
2941
+ label: "Rotate",
2942
+ type: "button",
2943
+ group: "view",
2944
+ execute: () => {
2945
+ instance.rotateCW?.();
2946
+ }
2947
+ },
2517
2948
  {
2518
2949
  id: "copy",
2519
2950
  icon: "copy",
@@ -2544,11 +2975,16 @@ var CodePlugin = class {
2544
2975
  instance.print?.();
2545
2976
  }
2546
2977
  }
2547
- ];
2978
+ );
2979
+ return actions;
2548
2980
  }
2549
2981
  async render(ctx) {
2550
2982
  const decoder = new TextDecoder("utf-8");
2551
- const text = decoder.decode(ctx.buffer);
2983
+ const fullText = decoder.decode(ctx.buffer);
2984
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2985
+ const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
2986
+ const totalPages = Math.max(1, rawPages.length);
2987
+ let currentPage = 1;
2552
2988
  const container = document.createElement("div");
2553
2989
  container.style.width = "100%";
2554
2990
  container.style.height = "100%";
@@ -2558,6 +2994,7 @@ var CodePlugin = class {
2558
2994
  container.style.padding = "16px";
2559
2995
  container.style.boxSizing = "border-box";
2560
2996
  let fontSize = 13;
2997
+ let rotation = 0;
2561
2998
  const pre = document.createElement("pre");
2562
2999
  pre.style.margin = "0";
2563
3000
  pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
@@ -2567,25 +3004,66 @@ var CodePlugin = class {
2567
3004
  pre.style.wordBreak = "break-all";
2568
3005
  const code = document.createElement("code");
2569
3006
  const ext = (ctx.metadata.extension || "").replace(".", "");
2570
- try {
2571
- if (ext && hljs.getLanguage(ext)) {
2572
- code.innerHTML = hljs.highlight(text, { language: ext }).value;
2573
- } else {
2574
- code.innerHTML = hljs.highlightAuto(text).value;
3007
+ const renderCodePage = (text) => {
3008
+ try {
3009
+ if (ext && hljs.getLanguage(ext)) {
3010
+ code.innerHTML = hljs.highlight(text, { language: ext }).value;
3011
+ } else {
3012
+ code.innerHTML = hljs.highlightAuto(text).value;
3013
+ }
3014
+ } catch {
3015
+ code.textContent = text;
2575
3016
  }
2576
- } catch {
2577
- code.textContent = text;
2578
- }
3017
+ };
3018
+ renderCodePage(rawPages[0] || fullText);
2579
3019
  pre.appendChild(code);
2580
3020
  container.appendChild(pre);
3021
+ let indicator = null;
3022
+ if (totalPages > 1) {
3023
+ indicator = document.createElement("div");
3024
+ indicator.className = "fp-code-page-indicator";
3025
+ indicator.style.position = "sticky";
3026
+ indicator.style.bottom = "16px";
3027
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3028
+ indicator.style.backdropFilter = "blur(8px)";
3029
+ indicator.style.color = "#f8fafc";
3030
+ indicator.style.fontSize = "12px";
3031
+ indicator.style.fontWeight = "600";
3032
+ indicator.style.padding = "5px 14px";
3033
+ indicator.style.borderRadius = "20px";
3034
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3035
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3036
+ indicator.style.zIndex = "10";
3037
+ indicator.style.userSelect = "none";
3038
+ indicator.style.pointerEvents = "none";
3039
+ indicator.style.textAlign = "center";
3040
+ indicator.style.width = "fit-content";
3041
+ indicator.style.margin = "16px auto 0";
3042
+ container.appendChild(indicator);
3043
+ }
3044
+ const showPage = (pageNum) => {
3045
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3046
+ renderCodePage(rawPages[currentPage - 1] || fullText);
3047
+ if (indicator) {
3048
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3049
+ }
3050
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3051
+ };
3052
+ if (totalPages > 1) {
3053
+ showPage(1);
3054
+ }
2581
3055
  ctx.container.appendChild(container);
2582
3056
  const cleanup = () => {
3057
+ indicator?.remove();
2583
3058
  container.remove();
2584
3059
  ctx.container.innerHTML = "";
2585
3060
  };
2586
3061
  ctx.signal.addEventListener("abort", cleanup);
2587
3062
  return {
2588
3063
  destroy: cleanup,
3064
+ getPageCount: () => totalPages,
3065
+ getCurrentPage: () => currentPage,
3066
+ goToPage: (page) => showPage(page),
2589
3067
  zoomIn: () => {
2590
3068
  fontSize = Math.min(32, fontSize + 2);
2591
3069
  pre.style.fontSize = `${fontSize}px`;
@@ -2599,6 +3077,22 @@ var CodePlugin = class {
2599
3077
  fontSize = Math.round(13 * level);
2600
3078
  pre.style.fontSize = `${fontSize}px`;
2601
3079
  },
3080
+ fitToPage: () => {
3081
+ fontSize = 13;
3082
+ rotation = 0;
3083
+ pre.style.fontSize = "13px";
3084
+ pre.style.transform = "none";
3085
+ },
3086
+ rotateCW: () => {
3087
+ rotation = (rotation + 90) % 360;
3088
+ pre.style.transform = `rotate(${rotation}deg)`;
3089
+ pre.style.transformOrigin = "top left";
3090
+ },
3091
+ rotateCCW: () => {
3092
+ rotation = (rotation - 90 + 360) % 360;
3093
+ pre.style.transform = `rotate(${rotation}deg)`;
3094
+ pre.style.transformOrigin = "top left";
3095
+ },
2602
3096
  download: () => {
2603
3097
  const mimeType = ctx.metadata.mimeType || "text/plain";
2604
3098
  const blob = new Blob([ctx.buffer], { type: mimeType });
@@ -2613,7 +3107,7 @@ var CodePlugin = class {
2613
3107
  window.print();
2614
3108
  },
2615
3109
  copy: () => {
2616
- navigator.clipboard?.writeText(text);
3110
+ navigator.clipboard?.writeText(fullText);
2617
3111
  }
2618
3112
  };
2619
3113
  }
@@ -3110,6 +3604,14 @@ var PptxPlugin = class {
3110
3604
  group: "zoom",
3111
3605
  execute: () => instance.fitToPage?.()
3112
3606
  },
3607
+ {
3608
+ id: "rotate-cw",
3609
+ icon: "rotate-cw",
3610
+ label: "Rotate",
3611
+ type: "button",
3612
+ group: "view",
3613
+ execute: () => instance.rotateCW?.()
3614
+ },
3113
3615
  {
3114
3616
  id: "download",
3115
3617
  icon: "download",
@@ -3134,6 +3636,7 @@ var PptxPlugin = class {
3134
3636
  const slideCount = renderer.slidePaths?.length || 1;
3135
3637
  let currentSlide = 1;
3136
3638
  let scale = 1;
3639
+ let rotation = 0;
3137
3640
  const wrapper = document.createElement("div");
3138
3641
  wrapper.className = "fp-pptx-wrapper";
3139
3642
  wrapper.style.cssText = `
@@ -3156,6 +3659,8 @@ var PptxPlugin = class {
3156
3659
  background: #ffffff;
3157
3660
  transform-origin: top center;
3158
3661
  transition: transform 0.2s ease;
3662
+ max-width: calc(100% - 32px);
3663
+ max-height: calc(100% - 48px);
3159
3664
  `;
3160
3665
  const canvas = document.createElement("canvas");
3161
3666
  slideContainer.appendChild(canvas);
@@ -3163,10 +3668,22 @@ var PptxPlugin = class {
3163
3668
  ctx.container.innerHTML = "";
3164
3669
  ctx.container.style.overflow = "auto";
3165
3670
  ctx.container.appendChild(wrapper);
3671
+ const calculateFitScale = () => {
3672
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3673
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3674
+ const cW = canvas.offsetWidth || 1280;
3675
+ const cH = canvas.offsetHeight || 720;
3676
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3677
+ };
3678
+ const applyTransform = () => {
3679
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3680
+ };
3166
3681
  const renderCurrentSlide = async () => {
3167
3682
  try {
3168
3683
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3169
3684
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3685
+ scale = calculateFitScale();
3686
+ applyTransform();
3170
3687
  } catch (err) {
3171
3688
  console.error("[PptxPlugin] Failed to render slide:", err);
3172
3689
  }
@@ -3189,21 +3706,30 @@ var PptxPlugin = class {
3189
3706
  getPageCount: () => slideCount,
3190
3707
  getCurrentPage: () => currentSlide,
3191
3708
  zoomIn: () => {
3192
- scale += 0.1;
3193
- slideContainer.style.transform = `scale(${scale})`;
3709
+ scale += 0.15;
3710
+ applyTransform();
3194
3711
  },
3195
3712
  zoomOut: () => {
3196
- scale = Math.max(0.2, scale - 0.1);
3197
- slideContainer.style.transform = `scale(${scale})`;
3713
+ scale = Math.max(0.2, scale - 0.15);
3714
+ applyTransform();
3198
3715
  },
3199
3716
  getZoom: () => scale,
3200
3717
  setZoom: (level) => {
3201
3718
  scale = level;
3202
- slideContainer.style.transform = `scale(${scale})`;
3719
+ applyTransform();
3203
3720
  },
3204
3721
  fitToPage: () => {
3205
- scale = 1;
3206
- slideContainer.style.transform = "scale(1)";
3722
+ scale = calculateFitScale();
3723
+ rotation = 0;
3724
+ applyTransform();
3725
+ },
3726
+ rotateCW: () => {
3727
+ rotation = (rotation + 90) % 360;
3728
+ applyTransform();
3729
+ },
3730
+ rotateCCW: () => {
3731
+ rotation = (rotation - 90 + 360) % 360;
3732
+ applyTransform();
3207
3733
  },
3208
3734
  getThumbnails: () => {
3209
3735
  const list = [];
@@ -3435,7 +3961,31 @@ var RtfPlugin = class {
3435
3961
  return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3436
3962
  }
3437
3963
  getToolbarActions(instance) {
3438
- return [
3964
+ const totalPages = instance.getPageCount?.() ?? 1;
3965
+ const actions = [];
3966
+ if (totalPages > 1) {
3967
+ actions.push({
3968
+ id: "page-nav",
3969
+ icon: "",
3970
+ label: "Page Navigation",
3971
+ type: "page-nav",
3972
+ group: "navigation",
3973
+ value: instance.getCurrentPage?.() ?? 1,
3974
+ max: totalPages,
3975
+ execute: (action, page) => {
3976
+ const cur = instance.getCurrentPage?.() ?? 1;
3977
+ const max = instance.getPageCount?.() ?? 1;
3978
+ if (action === "prev") {
3979
+ if (cur > 1) instance.goToPage?.(cur - 1);
3980
+ } else if (action === "next") {
3981
+ if (cur < max) instance.goToPage?.(cur + 1);
3982
+ } else if (typeof page === "number") {
3983
+ instance.goToPage?.(page);
3984
+ }
3985
+ }
3986
+ });
3987
+ }
3988
+ actions.push(
3439
3989
  {
3440
3990
  id: "zoom-out",
3441
3991
  icon: "zoom-out",
@@ -3460,6 +4010,14 @@ var RtfPlugin = class {
3460
4010
  group: "zoom",
3461
4011
  execute: () => instance.fitToPage?.()
3462
4012
  },
4013
+ {
4014
+ id: "rotate-cw",
4015
+ icon: "rotate-cw",
4016
+ label: "Rotate",
4017
+ type: "button",
4018
+ group: "view",
4019
+ execute: () => instance.rotateCW?.()
4020
+ },
3463
4021
  {
3464
4022
  id: "download",
3465
4023
  icon: "download",
@@ -3476,7 +4034,8 @@ var RtfPlugin = class {
3476
4034
  group: "actions",
3477
4035
  execute: () => instance.print?.()
3478
4036
  }
3479
- ];
4037
+ );
4038
+ return actions;
3480
4039
  }
3481
4040
  async render(ctx) {
3482
4041
  const wrapper = document.createElement("div");
@@ -3495,44 +4054,124 @@ var RtfPlugin = class {
3495
4054
  ctx.container.style.backgroundColor = "#f1f5f9";
3496
4055
  ctx.container.appendChild(wrapper);
3497
4056
  let scale = 1;
4057
+ let rotation = 0;
4058
+ let pageElements = [];
4059
+ const calculateFitScale = () => {
4060
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4061
+ const elW = activeEl.offsetWidth || 850;
4062
+ const elH = activeEl.offsetHeight || 1e3;
4063
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4064
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4065
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4066
+ };
4067
+ const applyTransform = () => {
4068
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4069
+ wrapper.style.transformOrigin = "top center";
4070
+ };
4071
+ setTimeout(() => {
4072
+ scale = calculateFitScale();
4073
+ applyTransform();
4074
+ }, 60);
3498
4075
  try {
3499
4076
  if (typeof RTFJS.loggingEnabled === "function") {
3500
4077
  RTFJS.loggingEnabled(false);
3501
4078
  }
3502
4079
  const doc = new RTFJS.Document(ctx.buffer, {});
3503
4080
  const htmlElements = await doc.render();
3504
- for (const el of htmlElements) {
4081
+ pageElements = htmlElements;
4082
+ for (let i = 0; i < htmlElements.length; i++) {
4083
+ const el = htmlElements[i];
4084
+ el.style.display = i === 0 ? "block" : "none";
3505
4085
  wrapper.appendChild(el);
3506
4086
  }
3507
4087
  } catch (err) {
3508
4088
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3509
4089
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3510
4090
  const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3511
- wrapper.innerHTML = `<pre style="white-space: pre-wrap; font-family: serif; color: #333;">${clean}</pre>`;
4091
+ const pre = document.createElement("pre");
4092
+ pre.style.whiteSpace = "pre-wrap";
4093
+ pre.style.fontFamily = "serif";
4094
+ pre.style.color = "#333";
4095
+ pre.textContent = clean;
4096
+ wrapper.appendChild(pre);
4097
+ pageElements = [pre];
4098
+ }
4099
+ const totalPages = Math.max(1, pageElements.length);
4100
+ let currentPage = 1;
4101
+ let indicator = null;
4102
+ if (totalPages > 1) {
4103
+ indicator = document.createElement("div");
4104
+ indicator.className = "fp-rtf-page-indicator";
4105
+ indicator.style.position = "sticky";
4106
+ indicator.style.bottom = "16px";
4107
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4108
+ indicator.style.backdropFilter = "blur(8px)";
4109
+ indicator.style.color = "#f8fafc";
4110
+ indicator.style.fontSize = "12px";
4111
+ indicator.style.fontWeight = "600";
4112
+ indicator.style.padding = "5px 14px";
4113
+ indicator.style.borderRadius = "20px";
4114
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4115
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4116
+ indicator.style.zIndex = "10";
4117
+ indicator.style.userSelect = "none";
4118
+ indicator.style.pointerEvents = "none";
4119
+ indicator.style.textAlign = "center";
4120
+ indicator.style.width = "fit-content";
4121
+ indicator.style.margin = "16px auto 0";
4122
+ ctx.container.appendChild(indicator);
4123
+ }
4124
+ const showPage = (pageNum) => {
4125
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4126
+ if (totalPages > 1) {
4127
+ pageElements.forEach((el, idx) => {
4128
+ el.style.display = idx + 1 === currentPage ? "block" : "none";
4129
+ });
4130
+ }
4131
+ if (indicator) {
4132
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4133
+ }
4134
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4135
+ };
4136
+ if (totalPages > 1) {
4137
+ showPage(1);
3512
4138
  }
3513
4139
  const cleanup = () => {
4140
+ indicator?.remove();
3514
4141
  wrapper.remove();
3515
4142
  ctx.container.innerHTML = "";
3516
4143
  };
3517
4144
  ctx.signal.addEventListener("abort", cleanup);
3518
4145
  return {
3519
4146
  destroy: cleanup,
4147
+ getPageCount: () => totalPages,
4148
+ getCurrentPage: () => currentPage,
4149
+ goToPage: (page) => showPage(page),
3520
4150
  zoomIn: () => {
3521
- scale += 0.1;
3522
- wrapper.style.transform = `scale(${scale})`;
4151
+ scale += 0.15;
4152
+ applyTransform();
3523
4153
  },
3524
4154
  zoomOut: () => {
3525
- scale = Math.max(0.2, scale - 0.1);
3526
- wrapper.style.transform = `scale(${scale})`;
4155
+ scale = Math.max(0.2, scale - 0.15);
4156
+ applyTransform();
3527
4157
  },
3528
4158
  getZoom: () => scale,
3529
4159
  setZoom: (level) => {
3530
4160
  scale = level;
3531
- wrapper.style.transform = `scale(${scale})`;
4161
+ applyTransform();
3532
4162
  },
3533
4163
  fitToPage: () => {
3534
- scale = 1;
3535
- wrapper.style.transform = "scale(1)";
4164
+ scale = calculateFitScale();
4165
+ rotation = 0;
4166
+ applyTransform();
4167
+ },
4168
+ rotateCW: () => {
4169
+ rotation = (rotation + 90) % 360;
4170
+ applyTransform();
4171
+ },
4172
+ rotateCCW: () => {
4173
+ rotation = (rotation - 90 + 360) % 360;
4174
+ applyTransform();
3536
4175
  },
3537
4176
  download: () => {
3538
4177
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3704,41 +4343,41 @@ var OpenDocumentPlugin = class {
3704
4343
  }
3705
4344
  getToolbarActions(instance) {
3706
4345
  const isPresentation = instance.isPresentation;
4346
+ const totalPages = instance.getPageCount?.() ?? 1;
3707
4347
  const actions = [];
3708
- if (isPresentation) {
3709
- actions.push(
3710
- {
4348
+ if (isPresentation || totalPages > 1) {
4349
+ if (isPresentation) {
4350
+ actions.push({
3711
4351
  id: "thumbnails",
3712
4352
  icon: "thumbnails",
3713
4353
  label: "Slide Thumbnails",
3714
4354
  type: "button",
3715
4355
  group: "navigation",
3716
4356
  execute: () => instance.toggleThumbnails?.()
3717
- },
3718
- {
3719
- id: "page-nav",
3720
- icon: "",
3721
- label: "Slide Navigation",
3722
- type: "page-nav",
3723
- group: "navigation",
3724
- value: instance.getCurrentPage?.() ?? 1,
3725
- max: instance.getPageCount?.() ?? 1,
3726
- execute: (action, page) => {
3727
- if (action === "prev") {
3728
- const cur = instance.getCurrentPage?.() ?? 1;
3729
- if (cur > 1) instance.goToPage?.(cur - 1);
3730
- } else if (action === "next") {
3731
- const cur = instance.getCurrentPage?.() ?? 1;
3732
- const total = instance.getPageCount?.() ?? 1;
3733
- if (cur < total) instance.goToPage?.(cur + 1);
3734
- } else if (typeof page === "number") {
3735
- instance.goToPage?.(page);
3736
- } else if (typeof action === "number") {
3737
- instance.goToPage?.(action);
3738
- }
4357
+ });
4358
+ }
4359
+ actions.push({
4360
+ id: "page-nav",
4361
+ icon: "",
4362
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
4363
+ type: "page-nav",
4364
+ group: "navigation",
4365
+ value: instance.getCurrentPage?.() ?? 1,
4366
+ max: totalPages,
4367
+ execute: (action, page) => {
4368
+ const cur = instance.getCurrentPage?.() ?? 1;
4369
+ const max = instance.getPageCount?.() ?? 1;
4370
+ if (action === "prev") {
4371
+ if (cur > 1) instance.goToPage?.(cur - 1);
4372
+ } else if (action === "next") {
4373
+ if (cur < max) instance.goToPage?.(cur + 1);
4374
+ } else if (typeof page === "number") {
4375
+ instance.goToPage?.(page);
4376
+ } else if (typeof action === "number") {
4377
+ instance.goToPage?.(action);
3739
4378
  }
3740
4379
  }
3741
- );
4380
+ });
3742
4381
  }
3743
4382
  actions.push(
3744
4383
  {
@@ -3760,11 +4399,19 @@ var OpenDocumentPlugin = class {
3760
4399
  {
3761
4400
  id: "fit-page",
3762
4401
  icon: "fit-page",
3763
- label: "Fit to Page",
4402
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
3764
4403
  type: "button",
3765
4404
  group: "zoom",
3766
4405
  execute: () => instance.fitToPage?.()
3767
4406
  },
4407
+ {
4408
+ id: "rotate-cw",
4409
+ icon: "rotate-cw",
4410
+ label: "Rotate",
4411
+ type: "button",
4412
+ group: "view",
4413
+ execute: () => instance.rotateCW?.()
4414
+ },
3768
4415
  {
3769
4416
  id: "download",
3770
4417
  icon: "download",
@@ -3828,6 +4475,22 @@ var OpenDocumentPlugin = class {
3828
4475
  container.appendChild(wrapper);
3829
4476
  ctx.container.appendChild(container);
3830
4477
  let scale = 1;
4478
+ let rotation = 0;
4479
+ const calculateFitScale = () => {
4480
+ const elW = wrapper.offsetWidth || 850;
4481
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4482
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4483
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4484
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4485
+ };
4486
+ const applyTransform = () => {
4487
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4488
+ wrapper.style.transformOrigin = "top center";
4489
+ };
4490
+ setTimeout(() => {
4491
+ scale = calculateFitScale();
4492
+ applyTransform();
4493
+ }, 60);
3831
4494
  let currentPage = 1;
3832
4495
  let totalPages = 1;
3833
4496
  let slides = [];
@@ -3886,21 +4549,30 @@ var OpenDocumentPlugin = class {
3886
4549
  destroy: cleanup,
3887
4550
  isPresentation,
3888
4551
  zoomIn: () => {
3889
- scale += 0.1;
3890
- wrapper.style.transform = `scale(${scale})`;
4552
+ scale += 0.15;
4553
+ applyTransform();
3891
4554
  },
3892
4555
  zoomOut: () => {
3893
- scale = Math.max(0.2, scale - 0.1);
3894
- wrapper.style.transform = `scale(${scale})`;
4556
+ scale = Math.max(0.2, scale - 0.15);
4557
+ applyTransform();
3895
4558
  },
3896
4559
  getZoom: () => scale,
3897
4560
  setZoom: (level) => {
3898
4561
  scale = level;
3899
- wrapper.style.transform = `scale(${scale})`;
4562
+ applyTransform();
3900
4563
  },
3901
4564
  fitToPage: () => {
3902
- scale = 1;
3903
- wrapper.style.transform = "scale(1)";
4565
+ scale = calculateFitScale();
4566
+ rotation = 0;
4567
+ applyTransform();
4568
+ },
4569
+ rotateCW: () => {
4570
+ rotation = (rotation + 90) % 360;
4571
+ applyTransform();
4572
+ },
4573
+ rotateCCW: () => {
4574
+ rotation = (rotation - 90 + 360) % 360;
4575
+ applyTransform();
3904
4576
  },
3905
4577
  goToPage,
3906
4578
  getPageCount: () => totalPages,
@@ -4121,7 +4793,31 @@ var DocPlugin = class {
4121
4793
  return this.mimeTypes.includes(mime || "");
4122
4794
  }
4123
4795
  getToolbarActions(instance) {
4124
- return [
4796
+ const totalPages = instance.getPageCount?.() ?? 1;
4797
+ const actions = [];
4798
+ if (totalPages > 1) {
4799
+ actions.push({
4800
+ id: "page-nav",
4801
+ icon: "",
4802
+ label: "Page Navigation",
4803
+ type: "page-nav",
4804
+ group: "navigation",
4805
+ value: instance.getCurrentPage?.() ?? 1,
4806
+ max: totalPages,
4807
+ execute: (action, page) => {
4808
+ const cur = instance.getCurrentPage?.() ?? 1;
4809
+ const max = instance.getPageCount?.() ?? 1;
4810
+ if (action === "prev") {
4811
+ if (cur > 1) instance.goToPage?.(cur - 1);
4812
+ } else if (action === "next") {
4813
+ if (cur < max) instance.goToPage?.(cur + 1);
4814
+ } else if (typeof page === "number") {
4815
+ instance.goToPage?.(page);
4816
+ }
4817
+ }
4818
+ });
4819
+ }
4820
+ actions.push(
4125
4821
  {
4126
4822
  id: "zoom-out",
4127
4823
  icon: "zoom-out",
@@ -4146,6 +4842,14 @@ var DocPlugin = class {
4146
4842
  group: "zoom",
4147
4843
  execute: () => instance.fitToPage?.()
4148
4844
  },
4845
+ {
4846
+ id: "rotate-cw",
4847
+ icon: "rotate-cw",
4848
+ label: "Rotate",
4849
+ type: "button",
4850
+ group: "view",
4851
+ execute: () => instance.rotateCW?.()
4852
+ },
4149
4853
  {
4150
4854
  id: "copy",
4151
4855
  icon: "copy",
@@ -4170,7 +4874,8 @@ var DocPlugin = class {
4170
4874
  group: "actions",
4171
4875
  execute: () => instance.print?.()
4172
4876
  }
4173
- ];
4877
+ );
4878
+ return actions;
4174
4879
  }
4175
4880
  async render(ctx) {
4176
4881
  const container = document.createElement("div");
@@ -4197,6 +4902,7 @@ var DocPlugin = class {
4197
4902
  ctx.container.appendChild(container);
4198
4903
  let scale = 1;
4199
4904
  let extractedRawText = "";
4905
+ let isFallback = false;
4200
4906
  try {
4201
4907
  const cfbf = new CfbfReader(ctx.buffer);
4202
4908
  const wordDocStream = cfbf.readStream("WordDocument");
@@ -4210,42 +4916,131 @@ var DocPlugin = class {
4210
4916
  const tableStream = cfbf.readStream(tableName);
4211
4917
  const text = this.extractDocText(wordDocStream, tableStream);
4212
4918
  extractedRawText = text;
4213
- wrapper.innerHTML = this.formatDocToHtml(text, ctx.metadata.name || "Document");
4214
4919
  } catch (err) {
4215
4920
  console.warn("[DocPlugin] Binary parsing error, fallback text:", err);
4216
4921
  const fallback = this.heuristicTextExtraction(ctx.buffer);
4217
4922
  extractedRawText = fallback;
4218
- wrapper.innerHTML = `
4219
- <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4220
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${ctx.metadata.name || "Word Document (.doc)"}</h2>
4221
- <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4222
- </div>
4223
- ${this.formatDocToHtml(fallback, ctx.metadata.name || "Document")}
4224
- `;
4923
+ isFallback = true;
4924
+ }
4925
+ const rawPages = this.splitIntoPages(extractedRawText);
4926
+ const totalPages = Math.max(1, rawPages.length);
4927
+ let currentPage = 1;
4928
+ wrapper.innerHTML = "";
4929
+ const pageCards = [];
4930
+ for (let i = 0; i < totalPages; i++) {
4931
+ const pageCard = document.createElement("div");
4932
+ pageCard.className = "fp-doc-page-card";
4933
+ pageCard.style.backgroundColor = "#ffffff";
4934
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4935
+ pageCard.style.borderRadius = "4px";
4936
+ pageCard.style.padding = "56px 48px";
4937
+ pageCard.style.minHeight = "100%";
4938
+ pageCard.style.display = i === 0 ? "block" : "none";
4939
+ if (isFallback && i === 0) {
4940
+ pageCard.innerHTML = `
4941
+ <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4942
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4943
+ <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4944
+ </div>
4945
+ ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
4946
+ `;
4947
+ } else {
4948
+ pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4949
+ }
4950
+ wrapper.appendChild(pageCard);
4951
+ pageCards.push(pageCard);
4952
+ }
4953
+ let indicator = null;
4954
+ if (totalPages > 1) {
4955
+ indicator = document.createElement("div");
4956
+ indicator.className = "fp-doc-page-indicator";
4957
+ indicator.style.position = "sticky";
4958
+ indicator.style.bottom = "16px";
4959
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4960
+ indicator.style.backdropFilter = "blur(8px)";
4961
+ indicator.style.color = "#f8fafc";
4962
+ indicator.style.fontSize = "12px";
4963
+ indicator.style.fontWeight = "600";
4964
+ indicator.style.padding = "5px 14px";
4965
+ indicator.style.borderRadius = "20px";
4966
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4967
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4968
+ indicator.style.zIndex = "10";
4969
+ indicator.style.userSelect = "none";
4970
+ indicator.style.pointerEvents = "none";
4971
+ indicator.style.textAlign = "center";
4972
+ indicator.style.width = "fit-content";
4973
+ indicator.style.margin = "16px auto 0";
4974
+ container.appendChild(indicator);
4975
+ }
4976
+ const showPage = (pageNum) => {
4977
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4978
+ if (totalPages > 1) {
4979
+ pageCards.forEach((card, idx) => {
4980
+ card.style.display = idx + 1 === currentPage ? "block" : "none";
4981
+ });
4982
+ }
4983
+ if (indicator) {
4984
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4985
+ }
4986
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4987
+ };
4988
+ if (totalPages > 1) {
4989
+ showPage(1);
4225
4990
  }
4991
+ let rotation = 0;
4992
+ const calculateFitScale = () => {
4993
+ const activeCard = pageCards[currentPage - 1] || wrapper;
4994
+ const elW = activeCard.offsetWidth || 850;
4995
+ const elH = activeCard.offsetHeight || 1e3;
4996
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4997
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4998
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4999
+ };
5000
+ const applyTransform = () => {
5001
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5002
+ wrapper.style.transformOrigin = "top center";
5003
+ };
5004
+ setTimeout(() => {
5005
+ scale = calculateFitScale();
5006
+ applyTransform();
5007
+ }, 60);
4226
5008
  const cleanup = () => {
5009
+ indicator?.remove();
4227
5010
  container.remove();
4228
5011
  ctx.container.innerHTML = "";
4229
5012
  };
4230
5013
  ctx.signal.addEventListener("abort", cleanup);
4231
5014
  return {
4232
5015
  destroy: cleanup,
5016
+ getPageCount: () => totalPages,
5017
+ getCurrentPage: () => currentPage,
5018
+ goToPage: (page) => showPage(page),
4233
5019
  zoomIn: () => {
4234
- scale += 0.1;
4235
- wrapper.style.transform = `scale(${scale})`;
5020
+ scale += 0.15;
5021
+ applyTransform();
4236
5022
  },
4237
5023
  zoomOut: () => {
4238
- scale = Math.max(0.2, scale - 0.1);
4239
- wrapper.style.transform = `scale(${scale})`;
5024
+ scale = Math.max(0.2, scale - 0.15);
5025
+ applyTransform();
4240
5026
  },
4241
5027
  getZoom: () => scale,
4242
5028
  setZoom: (level) => {
4243
5029
  scale = level;
4244
- wrapper.style.transform = `scale(${scale})`;
5030
+ applyTransform();
4245
5031
  },
4246
5032
  fitToPage: () => {
4247
- scale = 1;
4248
- wrapper.style.transform = "scale(1)";
5033
+ scale = calculateFitScale();
5034
+ rotation = 0;
5035
+ applyTransform();
5036
+ },
5037
+ rotateCW: () => {
5038
+ rotation = (rotation + 90) % 360;
5039
+ applyTransform();
5040
+ },
5041
+ rotateCCW: () => {
5042
+ rotation = (rotation - 90 + 360) % 360;
5043
+ applyTransform();
4249
5044
  },
4250
5045
  copy: () => {
4251
5046
  navigator.clipboard.writeText(extractedRawText);
@@ -4361,11 +5156,16 @@ var DocPlugin = class {
4361
5156
  heuristicTextExtraction(buffer) {
4362
5157
  return this.extractStringsFromBytes(new Uint8Array(buffer));
4363
5158
  }
5159
+ splitIntoPages(text) {
5160
+ if (!text) return [""];
5161
+ 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);
5162
+ return parts.length > 0 ? parts : [text];
5163
+ }
4364
5164
  /**
4365
5165
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
4366
5166
  */
4367
5167
  formatDocToHtml(text, filename) {
4368
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x0C/g, "\n\n").split("\n");
5168
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4369
5169
  let html = "";
4370
5170
  let inList = false;
4371
5171
  for (const rawLine of lines) {
@@ -4480,6 +5280,14 @@ var PptPlugin = class {
4480
5280
  group: "zoom",
4481
5281
  execute: () => instance.fitToPage?.()
4482
5282
  },
5283
+ {
5284
+ id: "rotate-cw",
5285
+ icon: "rotate-cw",
5286
+ label: "Rotate",
5287
+ type: "button",
5288
+ group: "view",
5289
+ execute: () => instance.rotateCW?.()
5290
+ },
4483
5291
  {
4484
5292
  id: "download",
4485
5293
  icon: "download",
@@ -4513,22 +5321,36 @@ var PptPlugin = class {
4513
5321
  const slideCard = document.createElement("div");
4514
5322
  slideCard.className = "fp-ppt-slide-card";
4515
5323
  slideCard.style.width = "960px";
4516
- slideCard.style.maxWidth = "92%";
5324
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5325
+ slideCard.style.maxHeight = "calc(100% - 48px)";
4517
5326
  slideCard.style.aspectRatio = "16 / 9";
4518
5327
  slideCard.style.backgroundColor = "#ffffff";
4519
5328
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4520
5329
  slideCard.style.borderRadius = "8px";
4521
5330
  slideCard.style.boxSizing = "border-box";
4522
5331
  slideCard.style.position = "relative";
4523
- slideCard.style.overflow = "hidden";
4524
- slideCard.style.transformOrigin = "center center";
4525
5332
  slideCard.style.transition = "transform 0.2s ease";
5333
+ slideCard.style.transformOrigin = "center center";
5334
+ slideCard.style.flexShrink = "0";
4526
5335
  container.appendChild(slideCard);
4527
5336
  ctx.container.appendChild(container);
4528
5337
  let scale = 1;
5338
+ let rotation = 0;
4529
5339
  let currentSlide = 1;
4530
5340
  let slides = [];
4531
5341
  const createdBlobUrls = [];
5342
+ const calculateFitScale = () => {
5343
+ const availW = Math.max(200, container.clientWidth - 48);
5344
+ const availH = Math.max(200, container.clientHeight - 72);
5345
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5346
+ };
5347
+ const applyTransform = () => {
5348
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5349
+ };
5350
+ setTimeout(() => {
5351
+ scale = calculateFitScale();
5352
+ applyTransform();
5353
+ }, 60);
4532
5354
  try {
4533
5355
  const cfbf = new CfbfReader(ctx.buffer);
4534
5356
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -4629,21 +5451,30 @@ var PptPlugin = class {
4629
5451
  return {
4630
5452
  destroy: cleanup,
4631
5453
  zoomIn: () => {
4632
- scale += 0.1;
4633
- slideCard.style.transform = `scale(${scale})`;
5454
+ scale += 0.15;
5455
+ applyTransform();
4634
5456
  },
4635
5457
  zoomOut: () => {
4636
- scale = Math.max(0.3, scale - 0.1);
4637
- slideCard.style.transform = `scale(${scale})`;
5458
+ scale = Math.max(0.2, scale - 0.15);
5459
+ applyTransform();
4638
5460
  },
4639
5461
  getZoom: () => scale,
4640
5462
  setZoom: (level) => {
4641
5463
  scale = level;
4642
- slideCard.style.transform = `scale(${scale})`;
5464
+ applyTransform();
4643
5465
  },
4644
5466
  fitToPage: () => {
4645
- scale = 1;
4646
- slideCard.style.transform = "scale(1)";
5467
+ scale = calculateFitScale();
5468
+ rotation = 0;
5469
+ applyTransform();
5470
+ },
5471
+ rotateCW: () => {
5472
+ rotation = (rotation + 90) % 360;
5473
+ applyTransform();
5474
+ },
5475
+ rotateCCW: () => {
5476
+ rotation = (rotation - 90 + 360) % 360;
5477
+ applyTransform();
4647
5478
  },
4648
5479
  goToPage: (page) => {
4649
5480
  if (page >= 1 && page <= totalSlides) {