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