@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/react.cjs CHANGED
@@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var react = require('react');
6
6
  var DOMPurify2 = require('dompurify');
7
+ var pdfjsLib = require('pdfjs-dist');
7
8
  var docx = require('docx-preview');
8
9
  var fflate = require('fflate');
9
10
  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);
@@ -511,11 +513,21 @@ var ToolbarController = class {
511
513
  el;
512
514
  toolbarEl;
513
515
  actions = [];
516
+ pageInputEl = null;
517
+ pageLabelEl = null;
514
518
  constructor(container) {
515
519
  this.el = container;
516
520
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
517
521
  this.el.appendChild(this.toolbarEl);
518
522
  }
523
+ setPage(page, max) {
524
+ if (this.pageInputEl) {
525
+ this.pageInputEl.value = page.toString();
526
+ }
527
+ if (max !== void 0 && this.pageLabelEl) {
528
+ this.pageLabelEl.textContent = ` / ${max}`;
529
+ }
530
+ }
519
531
  update(actions) {
520
532
  this.actions = actions;
521
533
  this.render();
@@ -558,6 +570,7 @@ var ToolbarController = class {
558
570
  if (action.type === "separator") {
559
571
  groupEl.appendChild(createElement("div", { className: "fp-toolbar-separator" }));
560
572
  } else if (action.type === "page-nav") {
573
+ const max = action.max ?? 1;
561
574
  const prevBtn = this.createButton(
562
575
  "prev",
563
576
  ICON_PAGE_PREV,
@@ -576,7 +589,6 @@ var ToolbarController = class {
576
589
  "Next Page",
577
590
  () => {
578
591
  const cur = parseInt(input.value, 10) || 1;
579
- const max = action.max ?? 1;
580
592
  if (cur < max) {
581
593
  input.value = (cur + 1).toString();
582
594
  action.execute("next", cur + 1);
@@ -588,15 +600,18 @@ var ToolbarController = class {
588
600
  type: "number",
589
601
  value: (action.value ?? 1).toString(),
590
602
  min: "1",
591
- max: (action.max ?? 1).toString()
603
+ max: max.toString()
592
604
  });
593
605
  input.addEventListener("change", () => {
594
- const val = parseInt(input.value, 10);
595
- if (!isNaN(val)) {
596
- action.execute("go", val);
597
- }
606
+ let val = parseInt(input.value, 10);
607
+ if (isNaN(val)) val = 1;
608
+ val = Math.max(1, Math.min(max, val));
609
+ input.value = val.toString();
610
+ action.execute("go", val);
598
611
  });
599
- const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
612
+ const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
613
+ this.pageInputEl = input;
614
+ this.pageLabelEl = label;
600
615
  groupEl.appendChild(prevBtn);
601
616
  groupEl.appendChild(input);
602
617
  groupEl.appendChild(label);
@@ -723,6 +738,8 @@ var FilePreviewViewer = class {
723
738
  keyHandler = null;
724
739
  currentContainer = null;
725
740
  currentOptions = {};
741
+ resizeObserver = null;
742
+ fullscreenHandler = null;
726
743
  /**
727
744
  * Register a preview plugin.
728
745
  */
@@ -774,7 +791,13 @@ var FilePreviewViewer = class {
774
791
  buffer,
775
792
  options,
776
793
  signal,
777
- emit: (event, payload) => this.eventEmitter.emit(event, payload)
794
+ emit: (event, payload) => {
795
+ if (event === "page-change" && payload && typeof payload.page === "number") {
796
+ const total = payload.total ?? payload.totalPages;
797
+ this.toolbar?.setPage(payload.page, total);
798
+ }
799
+ this.eventEmitter.emit(event, payload);
800
+ }
778
801
  });
779
802
  this.activeInstance = instance;
780
803
  this.hideLoading();
@@ -789,12 +812,31 @@ var FilePreviewViewer = class {
789
812
  label: "Toggle Fullscreen",
790
813
  type: "button",
791
814
  group: "view",
792
- execute: () => {
793
- if (!document.fullscreenElement) {
794
- this.wrapperEl?.requestFullscreen?.();
795
- } else {
796
- document.exitFullscreen?.();
815
+ execute: async () => {
816
+ try {
817
+ const isNativeFs = !!document.fullscreenElement;
818
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
819
+ if (!isNativeFs && !isCssFs) {
820
+ if (this.wrapperEl?.requestFullscreen) {
821
+ await this.wrapperEl.requestFullscreen().catch(() => {
822
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
823
+ });
824
+ } else {
825
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
826
+ }
827
+ } else {
828
+ if (document.fullscreenElement) {
829
+ await document.exitFullscreen().catch(() => {
830
+ });
831
+ }
832
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
833
+ }
834
+ } catch {
835
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
797
836
  }
837
+ setTimeout(() => {
838
+ this.activeInstance?.fitToPage?.();
839
+ }, 120);
798
840
  }
799
841
  });
800
842
  }
@@ -808,6 +850,7 @@ var FilePreviewViewer = class {
808
850
  if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
809
851
  this.thumbnailPanel.update(thumbnails, (index) => {
810
852
  instance.goToPage?.(index + 1);
853
+ this.toolbar?.setPage(index + 1);
811
854
  });
812
855
  if (options.showThumbnails) {
813
856
  this.thumbnailPanel.show();
@@ -843,6 +886,15 @@ var FilePreviewViewer = class {
843
886
  window.removeEventListener("keydown", this.keyHandler);
844
887
  this.keyHandler = null;
845
888
  }
889
+ if (this.resizeObserver) {
890
+ this.resizeObserver.disconnect();
891
+ this.resizeObserver = null;
892
+ }
893
+ if (this.fullscreenHandler) {
894
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
895
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
896
+ this.fullscreenHandler = null;
897
+ }
846
898
  this.abort();
847
899
  this.destroyInstance();
848
900
  this.toolbar?.destroy();
@@ -922,6 +974,25 @@ var FilePreviewViewer = class {
922
974
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
923
975
  this.setupKeyboardShortcuts();
924
976
  this.setupDragAndDrop(container, options);
977
+ this.setupResizeAndFullscreenListeners();
978
+ }
979
+ setupResizeAndFullscreenListeners() {
980
+ if (this.fullscreenHandler) return;
981
+ this.fullscreenHandler = () => {
982
+ setTimeout(() => {
983
+ this.activeInstance?.fitToPage?.();
984
+ }, 100);
985
+ };
986
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
987
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
988
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
989
+ this.resizeObserver = new ResizeObserver(() => {
990
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
991
+ this.activeInstance.fitToPage?.();
992
+ }
993
+ });
994
+ this.resizeObserver.observe(this.contentEl);
995
+ }
925
996
  }
926
997
  setupKeyboardShortcuts() {
927
998
  if (this.keyHandler) return;
@@ -933,9 +1004,13 @@ var FilePreviewViewer = class {
933
1004
  if (e.key === "ArrowRight" || e.key === "PageDown") {
934
1005
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
935
1006
  this.activeInstance.goToPage?.(cur + 1);
1007
+ const nextCur = this.activeInstance.getCurrentPage?.() ?? cur + 1;
1008
+ this.toolbar?.setPage(nextCur);
936
1009
  } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
937
1010
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
938
1011
  this.activeInstance.goToPage?.(Math.max(1, cur - 1));
1012
+ const prevCur = this.activeInstance.getCurrentPage?.() ?? Math.max(1, cur - 1);
1013
+ this.toolbar?.setPage(prevCur);
939
1014
  } else if (e.key === "+" || e.key === "=") {
940
1015
  this.activeInstance.zoomIn?.();
941
1016
  } else if (e.key === "-" || e.key === "_") {
@@ -1201,30 +1276,62 @@ var CfbfReader = class {
1201
1276
  return result.subarray(0, targetSize);
1202
1277
  }
1203
1278
  };
1204
-
1205
- // ../plugins/pdf/dist/index.js
1279
+ if (typeof window !== "undefined" && pdfjsLib__namespace.GlobalWorkerOptions) {
1280
+ if (!pdfjsLib__namespace.GlobalWorkerOptions.workerSrc) {
1281
+ pdfjsLib__namespace.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1282
+ }
1283
+ }
1206
1284
  var PdfPlugin = class {
1207
1285
  id = "pdf";
1208
- name = "PDF Preview";
1286
+ name = "PDF Document Preview";
1209
1287
  extensions = [".pdf"];
1210
1288
  mimeTypes = ["application/pdf"];
1211
1289
  weight = 100;
1212
1290
  supports(file) {
1213
1291
  const ext = file.metadata.extension?.toLowerCase();
1214
1292
  const mime = file.metadata.mimeType?.toLowerCase();
1215
- return ext === ".pdf" || mime === "application/pdf";
1293
+ if (ext) return this.extensions.includes(ext);
1294
+ return this.mimeTypes.includes(mime || "");
1216
1295
  }
1217
1296
  getToolbarActions(instance) {
1297
+ const totalPages = instance.getPageCount?.() ?? 1;
1298
+ const curPage = instance.getCurrentPage?.() ?? 1;
1218
1299
  return [
1300
+ {
1301
+ id: "thumbnails",
1302
+ icon: "thumbnails",
1303
+ label: "Page Thumbnails",
1304
+ type: "button",
1305
+ group: "navigation",
1306
+ execute: () => instance.toggleThumbnails?.()
1307
+ },
1308
+ {
1309
+ id: "page-nav",
1310
+ icon: "",
1311
+ label: "Page Navigation",
1312
+ type: "page-nav",
1313
+ group: "navigation",
1314
+ value: curPage,
1315
+ max: totalPages,
1316
+ execute: (action, page) => {
1317
+ const cur = instance.getCurrentPage?.() ?? 1;
1318
+ const max = instance.getPageCount?.() ?? 1;
1319
+ if (action === "prev") {
1320
+ if (cur > 1) instance.goToPage?.(cur - 1);
1321
+ } else if (action === "next") {
1322
+ if (cur < max) instance.goToPage?.(cur + 1);
1323
+ } else if (typeof page === "number") {
1324
+ instance.goToPage?.(page);
1325
+ }
1326
+ }
1327
+ },
1219
1328
  {
1220
1329
  id: "zoom-out",
1221
1330
  icon: "zoom-out",
1222
1331
  label: "Zoom Out",
1223
1332
  type: "button",
1224
1333
  group: "zoom",
1225
- execute: () => {
1226
- instance.zoomOut?.();
1227
- }
1334
+ execute: () => instance.zoomOut?.()
1228
1335
  },
1229
1336
  {
1230
1337
  id: "zoom-in",
@@ -1232,9 +1339,7 @@ var PdfPlugin = class {
1232
1339
  label: "Zoom In",
1233
1340
  type: "button",
1234
1341
  group: "zoom",
1235
- execute: () => {
1236
- instance.zoomIn?.();
1237
- }
1342
+ execute: () => instance.zoomIn?.()
1238
1343
  },
1239
1344
  {
1240
1345
  id: "fit-page",
@@ -1242,39 +1347,23 @@ var PdfPlugin = class {
1242
1347
  label: "Fit to Page",
1243
1348
  type: "button",
1244
1349
  group: "zoom",
1245
- execute: () => {
1246
- instance.fitToPage?.();
1247
- }
1350
+ execute: () => instance.fitToPage?.()
1248
1351
  },
1249
1352
  {
1250
1353
  id: "rotate-cw",
1251
1354
  icon: "rotate-cw",
1252
- label: "Rotate",
1355
+ label: "Rotate Clockwise",
1253
1356
  type: "button",
1254
1357
  group: "view",
1255
- execute: () => {
1256
- instance.rotateCW?.();
1257
- }
1258
- },
1259
- {
1260
- id: "page-nav",
1261
- icon: "page-nav",
1262
- label: "Page Navigation",
1263
- type: "page-nav",
1264
- group: "navigation",
1265
- execute: (page) => {
1266
- if (typeof page === "number") instance.goToPage?.(page);
1267
- }
1358
+ execute: () => instance.rotateCW?.()
1268
1359
  },
1269
1360
  {
1270
1361
  id: "download",
1271
1362
  icon: "download",
1272
- label: "Download",
1363
+ label: "Download PDF",
1273
1364
  type: "button",
1274
1365
  group: "actions",
1275
- execute: () => {
1276
- instance.download?.();
1277
- }
1366
+ execute: () => instance.download?.()
1278
1367
  },
1279
1368
  {
1280
1369
  id: "print",
@@ -1282,99 +1371,224 @@ var PdfPlugin = class {
1282
1371
  label: "Print",
1283
1372
  type: "button",
1284
1373
  group: "actions",
1285
- execute: () => {
1286
- instance.print?.();
1287
- }
1374
+ execute: () => instance.print?.()
1288
1375
  }
1289
1376
  ];
1290
1377
  }
1291
1378
  async render(ctx) {
1292
- const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1293
- const url = URL.createObjectURL(blob);
1294
- const wrapper = document.createElement("div");
1295
- wrapper.style.width = "100%";
1296
- wrapper.style.height = "100%";
1297
- wrapper.style.overflow = "hidden";
1298
- wrapper.style.display = "flex";
1299
- wrapper.style.justifyContent = "center";
1300
- wrapper.style.alignItems = "center";
1301
- const iframe = document.createElement("iframe");
1302
- iframe.src = url;
1303
- iframe.style.width = "100%";
1304
- iframe.style.height = "100%";
1305
- iframe.style.border = "none";
1306
- wrapper.appendChild(iframe);
1307
- ctx.container.appendChild(wrapper);
1379
+ const container = document.createElement("div");
1380
+ container.className = "fp-pdf-container";
1381
+ container.style.width = "100%";
1382
+ container.style.height = "100%";
1383
+ container.style.overflow = "auto";
1384
+ container.style.display = "flex";
1385
+ container.style.flexDirection = "column";
1386
+ container.style.alignItems = "center";
1387
+ container.style.justifyContent = "flex-start";
1388
+ container.style.padding = "20px 16px";
1389
+ container.style.backgroundColor = "#0f172a";
1390
+ container.style.boxSizing = "border-box";
1391
+ container.style.position = "relative";
1392
+ const pageCard = document.createElement("div");
1393
+ pageCard.className = "fp-pdf-page-card";
1394
+ pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
1395
+ pageCard.style.backgroundColor = "#ffffff";
1396
+ pageCard.style.borderRadius = "4px";
1397
+ pageCard.style.overflow = "hidden";
1398
+ pageCard.style.lineHeight = "0";
1399
+ pageCard.style.transition = "transform 0.15s ease";
1400
+ pageCard.style.position = "relative";
1401
+ pageCard.style.flexShrink = "0";
1402
+ let canvas = document.createElement("canvas");
1403
+ pageCard.appendChild(canvas);
1404
+ container.appendChild(pageCard);
1405
+ const indicator = document.createElement("div");
1406
+ indicator.className = "fp-pdf-page-indicator";
1407
+ indicator.style.position = "sticky";
1408
+ indicator.style.bottom = "16px";
1409
+ indicator.style.marginTop = "16px";
1410
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1411
+ indicator.style.backdropFilter = "blur(8px)";
1412
+ indicator.style.color = "#f8fafc";
1413
+ indicator.style.fontSize = "12px";
1414
+ indicator.style.fontWeight = "600";
1415
+ indicator.style.padding = "5px 14px";
1416
+ indicator.style.borderRadius = "20px";
1417
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1418
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1419
+ indicator.style.zIndex = "10";
1420
+ indicator.style.userSelect = "none";
1421
+ indicator.style.pointerEvents = "none";
1422
+ container.appendChild(indicator);
1423
+ ctx.container.appendChild(container);
1424
+ const loadingTask = pdfjsLib__namespace.getDocument({
1425
+ data: new Uint8Array(ctx.buffer),
1426
+ cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/cmaps/`,
1427
+ cMapPacked: true,
1428
+ standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib__namespace.version || "4.10.38"}/standard_fonts/`
1429
+ });
1430
+ const pdfDoc = await loadingTask.promise;
1431
+ const totalPages = Math.max(1, pdfDoc.numPages);
1308
1432
  let currentPage = 1;
1309
- let currentZoom = 1;
1433
+ let zoomScale = 1;
1310
1434
  let rotation = 0;
1435
+ let currentRenderTask = null;
1436
+ const renderPage = async (pageNum) => {
1437
+ if (currentRenderTask) {
1438
+ try {
1439
+ currentRenderTask.cancel();
1440
+ } catch {
1441
+ }
1442
+ currentRenderTask = null;
1443
+ }
1444
+ const newCanvas = document.createElement("canvas");
1445
+ pageCard.replaceChild(newCanvas, canvas);
1446
+ canvas = newCanvas;
1447
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
1448
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1449
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
1450
+ const page = await pdfDoc.getPage(currentPage);
1451
+ const containerWidth = container.clientWidth || 900;
1452
+ const containerHeight = container.clientHeight || 700;
1453
+ const unscaledVp = page.getViewport({ scale: 1, rotation });
1454
+ const availWidth = Math.max(100, containerWidth - 48);
1455
+ const availHeight = Math.max(100, containerHeight - 88);
1456
+ const scaleW = availWidth / unscaledVp.width;
1457
+ const scaleH = availHeight / unscaledVp.height;
1458
+ const fitScale = Math.min(scaleW, scaleH);
1459
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1460
+ const pixelRatio = window.devicePixelRatio || 1;
1461
+ const viewport = page.getViewport({ scale: effectiveScale, rotation });
1462
+ canvas.width = Math.floor(viewport.width * pixelRatio);
1463
+ canvas.height = Math.floor(viewport.height * pixelRatio);
1464
+ canvas.style.width = `${Math.floor(viewport.width)}px`;
1465
+ canvas.style.height = `${Math.floor(viewport.height)}px`;
1466
+ const canvasCtx = canvas.getContext("2d");
1467
+ if (!canvasCtx) return;
1468
+ canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
1469
+ currentRenderTask = page.render({
1470
+ canvasContext: canvasCtx,
1471
+ viewport
1472
+ });
1473
+ try {
1474
+ await currentRenderTask.promise;
1475
+ } catch (err) {
1476
+ if (err?.name !== "RenderingCancelledException") {
1477
+ console.warn("[PdfPlugin] Page render warning:", err);
1478
+ }
1479
+ } finally {
1480
+ currentRenderTask = null;
1481
+ }
1482
+ };
1483
+ await renderPage(1);
1311
1484
  const cleanup = () => {
1312
- URL.revokeObjectURL(url);
1313
- wrapper.remove();
1485
+ if (currentRenderTask) {
1486
+ try {
1487
+ currentRenderTask.cancel();
1488
+ } catch {
1489
+ }
1490
+ }
1491
+ try {
1492
+ pdfDoc.destroy();
1493
+ } catch {
1494
+ }
1495
+ container.remove();
1314
1496
  ctx.container.innerHTML = "";
1315
1497
  };
1316
1498
  ctx.signal.addEventListener("abort", cleanup);
1317
- return {
1499
+ const instance = {
1318
1500
  destroy: cleanup,
1319
1501
  zoomIn: () => {
1320
- currentZoom += 0.1;
1321
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1502
+ zoomScale = Math.min(3.5, zoomScale + 0.2);
1503
+ renderPage(currentPage);
1322
1504
  },
1323
1505
  zoomOut: () => {
1324
- currentZoom = Math.max(0.2, currentZoom - 0.1);
1325
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1506
+ zoomScale = Math.max(0.3, zoomScale - 0.2);
1507
+ renderPage(currentPage);
1326
1508
  },
1327
- getZoom: () => currentZoom,
1509
+ getZoom: () => zoomScale,
1328
1510
  setZoom: (level) => {
1329
- currentZoom = level;
1330
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1511
+ zoomScale = Math.max(0.3, Math.min(3.5, level));
1512
+ renderPage(currentPage);
1331
1513
  },
1332
1514
  fitToPage: () => {
1333
- currentZoom = 1;
1334
- iframe.style.transform = `scale(1) rotate(${rotation}deg)`;
1515
+ zoomScale = 1;
1516
+ rotation = 0;
1517
+ renderPage(currentPage);
1335
1518
  },
1336
1519
  rotateCW: () => {
1337
1520
  rotation = (rotation + 90) % 360;
1338
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1521
+ renderPage(currentPage);
1339
1522
  },
1340
1523
  rotateCCW: () => {
1341
1524
  rotation = (rotation - 90 + 360) % 360;
1342
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1525
+ renderPage(currentPage);
1343
1526
  },
1344
1527
  getRotation: () => rotation,
1528
+ getPageCount: () => totalPages,
1529
+ getCurrentPage: () => currentPage,
1345
1530
  goToPage: (page) => {
1346
- currentPage = page;
1347
- iframe.src = `${url}#page=${page}`;
1531
+ renderPage(page);
1348
1532
  },
1349
- getCurrentPage: () => currentPage,
1350
1533
  download: () => {
1534
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1535
+ const url = URL.createObjectURL(blob);
1351
1536
  const a = document.createElement("a");
1352
1537
  a.href = url;
1353
1538
  a.download = ctx.metadata.name || "document.pdf";
1354
1539
  a.click();
1540
+ URL.revokeObjectURL(url);
1355
1541
  },
1356
1542
  print: () => {
1357
- iframe.contentWindow?.print();
1543
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1544
+ const url = URL.createObjectURL(blob);
1545
+ const hiddenIframe = document.createElement("iframe");
1546
+ hiddenIframe.style.position = "fixed";
1547
+ hiddenIframe.style.right = "0";
1548
+ hiddenIframe.style.bottom = "0";
1549
+ hiddenIframe.style.width = "0";
1550
+ hiddenIframe.style.height = "0";
1551
+ hiddenIframe.style.border = "0";
1552
+ document.body.appendChild(hiddenIframe);
1553
+ hiddenIframe.src = url;
1554
+ hiddenIframe.onload = () => {
1555
+ setTimeout(() => {
1556
+ hiddenIframe.contentWindow?.print();
1557
+ setTimeout(() => {
1558
+ hiddenIframe.remove();
1559
+ URL.revokeObjectURL(url);
1560
+ }, 1e3);
1561
+ }, 300);
1562
+ };
1358
1563
  },
1359
1564
  getThumbnails: async () => {
1360
- return [
1361
- {
1362
- index: 1,
1363
- label: "Page 1",
1364
- render: async (canvas) => {
1365
- const context = canvas.getContext("2d");
1366
- if (context) {
1367
- context.fillStyle = "#fff";
1368
- context.fillRect(0, 0, canvas.width, canvas.height);
1369
- context.fillStyle = "#333";
1370
- context.font = "12px sans-serif";
1371
- context.fillText("PDF Preview", 10, 20);
1565
+ const thumbnails = [];
1566
+ const count = Math.min(totalPages, 50);
1567
+ for (let i = 1; i <= count; i++) {
1568
+ thumbnails.push({
1569
+ index: i,
1570
+ label: `Page ${i}`,
1571
+ render: async (thumbCanvas) => {
1572
+ try {
1573
+ const p = await pdfDoc.getPage(i);
1574
+ const baseVp = p.getViewport({ scale: 1 });
1575
+ const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1576
+ const thumbVp = p.getViewport({ scale: thumbScale });
1577
+ thumbCanvas.height = Math.floor(thumbVp.height);
1578
+ const tCtx = thumbCanvas.getContext("2d");
1579
+ if (tCtx) {
1580
+ await p.render({ canvasContext: tCtx, viewport: thumbVp }).promise;
1581
+ }
1582
+ } catch (e) {
1583
+ console.warn(`[PdfPlugin] Error generating thumbnail for page ${i}:`, e);
1372
1584
  }
1373
1585
  }
1374
- }
1375
- ];
1586
+ });
1587
+ }
1588
+ return thumbnails;
1376
1589
  }
1377
1590
  };
1591
+ return instance;
1378
1592
  }
1379
1593
  };
1380
1594
  function pdfPlugin() {
@@ -1701,7 +1915,31 @@ var DocxPlugin = class {
1701
1915
  return this.mimeTypes.includes(mime || "");
1702
1916
  }
1703
1917
  getToolbarActions(instance) {
1704
- return [
1918
+ const totalPages = instance.getPageCount?.() ?? 1;
1919
+ const actions = [];
1920
+ if (totalPages > 1) {
1921
+ actions.push({
1922
+ id: "page-nav",
1923
+ icon: "",
1924
+ label: "Page Navigation",
1925
+ type: "page-nav",
1926
+ group: "navigation",
1927
+ value: instance.getCurrentPage?.() ?? 1,
1928
+ max: totalPages,
1929
+ execute: (action, page) => {
1930
+ const cur = instance.getCurrentPage?.() ?? 1;
1931
+ const max = instance.getPageCount?.() ?? 1;
1932
+ if (action === "prev") {
1933
+ if (cur > 1) instance.goToPage?.(cur - 1);
1934
+ } else if (action === "next") {
1935
+ if (cur < max) instance.goToPage?.(cur + 1);
1936
+ } else if (typeof page === "number") {
1937
+ instance.goToPage?.(page);
1938
+ }
1939
+ }
1940
+ });
1941
+ }
1942
+ actions.push(
1705
1943
  {
1706
1944
  id: "zoom-out",
1707
1945
  icon: "zoom-out",
@@ -1726,6 +1964,14 @@ var DocxPlugin = class {
1726
1964
  group: "zoom",
1727
1965
  execute: () => instance.fitToPage?.()
1728
1966
  },
1967
+ {
1968
+ id: "rotate-cw",
1969
+ icon: "rotate-cw",
1970
+ label: "Rotate",
1971
+ type: "button",
1972
+ group: "view",
1973
+ execute: () => instance.rotateCW?.()
1974
+ },
1729
1975
  {
1730
1976
  id: "download",
1731
1977
  icon: "download",
@@ -1742,7 +1988,8 @@ var DocxPlugin = class {
1742
1988
  group: "actions",
1743
1989
  execute: () => instance.print?.()
1744
1990
  }
1745
- ];
1991
+ );
1992
+ return actions;
1746
1993
  }
1747
1994
  async render(ctx) {
1748
1995
  const wrapper = document.createElement("div");
@@ -1818,7 +2065,71 @@ var DocxPlugin = class {
1818
2065
  }
1819
2066
  }
1820
2067
  }
2068
+ const sections = wrapper.querySelectorAll("section.docx");
2069
+ const cards = wrapper.querySelectorAll(".fp-docx-page-card");
2070
+ const pageElements = sections.length > 0 ? sections : cards;
2071
+ const totalPages = Math.max(1, pageElements.length);
2072
+ let currentPage = 1;
2073
+ let indicator = null;
2074
+ if (totalPages > 1) {
2075
+ indicator = document.createElement("div");
2076
+ indicator.className = "fp-docx-page-indicator";
2077
+ indicator.style.position = "sticky";
2078
+ indicator.style.bottom = "16px";
2079
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2080
+ indicator.style.backdropFilter = "blur(8px)";
2081
+ indicator.style.color = "#f8fafc";
2082
+ indicator.style.fontSize = "12px";
2083
+ indicator.style.fontWeight = "600";
2084
+ indicator.style.padding = "5px 14px";
2085
+ indicator.style.borderRadius = "20px";
2086
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2087
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2088
+ indicator.style.zIndex = "10";
2089
+ indicator.style.userSelect = "none";
2090
+ indicator.style.pointerEvents = "none";
2091
+ indicator.style.textAlign = "center";
2092
+ indicator.style.width = "fit-content";
2093
+ indicator.style.margin = "16px auto 0";
2094
+ ctx.container.appendChild(indicator);
2095
+ }
2096
+ const showPage = (pageNum) => {
2097
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
2098
+ if (pageElements.length > 1) {
2099
+ pageElements.forEach((sec, idx) => {
2100
+ sec.style.display = idx + 1 === currentPage ? "block" : "none";
2101
+ });
2102
+ }
2103
+ if (indicator) {
2104
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2105
+ }
2106
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
2107
+ };
2108
+ if (totalPages > 1) {
2109
+ showPage(1);
2110
+ }
2111
+ scale = 1;
2112
+ let rotation = 0;
2113
+ const calculateFitScale = () => {
2114
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2115
+ const elW = activeEl.offsetWidth || 816;
2116
+ const elH = activeEl.offsetHeight || 1056;
2117
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2118
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2119
+ const sW = availW / elW;
2120
+ const sH = availH / elH;
2121
+ return Math.min(1.1, Math.min(sW, sH));
2122
+ };
2123
+ const applyTransform = () => {
2124
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2125
+ wrapper.style.transformOrigin = "top center";
2126
+ };
2127
+ setTimeout(() => {
2128
+ scale = calculateFitScale();
2129
+ applyTransform();
2130
+ }, 60);
1821
2131
  const cleanup = () => {
2132
+ indicator?.remove();
1822
2133
  for (const url of createdBlobUrls) {
1823
2134
  URL.revokeObjectURL(url);
1824
2135
  }
@@ -1829,22 +2140,36 @@ var DocxPlugin = class {
1829
2140
  ctx.signal.addEventListener("abort", cleanup);
1830
2141
  return {
1831
2142
  destroy: cleanup,
2143
+ getPageCount: () => totalPages,
2144
+ getCurrentPage: () => currentPage,
2145
+ goToPage: (page) => {
2146
+ showPage(page);
2147
+ },
1832
2148
  zoomIn: () => {
1833
- scale += 0.1;
1834
- wrapper.style.transform = `scale(${scale})`;
2149
+ scale += 0.15;
2150
+ applyTransform();
1835
2151
  },
1836
2152
  zoomOut: () => {
1837
- scale = Math.max(0.2, scale - 0.1);
1838
- wrapper.style.transform = `scale(${scale})`;
2153
+ scale = Math.max(0.2, scale - 0.15);
2154
+ applyTransform();
1839
2155
  },
1840
2156
  getZoom: () => scale,
1841
2157
  setZoom: (level) => {
1842
2158
  scale = level;
1843
- wrapper.style.transform = `scale(${scale})`;
2159
+ applyTransform();
1844
2160
  },
1845
2161
  fitToPage: () => {
1846
- scale = 1;
1847
- wrapper.style.transform = "scale(1)";
2162
+ scale = calculateFitScale();
2163
+ rotation = 0;
2164
+ applyTransform();
2165
+ },
2166
+ rotateCW: () => {
2167
+ rotation = (rotation + 90) % 360;
2168
+ applyTransform();
2169
+ },
2170
+ rotateCCW: () => {
2171
+ rotation = (rotation - 90 + 360) % 360;
2172
+ applyTransform();
1848
2173
  },
1849
2174
  download: () => {
1850
2175
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2093,7 +2418,31 @@ var ExcelPlugin = class {
2093
2418
  return this.mimeTypes.includes(mime || "");
2094
2419
  }
2095
2420
  getToolbarActions(instance) {
2096
- return [
2421
+ const totalSheets = instance.getPageCount?.() ?? 1;
2422
+ const actions = [];
2423
+ if (totalSheets > 1) {
2424
+ actions.push({
2425
+ id: "page-nav",
2426
+ icon: "",
2427
+ label: "Sheet Navigation",
2428
+ type: "page-nav",
2429
+ group: "navigation",
2430
+ value: instance.getCurrentPage?.() ?? 1,
2431
+ max: totalSheets,
2432
+ execute: (action, sheet) => {
2433
+ const cur = instance.getCurrentPage?.() ?? 1;
2434
+ const max = instance.getPageCount?.() ?? 1;
2435
+ if (action === "prev") {
2436
+ if (cur > 1) instance.goToPage?.(cur - 1);
2437
+ } else if (action === "next") {
2438
+ if (cur < max) instance.goToPage?.(cur + 1);
2439
+ } else if (typeof sheet === "number") {
2440
+ instance.goToPage?.(sheet);
2441
+ }
2442
+ }
2443
+ });
2444
+ }
2445
+ actions.push(
2097
2446
  {
2098
2447
  id: "zoom-out",
2099
2448
  icon: "zoom-out",
@@ -2115,13 +2464,23 @@ var ExcelPlugin = class {
2115
2464
  }
2116
2465
  },
2117
2466
  {
2118
- id: "page-nav",
2119
- icon: "page-nav",
2120
- label: "Sheet Navigation",
2121
- type: "page-nav",
2122
- group: "navigation",
2123
- execute: (sheet) => {
2124
- if (typeof sheet === "number") instance.goToPage?.(sheet);
2467
+ id: "fit-page",
2468
+ icon: "fit-page",
2469
+ label: "Fit to View",
2470
+ type: "button",
2471
+ group: "zoom",
2472
+ execute: () => {
2473
+ instance.fitToPage?.();
2474
+ }
2475
+ },
2476
+ {
2477
+ id: "rotate-cw",
2478
+ icon: "rotate-cw",
2479
+ label: "Rotate",
2480
+ type: "button",
2481
+ group: "view",
2482
+ execute: () => {
2483
+ instance.rotateCW?.();
2125
2484
  }
2126
2485
  },
2127
2486
  {
@@ -2144,7 +2503,8 @@ var ExcelPlugin = class {
2144
2503
  instance.print?.();
2145
2504
  }
2146
2505
  }
2147
- ];
2506
+ );
2507
+ return actions;
2148
2508
  }
2149
2509
  async render(ctx) {
2150
2510
  const container = document.createElement("div");
@@ -2173,9 +2533,23 @@ var ExcelPlugin = class {
2173
2533
  container.appendChild(tabsArea);
2174
2534
  ctx.container.appendChild(container);
2175
2535
  let scale = 1;
2536
+ let rotation = 0;
2176
2537
  let currentSheetIndex = 1;
2177
2538
  let sheetNames = [];
2178
2539
  let wb = null;
2540
+ const applyTransform = () => {
2541
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2542
+ contentArea.style.transformOrigin = "top left";
2543
+ };
2544
+ const calculateFitScale = () => {
2545
+ const table = contentArea.querySelector("table");
2546
+ if (!table) return 1;
2547
+ const availW = Math.max(200, container.clientWidth - 48);
2548
+ const availH = Math.max(200, container.clientHeight - 80);
2549
+ const tW = table.offsetWidth || 800;
2550
+ const tH = table.offsetHeight || 600;
2551
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2552
+ };
2179
2553
  try {
2180
2554
  wb = XLSX__namespace.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2181
2555
  sheetNames = wb.SheetNames || [];
@@ -2228,6 +2602,7 @@ var ExcelPlugin = class {
2228
2602
  b.style.fontWeight = "normal";
2229
2603
  }
2230
2604
  });
2605
+ ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
2231
2606
  };
2232
2607
  if (sheetNames.length > 0) {
2233
2608
  sheetNames.forEach((name, idx) => {
@@ -2262,17 +2637,30 @@ var ExcelPlugin = class {
2262
2637
  return {
2263
2638
  destroy: cleanup,
2264
2639
  zoomIn: () => {
2265
- scale += 0.1;
2266
- contentArea.style.transform = `scale(${scale})`;
2640
+ scale += 0.15;
2641
+ applyTransform();
2267
2642
  },
2268
2643
  zoomOut: () => {
2269
- scale = Math.max(0.2, scale - 0.1);
2270
- contentArea.style.transform = `scale(${scale})`;
2644
+ scale = Math.max(0.2, scale - 0.15);
2645
+ applyTransform();
2271
2646
  },
2272
2647
  getZoom: () => scale,
2273
2648
  setZoom: (level) => {
2274
2649
  scale = level;
2275
- contentArea.style.transform = `scale(${scale})`;
2650
+ applyTransform();
2651
+ },
2652
+ fitToPage: () => {
2653
+ scale = calculateFitScale();
2654
+ rotation = 0;
2655
+ applyTransform();
2656
+ },
2657
+ rotateCW: () => {
2658
+ rotation = (rotation + 90) % 360;
2659
+ applyTransform();
2660
+ },
2661
+ rotateCCW: () => {
2662
+ rotation = (rotation - 90 + 360) % 360;
2663
+ applyTransform();
2276
2664
  },
2277
2665
  goToPage: (page) => {
2278
2666
  if (page > 0 && page <= sheetNames.length) {
@@ -2477,7 +2865,31 @@ var CodePlugin = class {
2477
2865
  return false;
2478
2866
  }
2479
2867
  getToolbarActions(instance) {
2480
- return [
2868
+ const totalPages = instance.getPageCount?.() ?? 1;
2869
+ const actions = [];
2870
+ if (totalPages > 1) {
2871
+ actions.push({
2872
+ id: "page-nav",
2873
+ icon: "",
2874
+ label: "Page Navigation",
2875
+ type: "page-nav",
2876
+ group: "navigation",
2877
+ value: instance.getCurrentPage?.() ?? 1,
2878
+ max: totalPages,
2879
+ execute: (action, page) => {
2880
+ const cur = instance.getCurrentPage?.() ?? 1;
2881
+ const max = instance.getPageCount?.() ?? 1;
2882
+ if (action === "prev") {
2883
+ if (cur > 1) instance.goToPage?.(cur - 1);
2884
+ } else if (action === "next") {
2885
+ if (cur < max) instance.goToPage?.(cur + 1);
2886
+ } else if (typeof page === "number") {
2887
+ instance.goToPage?.(page);
2888
+ }
2889
+ }
2890
+ });
2891
+ }
2892
+ actions.push(
2481
2893
  {
2482
2894
  id: "zoom-out",
2483
2895
  icon: "zoom-out",
@@ -2498,6 +2910,26 @@ var CodePlugin = class {
2498
2910
  instance.zoomIn?.();
2499
2911
  }
2500
2912
  },
2913
+ {
2914
+ id: "fit-page",
2915
+ icon: "fit-page",
2916
+ label: "Fit to View",
2917
+ type: "button",
2918
+ group: "zoom",
2919
+ execute: () => {
2920
+ instance.fitToPage?.();
2921
+ }
2922
+ },
2923
+ {
2924
+ id: "rotate-cw",
2925
+ icon: "rotate-cw",
2926
+ label: "Rotate",
2927
+ type: "button",
2928
+ group: "view",
2929
+ execute: () => {
2930
+ instance.rotateCW?.();
2931
+ }
2932
+ },
2501
2933
  {
2502
2934
  id: "copy",
2503
2935
  icon: "copy",
@@ -2528,11 +2960,16 @@ var CodePlugin = class {
2528
2960
  instance.print?.();
2529
2961
  }
2530
2962
  }
2531
- ];
2963
+ );
2964
+ return actions;
2532
2965
  }
2533
2966
  async render(ctx) {
2534
2967
  const decoder = new TextDecoder("utf-8");
2535
- const text = decoder.decode(ctx.buffer);
2968
+ const fullText = decoder.decode(ctx.buffer);
2969
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2970
+ const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
2971
+ const totalPages = Math.max(1, rawPages.length);
2972
+ let currentPage = 1;
2536
2973
  const container = document.createElement("div");
2537
2974
  container.style.width = "100%";
2538
2975
  container.style.height = "100%";
@@ -2542,6 +2979,7 @@ var CodePlugin = class {
2542
2979
  container.style.padding = "16px";
2543
2980
  container.style.boxSizing = "border-box";
2544
2981
  let fontSize = 13;
2982
+ let rotation = 0;
2545
2983
  const pre = document.createElement("pre");
2546
2984
  pre.style.margin = "0";
2547
2985
  pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
@@ -2551,25 +2989,66 @@ var CodePlugin = class {
2551
2989
  pre.style.wordBreak = "break-all";
2552
2990
  const code = document.createElement("code");
2553
2991
  const ext = (ctx.metadata.extension || "").replace(".", "");
2554
- try {
2555
- if (ext && hljs__default.default.getLanguage(ext)) {
2556
- code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
2557
- } else {
2558
- code.innerHTML = hljs__default.default.highlightAuto(text).value;
2992
+ const renderCodePage = (text) => {
2993
+ try {
2994
+ if (ext && hljs__default.default.getLanguage(ext)) {
2995
+ code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
2996
+ } else {
2997
+ code.innerHTML = hljs__default.default.highlightAuto(text).value;
2998
+ }
2999
+ } catch {
3000
+ code.textContent = text;
2559
3001
  }
2560
- } catch {
2561
- code.textContent = text;
2562
- }
3002
+ };
3003
+ renderCodePage(rawPages[0] || fullText);
2563
3004
  pre.appendChild(code);
2564
3005
  container.appendChild(pre);
3006
+ let indicator = null;
3007
+ if (totalPages > 1) {
3008
+ indicator = document.createElement("div");
3009
+ indicator.className = "fp-code-page-indicator";
3010
+ indicator.style.position = "sticky";
3011
+ indicator.style.bottom = "16px";
3012
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3013
+ indicator.style.backdropFilter = "blur(8px)";
3014
+ indicator.style.color = "#f8fafc";
3015
+ indicator.style.fontSize = "12px";
3016
+ indicator.style.fontWeight = "600";
3017
+ indicator.style.padding = "5px 14px";
3018
+ indicator.style.borderRadius = "20px";
3019
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3020
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3021
+ indicator.style.zIndex = "10";
3022
+ indicator.style.userSelect = "none";
3023
+ indicator.style.pointerEvents = "none";
3024
+ indicator.style.textAlign = "center";
3025
+ indicator.style.width = "fit-content";
3026
+ indicator.style.margin = "16px auto 0";
3027
+ container.appendChild(indicator);
3028
+ }
3029
+ const showPage = (pageNum) => {
3030
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3031
+ renderCodePage(rawPages[currentPage - 1] || fullText);
3032
+ if (indicator) {
3033
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3034
+ }
3035
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3036
+ };
3037
+ if (totalPages > 1) {
3038
+ showPage(1);
3039
+ }
2565
3040
  ctx.container.appendChild(container);
2566
3041
  const cleanup = () => {
3042
+ indicator?.remove();
2567
3043
  container.remove();
2568
3044
  ctx.container.innerHTML = "";
2569
3045
  };
2570
3046
  ctx.signal.addEventListener("abort", cleanup);
2571
3047
  return {
2572
3048
  destroy: cleanup,
3049
+ getPageCount: () => totalPages,
3050
+ getCurrentPage: () => currentPage,
3051
+ goToPage: (page) => showPage(page),
2573
3052
  zoomIn: () => {
2574
3053
  fontSize = Math.min(32, fontSize + 2);
2575
3054
  pre.style.fontSize = `${fontSize}px`;
@@ -2583,6 +3062,22 @@ var CodePlugin = class {
2583
3062
  fontSize = Math.round(13 * level);
2584
3063
  pre.style.fontSize = `${fontSize}px`;
2585
3064
  },
3065
+ fitToPage: () => {
3066
+ fontSize = 13;
3067
+ rotation = 0;
3068
+ pre.style.fontSize = "13px";
3069
+ pre.style.transform = "none";
3070
+ },
3071
+ rotateCW: () => {
3072
+ rotation = (rotation + 90) % 360;
3073
+ pre.style.transform = `rotate(${rotation}deg)`;
3074
+ pre.style.transformOrigin = "top left";
3075
+ },
3076
+ rotateCCW: () => {
3077
+ rotation = (rotation - 90 + 360) % 360;
3078
+ pre.style.transform = `rotate(${rotation}deg)`;
3079
+ pre.style.transformOrigin = "top left";
3080
+ },
2586
3081
  download: () => {
2587
3082
  const mimeType = ctx.metadata.mimeType || "text/plain";
2588
3083
  const blob = new Blob([ctx.buffer], { type: mimeType });
@@ -2597,7 +3092,7 @@ var CodePlugin = class {
2597
3092
  window.print();
2598
3093
  },
2599
3094
  copy: () => {
2600
- navigator.clipboard?.writeText(text);
3095
+ navigator.clipboard?.writeText(fullText);
2601
3096
  }
2602
3097
  };
2603
3098
  }
@@ -3094,6 +3589,14 @@ var PptxPlugin = class {
3094
3589
  group: "zoom",
3095
3590
  execute: () => instance.fitToPage?.()
3096
3591
  },
3592
+ {
3593
+ id: "rotate-cw",
3594
+ icon: "rotate-cw",
3595
+ label: "Rotate",
3596
+ type: "button",
3597
+ group: "view",
3598
+ execute: () => instance.rotateCW?.()
3599
+ },
3097
3600
  {
3098
3601
  id: "download",
3099
3602
  icon: "download",
@@ -3118,6 +3621,7 @@ var PptxPlugin = class {
3118
3621
  const slideCount = renderer.slidePaths?.length || 1;
3119
3622
  let currentSlide = 1;
3120
3623
  let scale = 1;
3624
+ let rotation = 0;
3121
3625
  const wrapper = document.createElement("div");
3122
3626
  wrapper.className = "fp-pptx-wrapper";
3123
3627
  wrapper.style.cssText = `
@@ -3140,6 +3644,8 @@ var PptxPlugin = class {
3140
3644
  background: #ffffff;
3141
3645
  transform-origin: top center;
3142
3646
  transition: transform 0.2s ease;
3647
+ max-width: calc(100% - 32px);
3648
+ max-height: calc(100% - 48px);
3143
3649
  `;
3144
3650
  const canvas = document.createElement("canvas");
3145
3651
  slideContainer.appendChild(canvas);
@@ -3147,10 +3653,22 @@ var PptxPlugin = class {
3147
3653
  ctx.container.innerHTML = "";
3148
3654
  ctx.container.style.overflow = "auto";
3149
3655
  ctx.container.appendChild(wrapper);
3656
+ const calculateFitScale = () => {
3657
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3658
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3659
+ const cW = canvas.offsetWidth || 1280;
3660
+ const cH = canvas.offsetHeight || 720;
3661
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3662
+ };
3663
+ const applyTransform = () => {
3664
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3665
+ };
3150
3666
  const renderCurrentSlide = async () => {
3151
3667
  try {
3152
3668
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3153
3669
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3670
+ scale = calculateFitScale();
3671
+ applyTransform();
3154
3672
  } catch (err) {
3155
3673
  console.error("[PptxPlugin] Failed to render slide:", err);
3156
3674
  }
@@ -3173,21 +3691,30 @@ var PptxPlugin = class {
3173
3691
  getPageCount: () => slideCount,
3174
3692
  getCurrentPage: () => currentSlide,
3175
3693
  zoomIn: () => {
3176
- scale += 0.1;
3177
- slideContainer.style.transform = `scale(${scale})`;
3694
+ scale += 0.15;
3695
+ applyTransform();
3178
3696
  },
3179
3697
  zoomOut: () => {
3180
- scale = Math.max(0.2, scale - 0.1);
3181
- slideContainer.style.transform = `scale(${scale})`;
3698
+ scale = Math.max(0.2, scale - 0.15);
3699
+ applyTransform();
3182
3700
  },
3183
3701
  getZoom: () => scale,
3184
3702
  setZoom: (level) => {
3185
3703
  scale = level;
3186
- slideContainer.style.transform = `scale(${scale})`;
3704
+ applyTransform();
3187
3705
  },
3188
3706
  fitToPage: () => {
3189
- scale = 1;
3190
- slideContainer.style.transform = "scale(1)";
3707
+ scale = calculateFitScale();
3708
+ rotation = 0;
3709
+ applyTransform();
3710
+ },
3711
+ rotateCW: () => {
3712
+ rotation = (rotation + 90) % 360;
3713
+ applyTransform();
3714
+ },
3715
+ rotateCCW: () => {
3716
+ rotation = (rotation - 90 + 360) % 360;
3717
+ applyTransform();
3191
3718
  },
3192
3719
  getThumbnails: () => {
3193
3720
  const list = [];
@@ -3419,7 +3946,31 @@ var RtfPlugin = class {
3419
3946
  return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3420
3947
  }
3421
3948
  getToolbarActions(instance) {
3422
- return [
3949
+ const totalPages = instance.getPageCount?.() ?? 1;
3950
+ const actions = [];
3951
+ if (totalPages > 1) {
3952
+ actions.push({
3953
+ id: "page-nav",
3954
+ icon: "",
3955
+ label: "Page Navigation",
3956
+ type: "page-nav",
3957
+ group: "navigation",
3958
+ value: instance.getCurrentPage?.() ?? 1,
3959
+ max: totalPages,
3960
+ execute: (action, page) => {
3961
+ const cur = instance.getCurrentPage?.() ?? 1;
3962
+ const max = instance.getPageCount?.() ?? 1;
3963
+ if (action === "prev") {
3964
+ if (cur > 1) instance.goToPage?.(cur - 1);
3965
+ } else if (action === "next") {
3966
+ if (cur < max) instance.goToPage?.(cur + 1);
3967
+ } else if (typeof page === "number") {
3968
+ instance.goToPage?.(page);
3969
+ }
3970
+ }
3971
+ });
3972
+ }
3973
+ actions.push(
3423
3974
  {
3424
3975
  id: "zoom-out",
3425
3976
  icon: "zoom-out",
@@ -3444,6 +3995,14 @@ var RtfPlugin = class {
3444
3995
  group: "zoom",
3445
3996
  execute: () => instance.fitToPage?.()
3446
3997
  },
3998
+ {
3999
+ id: "rotate-cw",
4000
+ icon: "rotate-cw",
4001
+ label: "Rotate",
4002
+ type: "button",
4003
+ group: "view",
4004
+ execute: () => instance.rotateCW?.()
4005
+ },
3447
4006
  {
3448
4007
  id: "download",
3449
4008
  icon: "download",
@@ -3460,7 +4019,8 @@ var RtfPlugin = class {
3460
4019
  group: "actions",
3461
4020
  execute: () => instance.print?.()
3462
4021
  }
3463
- ];
4022
+ );
4023
+ return actions;
3464
4024
  }
3465
4025
  async render(ctx) {
3466
4026
  const wrapper = document.createElement("div");
@@ -3479,44 +4039,124 @@ var RtfPlugin = class {
3479
4039
  ctx.container.style.backgroundColor = "#f1f5f9";
3480
4040
  ctx.container.appendChild(wrapper);
3481
4041
  let scale = 1;
4042
+ let rotation = 0;
4043
+ let pageElements = [];
4044
+ const calculateFitScale = () => {
4045
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4046
+ const elW = activeEl.offsetWidth || 850;
4047
+ const elH = activeEl.offsetHeight || 1e3;
4048
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4049
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4050
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4051
+ };
4052
+ const applyTransform = () => {
4053
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4054
+ wrapper.style.transformOrigin = "top center";
4055
+ };
4056
+ setTimeout(() => {
4057
+ scale = calculateFitScale();
4058
+ applyTransform();
4059
+ }, 60);
3482
4060
  try {
3483
4061
  if (typeof RTFJS__namespace.loggingEnabled === "function") {
3484
4062
  RTFJS__namespace.loggingEnabled(false);
3485
4063
  }
3486
4064
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
3487
4065
  const htmlElements = await doc.render();
3488
- for (const el of htmlElements) {
4066
+ pageElements = htmlElements;
4067
+ for (let i = 0; i < htmlElements.length; i++) {
4068
+ const el = htmlElements[i];
4069
+ el.style.display = i === 0 ? "block" : "none";
3489
4070
  wrapper.appendChild(el);
3490
4071
  }
3491
4072
  } catch (err) {
3492
4073
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3493
4074
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3494
4075
  const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3495
- wrapper.innerHTML = `<pre style="white-space: pre-wrap; font-family: serif; color: #333;">${clean}</pre>`;
4076
+ const pre = document.createElement("pre");
4077
+ pre.style.whiteSpace = "pre-wrap";
4078
+ pre.style.fontFamily = "serif";
4079
+ pre.style.color = "#333";
4080
+ pre.textContent = clean;
4081
+ wrapper.appendChild(pre);
4082
+ pageElements = [pre];
4083
+ }
4084
+ const totalPages = Math.max(1, pageElements.length);
4085
+ let currentPage = 1;
4086
+ let indicator = null;
4087
+ if (totalPages > 1) {
4088
+ indicator = document.createElement("div");
4089
+ indicator.className = "fp-rtf-page-indicator";
4090
+ indicator.style.position = "sticky";
4091
+ indicator.style.bottom = "16px";
4092
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4093
+ indicator.style.backdropFilter = "blur(8px)";
4094
+ indicator.style.color = "#f8fafc";
4095
+ indicator.style.fontSize = "12px";
4096
+ indicator.style.fontWeight = "600";
4097
+ indicator.style.padding = "5px 14px";
4098
+ indicator.style.borderRadius = "20px";
4099
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4100
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4101
+ indicator.style.zIndex = "10";
4102
+ indicator.style.userSelect = "none";
4103
+ indicator.style.pointerEvents = "none";
4104
+ indicator.style.textAlign = "center";
4105
+ indicator.style.width = "fit-content";
4106
+ indicator.style.margin = "16px auto 0";
4107
+ ctx.container.appendChild(indicator);
4108
+ }
4109
+ const showPage = (pageNum) => {
4110
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4111
+ if (totalPages > 1) {
4112
+ pageElements.forEach((el, idx) => {
4113
+ el.style.display = idx + 1 === currentPage ? "block" : "none";
4114
+ });
4115
+ }
4116
+ if (indicator) {
4117
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4118
+ }
4119
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4120
+ };
4121
+ if (totalPages > 1) {
4122
+ showPage(1);
3496
4123
  }
3497
4124
  const cleanup = () => {
4125
+ indicator?.remove();
3498
4126
  wrapper.remove();
3499
4127
  ctx.container.innerHTML = "";
3500
4128
  };
3501
4129
  ctx.signal.addEventListener("abort", cleanup);
3502
4130
  return {
3503
4131
  destroy: cleanup,
4132
+ getPageCount: () => totalPages,
4133
+ getCurrentPage: () => currentPage,
4134
+ goToPage: (page) => showPage(page),
3504
4135
  zoomIn: () => {
3505
- scale += 0.1;
3506
- wrapper.style.transform = `scale(${scale})`;
4136
+ scale += 0.15;
4137
+ applyTransform();
3507
4138
  },
3508
4139
  zoomOut: () => {
3509
- scale = Math.max(0.2, scale - 0.1);
3510
- wrapper.style.transform = `scale(${scale})`;
4140
+ scale = Math.max(0.2, scale - 0.15);
4141
+ applyTransform();
3511
4142
  },
3512
4143
  getZoom: () => scale,
3513
4144
  setZoom: (level) => {
3514
4145
  scale = level;
3515
- wrapper.style.transform = `scale(${scale})`;
4146
+ applyTransform();
3516
4147
  },
3517
4148
  fitToPage: () => {
3518
- scale = 1;
3519
- wrapper.style.transform = "scale(1)";
4149
+ scale = calculateFitScale();
4150
+ rotation = 0;
4151
+ applyTransform();
4152
+ },
4153
+ rotateCW: () => {
4154
+ rotation = (rotation + 90) % 360;
4155
+ applyTransform();
4156
+ },
4157
+ rotateCCW: () => {
4158
+ rotation = (rotation - 90 + 360) % 360;
4159
+ applyTransform();
3520
4160
  },
3521
4161
  download: () => {
3522
4162
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3688,41 +4328,41 @@ var OpenDocumentPlugin = class {
3688
4328
  }
3689
4329
  getToolbarActions(instance) {
3690
4330
  const isPresentation = instance.isPresentation;
4331
+ const totalPages = instance.getPageCount?.() ?? 1;
3691
4332
  const actions = [];
3692
- if (isPresentation) {
3693
- actions.push(
3694
- {
4333
+ if (isPresentation || totalPages > 1) {
4334
+ if (isPresentation) {
4335
+ actions.push({
3695
4336
  id: "thumbnails",
3696
4337
  icon: "thumbnails",
3697
4338
  label: "Slide Thumbnails",
3698
4339
  type: "button",
3699
4340
  group: "navigation",
3700
4341
  execute: () => instance.toggleThumbnails?.()
3701
- },
3702
- {
3703
- id: "page-nav",
3704
- icon: "",
3705
- label: "Slide Navigation",
3706
- type: "page-nav",
3707
- group: "navigation",
3708
- value: instance.getCurrentPage?.() ?? 1,
3709
- max: instance.getPageCount?.() ?? 1,
3710
- execute: (action, page) => {
3711
- if (action === "prev") {
3712
- const cur = instance.getCurrentPage?.() ?? 1;
3713
- if (cur > 1) instance.goToPage?.(cur - 1);
3714
- } else if (action === "next") {
3715
- const cur = instance.getCurrentPage?.() ?? 1;
3716
- const total = instance.getPageCount?.() ?? 1;
3717
- if (cur < total) instance.goToPage?.(cur + 1);
3718
- } else if (typeof page === "number") {
3719
- instance.goToPage?.(page);
3720
- } else if (typeof action === "number") {
3721
- instance.goToPage?.(action);
3722
- }
4342
+ });
4343
+ }
4344
+ actions.push({
4345
+ id: "page-nav",
4346
+ icon: "",
4347
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
4348
+ type: "page-nav",
4349
+ group: "navigation",
4350
+ value: instance.getCurrentPage?.() ?? 1,
4351
+ max: totalPages,
4352
+ execute: (action, page) => {
4353
+ const cur = instance.getCurrentPage?.() ?? 1;
4354
+ const max = instance.getPageCount?.() ?? 1;
4355
+ if (action === "prev") {
4356
+ if (cur > 1) instance.goToPage?.(cur - 1);
4357
+ } else if (action === "next") {
4358
+ if (cur < max) instance.goToPage?.(cur + 1);
4359
+ } else if (typeof page === "number") {
4360
+ instance.goToPage?.(page);
4361
+ } else if (typeof action === "number") {
4362
+ instance.goToPage?.(action);
3723
4363
  }
3724
4364
  }
3725
- );
4365
+ });
3726
4366
  }
3727
4367
  actions.push(
3728
4368
  {
@@ -3744,11 +4384,19 @@ var OpenDocumentPlugin = class {
3744
4384
  {
3745
4385
  id: "fit-page",
3746
4386
  icon: "fit-page",
3747
- label: "Fit to Page",
4387
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
3748
4388
  type: "button",
3749
4389
  group: "zoom",
3750
4390
  execute: () => instance.fitToPage?.()
3751
4391
  },
4392
+ {
4393
+ id: "rotate-cw",
4394
+ icon: "rotate-cw",
4395
+ label: "Rotate",
4396
+ type: "button",
4397
+ group: "view",
4398
+ execute: () => instance.rotateCW?.()
4399
+ },
3752
4400
  {
3753
4401
  id: "download",
3754
4402
  icon: "download",
@@ -3812,6 +4460,22 @@ var OpenDocumentPlugin = class {
3812
4460
  container.appendChild(wrapper);
3813
4461
  ctx.container.appendChild(container);
3814
4462
  let scale = 1;
4463
+ let rotation = 0;
4464
+ const calculateFitScale = () => {
4465
+ const elW = wrapper.offsetWidth || 850;
4466
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4467
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4468
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4469
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4470
+ };
4471
+ const applyTransform = () => {
4472
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4473
+ wrapper.style.transformOrigin = "top center";
4474
+ };
4475
+ setTimeout(() => {
4476
+ scale = calculateFitScale();
4477
+ applyTransform();
4478
+ }, 60);
3815
4479
  let currentPage = 1;
3816
4480
  let totalPages = 1;
3817
4481
  let slides = [];
@@ -3870,21 +4534,30 @@ var OpenDocumentPlugin = class {
3870
4534
  destroy: cleanup,
3871
4535
  isPresentation,
3872
4536
  zoomIn: () => {
3873
- scale += 0.1;
3874
- wrapper.style.transform = `scale(${scale})`;
4537
+ scale += 0.15;
4538
+ applyTransform();
3875
4539
  },
3876
4540
  zoomOut: () => {
3877
- scale = Math.max(0.2, scale - 0.1);
3878
- wrapper.style.transform = `scale(${scale})`;
4541
+ scale = Math.max(0.2, scale - 0.15);
4542
+ applyTransform();
3879
4543
  },
3880
4544
  getZoom: () => scale,
3881
4545
  setZoom: (level) => {
3882
4546
  scale = level;
3883
- wrapper.style.transform = `scale(${scale})`;
4547
+ applyTransform();
3884
4548
  },
3885
4549
  fitToPage: () => {
3886
- scale = 1;
3887
- wrapper.style.transform = "scale(1)";
4550
+ scale = calculateFitScale();
4551
+ rotation = 0;
4552
+ applyTransform();
4553
+ },
4554
+ rotateCW: () => {
4555
+ rotation = (rotation + 90) % 360;
4556
+ applyTransform();
4557
+ },
4558
+ rotateCCW: () => {
4559
+ rotation = (rotation - 90 + 360) % 360;
4560
+ applyTransform();
3888
4561
  },
3889
4562
  goToPage,
3890
4563
  getPageCount: () => totalPages,
@@ -4105,7 +4778,31 @@ var DocPlugin = class {
4105
4778
  return this.mimeTypes.includes(mime || "");
4106
4779
  }
4107
4780
  getToolbarActions(instance) {
4108
- return [
4781
+ const totalPages = instance.getPageCount?.() ?? 1;
4782
+ const actions = [];
4783
+ if (totalPages > 1) {
4784
+ actions.push({
4785
+ id: "page-nav",
4786
+ icon: "",
4787
+ label: "Page Navigation",
4788
+ type: "page-nav",
4789
+ group: "navigation",
4790
+ value: instance.getCurrentPage?.() ?? 1,
4791
+ max: totalPages,
4792
+ execute: (action, page) => {
4793
+ const cur = instance.getCurrentPage?.() ?? 1;
4794
+ const max = instance.getPageCount?.() ?? 1;
4795
+ if (action === "prev") {
4796
+ if (cur > 1) instance.goToPage?.(cur - 1);
4797
+ } else if (action === "next") {
4798
+ if (cur < max) instance.goToPage?.(cur + 1);
4799
+ } else if (typeof page === "number") {
4800
+ instance.goToPage?.(page);
4801
+ }
4802
+ }
4803
+ });
4804
+ }
4805
+ actions.push(
4109
4806
  {
4110
4807
  id: "zoom-out",
4111
4808
  icon: "zoom-out",
@@ -4130,6 +4827,14 @@ var DocPlugin = class {
4130
4827
  group: "zoom",
4131
4828
  execute: () => instance.fitToPage?.()
4132
4829
  },
4830
+ {
4831
+ id: "rotate-cw",
4832
+ icon: "rotate-cw",
4833
+ label: "Rotate",
4834
+ type: "button",
4835
+ group: "view",
4836
+ execute: () => instance.rotateCW?.()
4837
+ },
4133
4838
  {
4134
4839
  id: "copy",
4135
4840
  icon: "copy",
@@ -4154,7 +4859,8 @@ var DocPlugin = class {
4154
4859
  group: "actions",
4155
4860
  execute: () => instance.print?.()
4156
4861
  }
4157
- ];
4862
+ );
4863
+ return actions;
4158
4864
  }
4159
4865
  async render(ctx) {
4160
4866
  const container = document.createElement("div");
@@ -4181,6 +4887,7 @@ var DocPlugin = class {
4181
4887
  ctx.container.appendChild(container);
4182
4888
  let scale = 1;
4183
4889
  let extractedRawText = "";
4890
+ let isFallback = false;
4184
4891
  try {
4185
4892
  const cfbf = new CfbfReader(ctx.buffer);
4186
4893
  const wordDocStream = cfbf.readStream("WordDocument");
@@ -4194,42 +4901,131 @@ var DocPlugin = class {
4194
4901
  const tableStream = cfbf.readStream(tableName);
4195
4902
  const text = this.extractDocText(wordDocStream, tableStream);
4196
4903
  extractedRawText = text;
4197
- wrapper.innerHTML = this.formatDocToHtml(text, ctx.metadata.name || "Document");
4198
4904
  } catch (err) {
4199
4905
  console.warn("[DocPlugin] Binary parsing error, fallback text:", err);
4200
4906
  const fallback = this.heuristicTextExtraction(ctx.buffer);
4201
4907
  extractedRawText = fallback;
4202
- wrapper.innerHTML = `
4203
- <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4204
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${ctx.metadata.name || "Word Document (.doc)"}</h2>
4205
- <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4206
- </div>
4207
- ${this.formatDocToHtml(fallback, ctx.metadata.name || "Document")}
4208
- `;
4908
+ isFallback = true;
4909
+ }
4910
+ const rawPages = this.splitIntoPages(extractedRawText);
4911
+ const totalPages = Math.max(1, rawPages.length);
4912
+ let currentPage = 1;
4913
+ wrapper.innerHTML = "";
4914
+ const pageCards = [];
4915
+ for (let i = 0; i < totalPages; i++) {
4916
+ const pageCard = document.createElement("div");
4917
+ pageCard.className = "fp-doc-page-card";
4918
+ pageCard.style.backgroundColor = "#ffffff";
4919
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4920
+ pageCard.style.borderRadius = "4px";
4921
+ pageCard.style.padding = "56px 48px";
4922
+ pageCard.style.minHeight = "100%";
4923
+ pageCard.style.display = i === 0 ? "block" : "none";
4924
+ if (isFallback && i === 0) {
4925
+ pageCard.innerHTML = `
4926
+ <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4927
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2__default.default.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4928
+ <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4929
+ </div>
4930
+ ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
4931
+ `;
4932
+ } else {
4933
+ pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4934
+ }
4935
+ wrapper.appendChild(pageCard);
4936
+ pageCards.push(pageCard);
4937
+ }
4938
+ let indicator = null;
4939
+ if (totalPages > 1) {
4940
+ indicator = document.createElement("div");
4941
+ indicator.className = "fp-doc-page-indicator";
4942
+ indicator.style.position = "sticky";
4943
+ indicator.style.bottom = "16px";
4944
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4945
+ indicator.style.backdropFilter = "blur(8px)";
4946
+ indicator.style.color = "#f8fafc";
4947
+ indicator.style.fontSize = "12px";
4948
+ indicator.style.fontWeight = "600";
4949
+ indicator.style.padding = "5px 14px";
4950
+ indicator.style.borderRadius = "20px";
4951
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4952
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4953
+ indicator.style.zIndex = "10";
4954
+ indicator.style.userSelect = "none";
4955
+ indicator.style.pointerEvents = "none";
4956
+ indicator.style.textAlign = "center";
4957
+ indicator.style.width = "fit-content";
4958
+ indicator.style.margin = "16px auto 0";
4959
+ container.appendChild(indicator);
4960
+ }
4961
+ const showPage = (pageNum) => {
4962
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4963
+ if (totalPages > 1) {
4964
+ pageCards.forEach((card, idx) => {
4965
+ card.style.display = idx + 1 === currentPage ? "block" : "none";
4966
+ });
4967
+ }
4968
+ if (indicator) {
4969
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4970
+ }
4971
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4972
+ };
4973
+ if (totalPages > 1) {
4974
+ showPage(1);
4209
4975
  }
4976
+ let rotation = 0;
4977
+ const calculateFitScale = () => {
4978
+ const activeCard = pageCards[currentPage - 1] || wrapper;
4979
+ const elW = activeCard.offsetWidth || 850;
4980
+ const elH = activeCard.offsetHeight || 1e3;
4981
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4982
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4983
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4984
+ };
4985
+ const applyTransform = () => {
4986
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4987
+ wrapper.style.transformOrigin = "top center";
4988
+ };
4989
+ setTimeout(() => {
4990
+ scale = calculateFitScale();
4991
+ applyTransform();
4992
+ }, 60);
4210
4993
  const cleanup = () => {
4994
+ indicator?.remove();
4211
4995
  container.remove();
4212
4996
  ctx.container.innerHTML = "";
4213
4997
  };
4214
4998
  ctx.signal.addEventListener("abort", cleanup);
4215
4999
  return {
4216
5000
  destroy: cleanup,
5001
+ getPageCount: () => totalPages,
5002
+ getCurrentPage: () => currentPage,
5003
+ goToPage: (page) => showPage(page),
4217
5004
  zoomIn: () => {
4218
- scale += 0.1;
4219
- wrapper.style.transform = `scale(${scale})`;
5005
+ scale += 0.15;
5006
+ applyTransform();
4220
5007
  },
4221
5008
  zoomOut: () => {
4222
- scale = Math.max(0.2, scale - 0.1);
4223
- wrapper.style.transform = `scale(${scale})`;
5009
+ scale = Math.max(0.2, scale - 0.15);
5010
+ applyTransform();
4224
5011
  },
4225
5012
  getZoom: () => scale,
4226
5013
  setZoom: (level) => {
4227
5014
  scale = level;
4228
- wrapper.style.transform = `scale(${scale})`;
5015
+ applyTransform();
4229
5016
  },
4230
5017
  fitToPage: () => {
4231
- scale = 1;
4232
- wrapper.style.transform = "scale(1)";
5018
+ scale = calculateFitScale();
5019
+ rotation = 0;
5020
+ applyTransform();
5021
+ },
5022
+ rotateCW: () => {
5023
+ rotation = (rotation + 90) % 360;
5024
+ applyTransform();
5025
+ },
5026
+ rotateCCW: () => {
5027
+ rotation = (rotation - 90 + 360) % 360;
5028
+ applyTransform();
4233
5029
  },
4234
5030
  copy: () => {
4235
5031
  navigator.clipboard.writeText(extractedRawText);
@@ -4345,11 +5141,16 @@ var DocPlugin = class {
4345
5141
  heuristicTextExtraction(buffer) {
4346
5142
  return this.extractStringsFromBytes(new Uint8Array(buffer));
4347
5143
  }
5144
+ splitIntoPages(text) {
5145
+ if (!text) return [""];
5146
+ 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);
5147
+ return parts.length > 0 ? parts : [text];
5148
+ }
4348
5149
  /**
4349
5150
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
4350
5151
  */
4351
5152
  formatDocToHtml(text, filename) {
4352
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x0C/g, "\n\n").split("\n");
5153
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4353
5154
  let html = "";
4354
5155
  let inList = false;
4355
5156
  for (const rawLine of lines) {
@@ -4464,6 +5265,14 @@ var PptPlugin = class {
4464
5265
  group: "zoom",
4465
5266
  execute: () => instance.fitToPage?.()
4466
5267
  },
5268
+ {
5269
+ id: "rotate-cw",
5270
+ icon: "rotate-cw",
5271
+ label: "Rotate",
5272
+ type: "button",
5273
+ group: "view",
5274
+ execute: () => instance.rotateCW?.()
5275
+ },
4467
5276
  {
4468
5277
  id: "download",
4469
5278
  icon: "download",
@@ -4497,22 +5306,36 @@ var PptPlugin = class {
4497
5306
  const slideCard = document.createElement("div");
4498
5307
  slideCard.className = "fp-ppt-slide-card";
4499
5308
  slideCard.style.width = "960px";
4500
- slideCard.style.maxWidth = "92%";
5309
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5310
+ slideCard.style.maxHeight = "calc(100% - 48px)";
4501
5311
  slideCard.style.aspectRatio = "16 / 9";
4502
5312
  slideCard.style.backgroundColor = "#ffffff";
4503
5313
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4504
5314
  slideCard.style.borderRadius = "8px";
4505
5315
  slideCard.style.boxSizing = "border-box";
4506
5316
  slideCard.style.position = "relative";
4507
- slideCard.style.overflow = "hidden";
4508
- slideCard.style.transformOrigin = "center center";
4509
5317
  slideCard.style.transition = "transform 0.2s ease";
5318
+ slideCard.style.transformOrigin = "center center";
5319
+ slideCard.style.flexShrink = "0";
4510
5320
  container.appendChild(slideCard);
4511
5321
  ctx.container.appendChild(container);
4512
5322
  let scale = 1;
5323
+ let rotation = 0;
4513
5324
  let currentSlide = 1;
4514
5325
  let slides = [];
4515
5326
  const createdBlobUrls = [];
5327
+ const calculateFitScale = () => {
5328
+ const availW = Math.max(200, container.clientWidth - 48);
5329
+ const availH = Math.max(200, container.clientHeight - 72);
5330
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5331
+ };
5332
+ const applyTransform = () => {
5333
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5334
+ };
5335
+ setTimeout(() => {
5336
+ scale = calculateFitScale();
5337
+ applyTransform();
5338
+ }, 60);
4516
5339
  try {
4517
5340
  const cfbf = new CfbfReader(ctx.buffer);
4518
5341
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -4613,21 +5436,30 @@ var PptPlugin = class {
4613
5436
  return {
4614
5437
  destroy: cleanup,
4615
5438
  zoomIn: () => {
4616
- scale += 0.1;
4617
- slideCard.style.transform = `scale(${scale})`;
5439
+ scale += 0.15;
5440
+ applyTransform();
4618
5441
  },
4619
5442
  zoomOut: () => {
4620
- scale = Math.max(0.3, scale - 0.1);
4621
- slideCard.style.transform = `scale(${scale})`;
5443
+ scale = Math.max(0.2, scale - 0.15);
5444
+ applyTransform();
4622
5445
  },
4623
5446
  getZoom: () => scale,
4624
5447
  setZoom: (level) => {
4625
5448
  scale = level;
4626
- slideCard.style.transform = `scale(${scale})`;
5449
+ applyTransform();
4627
5450
  },
4628
5451
  fitToPage: () => {
4629
- scale = 1;
4630
- slideCard.style.transform = "scale(1)";
5452
+ scale = calculateFitScale();
5453
+ rotation = 0;
5454
+ applyTransform();
5455
+ },
5456
+ rotateCW: () => {
5457
+ rotation = (rotation + 90) % 360;
5458
+ applyTransform();
5459
+ },
5460
+ rotateCCW: () => {
5461
+ rotation = (rotation - 90 + 360) % 360;
5462
+ applyTransform();
4631
5463
  },
4632
5464
  goToPage: (page) => {
4633
5465
  if (page >= 1 && page <= totalSlides) {