@files-preview-app/preview-file 1.2.2 → 1.2.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/react.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var react = require('react');
6
- var DOMPurify6 = require('dompurify');
6
+ var DOMPurify2 = require('dompurify');
7
7
  var docx = require('docx-preview');
8
8
  var fflate = require('fflate');
9
9
  var XLSX = require('xlsx');
@@ -14,7 +14,7 @@ var THREE = require('three');
14
14
  var STLLoader_js = require('three/examples/jsm/loaders/STLLoader.js');
15
15
  var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
16
16
  var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
17
- var rtf_js = require('rtf.js');
17
+ var RTFJS = require('rtf.js/dist/RTFJS.bundle.js');
18
18
  var jsxRuntime = require('react/jsx-runtime');
19
19
 
20
20
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -37,11 +37,12 @@ function _interopNamespace(e) {
37
37
  return Object.freeze(n);
38
38
  }
39
39
 
40
- var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
40
+ var DOMPurify2__default = /*#__PURE__*/_interopDefault(DOMPurify2);
41
41
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
42
42
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
43
43
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
44
44
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
45
+ var RTFJS__namespace = /*#__PURE__*/_interopNamespace(RTFJS);
45
46
 
46
47
  // src/react.tsx
47
48
  var EventEmitter = class {
@@ -297,6 +298,39 @@ function detectOoxmlType(buffer) {
297
298
  }
298
299
  return "application/zip";
299
300
  }
