@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/vue.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { defineComponent, ref, onMounted, watch, onBeforeUnmount, h } from 'vue';
2
2
  import DOMPurify2 from 'dompurify';
3
+ import * as pdfjsLib from 'pdfjs-dist';
3
4
  import * as docx from 'docx-preview';
4
5
  import { unzipSync, strFromU8, unzip } from 'fflate';
5
6
  import * as XLSX from 'xlsx';
@@ -479,11 +480,21 @@ var ToolbarController = class {
479
480
  el;
480
481
  toolbarEl;
481
482
  actions = [];
483
+ pageInputEl = null;
484
+ pageLabelEl = null;
482
485
  constructor(container) {
483
486
  this.el = container;
484
487
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
485
488
  this.el.appendChild(this.toolbarEl);
486
489
  }
490
+ setPage(page, max) {
491
+ if (this.pageInputEl) {
492
+ this.pageInputEl.value = page.toString();
493
+ }
494
+ if (max !== void 0 && this.pageLabelEl) {
495
+ this.pageLabelEl.textContent = ` / ${max}`;
496
+ }
497
+ }
487
498
  update(actions) {
488
499
  this.actions = actions;
489
500
  this.render();
@@ -526,6 +537,7 @@ var ToolbarController = class {
526
537
  if (action.type === "separator") {
527
538
  groupEl.appendChild(createElement("div", { className: "fp-toolbar-separator" }));
528
539
  } else if (action.type === "page-nav") {
540
+ const max = action.max ?? 1;
529
541
  const prevBtn = this.createButton(
530
542
  "prev",
531
543
  ICON_PAGE_PREV,
@@ -544,7 +556,6 @@ var ToolbarController = class {
544
556
  "Next Page",
545
557
  () => {
546
558
  const cur = parseInt(input.value, 10) || 1;
547
- const max = action.max ?? 1;
548
559
  if (cur < max) {
549
560
  input.value = (cur + 1).toString();
550
561
  action.execute("next", cur + 1);
@@ -556,15 +567,18 @@ var ToolbarController = class {
556
567
  type: "number",
557
568
  value: (action.value ?? 1).toString(),
558
569
  min: "1",
559
- max: (action.max ?? 1).toString()
570
+ max: max.toString()
560
571
  });
561
572
  input.addEventListener("change", () => {
562
- const val = parseInt(input.value, 10);
563
- if (!isNaN(val)) {
564
- action.execute("go", val);
565
- }
573
+ let val = parseInt(input.value, 10);
574
+ if (isNaN(val)) val = 1;
575
+ val = Math.max(1, Math.min(max, val));
576
+ input.value = val.toString();
577
+ action.execute("go", val);
566
578
  });
567
- const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
579
+ const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
580
+ this.pageInputEl = input;
581
+ this.pageLabelEl = label;
568
582
  groupEl.appendChild(prevBtn);
569
583
  groupEl.appendChild(input);
570
584
  groupEl.appendChild(label);
@@ -691,6 +705,8 @@ var FilePreviewViewer = class {
691
705
  keyHandler = null;
692
706
  currentContainer = null;
693
707
  currentOptions = {};
708
+ resizeObserver = null;
709
+ fullscreenHandler = null;
694
710
  /**
695
711
  * Register a preview plugin.
696
712
  */
@@ -742,7 +758,13 @@ var FilePreviewViewer = class {
742
758
  buffer,
743
759
  options,
744
760
  signal,
745
- emit: (event, payload) => this.eventEmitter.emit(event, payload)
761
+ emit: (event, payload) => {
762
+ if (event === "page-change" && payload && typeof payload.page === "number") {
763
+ const total = payload.total ?? payload.totalPages;
764
+ this.toolbar?.setPage(payload.page, total);
765
+ }
766
+ this.eventEmitter.emit(event, payload);
767
+ }
746
768
  });
747
769
  this.activeInstance = instance;
748
770
  this.hideLoading();
@@ -757,12 +779,31 @@ var FilePreviewViewer = class {
757
779
  label: "Toggle Fullscreen",
758
780
  type: "button",
759
781
  group: "view",
760
- execute: () => {
761
- if (!document.fullscreenElement) {
762
- this.wrapperEl?.requestFullscreen?.();
763
- } else {
764
- document.exitFullscreen?.();
782
+ execute: async () => {
783
+ try {
784
+ const isNativeFs = !!document.fullscreenElement;
785
+ const isCssFs = this.wrapperEl?.classList.contains("fp-fullscreen-active");
786
+ if (!isNativeFs && !isCssFs) {
787
+ if (this.wrapperEl?.requestFullscreen) {
788
+ await this.wrapperEl.requestFullscreen().catch(() => {
789
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
790
+ });
791
+ } else {
792
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
793
+ }
794
+ } else {
795
+ if (document.fullscreenElement) {
796
+ await document.exitFullscreen().catch(() => {
797
+ });
798
+ }
799
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
800
+ }
801
+ } catch {
802
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
765
803
  }
804
+ setTimeout(() => {
805
+ this.activeInstance?.fitToPage?.();
806
+ }, 120);
766
807
  }
767
808
  });
768
809
  }
@@ -776,6 +817,7 @@ var FilePreviewViewer = class {
776
817
  if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
777
818
  this.thumbnailPanel.update(thumbnails, (index) => {
778
819
  instance.goToPage?.(index + 1);
820
+ this.toolbar?.setPage(index + 1);
779
821
  });
780
822
  if (options.showThumbnails) {
781
823
  this.thumbnailPanel.show();
@@ -811,6 +853,15 @@ var FilePreviewViewer = class {
811
853
  window.removeEventListener("keydown", this.keyHandler);
812
854
  this.keyHandler = null;
813
855
  }
856
+ if (this.resizeObserver) {
857
+ this.resizeObserver.disconnect();
858
+ this.resizeObserver = null;
859
+ }
860
+ if (this.fullscreenHandler) {
861
+ document.removeEventListener("fullscreenchange", this.fullscreenHandler);
862
+ document.removeEventListener("webkitfullscreenchange", this.fullscreenHandler);
863
+ this.fullscreenHandler = null;
864
+ }
814
865
  this.abort();
815
866
  this.destroyInstance();
816
867
  this.toolbar?.destroy();
@@ -890,6 +941,25 @@ var FilePreviewViewer = class {
890
941
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
891
942
  this.setupKeyboardShortcuts();
892
943
  this.setupDragAndDrop(container, options);
944
+ this.setupResizeAndFullscreenListeners();
945
+ }
946
+ setupResizeAndFullscreenListeners() {
947
+ if (this.fullscreenHandler) return;
948
+ this.fullscreenHandler = () => {
949
+ setTimeout(() => {
950
+ this.activeInstance?.fitToPage?.();
951
+ }, 100);
952
+ };
953
+ document.addEventListener("fullscreenchange", this.fullscreenHandler);
954
+ document.addEventListener("webkitfullscreenchange", this.fullscreenHandler);
955
+ if (typeof ResizeObserver !== "undefined" && this.contentEl) {
956
+ this.resizeObserver = new ResizeObserver(() => {
957
+ if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
958
+ this.activeInstance.fitToPage?.();
959
+ }
960
+ });
961
+ this.resizeObserver.observe(this.contentEl);
962
+ }
893
963
  }
894
964
  setupKeyboardShortcuts() {
895
965
  if (this.keyHandler) return;
@@ -901,9 +971,13 @@ var FilePreviewViewer = class {
901
971
  if (e.key === "ArrowRight" || e.key === "PageDown") {
902
972
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
903
973
  this.activeInstance.goToPage?.(cur + 1);
974
+ const nextCur = this.activeInstance.getCurrentPage?.() ?? cur + 1;
975
+ this.toolbar?.setPage(nextCur);
904
976
  } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
905
977
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
906
978
  this.activeInstance.goToPage?.(Math.max(1, cur - 1));
979
+ const prevCur = this.activeInstance.getCurrentPage?.() ?? Math.max(1, cur - 1);
980
+ this.toolbar?.setPage(prevCur);
907
981
  } else if (e.key === "+" || e.key === "=") {
908
982
  this.activeInstance.zoomIn?.();
909
983
  } else if (e.key === "-" || e.key === "_") {
@@ -1169,30 +1243,62 @@ var CfbfReader = class {
1169
1243
  return result.subarray(0, targetSize);
1170
1244
  }
1171
1245
  };
1172
-
1173
- // ../plugins/pdf/dist/index.js
1246
+ if (typeof window !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
1247
+ if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
1248
+ pdfjsLib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1249
+ }
1250
+ }
1174
1251
  var PdfPlugin = class {
1175
1252
  id = "pdf";
1176
- name = "PDF Preview";
1253
+ name = "PDF Document Preview";
1177
1254
  extensions = [".pdf"];
1178
1255
  mimeTypes = ["application/pdf"];
1179
1256
  weight = 100;
1180
1257
  supports(file) {
1181
1258
  const ext = file.metadata.extension?.toLowerCase();
1182
1259
  const mime = file.metadata.mimeType?.toLowerCase();
1183
- return ext === ".pdf" || mime === "application/pdf";
1260
+ if (ext) return this.extensions.includes(ext);
1261
+ return this.mimeTypes.includes(mime || "");
1184
1262
  }
1185
1263
  getToolbarActions(instance) {
1264
+ const totalPages = instance.getPageCount?.() ?? 1;
1265
+ const curPage = instance.getCurrentPage?.() ?? 1;
1186
1266
  return [
1267
+ {
1268
+ id: "thumbnails",
1269
+ icon: "thumbnails",
1270
+ label: "Page Thumbnails",
1271
+ type: "button",
1272
+ group: "navigation",
1273
+ execute: () => instance.toggleThumbnails?.()
1274
+ },
1275
+ {
1276
+ id: "page-nav",
1277
+ icon: "",
1278
+ label: "Page Navigation",
1279
+ type: "page-nav",
1280
+ group: "navigation",
1281
+ value: curPage,
1282
+ max: totalPages,
1283
+ execute: (action, page) => {
1284
+ const cur = instance.getCurrentPage?.() ?? 1;
1285
+ const max = instance.getPageCount?.() ?? 1;
1286
+ if (action === "prev") {
1287
+ if (cur > 1) instance.goToPage?.(cur - 1);
1288
+ } else if (action === "next") {
1289
+ if (cur < max) instance.goToPage?.(cur + 1);
1290
+ } else if (typeof page === "number") {
1291
+ instance.goToPage?.(page);
1292
+ }
1293
+ }
1294
+ },
1187
1295
  {
1188
1296
  id: "zoom-out",
1189
1297
  icon: "zoom-out",
1190
1298
  label: "Zoom Out",
1191
1299
  type: "button",
1192
1300
  group: "zoom",
1193
- execute: () => {
1194
- instance.zoomOut?.();
1195
- }
1301
+ execute: () => instance.zoomOut?.()
1196
1302
  },
1197
1303
  {
1198
1304
  id: "zoom-in",
@@ -1200,9 +1306,7 @@ var PdfPlugin = class {
1200
1306
  label: "Zoom In",
1201
1307
  type: "button",
1202
1308
  group: "zoom",
1203
- execute: () => {
1204
- instance.zoomIn?.();
1205
- }
1309
+ execute: () => instance.zoomIn?.()
1206
1310
  },
1207
1311
  {
1208
1312
  id: "fit-page",
@@ -1210,39 +1314,23 @@ var PdfPlugin = class {
1210
1314
  label: "Fit to Page",
1211
1315
  type: "button",
1212
1316
  group: "zoom",
1213
- execute: () => {
1214
- instance.fitToPage?.();
1215
- }
1317
+ execute: () => instance.fitToPage?.()
1216
1318
  },
1217
1319
  {
1218
1320
  id: "rotate-cw",
1219
1321
  icon: "rotate-cw",
1220
- label: "Rotate",
1322
+ label: "Rotate Clockwise",
1221
1323
  type: "button",
1222
1324
  group: "view",
1223
- execute: () => {
1224
- instance.rotateCW?.();
1225
- }
1226
- },
1227
- {
1228
- id: "page-nav",
1229
- icon: "page-nav",
1230
- label: "Page Navigation",
1231
- type: "page-nav",
1232
- group: "navigation",
1233
- execute: (page) => {
1234
- if (typeof page === "number") instance.goToPage?.(page);
1235
- }
1325
+ execute: () => instance.rotateCW?.()
1236
1326
  },
1237
1327
  {
1238
1328
  id: "download",
1239
1329
  icon: "download",
1240
- label: "Download",
1330
+ label: "Download PDF",
1241
1331
  type: "button",
1242
1332
  group: "actions",
1243
- execute: () => {
1244
- instance.download?.();
1245
- }
1333
+ execute: () => instance.download?.()
1246
1334
  },
1247
1335
  {
1248
1336
  id: "print",
@@ -1250,99 +1338,224 @@ var PdfPlugin = class {
1250
1338
  label: "Print",
1251
1339
  type: "button",
1252
1340
  group: "actions",
1253
- execute: () => {
1254
- instance.print?.();
1255
- }
1341
+ execute: () => instance.print?.()
1256
1342
  }
1257
1343
  ];
1258
1344
  }
1259
1345
  async render(ctx) {
1260
- const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1261
- const url = URL.createObjectURL(blob);
1262
- const wrapper = document.createElement("div");
1263
- wrapper.style.width = "100%";
1264
- wrapper.style.height = "100%";
1265
- wrapper.style.overflow = "hidden";
1266
- wrapper.style.display = "flex";
1267
- wrapper.style.justifyContent = "center";
1268
- wrapper.style.alignItems = "center";
1269
- const iframe = document.createElement("iframe");
1270
- iframe.src = url;
1271
- iframe.style.width = "100%";
1272
- iframe.style.height = "100%";
1273
- iframe.style.border = "none";
1274
- wrapper.appendChild(iframe);
1275
- ctx.container.appendChild(wrapper);
1346
+ const container = document.createElement("div");
1347
+ container.className = "fp-pdf-container";
1348
+ container.style.width = "100%";
1349
+ container.style.height = "100%";
1350
+ container.style.overflow = "auto";
1351
+ container.style.display = "flex";
1352
+ container.style.flexDirection = "column";
1353
+ container.style.alignItems = "center";
1354
+ container.style.justifyContent = "flex-start";
1355
+ container.style.padding = "20px 16px";
1356
+ container.style.backgroundColor = "#0f172a";
1357
+ container.style.boxSizing = "border-box";
1358
+ container.style.position = "relative";
1359
+ const pageCard = document.createElement("div");
1360
+ pageCard.className = "fp-pdf-page-card";
1361
+ pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
1362
+ pageCard.style.backgroundColor = "#ffffff";
1363
+ pageCard.style.borderRadius = "4px";
1364
+ pageCard.style.overflow = "hidden";
1365
+ pageCard.style.lineHeight = "0";
1366
+ pageCard.style.transition = "transform 0.15s ease";
1367
+ pageCard.style.position = "relative";
1368
+ pageCard.style.flexShrink = "0";
1369
+ let canvas = document.createElement("canvas");
1370
+ pageCard.appendChild(canvas);
1371
+ container.appendChild(pageCard);
1372
+ const indicator = document.createElement("div");
1373
+ indicator.className = "fp-pdf-page-indicator";
1374
+ indicator.style.position = "sticky";
1375
+ indicator.style.bottom = "16px";
1376
+ indicator.style.marginTop = "16px";
1377
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1378
+ indicator.style.backdropFilter = "blur(8px)";
1379
+ indicator.style.color = "#f8fafc";
1380
+ indicator.style.fontSize = "12px";
1381
+ indicator.style.fontWeight = "600";
1382
+ indicator.style.padding = "5px 14px";
1383
+ indicator.style.borderRadius = "20px";
1384
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1385
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1386
+ indicator.style.zIndex = "10";
1387
+ indicator.style.userSelect = "none";
1388
+ indicator.style.pointerEvents = "none";
1389
+ container.appendChild(indicator);
1390
+ ctx.container.appendChild(container);
1391
+ const loadingTask = pdfjsLib.getDocument({
1392
+ data: new Uint8Array(ctx.buffer),
1393
+ cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/cmaps/`,
1394
+ cMapPacked: true,
1395
+ standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/standard_fonts/`
1396
+ });
1397
+ const pdfDoc = await loadingTask.promise;
1398
+ const totalPages = Math.max(1, pdfDoc.numPages);
1276
1399
  let currentPage = 1;
1277
- let currentZoom = 1;
1400
+ let zoomScale = 1;
1278
1401
  let rotation = 0;
1402
+ let currentRenderTask = null;
1403
+ const renderPage = async (pageNum) => {
1404
+ if (currentRenderTask) {
1405
+ try {
1406
+ currentRenderTask.cancel();
1407
+ } catch {
1408
+ }
1409
+ currentRenderTask = null;
1410
+ }
1411
+ const newCanvas = document.createElement("canvas");
1412
+ pageCard.replaceChild(newCanvas, canvas);
1413
+ canvas = newCanvas;
1414
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
1415
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1416
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
1417
+ const page = await pdfDoc.getPage(currentPage);
1418
+ const containerWidth = container.clientWidth || 900;
1419
+ const containerHeight = container.clientHeight || 700;
1420
+ const unscaledVp = page.getViewport({ scale: 1, rotation });
1421
+ const availWidth = Math.max(100, containerWidth - 48);
1422
+ const availHeight = Math.max(100, containerHeight - 88);
1423
+ const scaleW = availWidth / unscaledVp.width;
1424
+ const scaleH = availHeight / unscaledVp.height;
1425
+ const fitScale = Math.min(scaleW, scaleH);
1426
+ const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1427
+ const pixelRatio = window.devicePixelRatio || 1;
1428
+ const viewport = page.getViewport({ scale: effectiveScale, rotation });
1429
+ canvas.width = Math.floor(viewport.width * pixelRatio);
1430
+ canvas.height = Math.floor(viewport.height * pixelRatio);
1431
+ canvas.style.width = `${Math.floor(viewport.width)}px`;
1432
+ canvas.style.height = `${Math.floor(viewport.height)}px`;
1433
+ const canvasCtx = canvas.getContext("2d");
1434
+ if (!canvasCtx) return;
1435
+ canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
1436
+ currentRenderTask = page.render({
1437
+ canvasContext: canvasCtx,
1438
+ viewport
1439
+ });
1440
+ try {
1441
+ await currentRenderTask.promise;
1442
+ } catch (err) {
1443
+ if (err?.name !== "RenderingCancelledException") {
1444
+ console.warn("[PdfPlugin] Page render warning:", err);
1445
+ }
1446
+ } finally {
1447
+ currentRenderTask = null;
1448
+ }
1449
+ };
1450
+ await renderPage(1);
1279
1451
  const cleanup = () => {
1280
- URL.revokeObjectURL(url);
1281
- wrapper.remove();
1452
+ if (currentRenderTask) {
1453
+ try {
1454
+ currentRenderTask.cancel();
1455
+ } catch {
1456
+ }
1457
+ }
1458
+ try {
1459
+ pdfDoc.destroy();
1460
+ } catch {
1461
+ }
1462
+ container.remove();
1282
1463
  ctx.container.innerHTML = "";
1283
1464
  };
1284
1465
  ctx.signal.addEventListener("abort", cleanup);
1285
- return {
1466
+ const instance = {
1286
1467
  destroy: cleanup,
1287
1468
  zoomIn: () => {
1288
- currentZoom += 0.1;
1289
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1469
+ zoomScale = Math.min(3.5, zoomScale + 0.2);
1470
+ renderPage(currentPage);
1290
1471
  },
1291
1472
  zoomOut: () => {
1292
- currentZoom = Math.max(0.2, currentZoom - 0.1);
1293
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1473
+ zoomScale = Math.max(0.3, zoomScale - 0.2);
1474
+ renderPage(currentPage);
1294
1475
  },
1295
- getZoom: () => currentZoom,
1476
+ getZoom: () => zoomScale,
1296
1477
  setZoom: (level) => {
1297
- currentZoom = level;
1298
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1478
+ zoomScale = Math.max(0.3, Math.min(3.5, level));
1479
+ renderPage(currentPage);
1299
1480
  },
1300
1481
  fitToPage: () => {
1301
- currentZoom = 1;
1302
- iframe.style.transform = `scale(1) rotate(${rotation}deg)`;
1482
+ zoomScale = 1;
1483
+ rotation = 0;
1484
+ renderPage(currentPage);
1303
1485
  },
1304
1486
  rotateCW: () => {
1305
1487
  rotation = (rotation + 90) % 360;
1306
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1488
+ renderPage(currentPage);
1307
1489
  },
1308
1490
  rotateCCW: () => {
1309
1491
  rotation = (rotation - 90 + 360) % 360;
1310
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1492
+ renderPage(currentPage);
1311
1493
  },
1312
1494
  getRotation: () => rotation,
1495
+ getPageCount: () => totalPages,
1496
+ getCurrentPage: () => currentPage,
1313
1497
  goToPage: (page) => {
1314
- currentPage = page;
1315
- iframe.src = `${url}#page=${page}`;
1498
+ renderPage(page);
1316
1499
  },
1317
- getCurrentPage: () => currentPage,
1318
1500
  download: () => {
1501
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1502
+ const url = URL.createObjectURL(blob);
1319
1503
  const a = document.createElement("a");
1320
1504
  a.href = url;
1321
1505
  a.download = ctx.metadata.name || "document.pdf";
1322
1506
  a.click();
1507
+ URL.revokeObjectURL(url);
1323
1508
  },
1324
1509
  print: () => {
1325
- iframe.contentWindow?.print();
1510
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1511
+ const url = URL.createObjectURL(blob);
1512
+ const hiddenIframe = document.createElement("iframe");
1513
+ hiddenIframe.style.position = "fixed";
1514
+ hiddenIframe.style.right = "0";
1515
+ hiddenIframe.style.bottom = "0";
1516
+ hiddenIframe.style.width = "0";
1517
+ hiddenIframe.style.height = "0";
1518
+ hiddenIframe.style.border = "0";
1519
+ document.body.appendChild(hiddenIframe);
1520
+ hiddenIframe.src = url;
1521
+ hiddenIframe.onload = () => {
1522
+ setTimeout(() => {
1523
+ hiddenIframe.contentWindow?.print();
1524
+ setTimeout(() => {
1525
+ hiddenIframe.remove();
1526
+ URL.revokeObjectURL(url);
1527
+ }, 1e3);
1528
+ }, 300);
1529
+ };
1326
1530
  },
1327
1531
  getThumbnails: async () => {
1328
- return [
1329
- {
1330
- index: 1,
1331
- label: "Page 1",
1332
- render: async (canvas) => {
1333
- const context = canvas.getContext("2d");
1334
- if (context) {
1335
- context.fillStyle = "#fff";
1336
- context.fillRect(0, 0, canvas.width, canvas.height);
1337
- context.fillStyle = "#333";
1338
- context.font = "12px sans-serif";
1339
- context.fillText("PDF Preview", 10, 20);
1532
+ const thumbnails = [];
1533
+ const count = Math.min(totalPages, 50);
1534
+ for (let i = 1; i <= count; i++) {
1535
+ thumbnails.push({
1536
+ index: i,
1537
+ label: `Page ${i}`,
1538
+ render: async (thumbCanvas) => {
1539
+ try {
1540
+ const p = await pdfDoc.getPage(i);
1541
+ const baseVp = p.getViewport({ scale: 1 });
1542
+ const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1543
+ const thumbVp = p.getViewport({ scale: thumbScale });
1544
+ thumbCanvas.height = Math.floor(thumbVp.height);
1545
+ const tCtx = thumbCanvas.getContext("2d");
1546
+ if (tCtx) {
1547
+ await p.render({ canvasContext: tCtx, viewport: thumbVp }).promise;
1548
+ }
1549
+ } catch (e) {
1550
+ console.warn(`[PdfPlugin] Error generating thumbnail for page ${i}:`, e);
1340
1551
  }
1341
1552
  }
1342
- }
1343
- ];
1553
+ });
1554
+ }
1555
+ return thumbnails;
1344
1556
  }
1345
1557
  };
1558
+ return instance;
1346
1559
  }
1347
1560
  };
1348
1561
  function pdfPlugin() {
@@ -1669,7 +1882,31 @@ var DocxPlugin = class {
1669
1882
  return this.mimeTypes.includes(mime || "");
1670
1883
  }
1671
1884
  getToolbarActions(instance) {
1672
- return [
1885
+ const totalPages = instance.getPageCount?.() ?? 1;
1886
+ const actions = [];
1887
+ if (totalPages > 1) {
1888
+ actions.push({
1889
+ id: "page-nav",
1890
+ icon: "",
1891
+ label: "Page Navigation",
1892
+ type: "page-nav",
1893
+ group: "navigation",
1894
+ value: instance.getCurrentPage?.() ?? 1,
1895
+ max: totalPages,
1896
+ execute: (action, page) => {
1897
+ const cur = instance.getCurrentPage?.() ?? 1;
1898
+ const max = instance.getPageCount?.() ?? 1;
1899
+ if (action === "prev") {
1900
+ if (cur > 1) instance.goToPage?.(cur - 1);
1901
+ } else if (action === "next") {
1902
+ if (cur < max) instance.goToPage?.(cur + 1);
1903
+ } else if (typeof page === "number") {
1904
+ instance.goToPage?.(page);
1905
+ }
1906
+ }
1907
+ });
1908
+ }
1909
+ actions.push(
1673
1910
  {
1674
1911
  id: "zoom-out",
1675
1912
  icon: "zoom-out",
@@ -1694,6 +1931,14 @@ var DocxPlugin = class {
1694
1931
  group: "zoom",
1695
1932
  execute: () => instance.fitToPage?.()
1696
1933
  },
1934
+ {
1935
+ id: "rotate-cw",
1936
+ icon: "rotate-cw",
1937
+ label: "Rotate",
1938
+ type: "button",
1939
+ group: "view",
1940
+ execute: () => instance.rotateCW?.()
1941
+ },
1697
1942
  {
1698
1943
  id: "download",
1699
1944
  icon: "download",
@@ -1710,7 +1955,8 @@ var DocxPlugin = class {
1710
1955
  group: "actions",
1711
1956
  execute: () => instance.print?.()
1712
1957
  }
1713
- ];
1958
+ );
1959
+ return actions;
1714
1960
  }
1715
1961
  async render(ctx) {
1716
1962
  const wrapper = document.createElement("div");
@@ -1786,7 +2032,71 @@ var DocxPlugin = class {
1786
2032
  }
1787
2033
  }
1788
2034
  }
2035
+ const sections = wrapper.querySelectorAll("section.docx");
2036
+ const cards = wrapper.querySelectorAll(".fp-docx-page-card");
2037
+ const pageElements = sections.length > 0 ? sections : cards;
2038
+ const totalPages = Math.max(1, pageElements.length);
2039
+ let currentPage = 1;
2040
+ let indicator = null;
2041
+ if (totalPages > 1) {
2042
+ indicator = document.createElement("div");
2043
+ indicator.className = "fp-docx-page-indicator";
2044
+ indicator.style.position = "sticky";
2045
+ indicator.style.bottom = "16px";
2046
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2047
+ indicator.style.backdropFilter = "blur(8px)";
2048
+ indicator.style.color = "#f8fafc";
2049
+ indicator.style.fontSize = "12px";
2050
+ indicator.style.fontWeight = "600";
2051
+ indicator.style.padding = "5px 14px";
2052
+ indicator.style.borderRadius = "20px";
2053
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2054
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2055
+ indicator.style.zIndex = "10";
2056
+ indicator.style.userSelect = "none";
2057
+ indicator.style.pointerEvents = "none";
2058
+ indicator.style.textAlign = "center";
2059
+ indicator.style.width = "fit-content";
2060
+ indicator.style.margin = "16px auto 0";
2061
+ ctx.container.appendChild(indicator);
2062
+ }
2063
+ const showPage = (pageNum) => {
2064
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
2065
+ if (pageElements.length > 1) {
2066
+ pageElements.forEach((sec, idx) => {
2067
+ sec.style.display = idx + 1 === currentPage ? "block" : "none";
2068
+ });
2069
+ }
2070
+ if (indicator) {
2071
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2072
+ }
2073
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
2074
+ };
2075
+ if (totalPages > 1) {
2076
+ showPage(1);
2077
+ }
2078
+ scale = 1;
2079
+ let rotation = 0;
2080
+ const calculateFitScale = () => {
2081
+ const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2082
+ const elW = activeEl.offsetWidth || 816;
2083
+ const elH = activeEl.offsetHeight || 1056;
2084
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
2085
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
2086
+ const sW = availW / elW;
2087
+ const sH = availH / elH;
2088
+ return Math.min(1.1, Math.min(sW, sH));
2089
+ };
2090
+ const applyTransform = () => {
2091
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2092
+ wrapper.style.transformOrigin = "top center";
2093
+ };
2094
+ setTimeout(() => {
2095
+ scale = calculateFitScale();
2096
+ applyTransform();
2097
+ }, 60);
1789
2098
  const cleanup = () => {
2099
+ indicator?.remove();
1790
2100
  for (const url of createdBlobUrls) {
1791
2101
  URL.revokeObjectURL(url);
1792
2102
  }
@@ -1797,22 +2107,36 @@ var DocxPlugin = class {
1797
2107
  ctx.signal.addEventListener("abort", cleanup);
1798
2108
  return {
1799
2109
  destroy: cleanup,
2110
+ getPageCount: () => totalPages,
2111
+ getCurrentPage: () => currentPage,
2112
+ goToPage: (page) => {
2113
+ showPage(page);
2114
+ },
1800
2115
  zoomIn: () => {
1801
- scale += 0.1;
1802
- wrapper.style.transform = `scale(${scale})`;
2116
+ scale += 0.15;
2117
+ applyTransform();
1803
2118
  },
1804
2119
  zoomOut: () => {
1805
- scale = Math.max(0.2, scale - 0.1);
1806
- wrapper.style.transform = `scale(${scale})`;
2120
+ scale = Math.max(0.2, scale - 0.15);
2121
+ applyTransform();
1807
2122
  },
1808
2123
  getZoom: () => scale,
1809
2124
  setZoom: (level) => {
1810
2125
  scale = level;
1811
- wrapper.style.transform = `scale(${scale})`;
2126
+ applyTransform();
1812
2127
  },
1813
2128
  fitToPage: () => {
1814
- scale = 1;
1815
- wrapper.style.transform = "scale(1)";
2129
+ scale = calculateFitScale();
2130
+ rotation = 0;
2131
+ applyTransform();
2132
+ },
2133
+ rotateCW: () => {
2134
+ rotation = (rotation + 90) % 360;
2135
+ applyTransform();
2136
+ },
2137
+ rotateCCW: () => {
2138
+ rotation = (rotation - 90 + 360) % 360;
2139
+ applyTransform();
1816
2140
  },
1817
2141
  download: () => {
1818
2142
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -2061,7 +2385,31 @@ var ExcelPlugin = class {
2061
2385
  return this.mimeTypes.includes(mime || "");
2062
2386
  }
2063
2387
  getToolbarActions(instance) {
2064
- return [
2388
+ const totalSheets = instance.getPageCount?.() ?? 1;
2389
+ const actions = [];
2390
+ if (totalSheets > 1) {
2391
+ actions.push({
2392
+ id: "page-nav",
2393
+ icon: "",
2394
+ label: "Sheet Navigation",
2395
+ type: "page-nav",
2396
+ group: "navigation",
2397
+ value: instance.getCurrentPage?.() ?? 1,
2398
+ max: totalSheets,
2399
+ execute: (action, sheet) => {
2400
+ const cur = instance.getCurrentPage?.() ?? 1;
2401
+ const max = instance.getPageCount?.() ?? 1;
2402
+ if (action === "prev") {
2403
+ if (cur > 1) instance.goToPage?.(cur - 1);
2404
+ } else if (action === "next") {
2405
+ if (cur < max) instance.goToPage?.(cur + 1);
2406
+ } else if (typeof sheet === "number") {
2407
+ instance.goToPage?.(sheet);
2408
+ }
2409
+ }
2410
+ });
2411
+ }
2412
+ actions.push(
2065
2413
  {
2066
2414
  id: "zoom-out",
2067
2415
  icon: "zoom-out",
@@ -2083,13 +2431,23 @@ var ExcelPlugin = class {
2083
2431
  }
2084
2432
  },
2085
2433
  {
2086
- id: "page-nav",
2087
- icon: "page-nav",
2088
- label: "Sheet Navigation",
2089
- type: "page-nav",
2090
- group: "navigation",
2091
- execute: (sheet) => {
2092
- if (typeof sheet === "number") instance.goToPage?.(sheet);
2434
+ id: "fit-page",
2435
+ icon: "fit-page",
2436
+ label: "Fit to View",
2437
+ type: "button",
2438
+ group: "zoom",
2439
+ execute: () => {
2440
+ instance.fitToPage?.();
2441
+ }
2442
+ },
2443
+ {
2444
+ id: "rotate-cw",
2445
+ icon: "rotate-cw",
2446
+ label: "Rotate",
2447
+ type: "button",
2448
+ group: "view",
2449
+ execute: () => {
2450
+ instance.rotateCW?.();
2093
2451
  }
2094
2452
  },
2095
2453
  {
@@ -2112,7 +2470,8 @@ var ExcelPlugin = class {
2112
2470
  instance.print?.();
2113
2471
  }
2114
2472
  }
2115
- ];
2473
+ );
2474
+ return actions;
2116
2475
  }
2117
2476
  async render(ctx) {
2118
2477
  const container = document.createElement("div");
@@ -2141,9 +2500,23 @@ var ExcelPlugin = class {
2141
2500
  container.appendChild(tabsArea);
2142
2501
  ctx.container.appendChild(container);
2143
2502
  let scale = 1;
2503
+ let rotation = 0;
2144
2504
  let currentSheetIndex = 1;
2145
2505
  let sheetNames = [];
2146
2506
  let wb = null;
2507
+ const applyTransform = () => {
2508
+ contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2509
+ contentArea.style.transformOrigin = "top left";
2510
+ };
2511
+ const calculateFitScale = () => {
2512
+ const table = contentArea.querySelector("table");
2513
+ if (!table) return 1;
2514
+ const availW = Math.max(200, container.clientWidth - 48);
2515
+ const availH = Math.max(200, container.clientHeight - 80);
2516
+ const tW = table.offsetWidth || 800;
2517
+ const tH = table.offsetHeight || 600;
2518
+ return Math.min(1, Math.min(availW / tW, availH / tH));
2519
+ };
2147
2520
  try {
2148
2521
  wb = XLSX.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
2149
2522
  sheetNames = wb.SheetNames || [];
@@ -2196,6 +2569,7 @@ var ExcelPlugin = class {
2196
2569
  b.style.fontWeight = "normal";
2197
2570
  }
2198
2571
  });
2572
+ ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
2199
2573
  };
2200
2574
  if (sheetNames.length > 0) {
2201
2575
  sheetNames.forEach((name, idx) => {
@@ -2230,17 +2604,30 @@ var ExcelPlugin = class {
2230
2604
  return {
2231
2605
  destroy: cleanup,
2232
2606
  zoomIn: () => {
2233
- scale += 0.1;
2234
- contentArea.style.transform = `scale(${scale})`;
2607
+ scale += 0.15;
2608
+ applyTransform();
2235
2609
  },
2236
2610
  zoomOut: () => {
2237
- scale = Math.max(0.2, scale - 0.1);
2238
- contentArea.style.transform = `scale(${scale})`;
2611
+ scale = Math.max(0.2, scale - 0.15);
2612
+ applyTransform();
2239
2613
  },
2240
2614
  getZoom: () => scale,
2241
2615
  setZoom: (level) => {
2242
2616
  scale = level;
2243
- contentArea.style.transform = `scale(${scale})`;
2617
+ applyTransform();
2618
+ },
2619
+ fitToPage: () => {
2620
+ scale = calculateFitScale();
2621
+ rotation = 0;
2622
+ applyTransform();
2623
+ },
2624
+ rotateCW: () => {
2625
+ rotation = (rotation + 90) % 360;
2626
+ applyTransform();
2627
+ },
2628
+ rotateCCW: () => {
2629
+ rotation = (rotation - 90 + 360) % 360;
2630
+ applyTransform();
2244
2631
  },
2245
2632
  goToPage: (page) => {
2246
2633
  if (page > 0 && page <= sheetNames.length) {
@@ -2445,7 +2832,31 @@ var CodePlugin = class {
2445
2832
  return false;
2446
2833
  }
2447
2834
  getToolbarActions(instance) {
2448
- return [
2835
+ const totalPages = instance.getPageCount?.() ?? 1;
2836
+ const actions = [];
2837
+ if (totalPages > 1) {
2838
+ actions.push({
2839
+ id: "page-nav",
2840
+ icon: "",
2841
+ label: "Page Navigation",
2842
+ type: "page-nav",
2843
+ group: "navigation",
2844
+ value: instance.getCurrentPage?.() ?? 1,
2845
+ max: totalPages,
2846
+ execute: (action, page) => {
2847
+ const cur = instance.getCurrentPage?.() ?? 1;
2848
+ const max = instance.getPageCount?.() ?? 1;
2849
+ if (action === "prev") {
2850
+ if (cur > 1) instance.goToPage?.(cur - 1);
2851
+ } else if (action === "next") {
2852
+ if (cur < max) instance.goToPage?.(cur + 1);
2853
+ } else if (typeof page === "number") {
2854
+ instance.goToPage?.(page);
2855
+ }
2856
+ }
2857
+ });
2858
+ }
2859
+ actions.push(
2449
2860
  {
2450
2861
  id: "zoom-out",
2451
2862
  icon: "zoom-out",
@@ -2466,6 +2877,26 @@ var CodePlugin = class {
2466
2877
  instance.zoomIn?.();
2467
2878
  }
2468
2879
  },
2880
+ {
2881
+ id: "fit-page",
2882
+ icon: "fit-page",
2883
+ label: "Fit to View",
2884
+ type: "button",
2885
+ group: "zoom",
2886
+ execute: () => {
2887
+ instance.fitToPage?.();
2888
+ }
2889
+ },
2890
+ {
2891
+ id: "rotate-cw",
2892
+ icon: "rotate-cw",
2893
+ label: "Rotate",
2894
+ type: "button",
2895
+ group: "view",
2896
+ execute: () => {
2897
+ instance.rotateCW?.();
2898
+ }
2899
+ },
2469
2900
  {
2470
2901
  id: "copy",
2471
2902
  icon: "copy",
@@ -2496,11 +2927,16 @@ var CodePlugin = class {
2496
2927
  instance.print?.();
2497
2928
  }
2498
2929
  }
2499
- ];
2930
+ );
2931
+ return actions;
2500
2932
  }
2501
2933
  async render(ctx) {
2502
2934
  const decoder = new TextDecoder("utf-8");
2503
- const text = decoder.decode(ctx.buffer);
2935
+ const fullText = decoder.decode(ctx.buffer);
2936
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2937
+ const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
2938
+ const totalPages = Math.max(1, rawPages.length);
2939
+ let currentPage = 1;
2504
2940
  const container = document.createElement("div");
2505
2941
  container.style.width = "100%";
2506
2942
  container.style.height = "100%";
@@ -2510,6 +2946,7 @@ var CodePlugin = class {
2510
2946
  container.style.padding = "16px";
2511
2947
  container.style.boxSizing = "border-box";
2512
2948
  let fontSize = 13;
2949
+ let rotation = 0;
2513
2950
  const pre = document.createElement("pre");
2514
2951
  pre.style.margin = "0";
2515
2952
  pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
@@ -2519,25 +2956,66 @@ var CodePlugin = class {
2519
2956
  pre.style.wordBreak = "break-all";
2520
2957
  const code = document.createElement("code");
2521
2958
  const ext = (ctx.metadata.extension || "").replace(".", "");
2522
- try {
2523
- if (ext && hljs.getLanguage(ext)) {
2524
- code.innerHTML = hljs.highlight(text, { language: ext }).value;
2525
- } else {
2526
- code.innerHTML = hljs.highlightAuto(text).value;
2959
+ const renderCodePage = (text) => {
2960
+ try {
2961
+ if (ext && hljs.getLanguage(ext)) {
2962
+ code.innerHTML = hljs.highlight(text, { language: ext }).value;
2963
+ } else {
2964
+ code.innerHTML = hljs.highlightAuto(text).value;
2965
+ }
2966
+ } catch {
2967
+ code.textContent = text;
2527
2968
  }
2528
- } catch {
2529
- code.textContent = text;
2530
- }
2969
+ };
2970
+ renderCodePage(rawPages[0] || fullText);
2531
2971
  pre.appendChild(code);
2532
2972
  container.appendChild(pre);
2973
+ let indicator = null;
2974
+ if (totalPages > 1) {
2975
+ indicator = document.createElement("div");
2976
+ indicator.className = "fp-code-page-indicator";
2977
+ indicator.style.position = "sticky";
2978
+ indicator.style.bottom = "16px";
2979
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2980
+ indicator.style.backdropFilter = "blur(8px)";
2981
+ indicator.style.color = "#f8fafc";
2982
+ indicator.style.fontSize = "12px";
2983
+ indicator.style.fontWeight = "600";
2984
+ indicator.style.padding = "5px 14px";
2985
+ indicator.style.borderRadius = "20px";
2986
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2987
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2988
+ indicator.style.zIndex = "10";
2989
+ indicator.style.userSelect = "none";
2990
+ indicator.style.pointerEvents = "none";
2991
+ indicator.style.textAlign = "center";
2992
+ indicator.style.width = "fit-content";
2993
+ indicator.style.margin = "16px auto 0";
2994
+ container.appendChild(indicator);
2995
+ }
2996
+ const showPage = (pageNum) => {
2997
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
2998
+ renderCodePage(rawPages[currentPage - 1] || fullText);
2999
+ if (indicator) {
3000
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3001
+ }
3002
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3003
+ };
3004
+ if (totalPages > 1) {
3005
+ showPage(1);
3006
+ }
2533
3007
  ctx.container.appendChild(container);
2534
3008
  const cleanup = () => {
3009
+ indicator?.remove();
2535
3010
  container.remove();
2536
3011
  ctx.container.innerHTML = "";
2537
3012
  };
2538
3013
  ctx.signal.addEventListener("abort", cleanup);
2539
3014
  return {
2540
3015
  destroy: cleanup,
3016
+ getPageCount: () => totalPages,
3017
+ getCurrentPage: () => currentPage,
3018
+ goToPage: (page) => showPage(page),
2541
3019
  zoomIn: () => {
2542
3020
  fontSize = Math.min(32, fontSize + 2);
2543
3021
  pre.style.fontSize = `${fontSize}px`;
@@ -2551,6 +3029,22 @@ var CodePlugin = class {
2551
3029
  fontSize = Math.round(13 * level);
2552
3030
  pre.style.fontSize = `${fontSize}px`;
2553
3031
  },
3032
+ fitToPage: () => {
3033
+ fontSize = 13;
3034
+ rotation = 0;
3035
+ pre.style.fontSize = "13px";
3036
+ pre.style.transform = "none";
3037
+ },
3038
+ rotateCW: () => {
3039
+ rotation = (rotation + 90) % 360;
3040
+ pre.style.transform = `rotate(${rotation}deg)`;
3041
+ pre.style.transformOrigin = "top left";
3042
+ },
3043
+ rotateCCW: () => {
3044
+ rotation = (rotation - 90 + 360) % 360;
3045
+ pre.style.transform = `rotate(${rotation}deg)`;
3046
+ pre.style.transformOrigin = "top left";
3047
+ },
2554
3048
  download: () => {
2555
3049
  const mimeType = ctx.metadata.mimeType || "text/plain";
2556
3050
  const blob = new Blob([ctx.buffer], { type: mimeType });
@@ -2565,7 +3059,7 @@ var CodePlugin = class {
2565
3059
  window.print();
2566
3060
  },
2567
3061
  copy: () => {
2568
- navigator.clipboard?.writeText(text);
3062
+ navigator.clipboard?.writeText(fullText);
2569
3063
  }
2570
3064
  };
2571
3065
  }
@@ -3062,6 +3556,14 @@ var PptxPlugin = class {
3062
3556
  group: "zoom",
3063
3557
  execute: () => instance.fitToPage?.()
3064
3558
  },
3559
+ {
3560
+ id: "rotate-cw",
3561
+ icon: "rotate-cw",
3562
+ label: "Rotate",
3563
+ type: "button",
3564
+ group: "view",
3565
+ execute: () => instance.rotateCW?.()
3566
+ },
3065
3567
  {
3066
3568
  id: "download",
3067
3569
  icon: "download",
@@ -3086,6 +3588,7 @@ var PptxPlugin = class {
3086
3588
  const slideCount = renderer.slidePaths?.length || 1;
3087
3589
  let currentSlide = 1;
3088
3590
  let scale = 1;
3591
+ let rotation = 0;
3089
3592
  const wrapper = document.createElement("div");
3090
3593
  wrapper.className = "fp-pptx-wrapper";
3091
3594
  wrapper.style.cssText = `
@@ -3108,6 +3611,8 @@ var PptxPlugin = class {
3108
3611
  background: #ffffff;
3109
3612
  transform-origin: top center;
3110
3613
  transition: transform 0.2s ease;
3614
+ max-width: calc(100% - 32px);
3615
+ max-height: calc(100% - 48px);
3111
3616
  `;
3112
3617
  const canvas = document.createElement("canvas");
3113
3618
  slideContainer.appendChild(canvas);
@@ -3115,10 +3620,22 @@ var PptxPlugin = class {
3115
3620
  ctx.container.innerHTML = "";
3116
3621
  ctx.container.style.overflow = "auto";
3117
3622
  ctx.container.appendChild(wrapper);
3623
+ const calculateFitScale = () => {
3624
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
3625
+ const availH = Math.max(200, ctx.container.clientHeight - 72);
3626
+ const cW = canvas.offsetWidth || 1280;
3627
+ const cH = canvas.offsetHeight || 720;
3628
+ return Math.min(1, Math.min(availW / cW, availH / cH));
3629
+ };
3630
+ const applyTransform = () => {
3631
+ slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3632
+ };
3118
3633
  const renderCurrentSlide = async () => {
3119
3634
  try {
3120
3635
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
3121
3636
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
3637
+ scale = calculateFitScale();
3638
+ applyTransform();
3122
3639
  } catch (err) {
3123
3640
  console.error("[PptxPlugin] Failed to render slide:", err);
3124
3641
  }
@@ -3141,21 +3658,30 @@ var PptxPlugin = class {
3141
3658
  getPageCount: () => slideCount,
3142
3659
  getCurrentPage: () => currentSlide,
3143
3660
  zoomIn: () => {
3144
- scale += 0.1;
3145
- slideContainer.style.transform = `scale(${scale})`;
3661
+ scale += 0.15;
3662
+ applyTransform();
3146
3663
  },
3147
3664
  zoomOut: () => {
3148
- scale = Math.max(0.2, scale - 0.1);
3149
- slideContainer.style.transform = `scale(${scale})`;
3665
+ scale = Math.max(0.2, scale - 0.15);
3666
+ applyTransform();
3150
3667
  },
3151
3668
  getZoom: () => scale,
3152
3669
  setZoom: (level) => {
3153
3670
  scale = level;
3154
- slideContainer.style.transform = `scale(${scale})`;
3671
+ applyTransform();
3155
3672
  },
3156
3673
  fitToPage: () => {
3157
- scale = 1;
3158
- slideContainer.style.transform = "scale(1)";
3674
+ scale = calculateFitScale();
3675
+ rotation = 0;
3676
+ applyTransform();
3677
+ },
3678
+ rotateCW: () => {
3679
+ rotation = (rotation + 90) % 360;
3680
+ applyTransform();
3681
+ },
3682
+ rotateCCW: () => {
3683
+ rotation = (rotation - 90 + 360) % 360;
3684
+ applyTransform();
3159
3685
  },
3160
3686
  getThumbnails: () => {
3161
3687
  const list = [];
@@ -3387,7 +3913,31 @@ var RtfPlugin = class {
3387
3913
  return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3388
3914
  }
3389
3915
  getToolbarActions(instance) {
3390
- return [
3916
+ const totalPages = instance.getPageCount?.() ?? 1;
3917
+ const actions = [];
3918
+ if (totalPages > 1) {
3919
+ actions.push({
3920
+ id: "page-nav",
3921
+ icon: "",
3922
+ label: "Page Navigation",
3923
+ type: "page-nav",
3924
+ group: "navigation",
3925
+ value: instance.getCurrentPage?.() ?? 1,
3926
+ max: totalPages,
3927
+ execute: (action, page) => {
3928
+ const cur = instance.getCurrentPage?.() ?? 1;
3929
+ const max = instance.getPageCount?.() ?? 1;
3930
+ if (action === "prev") {
3931
+ if (cur > 1) instance.goToPage?.(cur - 1);
3932
+ } else if (action === "next") {
3933
+ if (cur < max) instance.goToPage?.(cur + 1);
3934
+ } else if (typeof page === "number") {
3935
+ instance.goToPage?.(page);
3936
+ }
3937
+ }
3938
+ });
3939
+ }
3940
+ actions.push(
3391
3941
  {
3392
3942
  id: "zoom-out",
3393
3943
  icon: "zoom-out",
@@ -3412,6 +3962,14 @@ var RtfPlugin = class {
3412
3962
  group: "zoom",
3413
3963
  execute: () => instance.fitToPage?.()
3414
3964
  },
3965
+ {
3966
+ id: "rotate-cw",
3967
+ icon: "rotate-cw",
3968
+ label: "Rotate",
3969
+ type: "button",
3970
+ group: "view",
3971
+ execute: () => instance.rotateCW?.()
3972
+ },
3415
3973
  {
3416
3974
  id: "download",
3417
3975
  icon: "download",
@@ -3428,7 +3986,8 @@ var RtfPlugin = class {
3428
3986
  group: "actions",
3429
3987
  execute: () => instance.print?.()
3430
3988
  }
3431
- ];
3989
+ );
3990
+ return actions;
3432
3991
  }
3433
3992
  async render(ctx) {
3434
3993
  const wrapper = document.createElement("div");
@@ -3447,44 +4006,124 @@ var RtfPlugin = class {
3447
4006
  ctx.container.style.backgroundColor = "#f1f5f9";
3448
4007
  ctx.container.appendChild(wrapper);
3449
4008
  let scale = 1;
4009
+ let rotation = 0;
4010
+ let pageElements = [];
4011
+ const calculateFitScale = () => {
4012
+ const activeEl = pageElements[currentPage - 1] || wrapper;
4013
+ const elW = activeEl.offsetWidth || 850;
4014
+ const elH = activeEl.offsetHeight || 1e3;
4015
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4016
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4017
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4018
+ };
4019
+ const applyTransform = () => {
4020
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4021
+ wrapper.style.transformOrigin = "top center";
4022
+ };
4023
+ setTimeout(() => {
4024
+ scale = calculateFitScale();
4025
+ applyTransform();
4026
+ }, 60);
3450
4027
  try {
3451
4028
  if (typeof RTFJS.loggingEnabled === "function") {
3452
4029
  RTFJS.loggingEnabled(false);
3453
4030
  }
3454
4031
  const doc = new RTFJS.Document(ctx.buffer, {});
3455
4032
  const htmlElements = await doc.render();
3456
- for (const el of htmlElements) {
4033
+ pageElements = htmlElements;
4034
+ for (let i = 0; i < htmlElements.length; i++) {
4035
+ const el = htmlElements[i];
4036
+ el.style.display = i === 0 ? "block" : "none";
3457
4037
  wrapper.appendChild(el);
3458
4038
  }
3459
4039
  } catch (err) {
3460
4040
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3461
4041
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3462
4042
  const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3463
- wrapper.innerHTML = `<pre style="white-space: pre-wrap; font-family: serif; color: #333;">${clean}</pre>`;
4043
+ const pre = document.createElement("pre");
4044
+ pre.style.whiteSpace = "pre-wrap";
4045
+ pre.style.fontFamily = "serif";
4046
+ pre.style.color = "#333";
4047
+ pre.textContent = clean;
4048
+ wrapper.appendChild(pre);
4049
+ pageElements = [pre];
4050
+ }
4051
+ const totalPages = Math.max(1, pageElements.length);
4052
+ let currentPage = 1;
4053
+ let indicator = null;
4054
+ if (totalPages > 1) {
4055
+ indicator = document.createElement("div");
4056
+ indicator.className = "fp-rtf-page-indicator";
4057
+ indicator.style.position = "sticky";
4058
+ indicator.style.bottom = "16px";
4059
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4060
+ indicator.style.backdropFilter = "blur(8px)";
4061
+ indicator.style.color = "#f8fafc";
4062
+ indicator.style.fontSize = "12px";
4063
+ indicator.style.fontWeight = "600";
4064
+ indicator.style.padding = "5px 14px";
4065
+ indicator.style.borderRadius = "20px";
4066
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4067
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4068
+ indicator.style.zIndex = "10";
4069
+ indicator.style.userSelect = "none";
4070
+ indicator.style.pointerEvents = "none";
4071
+ indicator.style.textAlign = "center";
4072
+ indicator.style.width = "fit-content";
4073
+ indicator.style.margin = "16px auto 0";
4074
+ ctx.container.appendChild(indicator);
4075
+ }
4076
+ const showPage = (pageNum) => {
4077
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4078
+ if (totalPages > 1) {
4079
+ pageElements.forEach((el, idx) => {
4080
+ el.style.display = idx + 1 === currentPage ? "block" : "none";
4081
+ });
4082
+ }
4083
+ if (indicator) {
4084
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4085
+ }
4086
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4087
+ };
4088
+ if (totalPages > 1) {
4089
+ showPage(1);
3464
4090
  }
3465
4091
  const cleanup = () => {
4092
+ indicator?.remove();
3466
4093
  wrapper.remove();
3467
4094
  ctx.container.innerHTML = "";
3468
4095
  };
3469
4096
  ctx.signal.addEventListener("abort", cleanup);
3470
4097
  return {
3471
4098
  destroy: cleanup,
4099
+ getPageCount: () => totalPages,
4100
+ getCurrentPage: () => currentPage,
4101
+ goToPage: (page) => showPage(page),
3472
4102
  zoomIn: () => {
3473
- scale += 0.1;
3474
- wrapper.style.transform = `scale(${scale})`;
4103
+ scale += 0.15;
4104
+ applyTransform();
3475
4105
  },
3476
4106
  zoomOut: () => {
3477
- scale = Math.max(0.2, scale - 0.1);
3478
- wrapper.style.transform = `scale(${scale})`;
4107
+ scale = Math.max(0.2, scale - 0.15);
4108
+ applyTransform();
3479
4109
  },
3480
4110
  getZoom: () => scale,
3481
4111
  setZoom: (level) => {
3482
4112
  scale = level;
3483
- wrapper.style.transform = `scale(${scale})`;
4113
+ applyTransform();
3484
4114
  },
3485
4115
  fitToPage: () => {
3486
- scale = 1;
3487
- wrapper.style.transform = "scale(1)";
4116
+ scale = calculateFitScale();
4117
+ rotation = 0;
4118
+ applyTransform();
4119
+ },
4120
+ rotateCW: () => {
4121
+ rotation = (rotation + 90) % 360;
4122
+ applyTransform();
4123
+ },
4124
+ rotateCCW: () => {
4125
+ rotation = (rotation - 90 + 360) % 360;
4126
+ applyTransform();
3488
4127
  },
3489
4128
  download: () => {
3490
4129
  const blob = new Blob([ctx.buffer], { type: "application/rtf" });
@@ -3656,41 +4295,41 @@ var OpenDocumentPlugin = class {
3656
4295
  }
3657
4296
  getToolbarActions(instance) {
3658
4297
  const isPresentation = instance.isPresentation;
4298
+ const totalPages = instance.getPageCount?.() ?? 1;
3659
4299
  const actions = [];
3660
- if (isPresentation) {
3661
- actions.push(
3662
- {
4300
+ if (isPresentation || totalPages > 1) {
4301
+ if (isPresentation) {
4302
+ actions.push({
3663
4303
  id: "thumbnails",
3664
4304
  icon: "thumbnails",
3665
4305
  label: "Slide Thumbnails",
3666
4306
  type: "button",
3667
4307
  group: "navigation",
3668
4308
  execute: () => instance.toggleThumbnails?.()
3669
- },
3670
- {
3671
- id: "page-nav",
3672
- icon: "",
3673
- label: "Slide Navigation",
3674
- type: "page-nav",
3675
- group: "navigation",
3676
- value: instance.getCurrentPage?.() ?? 1,
3677
- max: instance.getPageCount?.() ?? 1,
3678
- execute: (action, page) => {
3679
- if (action === "prev") {
3680
- const cur = instance.getCurrentPage?.() ?? 1;
3681
- if (cur > 1) instance.goToPage?.(cur - 1);
3682
- } else if (action === "next") {
3683
- const cur = instance.getCurrentPage?.() ?? 1;
3684
- const total = instance.getPageCount?.() ?? 1;
3685
- if (cur < total) instance.goToPage?.(cur + 1);
3686
- } else if (typeof page === "number") {
3687
- instance.goToPage?.(page);
3688
- } else if (typeof action === "number") {
3689
- instance.goToPage?.(action);
3690
- }
4309
+ });
4310
+ }
4311
+ actions.push({
4312
+ id: "page-nav",
4313
+ icon: "",
4314
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
4315
+ type: "page-nav",
4316
+ group: "navigation",
4317
+ value: instance.getCurrentPage?.() ?? 1,
4318
+ max: totalPages,
4319
+ execute: (action, page) => {
4320
+ const cur = instance.getCurrentPage?.() ?? 1;
4321
+ const max = instance.getPageCount?.() ?? 1;
4322
+ if (action === "prev") {
4323
+ if (cur > 1) instance.goToPage?.(cur - 1);
4324
+ } else if (action === "next") {
4325
+ if (cur < max) instance.goToPage?.(cur + 1);
4326
+ } else if (typeof page === "number") {
4327
+ instance.goToPage?.(page);
4328
+ } else if (typeof action === "number") {
4329
+ instance.goToPage?.(action);
3691
4330
  }
3692
4331
  }
3693
- );
4332
+ });
3694
4333
  }
3695
4334
  actions.push(
3696
4335
  {
@@ -3712,11 +4351,19 @@ var OpenDocumentPlugin = class {
3712
4351
  {
3713
4352
  id: "fit-page",
3714
4353
  icon: "fit-page",
3715
- label: "Fit to Page",
4354
+ label: isPresentation ? "Fit to Slide" : "Fit to Page",
3716
4355
  type: "button",
3717
4356
  group: "zoom",
3718
4357
  execute: () => instance.fitToPage?.()
3719
4358
  },
4359
+ {
4360
+ id: "rotate-cw",
4361
+ icon: "rotate-cw",
4362
+ label: "Rotate",
4363
+ type: "button",
4364
+ group: "view",
4365
+ execute: () => instance.rotateCW?.()
4366
+ },
3720
4367
  {
3721
4368
  id: "download",
3722
4369
  icon: "download",
@@ -3780,6 +4427,22 @@ var OpenDocumentPlugin = class {
3780
4427
  container.appendChild(wrapper);
3781
4428
  ctx.container.appendChild(container);
3782
4429
  let scale = 1;
4430
+ let rotation = 0;
4431
+ const calculateFitScale = () => {
4432
+ const elW = wrapper.offsetWidth || 850;
4433
+ const elH = wrapper.offsetHeight || (isPresentation ? 540 : 1e3);
4434
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4435
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4436
+ return Math.min(1, Math.min(availW / elW, availH / elH));
4437
+ };
4438
+ const applyTransform = () => {
4439
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4440
+ wrapper.style.transformOrigin = "top center";
4441
+ };
4442
+ setTimeout(() => {
4443
+ scale = calculateFitScale();
4444
+ applyTransform();
4445
+ }, 60);
3783
4446
  let currentPage = 1;
3784
4447
  let totalPages = 1;
3785
4448
  let slides = [];
@@ -3838,21 +4501,30 @@ var OpenDocumentPlugin = class {
3838
4501
  destroy: cleanup,
3839
4502
  isPresentation,
3840
4503
  zoomIn: () => {
3841
- scale += 0.1;
3842
- wrapper.style.transform = `scale(${scale})`;
4504
+ scale += 0.15;
4505
+ applyTransform();
3843
4506
  },
3844
4507
  zoomOut: () => {
3845
- scale = Math.max(0.2, scale - 0.1);
3846
- wrapper.style.transform = `scale(${scale})`;
4508
+ scale = Math.max(0.2, scale - 0.15);
4509
+ applyTransform();
3847
4510
  },
3848
4511
  getZoom: () => scale,
3849
4512
  setZoom: (level) => {
3850
4513
  scale = level;
3851
- wrapper.style.transform = `scale(${scale})`;
4514
+ applyTransform();
3852
4515
  },
3853
4516
  fitToPage: () => {
3854
- scale = 1;
3855
- wrapper.style.transform = "scale(1)";
4517
+ scale = calculateFitScale();
4518
+ rotation = 0;
4519
+ applyTransform();
4520
+ },
4521
+ rotateCW: () => {
4522
+ rotation = (rotation + 90) % 360;
4523
+ applyTransform();
4524
+ },
4525
+ rotateCCW: () => {
4526
+ rotation = (rotation - 90 + 360) % 360;
4527
+ applyTransform();
3856
4528
  },
3857
4529
  goToPage,
3858
4530
  getPageCount: () => totalPages,
@@ -4073,7 +4745,31 @@ var DocPlugin = class {
4073
4745
  return this.mimeTypes.includes(mime || "");
4074
4746
  }
4075
4747
  getToolbarActions(instance) {
4076
- return [
4748
+ const totalPages = instance.getPageCount?.() ?? 1;
4749
+ const actions = [];
4750
+ if (totalPages > 1) {
4751
+ actions.push({
4752
+ id: "page-nav",
4753
+ icon: "",
4754
+ label: "Page Navigation",
4755
+ type: "page-nav",
4756
+ group: "navigation",
4757
+ value: instance.getCurrentPage?.() ?? 1,
4758
+ max: totalPages,
4759
+ execute: (action, page) => {
4760
+ const cur = instance.getCurrentPage?.() ?? 1;
4761
+ const max = instance.getPageCount?.() ?? 1;
4762
+ if (action === "prev") {
4763
+ if (cur > 1) instance.goToPage?.(cur - 1);
4764
+ } else if (action === "next") {
4765
+ if (cur < max) instance.goToPage?.(cur + 1);
4766
+ } else if (typeof page === "number") {
4767
+ instance.goToPage?.(page);
4768
+ }
4769
+ }
4770
+ });
4771
+ }
4772
+ actions.push(
4077
4773
  {
4078
4774
  id: "zoom-out",
4079
4775
  icon: "zoom-out",
@@ -4098,6 +4794,14 @@ var DocPlugin = class {
4098
4794
  group: "zoom",
4099
4795
  execute: () => instance.fitToPage?.()
4100
4796
  },
4797
+ {
4798
+ id: "rotate-cw",
4799
+ icon: "rotate-cw",
4800
+ label: "Rotate",
4801
+ type: "button",
4802
+ group: "view",
4803
+ execute: () => instance.rotateCW?.()
4804
+ },
4101
4805
  {
4102
4806
  id: "copy",
4103
4807
  icon: "copy",
@@ -4122,7 +4826,8 @@ var DocPlugin = class {
4122
4826
  group: "actions",
4123
4827
  execute: () => instance.print?.()
4124
4828
  }
4125
- ];
4829
+ );
4830
+ return actions;
4126
4831
  }
4127
4832
  async render(ctx) {
4128
4833
  const container = document.createElement("div");
@@ -4149,6 +4854,7 @@ var DocPlugin = class {
4149
4854
  ctx.container.appendChild(container);
4150
4855
  let scale = 1;
4151
4856
  let extractedRawText = "";
4857
+ let isFallback = false;
4152
4858
  try {
4153
4859
  const cfbf = new CfbfReader(ctx.buffer);
4154
4860
  const wordDocStream = cfbf.readStream("WordDocument");
@@ -4162,42 +4868,131 @@ var DocPlugin = class {
4162
4868
  const tableStream = cfbf.readStream(tableName);
4163
4869
  const text = this.extractDocText(wordDocStream, tableStream);
4164
4870
  extractedRawText = text;
4165
- wrapper.innerHTML = this.formatDocToHtml(text, ctx.metadata.name || "Document");
4166
4871
  } catch (err) {
4167
4872
  console.warn("[DocPlugin] Binary parsing error, fallback text:", err);
4168
4873
  const fallback = this.heuristicTextExtraction(ctx.buffer);
4169
4874
  extractedRawText = fallback;
4170
- wrapper.innerHTML = `
4171
- <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4172
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${ctx.metadata.name || "Word Document (.doc)"}</h2>
4173
- <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4174
- </div>
4175
- ${this.formatDocToHtml(fallback, ctx.metadata.name || "Document")}
4176
- `;
4875
+ isFallback = true;
4876
+ }
4877
+ const rawPages = this.splitIntoPages(extractedRawText);
4878
+ const totalPages = Math.max(1, rawPages.length);
4879
+ let currentPage = 1;
4880
+ wrapper.innerHTML = "";
4881
+ const pageCards = [];
4882
+ for (let i = 0; i < totalPages; i++) {
4883
+ const pageCard = document.createElement("div");
4884
+ pageCard.className = "fp-doc-page-card";
4885
+ pageCard.style.backgroundColor = "#ffffff";
4886
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4887
+ pageCard.style.borderRadius = "4px";
4888
+ pageCard.style.padding = "56px 48px";
4889
+ pageCard.style.minHeight = "100%";
4890
+ pageCard.style.display = i === 0 ? "block" : "none";
4891
+ if (isFallback && i === 0) {
4892
+ pageCard.innerHTML = `
4893
+ <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4894
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4895
+ <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4896
+ </div>
4897
+ ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
4898
+ `;
4899
+ } else {
4900
+ pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4901
+ }
4902
+ wrapper.appendChild(pageCard);
4903
+ pageCards.push(pageCard);
4904
+ }
4905
+ let indicator = null;
4906
+ if (totalPages > 1) {
4907
+ indicator = document.createElement("div");
4908
+ indicator.className = "fp-doc-page-indicator";
4909
+ indicator.style.position = "sticky";
4910
+ indicator.style.bottom = "16px";
4911
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4912
+ indicator.style.backdropFilter = "blur(8px)";
4913
+ indicator.style.color = "#f8fafc";
4914
+ indicator.style.fontSize = "12px";
4915
+ indicator.style.fontWeight = "600";
4916
+ indicator.style.padding = "5px 14px";
4917
+ indicator.style.borderRadius = "20px";
4918
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4919
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4920
+ indicator.style.zIndex = "10";
4921
+ indicator.style.userSelect = "none";
4922
+ indicator.style.pointerEvents = "none";
4923
+ indicator.style.textAlign = "center";
4924
+ indicator.style.width = "fit-content";
4925
+ indicator.style.margin = "16px auto 0";
4926
+ container.appendChild(indicator);
4927
+ }
4928
+ const showPage = (pageNum) => {
4929
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4930
+ if (totalPages > 1) {
4931
+ pageCards.forEach((card, idx) => {
4932
+ card.style.display = idx + 1 === currentPage ? "block" : "none";
4933
+ });
4934
+ }
4935
+ if (indicator) {
4936
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4937
+ }
4938
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4939
+ };
4940
+ if (totalPages > 1) {
4941
+ showPage(1);
4177
4942
  }
4943
+ let rotation = 0;
4944
+ const calculateFitScale = () => {
4945
+ const activeCard = pageCards[currentPage - 1] || wrapper;
4946
+ const elW = activeCard.offsetWidth || 850;
4947
+ const elH = activeCard.offsetHeight || 1e3;
4948
+ const availW = Math.max(200, ctx.container.clientWidth - 48);
4949
+ const availH = Math.max(200, ctx.container.clientHeight - 80);
4950
+ return Math.min(1.1, Math.min(availW / elW, availH / elH));
4951
+ };
4952
+ const applyTransform = () => {
4953
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4954
+ wrapper.style.transformOrigin = "top center";
4955
+ };
4956
+ setTimeout(() => {
4957
+ scale = calculateFitScale();
4958
+ applyTransform();
4959
+ }, 60);
4178
4960
  const cleanup = () => {
4961
+ indicator?.remove();
4179
4962
  container.remove();
4180
4963
  ctx.container.innerHTML = "";
4181
4964
  };
4182
4965
  ctx.signal.addEventListener("abort", cleanup);
4183
4966
  return {
4184
4967
  destroy: cleanup,
4968
+ getPageCount: () => totalPages,
4969
+ getCurrentPage: () => currentPage,
4970
+ goToPage: (page) => showPage(page),
4185
4971
  zoomIn: () => {
4186
- scale += 0.1;
4187
- wrapper.style.transform = `scale(${scale})`;
4972
+ scale += 0.15;
4973
+ applyTransform();
4188
4974
  },
4189
4975
  zoomOut: () => {
4190
- scale = Math.max(0.2, scale - 0.1);
4191
- wrapper.style.transform = `scale(${scale})`;
4976
+ scale = Math.max(0.2, scale - 0.15);
4977
+ applyTransform();
4192
4978
  },
4193
4979
  getZoom: () => scale,
4194
4980
  setZoom: (level) => {
4195
4981
  scale = level;
4196
- wrapper.style.transform = `scale(${scale})`;
4982
+ applyTransform();
4197
4983
  },
4198
4984
  fitToPage: () => {
4199
- scale = 1;
4200
- wrapper.style.transform = "scale(1)";
4985
+ scale = calculateFitScale();
4986
+ rotation = 0;
4987
+ applyTransform();
4988
+ },
4989
+ rotateCW: () => {
4990
+ rotation = (rotation + 90) % 360;
4991
+ applyTransform();
4992
+ },
4993
+ rotateCCW: () => {
4994
+ rotation = (rotation - 90 + 360) % 360;
4995
+ applyTransform();
4201
4996
  },
4202
4997
  copy: () => {
4203
4998
  navigator.clipboard.writeText(extractedRawText);
@@ -4313,11 +5108,16 @@ var DocPlugin = class {
4313
5108
  heuristicTextExtraction(buffer) {
4314
5109
  return this.extractStringsFromBytes(new Uint8Array(buffer));
4315
5110
  }
5111
+ splitIntoPages(text) {
5112
+ if (!text) return [""];
5113
+ 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);
5114
+ return parts.length > 0 ? parts : [text];
5115
+ }
4316
5116
  /**
4317
5117
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
4318
5118
  */
4319
5119
  formatDocToHtml(text, filename) {
4320
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x0C/g, "\n\n").split("\n");
5120
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4321
5121
  let html = "";
4322
5122
  let inList = false;
4323
5123
  for (const rawLine of lines) {
@@ -4432,6 +5232,14 @@ var PptPlugin = class {
4432
5232
  group: "zoom",
4433
5233
  execute: () => instance.fitToPage?.()
4434
5234
  },
5235
+ {
5236
+ id: "rotate-cw",
5237
+ icon: "rotate-cw",
5238
+ label: "Rotate",
5239
+ type: "button",
5240
+ group: "view",
5241
+ execute: () => instance.rotateCW?.()
5242
+ },
4435
5243
  {
4436
5244
  id: "download",
4437
5245
  icon: "download",
@@ -4465,22 +5273,36 @@ var PptPlugin = class {
4465
5273
  const slideCard = document.createElement("div");
4466
5274
  slideCard.className = "fp-ppt-slide-card";
4467
5275
  slideCard.style.width = "960px";
4468
- slideCard.style.maxWidth = "92%";
5276
+ slideCard.style.maxWidth = "calc(100% - 32px)";
5277
+ slideCard.style.maxHeight = "calc(100% - 48px)";
4469
5278
  slideCard.style.aspectRatio = "16 / 9";
4470
5279
  slideCard.style.backgroundColor = "#ffffff";
4471
5280
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4472
5281
  slideCard.style.borderRadius = "8px";
4473
5282
  slideCard.style.boxSizing = "border-box";
4474
5283
  slideCard.style.position = "relative";
4475
- slideCard.style.overflow = "hidden";
4476
- slideCard.style.transformOrigin = "center center";
4477
5284
  slideCard.style.transition = "transform 0.2s ease";
5285
+ slideCard.style.transformOrigin = "center center";
5286
+ slideCard.style.flexShrink = "0";
4478
5287
  container.appendChild(slideCard);
4479
5288
  ctx.container.appendChild(container);
4480
5289
  let scale = 1;
5290
+ let rotation = 0;
4481
5291
  let currentSlide = 1;
4482
5292
  let slides = [];
4483
5293
  const createdBlobUrls = [];
5294
+ const calculateFitScale = () => {
5295
+ const availW = Math.max(200, container.clientWidth - 48);
5296
+ const availH = Math.max(200, container.clientHeight - 72);
5297
+ return Math.min(1, Math.min(availW / 960, availH / 540));
5298
+ };
5299
+ const applyTransform = () => {
5300
+ slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5301
+ };
5302
+ setTimeout(() => {
5303
+ scale = calculateFitScale();
5304
+ applyTransform();
5305
+ }, 60);
4484
5306
  try {
4485
5307
  const cfbf = new CfbfReader(ctx.buffer);
4486
5308
  const pptStream = cfbf.readStream("PowerPoint Document");
@@ -4581,21 +5403,30 @@ var PptPlugin = class {
4581
5403
  return {
4582
5404
  destroy: cleanup,
4583
5405
  zoomIn: () => {
4584
- scale += 0.1;
4585
- slideCard.style.transform = `scale(${scale})`;
5406
+ scale += 0.15;
5407
+ applyTransform();
4586
5408
  },
4587
5409
  zoomOut: () => {
4588
- scale = Math.max(0.3, scale - 0.1);
4589
- slideCard.style.transform = `scale(${scale})`;
5410
+ scale = Math.max(0.2, scale - 0.15);
5411
+ applyTransform();
4590
5412
  },
4591
5413
  getZoom: () => scale,
4592
5414
  setZoom: (level) => {
4593
5415
  scale = level;
4594
- slideCard.style.transform = `scale(${scale})`;
5416
+ applyTransform();
4595
5417
  },
4596
5418
  fitToPage: () => {
4597
- scale = 1;
4598
- slideCard.style.transform = "scale(1)";
5419
+ scale = calculateFitScale();
5420
+ rotation = 0;
5421
+ applyTransform();
5422
+ },
5423
+ rotateCW: () => {
5424
+ rotation = (rotation + 90) % 360;
5425
+ applyTransform();
5426
+ },
5427
+ rotateCCW: () => {
5428
+ rotation = (rotation - 90 + 360) % 360;
5429
+ applyTransform();
4599
5430
  },
4600
5431
  goToPage: (page) => {
4601
5432
  if (page >= 1 && page <= totalSlides) {