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