@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.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var DOMPurify2 = require('dompurify');
4
+ var pdfjsLib = require('pdfjs-dist');
4
5
  var docx = require('docx-preview');
5
6
  var fflate = require('fflate');
6
7
  var XLSX = require('xlsx');
@@ -34,6 +35,7 @@ function _interopNamespace(e) {
34
35
  }
35
36
 
36
37
  var DOMPurify2__default = /*#__PURE__*/_interopDefault(DOMPurify2);
38
+ var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
37
39
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
38
40
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
39
41
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
@@ -551,11 +553,21 @@ var ToolbarController = class {
551
553
  el;
552
554
  toolbarEl;
553
555
  actions = [];
556
+ pageInputEl = null;
557
+ pageLabelEl = null;
554
558
  constructor(container) {
555
559
  this.el = container;
556
560
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
557
561
  this.el.appendChild(this.toolbarEl);
558
562
  }
563
+ setPage(page, max) {
564
+ if (this.pageInputEl) {
565
+ this.pageInputEl.value = page.toString();
566
+ }
567
+ if (max !== void 0 && this.pageLabelEl) {
568
+ this.pageLabelEl.textContent = ` / ${max}`;
569
+ }
570
+ }
559
571
  update(actions) {
560
572
  this.actions = actions;
561
573
  this.render();
@@ -598,6 +610,7 @@ var ToolbarController = class {
598
610
  if (action.type === "separator") {
599
611
  groupEl.appendChild(createElement("div", { className: "fp-toolbar-separator" }));
600
612
  } else if (action.type === "page-nav") {
613
+ const max = action.max ?? 1;
601
614
  const prevBtn = this.createButton(
602
615
  "prev",
603
616
  ICON_PAGE_PREV,
@@ -616,7 +629,6 @@ var ToolbarController = class {
616
629
  "Next Page",
617
630
  () => {
618
631
  const cur = parseInt(input.value, 10) || 1;
619
- const max = action.max ?? 1;
620
632
  if (cur < max) {
621
633
  input.value = (cur + 1).toString();
622
634
  action.execute("next", cur + 1);
@@ -628,15 +640,18 @@ var ToolbarController = class {
628
640
  type: "number",
629
641
  value: (action.value ?? 1).toString(),
630
642
  min: "1",
631
- max: (action.max ?? 1).toString()
643
+ max: max.toString()
632
644
  });
633
645
  input.addEventListener("change", () => {
634
- const val = parseInt(input.value, 10);
635
- if (!isNaN(val)) {
636
- action.execute("go", val);
637
- }
646
+ let val = parseInt(input.value, 10);
647
+ if (isNaN(val)) val = 1;
648
+ val = Math.max(1, Math.min(max, val));
649
+ input.value = val.toString();
650
+ action.execute("go", val);
638
651
  });
639
- const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
652
+ const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
653
+ this.pageInputEl = input;
654
+ this.pageLabelEl = label;
640
655
  groupEl.appendChild(prevBtn);
641
656
  groupEl.appendChild(input);
642
657
  groupEl.appendChild(label);
@@ -763,6 +778,8 @@ var FilePreviewViewer = class {
763
778
  keyHandler = null;
764
779
  currentContainer = null;
765
780
  currentOptions = {};
781
+ resizeObserver = null;
782
+ fullscreenHandler = null;
766
783
  /**
767
784
  * Register a preview plugin.
768
785
  */
@@ -814,7 +831,13 @@ var FilePreviewViewer = class {
814
831
  buffer,
815
832
  options,
816
833
  signal,
817
- emit: (event, payload) => this.eventEmitter.emit(event, payload)
834
+ emit: (event, payload) => {
835
+ if (event === "page-change" && payload && typeof payload.page === "number") {
836
+ const total = payload.total ?? payload.totalPages;
837
+ this.toolbar?.setPage(payload.page, total);
838
+ }
839
+ this.eventEmitter.emit(event, payload);
840
+ }
818
841
  });
819
842
  this.activeInstance = instance;
820
843
  this.hideLoading();
@@ -829,12 +852,31 @@ var FilePreviewViewer = class {
829
852
  label: "Toggle Fullscreen",
830
853
  type: "button",
831
854
  group: "view",
832
- execute: () => {
833
- if (!document.fullscreenElement) {
834
- this.wrapperEl?.requestFullscreen?.();
835
- } else {
836
- document.exitFullscreen?.();
855
+ execute: async () => {
856
+ try {
857
+ const isNativeFs = !!document.fullscreenElement;
858
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
859
+ if (!isNativeFs && !isCssFs) {
860
+ if (this.wrapperEl?.requestFullscreen) {
861
+ await this.wrapperEl.requestFullscreen().catch(() => {
862
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
863
+ });
864
+ } else {
865
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
866
+ }
867
+ } else {
868
+ if (document.fullscreenElement) {
869
+ await document.exitFullscreen().catch(() => {
870
+ });
871
+ }
872
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
873
+ }
874
+ } catch {
875
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
837
876
  }
877
+ setTimeout(() => {
878
+ this.activeInstance?.fitToPage?.();
879
+ }, 120);
838
880
  }
839
881
  });
840
882
  }
@@ -848,6 +890,7 @@ var FilePreviewViewer = class {
848
890
  if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
849
891
  this.thumbnailPanel.update(thumbnails, (index) => {
850
892
  instance.goToPage?.(index + 1);
893
+ this.toolbar?.setPage(index + 1);
851
894
  });
852
895
  if (options.showThumbnails) {
853
896
  this.thumbnailPanel.show();
@@ -883,6 +926,15 @@ var FilePreviewViewer = class {
883
926
  window.removeEventListener("keydown", this.keyHandler);
884
927
  this.keyHandler = null;
885
928
  }
929
+ if (this.resizeObserver) {
930
+ this.resizeObserver.disconnect();
931
+ this.resizeObserver = null;
932
+ }
933
+ if (this.fullscreenHandler) {
934
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
935
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
936
+ this.fullscreenHandler = null;
937
+ }
886
938
  this.abort();
887
939
  this.destroyInstance();
888
940
  this.toolbar?.destroy();
@@ -962,6 +1014,25 @@ var FilePreviewViewer = class {
962
1014
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
963
1015
  this.setupKeyboardShortcuts();
964
1016
  this.setupDragAndDrop(container, options);
1017
+ this.setupResizeAndFullscreenListeners();
1018
+ }
1019
+ setupResizeAndFullscreenListeners() {
1020
+ if (this.fullscreenHandler) return;
1021
+ this.fullscreenHandler = () => {
1022
+ setTimeout(() => {
1023
+ this.activeInstance?.fitToPage?.();
1024
+ }, 100);
1025
+ };
1026
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
1027
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
1028
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1029
+ this.resizeObserver = new ResizeObserver(() => {
1030
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1031
+ this.activeInstance.fitToPage?.();
1032
+ }
1033
+ });
1034
+ this.resizeObserver.observe(this.contentEl);
1035
+ }
965
1036
  }
966
1037
  setupKeyboardShortcuts() {
967
1038
  if (this.keyHandler) return;
@@ -973,9 +1044,13 @@ var FilePreviewViewer = class {
973
1044
  if (e.key === "ArrowRight" || e.key === "PageDown") {
974
1045
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
975
1046
  this.activeInstance.goToPage?.(cur + 1);
1047
+ const nextCur = this.activeInstance.getCurrentPage?.() ?? cur + 1;
1048
+ this.toolbar?.setPage(nextCur);
976
1049
  } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
977
1050
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
978
1051
  this.activeInstance.goToPage?.(Math.max(1, cur - 1));
1052
+ const prevCur = this.activeInstance.getCurrentPage?.() ?? Math.max(1, cur - 1);
1053
+ this.toolbar?.setPage(prevCur);
979
1054
  } else if (e.key === "+" || e.key === "=") {
980
1055
  this.activeInstance.zoomIn?.();
981
1056
  } else if (e.key === "-" || e.key === "_") {
@@ -1241,30 +1316,62 @@ var CfbfReader = class {
1241
1316
  return result.subarray(0, targetSize);
1242
1317
  }
1243
1318
  };
1244
-
1245
- // ../plugins/pdf/dist/index.js
1319
+ if (typeof window !== "undefined" && pdfjsLib__namespace.GlobalWorkerOptions) {
1320
+ if (!pdfjsLib__namespace.GlobalWorkerOptions.workerSrc) {
1321
+ pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1322
+ }
1323
+ }
1246
1324
  var PdfPlugin = class {
1247
1325
  id = "pdf";
1248
- name = "PDF Preview";
1326
+ name = "PDF Document Preview";
1249
1327
  extensions = [".pdf"];
1250
1328
  mimeTypes = ["application/pdf"];
1251
1329
  weight = 100;
1252
1330
  supports(file) {
1253
1331
  const ext = file.metadata.extension?.toLowerCase();
1254
1332
  const mime = file.metadata.mimeType?.toLowerCase();
1255
- return ext === ".pdf" || mime === "application/pdf";
1333
+ if (ext) return this.extensions.includes(ext);
1334
+ return this.mimeTypes.includes(mime || "");
1256
1335
  }
1257
1336
  getToolbarActions(instance) {
1337
+ const totalPages = instance.getPageCount?.() ?? 1;
1338
+ const curPage = instance.getCurrentPage?.() ?? 1;
1258
1339
  return [
1340
+ {
1341
+ id: "thumbnails",
1342
+ icon: "thumbnails",
1343
+ label: "Page Thumbnails",
1344
+ type: "button",
1345
+ group: "navigation",
1346
+ execute: () => instance.toggleThumbnails?.()
1347
+ },
1348
+ {
1349
+ id: "page-nav",
1350
+ icon: "",
1351
+ label: "Page Navigation",
1352
+ type: "page-nav",
1353
+ group: "navigation",
1354
+ value: curPage,
1355
+ max: totalPages,
1356
+ execute: (action, page) => {
1357
+ const cur = instance.getCurrentPage?.() ?? 1;
1358
+ const max = instance.getPageCount?.() ?? 1;
1359
+ if (action === "prev") {
1360
+ if (cur > 1) instance.goToPage?.(cur - 1);
1361
+ } else if (action === "next") {
1362
+ if (cur < max) instance.goToPage?.(cur + 1);
1363
+ } else if (typeof page === "number") {
1364
+ instance.goToPage?.(page);
1365
+ }
1366
+ }
1367
+ },
1259
1368
  {
1260
1369
  id: "zoom-out",
1261
1370
  icon: "zoom-out",
1262
1371
  label: "Zoom Out",
1263
1372
  type: "button",
1264
1373
  group: "zoom",
1265
- execute: () => {
1266
- instance.zoomOut?.();
1267
- }
1374
+ execute: () => instance.zoomOut?.()
1268
1375
  },
1269
1376
  {
1270
1377
  id: "zoom-in",
@@ -1272,9 +1379,7 @@ var PdfPlugin = class {
1272
1379
  label: "Zoom In",
1273
1380
  type: "button",
1274
1381
  group: "zoom",
1275
- execute: () => {
1276
- instance.zoomIn?.();
1277
- }
1382
+ execute: () => instance.zoomIn?.()
1278
1383
  },
1279
1384
  {
1280
1385
  id: "fit-page",
@@ -1282,39 +1387,23 @@ var PdfPlugin = class {
1282
1387
  label: "Fit to Page",
1283
1388
  type: "button",
1284
1389
  group: "zoom",
1285
- execute: () => {
1286
- instance.fitToPage?.();
1287
- }
1390
+ execute: () => instance.fitToPage?.()
1288
1391
  },
1289
1392
  {
1290
1393
  id: "rotate-cw",
1291
1394
  icon: "rotate-cw",
1292
- label: "Rotate",
1395
+ label: "Rotate Clockwise",
1293
1396
  type: "button",
1294
1397
  group: "view",
1295
- execute: () => {
1296
- instance.rotateCW?.();
1297
- }
1298
- },
1299
- {
1300
- id: "page-nav",
1301
- icon: "page-nav",
1302
- label: "Page Navigation",
1303
- type: "page-nav",
1304
- group: "navigation",
1305
- execute: (page) => {
1306
- if (typeof page === "number") instance.goToPage?.(page);
1307
- }
1398
+ execute: () => instance.rotateCW?.()
1308
1399
  },
1309
1400
  {
1310
1401
  id: "download",
1311
1402
  icon: "download",
1312
- label: "Download",
1403
+ label: "Download PDF",
1313
1404
  type: "button",
1314
1405
  group: "actions",
1315
- execute: () => {
1316
- instance.download?.();
1317
- }
1406
+ execute: () => instance.download?.()
1318
1407
  },
1319
1408
  {
1320
1409
  id: "print",
@@ -1322,99 +1411,224 @@ var PdfPlugin = class {
1322
1411
  label: "Print",
1323
1412
  type: "button",
1324
1413
  group: "actions",
1325
- execute: () => {
1326
- instance.print?.();
1327
- }
1414
+ execute: () => instance.print?.()
1328
1415
  }
1329
1416
  ];
1330
1417
  }
1331
1418
  async render(ctx) {
1332
- const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1333
- const url = URL.createObjectURL(blob);
1334
- const wrapper = document.createElement("div");
1335
- wrapper.style.width = "100%";
1336
- wrapper.style.height = "100%";
1337
- wrapper.style.overflow = "hidden";
1338
- wrapper.style.display = "flex";
1339
- wrapper.style.justifyContent = "center";
1340
- wrapper.style.alignItems = "center";
1341
- const iframe = document.createElement("iframe");
1342
- iframe.src = url;
1343
- iframe.style.width = "100%";
1344
- iframe.style.height = "100%";
1345
- iframe.style.border = "none";
1346
- wrapper.appendChild(iframe);
1347
- ctx.container.appendChild(wrapper);
1419
+ const container = document.createElement("div");
1420
+ container.className = "fp-pdf-container";
1421
+ container.style.width = "100%";
1422
+ container.style.height = "100%";
1423
+ container.style.overflow = "auto";
1424
+ container.style.display = "flex";
1425
+ container.style.flexDirection = "column";
1426
+ container.style.alignItems = "center";
1427
+ container.style.justifyContent = "flex-start";
1428
+ container.style.padding = "20px 16px";
1429
+ container.style.backgroundColor = "#0f172a";
1430
+ container.style.boxSizing = "border-box";
1431
+ container.style.position = "relative";
1432
+ const pageCard = document.createElement("div");
1433
+ pageCard.className = "fp-pdf-page-card";
1434
+ pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
1435
+ pageCard.style.backgroundColor = "#ffffff";
1436
+ pageCard.style.borderRadius = "4px";
1437
+ pageCard.style.overflow = "hidden";
1438
+ pageCard.style.lineHeight = "0";
1439
+ pageCard.style.transition = "transform 0.15s ease";
1440
+ pageCard.style.position = "relative";
1441
+ pageCard.style.flexShrink = "0";
1442
+ let canvas = document.createElement("canvas");
1443
+ pageCard.appendChild(canvas);
1444
+ container.appendChild(pageCard);
1445
+ const indicator = document.createElement("div");
1446
+ indicator.className = "fp-pdf-page-indicator";
1447
+ indicator.style.position = "sticky";
1448
+ indicator.style.bottom = "16px";
1449
+ indicator.style.marginTop = "16px";
1450
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1451
+ indicator.style.backdropFilter = "blur(8px)";
1452
+ indicator.style.color = "#f8fafc";
1453
+ indicator.style.fontSize = "12px";
1454
+ indicator.style.fontWeight = "600";
1455
+ indicator.style.padding = "5px 14px";
1456
+ indicator.style.borderRadius = "20px";
1457
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1458
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1459
+ indicator.style.zIndex = "10";
1460
+ indicator.style.userSelect = "none";
1461
+ indicator.style.pointerEvents = "none";
1462
+ container.appendChild(indicator);
1463
+ ctx.container.appendChild(container);
1464
+ const loadingTask = pdfjsLib__namespace.getDocument({
1465
+ data: new Uint8Array(ctx.buffer),
1466
+ cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/cmaps/`,
1467
+ cMapPacked: true,
1468
+ standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/standard_fonts/`
1469
+ });
1470
+ const pdfDoc = await loadingTask.promise;
1471
+ const totalPages = Math.max(1, pdfDoc.numPages);
1348
1472
  let currentPage = 1;
1349
- let currentZoom = 1;
1473
+ let zoomScale = 1;
1350
1474
  let rotation = 0;
1475
+ let currentRenderTask = null;
1476
+ const renderPage = async (pageNum) => {
1477
+ if (currentRenderTask) {
1478
+ try {
1479
+ currentRenderTask.cancel();
1480
+ } catch {
1481
+ }
1482
+ currentRenderTask = null;
1483
+ }
1484
+ const newCanvas = document.createElement("canvas");
1485
+ pageCard.replaceChild(newCanvas, canvas);
1486
+ canvas = newCanvas;
1487
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
1488
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1489
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
1490
+ const page = await pdfDoc.getPage(currentPage);
1491
+ const containerWidth = container.clientWidth || 900;
1492
+ const containerHeight = container.clientHeight || 700;
1493
+ const unscaledVp = page.getViewport({ scale: 1, rotation });
1494
+ const availWidth = Math.max(100, containerWidth - 48);
1495
+ const availHeight = Math.max(100, containerHeight - 88);
1496
+ const scaleW = availWidth / unscaledVp.width;
1497
+ const scaleH = availHeight / unscaledVp.height;
1498
+ const fitScale = Math.min(scaleW, scaleH);
1499
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1500
+ const pixelRatio = window.devicePixelRatio || 1;
1501
+ const viewport = page.getViewport({ scale: effectiveScale, rotation });
1502
+ canvas.width = Math.floor(viewport.width * pixelRatio);
1503
+ canvas.height = Math.floor(viewport.height * pixelRatio);
1504
+ canvas.style.width = `${Math.floor(viewport.width)}px`;
1505
+ canvas.style.height = `${Math.floor(viewport.height)}px`;
1506
+ const canvasCtx = canvas.getContext("2d");
1507
+ if (!canvasCtx) return;
1508
+ canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
1509
+ currentRenderTask = page.render({
1510
+ canvasContext: canvasCtx,
1511
+ viewport
1512
+ });
1513
+ try {
1514
+ await currentRenderTask.promise;
1515
+ } catch (err) {
1516
+ if (err?.name !== "RenderingCancelledException") {
1517
+ console.warn("[PdfPlugin] Page render warning:", err);
1518
+ }
1519
+ } finally {
1520
+ currentRenderTask = null;
1521
+ }
1522
+ };
1523
+ await renderPage(1);
1351
1524
  const cleanup = () => {
1352
- URL.revokeObjectURL(url);
1353
- wrapper.remove();
1525
+ if (currentRenderTask) {
1526
+ try {
1527
+ currentRenderTask.cancel();
1528
+ } catch {
1529
+ }
1530
+ }
1531
+ try {
1532
+ pdfDoc.destroy();
1533
+ } catch {
1534
+ }
1535
+ container.remove();
1354
1536
  ctx.container.innerHTML = "";
1355
1537
  };
1356
1538
  ctx.signal.addEventListener("abort", cleanup);
1357
- return {
1539
+ const instance = {
1358
1540
  destroy: cleanup,
1359
1541
  zoomIn: () => {
1360
- currentZoom += 0.1;
1361
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1542
+ zoomScale = Math.min(3.5, zoomScale + 0.2);
1543
+ renderPage(currentPage);
1362
1544
  },
1363
1545
  zoomOut: () => {
1364
- currentZoom = Math.max(0.2, currentZoom - 0.1);
1365
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1546
+ zoomScale = Math.max(0.3, zoomScale - 0.2);
1547
+ renderPage(currentPage);
1366
1548
  },
1367
- getZoom: () => currentZoom,
1549
+ getZoom: () => zoomScale,
1368
1550
  setZoom: (level) => {
1369
- currentZoom = level;
1370
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1551
+ zoomScale = Math.max(0.3, Math.min(3.5, level));
1552
+ renderPage(currentPage);
1371
1553
  },
1372
1554
  fitToPage: () => {
1373
- currentZoom = 1;
1374
- iframe.style.transform = `scale(1) rotate(${rotation}deg)`;
1555
+ zoomScale = 1;
1556
+ rotation = 0;
1557
+ renderPage(currentPage);
1375
1558
  },
1376
1559
  rotateCW: () => {
1377
1560
  rotation = (rotation + 90) % 360;
1378
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1561
+ renderPage(currentPage);
1379
1562
  },
1380
1563
  rotateCCW: () => {
1381
1564
  rotation = (rotation - 90 + 360) % 360;
1382
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1565
+ renderPage(currentPage);
1383
1566
  },
1384
1567
  getRotation: () => rotation,
1568
+ getPageCount: () => totalPages,
1569
+ getCurrentPage: () => currentPage,
1385
1570
  goToPage: (page) => {
1386
- currentPage = page;
1387
- iframe.src = `${url}#page=${page}`;
1571
+ renderPage(page);
1388
1572
  },
1389
- getCurrentPage: () => currentPage,
1390
1573
  download: () => {
1574
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1575
+ const url = URL.createObjectURL(blob);
1391
1576
  const a = document.createElement("a");
1392
1577
  a.href = url;
1393
1578
  a.download = ctx.metadata.name || "document.pdf";
1394
1579
  a.click();
1580
+ URL.revokeObjectURL(url);
1395
1581
  },
1396
1582
  print: () => {
1397
- iframe.contentWindow?.print();
1583
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1584
+ const url = URL.createObjectURL(blob);
1585
+ const hiddenIframe = document.createElement("iframe");
1586
+ hiddenIframe.style.position = "fixed";
1587
+ hiddenIframe.style.right = "0";
1588
+ hiddenIframe.style.bottom = "0";
1589
+ hiddenIframe.style.width = "0";
1590
+ hiddenIframe.style.height = "0";
1591
+ hiddenIframe.style.border = "0";
1592
+ document.body.appendChild(hiddenIframe);
1593
+ hiddenIframe.src = url;
1594
+ hiddenIframe.onload = () => {
1595
+ setTimeout(() => {
1596
+ hiddenIframe.contentWindow?.print();
1597
+ setTimeout(() => {
1598
+ hiddenIframe.remove();
1599
+ URL.revokeObjectURL(url);
1600
+ }, 1e3);
1601
+ }, 300);
1602
+ };
1398
1603
  },
1399
1604
  getThumbnails: async () => {
1400
- return [
1401
- {
1402
- index: 1,
1403
- label: "Page 1",
1404
- render: async (canvas) => {
1405
- const context = canvas.getContext("2d");
1406
- if (context) {
1407
- context.fillStyle = "#fff";
1408
- context.fillRect(0, 0, canvas.width, canvas.height);
1409
- context.fillStyle = "#333";
1410
- context.font = "12px sans-serif";
1411
- context.fillText("PDF Preview", 10, 20);
1605
+ const thumbnails = [];
1606
+ const count = Math.min(totalPages, 50);
1607
+ for (let i = 1; i <= count; i++) {
1608
+ thumbnails.push({
1609
+ index: i,
1610
+ label: `Page ${i}`,
1611
+ render: async (thumbCanvas) => {
1612
+ try {
1613
+ const p = await pdfDoc.getPage(i);
1614
+ const baseVp = p.getViewport({ scale: 1 });
1615
+ const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1616
+ const thumbVp = p.getViewport({ scale: thumbScale });
1617
+ thumbCanvas.height = Math.floor(thumbVp.height);
1618
+ const tCtx = thumbCanvas.getContext("2d");
1619
+ if (tCtx) {
1620
+ await p.render({ canvasContext: tCtx, viewport: thumbVp }).promise;
1621
+ }
1622
+ } catch (e) {
1623
+ console.warn(`[PdfPlugin] Error generating thumbnail for page ${i}:`, e);
1412
1624
  }
1413
1625
  }
1414
- }
1415
- ];
1626
+ });
1627
+ }
1628
+ return thumbnails;
1416
1629
  }
1417
1630
  };
1631
+ return instance;
1418
1632
  }
1419
1633
  };
1420
1634
  function pdfPlugin() {
@@ -1741,7 +1955,31 @@ var DocxPlugin = class {
1741
1955
  return this.mimeTypes.includes(mime || "");
1742
1956
  }
1743
1957
  getToolbarActions(instance) {
1744
- return [
1958
+ const totalPages = instance.getPageCount?.() ?? 1;
1959
+ const actions = [];
1960
+ if (totalPages > 1) {
1961
+ actions.push({
1962
+ id: "page-nav",
1963
+ icon: "",
1964
+ label: "Page Navigation",
1965
+ type: "page-nav",
1966
+ group: "navigation",
1967
+ value: instance.getCurrentPage?.() ?? 1,
1968
+ max: totalPages,
1969
+ execute: (action, page) => {
1970
+ const cur = instance.getCurrentPage?.() ?? 1;
1971
+ const max = instance.getPageCount?.() ?? 1;
1972
+ if (action === "prev") {
1973
+ if (cur > 1) instance.goToPage?.(cur - 1);
1974
+ } else if (action === "next") {
1975
+ if (cur < max) instance.goToPage?.(cur + 1);
1976
+ } else if (typeof page === "number") {
1977
+ instance.goToPage?.(page);
1978
+ }
1979
+ }
1980
+ });
1981
+ }
1982
+ actions.push(
1745
1983
  {
1746
1984
  id: "zoom-out",
1747
1985
  icon: "zoom-out",
@@ -1766,6 +2004,14 @@ var DocxPlugin = class {
1766
2004
  group: "zoom",
1767
2005
  execute: () => instance.fitToPage?.()
1768
2006
  },
2007
+ {
2008
+ id: "rotate-cw",
2009
+ icon: "rotate-cw",
2010
+ label: "Rotate",
2011
+ type: "button",
2012
+ group: "view",
2013
+ execute: () => instance.rotateCW?.()
2014
+ },
1769
2015
  {
1770
2016
  id: "download",
1771
2017
  icon: "download",
@@ -1782,7 +2028,8 @@ var DocxPlugin = class {
1782
2028
  group: "actions",
1783
2029
  execute: () => instance.print?.()
1784
2030
  }
1785
- ];
2031
+ );
2032
+ return actions;
1786
2033
  }
1787
2034
  async render(ctx) {
1788
2035
  const wrapper = document.createElement("div");
@@ -1858,7 +2105,71 @@ var DocxPlugin = class {
1858
2105
  }
1859
2106
  }
1860
2107
  }
2108
+ const sections = wrapper.querySelectorAll("section.docx");
2109
+ const cards = wrapper.querySelectorAll(".fp-docx-page-card");
2110
+ const pageElements = sections.length > 0 ? sections : cards;
2111
+ const totalPages = Math.max(1, pageElements.length);
2112
+ let currentPage = 1;
2113
+ let indicator = null;
2114
+ if (totalPages > 1) {
2115
+ indicator = document.createElement("div");
2116
+ indicator.className = "fp-docx-page-indicator";
2117
+ indicator.style.position = "sticky";
2118
+ indicator.style.bottom = "16px";
2119
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2120
+ indicator.style.backdropFilter = "blur(8px)";
2121
+ indicator.style.color = "#f8fafc";
2122
+ indicator.style.fontSize = "12px";
2123
+ indicator.style.fontWeight = "600";
2124
+ indicator.style.padding = "5px 14px";
2125
+ indicator.style.borderRadius = "20px";
2126
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2127
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2128
+ indicator.style.zIndex = "10";
2129
+ indicator.style.userSelect = "none";
2130
+ indicator.style.pointerEvents = "none";
2131
+ indicator.style.textAlign = "center";
2132
+ indicator.style.width = "fit-content";
2133
+ indicator.style.margin = "16px auto 0";
2134
+ ctx.container.appendChild(indicator);
2135
+ }
2136
+ const showPage = (pageNum) => {
2137
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
2138
+ if (pageElements.length > 1) {
2139
+ pageElements.forEach((sec, idx) => {
2140
+ sec.style.display = idx + 1 === currentPage ? "block" : "none";
2141
+ });
2142
+ }
2143
+ if (indicator) {
2144
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2145
+ }
2146
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
2147
+ };
2148
+ if (totalPages > 1) {
2149
+ showPage(1);
2150
+ }
2151
+ scale = 1;
2152
+ let rotation = 0;
2153
+ const calculateFitScale = () => {
2154
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2155
+ const elW = activeEl.offsetWidth || 816;
2156
+ const elH = activeEl.offsetHeight || 1056;
2157
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2158
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2159
+ const sW = availW / elW;
2160
+ const sH = availH / elH;
2161
+ return Math.min(1.1, Math.min(sW, sH));
2162
+ };
2163
+ const applyTransform = () => {
2164
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2165
+ wrapper.style.transformOrigin = "top center";
2166
+ };
2167
+ setTimeout(() => {
2168
+ scale = calculateFitScale();
2169
+ applyTransform();
2170
+ }, 60);
1861
2171
  const cleanup = () => {
2172
+ indicator?.remove();
1862
2173
  for (const url of createdBlobUrls) {
1863
2174
  URL.revokeObjectURL(url);
1864
2175
  }
@@ -1869,22 +2180,36 @@ var DocxPlugin = class {
1869
2180
  ctx.signal.addEventListener("abort", cleanup);
1870
2181
  return {
1871
2182
  destroy: cleanup,
2183
+ getPageCount: () => totalPages,
2184
+ getCurrentPage: () => currentPage,
2185
+ goToPage: (page) => {
2186
+ showPage(page);
2187
+ },
1872
2188
  zoomIn: () => {
1873
- scale += 0.1;
1874
- wrapper.style.transform = `scale(${scale})`;
2189
+ scale += 0.15;
2190
+ applyTransform();
1875
2191
  },
1876
2192
  zoomOut: () => {
1877
- scale = Math.max(0.2, scale - 0.1);
1878
- wrapper.style.transform = `scale(${scale})`;
2193
+ scale = Math.max(0.2, scale - 0.15);
2194
+ applyTransform();
1879
2195
  },
1880
2196
  getZoom: () => scale,
1881
2197
  setZoom: (level) => {
1882
2198
  scale = level;
1883
- wrapper.style.transform = `scale(${scale})`;
2199
+ applyTransform();
1884
2200
  },
1885
2201
  fitToPage: () => {
1886
- scale = 1;
1887
- wrapper.style.transform = "scale(1)";
2202
+ scale = calculateFitScale();
2203
+ rotation = 0;
2204
+ applyTransform();
2205
+ },
2206
+ rotateCW: () => {
2207
+ rotation = (rotation + 90) % 360;
2208
+ applyTransform();
2209
+ },
2210
+ rotateCCW: () => {
2211
+ rotation = (rotation - 90 + 360) % 360;
2212
+ applyTransform();
1888
2213
  },
1889
2214
  download: () => {
1890
2215
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2133,7 +2458,31 @@ var ExcelPlugin = class {
2133
2458
  return this.mimeTypes.includes(mime || "");
2134
2459
  }
2135
2460
  getToolbarActions(instance) {
2136
- return [
2461
+ const totalSheets = instance.getPageCount?.() ?? 1;
2462
+ const actions = [];
2463
+ if (totalSheets > 1) {
2464
+ actions.push({
2465
+ id: "page-nav",
2466
+ icon: "",
2467
+ label: "Sheet Navigation",
2468
+ type: "page-nav",
2469
+ group: "navigation",
2470
+ value: instance.getCurrentPage?.() ?? 1,
2471
+ max: totalSheets,
2472
+ execute: (action, sheet) => {
2473
+ const cur = instance.getCurrentPage?.() ?? 1;
2474
+ const max = instance.getPageCount?.() ?? 1;
2475
+ if (action === "prev") {
2476
+ if (cur > 1) instance.goToPage?.(cur - 1);
2477
+ } else if (action === "next") {
2478
+ if (cur < max) instance.goToPage?.(cur + 1);
2479
+ } else if (typeof sheet === "number") {
2480
+ instance.goToPage?.(sheet);
2481
+ }
2482
+ }
2483
+ });
2484
+ }
2485
+ actions.push(
2137
2486
  {
2138
2487
  id: "zoom-out",
2139
2488
  icon: "zoom-out",
@@ -2155,13 +2504,23 @@ var ExcelPlugin = class {
2155
2504
  }
2156
2505
  },
2157
2506
  {
2158
- id: "page-nav",
2159
- icon: "page-nav",
2160
- label: "Sheet Navigation",
2161
- type: "page-nav",
2162
- group: "navigation",
2163
- execute: (sheet) => {
2164
- if (typeof sheet === "number") instance.goToPage?.(sheet);
2507
+ id: "fit-page",
2508
+ icon: "fit-page",
2509
+ label: "Fit to View",
2510
+ type: "button",
2511
+ group: "zoom",
2512
+ execute: () => {
2513
+ instance.fitToPage?.();
2514
+ }
2515
+ },
2516
+ {
2517
+ id: "rotate-cw",
2518
+ icon: "rotate-cw",
2519
+ label: "Rotate",
2520
+ type: "button",
2521
+ group: "view",
2522
+ execute: () => {
2523
+ instance.rotateCW?.();
2165
2524
  }
2166
2525
  },
2167
2526
  {
@@ -2184,7 +2543,8 @@ var ExcelPlugin = class {
2184
2543
  instance.print?.();
2185
2544
  }
2186
2545
  }
2187
- ];
2546
+ );
2547
+ return actions;
2188
2548
  }
2189
2549
  async render(ctx) {
2190
2550
  const container = document.createElement("div");
@@ -2213,9 +2573,23 @@ var ExcelPlugin = class {
2213
2573
  container.appendChild(tabsArea);
2214
2574
  ctx.container.appendChild(container);
2215
2575
  let scale = 1;
2576
+ let rotation = 0;
2216
2577
  let currentSheetIndex = 1;
2217
2578
  let sheetNames = [];
2218
2579
  let wb = null;
2580
+ const applyTransform = () => {
2581
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2582
+ contentArea.style.transformOrigin = "top left";
2583
+ };
2584
+ const calculateFitScale = () => {
2585
+ const table = contentArea.querySelector("table");
2586
+ if (!table) return 1;
2587
+ const availW = Math.max(200, container.clientWidth - 48);
2588
+ const availH = Math.max(200, container.clientHeight - 80);
2589
+ const tW = table.offsetWidth || 800;
2590
+ const tH = table.offsetHeight || 600;
2591
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2592
+ };
2219
2593
  try {
2220
2594
  wb = XLSX__namespace.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2221
2595
  sheetNames = wb.SheetNames || [];
@@ -2268,6 +2642,7 @@ var ExcelPlugin = class {
2268
2642
  b.style.fontWeight = "normal";
2269
2643
  }
2270
2644
  });
2645
+ ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
2271
2646
  };
2272
2647
  if (sheetNames.length > 0) {
2273
2648
  sheetNames.forEach((name, idx) => {
@@ -2302,17 +2677,30 @@ var ExcelPlugin = class {
2302
2677
  return {
2303
2678
  destroy: cleanup,
2304
2679
  zoomIn: () => {
2305
- scale += 0.1;
2306
- contentArea.style.transform = `scale(${scale})`;
2680
+ scale += 0.15;
2681
+ applyTransform();
2307
2682
  },
2308
2683
  zoomOut: () => {
2309
- scale = Math.max(0.2, scale - 0.1);
2310
- contentArea.style.transform = `scale(${scale})`;
2684
+ scale = Math.max(0.2, scale - 0.15);
2685
+ applyTransform();
2311
2686
  },
2312
2687
  getZoom: () => scale,
2313
2688
  setZoom: (level) => {
2314
2689
  scale = level;
2315
- contentArea.style.transform = `scale(${scale})`;
2690
+ applyTransform();
2691
+ },
2692
+ fitToPage: () => {
2693
+ scale = calculateFitScale();
2694
+ rotation = 0;
2695
+ applyTransform();
2696
+ },
2697
+ rotateCW: () => {
2698
+ rotation = (rotation + 90) % 360;
2699
+ applyTransform();
2700
+ },
2701
+ rotateCCW: () => {
2702
+ rotation = (rotation - 90 + 360) % 360;
2703
+ applyTransform();
2316
2704
  },
2317
2705
  goToPage: (page) => {
2318
2706
  if (page > 0 && page <= sheetNames.length) {
@@ -2517,7 +2905,31 @@ var CodePlugin = class {
2517
2905
  return false;
2518
2906
  }
2519
2907
  getToolbarActions(instance) {
2520
- return [
2908
+ const totalPages = instance.getPageCount?.() ?? 1;
2909
+ const actions = [];
2910
+ if (totalPages > 1) {
2911
+ actions.push({
2912
+ id: "page-nav",
2913
+ icon: "",
2914
+ label: "Page Navigation",
2915
+ type: "page-nav",
2916
+ group: "navigation",
2917
+ value: instance.getCurrentPage?.() ?? 1,
2918
+ max: totalPages,
2919
+ execute: (action, page) => {
2920
+ const cur = instance.getCurrentPage?.() ?? 1;
2921
+ const max = instance.getPageCount?.() ?? 1;
2922
+ if (action === "prev") {
2923
+ if (cur > 1) instance.goToPage?.(cur - 1);
2924
+ } else if (action === "next") {
2925
+ if (cur < max) instance.goToPage?.(cur + 1);
2926
+ } else if (typeof page === "number") {
2927
+ instance.goToPage?.(page);
2928
+ }
2929
+ }
2930
+ });
2931
+ }
2932
+ actions.push(
2521
2933
  {
2522
2934
  id: "zoom-out",
2523
2935
  icon: "zoom-out",
@@ -2538,6 +2950,26 @@ var CodePlugin = class {
2538
2950
  instance.zoomIn?.();
2539
2951
  }
2540
2952
  },
2953
+ {
2954
+ id: "fit-page",
2955
+ icon: "fit-page",
2956
+ label: "Fit to View",
2957
+ type: "button",
2958
+ group: "zoom",
2959
+ execute: () => {
2960
+ instance.fitToPage?.();
2961
+ }
2962
+ },
2963
+ {
2964
+ id: "rotate-cw",
2965
+ icon: "rotate-cw",
2966
+ label: "Rotate",
2967
+ type: "button",
2968
+ group: "view",
2969
+ execute: () => {
2970
+ instance.rotateCW?.();
2971
+ }
2972
+ },
2541
2973
  {
2542
2974
  id: "copy",
2543
2975
  icon: "copy",
@@ -2568,11 +3000,16 @@ var CodePlugin = class {
2568
3000
  instance.print?.();
2569
3001
  }
2570
3002
  }
2571
- ];
3003
+ );
3004
+ return actions;
2572
3005
  }
2573
3006
  async render(ctx) {
2574
3007
  const decoder = new TextDecoder("utf-8");
2575
- const text = decoder.decode(ctx.buffer);
3008
+ const fullText = decoder.decode(ctx.buffer);
3009
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
3010
+ const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3011
+ const totalPages = Math.max(1, rawPages.length);
3012
+ let currentPage = 1;
2576
3013
  const container = document.createElement("div");
2577
3014
  container.style.width = "100%";
2578
3015
  container.style.height = "100%";
@@ -2582,6 +3019,7 @@ var CodePlugin = class {
2582
3019
  container.style.padding = "16px";
2583
3020
  container.style.boxSizing = "border-box";
2584
3021
  let fontSize = 13;
3022
+ let rotation = 0;
2585
3023
  const pre = document.createElement("pre");
2586
3024
  pre.style.margin = "0";
2587
3025
  pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
@@ -2591,25 +3029,66 @@ var CodePlugin = class {
2591
3029
  pre.style.wordBreak = "break-all";
2592
3030
  const code = document.createElement("code");
2593
3031
  const ext = (ctx.metadata.extension || "").replace(".", "");
2594
- try {
2595
- if (ext && hljs__default.default.getLanguage(ext)) {
2596
- code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
2597
- } else {
2598
- code.innerHTML = hljs__default.default.highlightAuto(text).value;
3032
+ const renderCodePage = (text) => {
3033
+ try {
3034
+ if (ext && hljs__default.default.getLanguage(ext)) {
3035
+ code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
3036
+ } else {
3037
+ code.innerHTML = hljs__default.default.highlightAuto(text).value;
3038
+ }
3039
+ } catch {
3040
+ code.textContent = text;
2599
3041
  }
2600
- } catch {
2601
- code.textContent = text;
2602
- }
3042
+ };
3043
+ renderCodePage(rawPages[0] || fullText);
2603
3044
  pre.appendChild(code);
2604
3045
  container.appendChild(pre);
3046
+ let indicator = null;
3047
+ if (totalPages > 1) {
3048
+ indicator = document.createElement("div");
3049
+ indicator.className = "fp-code-page-indicator";
3050
+ indicator.style.position = "sticky";
3051
+ indicator.style.bottom = "16px";
3052
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3053
+ indicator.style.backdropFilter = "blur(8px)";
3054
+ indicator.style.color = "#f8fafc";
3055
+ indicator.style.fontSize = "12px";
3056
+ indicator.style.fontWeight = "600";
3057
+ indicator.style.padding = "5px 14px";
3058
+ indicator.style.borderRadius = "20px";
3059
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3060
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3061
+ indicator.style.zIndex = "10";
3062
+ indicator.style.userSelect = "none";
3063
+ indicator.style.pointerEvents = "none";
3064
+ indicator.style.textAlign = "center";
3065
+ indicator.style.width = "fit-content";
3066
+ indicator.style.margin = "16px auto 0";
3067
+ container.appendChild(indicator);
3068
+ }
3069
+ const showPage = (pageNum) => {
3070
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3071
+ renderCodePage(rawPages[currentPage - 1] || fullText);
3072
+ if (indicator) {
3073
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3074
+ }
3075
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3076
+ };
3077
+ if (totalPages > 1) {
3078
+ showPage(1);
3079
+ }
2605
3080
  ctx.container.appendChild(container);
2606
3081
  const cleanup = () => {
3082
+ indicator?.remove();
2607
3083
  container.remove();
2608
3084
  ctx.container.innerHTML = "";
2609
3085
  };
2610
3086
  ctx.signal.addEventListener("abort", cleanup);
2611
3087
  return {
2612
3088
  destroy: cleanup,
3089
+ getPageCount: () => totalPages,
3090
+ getCurrentPage: () => currentPage,
3091
+ goToPage: (page) => showPage(page),
2613
3092
  zoomIn: () => {
2614
3093
  fontSize = Math.min(32, fontSize + 2);
2615
3094
  pre.style.fontSize = `${fontSize}px`;
@@ -2623,6 +3102,22 @@ var CodePlugin = class {
2623
3102
  fontSize = Math.round(13 * level);
2624
3103
  pre.style.fontSize = `${fontSize}px`;
2625
3104
  },
3105
+ fitToPage: () => {
3106
+ fontSize = 13;
3107
+ rotation = 0;
3108
+ pre.style.fontSize = "13px";
3109
+ pre.style.transform = "none";
3110
+ },
3111
+ rotateCW: () => {
3112
+ rotation = (rotation + 90) % 360;
3113
+ pre.style.transform = `rotate(${rotation}deg)`;
3114
+ pre.style.transformOrigin = "top left";
3115
+ },
3116
+ rotateCCW: () => {
3117
+ rotation = (rotation - 90 + 360) % 360;
3118
+ pre.style.transform = `rotate(${rotation}deg)`;
3119
+ pre.style.transformOrigin = "top left";
3120
+ },
2626
3121
  download: () => {
2627
3122
  const mimeType = ctx.metadata.mimeType || "text/plain";
2628
3123
  const blob = new Blob([ctx.buffer], { type: mimeType });
@@ -2637,7 +3132,7 @@ var CodePlugin = class {
2637
3132
  window.print();
2638
3133
  },
2639
3134
  copy: () => {
2640
- navigator.clipboard?.writeText(text);
3135
+ navigator.clipboard?.writeText(fullText);
2641
3136
  }
2642
3137
  };
2643
3138
  }
@@ -3134,6 +3629,14 @@ var PptxPlugin = class {
3134
3629
  group: "zoom",
3135
3630
  execute: () => instance.fitToPage?.()
3136
3631
  },
3632
+ {
3633
+ id: "rotate-cw",
3634
+ icon: "rotate-cw",
3635
+ label: "Rotate",
3636
+ type: "button",
3637
+ group: "view",
3638
+ execute: () => instance.rotateCW?.()
3639
+ },
3137
3640
  {
3138
3641
  id: "download",
3139
3642
  icon: "download",
@@ -3158,6 +3661,7 @@ var PptxPlugin = class {
3158
3661
  const slideCount = renderer.slidePaths?.length || 1;
3159
3662
  let currentSlide = 1;
3160
3663
  let scale = 1;
3664
+ let rotation = 0;
3161
3665
  const wrapper = document.createElement("div");
3162
3666
  wrapper.className = "fp-pptx-wrapper";
3163
3667
  wrapper.style.cssText = `
@@ -3180,6 +3684,8 @@ var PptxPlugin = class {
3180
3684
  background: #ffffff;
3181
3685
  transform-origin: top center;
3182
3686
  transition: transform 0.2s ease;
3687
+ max-width: calc(100% - 32px);
3688
+ max-height: calc(100% - 48px);
3183
3689
  `;
3184
3690
  const canvas = document.createElement("canvas");
3185
3691
  slideContainer.appendChild(canvas);
@@ -3187,10 +3693,22 @@ var PptxPlugin = class {
3187
3693
  ctx.container.innerHTML = "";
3188
3694
  ctx.container.style.overflow = "auto";
3189
3695
  ctx.container.appendChild(wrapper);
3696
+ const calculateFitScale = () => {
3697
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3698
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3699
+ const cW = canvas.offsetWidth || 1280;
3700
+ const cH = canvas.offsetHeight || 720;
3701
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3702
+ };
3703
+ const applyTransform = () => {
3704
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3705
+ };
3190
3706
  const renderCurrentSlide = async () => {
3191
3707
  try {
3192
3708
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3193
3709
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3710
+ scale = calculateFitScale();
3711
+ applyTransform();
3194
3712
  } catch (err) {
3195
3713
  console.error("[PptxPlugin] Failed to render slide:", err);
3196
3714
  }
@@ -3213,21 +3731,30 @@ var PptxPlugin = class {
3213
3731
  getPageCount: () => slideCount,
3214
3732
  getCurrentPage: () => currentSlide,
3215
3733
  zoomIn: () => {
3216
- scale += 0.1;
3217
- slideContainer.style.transform = `scale(${scale})`;
3734
+ scale += 0.15;
3735
+ applyTransform();
3218
3736
  },
3219
3737
  zoomOut: () => {
3220
- scale = Math.max(0.2, scale - 0.1);
3221
- slideContainer.style.transform = `scale(${scale})`;
3738
+ scale = Math.max(0.2, scale - 0.15);
3739
+ applyTransform();
3222
3740
  },
3223
3741
  getZoom: () => scale,
3224
3742
  setZoom: (level) => {
3225
3743
  scale = level;
3226
- slideContainer.style.transform = `scale(${scale})`;
3744
+ applyTransform();
3227
3745
  },
3228
3746
  fitToPage: () => {
3229
- scale = 1;
3230
- slideContainer.style.transform = "scale(1)";
3747
+ scale = calculateFitScale();
3748
+ rotation = 0;
3749
+ applyTransform();
3750
+ },
3751
+ rotateCW: () => {
3752
+ rotation = (rotation + 90) % 360;
3753
+ applyTransform();
3754
+ },
3755
+ rotateCCW: () => {
3756
+ rotation = (rotation - 90 + 360) % 360;
3757
+ applyTransform();
3231
3758
  },
3232
3759
  getThumbnails: () => {
3233
3760
  const list = [];
@@ -3459,7 +3986,31 @@ var RtfPlugin = class {
3459
3986
  return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3460
3987
  }
3461
3988
  getToolbarActions(instance) {
3462
- return [
3989
+ const totalPages = instance.getPageCount?.() ?? 1;
3990
+ const actions = [];
3991
+ if (totalPages > 1) {
3992
+ actions.push({
3993
+ id: "page-nav",
3994
+ icon: "",
3995
+ label: "Page Navigation",
3996
+ type: "page-nav",
3997
+ group: "navigation",
3998
+ value: instance.getCurrentPage?.() ?? 1,
3999
+ max: totalPages,
4000
+ execute: (action, page) => {
4001
+ const cur = instance.getCurrentPage?.() ?? 1;
4002
+ const max = instance.getPageCount?.() ?? 1;
4003
+ if (action === "prev") {
4004
+ if (cur > 1) instance.goToPage?.(cur - 1);
4005
+ } else if (action === "next") {
4006
+ if (cur < max) instance.goToPage?.(cur + 1);
4007
+ } else if (typeof page === "number") {
4008
+ instance.goToPage?.(page);
4009
+ }
4010
+ }
4011
+ });
4012
+ }
4013
+ actions.push(
3463
4014
  {
3464
4015
  id: "zoom-out",
3465
4016
  icon: "zoom-out",
@@ -3484,6 +4035,14 @@ var RtfPlugin = class {
3484
4035
  group: "zoom",
3485
4036
  execute: () => instance.fitToPage?.()
3486
4037
  },
4038
+ {
4039
+ id: "rotate-cw",
4040
+ icon: "rotate-cw",
4041
+ label: "Rotate",
4042
+ type: "button",
4043
+ group: "view",
4044
+ execute: () => instance.rotateCW?.()
4045
+ },
3487
4046
  {
3488
4047
  id: "download",
3489
4048
  icon: "download",
@@ -3500,7 +4059,8 @@ var RtfPlugin = class {
3500
4059
  group: "actions",
3501
4060
  execute: () => instance.print?.()
3502
4061
  }
3503
- ];
4062
+ );
4063
+ return actions;
3504
4064
  }
3505
4065
  async render(ctx) {
3506
4066
  const wrapper = document.createElement("div");
@@ -3519,44 +4079,124 @@ var RtfPlugin = class {
3519
4079
  ctx.container.style.backgroundColor = "#f1f5f9";
3520
4080
  ctx.container.appendChild(wrapper);
3521
4081
  let scale = 1;
4082
+ let rotation = 0;
4083
+ let pageElements = [];
4084
+ const calculateFitScale = () => {
4085
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4086
+ const elW = activeEl.offsetWidth || 850;
4087
+ const elH = activeEl.offsetHeight || 1e3;
4088
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4089
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4090
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4091
+ };
4092
+ const applyTransform = () => {
4093
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4094
+ wrapper.style.transformOrigin = "top center";
4095
+ };
4096
+ setTimeout(() => {
4097
+ scale = calculateFitScale();
4098
+ applyTransform();
4099
+ }, 60);
3522
4100
  try {
3523
4101
  if (typeof RTFJS__namespace.loggingEnabled === "function") {
3524
4102
  RTFJS__namespace.loggingEnabled(false);
3525
4103
  }
3526
4104
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
3527
4105
  const htmlElements = await doc.render();
3528
- for (const el of htmlElements) {
4106
+ pageElements = htmlElements;
4107
+ for (let i = 0; i < htmlElements.length; i++) {
4108
+ const el = htmlElements[i];
4109
+ el.style.display = i === 0 ? "block" : "none";
3529
4110
  wrapper.appendChild(el);
3530
4111
  }
3531
4112
  } catch (err) {
3532
4113
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3533
4114
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3534
4115
  const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3535
- wrapper.innerHTML = `<pre style="white-space: pre-wrap; font-family: serif; color: #333;">${clean}</pre>`;
4116
+ const pre = document.createElement("pre");
4117
+ pre.style.whiteSpace = "pre-wrap";
4118
+ pre.style.fontFamily = "serif";
4119
+ pre.style.color = "#333";
4120
+ pre.textContent = clean;
4121
+ wrapper.appendChild(pre);
4122
+ pageElements = [pre];
4123
+ }
4124
+ const totalPages = Math.max(1, pageElements.length);
4125
+ let currentPage = 1;
4126
+ let indicator = null;
4127
+ if (totalPages > 1) {
4128
+ indicator = document.createElement("div");
4129
+ indicator.className = "fp-rtf-page-indicator";
4130
+ indicator.style.position = "sticky";
4131
+ indicator.style.bottom = "16px";
4132
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4133
+ indicator.style.backdropFilter = "blur(8px)";
4134
+ indicator.style.color = "#f8fafc";
4135
+ indicator.style.fontSize = "12px";
4136
+ indicator.style.fontWeight = "600";
4137
+ indicator.style.padding = "5px 14px";
4138
+ indicator.style.borderRadius = "20px";
4139
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4140
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4141
+ indicator.style.zIndex = "10";
4142
+ indicator.style.userSelect = "none";
4143
+ indicator.style.pointerEvents = "none";
4144
+ indicator.style.textAlign = "center";
4145
+ indicator.style.width = "fit-content";
4146
+ indicator.style.margin = "16px auto 0";
4147
+ ctx.container.appendChild(indicator);
4148
+ }
4149
+ const showPage = (pageNum) => {
4150
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4151
+ if (totalPages > 1) {
4152
+ pageElements.forEach((el, idx) => {
4153
+ el.style.display = idx + 1 === currentPage ? "block" : "none";
4154
+ });
4155
+ }
4156
+ if (indicator) {
4157
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4158
+ }
4159
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4160
+ };
4161
+ if (totalPages > 1) {
4162
+ showPage(1);
3536
4163
  }
3537
4164
  const cleanup = () => {
4165
+ indicator?.remove();
3538
4166
  wrapper.remove();
3539
4167
  ctx.container.innerHTML = "";
3540
4168
  };
3541
4169
  ctx.signal.addEventListener("abort", cleanup);
3542
4170
  return {
3543
4171
  destroy: cleanup,
4172
+ getPageCount: () => totalPages,
4173
+ getCurrentPage: () => currentPage,
4174
+ goToPage: (page) => showPage(page),
3544
4175
  zoomIn: () => {
3545
- scale += 0.1;
3546
- wrapper.style.transform = `scale(${scale})`;
4176
+ scale += 0.15;
4177
+ applyTransform();
3547
4178
  },
3548
4179
  zoomOut: () => {
3549
- scale = Math.max(0.2, scale - 0.1);
3550
- wrapper.style.transform = `scale(${scale})`;
4180
+ scale = Math.max(0.2, scale - 0.15);
4181
+ applyTransform();
3551
4182
  },
3552
4183
  getZoom: () => scale,
3553
4184
  setZoom: (level) => {
3554
4185
  scale = level;
3555
- wrapper.style.transform = `scale(${scale})`;
4186
+ applyTransform();
3556
4187
  },
3557
4188
  fitToPage: () => {
3558
- scale = 1;
3559
- wrapper.style.transform = "scale(1)";
4189
+ scale = calculateFitScale();
4190
+ rotation = 0;
4191
+ applyTransform();
4192
+ },
4193
+ rotateCW: () => {
4194
+ rotation = (rotation + 90) % 360;
4195
+ applyTransform();
4196
+ },
4197
+ rotateCCW: () => {
4198
+ rotation = (rotation - 90 + 360) % 360;
4199
+ applyTransform();
3560
4200
  },
3561
4201
  download: () => {
3562
4202
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3728,41 +4368,41 @@ var OpenDocumentPlugin = class {
3728
4368
  }
3729
4369
  getToolbarActions(instance) {
3730
4370
  const isPresentation = instance.isPresentation;
4371
+ const totalPages = instance.getPageCount?.() ?? 1;
3731
4372
  const actions = [];
3732
- if (isPresentation) {
3733
- actions.push(
3734
- {
4373
+ if (isPresentation || totalPages > 1) {
4374
+ if (isPresentation) {
4375
+ actions.push({
3735
4376
  id: "thumbnails",
3736
4377
  icon: "thumbnails",
3737
4378
  label: "Slide Thumbnails",
3738
4379
  type: "button",
3739
4380
  group: "navigation",
3740
4381
  execute: () => instance.toggleThumbnails?.()
3741
- },
3742
- {
3743
- id: "page-nav",
3744
- icon: "",
3745
- label: "Slide Navigation",
3746
- type: "page-nav",
3747
- group: "navigation",
3748
- value: instance.getCurrentPage?.() ?? 1,
3749
- max: instance.getPageCount?.() ?? 1,
3750
- execute: (action, page) => {
3751
- if (action === "prev") {
3752
- const cur = instance.getCurrentPage?.() ?? 1;
3753
- if (cur > 1) instance.goToPage?.(cur - 1);
3754
- } else if (action === "next") {
3755
- const cur = instance.getCurrentPage?.() ?? 1;
3756
- const total = instance.getPageCount?.() ?? 1;
3757
- if (cur < total) instance.goToPage?.(cur + 1);
3758
- } else if (typeof page === "number") {
3759
- instance.goToPage?.(page);
3760
- } else if (typeof action === "number") {
3761
- instance.goToPage?.(action);
3762
- }
4382
+ });
4383
+ }
4384
+ actions.push({
4385
+ id: "page-nav",
4386
+ icon: "",
4387
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
4388
+ type: "page-nav",
4389
+ group: "navigation",
4390
+ value: instance.getCurrentPage?.() ?? 1,
4391
+ max: totalPages,
4392
+ execute: (action, page) => {
4393
+ const cur = instance.getCurrentPage?.() ?? 1;
4394
+ const max = instance.getPageCount?.() ?? 1;
4395
+ if (action === "prev") {
4396
+ if (cur > 1) instance.goToPage?.(cur - 1);
4397
+ } else if (action === "next") {
4398
+ if (cur < max) instance.goToPage?.(cur + 1);
4399
+ } else if (typeof page === "number") {
4400
+ instance.goToPage?.(page);
4401
+ } else if (typeof action === "number") {
4402
+ instance.goToPage?.(action);
3763
4403
  }
3764
4404
  }
3765
- );
4405
+ });
3766
4406
  }
3767
4407
  actions.push(
3768
4408
  {
@@ -3784,11 +4424,19 @@ var OpenDocumentPlugin = class {
3784
4424
  {
3785
4425
  id: "fit-page",
3786
4426
  icon: "fit-page",
3787
- label: "Fit to Page",
4427
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
3788
4428
  type: "button",
3789
4429
  group: "zoom",
3790
4430
  execute: () => instance.fitToPage?.()
3791
4431
  },
4432
+ {
4433
+ id: "rotate-cw",
4434
+ icon: "rotate-cw",
4435
+ label: "Rotate",
4436
+ type: "button",
4437
+ group: "view",
4438
+ execute: () => instance.rotateCW?.()
4439
+ },
3792
4440
  {
3793
4441
  id: "download",
3794
4442
  icon: "download",
@@ -3852,6 +4500,22 @@ var OpenDocumentPlugin = class {
3852
4500
  container.appendChild(wrapper);
3853
4501
  ctx.container.appendChild(container);
3854
4502
  let scale = 1;
4503
+ let rotation = 0;
4504
+ const calculateFitScale = () => {
4505
+ const elW = wrapper.offsetWidth || 850;
4506
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4507
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4508
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4509
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4510
+ };
4511
+ const applyTransform = () => {
4512
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4513
+ wrapper.style.transformOrigin = "top center";
4514
+ };
4515
+ setTimeout(() => {
4516
+ scale = calculateFitScale();
4517
+ applyTransform();
4518
+ }, 60);
3855
4519
  let currentPage = 1;
3856
4520
  let totalPages = 1;
3857
4521
  let slides = [];
@@ -3910,21 +4574,30 @@ var OpenDocumentPlugin = class {
3910
4574
  destroy: cleanup,
3911
4575
  isPresentation,
3912
4576
  zoomIn: () => {
3913
- scale += 0.1;
3914
- wrapper.style.transform = `scale(${scale})`;
4577
+ scale += 0.15;
4578
+ applyTransform();
3915
4579
  },
3916
4580
  zoomOut: () => {
3917
- scale = Math.max(0.2, scale - 0.1);
3918
- wrapper.style.transform = `scale(${scale})`;
4581
+ scale = Math.max(0.2, scale - 0.15);
4582
+ applyTransform();
3919
4583
  },
3920
4584
  getZoom: () => scale,
3921
4585
  setZoom: (level) => {
3922
4586
  scale = level;
3923
- wrapper.style.transform = `scale(${scale})`;
4587
+ applyTransform();
3924
4588
  },
3925
4589
  fitToPage: () => {
3926
- scale = 1;
3927
- wrapper.style.transform = "scale(1)";
4590
+ scale = calculateFitScale();
4591
+ rotation = 0;
4592
+ applyTransform();
4593
+ },
4594
+ rotateCW: () => {
4595
+ rotation = (rotation + 90) % 360;
4596
+ applyTransform();
4597
+ },
4598
+ rotateCCW: () => {
4599
+ rotation = (rotation - 90 + 360) % 360;
4600
+ applyTransform();
3928
4601
  },
3929
4602
  goToPage,
3930
4603
  getPageCount: () => totalPages,
@@ -4145,7 +4818,31 @@ var DocPlugin = class {
4145
4818
  return this.mimeTypes.includes(mime || "");
4146
4819
  }
4147
4820
  getToolbarActions(instance) {
4148
- return [
4821
+ const totalPages = instance.getPageCount?.() ?? 1;
4822
+ const actions = [];
4823
+ if (totalPages > 1) {
4824
+ actions.push({
4825
+ id: "page-nav",
4826
+ icon: "",
4827
+ label: "Page Navigation",
4828
+ type: "page-nav",
4829
+ group: "navigation",
4830
+ value: instance.getCurrentPage?.() ?? 1,
4831
+ max: totalPages,
4832
+ execute: (action, page) => {
4833
+ const cur = instance.getCurrentPage?.() ?? 1;
4834
+ const max = instance.getPageCount?.() ?? 1;
4835
+ if (action === "prev") {
4836
+ if (cur > 1) instance.goToPage?.(cur - 1);
4837
+ } else if (action === "next") {
4838
+ if (cur < max) instance.goToPage?.(cur + 1);
4839
+ } else if (typeof page === "number") {
4840
+ instance.goToPage?.(page);
4841
+ }
4842
+ }
4843
+ });
4844
+ }
4845
+ actions.push(
4149
4846
  {
4150
4847
  id: "zoom-out",
4151
4848
  icon: "zoom-out",
@@ -4170,6 +4867,14 @@ var DocPlugin = class {
4170
4867
  group: "zoom",
4171
4868
  execute: () => instance.fitToPage?.()
4172
4869
  },
4870
+ {
4871
+ id: "rotate-cw",
4872
+ icon: "rotate-cw",
4873
+ label: "Rotate",
4874
+ type: "button",
4875
+ group: "view",
4876
+ execute: () => instance.rotateCW?.()
4877
+ },
4173
4878
  {
4174
4879
  id: "copy",
4175
4880
  icon: "copy",
@@ -4194,7 +4899,8 @@ var DocPlugin = class {
4194
4899
  group: "actions",
4195
4900
  execute: () => instance.print?.()
4196
4901
  }
4197
- ];
4902
+ );
4903
+ return actions;
4198
4904
  }
4199
4905
  async render(ctx) {
4200
4906
  const container = document.createElement("div");
@@ -4221,6 +4927,7 @@ var DocPlugin = class {
4221
4927
  ctx.container.appendChild(container);
4222
4928
  let scale = 1;
4223
4929
  let extractedRawText = "";
4930
+ let isFallback = false;
4224
4931
  try {
4225
4932
  const cfbf = new CfbfReader(ctx.buffer);
4226
4933
  const wordDocStream = cfbf.readStream("WordDocument");
@@ -4234,42 +4941,131 @@ var DocPlugin = class {
4234
4941
  const tableStream = cfbf.readStream(tableName);
4235
4942
  const text = this.extractDocText(wordDocStream, tableStream);
4236
4943
  extractedRawText = text;
4237
- wrapper.innerHTML = this.formatDocToHtml(text, ctx.metadata.name || "Document");
4238
4944
  } catch (err) {
4239
4945
  console.warn("[DocPlugin] Binary parsing error, fallback text:", err);
4240
4946
  const fallback = this.heuristicTextExtraction(ctx.buffer);
4241
4947
  extractedRawText = fallback;
4242
- wrapper.innerHTML = `
4243
- <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4244
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${ctx.metadata.name || "Word Document (.doc)"}</h2>
4245
- <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4246
- </div>
4247
- ${this.formatDocToHtml(fallback, ctx.metadata.name || "Document")}
4248
- `;
4948
+ isFallback = true;
4949
+ }
4950
+ const rawPages = this.splitIntoPages(extractedRawText);
4951
+ const totalPages = Math.max(1, rawPages.length);
4952
+ let currentPage = 1;
4953
+ wrapper.innerHTML = "";
4954
+ const pageCards = [];
4955
+ for (let i = 0; i < totalPages; i++) {
4956
+ const pageCard = document.createElement("div");
4957
+ pageCard.className = "fp-doc-page-card";
4958
+ pageCard.style.backgroundColor = "#ffffff";
4959
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4960
+ pageCard.style.borderRadius = "4px";
4961
+ pageCard.style.padding = "56px 48px";
4962
+ pageCard.style.minHeight = "100%";
4963
+ pageCard.style.display = i === 0 ? "block" : "none";
4964
+ if (isFallback && i === 0) {
4965
+ pageCard.innerHTML = `
4966
+ <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4967
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2__default.default.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4968
+ <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4969
+ </div>
4970
+ ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
4971
+ `;
4972
+ } else {
4973
+ pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4974
+ }
4975
+ wrapper.appendChild(pageCard);
4976
+ pageCards.push(pageCard);
4977
+ }
4978
+ let indicator = null;
4979
+ if (totalPages > 1) {
4980
+ indicator = document.createElement("div");
4981
+ indicator.className = "fp-doc-page-indicator";
4982
+ indicator.style.position = "sticky";
4983
+ indicator.style.bottom = "16px";
4984
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4985
+ indicator.style.backdropFilter = "blur(8px)";
4986
+ indicator.style.color = "#f8fafc";
4987
+ indicator.style.fontSize = "12px";
4988
+ indicator.style.fontWeight = "600";
4989
+ indicator.style.padding = "5px 14px";
4990
+ indicator.style.borderRadius = "20px";
4991
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4992
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4993
+ indicator.style.zIndex = "10";
4994
+ indicator.style.userSelect = "none";
4995
+ indicator.style.pointerEvents = "none";
4996
+ indicator.style.textAlign = "center";
4997
+ indicator.style.width = "fit-content";
4998
+ indicator.style.margin = "16px auto 0";
4999
+ container.appendChild(indicator);
5000
+ }
5001
+ const showPage = (pageNum) => {
5002
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
5003
+ if (totalPages > 1) {
5004
+ pageCards.forEach((card, idx) => {
5005
+ card.style.display = idx + 1 === currentPage ? "block" : "none";
5006
+ });
5007
+ }
5008
+ if (indicator) {
5009
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5010
+ }
5011
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
5012
+ };
5013
+ if (totalPages > 1) {
5014
+ showPage(1);
4249
5015
  }
5016
+ let rotation = 0;
5017
+ const calculateFitScale = () => {
5018
+ const activeCard = pageCards[currentPage - 1] || wrapper;
5019
+ const elW = activeCard.offsetWidth || 850;
5020
+ const elH = activeCard.offsetHeight || 1e3;
5021
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
5022
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
5023
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
5024
+ };
5025
+ const applyTransform = () => {
5026
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5027
+ wrapper.style.transformOrigin = "top center";
5028
+ };
5029
+ setTimeout(() => {
5030
+ scale = calculateFitScale();
5031
+ applyTransform();
5032
+ }, 60);
4250
5033
  const cleanup = () => {
5034
+ indicator?.remove();
4251
5035
  container.remove();
4252
5036
  ctx.container.innerHTML = "";
4253
5037
  };
4254
5038
  ctx.signal.addEventListener("abort", cleanup);
4255
5039
  return {
4256
5040
  destroy: cleanup,
5041
+ getPageCount: () => totalPages,
5042
+ getCurrentPage: () => currentPage,
5043
+ goToPage: (page) => showPage(page),
4257
5044
  zoomIn: () => {
4258
- scale += 0.1;
4259
- wrapper.style.transform = `scale(${scale})`;
5045
+ scale += 0.15;
5046
+ applyTransform();
4260
5047
  },
4261
5048
  zoomOut: () => {
4262
- scale = Math.max(0.2, scale - 0.1);
4263
- wrapper.style.transform = `scale(${scale})`;
5049
+ scale = Math.max(0.2, scale - 0.15);
5050
+ applyTransform();
4264
5051
  },
4265
5052
  getZoom: () => scale,
4266
5053
  setZoom: (level) => {
4267
5054
  scale = level;
4268
- wrapper.style.transform = `scale(${scale})`;
5055
+ applyTransform();
4269
5056
  },
4270
5057
  fitToPage: () => {
4271
- scale = 1;
4272
- wrapper.style.transform = "scale(1)";
5058
+ scale = calculateFitScale();
5059
+ rotation = 0;
5060
+ applyTransform();
5061
+ },
5062
+ rotateCW: () => {
5063
+ rotation = (rotation + 90) % 360;
5064
+ applyTransform();
5065
+ },
5066
+ rotateCCW: () => {
5067
+ rotation = (rotation - 90 + 360) % 360;
5068
+ applyTransform();
4273
5069
  },
4274
5070
  copy: () => {
4275
5071
  navigator.clipboard.writeText(extractedRawText);
@@ -4385,11 +5181,16 @@ var DocPlugin = class {
4385
5181
  heuristicTextExtraction(buffer) {
4386
5182
  return this.extractStringsFromBytes(new Uint8Array(buffer));
4387
5183
  }
5184
+ splitIntoPages(text) {
5185
+ if (!text) return [""];
5186
+ 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);
5187
+ return parts.length > 0 ? parts : [text];
5188
+ }
4388
5189
  /**
4389
5190
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
4390
5191
  */
4391
5192
  formatDocToHtml(text, filename) {
4392
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x0C/g, "\n\n").split("\n");
5193
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4393
5194
  let html = "";
4394
5195
  let inList = false;
4395
5196
  for (const rawLine of lines) {
@@ -4504,6 +5305,14 @@ var PptPlugin = class {
4504
5305
  group: "zoom",
4505
5306
  execute: () => instance.fitToPage?.()
4506
5307
  },
5308
+ {
5309
+ id: "rotate-cw",
5310
+ icon: "rotate-cw",
5311
+ label: "Rotate",
5312
+ type: "button",
5313
+ group: "view",
5314
+ execute: () => instance.rotateCW?.()
5315
+ },
4507
5316
  {
4508
5317
  id: "download",
4509
5318
  icon: "download",
@@ -4537,22 +5346,36 @@ var PptPlugin = class {
4537
5346
  const slideCard = document.createElement("div");
4538
5347
  slideCard.className = "fp-ppt-slide-card";
4539
5348
  slideCard.style.width = "960px";
4540
- slideCard.style.maxWidth = "92%";
5349
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5350
+ slideCard.style.maxHeight = "calc(100% - 48px)";
4541
5351
  slideCard.style.aspectRatio = "16 / 9";
4542
5352
  slideCard.style.backgroundColor = "#ffffff";
4543
5353
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4544
5354
  slideCard.style.borderRadius = "8px";
4545
5355
  slideCard.style.boxSizing = "border-box";
4546
5356
  slideCard.style.position = "relative";
4547
- slideCard.style.overflow = "hidden";
4548
- slideCard.style.transformOrigin = "center center";
4549
5357
  slideCard.style.transition = "transform 0.2s ease";
5358
+ slideCard.style.transformOrigin = "center center";
5359
+ slideCard.style.flexShrink = "0";
4550
5360
  container.appendChild(slideCard);
4551
5361
  ctx.container.appendChild(container);
4552
5362
  let scale = 1;
5363
+ let rotation = 0;
4553
5364
  let currentSlide = 1;
4554
5365
  let slides = [];
4555
5366
  const createdBlobUrls = [];
5367
+ const calculateFitScale = () => {
5368
+ const availW = Math.max(200, container.clientWidth - 48);
5369
+ const availH = Math.max(200, container.clientHeight - 72);
5370
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5371
+ };
5372
+ const applyTransform = () => {
5373
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5374
+ };
5375
+ setTimeout(() => {
5376
+ scale = calculateFitScale();
5377
+ applyTransform();
5378
+ }, 60);
4556
5379
  try {
4557
5380
  const cfbf = new CfbfReader(ctx.buffer);
4558
5381
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -4653,21 +5476,30 @@ var PptPlugin = class {
4653
5476
  return {
4654
5477
  destroy: cleanup,
4655
5478
  zoomIn: () => {
4656
- scale += 0.1;
4657
- slideCard.style.transform = `scale(${scale})`;
5479
+ scale += 0.15;
5480
+ applyTransform();
4658
5481
  },
4659
5482
  zoomOut: () => {
4660
- scale = Math.max(0.3, scale - 0.1);
4661
- slideCard.style.transform = `scale(${scale})`;
5483
+ scale = Math.max(0.2, scale - 0.15);
5484
+ applyTransform();
4662
5485
  },
4663
5486
  getZoom: () => scale,
4664
5487
  setZoom: (level) => {
4665
5488
  scale = level;
4666
- slideCard.style.transform = `scale(${scale})`;
5489
+ applyTransform();
4667
5490
  },
4668
5491
  fitToPage: () => {
4669
- scale = 1;
4670
- slideCard.style.transform = "scale(1)";
5492
+ scale = calculateFitScale();
5493
+ rotation = 0;
5494
+ applyTransform();
5495
+ },
5496
+ rotateCW: () => {
5497
+ rotation = (rotation + 90) % 360;
5498
+ applyTransform();
5499
+ },
5500
+ rotateCCW: () => {
5501
+ rotation = (rotation - 90 + 360) % 360;
5502
+ applyTransform();
4671
5503
  },
4672
5504
  goToPage: (page) => {
4673
5505
  if (page >= 1 && page <= totalSlides) {