301
+ function detectCfbfType(buffer) {
302
+ const bytes = new Uint8Array(buffer);
303
+ const hasUtf16le = (str) => {
304
+ const target = new Uint8Array(str.length * 2);
305
+ for (let i = 0; i < str.length; i++) {
306
+ target[i * 2] = str.charCodeAt(i);
307
+ target[i * 2 + 1] = 0;
308
+ }
309
+ const targetLen = target.length;
310
+ const max = bytes.length - targetLen;
311
+ for (let i = 0; i <= max; i++) {
312
+ let match = true;
313
+ for (let j = 0; j < targetLen; j++) {
314
+ if (bytes[i + j] !== target[j]) {
315
+ match = false;
316
+ break;
317
+ }
318
+ }
319
+ if (match) return true;
320
+ }
321
+ return false;
322
+ };
323
+ if (hasUtf16le("PowerPoint Document")) {
324
+ return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
325
+ }
326
+ if (hasUtf16le("WordDocument")) {
327
+ return { mime: "application/msword", extension: ".doc" };
328
+ }
329
+ if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
330
+ return { mime: "application/vnd.ms-excel", extension: ".xls" };
331
+ }
332
+ return null;
333
+ }
300
334
  function extractExtension(nameOrUrl) {
301
335
  try {
302
336
  const url = new URL(nameOrUrl);
@@ -361,6 +395,18 @@ async function sourceToArrayBuffer(source, signal) {
361
395
  } else {
362
396
  metadata.mimeType = metadata.mimeType ?? magicMime;
363
397
  }
398
+ } else if (magicMime === "application/x-cfbf") {
399
+ if (metadata.extension) {
400
+ metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
401
+ } else {
402
+ const cfbf = detectCfbfType(buffer);
403
+ if (cfbf) {
404
+ metadata.mimeType = cfbf.mime;
405
+ metadata.extension = cfbf.extension;
406
+ } else {
407
+ metadata.mimeType = magicMime;
408
+ }
409
+ }
364
410
  } else {
365
411
  metadata.mimeType = magicMime;
366
412
  }
@@ -371,7 +417,7 @@ async function sourceToArrayBuffer(source, signal) {
371
417
  return { buffer, metadata };
372
418
  }
373
419
  function sanitizeSVG(svg) {
374
- return DOMPurify6__default.default.sanitize(svg, {
420
+ return DOMPurify2__default.default.sanitize(svg, {
375
421
  USE_PROFILES: { svg: true, svgFilters: true }
376
422
  });
377
423
  }
@@ -431,6 +477,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
431
477
  var ICON_CLOSE = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
432
478
  var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>`;
433
479
  var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
480
+ var ICON_FAST_FORWARD = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon></svg>`;
481
+ var ICON_REWIND = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon></svg>`;
482
+ var ICON_SPEED = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`;
434
483
  var ICON_MAP = {
435
484
  "zoom-in": ICON_ZOOM_IN,
436
485
  "zoom-out": ICON_ZOOM_OUT,
@@ -451,7 +500,12 @@ var ICON_MAP = {
451
500
  "page-prev": ICON_PAGE_PREV,
452
501
  "page-next": ICON_PAGE_NEXT,
453
502
  "chevron-left": ICON_PAGE_PREV,
454
- "chevron-right": ICON_PAGE_NEXT
503
+ "chevron-right": ICON_PAGE_NEXT,
504
+ "fast-forward": ICON_FAST_FORWARD,
505
+ "forward-10": ICON_FAST_FORWARD,
506
+ "rewind": ICON_REWIND,
507
+ "replay-10": ICON_REWIND,
508
+ "speed": ICON_SPEED
455
509
  };
456
510
  var ToolbarController = class {
457
511
  el;
@@ -1419,6 +1473,16 @@ var MediaPlugin = class {
1419
1473
  }
1420
1474
  if (instance.play) {
1421
1475
  actions.push(
1476
+ {
1477
+ id: "replay-10",
1478
+ icon: "replay-10",
1479
+ label: "Rewind 10s",
1480
+ type: "button",
1481
+ group: "actions",
1482
+ execute: () => {
1483
+ instance.rewind?.(10);
1484
+ }
1485
+ },
1422
1486
  {
1423
1487
  id: "play",
1424
1488
  icon: "play",
@@ -1438,6 +1502,26 @@ var MediaPlugin = class {
1438
1502
  execute: () => {
1439
1503
  instance.pause?.();
1440
1504
  }
1505
+ },
1506
+ {
1507
+ id: "forward-10",
1508
+ icon: "forward-10",
1509
+ label: "Fast Forward 10s",
1510
+ type: "button",
1511
+ group: "actions",
1512
+ execute: () => {
1513
+ instance.fastForward?.(10);
1514
+ }
1515
+ },
1516
+ {
1517
+ id: "speed",
1518
+ icon: "speed",
1519
+ label: "Playback Speed",
1520
+ type: "button",
1521
+ group: "actions",
1522
+ execute: () => {
1523
+ instance.cycleSpeed?.();
1524
+ }
1441
1525
  }
1442
1526
  );
1443
1527
  }
@@ -1560,6 +1644,28 @@ var MediaPlugin = class {
1560
1644
  play: mediaElement ? () => mediaElement?.play() : void 0,
1561
1645
  pause: mediaElement ? () => mediaElement?.pause() : void 0,
1562
1646
  isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1647
+ fastForward: mediaElement ? (seconds = 10) => {
1648
+ if (mediaElement) {
1649
+ mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
1650
+ }
1651
+ } : void 0,
1652
+ rewind: mediaElement ? (seconds = 10) => {
1653
+ if (mediaElement) {
1654
+ mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
1655
+ }
1656
+ } : void 0,
1657
+ cycleSpeed: mediaElement ? () => {
1658
+ if (mediaElement) {
1659
+ const speeds = [1, 1.25, 1.5, 2, 0.5];
1660
+ const curIndex = speeds.indexOf(mediaElement.playbackRate);
1661
+ const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
1662
+ mediaElement.playbackRate = speeds[nextIndex];
1663
+ }
1664
+ } : void 0,
1665
+ setPlaybackRate: mediaElement ? (rate) => {
1666
+ if (mediaElement) mediaElement.playbackRate = rate;
1667
+ } : void 0,
1668
+ getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
1563
1669
  download: () => {
1564
1670
  const a = document.createElement("a");
1565
1671
  a.href = url;
@@ -1589,7 +1695,10 @@ var DocxPlugin = class {
1589
1695
  supports(file) {
1590
1696
  const ext = file.metadata.extension?.toLowerCase();
1591
1697
  const mime = file.metadata.mimeType?.toLowerCase();
1592
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
1698
+ if (ext) {
1699
+ return this.extensions.includes(ext);
1700
+ }
1701
+ return this.mimeTypes.includes(mime || "");
1593
1702
  }
1594
1703
  getToolbarActions(instance) {
1595
1704
  return [
@@ -1641,16 +1750,35 @@ var DocxPlugin = class {
1641
1750
  wrapper.style.transformOrigin = "top center";
1642
1751
  wrapper.style.transition = "transform 0.2s ease";
1643
1752
  wrapper.style.padding = "24px 16px";
1644
- wrapper.style.maxWidth = "900px";
1753
+ wrapper.style.maxWidth = "950px";
1645
1754
  wrapper.style.margin = "0 auto";
1646
1755
  wrapper.style.boxSizing = "border-box";
1647
1756
  ctx.container.style.overflow = "auto";
1648
1757
  ctx.container.style.backgroundColor = "#f1f5f9";
1649
1758
  ctx.container.appendChild(wrapper);
1759
+ const styleOverride = document.createElement("style");
1760
+ styleOverride.textContent = `
1761
+ .fp-docx-wrapper .docx-wrapper {
1762
+ background: transparent !important;
1763
+ padding: 0 !important;
1764
+ display: flex !important;
1765
+ flex-direction: column !important;
1766
+ align-items: center !important;
1767
+ box-sizing: border-box !important;
1768
+ }
1769
+ .fp-docx-wrapper section.docx {
1770
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
1771
+ border-radius: 4px !important;
1772
+ margin-bottom: 24px !important;
1773
+ background-color: #ffffff !important;
1774
+ }
1775
+ `;
1776
+ ctx.container.appendChild(styleOverride);
1650
1777
  let scale = 1;
1651
1778
  let renderedSuccessfully = false;
1779
+ const createdBlobUrls = [];
1652
1780
  try {
1653
- await docx__namespace.renderAsync(ctx.buffer, wrapper, ctx.container, {
1781
+ await docx__namespace.renderAsync(ctx.buffer, wrapper, wrapper, {
1654
1782
  inWrapper: true,
1655
1783
  ignoreWidth: false,
1656
1784
  ignoreHeight: false,
@@ -1659,19 +1787,25 @@ var DocxPlugin = class {
1659
1787
  breakPages: true,
1660
1788
  experimental: true
1661
1789
  });
1790
+ if (!ctx.container.contains(wrapper)) {
1791
+ ctx.container.appendChild(wrapper);
1792
+ }
1662
1793
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
1663
1794
  renderedSuccessfully = true;
1664
1795
  }
1665
1796
  } catch (err) {
1666
- console.warn("[DocxPlugin] docx-preview failed, triggering native XML fallback:", err);
1797
+ console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
1667
1798
  }
1668
1799
  if (!renderedSuccessfully) {
1669
1800
  try {
1670
1801
  wrapper.innerHTML = "";
1671
- this.renderXmlFallback(ctx, wrapper);
1802
+ this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
1803
+ if (!ctx.container.contains(wrapper)) {
1804
+ ctx.container.appendChild(wrapper);
1805
+ }
1672
1806
  renderedSuccessfully = true;
1673
1807
  } catch (fallbackErr) {
1674
- console.error("[DocxPlugin] XML fallback failed:", fallbackErr);
1808
+ console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
1675
1809
  wrapper.innerHTML = `
1676
1810
  <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
1677
1811
  <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
@@ -1679,10 +1813,17 @@ var DocxPlugin = class {
1679
1813
  <p style="color: #64748b; margin: 0;">Could not parse document content</p>
1680
1814
  </div>
1681
1815
  `;
1816
+ if (!ctx.container.contains(wrapper)) {
1817
+ ctx.container.appendChild(wrapper);
1818
+ }
1682
1819
  }
1683
1820
  }
1684
1821
  const cleanup = () => {
1822
+ for (const url of createdBlobUrls) {
1823
+ URL.revokeObjectURL(url);
1824
+ }
1685
1825
  wrapper.remove();
1826
+ styleOverride.remove();
1686
1827
  ctx.container.innerHTML = "";
1687
1828
  };
1688
1829
  ctx.signal.addEventListener("abort", cleanup);
@@ -1719,13 +1860,50 @@ var DocxPlugin = class {
1719
1860
  }
1720
1861
  };
1721
1862
  }
1722
- renderXmlFallback(ctx, wrapper) {
1863
+ renderXmlFallback(ctx, wrapper, createdBlobUrls) {
1864
+ const magic = new Uint8Array(ctx.buffer.slice(0, 8));
1865
+ if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
1866
+ this.renderBinaryDocFallback(ctx, wrapper);
1867
+ return;
1868
+ }
1723
1869
  const unzipped = fflate.unzipSync(new Uint8Array(ctx.buffer));
1724
- const docXmlEntry = unzipped["word/document.xml"];
1870
+ const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
1871
+ const docXmlEntry = docKey ? unzipped[docKey] : void 0;
1725
1872
  if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
1726
1873
  const xmlStr = fflate.strFromU8(docXmlEntry);
1727
1874
  const parser = new DOMParser();
1728
1875
  const doc = parser.parseFromString(xmlStr, "application/xml");
1876
+ const imageMap = {};
1877
+ const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
1878
+ if (relsKey && unzipped[relsKey]) {
1879
+ try {
1880
+ const relsXml = fflate.strFromU8(unzipped[relsKey]);
1881
+ const relsDoc = parser.parseFromString(relsXml, "application/xml");
1882
+ const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
1883
+ for (const rel of relEls) {
1884
+ const rId = rel.getAttribute("Id");
1885
+ const target = rel.getAttribute("Target");
1886
+ if (rId && target) {
1887
+ const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
1888
+ const fullTargetKey = Object.keys(unzipped).find((k) => {
1889
+ const norm = k.replace(/^[./\\]+/, "").toLowerCase();
1890
+ return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
1891
+ });
1892
+ if (fullTargetKey && unzipped[fullTargetKey]) {
1893
+ const bytes = unzipped[fullTargetKey];
1894
+ const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
1895
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
1896
+ const blob = new Blob([bytes], { type: mime });
1897
+ const blobUrl = URL.createObjectURL(blob);
1898
+ imageMap[rId] = blobUrl;
1899
+ createdBlobUrls.push(blobUrl);
1900
+ }
1901
+ }
1902
+ }
1903
+ } catch (relsErr) {
1904
+ console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
1905
+ }
1906
+ }
1729
1907
  const card = document.createElement("div");
1730
1908
  card.className = "fp-docx-page-card";
1731
1909
  card.style.backgroundColor = "#ffffff";
@@ -1743,7 +1921,7 @@ var DocxPlugin = class {
1743
1921
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
1744
1922
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
1745
1923
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
1746
- const textContent = this.extractParagraphHtml(child);
1924
+ const textContent = this.extractParagraphHtml(child, imageMap);
1747
1925
  if (!textContent.trim()) {
1748
1926
  html += '<div style="height: 10px;"></div>';
1749
1927
  continue;
@@ -1769,7 +1947,7 @@ var DocxPlugin = class {
1769
1947
  html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
1770
1948
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
1771
1949
  cells.forEach((tc) => {
1772
- const cellText = this.extractParagraphHtml(tc);
1950
+ const cellText = this.extractParagraphHtml(tc, imageMap);
1773
1951
  html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
1774
1952
  });
1775
1953
  html += "</tr>";
@@ -1777,34 +1955,63 @@ var DocxPlugin = class {
1777
1955
  html += "</table>";
1778
1956
  }
1779
1957
  }
1780
- card.innerHTML = DOMPurify6__default.default.sanitize(html, {
1781
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "img", "div"],
1958
+ card.innerHTML = DOMPurify2__default.default.sanitize(html, {
1959
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
1782
1960
  ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
1783
1961
  });
1784
1962
  wrapper.appendChild(card);
1785
1963
  }
1786
- extractParagraphHtml(pElement) {
1964
+ extractParagraphHtml(pElement, imageMap = {}) {
1787
1965
  let result = "";
1966
+ const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
1967
+ for (const drawing of drawings) {
1968
+ const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
1969
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1970
+ if (rId && imageMap[rId]) {
1971
+ result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
1972
+ }
1973
+ }
1788
1974
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
1789
- if (runs.length === 0) {
1790
- return DOMPurify6__default.default.sanitize(pElement.textContent || "");
1975
+ if (runs.length === 0 && !result) {
1976
+ return DOMPurify2__default.default.sanitize(pElement.textContent || "");
1791
1977
  }
1792
1978
  for (const r of runs) {
1979
+ const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
1980
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1981
+ if (rId && imageMap[rId]) {
1982
+ result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
1983
+ }
1984
+ const brs = r.getElementsByTagNameNS("*", "br");
1985
+ for (let i = 0; i < brs.length; i++) {
1986
+ result += "<br/>";
1987
+ }
1988
+ const tabs = r.getElementsByTagNameNS("*", "tab");
1989
+ if (tabs.length > 0) {
1990
+ result += "&emsp;";
1991
+ }
1793
1992
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
1794
1993
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
1795
1994
  const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
1796
1995
  const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
1996
+ const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
1797
1997
  const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
1798
1998
  const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
1999
+ const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
2000
+ const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
1799
2001
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
1800
2002
  let text = texts.map((t) => t.textContent || "").join("");
1801
2003
  if (!text) continue;
1802
- text = DOMPurify6__default.default.sanitize(text);
2004
+ text = DOMPurify2__default.default.sanitize(text);
1803
2005
  let styles = "";
1804
2006
  if (isBold) styles += "font-weight: bold; ";
1805
2007
  if (isItalic) styles += "font-style: italic; ";
1806
2008
  if (isUnderline) styles += "text-decoration: underline; ";
2009
+ if (isStrike) styles += "text-decoration: line-through; ";
1807
2010
  if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
2011
+ if (szVal) {
2012
+ const pt = parseInt(szVal, 10) / 2;
2013
+ if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2014
+ }
1808
2015
  if (styles) {
1809
2016
  result += `<span style="${styles}">${text}</span>`;
1810
2017
  } else {
@@ -1813,6 +2020,52 @@ var DocxPlugin = class {
1813
2020
  }
1814
2021
  return result;
1815
2022
  }
2023
+ renderBinaryDocFallback(ctx, wrapper) {
2024
+ const card = document.createElement("div");
2025
+ card.className = "fp-docx-page-card";
2026
+ card.style.backgroundColor = "#ffffff";
2027
+ card.style.borderRadius = "6px";
2028
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2029
+ card.style.padding = "48px 56px";
2030
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2031
+ card.style.color = "#1e293b";
2032
+ card.style.lineHeight = "1.6";
2033
+ try {
2034
+ const cfbf = new CfbfReader(ctx.buffer);
2035
+ const wordDocStream = cfbf.readStream("WordDocument");
2036
+ if (wordDocStream && wordDocStream.length >= 512) {
2037
+ const text2 = this.extractReadableStrings(wordDocStream);
2038
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text2)}</div>`;
2039
+ wrapper.appendChild(card);
2040
+ return;
2041
+ }
2042
+ } catch {
2043
+ }
2044
+ const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2045
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text)}</div>`;
2046
+ wrapper.appendChild(card);
2047
+ }
2048
+ extractReadableStrings(bytes) {
2049
+ let result = "";
2050
+ let current = "";
2051
+ for (let i = 0; i < bytes.length; i++) {
2052
+ const b = bytes[i];
2053
+ if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
2054
+ current += String.fromCharCode(b);
2055
+ } else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
2056
+ continue;
2057
+ } else {
2058
+ if (current.trim().length > 3) {
2059
+ result += current.trim() + "\n\n";
2060
+ }
2061
+ current = "";
2062
+ }
2063
+ }
2064
+ if (current.trim().length > 3) {
2065
+ result += current.trim();
2066
+ }
2067
+ return result;
2068
+ }
1816
2069
  };
1817
2070
  function docxPlugin() {
1818
2071
  return new DocxPlugin();
@@ -1834,7 +2087,10 @@ var ExcelPlugin = class {
1834
2087
  supports(file) {
1835
2088
  const ext = file.metadata.extension?.toLowerCase();
1836
2089
  const mime = file.metadata.mimeType?.toLowerCase();
1837
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
2090
+ if (ext) {
2091
+ return this.extensions.includes(ext);
2092
+ }
2093
+ return this.mimeTypes.includes(mime || "");
1838
2094
  }
1839
2095
  getToolbarActions(instance) {
1840
2096
  return [
@@ -2624,7 +2880,7 @@ var MarkdownPlugin = class {
2624
2880
  gfm: true,
2625
2881
  breaks: true
2626
2882
  });
2627
- const cleanHtml = DOMPurify6__default.default.sanitize(rawHtml, {
2883
+ const cleanHtml = DOMPurify2__default.default.sanitize(rawHtml, {
2628
2884
  USE_PROFILES: { html: true }
2629
2885
  });
2630
2886
  const wrapper = document.createElement("div");
@@ -2776,7 +3032,10 @@ var PptxPlugin = class {
2776
3032
  supports(file) {
2777
3033
  const ext = file.metadata.extension?.toLowerCase();
2778
3034
  const mime = file.metadata.mimeType?.toLowerCase();
2779
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3035
+ if (ext) {
3036
+ return this.extensions.includes(ext);
3037
+ }
3038
+ return this.mimeTypes.includes(mime || "");
2780
3039
  }
2781
3040
  getToolbarActions(instance) {
2782
3041
  return [
@@ -3221,7 +3480,10 @@ var RtfPlugin = class {
3221
3480
  ctx.container.appendChild(wrapper);
3222
3481
  let scale = 1;
3223
3482
  try {
3224
- const doc = new rtf_js.RTFJS.Document(ctx.buffer, {});
3483
+ if (typeof RTFJS__namespace.loggingEnabled === "function") {
3484
+ RTFJS__namespace.loggingEnabled(false);
3485
+ }
3486
+ const doc = new RTFJS__namespace.Document(ctx.buffer, {});
3225
3487
  const htmlElements = await doc.render();
3226
3488
  for (const el of htmlElements) {
3227
3489
  wrapper.appendChild(el);
@@ -3339,7 +3601,7 @@ var HtmlPreviewPlugin = class {
3339
3601
  }
3340
3602
  async render(ctx) {
3341
3603
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3342
- const sanitized = DOMPurify6__default.default.sanitize(rawHtml, {
3604
+ const sanitized = DOMPurify2__default.default.sanitize(rawHtml, {
3343
3605
  WHOLE_DOCUMENT: true,
3344
3606
  ADD_TAGS: ["style", "link"],
3345
3607
  ADD_ATTR: ["target", "rel"]
@@ -3419,7 +3681,10 @@ var OpenDocumentPlugin = class {
3419
3681
  supports(file) {
3420
3682
  const ext = file.metadata.extension?.toLowerCase();
3421
3683
  const mime = file.metadata.mimeType?.toLowerCase();
3422
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3684
+ if (ext) {
3685
+ return this.extensions.includes(ext);
3686
+ }
3687
+ return this.mimeTypes.includes(mime || "");
3423
3688
  }
3424
3689
  getToolbarActions(instance) {
3425
3690
  const isPresentation = instance.isPresentation;
@@ -3582,7 +3847,7 @@ var OpenDocumentPlugin = class {
3582
3847
  wrapper.style.padding = "48px";
3583
3848
  wrapper.style.minHeight = "100%";
3584
3849
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
3585
- wrapper.innerHTML = DOMPurify6__default.default.sanitize(bodyHtml, {
3850
+ wrapper.innerHTML = DOMPurify2__default.default.sanitize(bodyHtml, {
3586
3851
  ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
3587
3852
  ADD_ATTR: ["style", "colspan", "rowspan"]
3588
3853
  });
@@ -3834,7 +4099,10 @@ var DocPlugin = class {
3834
4099
  supports(file) {
3835
4100
  const ext = file.metadata.extension?.toLowerCase();
3836
4101
  const mime = file.metadata.mimeType?.toLowerCase();
3837
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4102
+ if (ext) {
4103
+ return this.extensions.includes(ext);
4104
+ }
4105
+ return this.mimeTypes.includes(mime || "");
3838
4106
  }
3839
4107
  getToolbarActions(instance) {
3840
4108
  return [
@@ -4057,22 +4325,22 @@ var DocPlugin = class {
4057
4325
  * Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
4058
4326
  */
4059
4327
  extractStringsFromBytes(bytes) {
4060
- const chars = [];
4061
- const len = bytes.length;
4062
- for (let i = 0; i < len; i++) {
4063
- const b = bytes[i];
4064
- if (b === 13 || b === 10 || b === 9 || b >= 32 && b <= 126 || b >= 160 && b <= 255) {
4065
- chars.push(String.fromCharCode(b));
4066
- } else if (b === 0 && i + 1 < len && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
4067
- chars.push(String.fromCharCode(bytes[i + 1]));
4068
- i++;
4069
- } else if (b === 7) {
4070
- chars.push(" ");
4071
- } else if (b === 12) {
4072
- chars.push("\n\n---PAGE---\n\n");
4328
+ const rawAnsi = new TextDecoder("latin1").decode(bytes);
4329
+ const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
4330
+ const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4331
+ const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4332
+ const candidateLines = [];
4333
+ const seen = /* @__PURE__ */ new Set();
4334
+ for (const run of [...ansiRuns, ...utf16Runs]) {
4335
+ const trimmed = run.trim();
4336
+ if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
4337
+ if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^[\W_0-9]+$/.test(trimmed)) {
4338
+ seen.add(trimmed);
4339
+ candidateLines.push(trimmed);
4340
+ }
4073
4341
  }
4074
4342
  }
4075
- return chars.join("");
4343
+ return candidateLines.join("\n\n");
4076
4344
  }
4077
4345
  heuristicTextExtraction(buffer) {
4078
4346
  return this.extractStringsFromBytes(new Uint8Array(buffer));
@@ -4102,24 +4370,24 @@ var DocPlugin = class {
4102
4370
  html += "</ul>";
4103
4371
  inList = false;
4104
4372
  }
4105
- html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line)}</h2>`;
4373
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify2__default.default.sanitize(line)}</h2>`;
4106
4374
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4107
4375
  if (!inList) {
4108
4376
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4109
4377
  inList = true;
4110
4378
  }
4111
4379
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4112
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify6__default.default.sanitize(bulletText)}</li>`;
4380
+ html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2__default.default.sanitize(bulletText)}</li>`;
4113
4381
  } else {
4114
4382
  if (inList) {
4115
4383
  html += "</ul>";
4116
4384
  inList = false;
4117
4385
  }
4118
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify6__default.default.sanitize(line)}</p>`;
4386
+ html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2__default.default.sanitize(line)}</p>`;
4119
4387
  }
4120
4388
  }
4121
4389
  if (inList) html += "</ul>";
4122
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6__default.default.sanitize(filename)})</p>`;
4390
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2__default.default.sanitize(filename)})</p>`;
4123
4391
  }
4124
4392
  };
4125
4393
  function docPlugin() {
@@ -4127,14 +4395,17 @@ function docPlugin() {
4127
4395
  }
4128
4396
  var PptPlugin = class {
4129
4397
  id = "ppt";
4130
- name = "Legacy PowerPoint Presentation (.ppt, .pps, .pot)";
4398
+ name = "PowerPoint Presentation (.ppt, .pps, .pot)";
4131
4399
  extensions = [".ppt", ".pps", ".pot"];
4132
4400
  mimeTypes = ["application/vnd.ms-powerpoint"];
4133
- weight = 75;
4401
+ weight = 85;
4134
4402
  supports(file) {
4135
4403
  const ext = file.metadata.extension?.toLowerCase();
4136
4404
  const mime = file.metadata.mimeType?.toLowerCase();
4137
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4405
+ if (ext) {
4406
+ return this.extensions.includes(ext);
4407
+ }
4408
+ return this.mimeTypes.includes(mime || "");
4138
4409
  }
4139
4410
  getToolbarActions(instance) {
4140
4411
  return [
@@ -4222,19 +4493,15 @@ var PptPlugin = class {
4222
4493
  container.style.alignItems = "center";
4223
4494
  container.style.padding = "32px 16px";
4224
4495
  container.style.backgroundColor = "#0f172a";
4496
+ container.style.boxSizing = "border-box";
4225
4497
  const slideCard = document.createElement("div");
4226
4498
  slideCard.className = "fp-ppt-slide-card";
4227
4499
  slideCard.style.width = "960px";
4228
- slideCard.style.maxWidth = "90%";
4500
+ slideCard.style.maxWidth = "92%";
4229
4501
  slideCard.style.aspectRatio = "16 / 9";
4230
4502
  slideCard.style.backgroundColor = "#ffffff";
4231
- slideCard.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
4503
+ slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4232
4504
  slideCard.style.borderRadius = "8px";
4233
- slideCard.style.padding = "48px";
4234
- slideCard.style.display = "flex";
4235
- slideCard.style.flexDirection = "column";
4236
- slideCard.style.justifyContent = "center";
4237
- slideCard.style.alignItems = "center";
4238
4505
  slideCard.style.boxSizing = "border-box";
4239
4506
  slideCard.style.position = "relative";
4240
4507
  slideCard.style.overflow = "hidden";
@@ -4245,21 +4512,25 @@ var PptPlugin = class {
4245
4512
  let scale = 1;
4246
4513
  let currentSlide = 1;
4247
4514
  let slides = [];
4515
+ const createdBlobUrls = [];
4248
4516
  try {
4249
4517
  const cfbf = new CfbfReader(ctx.buffer);
4250
4518
  const pptStream = cfbf.readStream("PowerPoint Document");
4251
4519
  if (!pptStream || pptStream.length < 512) {
4252
4520
  throw new Error("PowerPoint Document stream not found in CFBF container");
4253
4521
  }
4254
- slides = this.extractSlides(pptStream);
4522
+ const pictures = this.extractPictures(cfbf, createdBlobUrls);
4523
+ slides = this.extractSlides(pptStream, pictures);
4255
4524
  } catch (err) {
4256
4525
  console.warn("[PptPlugin] Error extracting binary slides:", err);
4257
4526
  }
4258
4527
  if (slides.length === 0) {
4259
4528
  slides = [
4260
4529
  {
4530
+ slideIndex: 1,
4261
4531
  title: ctx.metadata.name || "PowerPoint Presentation",
4262
- texts: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"]
4532
+ paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
4533
+ tableColumns: []
4263
4534
  }
4264
4535
  ];
4265
4536
  }
@@ -4268,23 +4539,73 @@ var PptPlugin = class {
4268
4539
  currentSlide = idx;
4269
4540
  const s = slides[idx - 1];
4270
4541
  if (!s) return;
4542
+ let contentHtml = "";
4543
+ if (s.pictureUrl) {
4544
+ contentHtml = `
4545
+ <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
4546
+ <img src="${s.pictureUrl}" alt="${DOMPurify2__default.default.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
4547
+ </div>
4548
+ `;
4549
+ } else if (s.tableColumns.length > 0) {
4550
+ const cols = s.tableColumns;
4551
+ contentHtml = `
4552
+ <div style="flex: 1; overflow: auto; padding: 8px 0;">
4553
+ <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
4554
+ <thead>
4555
+ <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
4556
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2__default.default.sanitize(c)}</th>`).join("")}
4557
+ </tr>
4558
+ </thead>
4559
+ <tbody>
4560
+ ${[1, 2, 3, 4, 5].map((rowIdx) => `
4561
+ <tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
4562
+ ${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
4563
+ </tr>
4564
+ `).join("")}
4565
+ </tbody>
4566
+ </table>
4567
+ </div>
4568
+ `;
4569
+ } else {
4570
+ const pTags = s.paragraphs.map((p) => {
4571
+ const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
4572
+ return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2__default.default.sanitize(l)}</p>`).join("");
4573
+ }).join("");
4574
+ contentHtml = `
4575
+ <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
4576
+ ${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
4577
+ </div>
4578
+ `;
4579
+ }
4271
4580
  slideCard.innerHTML = `
4272
- <div style="position: absolute; top: 20px; right: 24px; font-size: 12px; color: #94a3b8; font-weight: 600;">
4273
- Slide ${idx} of ${totalSlides}
4274
- </div>
4275
- <div style="text-align: center; width: 100%;">
4276
- <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4277
- ${DOMPurify6__default.default.sanitize(s.title || `Slide ${idx}`)}
4278
- </h1>
4279
- <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4280
- ${s.texts.map((t) => `<div style="font-size: 18px; color: #334155; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${DOMPurify6__default.default.sanitize(t)}</div>`).join("")}
4581
+ <div style="width: 100%; height: 100%; background: #ffffff; border: 14px solid #334155; box-sizing: border-box; display: flex; flex-direction: column; padding: 24px 32px; position: relative; font-family: Calibri, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; overflow: hidden; border-radius: 4px;">
4582
+ <!-- Header Banner matching PowerPoint design -->
4583
+ <div style="background: linear-gradient(90deg, #a3e635 0%, #84cc16 100%); padding: 12px 24px; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); flex-shrink: 0;">
4584
+ <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
4585
+ ${DOMPurify2__default.default.sanitize(s.title)}
4586
+ </h1>
4587
+ ${s.subtitle ? `
4588
+ <span style="background: #38bdf8; color: #ffffff; padding: 4px 14px; border-radius: 4px; font-weight: 700; font-size: 13px; letter-spacing: 0.5px; box-shadow: 0 1px 4px rgba(0,0,0,0.15);">
4589
+ ${DOMPurify2__default.default.sanitize(s.subtitle)}
4590
+ </span>
4591
+ ` : `
4592
+ <span style="font-size: 12px; color: #365314; font-weight: 600;">
4593
+ Slide ${idx} / ${totalSlides}
4594
+ </span>
4595
+ `}
4281
4596
  </div>
4597
+
4598
+ <!-- Slide Content -->
4599
+ ${contentHtml}
4282
4600
  </div>
4283
4601
  `;
4284
4602
  ctx.emit("page-change", { page: currentSlide, total: totalSlides });
4285
4603
  };
4286
4604
  renderSlide(1);
4287
4605
  const cleanup = () => {
4606
+ for (const u of createdBlobUrls) {
4607
+ URL.revokeObjectURL(u);
4608
+ }
4288
4609
  container.remove();
4289
4610
  ctx.container.innerHTML = "";
4290
4611
  };
@@ -4324,13 +4645,48 @@ var PptPlugin = class {
4324
4645
  if (!ctx2d) return;
4325
4646
  canvas.width = 160;
4326
4647
  canvas.height = 90;
4327
- ctx2d.fillStyle = "#ffffff";
4648
+ ctx2d.fillStyle = "#334155";
4328
4649
  ctx2d.fillRect(0, 0, 160, 90);
4329
- ctx2d.fillStyle = "#1e3a8a";
4330
- ctx2d.font = "bold 11px sans-serif";
4331
- ctx2d.textAlign = "center";
4332
- const title = s.title.slice(0, 18) || `Slide ${idx + 1}`;
4333
- ctx2d.fillText(title, 80, 50);
4650
+ ctx2d.fillStyle = "#ffffff";
4651
+ ctx2d.fillRect(3, 3, 154, 84);
4652
+ ctx2d.fillStyle = "#84cc16";
4653
+ ctx2d.fillRect(6, 6, 148, 18);
4654
+ ctx2d.fillStyle = "#1e293b";
4655
+ ctx2d.font = "bold 9px sans-serif";
4656
+ ctx2d.textAlign = "left";
4657
+ const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
4658
+ ctx2d.fillText(displayTitle, 10, 19);
4659
+ if (s.subtitle) {
4660
+ ctx2d.fillStyle = "#38bdf8";
4661
+ ctx2d.fillRect(116, 9, 34, 12);
4662
+ ctx2d.fillStyle = "#ffffff";
4663
+ ctx2d.font = "bold 7px sans-serif";
4664
+ ctx2d.textAlign = "center";
4665
+ ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
4666
+ }
4667
+ if (s.pictureUrl) {
4668
+ ctx2d.fillStyle = "#3b82f6";
4669
+ ctx2d.fillRect(52, 34, 56, 42);
4670
+ ctx2d.fillStyle = "#ffffff";
4671
+ ctx2d.font = "8px sans-serif";
4672
+ ctx2d.textAlign = "center";
4673
+ ctx2d.fillText("Chart", 80, 58);
4674
+ } else if (s.tableColumns.length > 0) {
4675
+ ctx2d.strokeStyle = "#cbd5e1";
4676
+ ctx2d.lineWidth = 1;
4677
+ ctx2d.strokeRect(14, 32, 132, 46);
4678
+ for (let l = 1; l <= 3; l++) {
4679
+ ctx2d.beginPath();
4680
+ ctx2d.moveTo(14, 32 + l * 11);
4681
+ ctx2d.lineTo(146, 32 + l * 11);
4682
+ ctx2d.stroke();
4683
+ }
4684
+ } else {
4685
+ ctx2d.fillStyle = "#94a3b8";
4686
+ for (let l = 0; l < 4; l++) {
4687
+ ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
4688
+ }
4689
+ }
4334
4690
  }
4335
4691
  }));
4336
4692
  },
@@ -4348,66 +4704,165 @@ var PptPlugin = class {
4348
4704
  }
4349
4705
  };
4350
4706
  }
4707
+ /**
4708
+ * Extract PNG and JPEG images from the Pictures stream
4709
+ */
4710
+ extractPictures(cfbf, createdUrls) {
4711
+ const urls = [];
4712
+ try {
4713
+ const picStream = cfbf.readStream("Pictures");
4714
+ if (!picStream || picStream.length < 32) return urls;
4715
+ const pBuf = new Uint8Array(picStream);
4716
+ const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
4717
+ const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
4718
+ for (let i = 0; i <= pBuf.length - 8; i++) {
4719
+ let match = true;
4720
+ for (let j = 0; j < 8; j++) {
4721
+ if (pBuf[i + j] !== pngSig[j]) {
4722
+ match = false;
4723
+ break;
4724
+ }
4725
+ }
4726
+ if (match) {
4727
+ let endIdx = -1;
4728
+ for (let k = i + 8; k <= pBuf.length - 8; k++) {
4729
+ let endMatch = true;
4730
+ for (let j = 0; j < 8; j++) {
4731
+ if (pBuf[k + j] !== iendSig[j]) {
4732
+ endMatch = false;
4733
+ break;
4734
+ }
4735
+ }
4736
+ if (endMatch) {
4737
+ endIdx = k + 8;
4738
+ break;
4739
+ }
4740
+ }
4741
+ if (endIdx !== -1) {
4742
+ const pngBytes = pBuf.subarray(i, endIdx);
4743
+ const blob = new Blob([pngBytes], { type: "image/png" });
4744
+ const url = URL.createObjectURL(blob);
4745
+ urls.push(url);
4746
+ createdUrls.push(url);
4747
+ i = endIdx;
4748
+ }
4749
+ }
4750
+ }
4751
+ for (let i = 0; i <= pBuf.length - 3; i++) {
4752
+ if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
4753
+ let endIdx = -1;
4754
+ for (let k = i + 3; k < pBuf.length - 1; k++) {
4755
+ if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
4756
+ endIdx = k + 2;
4757
+ break;
4758
+ }
4759
+ }
4760
+ if (endIdx !== -1) {
4761
+ const jpgBytes = pBuf.subarray(i, endIdx);
4762
+ const blob = new Blob([jpgBytes], { type: "image/jpeg" });
4763
+ const url = URL.createObjectURL(blob);
4764
+ urls.push(url);
4765
+ createdUrls.push(url);
4766
+ i = endIdx;
4767
+ }
4768
+ }
4769
+ }
4770
+ } catch (err) {
4771
+ console.warn("[PptPlugin] Error extracting pictures:", err);
4772
+ }
4773
+ return urls;
4774
+ }
4351
4775
  /**
4352
4776
  * Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
4353
4777
  */
4354
- extractSlides(stream) {
4778
+ extractSlides(stream, pictures) {
4355
4779
  const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
4356
4780
  const len = stream.length;
4357
4781
  let offset = 0;
4358
4782
  const slides = [];
4359
- let currentSlideTexts = [];
4783
+ let picIdx = 0;
4360
4784
  while (offset + 8 <= len) {
4361
4785
  const recVerInst = view.getUint16(offset, true);
4362
4786
  const recType = view.getUint16(offset + 2, true);
4363
4787
  const recLen = view.getUint32(offset + 4, true);
4788
+ const isContainer = (recVerInst & 15) === 15;
4364
4789
  if (recType === 1006) {
4365
- if (currentSlideTexts.length > 0) {
4366
- const title = currentSlideTexts[0] || "Slide";
4367
- const texts = currentSlideTexts.slice(1);
4368
- slides.push({ title, texts });
4369
- currentSlideTexts = [];
4790
+ const slideEnd = Math.min(len, offset + 8 + recLen);
4791
+ const rawTexts = [];
4792
+ let hasOle = false;
4793
+ let sOff = offset + 8;
4794
+ while (sOff + 8 <= slideEnd) {
4795
+ const cVerInst = view.getUint16(sOff, true);
4796
+ const cType = view.getUint16(sOff + 2, true);
4797
+ const cLen = view.getUint32(sOff + 4, true);
4798
+ const cIsContainer = (cVerInst & 15) === 15;
4799
+ if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4800
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4801
+ const txt = new TextDecoder("latin1").decode(bytes).trim();
4802
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4803
+ rawTexts.push(txt);
4804
+ }
4805
+ } else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4806
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4807
+ const txt = new TextDecoder("utf-16le").decode(bytes).trim();
4808
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4809
+ rawTexts.push(txt);
4810
+ }
4811
+ } else if (cType === 3009 || cType === 3011) {
4812
+ hasOle = true;
4813
+ }
4814
+ if (cIsContainer) sOff += 8;
4815
+ else sOff += 8 + cLen;
4370
4816
  }
4371
- offset += 8;
4372
- continue;
4373
- }
4374
- if (recType === 3998 && recLen > 0 && offset + 8 + recLen <= len) {
4375
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4376
- const text = new TextDecoder("latin1").decode(bytes).trim();
4377
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4378
- currentSlideTexts.push(text);
4817
+ let title = "";
4818
+ let subtitle = "";
4819
+ const paragraphs = [];
4820
+ const tableColumns = [];
4821
+ for (const t of rawTexts) {
4822
+ if (!title && t.length < 60 && !t.includes("\n")) {
4823
+ title = t;
4824
+ } else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
4825
+ tableColumns.push(t);
4826
+ } else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
4827
+ subtitle = t;
4828
+ } else {
4829
+ paragraphs.push(t);
4830
+ }
4379
4831
  }
4380
- }
4381
- if (recType === 3999 && recLen > 0 && offset + 8 + recLen <= len) {
4382
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4383
- const text = new TextDecoder("utf-16le").decode(bytes).trim();
4384
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4385
- currentSlideTexts.push(text);
4832
+ let pictureUrl = null;
4833
+ if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
4834
+ pictureUrl = pictures[picIdx++];
4386
4835
  }
4836
+ slides.push({
4837
+ slideIndex: slides.length + 1,
4838
+ title: title || `Slide ${slides.length + 1}`,
4839
+ subtitle,
4840
+ paragraphs,
4841
+ tableColumns,
4842
+ pictureUrl,
4843
+ hasOle
4844
+ });
4387
4845
  }
4388
- const isContainer = (recVerInst & 15) === 15;
4389
- if (isContainer) {
4390
- offset += 8;
4391
- } else {
4392
- offset += 8 + recLen;
4393
- }
4394
- }
4395
- if (currentSlideTexts.length > 0) {
4396
- const title = currentSlideTexts[0] || "Slide";
4397
- const texts = currentSlideTexts.slice(1);
4398
- slides.push({ title, texts });
4846
+ if (isContainer) offset += 8;
4847
+ else offset += 8 + recLen;
4399
4848
  }
4400
4849
  if (slides.length === 0) {
4401
4850
  const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
4402
- const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{4,}/g) || [];
4403
- const filtered = matches.map((m) => m.trim()).filter((m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times"));
4404
- if (filtered.length > 0) {
4405
- const chunkSize = 4;
4406
- for (let i = 0; i < filtered.length; i += chunkSize) {
4407
- const chunk = filtered.slice(i, i + chunkSize);
4851
+ const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
4852
+ const clean = matches.map((m) => m.trim()).filter(
4853
+ (m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
4854
+ );
4855
+ if (clean.length > 0) {
4856
+ const chunkSize = 3;
4857
+ for (let i = 0; i < clean.length; i += chunkSize) {
4858
+ const chunk = clean.slice(i, i + chunkSize);
4408
4859
  slides.push({
4860
+ slideIndex: slides.length + 1,
4409
4861
  title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
4410
- texts: chunk.slice(1)
4862
+ subtitle: chunk.length > 2 ? chunk[1] : void 0,
4863
+ paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
4864
+ tableColumns: [],
4865
+ pictureUrl: pictures[slides.length] || null
4411
4866
  });
4412
4867
  }
4413
4868
  }