@files-preview-app/preview-file 1.3.2 → 1.3.4

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/index.cjs CHANGED
@@ -3590,26 +3590,6 @@ var ExcelPlugin = class {
3590
3590
  instance.zoomIn?.();
3591
3591
  }
3592
3592
  },
3593
- {
3594
- id: "fit-page",
3595
- icon: "fit-page",
3596
- label: "Fit to View",
3597
- type: "button",
3598
- group: "zoom",
3599
- execute: () => {
3600
- instance.fitToPage?.();
3601
- }
3602
- },
3603
- {
3604
- id: "rotate-cw",
3605
- icon: "rotate-cw",
3606
- label: "Rotate",
3607
- type: "button",
3608
- group: "view",
3609
- execute: () => {
3610
- instance.rotateCW?.();
3611
- }
3612
- },
3613
3593
  {
3614
3594
  id: "download",
3615
3595
  icon: "download",
@@ -3670,12 +3650,11 @@ var ExcelPlugin = class {
3670
3650
  container.appendChild(tabsArea);
3671
3651
  ctx.container.appendChild(container);
3672
3652
  let scale = 1;
3673
- let rotation = 0;
3674
3653
  let currentSheetIndex = 1;
3675
3654
  let sheetNames = [];
3676
3655
  let wb = null;
3677
3656
  const applyTransform = () => {
3678
- contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3657
+ contentArea.style.transform = `scale(${scale})`;
3679
3658
  contentArea.style.transformOrigin = "top left";
3680
3659
  };
3681
3660
  const calculateFitScale = () => {
@@ -3683,7 +3662,7 @@ var ExcelPlugin = class {
3683
3662
  if (!table) return 1;
3684
3663
  const availW = Math.max(280, container.clientWidth - 48);
3685
3664
  const tW = table.offsetWidth || 800;
3686
- return Math.max(0.65, Math.min(1, availW / tW));
3665
+ return Math.max(0.4, Math.min(1, availW / tW));
3687
3666
  };
3688
3667
  try {
3689
3668
  wb = XLSX__namespace.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
@@ -3738,7 +3717,16 @@ var ExcelPlugin = class {
3738
3717
  }
3739
3718
  });
3740
3719
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3720
+ requestAnimationFrame(() => {
3721
+ scale = calculateFitScale();
3722
+ applyTransform();
3723
+ });
3741
3724
  };
3725
+ const ro = new ResizeObserver(() => {
3726
+ scale = calculateFitScale();
3727
+ applyTransform();
3728
+ });
3729
+ ro.observe(container);
3742
3730
  if (sheetNames.length > 0) {
3743
3731
  sheetNames.forEach((name, idx) => {
3744
3732
  const btn = document.createElement("button");
@@ -3765,6 +3753,7 @@ var ExcelPlugin = class {
3765
3753
  `;
3766
3754
  }
3767
3755
  const cleanup = () => {
3756
+ ro.disconnect();
3768
3757
  container.remove();
3769
3758
  ctx.container.innerHTML = "";
3770
3759
  };
@@ -3786,16 +3775,11 @@ var ExcelPlugin = class {
3786
3775
  },
3787
3776
  fitToPage: () => {
3788
3777
  scale = calculateFitScale();
3789
- rotation = 0;
3790
3778
  applyTransform();
3791
3779
  },
3792
3780
  rotateCW: () => {
3793
- rotation = (rotation + 90) % 360;
3794
- applyTransform();
3795
3781
  },
3796
3782
  rotateCCW: () => {
3797
- rotation = (rotation - 90 + 360) % 360;
3798
- applyTransform();
3799
3783
  },
3800
3784
  goToPage: (page) => {
3801
3785
  if (page > 0 && page <= sheetNames.length) {
@@ -3893,38 +3877,15 @@ var CsvPlugin = class {
3893
3877
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3894
3878
  }
3895
3879
  getToolbarActions(instance) {
3896
- const totalPages = instance.getPageCount?.() ?? 1;
3897
3880
  const actions = [];
3898
3881
  actions.push({
3899
3882
  id: "thumbnails",
3900
3883
  icon: "thumbnails",
3901
- label: "Page Thumbnails",
3884
+ label: "Sheet Thumbnails",
3902
3885
  type: "button",
3903
3886
  group: "navigation",
3904
3887
  execute: () => instance.toggleThumbnails?.()
3905
3888
  });
3906
- if (totalPages > 1) {
3907
- actions.push({
3908
- id: "page-nav",
3909
- icon: "",
3910
- label: "Page Navigation",
3911
- type: "page-nav",
3912
- group: "navigation",
3913
- value: instance.getCurrentPage?.() ?? 1,
3914
- max: totalPages,
3915
- execute: (action, page) => {
3916
- const cur = instance.getCurrentPage?.() ?? 1;
3917
- const max = instance.getPageCount?.() ?? 1;
3918
- if (action === "prev") {
3919
- if (cur > 1) instance.goToPage?.(cur - 1);
3920
- } else if (action === "next") {
3921
- if (cur < max) instance.goToPage?.(cur + 1);
3922
- } else if (typeof page === "number") {
3923
- instance.goToPage?.(page);
3924
- }
3925
- }
3926
- });
3927
- }
3928
3889
  actions.push(
3929
3890
  {
3930
3891
  id: "zoom-out",
@@ -3946,16 +3907,6 @@ var CsvPlugin = class {
3946
3907
  instance.zoomIn?.();
3947
3908
  }
3948
3909
  },
3949
- {
3950
- id: "fit-page",
3951
- icon: "fit-page",
3952
- label: "Fit to View",
3953
- type: "button",
3954
- group: "zoom",
3955
- execute: () => {
3956
- instance.fitToPage?.();
3957
- }
3958
- },
3959
3910
  {
3960
3911
  id: "download",
3961
3912
  icon: "download",
@@ -3998,131 +3949,135 @@ var CsvPlugin = class {
3998
3949
  container.style.overflow = "auto";
3999
3950
  container.style.padding = "16px";
4000
3951
  container.style.boxSizing = "border-box";
4001
- container.style.transformOrigin = "top left";
3952
+ const tableWrapper = document.createElement("div");
3953
+ tableWrapper.style.transformOrigin = "top left";
4002
3954
  const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
4003
3955
  const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
4004
3956
  const headerLine = allLines[0] || "";
4005
3957
  const headerCells = headerLine.split(delimiter);
4006
3958
  const dataLines = allLines.slice(1);
4007
- const ROWS_PER_PAGE = 100;
4008
- const totalPages = Math.max(1, Math.ceil(dataLines.length / ROWS_PER_PAGE));
4009
- let currentPage = 1;
4010
3959
  const table = document.createElement("table");
4011
3960
  table.style.borderCollapse = "collapse";
4012
- table.style.width = "100%";
3961
+ table.style.minWidth = "100%";
4013
3962
  table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
4014
3963
  table.style.fontSize = "13px";
4015
- const renderPage = (pageNum) => {
4016
- currentPage = Math.max(1, Math.min(totalPages, pageNum));
4017
- table.innerHTML = "";
4018
- const headerTr = document.createElement("tr");
4019
- headerCells.forEach((cell) => {
4020
- const th = document.createElement("th");
4021
- th.textContent = cell.trim();
4022
- th.style.border = "1px solid #d0d7de";
4023
- th.style.padding = "8px 12px";
4024
- th.style.backgroundColor = "#f6f8fa";
4025
- th.style.fontWeight = "600";
4026
- th.style.whiteSpace = "nowrap";
4027
- headerTr.appendChild(th);
4028
- });
4029
- table.appendChild(headerTr);
4030
- const start = (currentPage - 1) * ROWS_PER_PAGE;
4031
- const end = Math.min(dataLines.length, start + ROWS_PER_PAGE);
4032
- const pageRows = dataLines.slice(start, end);
4033
- pageRows.forEach((row, idx) => {
4034
- const tr = document.createElement("tr");
4035
- if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
4036
- const cells = row.split(delimiter);
4037
- cells.forEach((cell) => {
4038
- const td = document.createElement("td");
4039
- td.textContent = cell.trim();
4040
- td.style.border = "1px solid #d0d7de";
4041
- td.style.padding = "6px 12px";
4042
- td.style.whiteSpace = "nowrap";
4043
- tr.appendChild(td);
4044
- });
4045
- table.appendChild(tr);
3964
+ table.style.background = "#ffffff";
3965
+ table.style.border = "1px solid #d0d7de";
3966
+ const headerTr = document.createElement("tr");
3967
+ headerCells.forEach((cell) => {
3968
+ const th = document.createElement("th");
3969
+ th.textContent = cell.trim();
3970
+ th.style.border = "1px solid #d0d7de";
3971
+ th.style.padding = "8px 12px";
3972
+ th.style.backgroundColor = "#f6f8fa";
3973
+ th.style.fontWeight = "600";
3974
+ th.style.whiteSpace = "nowrap";
3975
+ headerTr.appendChild(th);
3976
+ });
3977
+ table.appendChild(headerTr);
3978
+ dataLines.forEach((row, idx) => {
3979
+ const tr = document.createElement("tr");
3980
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
3981
+ const cells = row.split(delimiter);
3982
+ cells.forEach((cell) => {
3983
+ const td = document.createElement("td");
3984
+ td.textContent = cell.trim();
3985
+ td.style.border = "1px solid #d0d7de";
3986
+ td.style.padding = "6px 12px";
3987
+ td.style.whiteSpace = "nowrap";
3988
+ tr.appendChild(td);
4046
3989
  });
4047
- container.scrollTop = 0;
4048
- ctx.emit("page-change", { page: currentPage, total: totalPages });
4049
- };
4050
- renderPage(1);
4051
- container.appendChild(table);
3990
+ table.appendChild(tr);
3991
+ });
3992
+ tableWrapper.appendChild(table);
3993
+ container.appendChild(tableWrapper);
4052
3994
  ctx.container.appendChild(container);
4053
3995
  let scale = 1;
3996
+ const calculateFitScale = () => {
3997
+ const availW = Math.max(280, container.clientWidth - 48);
3998
+ const tW = table.offsetWidth || 800;
3999
+ return Math.max(0.4, Math.min(1, availW / tW));
4000
+ };
4054
4001
  const applyScale = () => {
4055
- container.style.transform = `scale(${scale})`;
4056
- container.style.transformOrigin = "top left";
4002
+ tableWrapper.style.transform = `scale(${scale})`;
4057
4003
  };
4004
+ requestAnimationFrame(() => {
4005
+ scale = calculateFitScale();
4006
+ applyScale();
4007
+ });
4008
+ const ro = new ResizeObserver(() => {
4009
+ scale = calculateFitScale();
4010
+ applyScale();
4011
+ });
4012
+ ro.observe(container);
4058
4013
  const cleanup = () => {
4014
+ ro.disconnect();
4059
4015
  container.remove();
4060
4016
  ctx.container.innerHTML = "";
4061
4017
  };
4062
4018
  ctx.signal.addEventListener("abort", cleanup);
4063
4019
  return {
4064
4020
  destroy: cleanup,
4065
- getPageCount: () => totalPages,
4066
- getCurrentPage: () => currentPage,
4067
- goToPage: (page) => renderPage(page),
4021
+ getPageCount: () => 1,
4022
+ getCurrentPage: () => 1,
4023
+ goToPage: () => {
4024
+ },
4068
4025
  getThumbnails: () => {
4069
- const thumbnails = [];
4070
- for (let i = 0; i < totalPages; i++) {
4071
- const pageIdx = i;
4072
- const startRow = pageIdx * ROWS_PER_PAGE + 1;
4073
- const endRow = Math.min(dataLines.length, (pageIdx + 1) * ROWS_PER_PAGE);
4074
- const label = totalPages === 1 ? "Table" : `Page ${pageIdx + 1} (${startRow}-${endRow})`;
4075
- thumbnails.push({
4076
- index: pageIdx,
4077
- label,
4078
- render: async (canvas) => {
4079
- canvas.width = 140;
4080
- canvas.height = 100;
4081
- const c = canvas.getContext("2d");
4082
- if (!c) return;
4083
- c.fillStyle = "#ffffff";
4084
- c.fillRect(0, 0, canvas.width, canvas.height);
4085
- c.fillStyle = "#2563eb";
4086
- c.fillRect(0, 0, canvas.width, 16);
4087
- c.fillStyle = "#ffffff";
4088
- c.font = "bold 8px sans-serif";
4089
- c.fillText(`\u{1F4C8} ${label.slice(0, 18)}`, 6, 11);
4090
- const startY = 17;
4091
- const rowH = 13;
4092
- const colW = 28;
4093
- const cols = Math.min(4, headerCells.length || 4);
4094
- c.fillStyle = "#f1f5f9";
4095
- c.fillRect(0, startY, canvas.width, rowH);
4096
- c.strokeStyle = "#cbd5e1";
4097
- c.lineWidth = 0.8;
4098
- c.strokeRect(0, startY, canvas.width, rowH);
4099
- c.fillStyle = "#64748b";
4100
- c.font = "bold 7px sans-serif";
4101
- for (let ci = 0; ci < cols; ci++) {
4102
- const hName = headerCells[ci] || `Col ${ci + 1}`;
4103
- c.fillText(hName.slice(0, 5), 8 + ci * colW, startY + 9);
4104
- }
4105
- const rowsToPreview = dataLines.slice(pageIdx * ROWS_PER_PAGE, pageIdx * ROWS_PER_PAGE + 5);
4106
- for (let ri = 0; ri < rowsToPreview.length; ri++) {
4107
- const y = startY + (ri + 1) * rowH;
4108
- if (y > canvas.height - 2) break;
4109
- c.strokeStyle = "#e2e8f0";
4110
- c.strokeRect(0, y, canvas.width, rowH);
4111
- const cVals = rowsToPreview[ri].split(delimiter);
4112
- c.fillStyle = "#334155";
4113
- c.font = "6px sans-serif";
4114
- for (let ci = 0; ci < cols; ci++) {
4115
- const val = cVals[ci] || "";
4116
- c.fillText(val.trim().slice(0, 6), 8 + ci * colW, y + 9);
4026
+ const sheetName = ctx.metadata.name?.replace(/\.[^/.]+$/, "") || "Sheet1";
4027
+ return [{
4028
+ index: 0,
4029
+ label: sheetName,
4030
+ render: async (canvas) => {
4031
+ canvas.width = 140;
4032
+ canvas.height = 100;
4033
+ const c = canvas.getContext("2d");
4034
+ if (!c) return;
4035
+ c.fillStyle = "#ffffff";
4036
+ c.fillRect(0, 0, canvas.width, canvas.height);
4037
+ c.fillStyle = "#107c41";
4038
+ c.fillRect(0, 0, canvas.width, 16);
4039
+ c.fillStyle = "#ffffff";
4040
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
4041
+ c.fillText(`\u{1F4CA} ${sheetName.slice(0, 16)}`, 6, 11);
4042
+ const startY = 17;
4043
+ const rowH = 13;
4044
+ const colW = 30;
4045
+ const cols = headerCells.slice(0, 4).map((h) => h.trim() || "Col");
4046
+ c.fillStyle = "#f1f5f9";
4047
+ c.fillRect(0, startY, canvas.width, rowH);
4048
+ c.strokeStyle = "#cbd5e1";
4049
+ c.lineWidth = 0.8;
4050
+ c.strokeRect(0, startY, canvas.width, rowH);
4051
+ c.fillStyle = "#64748b";
4052
+ c.font = "bold 7px sans-serif";
4053
+ for (let ci = 0; ci < cols.length; ci++) {
4054
+ c.fillText(cols[ci].slice(0, 5), 14 + ci * colW, startY + 9);
4055
+ }
4056
+ for (let ri = 0; ri < Math.min(5, dataLines.length); ri++) {
4057
+ const y = startY + (ri + 1) * rowH;
4058
+ if (y > canvas.height - 2) break;
4059
+ c.fillStyle = "#f8fafc";
4060
+ c.fillRect(0, y, 12, rowH);
4061
+ c.fillStyle = "#94a3b8";
4062
+ c.font = "6px sans-serif";
4063
+ c.fillText(String(ri + 1), 3, y + 9);
4064
+ c.strokeStyle = "#e2e8f0";
4065
+ c.strokeRect(0, y, canvas.width, rowH);
4066
+ const rowCells = dataLines[ri].split(delimiter);
4067
+ c.fillStyle = "#334155";
4068
+ c.font = "6px sans-serif";
4069
+ for (let ci = 0; ci < cols.length; ci++) {
4070
+ const val = (rowCells[ci] || "").trim();
4071
+ if (val) {
4072
+ c.fillText(val.slice(0, 6), 14 + ci * colW, y + 9);
4117
4073
  }
4118
4074
  }
4119
- c.strokeStyle = "#cbd5e1";
4120
- c.lineWidth = 1;
4121
- c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4122
4075
  }
4123
- });
4124
- }
4125
- return thumbnails;
4076
+ c.strokeStyle = "#cbd5e1";
4077
+ c.lineWidth = 1;
4078
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4079
+ }
4080
+ }];
4126
4081
  },
4127
4082
  zoomIn: () => {
4128
4083
  scale += 0.1;
@@ -4138,11 +4093,13 @@ var CsvPlugin = class {
4138
4093
  applyScale();
4139
4094
  },
4140
4095
  fitToPage: () => {
4141
- const availW = Math.max(280, (ctx.container.clientWidth || window.innerWidth) - 48);
4142
- const tW = table.offsetWidth || 600;
4143
- scale = Math.max(0.35, Math.min(1, availW / tW));
4096
+ scale = calculateFitScale();
4144
4097
  applyScale();
4145
4098
  },
4099
+ rotateCW: () => {
4100
+ },
4101
+ rotateCCW: () => {
4102
+ },
4146
4103
  download: () => {
4147
4104
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
4148
4105
  const url = URL.createObjectURL(blob);
@@ -4254,26 +4211,6 @@ var CodePlugin = class {
4254
4211
  instance.zoomIn?.();
4255
4212
  }
4256
4213
  },
4257
- {
4258
- id: "fit-page",
4259
- icon: "fit-page",
4260
- label: "Fit to View",
4261
- type: "button",
4262
- group: "zoom",
4263
- execute: () => {
4264
- instance.fitToPage?.();
4265
- }
4266
- },
4267
- {
4268
- id: "rotate-cw",
4269
- icon: "rotate-cw",
4270
- label: "Rotate",
4271
- type: "button",
4272
- group: "view",
4273
- execute: () => {
4274
- instance.rotateCW?.();
4275
- }
4276
- },
4277
4214
  {
4278
4215
  id: "copy",
4279
4216
  icon: "copy",
@@ -5417,6 +5354,166 @@ var ThreeDPlugin = class {
5417
5354
  function threeDPlugin() {
5418
5355
  return new ThreeDPlugin();
5419
5356
  }
5357
+ function cleanRtfText(raw) {
5358
+ return raw.replace(/\\bin\d+\s/g, "").replace(/{\\\*[^}]+}/g, "").replace(/\\u(-?\d+)\??/g, (_, code) => {
5359
+ let num = parseInt(code, 10);
5360
+ if (num < 0) num += 65536;
5361
+ return String.fromCharCode(num);
5362
+ }).replace(/\\'([0-9a-fA-F]{2})/g, (_, hex) => {
5363
+ return String.fromCharCode(parseInt(hex, 16));
5364
+ }).replace(/\\tab\b/g, " ").replace(/\\emdash\b/g, "\u2014").replace(/\\endash\b/g, "\u2013").replace(/\\bullet\b/g, "\u2022").replace(/\\lquote\b/g, "\u2018").replace(/\\rquote\b/g, "\u2019").replace(/\\ldblquote\b/g, "\u201C").replace(/\\rdblquote\b/g, "\u201D").replace(/\\\w+(?:-?\d+)?\s?/g, " ").replace(/[{}\r\n]/g, " ").replace(/\s+/g, " ").trim();
5365
+ }
5366
+ function parseRtfTables(rtfText) {
5367
+ const rowSegments = rtfText.split(/\\row\b/);
5368
+ if (rowSegments.length <= 1) return [];
5369
+ const rawTables = [];
5370
+ let currentTable = null;
5371
+ let charOffset = 0;
5372
+ for (let i = 0; i < rowSegments.length - 1; i++) {
5373
+ const segment = rowSegments[i];
5374
+ const segmentStart = charOffset;
5375
+ const segmentEnd = charOffset + segment.length + 4;
5376
+ const cellParts = segment.split(/\\cell\b/);
5377
+ if (cellParts.length > 1) {
5378
+ const cellxMatches = [...segment.matchAll(/\\cellx(\d+)/g)].map((m) => parseInt(m[1], 10));
5379
+ let cellWidths = [];
5380
+ if (cellxMatches.length > 0) {
5381
+ let seq = [cellxMatches[0]];
5382
+ for (let k = 1; k < cellxMatches.length; k++) {
5383
+ if (cellxMatches[k] > cellxMatches[k - 1]) seq.push(cellxMatches[k]);
5384
+ else break;
5385
+ }
5386
+ let prev = 0;
5387
+ cellWidths = seq.map((x) => {
5388
+ const w = x - prev;
5389
+ prev = x;
5390
+ return w;
5391
+ });
5392
+ }
5393
+ const cells = [];
5394
+ for (let c = 0; c < cellParts.length - 1; c++) {
5395
+ let rawCell = cellParts[c];
5396
+ if (c === 0) {
5397
+ const lastTrowd = rawCell.lastIndexOf("\\trowd");
5398
+ if (lastTrowd !== -1) rawCell = rawCell.slice(lastTrowd);
5399
+ }
5400
+ cells.push(cleanRtfText(rawCell));
5401
+ }
5402
+ const isNewTable = !currentTable || segmentStart - currentTable.lastEnd > 2500;
5403
+ if (isNewTable) {
5404
+ const prevTextSlice = rtfText.slice(Math.max(0, segmentStart - 4e3), segmentStart);
5405
+ const cleanPrev = cleanRtfText(prevTextSlice);
5406
+ const anchorBefore = cleanPrev.slice(-80).trim();
5407
+ currentTable = {
5408
+ id: `RTF_TABLE_${rawTables.length}`,
5409
+ startPos: segmentStart,
5410
+ lastEnd: segmentEnd,
5411
+ rows: [],
5412
+ columnWidths: cellWidths,
5413
+ anchorBefore,
5414
+ anchorAfter: ""
5415
+ };
5416
+ rawTables.push(currentTable);
5417
+ }
5418
+ if (currentTable) {
5419
+ currentTable.rows.push({
5420
+ cells,
5421
+ widths: cellWidths.length === cells.length ? cellWidths : currentTable.columnWidths
5422
+ });
5423
+ currentTable.lastEnd = segmentEnd;
5424
+ }
5425
+ }
5426
+ charOffset += segment.length + 4;
5427
+ }
5428
+ rawTables.forEach((tbl) => {
5429
+ const afterSlice = rtfText.slice(tbl.lastEnd, Math.min(rtfText.length, tbl.lastEnd + 2e3));
5430
+ const cleanAfter = cleanRtfText(afterSlice);
5431
+ tbl.anchorAfter = cleanAfter.slice(0, 80).trim();
5432
+ });
5433
+ return rawTables.map((tbl) => {
5434
+ const totalW = (tbl.columnWidths || []).reduce((a, b) => a + b, 0);
5435
+ const colPercents = totalW > 0 ? tbl.columnWidths.map((w) => Math.round(w / totalW * 100)) : [];
5436
+ return {
5437
+ id: tbl.id,
5438
+ rows: tbl.rows.map((r) => r.cells),
5439
+ colPercents,
5440
+ anchorBefore: tbl.anchorBefore,
5441
+ anchorAfter: tbl.anchorAfter
5442
+ };
5443
+ });
5444
+ }
5445
+ function renderRtfTableElement(table) {
5446
+ const tableEl = document.createElement("table");
5447
+ tableEl.className = "fp-rtf-table";
5448
+ tableEl.style.width = "100%";
5449
+ tableEl.style.borderCollapse = "collapse";
5450
+ tableEl.style.margin = "16px 0 20px";
5451
+ tableEl.style.fontSize = "10pt";
5452
+ tableEl.style.lineHeight = "1.5";
5453
+ tableEl.style.border = "1px solid #cbd5e1";
5454
+ tableEl.style.borderRadius = "4px";
5455
+ tableEl.style.boxSizing = "border-box";
5456
+ tableEl.style.backgroundColor = "#ffffff";
5457
+ const thead = document.createElement("thead");
5458
+ const tbody = document.createElement("tbody");
5459
+ table.rows.forEach((rowCells, rIdx) => {
5460
+ const tr = document.createElement("tr");
5461
+ const isHeader = rIdx === 0;
5462
+ if (isHeader) {
5463
+ tr.style.backgroundColor = "#f8fafc";
5464
+ tr.style.borderBottom = "2px solid #cbd5e1";
5465
+ } else {
5466
+ tr.style.borderBottom = "1px solid #e2e8f0";
5467
+ if (rIdx % 2 === 0) {
5468
+ tr.style.backgroundColor = "#fcfdfe";
5469
+ }
5470
+ }
5471
+ rowCells.forEach((cellText, cIdx) => {
5472
+ const cellEl = document.createElement(isHeader ? "th" : "td");
5473
+ cellEl.style.padding = "8px 12px";
5474
+ cellEl.style.border = "1px solid #cbd5e1";
5475
+ cellEl.style.verticalAlign = "top";
5476
+ cellEl.style.color = isHeader ? "#0f172a" : "#1e293b";
5477
+ cellEl.style.fontWeight = isHeader ? "600" : "normal";
5478
+ if (table.colPercents && table.colPercents[cIdx]) {
5479
+ cellEl.style.width = `${table.colPercents[cIdx]}%`;
5480
+ }
5481
+ if (/^\d+$/.test(cellText.trim()) || cellText.trim() === "#") {
5482
+ cellEl.style.textAlign = "center";
5483
+ } else {
5484
+ cellEl.style.textAlign = "left";
5485
+ }
5486
+ cellEl.textContent = cellText;
5487
+ tr.appendChild(cellEl);
5488
+ });
5489
+ if (isHeader) {
5490
+ thead.appendChild(tr);
5491
+ } else {
5492
+ tbody.appendChild(tr);
5493
+ }
5494
+ });
5495
+ tableEl.appendChild(thead);
5496
+ tableEl.appendChild(tbody);
5497
+ return tableEl;
5498
+ }
5499
+ function extractMediaFingerprint(el) {
5500
+ const img = el.tagName.toLowerCase() === "img" ? el : el.querySelector("img");
5501
+ if (img && img.src) {
5502
+ const s = img.src;
5503
+ return s.length > 300 ? `img_${s.slice(0, 80)}_${s.length}_${s.slice(-80)}` : `img_${s}`;
5504
+ }
5505
+ const innerImg = el.querySelector("image");
5506
+ if (innerImg) {
5507
+ const h = innerImg.getAttribute("xlink:href") || innerImg.getAttribute("href") || "";
5508
+ if (h) return h.length > 300 ? `svgimg_${h.slice(0, 80)}_${h.length}_${h.slice(-80)}` : `svgimg_${h}`;
5509
+ }
5510
+ const svg = el.tagName.toLowerCase() === "svg" ? el : el.querySelector("svg");
5511
+ if (svg) {
5512
+ const vb = svg.getAttribute("viewBox") || "";
5513
+ return `svg_${vb}_${svg.innerHTML.length}`;
5514
+ }
5515
+ return null;
5516
+ }
5420
5517
  var RtfPlugin = class {
5421
5518
  id = "rtf";
5422
5519
  name = "Rich Text Format (RTF)";
@@ -5452,6 +5549,14 @@ var RtfPlugin = class {
5452
5549
  }
5453
5550
  });
5454
5551
  actions.push(
5552
+ {
5553
+ id: "thumbnails",
5554
+ icon: "thumbnails",
5555
+ label: "Thumbnails",
5556
+ type: "button",
5557
+ group: "view",
5558
+ execute: () => instance.toggleThumbnails?.()
5559
+ },
5455
5560
  {
5456
5561
  id: "zoom-out",
5457
5562
  icon: "zoom-out",
@@ -5579,9 +5684,29 @@ var RtfPlugin = class {
5579
5684
  if (typeof RTFJS__namespace.loggingEnabled === "function") {
5580
5685
  RTFJS__namespace.loggingEnabled(false);
5581
5686
  }
5582
- const doc = new RTFJS__namespace.Document(ctx.buffer, {});
5687
+ const rawText = new TextDecoder("latin1").decode(ctx.buffer);
5688
+ const tables = parseRtfTables(rawText);
5689
+ const seenMediaSignatures = /* @__PURE__ */ new Set();
5690
+ const doc = new RTFJS__namespace.Document(ctx.buffer, {
5691
+ onPicture: (isLegacy, createPic) => {
5692
+ if (isLegacy === true) {
5693
+ return null;
5694
+ }
5695
+ const pic = createPic();
5696
+ if (!pic) return null;
5697
+ const fp = extractMediaFingerprint(pic);
5698
+ if (fp) {
5699
+ if (seenMediaSignatures.has(fp)) {
5700
+ return null;
5701
+ }
5702
+ seenMediaSignatures.add(fp);
5703
+ }
5704
+ return pic;
5705
+ }
5706
+ });
5583
5707
  const htmlElements = await doc.render();
5584
5708
  const contentNodes = [];
5709
+ const seenInContentBlocks = /* @__PURE__ */ new Set();
5585
5710
  const extractBlocks = (nodes) => {
5586
5711
  for (const item of nodes) {
5587
5712
  if (!item || !(item instanceof HTMLElement)) continue;
@@ -5593,6 +5718,32 @@ var RtfPlugin = class {
5593
5718
  if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5594
5719
  extractBlocks(Array.from(item.children));
5595
5720
  } else {
5721
+ const fp = extractMediaFingerprint(item);
5722
+ if (fp) {
5723
+ if (seenInContentBlocks.has(fp)) {
5724
+ continue;
5725
+ }
5726
+ seenInContentBlocks.add(fp);
5727
+ }
5728
+ const hasMedia = item.querySelector("img, svg, canvas, table") !== null || ["img", "svg", "table"].includes(tag);
5729
+ const textContent = (item.textContent || "").trim();
5730
+ if (!hasMedia && textContent.length === 0) {
5731
+ continue;
5732
+ }
5733
+ const svgs = item.querySelectorAll("svg, img");
5734
+ svgs.forEach((s) => {
5735
+ const el = s;
5736
+ el.style.maxWidth = "100%";
5737
+ el.style.maxHeight = "360px";
5738
+ el.style.display = "block";
5739
+ el.style.margin = "12px auto";
5740
+ if (s.tagName.toLowerCase() === "svg") {
5741
+ s.removeAttribute("width");
5742
+ s.removeAttribute("height");
5743
+ el.style.width = "100%";
5744
+ el.style.maxHeight = "360px";
5745
+ }
5746
+ });
5596
5747
  if (tag === "p" || tag === "div") {
5597
5748
  item.style.display = "block";
5598
5749
  item.style.marginBottom = "12px";
@@ -5603,52 +5754,120 @@ var RtfPlugin = class {
5603
5754
  }
5604
5755
  };
5605
5756
  extractBlocks(htmlElements);
5606
- wrapper.innerHTML = "";
5607
- contentNodes.forEach((node) => wrapper.appendChild(node));
5608
- const childHeights = contentNodes.map((c) => {
5609
- const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
5610
- const offH = c.offsetHeight || 0;
5611
- const textLen = c.textContent?.trim().length || 0;
5612
- const estH = Math.max(28, Math.ceil(textLen / 75) * 24 + 16);
5613
- return Math.max(rectH, offH, estH);
5757
+ tables.forEach((table) => {
5758
+ const tableEl = renderRtfTableElement(table);
5759
+ let inserted = false;
5760
+ if (table.anchorBefore && table.anchorBefore.length > 10) {
5761
+ const keyword = table.anchorBefore.slice(-35).trim();
5762
+ for (let i = 0; i < contentNodes.length; i++) {
5763
+ if (contentNodes[i].textContent?.includes(keyword)) {
5764
+ contentNodes.splice(i + 1, 0, tableEl);
5765
+ inserted = true;
5766
+ break;
5767
+ }
5768
+ }
5769
+ }
5770
+ if (!inserted && table.anchorAfter && table.anchorAfter.length > 10) {
5771
+ const keyword = table.anchorAfter.slice(0, 35).trim();
5772
+ for (let i = 0; i < contentNodes.length; i++) {
5773
+ if (contentNodes[i].textContent?.includes(keyword)) {
5774
+ contentNodes.splice(i, 0, tableEl);
5775
+ inserted = true;
5776
+ break;
5777
+ }
5778
+ }
5779
+ }
5780
+ if (!inserted) {
5781
+ if (contentNodes.length > 2) {
5782
+ contentNodes.splice(2, 0, tableEl);
5783
+ } else {
5784
+ contentNodes.push(tableEl);
5785
+ }
5786
+ }
5614
5787
  });
5615
- wrapper.innerHTML = "";
5616
- const createRtfCard = () => {
5788
+ const createRtfCard = (pageNum) => {
5617
5789
  const card = document.createElement("div");
5618
5790
  card.className = "fp-rtf-page-card";
5791
+ card.setAttribute("data-page-number", String(pageNum));
5619
5792
  card.style.backgroundColor = "#ffffff";
5620
- card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5793
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.04)";
5621
5794
  card.style.borderRadius = "4px";
5622
- card.style.padding = "72px 56px";
5795
+ card.style.padding = "64px 56px";
5623
5796
  card.style.width = "816px";
5624
5797
  card.style.minHeight = "1056px";
5798
+ card.style.maxHeight = "1056px";
5799
+ card.style.overflow = "hidden";
5625
5800
  card.style.boxSizing = "border-box";
5626
5801
  card.style.marginBottom = "24px";
5627
5802
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5628
5803
  card.style.lineHeight = "1.6";
5629
5804
  card.style.color = "#1e293b";
5805
+ card.style.position = "relative";
5630
5806
  return card;
5631
5807
  };
5632
- let curCard = createRtfCard();
5808
+ wrapper.innerHTML = "";
5809
+ const measureCard = createRtfCard(0);
5810
+ measureCard.style.maxHeight = "none";
5811
+ measureCard.style.height = "auto";
5812
+ wrapper.appendChild(measureCard);
5813
+ contentNodes.forEach((node) => measureCard.appendChild(node));
5814
+ const childHeights = contentNodes.map((c) => {
5815
+ const rect = c.getBoundingClientRect ? c.getBoundingClientRect() : null;
5816
+ const rectH = rect ? rect.height : 0;
5817
+ const offH = c.offsetHeight || 0;
5818
+ let realH = Math.max(rectH, offH);
5819
+ try {
5820
+ const cs = window.getComputedStyle(c);
5821
+ realH += (parseFloat(cs.marginTop) || 0) + (parseFloat(cs.marginBottom) || 0);
5822
+ } catch {
5823
+ }
5824
+ const hasMedia = c.querySelector("img, svg") !== null || ["img", "svg"].includes(c.tagName.toLowerCase());
5825
+ const hasTable = c.querySelector("table") !== null || c.tagName.toLowerCase() === "table";
5826
+ if (hasMedia) {
5827
+ realH = Math.max(realH, 300);
5828
+ } else if (hasTable) {
5829
+ realH = Math.max(realH, 240);
5830
+ }
5831
+ if (realH > 0) return realH;
5832
+ const textLen = c.textContent?.trim().length || 0;
5833
+ return Math.max(28, Math.ceil(textLen / 75) * 26 + 16);
5834
+ });
5835
+ wrapper.innerHTML = "";
5836
+ let curCard = createRtfCard(1);
5633
5837
  wrapper.appendChild(curCard);
5634
5838
  pageElements = [curCard];
5635
5839
  let curH = 0;
5636
- const maxH = 912;
5840
+ const maxH = 860;
5637
5841
  for (let i = 0; i < contentNodes.length; i++) {
5638
5842
  const child = contentNodes[i];
5639
5843
  const chH = childHeights[i];
5640
- curCard.appendChild(child);
5641
- curH += chH;
5642
- if (curH >= maxH && i < contentNodes.length - 1) {
5643
- curCard = createRtfCard();
5844
+ const isChildEmpty = (child.textContent || "").trim().length === 0 && !child.querySelector("table, img, svg, canvas");
5845
+ if (curH === 0 && isChildEmpty) {
5846
+ continue;
5847
+ }
5848
+ const isTable = child.querySelector("table") !== null || child.tagName.toLowerCase() === "table";
5849
+ const shouldBreakBeforeTable = isTable && curH > 400 && curCard.childNodes.length > 0;
5850
+ if ((curH + chH > maxH || shouldBreakBeforeTable) && curCard.childNodes.length > 0) {
5851
+ curCard = createRtfCard(pageElements.length + 1);
5644
5852
  wrapper.appendChild(curCard);
5645
5853
  pageElements.push(curCard);
5646
5854
  curH = 0;
5647
5855
  }
5856
+ curCard.appendChild(child);
5857
+ curH += chH;
5858
+ }
5859
+ pageElements = pageElements.filter((card) => {
5860
+ const hasText = (card.textContent || "").trim().length > 0;
5861
+ const hasMedia = card.querySelector("table, img, svg, canvas") !== null;
5862
+ return hasText || hasMedia;
5863
+ });
5864
+ if (pageElements.length === 0) {
5865
+ pageElements = [curCard];
5648
5866
  }
5649
5867
  } catch (err) {
5650
5868
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
5651
5869
  const text = new TextDecoder("latin1").decode(ctx.buffer);
5870
+ const tables = parseRtfTables(text);
5652
5871
  const rawPages = text.split(/\\page\b/).map((segment) => {
5653
5872
  return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
5654
5873
  }).filter((p) => p.length > 0);
@@ -5659,7 +5878,7 @@ var RtfPlugin = class {
5659
5878
  pageCard.style.backgroundColor = "#ffffff";
5660
5879
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
5661
5880
  pageCard.style.borderRadius = "4px";
5662
- pageCard.style.padding = "72px 56px";
5881
+ pageCard.style.padding = "64px 56px";
5663
5882
  pageCard.style.width = "816px";
5664
5883
  pageCard.style.minHeight = "1056px";
5665
5884
  pageCard.style.boxSizing = "border-box";
@@ -5670,6 +5889,9 @@ var RtfPlugin = class {
5670
5889
  pageCard.style.whiteSpace = "pre-wrap";
5671
5890
  pageCard.style.display = i === 0 ? "block" : "none";
5672
5891
  pageCard.textContent = pages[i];
5892
+ if (i === 1 && tables.length > 0) {
5893
+ tables.forEach((tbl) => pageCard.appendChild(renderRtfTableElement(tbl)));
5894
+ }
5673
5895
  wrapper.appendChild(pageCard);
5674
5896
  pageElements.push(pageCard);
5675
5897
  }
@@ -5700,6 +5922,32 @@ var RtfPlugin = class {
5700
5922
  getPageCount: () => totalPages,
5701
5923
  getCurrentPage: () => currentPage,
5702
5924
  goToPage: (page) => showPage(page),
5925
+ getThumbnails: async () => {
5926
+ const items = [];
5927
+ for (let idx = 0; idx < totalPages; idx++) {
5928
+ const pageIdx = idx;
5929
+ items.push({
5930
+ index: pageIdx,
5931
+ label: `Page ${pageIdx + 1}`,
5932
+ render: async (canvas) => {
5933
+ const ctx2d = canvas.getContext("2d");
5934
+ if (!ctx2d) return;
5935
+ canvas.width = 140;
5936
+ canvas.height = 180;
5937
+ ctx2d.fillStyle = "#ffffff";
5938
+ ctx2d.fillRect(0, 0, 140, 180);
5939
+ ctx2d.strokeStyle = "#cbd5e1";
5940
+ ctx2d.lineWidth = 1;
5941
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
5942
+ ctx2d.fillStyle = "#334155";
5943
+ ctx2d.font = 'bold 11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
5944
+ ctx2d.textAlign = "center";
5945
+ ctx2d.fillText(`Page ${pageIdx + 1}`, 70, 95);
5946
+ }
5947
+ });
5948
+ }
5949
+ return items;
5950
+ },
5703
5951
  zoomIn: () => {
5704
5952
  isUserZoomed = true;
5705
5953
  scale += 0.15;