@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.cjs CHANGED
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var core = require('@angular/core');
6
6
  var common = require('@angular/common');
7
7
  var DOMPurify2 = require('dompurify');
8
+ var pdfjsLib = require('pdfjs-dist');
8
9
  var docx = require('docx-preview');
9
10
  var fflate = require('fflate');
10
11
  var XLSX = require('xlsx');
@@ -38,6 +39,7 @@ function _interopNamespace(e) {
38
39
  }
39
40
 
40
41
  var DOMPurify2__default = /*#__PURE__*/_interopDefault(DOMPurify2);
42
+ var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
41
43
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
42
44
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
43
45
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
@@ -558,11 +560,21 @@ var ToolbarController = class {
558
560
  el;
559
561
  toolbarEl;
560
562
  actions = [];
563
+ pageInputEl = null;
564
+ pageLabelEl = null;
561
565
  constructor(container) {
562
566
  this.el = container;
563
567
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
564
568
  this.el.appendChild(this.toolbarEl);
565
569
  }
570
+ setPage(page, max) {
571
+ if (this.pageInputEl) {
572
+ this.pageInputEl.value = page.toString();
573
+ }
574
+ if (max !== void 0 && this.pageLabelEl) {
575
+ this.pageLabelEl.textContent = ` / ${max}`;
576
+ }
577
+ }
566
578
  update(actions) {
567
579
  this.actions = actions;
568
580
  this.render();
@@ -605,6 +617,7 @@ var ToolbarController = class {
605
617
  if (action.type === "separator") {
606
618
  groupEl.appendChild(createElement("div", { className: "fp-toolbar-separator" }));
607
619
  } else if (action.type === "page-nav") {
620
+ const max = action.max ?? 1;
608
621
  const prevBtn = this.createButton(
609
622
  "prev",
610
623
  ICON_PAGE_PREV,
@@ -623,7 +636,6 @@ var ToolbarController = class {
623
636
  "Next Page",
624
637
  () => {
625
638
  const cur = parseInt(input.value, 10) || 1;
626
- const max = action.max ?? 1;
627
639
  if (cur < max) {
628
640
  input.value = (cur + 1).toString();
629
641
  action.execute("next", cur + 1);
@@ -635,15 +647,18 @@ var ToolbarController = class {
635
647
  type: "number",
636
648
  value: (action.value ?? 1).toString(),
637
649
  min: "1",
638
- max: (action.max ?? 1).toString()
650
+ max: max.toString()
639
651
  });
640
652
  input.addEventListener("change", () => {
641
- const val = parseInt(input.value, 10);
642
- if (!isNaN(val)) {
643
- action.execute("go", val);
644
- }
653
+ let val = parseInt(input.value, 10);
654
+ if (isNaN(val)) val = 1;
655
+ val = Math.max(1, Math.min(max, val));
656
+ input.value = val.toString();
657
+ action.execute("go", val);
645
658
  });
646
- const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
659
+ const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
660
+ this.pageInputEl = input;
661
+ this.pageLabelEl = label;
647
662
  groupEl.appendChild(prevBtn);
648
663
  groupEl.appendChild(input);
649
664
  groupEl.appendChild(label);
@@ -770,6 +785,8 @@ var FilePreviewViewer = class {
770
785
  keyHandler = null;
771
786
  currentContainer = null;
772
787
  currentOptions = {};
788
+ resizeObserver = null;
789
+ fullscreenHandler = null;
773
790
  /**
774
791
  * Register a preview plugin.
775
792
  */
@@ -821,7 +838,13 @@ var FilePreviewViewer = class {
821
838
  buffer,
822
839
  options,
823
840
  signal,
824
- emit: (event, payload) => this.eventEmitter.emit(event, payload)
841
+ emit: (event, payload) => {
842
+ if (event === "page-change" && payload && typeof payload.page === "number") {
843
+ const total = payload.total ?? payload.totalPages;
844
+ this.toolbar?.setPage(payload.page, total);
845
+ }
846
+ this.eventEmitter.emit(event, payload);
847
+ }
825
848
  });
826
849
  this.activeInstance = instance;
827
850
  this.hideLoading();
@@ -836,12 +859,31 @@ var FilePreviewViewer = class {
836
859
  label: "Toggle Fullscreen",
837
860
  type: "button",
838
861
  group: "view",
839
- execute: () => {
840
- if (!document.fullscreenElement) {
841
- this.wrapperEl?.requestFullscreen?.();
842
- } else {
843
- document.exitFullscreen?.();
862
+ execute: async () => {
863
+ try {
864
+ const isNativeFs = !!document.fullscreenElement;
865
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
866
+ if (!isNativeFs && !isCssFs) {
867
+ if (this.wrapperEl?.requestFullscreen) {
868
+ await this.wrapperEl.requestFullscreen().catch(() => {
869
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
870
+ });
871
+ } else {
872
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
873
+ }
874
+ } else {
875
+ if (document.fullscreenElement) {
876
+ await document.exitFullscreen().catch(() => {
877
+ });
878
+ }
879
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
880
+ }
881
+ } catch {
882
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
844
883
  }
884
+ setTimeout(() => {
885
+ this.activeInstance?.fitToPage?.();
886
+ }, 120);
845
887
  }
846
888
  });
847
889
  }
@@ -855,6 +897,7 @@ var FilePreviewViewer = class {
855
897
  if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
856
898
  this.thumbnailPanel.update(thumbnails, (index) => {
857
899
  instance.goToPage?.(index + 1);
900
+ this.toolbar?.setPage(index + 1);
858
901
  });
859
902
  if (options.showThumbnails) {
860
903
  this.thumbnailPanel.show();
@@ -890,6 +933,15 @@ var FilePreviewViewer = class {
890
933
  window.removeEventListener("keydown", this.keyHandler);
891
934
  this.keyHandler = null;
892
935
  }
936
+ if (this.resizeObserver) {
937
+ this.resizeObserver.disconnect();
938
+ this.resizeObserver = null;
939
+ }
940
+ if (this.fullscreenHandler) {
941
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
942
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
943
+ this.fullscreenHandler = null;
944
+ }
893
945
  this.abort();
894
946
  this.destroyInstance();
895
947
  this.toolbar?.destroy();
@@ -969,6 +1021,25 @@ var FilePreviewViewer = class {
969
1021
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
970
1022
  this.setupKeyboardShortcuts();
971
1023
  this.setupDragAndDrop(container, options);
1024
+ this.setupResizeAndFullscreenListeners();
1025
+ }
1026
+ setupResizeAndFullscreenListeners() {
1027
+ if (this.fullscreenHandler) return;
1028
+ this.fullscreenHandler = () => {
1029
+ setTimeout(() => {
1030
+ this.activeInstance?.fitToPage?.();
1031
+ }, 100);
1032
+ };
1033
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
1034
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
1035
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1036
+ this.resizeObserver = new ResizeObserver(() => {
1037
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1038
+ this.activeInstance.fitToPage?.();
1039
+ }
1040
+ });
1041
+ this.resizeObserver.observe(this.contentEl);
1042
+ }
972
1043
  }
973
1044
  setupKeyboardShortcuts() {
974
1045
  if (this.keyHandler) return;
@@ -980,9 +1051,13 @@ var FilePreviewViewer = class {
980
1051
  if (e.key === "ArrowRight" || e.key === "PageDown") {
981
1052
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
982
1053
  this.activeInstance.goToPage?.(cur + 1);
1054
+ const nextCur = this.activeInstance.getCurrentPage?.() ?? cur + 1;
1055
+ this.toolbar?.setPage(nextCur);
983
1056
  } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
984
1057
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
985
1058
  this.activeInstance.goToPage?.(Math.max(1, cur - 1));
1059
+ const prevCur = this.activeInstance.getCurrentPage?.() ?? Math.max(1, cur - 1);
1060
+ this.toolbar?.setPage(prevCur);
986
1061
  } else if (e.key === "+" || e.key === "=") {
987
1062
  this.activeInstance.zoomIn?.();
988
1063
  } else if (e.key === "-" || e.key === "_") {
@@ -1248,30 +1323,62 @@ var CfbfReader = class {
1248
1323
  return result.subarray(0, targetSize);
1249
1324
  }
1250
1325
  };
1251
-
1252
- // ../plugins/pdf/dist/index.js
1326
+ if (typeof window !== "undefined" && pdfjsLib__namespace.GlobalWorkerOptions) {
1327
+ if (!pdfjsLib__namespace.GlobalWorkerOptions.workerSrc) {
1328
+ pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1329
+ }
1330
+ }
1253
1331
  var PdfPlugin = class {
1254
1332
  id = "pdf";
1255
- name = "PDF Preview";
1333
+ name = "PDF Document Preview";
1256
1334
  extensions = [".pdf"];
1257
1335
  mimeTypes = ["application/pdf"];
1258
1336
  weight = 100;
1259
1337
  supports(file) {
1260
1338
  const ext = file.metadata.extension?.toLowerCase();
1261
1339
  const mime = file.metadata.mimeType?.toLowerCase();
1262
- return ext === ".pdf" || mime === "application/pdf";
1340
+ if (ext) return this.extensions.includes(ext);
1341
+ return this.mimeTypes.includes(mime || "");
1263
1342
  }
1264
1343
  getToolbarActions(instance) {
1344
+ const totalPages = instance.getPageCount?.() ?? 1;
1345
+ const curPage = instance.getCurrentPage?.() ?? 1;
1265
1346
  return [
1347
+ {
1348
+ id: "thumbnails",
1349
+ icon: "thumbnails",
1350
+ label: "Page Thumbnails",
1351
+ type: "button",
1352
+ group: "navigation",
1353
+ execute: () => instance.toggleThumbnails?.()
1354
+ },
1355
+ {
1356
+ id: "page-nav",
1357
+ icon: "",
1358
+ label: "Page Navigation",
1359
+ type: "page-nav",
1360
+ group: "navigation",
1361
+ value: curPage,
1362
+ max: totalPages,
1363
+ execute: (action, page) => {
1364
+ const cur = instance.getCurrentPage?.() ?? 1;
1365
+ const max = instance.getPageCount?.() ?? 1;
1366
+ if (action === "prev") {
1367
+ if (cur > 1) instance.goToPage?.(cur - 1);
1368
+ } else if (action === "next") {
1369
+ if (cur < max) instance.goToPage?.(cur + 1);
1370
+ } else if (typeof page === "number") {
1371
+ instance.goToPage?.(page);
1372
+ }
1373
+ }
1374
+ },
1266
1375
  {
1267
1376
  id: "zoom-out",
1268
1377
  icon: "zoom-out",
1269
1378
  label: "Zoom Out",
1270
1379
  type: "button",
1271
1380
  group: "zoom",
1272
- execute: () => {
1273
- instance.zoomOut?.();
1274
- }
1381
+ execute: () => instance.zoomOut?.()
1275
1382
  },
1276
1383
  {
1277
1384
  id: "zoom-in",
@@ -1279,9 +1386,7 @@ var PdfPlugin = class {
1279
1386
  label: "Zoom In",
1280
1387
  type: "button",
1281
1388
  group: "zoom",
1282
- execute: () => {
1283
- instance.zoomIn?.();
1284
- }
1389
+ execute: () => instance.zoomIn?.()
1285
1390
  },
1286
1391
  {
1287
1392
  id: "fit-page",
@@ -1289,39 +1394,23 @@ var PdfPlugin = class {
1289
1394
  label: "Fit to Page",
1290
1395
  type: "button",
1291
1396
  group: "zoom",
1292
- execute: () => {
1293
- instance.fitToPage?.();
1294
- }
1397
+ execute: () => instance.fitToPage?.()
1295
1398
  },
1296
1399
  {
1297
1400
  id: "rotate-cw",
1298
1401
  icon: "rotate-cw",
1299
- label: "Rotate",
1402
+ label: "Rotate Clockwise",
1300
1403
  type: "button",
1301
1404
  group: "view",
1302
- execute: () => {
1303
- instance.rotateCW?.();
1304
- }
1305
- },
1306
- {
1307
- id: "page-nav",
1308
- icon: "page-nav",
1309
- label: "Page Navigation",
1310
- type: "page-nav",
1311
- group: "navigation",
1312
- execute: (page) => {
1313
- if (typeof page === "number") instance.goToPage?.(page);
1314
- }
1405
+ execute: () => instance.rotateCW?.()
1315
1406
  },
1316
1407
  {
1317
1408
  id: "download",
1318
1409
  icon: "download",
1319
- label: "Download",
1410
+ label: "Download PDF",
1320
1411
  type: "button",
1321
1412
  group: "actions",
1322
- execute: () => {
1323
- instance.download?.();
1324
- }
1413
+ execute: () => instance.download?.()
1325
1414
  },
1326
1415
  {
1327
1416
  id: "print",
@@ -1329,99 +1418,224 @@ var PdfPlugin = class {
1329
1418
  label: "Print",
1330
1419
  type: "button",
1331
1420
  group: "actions",
1332
- execute: () => {
1333
- instance.print?.();
1334
- }
1421
+ execute: () => instance.print?.()
1335
1422
  }
1336
1423
  ];
1337
1424
  }
1338
1425
  async render(ctx) {
1339
- const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1340
- const url = URL.createObjectURL(blob);
1341
- const wrapper = document.createElement("div");
1342
- wrapper.style.width = "100%";
1343
- wrapper.style.height = "100%";
1344
- wrapper.style.overflow = "hidden";
1345
- wrapper.style.display = "flex";
1346
- wrapper.style.justifyContent = "center";
1347
- wrapper.style.alignItems = "center";
1348
- const iframe = document.createElement("iframe");
1349
- iframe.src = url;
1350
- iframe.style.width = "100%";
1351
- iframe.style.height = "100%";
1352
- iframe.style.border = "none";
1353
- wrapper.appendChild(iframe);
1354
- ctx.container.appendChild(wrapper);
1426
+ const container = document.createElement("div");
1427
+ container.className = "fp-pdf-container";
1428
+ container.style.width = "100%";
1429
+ container.style.height = "100%";
1430
+ container.style.overflow = "auto";
1431
+ container.style.display = "flex";
1432
+ container.style.flexDirection = "column";
1433
+ container.style.alignItems = "center";
1434
+ container.style.justifyContent = "flex-start";
1435
+ container.style.padding = "20px 16px";
1436
+ container.style.backgroundColor = "#0f172a";
1437
+ container.style.boxSizing = "border-box";
1438
+ container.style.position = "relative";
1439
+ const pageCard = document.createElement("div");
1440
+ pageCard.className = "fp-pdf-page-card";
1441
+ pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
1442
+ pageCard.style.backgroundColor = "#ffffff";
1443
+ pageCard.style.borderRadius = "4px";
1444
+ pageCard.style.overflow = "hidden";
1445
+ pageCard.style.lineHeight = "0";
1446
+ pageCard.style.transition = "transform 0.15s ease";
1447
+ pageCard.style.position = "relative";
1448
+ pageCard.style.flexShrink = "0";
1449
+ let canvas = document.createElement("canvas");
1450
+ pageCard.appendChild(canvas);
1451
+ container.appendChild(pageCard);
1452
+ const indicator = document.createElement("div");
1453
+ indicator.className = "fp-pdf-page-indicator";
1454
+ indicator.style.position = "sticky";
1455
+ indicator.style.bottom = "16px";
1456
+ indicator.style.marginTop = "16px";
1457
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1458
+ indicator.style.backdropFilter = "blur(8px)";
1459
+ indicator.style.color = "#f8fafc";
1460
+ indicator.style.fontSize = "12px";
1461
+ indicator.style.fontWeight = "600";
1462
+ indicator.style.padding = "5px 14px";
1463
+ indicator.style.borderRadius = "20px";
1464
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1465
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1466
+ indicator.style.zIndex = "10";
1467
+ indicator.style.userSelect = "none";
1468
+ indicator.style.pointerEvents = "none";
1469
+ container.appendChild(indicator);
1470
+ ctx.container.appendChild(container);
1471
+ const loadingTask = pdfjsLib__namespace.getDocument({
1472
+ data: new Uint8Array(ctx.buffer),
1473
+ cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/cmaps/`,
1474
+ cMapPacked: true,
1475
+ standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/standard_fonts/`
1476
+ });
1477
+ const pdfDoc = await loadingTask.promise;
1478
+ const totalPages = Math.max(1, pdfDoc.numPages);
1355
1479
  let currentPage = 1;
1356
- let currentZoom = 1;
1480
+ let zoomScale = 1;
1357
1481
  let rotation = 0;
1482
+ let currentRenderTask = null;
1483
+ const renderPage = async (pageNum) => {
1484
+ if (currentRenderTask) {
1485
+ try {
1486
+ currentRenderTask.cancel();
1487
+ } catch {
1488
+ }
1489
+ currentRenderTask = null;
1490
+ }
1491
+ const newCanvas = document.createElement("canvas");
1492
+ pageCard.replaceChild(newCanvas, canvas);
1493
+ canvas = newCanvas;
1494
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
1495
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1496
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
1497
+ const page = await pdfDoc.getPage(currentPage);
1498
+ const containerWidth = container.clientWidth || 900;
1499
+ const containerHeight = container.clientHeight || 700;
1500
+ const unscaledVp = page.getViewport({ scale: 1, rotation });
1501
+ const availWidth = Math.max(100, containerWidth - 48);
1502
+ const availHeight = Math.max(100, containerHeight - 88);
1503
+ const scaleW = availWidth / unscaledVp.width;
1504
+ const scaleH = availHeight / unscaledVp.height;
1505
+ const fitScale = Math.min(scaleW, scaleH);
1506
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1507
+ const pixelRatio = window.devicePixelRatio || 1;
1508
+ const viewport = page.getViewport({ scale: effectiveScale, rotation });
1509
+ canvas.width = Math.floor(viewport.width * pixelRatio);
1510
+ canvas.height = Math.floor(viewport.height * pixelRatio);
1511
+ canvas.style.width = `${Math.floor(viewport.width)}px`;
1512
+ canvas.style.height = `${Math.floor(viewport.height)}px`;
1513
+ const canvasCtx = canvas.getContext("2d");
1514
+ if (!canvasCtx) return;
1515
+ canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
1516
+ currentRenderTask = page.render({
1517
+ canvasContext: canvasCtx,
1518
+ viewport
1519
+ });
1520
+ try {
1521
+ await currentRenderTask.promise;
1522
+ } catch (err) {
1523
+ if (err?.name !== "RenderingCancelledException") {
1524
+ console.warn("[PdfPlugin] Page render warning:", err);
1525
+ }
1526
+ } finally {
1527
+ currentRenderTask = null;
1528
+ }
1529
+ };
1530
+ await renderPage(1);
1358
1531
  const cleanup = () => {
1359
- URL.revokeObjectURL(url);
1360
- wrapper.remove();
1532
+ if (currentRenderTask) {
1533
+ try {
1534
+ currentRenderTask.cancel();
1535
+ } catch {
1536
+ }
1537
+ }
1538
+ try {
1539
+ pdfDoc.destroy();
1540
+ } catch {
1541
+ }
1542
+ container.remove();
1361
1543
  ctx.container.innerHTML = "";
1362
1544
  };
1363
1545
  ctx.signal.addEventListener("abort", cleanup);
1364
- return {
1546
+ const instance = {
1365
1547
  destroy: cleanup,
1366
1548
  zoomIn: () => {
1367
- currentZoom += 0.1;
1368
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1549
+ zoomScale = Math.min(3.5, zoomScale + 0.2);
1550
+ renderPage(currentPage);
1369
1551
  },
1370
1552
  zoomOut: () => {
1371
- currentZoom = Math.max(0.2, currentZoom - 0.1);
1372
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1553
+ zoomScale = Math.max(0.3, zoomScale - 0.2);
1554
+ renderPage(currentPage);
1373
1555
  },
1374
- getZoom: () => currentZoom,
1556
+ getZoom: () => zoomScale,
1375
1557
  setZoom: (level) => {
1376
- currentZoom = level;
1377
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1558
+ zoomScale = Math.max(0.3, Math.min(3.5, level));
1559
+ renderPage(currentPage);
1378
1560
  },
1379
1561
  fitToPage: () => {
1380
- currentZoom = 1;
1381
- iframe.style.transform = `scale(1) rotate(${rotation}deg)`;
1562
+ zoomScale = 1;
1563
+ rotation = 0;
1564
+ renderPage(currentPage);
1382
1565
  },
1383
1566
  rotateCW: () => {
1384
1567
  rotation = (rotation + 90) % 360;
1385
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1568
+ renderPage(currentPage);
1386
1569
  },
1387
1570
  rotateCCW: () => {
1388
1571
  rotation = (rotation - 90 + 360) % 360;
1389
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1572
+ renderPage(currentPage);
1390
1573
  },
1391
1574
  getRotation: () => rotation,
1575
+ getPageCount: () => totalPages,
1576
+ getCurrentPage: () => currentPage,
1392
1577
  goToPage: (page) => {
1393
- currentPage = page;
1394
- iframe.src = `${url}#page=${page}`;
1578
+ renderPage(page);
1395
1579
  },
1396
- getCurrentPage: () => currentPage,
1397
1580
  download: () => {
1581
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1582
+ const url = URL.createObjectURL(blob);
1398
1583
  const a = document.createElement("a");
1399
1584
  a.href = url;
1400
1585
  a.download = ctx.metadata.name || "document.pdf";
1401
1586
  a.click();
1587
+ URL.revokeObjectURL(url);
1402
1588
  },
1403
1589
  print: () => {
1404
- iframe.contentWindow?.print();
1590
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1591
+ const url = URL.createObjectURL(blob);
1592
+ const hiddenIframe = document.createElement("iframe");
1593
+ hiddenIframe.style.position = "fixed";
1594
+ hiddenIframe.style.right = "0";
1595
+ hiddenIframe.style.bottom = "0";
1596
+ hiddenIframe.style.width = "0";
1597
+ hiddenIframe.style.height = "0";
1598
+ hiddenIframe.style.border = "0";
1599
+ document.body.appendChild(hiddenIframe);
1600
+ hiddenIframe.src = url;
1601
+ hiddenIframe.onload = () => {
1602
+ setTimeout(() => {
1603
+ hiddenIframe.contentWindow?.print();
1604
+ setTimeout(() => {
1605
+ hiddenIframe.remove();
1606
+ URL.revokeObjectURL(url);
1607
+ }, 1e3);
1608
+ }, 300);
1609
+ };
1405
1610
  },
1406
1611
  getThumbnails: async () => {
1407
- return [
1408
- {
1409
- index: 1,
1410
- label: "Page 1",
1411
- render: async (canvas) => {
1412
- const context = canvas.getContext("2d");
1413
- if (context) {
1414
- context.fillStyle = "#fff";
1415
- context.fillRect(0, 0, canvas.width, canvas.height);
1416
- context.fillStyle = "#333";
1417
- context.font = "12px sans-serif";
1418
- context.fillText("PDF Preview", 10, 20);
1612
+ const thumbnails = [];
1613
+ const count = Math.min(totalPages, 50);
1614
+ for (let i = 1; i <= count; i++) {
1615
+ thumbnails.push({
1616
+ index: i,
1617
+ label: `Page ${i}`,
1618
+ render: async (thumbCanvas) => {
1619
+ try {
1620
+ const p = await pdfDoc.getPage(i);
1621
+ const baseVp = p.getViewport({ scale: 1 });
1622
+ const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1623
+ const thumbVp = p.getViewport({ scale: thumbScale });
1624
+ thumbCanvas.height = Math.floor(thumbVp.height);
1625
+ const tCtx = thumbCanvas.getContext("2d");
1626
+ if (tCtx) {
1627
+ await p.render({ canvasContext: tCtx, viewport: thumbVp }).promise;
1628
+ }
1629
+ } catch (e) {
1630
+ console.warn(`[PdfPlugin] Error generating thumbnail for page ${i}:`, e);
1419
1631
  }
1420
1632
  }
1421
- }
1422
- ];
1633
+ });
1634
+ }
1635
+ return thumbnails;
1423
1636
  }
1424
1637
  };
1638
+ return instance;
1425
1639
  }
1426
1640
  };
1427
1641
  function pdfPlugin() {
@@ -1748,7 +1962,31 @@ var DocxPlugin = class {
1748
1962
  return this.mimeTypes.includes(mime || "");
1749
1963
  }
1750
1964
  getToolbarActions(instance) {
1751
- return [
1965
+ const totalPages = instance.getPageCount?.() ?? 1;
1966
+ const actions = [];
1967
+ if (totalPages > 1) {
1968
+ actions.push({
1969
+ id: "page-nav",
1970
+ icon: "",
1971
+ label: "Page Navigation",
1972
+ type: "page-nav",
1973
+ group: "navigation",
1974
+ value: instance.getCurrentPage?.() ?? 1,
1975
+ max: totalPages,
1976
+ execute: (action, page) => {
1977
+ const cur = instance.getCurrentPage?.() ?? 1;
1978
+ const max = instance.getPageCount?.() ?? 1;
1979
+ if (action === "prev") {
1980
+ if (cur > 1) instance.goToPage?.(cur - 1);
1981
+ } else if (action === "next") {
1982
+ if (cur < max) instance.goToPage?.(cur + 1);
1983
+ } else if (typeof page === "number") {
1984
+ instance.goToPage?.(page);
1985
+ }
1986
+ }
1987
+ });
1988
+ }
1989
+ actions.push(
1752
1990
  {
1753
1991
  id: "zoom-out",
1754
1992
  icon: "zoom-out",
@@ -1773,6 +2011,14 @@ var DocxPlugin = class {
1773
2011
  group: "zoom",
1774
2012
  execute: () => instance.fitToPage?.()
1775
2013
  },
2014
+ {
2015
+ id: "rotate-cw",
2016
+ icon: "rotate-cw",
2017
+ label: "Rotate",
2018
+ type: "button",
2019
+ group: "view",
2020
+ execute: () => instance.rotateCW?.()
2021
+ },
1776
2022
  {
1777
2023
  id: "download",
1778
2024
  icon: "download",
@@ -1789,7 +2035,8 @@ var DocxPlugin = class {
1789
2035
  group: "actions",
1790
2036
  execute: () => instance.print?.()
1791
2037
  }
1792
- ];
2038
+ );
2039
+ return actions;
1793
2040
  }
1794
2041
  async render(ctx) {
1795
2042
  const wrapper = document.createElement("div");
@@ -1865,7 +2112,71 @@ var DocxPlugin = class {
1865
2112
  }
1866
2113
  }
1867
2114
  }
2115
+ const sections = wrapper.querySelectorAll("section.docx");
2116
+ const cards = wrapper.querySelectorAll(".fp-docx-page-card");
2117
+ const pageElements = sections.length > 0 ? sections : cards;
2118
+ const totalPages = Math.max(1, pageElements.length);
2119
+ let currentPage = 1;
2120
+ let indicator = null;
2121
+ if (totalPages > 1) {
2122
+ indicator = document.createElement("div");
2123
+ indicator.className = "fp-docx-page-indicator";
2124
+ indicator.style.position = "sticky";
2125
+ indicator.style.bottom = "16px";
2126
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2127
+ indicator.style.backdropFilter = "blur(8px)";
2128
+ indicator.style.color = "#f8fafc";
2129
+ indicator.style.fontSize = "12px";
2130
+ indicator.style.fontWeight = "600";
2131
+ indicator.style.padding = "5px 14px";
2132
+ indicator.style.borderRadius = "20px";
2133
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2134
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2135
+ indicator.style.zIndex = "10";
2136
+ indicator.style.userSelect = "none";
2137
+ indicator.style.pointerEvents = "none";
2138
+ indicator.style.textAlign = "center";
2139
+ indicator.style.width = "fit-content";
2140
+ indicator.style.margin = "16px auto 0";
2141
+ ctx.container.appendChild(indicator);
2142
+ }
2143
+ const showPage = (pageNum) => {
2144
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
2145
+ if (pageElements.length > 1) {
2146
+ pageElements.forEach((sec, idx) => {
2147
+ sec.style.display = idx + 1 === currentPage ? "block" : "none";
2148
+ });
2149
+ }
2150
+ if (indicator) {
2151
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2152
+ }
2153
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
2154
+ };
2155
+ if (totalPages > 1) {
2156
+ showPage(1);
2157
+ }
2158
+ scale = 1;
2159
+ let rotation = 0;
2160
+ const calculateFitScale = () => {
2161
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2162
+ const elW = activeEl.offsetWidth || 816;
2163
+ const elH = activeEl.offsetHeight || 1056;
2164
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2165
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2166
+ const sW = availW / elW;
2167
+ const sH = availH / elH;
2168
+ return Math.min(1.1, Math.min(sW, sH));
2169
+ };
2170
+ const applyTransform = () => {
2171
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2172
+ wrapper.style.transformOrigin = "top center";
2173
+ };
2174
+ setTimeout(() => {
2175
+ scale = calculateFitScale();
2176
+ applyTransform();
2177
+ }, 60);
1868
2178
  const cleanup = () => {
2179
+ indicator?.remove();
1869
2180
  for (const url of createdBlobUrls) {
1870
2181
  URL.revokeObjectURL(url);
1871
2182
  }
@@ -1876,22 +2187,36 @@ var DocxPlugin = class {
1876
2187
  ctx.signal.addEventListener("abort", cleanup);
1877
2188
  return {
1878
2189
  destroy: cleanup,
2190
+ getPageCount: () => totalPages,
2191
+ getCurrentPage: () => currentPage,
2192
+ goToPage: (page) => {
2193
+ showPage(page);
2194
+ },
1879
2195
  zoomIn: () => {
1880
- scale += 0.1;
1881
- wrapper.style.transform = `scale(${scale})`;
2196
+ scale += 0.15;
2197
+ applyTransform();
1882
2198
  },
1883
2199
  zoomOut: () => {
1884
- scale = Math.max(0.2, scale - 0.1);
1885
- wrapper.style.transform = `scale(${scale})`;
2200
+ scale = Math.max(0.2, scale - 0.15);
2201
+ applyTransform();
1886
2202
  },
1887
2203
  getZoom: () => scale,
1888
2204
  setZoom: (level) => {
1889
2205
  scale = level;
1890
- wrapper.style.transform = `scale(${scale})`;
2206
+ applyTransform();
1891
2207
  },
1892
2208
  fitToPage: () => {
1893
- scale = 1;
1894
- wrapper.style.transform = "scale(1)";
2209
+ scale = calculateFitScale();
2210
+ rotation = 0;
2211
+ applyTransform();
2212
+ },
2213
+ rotateCW: () => {
2214
+ rotation = (rotation + 90) % 360;
2215
+ applyTransform();
2216
+ },
2217
+ rotateCCW: () => {
2218
+ rotation = (rotation - 90 + 360) % 360;
2219
+ applyTransform();
1895
2220
  },
1896
2221
  download: () => {
1897
2222
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2140,7 +2465,31 @@ var ExcelPlugin = class {
2140
2465
  return this.mimeTypes.includes(mime || "");
2141
2466
  }
2142
2467
  getToolbarActions(instance) {
2143
- return [
2468
+ const totalSheets = instance.getPageCount?.() ?? 1;
2469
+ const actions = [];
2470
+ if (totalSheets > 1) {
2471
+ actions.push({
2472
+ id: "page-nav",
2473
+ icon: "",
2474
+ label: "Sheet Navigation",
2475
+ type: "page-nav",
2476
+ group: "navigation",
2477
+ value: instance.getCurrentPage?.() ?? 1,
2478
+ max: totalSheets,
2479
+ execute: (action, sheet) => {
2480
+ const cur = instance.getCurrentPage?.() ?? 1;
2481
+ const max = instance.getPageCount?.() ?? 1;
2482
+ if (action === "prev") {
2483
+ if (cur > 1) instance.goToPage?.(cur - 1);
2484
+ } else if (action === "next") {
2485
+ if (cur < max) instance.goToPage?.(cur + 1);
2486
+ } else if (typeof sheet === "number") {
2487
+ instance.goToPage?.(sheet);
2488
+ }
2489
+ }
2490
+ });
2491
+ }
2492
+ actions.push(
2144
2493
  {
2145
2494
  id: "zoom-out",
2146
2495
  icon: "zoom-out",
@@ -2162,13 +2511,23 @@ var ExcelPlugin = class {
2162
2511
  }
2163
2512
  },
2164
2513
  {
2165
- id: "page-nav",
2166
- icon: "page-nav",
2167
- label: "Sheet Navigation",
2168
- type: "page-nav",
2169
- group: "navigation",
2170
- execute: (sheet) => {
2171
- if (typeof sheet === "number") instance.goToPage?.(sheet);
2514
+ id: "fit-page",
2515
+ icon: "fit-page",
2516
+ label: "Fit to View",
2517
+ type: "button",
2518
+ group: "zoom",
2519
+ execute: () => {
2520
+ instance.fitToPage?.();
2521
+ }
2522
+ },
2523
+ {
2524
+ id: "rotate-cw",
2525
+ icon: "rotate-cw",
2526
+ label: "Rotate",
2527
+ type: "button",
2528
+ group: "view",
2529
+ execute: () => {
2530
+ instance.rotateCW?.();
2172
2531
  }
2173
2532
  },
2174
2533
  {
@@ -2191,7 +2550,8 @@ var ExcelPlugin = class {
2191
2550
  instance.print?.();
2192
2551
  }
2193
2552
  }
2194
- ];
2553
+ );
2554
+ return actions;
2195
2555
  }
2196
2556
  async render(ctx) {
2197
2557
  const container = document.createElement("div");
@@ -2220,9 +2580,23 @@ var ExcelPlugin = class {
2220
2580
  container.appendChild(tabsArea);
2221
2581
  ctx.container.appendChild(container);
2222
2582
  let scale = 1;
2583
+ let rotation = 0;
2223
2584
  let currentSheetIndex = 1;
2224
2585
  let sheetNames = [];
2225
2586
  let wb = null;
2587
+ const applyTransform = () => {
2588
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2589
+ contentArea.style.transformOrigin = "top left";
2590
+ };
2591
+ const calculateFitScale = () => {
2592
+ const table = contentArea.querySelector("table");
2593
+ if (!table) return 1;
2594
+ const availW = Math.max(200, container.clientWidth - 48);
2595
+ const availH = Math.max(200, container.clientHeight - 80);
2596
+ const tW = table.offsetWidth || 800;
2597
+ const tH = table.offsetHeight || 600;
2598
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2599
+ };
2226
2600
  try {
2227
2601
  wb = XLSX__namespace.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2228
2602
  sheetNames = wb.SheetNames || [];
@@ -2275,6 +2649,7 @@ var ExcelPlugin = class {
2275
2649
  b.style.fontWeight = "normal";
2276
2650
  }
2277
2651
  });
2652
+ ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
2278
2653
  };
2279
2654
  if (sheetNames.length > 0) {
2280
2655
  sheetNames.forEach((name, idx) => {
@@ -2309,17 +2684,30 @@ var ExcelPlugin = class {
2309
2684
  return {
2310
2685
  destroy: cleanup,
2311
2686
  zoomIn: () => {
2312
- scale += 0.1;
2313
- contentArea.style.transform = `scale(${scale})`;
2687
+ scale += 0.15;
2688
+ applyTransform();
2314
2689
  },
2315
2690
  zoomOut: () => {
2316
- scale = Math.max(0.2, scale - 0.1);
2317
- contentArea.style.transform = `scale(${scale})`;
2691
+ scale = Math.max(0.2, scale - 0.15);
2692
+ applyTransform();
2318
2693
  },
2319
2694
  getZoom: () => scale,
2320
2695
  setZoom: (level) => {
2321
2696
  scale = level;
2322
- contentArea.style.transform = `scale(${scale})`;
2697
+ applyTransform();
2698
+ },
2699
+ fitToPage: () => {
2700
+ scale = calculateFitScale();
2701
+ rotation = 0;
2702
+ applyTransform();
2703
+ },
2704
+ rotateCW: () => {
2705
+ rotation = (rotation + 90) % 360;
2706
+ applyTransform();
2707
+ },
2708
+ rotateCCW: () => {
2709
+ rotation = (rotation - 90 + 360) % 360;
2710
+ applyTransform();
2323
2711
  },
2324
2712
  goToPage: (page) => {
2325
2713
  if (page > 0 && page <= sheetNames.length) {
@@ -2524,7 +2912,31 @@ var CodePlugin = class {
2524
2912
  return false;
2525
2913
  }
2526
2914
  getToolbarActions(instance) {
2527
- return [
2915
+ const totalPages = instance.getPageCount?.() ?? 1;
2916
+ const actions = [];
2917
+ if (totalPages > 1) {
2918
+ actions.push({
2919
+ id: "page-nav",
2920
+ icon: "",
2921
+ label: "Page Navigation",
2922
+ type: "page-nav",
2923
+ group: "navigation",
2924
+ value: instance.getCurrentPage?.() ?? 1,
2925
+ max: totalPages,
2926
+ execute: (action, page) => {
2927
+ const cur = instance.getCurrentPage?.() ?? 1;
2928
+ const max = instance.getPageCount?.() ?? 1;
2929
+ if (action === "prev") {
2930
+ if (cur > 1) instance.goToPage?.(cur - 1);
2931
+ } else if (action === "next") {
2932
+ if (cur < max) instance.goToPage?.(cur + 1);
2933
+ } else if (typeof page === "number") {
2934
+ instance.goToPage?.(page);
2935
+ }
2936
+ }
2937
+ });
2938
+ }
2939
+ actions.push(
2528
2940
  {
2529
2941
  id: "zoom-out",
2530
2942
  icon: "zoom-out",
@@ -2545,6 +2957,26 @@ var CodePlugin = class {
2545
2957
  instance.zoomIn?.();
2546
2958
  }
2547
2959
  },
2960
+ {
2961
+ id: "fit-page",
2962
+ icon: "fit-page",
2963
+ label: "Fit to View",
2964
+ type: "button",
2965
+ group: "zoom",
2966
+ execute: () => {
2967
+ instance.fitToPage?.();
2968
+ }
2969
+ },
2970
+ {
2971
+ id: "rotate-cw",
2972
+ icon: "rotate-cw",
2973
+ label: "Rotate",
2974
+ type: "button",
2975
+ group: "view",
2976
+ execute: () => {
2977
+ instance.rotateCW?.();
2978
+ }
2979
+ },
2548
2980
  {
2549
2981
  id: "copy",
2550
2982
  icon: "copy",
@@ -2575,11 +3007,16 @@ var CodePlugin = class {
2575
3007
  instance.print?.();
2576
3008
  }
2577
3009
  }
2578
- ];
3010
+ );
3011
+ return actions;
2579
3012
  }
2580
3013
  async render(ctx) {
2581
3014
  const decoder = new TextDecoder("utf-8");
2582
- const text = decoder.decode(ctx.buffer);
3015
+ const fullText = decoder.decode(ctx.buffer);
3016
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
3017
+ const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
3018
+ const totalPages = Math.max(1, rawPages.length);
3019
+ let currentPage = 1;
2583
3020
  const container = document.createElement("div");
2584
3021
  container.style.width = "100%";
2585
3022
  container.style.height = "100%";
@@ -2589,6 +3026,7 @@ var CodePlugin = class {
2589
3026
  container.style.padding = "16px";
2590
3027
  container.style.boxSizing = "border-box";
2591
3028
  let fontSize = 13;
3029
+ let rotation = 0;
2592
3030
  const pre = document.createElement("pre");
2593
3031
  pre.style.margin = "0";
2594
3032
  pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
@@ -2598,25 +3036,66 @@ var CodePlugin = class {
2598
3036
  pre.style.wordBreak = "break-all";
2599
3037
  const code = document.createElement("code");
2600
3038
  const ext = (ctx.metadata.extension || "").replace(".", "");
2601
- try {
2602
- if (ext && hljs__default.default.getLanguage(ext)) {
2603
- code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
2604
- } else {
2605
- code.innerHTML = hljs__default.default.highlightAuto(text).value;
3039
+ const renderCodePage = (text) => {
3040
+ try {
3041
+ if (ext && hljs__default.default.getLanguage(ext)) {
3042
+ code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
3043
+ } else {
3044
+ code.innerHTML = hljs__default.default.highlightAuto(text).value;
3045
+ }
3046
+ } catch {
3047
+ code.textContent = text;
2606
3048
  }
2607
- } catch {
2608
- code.textContent = text;
2609
- }
3049
+ };
3050
+ renderCodePage(rawPages[0] || fullText);
2610
3051
  pre.appendChild(code);
2611
3052
  container.appendChild(pre);
3053
+ let indicator = null;
3054
+ if (totalPages > 1) {
3055
+ indicator = document.createElement("div");
3056
+ indicator.className = "fp-code-page-indicator";
3057
+ indicator.style.position = "sticky";
3058
+ indicator.style.bottom = "16px";
3059
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3060
+ indicator.style.backdropFilter = "blur(8px)";
3061
+ indicator.style.color = "#f8fafc";
3062
+ indicator.style.fontSize = "12px";
3063
+ indicator.style.fontWeight = "600";
3064
+ indicator.style.padding = "5px 14px";
3065
+ indicator.style.borderRadius = "20px";
3066
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3067
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3068
+ indicator.style.zIndex = "10";
3069
+ indicator.style.userSelect = "none";
3070
+ indicator.style.pointerEvents = "none";
3071
+ indicator.style.textAlign = "center";
3072
+ indicator.style.width = "fit-content";
3073
+ indicator.style.margin = "16px auto 0";
3074
+ container.appendChild(indicator);
3075
+ }
3076
+ const showPage = (pageNum) => {
3077
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3078
+ renderCodePage(rawPages[currentPage - 1] || fullText);
3079
+ if (indicator) {
3080
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3081
+ }
3082
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3083
+ };
3084
+ if (totalPages > 1) {
3085
+ showPage(1);
3086
+ }
2612
3087
  ctx.container.appendChild(container);
2613
3088
  const cleanup = () => {
3089
+ indicator?.remove();
2614
3090
  container.remove();
2615
3091
  ctx.container.innerHTML = "";
2616
3092
  };
2617
3093
  ctx.signal.addEventListener("abort", cleanup);
2618
3094
  return {
2619
3095
  destroy: cleanup,
3096
+ getPageCount: () => totalPages,
3097
+ getCurrentPage: () => currentPage,
3098
+ goToPage: (page) => showPage(page),
2620
3099
  zoomIn: () => {
2621
3100
  fontSize = Math.min(32, fontSize + 2);
2622
3101
  pre.style.fontSize = `${fontSize}px`;
@@ -2630,6 +3109,22 @@ var CodePlugin = class {
2630
3109
  fontSize = Math.round(13 * level);
2631
3110
  pre.style.fontSize = `${fontSize}px`;
2632
3111
  },
3112
+ fitToPage: () => {
3113
+ fontSize = 13;
3114
+ rotation = 0;
3115
+ pre.style.fontSize = "13px";
3116
+ pre.style.transform = "none";
3117
+ },
3118
+ rotateCW: () => {
3119
+ rotation = (rotation + 90) % 360;
3120
+ pre.style.transform = `rotate(${rotation}deg)`;
3121
+ pre.style.transformOrigin = "top left";
3122
+ },
3123
+ rotateCCW: () => {
3124
+ rotation = (rotation - 90 + 360) % 360;
3125
+ pre.style.transform = `rotate(${rotation}deg)`;
3126
+ pre.style.transformOrigin = "top left";
3127
+ },
2633
3128
  download: () => {
2634
3129
  const mimeType = ctx.metadata.mimeType || "text/plain";
2635
3130
  const blob = new Blob([ctx.buffer], { type: mimeType });
@@ -2644,7 +3139,7 @@ var CodePlugin = class {
2644
3139
  window.print();
2645
3140
  },
2646
3141
  copy: () => {
2647
- navigator.clipboard?.writeText(text);
3142
+ navigator.clipboard?.writeText(fullText);
2648
3143
  }
2649
3144
  };
2650
3145
  }
@@ -3141,6 +3636,14 @@ var PptxPlugin = class {
3141
3636
  group: "zoom",
3142
3637
  execute: () => instance.fitToPage?.()
3143
3638
  },
3639
+ {
3640
+ id: "rotate-cw",
3641
+ icon: "rotate-cw",
3642
+ label: "Rotate",
3643
+ type: "button",
3644
+ group: "view",
3645
+ execute: () => instance.rotateCW?.()
3646
+ },
3144
3647
  {
3145
3648
  id: "download",
3146
3649
  icon: "download",
@@ -3165,6 +3668,7 @@ var PptxPlugin = class {
3165
3668
  const slideCount = renderer.slidePaths?.length || 1;
3166
3669
  let currentSlide = 1;
3167
3670
  let scale = 1;
3671
+ let rotation = 0;
3168
3672
  const wrapper = document.createElement("div");
3169
3673
  wrapper.className = "fp-pptx-wrapper";
3170
3674
  wrapper.style.cssText = `
@@ -3187,6 +3691,8 @@ var PptxPlugin = class {
3187
3691
  background: #ffffff;
3188
3692
  transform-origin: top center;
3189
3693
  transition: transform 0.2s ease;
3694
+ max-width: calc(100% - 32px);
3695
+ max-height: calc(100% - 48px);
3190
3696
  `;
3191
3697
  const canvas = document.createElement("canvas");
3192
3698
  slideContainer.appendChild(canvas);
@@ -3194,10 +3700,22 @@ var PptxPlugin = class {
3194
3700
  ctx.container.innerHTML = "";
3195
3701
  ctx.container.style.overflow = "auto";
3196
3702
  ctx.container.appendChild(wrapper);
3703
+ const calculateFitScale = () => {
3704
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3705
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3706
+ const cW = canvas.offsetWidth || 1280;
3707
+ const cH = canvas.offsetHeight || 720;
3708
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3709
+ };
3710
+ const applyTransform = () => {
3711
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3712
+ };
3197
3713
  const renderCurrentSlide = async () => {
3198
3714
  try {
3199
3715
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3200
3716
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3717
+ scale = calculateFitScale();
3718
+ applyTransform();
3201
3719
  } catch (err) {
3202
3720
  console.error("[PptxPlugin] Failed to render slide:", err);
3203
3721
  }
@@ -3220,21 +3738,30 @@ var PptxPlugin = class {
3220
3738
  getPageCount: () => slideCount,
3221
3739
  getCurrentPage: () => currentSlide,
3222
3740
  zoomIn: () => {
3223
- scale += 0.1;
3224
- slideContainer.style.transform = `scale(${scale})`;
3741
+ scale += 0.15;
3742
+ applyTransform();
3225
3743
  },
3226
3744
  zoomOut: () => {
3227
- scale = Math.max(0.2, scale - 0.1);
3228
- slideContainer.style.transform = `scale(${scale})`;
3745
+ scale = Math.max(0.2, scale - 0.15);
3746
+ applyTransform();
3229
3747
  },
3230
3748
  getZoom: () => scale,
3231
3749
  setZoom: (level) => {
3232
3750
  scale = level;
3233
- slideContainer.style.transform = `scale(${scale})`;
3751
+ applyTransform();
3234
3752
  },
3235
3753
  fitToPage: () => {
3236
- scale = 1;
3237
- slideContainer.style.transform = "scale(1)";
3754
+ scale = calculateFitScale();
3755
+ rotation = 0;
3756
+ applyTransform();
3757
+ },
3758
+ rotateCW: () => {
3759
+ rotation = (rotation + 90) % 360;
3760
+ applyTransform();
3761
+ },
3762
+ rotateCCW: () => {
3763
+ rotation = (rotation - 90 + 360) % 360;
3764
+ applyTransform();
3238
3765
  },
3239
3766
  getThumbnails: () => {
3240
3767
  const list = [];
@@ -3466,7 +3993,31 @@ var RtfPlugin = class {
3466
3993
  return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3467
3994
  }
3468
3995
  getToolbarActions(instance) {
3469
- return [
3996
+ const totalPages = instance.getPageCount?.() ?? 1;
3997
+ const actions = [];
3998
+ if (totalPages > 1) {
3999
+ actions.push({
4000
+ id: "page-nav",
4001
+ icon: "",
4002
+ label: "Page Navigation",
4003
+ type: "page-nav",
4004
+ group: "navigation",
4005
+ value: instance.getCurrentPage?.() ?? 1,
4006
+ max: totalPages,
4007
+ execute: (action, page) => {
4008
+ const cur = instance.getCurrentPage?.() ?? 1;
4009
+ const max = instance.getPageCount?.() ?? 1;
4010
+ if (action === "prev") {
4011
+ if (cur > 1) instance.goToPage?.(cur - 1);
4012
+ } else if (action === "next") {
4013
+ if (cur < max) instance.goToPage?.(cur + 1);
4014
+ } else if (typeof page === "number") {
4015
+ instance.goToPage?.(page);
4016
+ }
4017
+ }
4018
+ });
4019
+ }
4020
+ actions.push(
3470
4021
  {
3471
4022
  id: "zoom-out",
3472
4023
  icon: "zoom-out",
@@ -3491,6 +4042,14 @@ var RtfPlugin = class {
3491
4042
  group: "zoom",
3492
4043
  execute: () => instance.fitToPage?.()
3493
4044
  },
4045
+ {
4046
+ id: "rotate-cw",
4047
+ icon: "rotate-cw",
4048
+ label: "Rotate",
4049
+ type: "button",
4050
+ group: "view",
4051
+ execute: () => instance.rotateCW?.()
4052
+ },
3494
4053
  {
3495
4054
  id: "download",
3496
4055
  icon: "download",
@@ -3507,7 +4066,8 @@ var RtfPlugin = class {
3507
4066
  group: "actions",
3508
4067
  execute: () => instance.print?.()
3509
4068
  }
3510
- ];
4069
+ );
4070
+ return actions;
3511
4071
  }
3512
4072
  async render(ctx) {
3513
4073
  const wrapper = document.createElement("div");
@@ -3526,44 +4086,124 @@ var RtfPlugin = class {
3526
4086
  ctx.container.style.backgroundColor = "#f1f5f9";
3527
4087
  ctx.container.appendChild(wrapper);
3528
4088
  let scale = 1;
4089
+ let rotation = 0;
4090
+ let pageElements = [];
4091
+ const calculateFitScale = () => {
4092
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4093
+ const elW = activeEl.offsetWidth || 850;
4094
+ const elH = activeEl.offsetHeight || 1e3;
4095
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4096
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4097
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4098
+ };
4099
+ const applyTransform = () => {
4100
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4101
+ wrapper.style.transformOrigin = "top center";
4102
+ };
4103
+ setTimeout(() => {
4104
+ scale = calculateFitScale();
4105
+ applyTransform();
4106
+ }, 60);
3529
4107
  try {
3530
4108
  if (typeof RTFJS__namespace.loggingEnabled === "function") {
3531
4109
  RTFJS__namespace.loggingEnabled(false);
3532
4110
  }
3533
4111
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
3534
4112
  const htmlElements = await doc.render();
3535
- for (const el of htmlElements) {
4113
+ pageElements = htmlElements;
4114
+ for (let i = 0; i < htmlElements.length; i++) {
4115
+ const el = htmlElements[i];
4116
+ el.style.display = i === 0 ? "block" : "none";
3536
4117
  wrapper.appendChild(el);
3537
4118
  }
3538
4119
  } catch (err) {
3539
4120
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3540
4121
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3541
4122
  const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3542
- wrapper.innerHTML = `<pre style="white-space: pre-wrap; font-family: serif; color: #333;">${clean}</pre>`;
4123
+ const pre = document.createElement("pre");
4124
+ pre.style.whiteSpace = "pre-wrap";
4125
+ pre.style.fontFamily = "serif";
4126
+ pre.style.color = "#333";
4127
+ pre.textContent = clean;
4128
+ wrapper.appendChild(pre);
4129
+ pageElements = [pre];
4130
+ }
4131
+ const totalPages = Math.max(1, pageElements.length);
4132
+ let currentPage = 1;
4133
+ let indicator = null;
4134
+ if (totalPages > 1) {
4135
+ indicator = document.createElement("div");
4136
+ indicator.className = "fp-rtf-page-indicator";
4137
+ indicator.style.position = "sticky";
4138
+ indicator.style.bottom = "16px";
4139
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4140
+ indicator.style.backdropFilter = "blur(8px)";
4141
+ indicator.style.color = "#f8fafc";
4142
+ indicator.style.fontSize = "12px";
4143
+ indicator.style.fontWeight = "600";
4144
+ indicator.style.padding = "5px 14px";
4145
+ indicator.style.borderRadius = "20px";
4146
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4147
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4148
+ indicator.style.zIndex = "10";
4149
+ indicator.style.userSelect = "none";
4150
+ indicator.style.pointerEvents = "none";
4151
+ indicator.style.textAlign = "center";
4152
+ indicator.style.width = "fit-content";
4153
+ indicator.style.margin = "16px auto 0";
4154
+ ctx.container.appendChild(indicator);
4155
+ }
4156
+ const showPage = (pageNum) => {
4157
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4158
+ if (totalPages > 1) {
4159
+ pageElements.forEach((el, idx) => {
4160
+ el.style.display = idx + 1 === currentPage ? "block" : "none";
4161
+ });
4162
+ }
4163
+ if (indicator) {
4164
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4165
+ }
4166
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4167
+ };
4168
+ if (totalPages > 1) {
4169
+ showPage(1);
3543
4170
  }
3544
4171
  const cleanup = () => {
4172
+ indicator?.remove();
3545
4173
  wrapper.remove();
3546
4174
  ctx.container.innerHTML = "";
3547
4175
  };
3548
4176
  ctx.signal.addEventListener("abort", cleanup);
3549
4177
  return {
3550
4178
  destroy: cleanup,
4179
+ getPageCount: () => totalPages,
4180
+ getCurrentPage: () => currentPage,
4181
+ goToPage: (page) => showPage(page),
3551
4182
  zoomIn: () => {
3552
- scale += 0.1;
3553
- wrapper.style.transform = `scale(${scale})`;
4183
+ scale += 0.15;
4184
+ applyTransform();
3554
4185
  },
3555
4186
  zoomOut: () => {
3556
- scale = Math.max(0.2, scale - 0.1);
3557
- wrapper.style.transform = `scale(${scale})`;
4187
+ scale = Math.max(0.2, scale - 0.15);
4188
+ applyTransform();
3558
4189
  },
3559
4190
  getZoom: () => scale,
3560
4191
  setZoom: (level) => {
3561
4192
  scale = level;
3562
- wrapper.style.transform = `scale(${scale})`;
4193
+ applyTransform();
3563
4194
  },
3564
4195
  fitToPage: () => {
3565
- scale = 1;
3566
- wrapper.style.transform = "scale(1)";
4196
+ scale = calculateFitScale();
4197
+ rotation = 0;
4198
+ applyTransform();
4199
+ },
4200
+ rotateCW: () => {
4201
+ rotation = (rotation + 90) % 360;
4202
+ applyTransform();
4203
+ },
4204
+ rotateCCW: () => {
4205
+ rotation = (rotation - 90 + 360) % 360;
4206
+ applyTransform();
3567
4207
  },
3568
4208
  download: () => {
3569
4209
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3735,41 +4375,41 @@ var OpenDocumentPlugin = class {
3735
4375
  }
3736
4376
  getToolbarActions(instance) {
3737
4377
  const isPresentation = instance.isPresentation;
4378
+ const totalPages = instance.getPageCount?.() ?? 1;
3738
4379
  const actions = [];
3739
- if (isPresentation) {
3740
- actions.push(
3741
- {
4380
+ if (isPresentation || totalPages > 1) {
4381
+ if (isPresentation) {
4382
+ actions.push({
3742
4383
  id: "thumbnails",
3743
4384
  icon: "thumbnails",
3744
4385
  label: "Slide Thumbnails",
3745
4386
  type: "button",
3746
4387
  group: "navigation",
3747
4388
  execute: () => instance.toggleThumbnails?.()
3748
- },
3749
- {
3750
- id: "page-nav",
3751
- icon: "",
3752
- label: "Slide Navigation",
3753
- type: "page-nav",
3754
- group: "navigation",
3755
- value: instance.getCurrentPage?.() ?? 1,
3756
- max: instance.getPageCount?.() ?? 1,
3757
- execute: (action, page) => {
3758
- if (action === "prev") {
3759
- const cur = instance.getCurrentPage?.() ?? 1;
3760
- if (cur > 1) instance.goToPage?.(cur - 1);
3761
- } else if (action === "next") {
3762
- const cur = instance.getCurrentPage?.() ?? 1;
3763
- const total = instance.getPageCount?.() ?? 1;
3764
- if (cur < total) instance.goToPage?.(cur + 1);
3765
- } else if (typeof page === "number") {
3766
- instance.goToPage?.(page);
3767
- } else if (typeof action === "number") {
3768
- instance.goToPage?.(action);
3769
- }
4389
+ });
4390
+ }
4391
+ actions.push({
4392
+ id: "page-nav",
4393
+ icon: "",
4394
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
4395
+ type: "page-nav",
4396
+ group: "navigation",
4397
+ value: instance.getCurrentPage?.() ?? 1,
4398
+ max: totalPages,
4399
+ execute: (action, page) => {
4400
+ const cur = instance.getCurrentPage?.() ?? 1;
4401
+ const max = instance.getPageCount?.() ?? 1;
4402
+ if (action === "prev") {
4403
+ if (cur > 1) instance.goToPage?.(cur - 1);
4404
+ } else if (action === "next") {
4405
+ if (cur < max) instance.goToPage?.(cur + 1);
4406
+ } else if (typeof page === "number") {
4407
+ instance.goToPage?.(page);
4408
+ } else if (typeof action === "number") {
4409
+ instance.goToPage?.(action);
3770
4410
  }
3771
4411
  }
3772
- );
4412
+ });
3773
4413
  }
3774
4414
  actions.push(
3775
4415
  {
@@ -3791,11 +4431,19 @@ var OpenDocumentPlugin = class {
3791
4431
  {
3792
4432
  id: "fit-page",
3793
4433
  icon: "fit-page",
3794
- label: "Fit to Page",
4434
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
3795
4435
  type: "button",
3796
4436
  group: "zoom",
3797
4437
  execute: () => instance.fitToPage?.()
3798
4438
  },
4439
+ {
4440
+ id: "rotate-cw",
4441
+ icon: "rotate-cw",
4442
+ label: "Rotate",
4443
+ type: "button",
4444
+ group: "view",
4445
+ execute: () => instance.rotateCW?.()
4446
+ },
3799
4447
  {
3800
4448
  id: "download",
3801
4449
  icon: "download",
@@ -3859,6 +4507,22 @@ var OpenDocumentPlugin = class {
3859
4507
  container.appendChild(wrapper);
3860
4508
  ctx.container.appendChild(container);
3861
4509
  let scale = 1;
4510
+ let rotation = 0;
4511
+ const calculateFitScale = () => {
4512
+ const elW = wrapper.offsetWidth || 850;
4513
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4514
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4515
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4516
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4517
+ };
4518
+ const applyTransform = () => {
4519
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4520
+ wrapper.style.transformOrigin = "top center";
4521
+ };
4522
+ setTimeout(() => {
4523
+ scale = calculateFitScale();
4524
+ applyTransform();
4525
+ }, 60);
3862
4526
  let currentPage = 1;
3863
4527
  let totalPages = 1;
3864
4528
  let slides = [];
@@ -3917,21 +4581,30 @@ var OpenDocumentPlugin = class {
3917
4581
  destroy: cleanup,
3918
4582
  isPresentation,
3919
4583
  zoomIn: () => {
3920
- scale += 0.1;
3921
- wrapper.style.transform = `scale(${scale})`;
4584
+ scale += 0.15;
4585
+ applyTransform();
3922
4586
  },
3923
4587
  zoomOut: () => {
3924
- scale = Math.max(0.2, scale - 0.1);
3925
- wrapper.style.transform = `scale(${scale})`;
4588
+ scale = Math.max(0.2, scale - 0.15);
4589
+ applyTransform();
3926
4590
  },
3927
4591
  getZoom: () => scale,
3928
4592
  setZoom: (level) => {
3929
4593
  scale = level;
3930
- wrapper.style.transform = `scale(${scale})`;
4594
+ applyTransform();
3931
4595
  },
3932
4596
  fitToPage: () => {
3933
- scale = 1;
3934
- wrapper.style.transform = "scale(1)";
4597
+ scale = calculateFitScale();
4598
+ rotation = 0;
4599
+ applyTransform();
4600
+ },
4601
+ rotateCW: () => {
4602
+ rotation = (rotation + 90) % 360;
4603
+ applyTransform();
4604
+ },
4605
+ rotateCCW: () => {
4606
+ rotation = (rotation - 90 + 360) % 360;
4607
+ applyTransform();
3935
4608
  },
3936
4609
  goToPage,
3937
4610
  getPageCount: () => totalPages,
@@ -4152,7 +4825,31 @@ var DocPlugin = class {
4152
4825
  return this.mimeTypes.includes(mime || "");
4153
4826
  }
4154
4827
  getToolbarActions(instance) {
4155
- return [
4828
+ const totalPages = instance.getPageCount?.() ?? 1;
4829
+ const actions = [];
4830
+ if (totalPages > 1) {
4831
+ actions.push({
4832
+ id: "page-nav",
4833
+ icon: "",
4834
+ label: "Page Navigation",
4835
+ type: "page-nav",
4836
+ group: "navigation",
4837
+ value: instance.getCurrentPage?.() ?? 1,
4838
+ max: totalPages,
4839
+ execute: (action, page) => {
4840
+ const cur = instance.getCurrentPage?.() ?? 1;
4841
+ const max = instance.getPageCount?.() ?? 1;
4842
+ if (action === "prev") {
4843
+ if (cur > 1) instance.goToPage?.(cur - 1);
4844
+ } else if (action === "next") {
4845
+ if (cur < max) instance.goToPage?.(cur + 1);
4846
+ } else if (typeof page === "number") {
4847
+ instance.goToPage?.(page);
4848
+ }
4849
+ }
4850
+ });
4851
+ }
4852
+ actions.push(
4156
4853
  {
4157
4854
  id: "zoom-out",
4158
4855
  icon: "zoom-out",
@@ -4177,6 +4874,14 @@ var DocPlugin = class {
4177
4874
  group: "zoom",
4178
4875
  execute: () => instance.fitToPage?.()
4179
4876
  },
4877
+ {
4878
+ id: "rotate-cw",
4879
+ icon: "rotate-cw",
4880
+ label: "Rotate",
4881
+ type: "button",
4882
+ group: "view",
4883
+ execute: () => instance.rotateCW?.()
4884
+ },
4180
4885
  {
4181
4886
  id: "copy",
4182
4887
  icon: "copy",
@@ -4201,7 +4906,8 @@ var DocPlugin = class {
4201
4906
  group: "actions",
4202
4907
  execute: () => instance.print?.()
4203
4908
  }
4204
- ];
4909
+ );
4910
+ return actions;
4205
4911
  }
4206
4912
  async render(ctx) {
4207
4913
  const container = document.createElement("div");
@@ -4228,6 +4934,7 @@ var DocPlugin = class {
4228
4934
  ctx.container.appendChild(container);
4229
4935
  let scale = 1;
4230
4936
  let extractedRawText = "";
4937
+ let isFallback = false;
4231
4938
  try {
4232
4939
  const cfbf = new CfbfReader(ctx.buffer);
4233
4940
  const wordDocStream = cfbf.readStream("WordDocument");
@@ -4241,42 +4948,131 @@ var DocPlugin = class {
4241
4948
  const tableStream = cfbf.readStream(tableName);
4242
4949
  const text = this.extractDocText(wordDocStream, tableStream);
4243
4950
  extractedRawText = text;
4244
- wrapper.innerHTML = this.formatDocToHtml(text, ctx.metadata.name || "Document");
4245
4951
  } catch (err) {
4246
4952
  console.warn("[DocPlugin] Binary parsing error, fallback text:", err);
4247
4953
  const fallback = this.heuristicTextExtraction(ctx.buffer);
4248
4954
  extractedRawText = fallback;
4249
- wrapper.innerHTML = `
4250
- <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4251
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${ctx.metadata.name || "Word Document (.doc)"}</h2>
4252
- <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4253
- </div>
4254
- ${this.formatDocToHtml(fallback, ctx.metadata.name || "Document")}
4255
- `;
4955
+ isFallback = true;
4956
+ }
4957
+ const rawPages = this.splitIntoPages(extractedRawText);
4958
+ const totalPages = Math.max(1, rawPages.length);
4959
+ let currentPage = 1;
4960
+ wrapper.innerHTML = "";
4961
+ const pageCards = [];
4962
+ for (let i = 0; i < totalPages; i++) {
4963
+ const pageCard = document.createElement("div");
4964
+ pageCard.className = "fp-doc-page-card";
4965
+ pageCard.style.backgroundColor = "#ffffff";
4966
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4967
+ pageCard.style.borderRadius = "4px";
4968
+ pageCard.style.padding = "56px 48px";
4969
+ pageCard.style.minHeight = "100%";
4970
+ pageCard.style.display = i === 0 ? "block" : "none";
4971
+ if (isFallback && i === 0) {
4972
+ pageCard.innerHTML = `
4973
+ <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4974
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2__default.default.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4975
+ <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4976
+ </div>
4977
+ ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
4978
+ `;
4979
+ } else {
4980
+ pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4981
+ }
4982
+ wrapper.appendChild(pageCard);
4983
+ pageCards.push(pageCard);
4984
+ }
4985
+ let indicator = null;
4986
+ if (totalPages > 1) {
4987
+ indicator = document.createElement("div");
4988
+ indicator.className = "fp-doc-page-indicator";
4989
+ indicator.style.position = "sticky";
4990
+ indicator.style.bottom = "16px";
4991
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4992
+ indicator.style.backdropFilter = "blur(8px)";
4993
+ indicator.style.color = "#f8fafc";
4994
+ indicator.style.fontSize = "12px";
4995
+ indicator.style.fontWeight = "600";
4996
+ indicator.style.padding = "5px 14px";
4997
+ indicator.style.borderRadius = "20px";
4998
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4999
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
5000
+ indicator.style.zIndex = "10";
5001
+ indicator.style.userSelect = "none";
5002
+ indicator.style.pointerEvents = "none";
5003
+ indicator.style.textAlign = "center";
5004
+ indicator.style.width = "fit-content";
5005
+ indicator.style.margin = "16px auto 0";
5006
+ container.appendChild(indicator);
5007
+ }
5008
+ const showPage = (pageNum) => {
5009
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
5010
+ if (totalPages > 1) {
5011
+ pageCards.forEach((card, idx) => {
5012
+ card.style.display = idx + 1 === currentPage ? "block" : "none";
5013
+ });
5014
+ }
5015
+ if (indicator) {
5016
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5017
+ }
5018
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
5019
+ };
5020
+ if (totalPages > 1) {
5021
+ showPage(1);
4256
5022
  }
5023
+ let rotation = 0;
5024
+ const calculateFitScale = () => {
5025
+ const activeCard = pageCards[currentPage - 1] || wrapper;
5026
+ const elW = activeCard.offsetWidth || 850;
5027
+ const elH = activeCard.offsetHeight || 1e3;
5028
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
5029
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
5030
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
5031
+ };
5032
+ const applyTransform = () => {
5033
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5034
+ wrapper.style.transformOrigin = "top center";
5035
+ };
5036
+ setTimeout(() => {
5037
+ scale = calculateFitScale();
5038
+ applyTransform();
5039
+ }, 60);
4257
5040
  const cleanup = () => {
5041
+ indicator?.remove();
4258
5042
  container.remove();
4259
5043
  ctx.container.innerHTML = "";
4260
5044
  };
4261
5045
  ctx.signal.addEventListener("abort", cleanup);
4262
5046
  return {
4263
5047
  destroy: cleanup,
5048
+ getPageCount: () => totalPages,
5049
+ getCurrentPage: () => currentPage,
5050
+ goToPage: (page) => showPage(page),
4264
5051
  zoomIn: () => {
4265
- scale += 0.1;
4266
- wrapper.style.transform = `scale(${scale})`;
5052
+ scale += 0.15;
5053
+ applyTransform();
4267
5054
  },
4268
5055
  zoomOut: () => {
4269
- scale = Math.max(0.2, scale - 0.1);
4270
- wrapper.style.transform = `scale(${scale})`;
5056
+ scale = Math.max(0.2, scale - 0.15);
5057
+ applyTransform();
4271
5058
  },
4272
5059
  getZoom: () => scale,
4273
5060
  setZoom: (level) => {
4274
5061
  scale = level;
4275
- wrapper.style.transform = `scale(${scale})`;
5062
+ applyTransform();
4276
5063
  },
4277
5064
  fitToPage: () => {
4278
- scale = 1;
4279
- wrapper.style.transform = "scale(1)";
5065
+ scale = calculateFitScale();
5066
+ rotation = 0;
5067
+ applyTransform();
5068
+ },
5069
+ rotateCW: () => {
5070
+ rotation = (rotation + 90) % 360;
5071
+ applyTransform();
5072
+ },
5073
+ rotateCCW: () => {
5074
+ rotation = (rotation - 90 + 360) % 360;
5075
+ applyTransform();
4280
5076
  },
4281
5077
  copy: () => {
4282
5078
  navigator.clipboard.writeText(extractedRawText);
@@ -4392,11 +5188,16 @@ var DocPlugin = class {
4392
5188
  heuristicTextExtraction(buffer) {
4393
5189
  return this.extractStringsFromBytes(new Uint8Array(buffer));
4394
5190
  }
5191
+ splitIntoPages(text) {
5192
+ if (!text) return [""];
5193
+ 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);
5194
+ return parts.length > 0 ? parts : [text];
5195
+ }
4395
5196
  /**
4396
5197
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
4397
5198
  */
4398
5199
  formatDocToHtml(text, filename) {
4399
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x0C/g, "\n\n").split("\n");
5200
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4400
5201
  let html = "";
4401
5202
  let inList = false;
4402
5203
  for (const rawLine of lines) {
@@ -4511,6 +5312,14 @@ var PptPlugin = class {
4511
5312
  group: "zoom",
4512
5313
  execute: () => instance.fitToPage?.()
4513
5314
  },
5315
+ {
5316
+ id: "rotate-cw",
5317
+ icon: "rotate-cw",
5318
+ label: "Rotate",
5319
+ type: "button",
5320
+ group: "view",
5321
+ execute: () => instance.rotateCW?.()
5322
+ },
4514
5323
  {
4515
5324
  id: "download",
4516
5325
  icon: "download",
@@ -4544,22 +5353,36 @@ var PptPlugin = class {
4544
5353
  const slideCard = document.createElement("div");
4545
5354
  slideCard.className = "fp-ppt-slide-card";
4546
5355
  slideCard.style.width = "960px";
4547
- slideCard.style.maxWidth = "92%";
5356
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5357
+ slideCard.style.maxHeight = "calc(100% - 48px)";
4548
5358
  slideCard.style.aspectRatio = "16 / 9";
4549
5359
  slideCard.style.backgroundColor = "#ffffff";
4550
5360
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4551
5361
  slideCard.style.borderRadius = "8px";
4552
5362
  slideCard.style.boxSizing = "border-box";
4553
5363
  slideCard.style.position = "relative";
4554
- slideCard.style.overflow = "hidden";
4555
- slideCard.style.transformOrigin = "center center";
4556
5364
  slideCard.style.transition = "transform 0.2s ease";
5365
+ slideCard.style.transformOrigin = "center center";
5366
+ slideCard.style.flexShrink = "0";
4557
5367
  container.appendChild(slideCard);
4558
5368
  ctx.container.appendChild(container);
4559
5369
  let scale = 1;
5370
+ let rotation = 0;
4560
5371
  let currentSlide = 1;
4561
5372
  let slides = [];
4562
5373
  const createdBlobUrls = [];
5374
+ const calculateFitScale = () => {
5375
+ const availW = Math.max(200, container.clientWidth - 48);
5376
+ const availH = Math.max(200, container.clientHeight - 72);
5377
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5378
+ };
5379
+ const applyTransform = () => {
5380
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5381
+ };
5382
+ setTimeout(() => {
5383
+ scale = calculateFitScale();
5384
+ applyTransform();
5385
+ }, 60);
4563
5386
  try {
4564
5387
  const cfbf = new CfbfReader(ctx.buffer);
4565
5388
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -4660,21 +5483,30 @@ var PptPlugin = class {
4660
5483
  return {
4661
5484
  destroy: cleanup,
4662
5485
  zoomIn: () => {
4663
- scale += 0.1;
4664
- slideCard.style.transform = `scale(${scale})`;
5486
+ scale += 0.15;
5487
+ applyTransform();
4665
5488
  },
4666
5489
  zoomOut: () => {
4667
- scale = Math.max(0.3, scale - 0.1);
4668
- slideCard.style.transform = `scale(${scale})`;
5490
+ scale = Math.max(0.2, scale - 0.15);
5491
+ applyTransform();
4669
5492
  },
4670
5493
  getZoom: () => scale,
4671
5494
  setZoom: (level) => {
4672
5495
  scale = level;
4673
- slideCard.style.transform = `scale(${scale})`;
5496
+ applyTransform();
4674
5497
  },
4675
5498
  fitToPage: () => {
4676
- scale = 1;
4677
- slideCard.style.transform = "scale(1)";
5499
+ scale = calculateFitScale();
5500
+ rotation = 0;
5501
+ applyTransform();
5502
+ },
5503
+ rotateCW: () => {
5504
+ rotation = (rotation + 90) % 360;
5505
+ applyTransform();
5506
+ },
5507
+ rotateCCW: () => {
5508
+ rotation = (rotation - 90 + 360) % 360;
5509
+ applyTransform();
4678
5510
  },
4679
5511
  goToPage: (page) => {
4680
5512
  if (page >= 1 && page <= totalSlides) {