@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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { memo, forwardRef, useRef, useImperativeHandle, useEffect } from 'react';
2
- import DOMPurify6 from 'dompurify';
2
+ import DOMPurify2 from 'dompurify';
3
3
  import * as docx from 'docx-preview';
4
4
  import { unzipSync, strFromU8, unzip } from 'fflate';
5
5
  import * as XLSX from 'xlsx';
@@ -10,7 +10,7 @@ import * as THREE from 'three';
10
10
  import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
11
11
  import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
12
12
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
13
- import { RTFJS } from 'rtf.js';
13
+ import * as RTFJS from 'rtf.js/dist/RTFJS.bundle.js';
14
14
  import { jsx } from 'react/jsx-runtime';
15
15
 
16
16
  // src/react.tsx
@@ -267,6 +267,39 @@ function detectOoxmlType(buffer) {
267
267
  }
268
268
  return "application/zip";
269
269
  }
270
+ function detectCfbfType(buffer) {
271
+ const bytes = new Uint8Array(buffer);
272
+ const hasUtf16le = (str) => {
273
+ const target = new Uint8Array(str.length * 2);
274
+ for (let i = 0; i < str.length; i++) {
275
+ target[i * 2] = str.charCodeAt(i);
276
+ target[i * 2 + 1] = 0;
277
+ }
278
+ const targetLen = target.length;
279
+ const max = bytes.length - targetLen;
280
+ for (let i = 0; i <= max; i++) {
281
+ let match = true;
282
+ for (let j = 0; j < targetLen; j++) {
283
+ if (bytes[i + j] !== target[j]) {
284
+ match = false;
285
+ break;
286
+ }
287
+ }
288
+ if (match) return true;
289
+ }
290
+ return false;
291
+ };
292
+ if (hasUtf16le("PowerPoint Document")) {
293
+ return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
294
+ }
295
+ if (hasUtf16le("WordDocument")) {
296
+ return { mime: "application/msword", extension: ".doc" };
297
+ }
298
+ if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
299
+ return { mime: "application/vnd.ms-excel", extension: ".xls" };
300
+ }
301
+ return null;
302
+ }
270
303
  function extractExtension(nameOrUrl) {
271
304
  try {
272
305
  const url = new URL(nameOrUrl);
@@ -331,6 +364,18 @@ async function sourceToArrayBuffer(source, signal) {
331
364
  } else {
332
365
  metadata.mimeType = metadata.mimeType ?? magicMime;
333
366
  }
367
+ } else if (magicMime === "application/x-cfbf") {
368
+ if (metadata.extension) {
369
+ metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
370
+ } else {
371
+ const cfbf = detectCfbfType(buffer);
372
+ if (cfbf) {
373
+ metadata.mimeType = cfbf.mime;
374
+ metadata.extension = cfbf.extension;
375
+ } else {
376
+ metadata.mimeType = magicMime;
377
+ }
378
+ }
334
379
  } else {
335
380
  metadata.mimeType = magicMime;
336
381
  }
@@ -341,7 +386,7 @@ async function sourceToArrayBuffer(source, signal) {
341
386
  return { buffer, metadata };
342
387
  }
343
388
  function sanitizeSVG(svg) {
344
- return DOMPurify6.sanitize(svg, {
389
+ return DOMPurify2.sanitize(svg, {
345
390
  USE_PROFILES: { svg: true, svgFilters: true }
346
391
  });
347
392
  }
@@ -401,6 +446,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
401
446
  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>`;
402
447
  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>`;
403
448
  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>`;
449
+ 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>`;
450
+ 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>`;
451
+ 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>`;
404
452
  var ICON_MAP = {
405
453
  "zoom-in": ICON_ZOOM_IN,
406
454
  "zoom-out": ICON_ZOOM_OUT,
@@ -421,7 +469,12 @@ var ICON_MAP = {
421
469
  "page-prev": ICON_PAGE_PREV,
422
470
  "page-next": ICON_PAGE_NEXT,
423
471
  "chevron-left": ICON_PAGE_PREV,
424
- "chevron-right": ICON_PAGE_NEXT
472
+ "chevron-right": ICON_PAGE_NEXT,
473
+ "fast-forward": ICON_FAST_FORWARD,
474
+ "forward-10": ICON_FAST_FORWARD,
475
+ "rewind": ICON_REWIND,
476
+ "replay-10": ICON_REWIND,
477
+ "speed": ICON_SPEED
425
478
  };
426
479
  var ToolbarController = class {
427
480
  el;
@@ -1389,6 +1442,16 @@ var MediaPlugin = class {
1389
1442
  }
1390
1443
  if (instance.play) {
1391
1444
  actions.push(
1445
+ {
1446
+ id: "replay-10",
1447
+ icon: "replay-10",
1448
+ label: "Rewind 10s",
1449
+ type: "button",
1450
+ group: "actions",
1451
+ execute: () => {
1452
+ instance.rewind?.(10);
1453
+ }
1454
+ },
1392
1455
  {
1393
1456
  id: "play",
1394
1457
  icon: "play",
@@ -1408,6 +1471,26 @@ var MediaPlugin = class {
1408
1471
  execute: () => {
1409
1472
  instance.pause?.();
1410
1473
  }
1474
+ },
1475
+ {
1476
+ id: "forward-10",
1477
+ icon: "forward-10",
1478
+ label: "Fast Forward 10s",
1479
+ type: "button",
1480
+ group: "actions",
1481
+ execute: () => {
1482
+ instance.fastForward?.(10);
1483
+ }
1484
+ },
1485
+ {
1486
+ id: "speed",
1487
+ icon: "speed",
1488
+ label: "Playback Speed",
1489
+ type: "button",
1490
+ group: "actions",
1491
+ execute: () => {
1492
+ instance.cycleSpeed?.();
1493
+ }
1411
1494
  }
1412
1495
  );
1413
1496
  }
@@ -1530,6 +1613,28 @@ var MediaPlugin = class {
1530
1613
  play: mediaElement ? () => mediaElement?.play() : void 0,
1531
1614
  pause: mediaElement ? () => mediaElement?.pause() : void 0,
1532
1615
  isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1616
+ fastForward: mediaElement ? (seconds = 10) => {
1617
+ if (mediaElement) {
1618
+ mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
1619
+ }
1620
+ } : void 0,
1621
+ rewind: mediaElement ? (seconds = 10) => {
1622
+ if (mediaElement) {
1623
+ mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
1624
+ }
1625
+ } : void 0,
1626
+ cycleSpeed: mediaElement ? () => {
1627
+ if (mediaElement) {
1628
+ const speeds = [1, 1.25, 1.5, 2, 0.5];
1629
+ const curIndex = speeds.indexOf(mediaElement.playbackRate);
1630
+ const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
1631
+ mediaElement.playbackRate = speeds[nextIndex];
1632
+ }
1633
+ } : void 0,
1634
+ setPlaybackRate: mediaElement ? (rate) => {
1635
+ if (mediaElement) mediaElement.playbackRate = rate;
1636
+ } : void 0,
1637
+ getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
1533
1638
  download: () => {
1534
1639
  const a = document.createElement("a");
1535
1640
  a.href = url;
@@ -1559,7 +1664,10 @@ var DocxPlugin = class {
1559
1664
  supports(file) {
1560
1665
  const ext = file.metadata.extension?.toLowerCase();
1561
1666
  const mime = file.metadata.mimeType?.toLowerCase();
1562
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
1667
+ if (ext) {
1668
+ return this.extensions.includes(ext);
1669
+ }
1670
+ return this.mimeTypes.includes(mime || "");
1563
1671
  }
1564
1672
  getToolbarActions(instance) {
1565
1673
  return [
@@ -1611,16 +1719,35 @@ var DocxPlugin = class {
1611
1719
  wrapper.style.transformOrigin = "top center";
1612
1720
  wrapper.style.transition = "transform 0.2s ease";
1613
1721
  wrapper.style.padding = "24px 16px";
1614
- wrapper.style.maxWidth = "900px";
1722
+ wrapper.style.maxWidth = "950px";
1615
1723
  wrapper.style.margin = "0 auto";
1616
1724
  wrapper.style.boxSizing = "border-box";
1617
1725
  ctx.container.style.overflow = "auto";
1618
1726
  ctx.container.style.backgroundColor = "#f1f5f9";
1619
1727
  ctx.container.appendChild(wrapper);
1728
+ const styleOverride = document.createElement("style");
1729
+ styleOverride.textContent = `
1730
+ .fp-docx-wrapper .docx-wrapper {
1731
+ background: transparent !important;
1732
+ padding: 0 !important;
1733
+ display: flex !important;
1734
+ flex-direction: column !important;
1735
+ align-items: center !important;
1736
+ box-sizing: border-box !important;
1737
+ }
1738
+ .fp-docx-wrapper section.docx {
1739
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
1740
+ border-radius: 4px !important;
1741
+ margin-bottom: 24px !important;
1742
+ background-color: #ffffff !important;
1743
+ }
1744
+ `;
1745
+ ctx.container.appendChild(styleOverride);
1620
1746
  let scale = 1;
1621
1747
  let renderedSuccessfully = false;
1748
+ const createdBlobUrls = [];
1622
1749
  try {
1623
- await docx.renderAsync(ctx.buffer, wrapper, ctx.container, {
1750
+ await docx.renderAsync(ctx.buffer, wrapper, wrapper, {
1624
1751
  inWrapper: true,
1625
1752
  ignoreWidth: false,
1626
1753
  ignoreHeight: false,
@@ -1629,19 +1756,25 @@ var DocxPlugin = class {
1629
1756
  breakPages: true,
1630
1757
  experimental: true
1631
1758
  });
1759
+ if (!ctx.container.contains(wrapper)) {
1760
+ ctx.container.appendChild(wrapper);
1761
+ }
1632
1762
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
1633
1763
  renderedSuccessfully = true;
1634
1764
  }
1635
1765
  } catch (err) {
1636
- console.warn("[DocxPlugin] docx-preview failed, triggering native XML fallback:", err);
1766
+ console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
1637
1767
  }
1638
1768
  if (!renderedSuccessfully) {
1639
1769
  try {
1640
1770
  wrapper.innerHTML = "";
1641
- this.renderXmlFallback(ctx, wrapper);
1771
+ this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
1772
+ if (!ctx.container.contains(wrapper)) {
1773
+ ctx.container.appendChild(wrapper);
1774
+ }
1642
1775
  renderedSuccessfully = true;
1643
1776
  } catch (fallbackErr) {
1644
- console.error("[DocxPlugin] XML fallback failed:", fallbackErr);
1777
+ console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
1645
1778
  wrapper.innerHTML = `
1646
1779
  <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
1647
1780
  <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
@@ -1649,10 +1782,17 @@ var DocxPlugin = class {
1649
1782
  <p style="color: #64748b; margin: 0;">Could not parse document content</p>
1650
1783
  </div>
1651
1784
  `;
1785
+ if (!ctx.container.contains(wrapper)) {
1786
+ ctx.container.appendChild(wrapper);
1787
+ }
1652
1788
  }
1653
1789
  }
1654
1790
  const cleanup = () => {
1791
+ for (const url of createdBlobUrls) {
1792
+ URL.revokeObjectURL(url);
1793
+ }
1655
1794
  wrapper.remove();
1795
+ styleOverride.remove();
1656
1796
  ctx.container.innerHTML = "";
1657
1797
  };
1658
1798
  ctx.signal.addEventListener("abort", cleanup);
@@ -1689,13 +1829,50 @@ var DocxPlugin = class {
1689
1829
  }
1690
1830
  };
1691
1831
  }
1692
- renderXmlFallback(ctx, wrapper) {
1832
+ renderXmlFallback(ctx, wrapper, createdBlobUrls) {
1833
+ const magic = new Uint8Array(ctx.buffer.slice(0, 8));
1834
+ if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
1835
+ this.renderBinaryDocFallback(ctx, wrapper);
1836
+ return;
1837
+ }
1693
1838
  const unzipped = unzipSync(new Uint8Array(ctx.buffer));
1694
- const docXmlEntry = unzipped["word/document.xml"];
1839
+ const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
1840
+ const docXmlEntry = docKey ? unzipped[docKey] : void 0;
1695
1841
  if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
1696
1842
  const xmlStr = strFromU8(docXmlEntry);
1697
1843
  const parser = new DOMParser();
1698
1844
  const doc = parser.parseFromString(xmlStr, "application/xml");
1845
+ const imageMap = {};
1846
+ const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
1847
+ if (relsKey && unzipped[relsKey]) {
1848
+ try {
1849
+ const relsXml = strFromU8(unzipped[relsKey]);
1850
+ const relsDoc = parser.parseFromString(relsXml, "application/xml");
1851
+ const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
1852
+ for (const rel of relEls) {
1853
+ const rId = rel.getAttribute("Id");
1854
+ const target = rel.getAttribute("Target");
1855
+ if (rId && target) {
1856
+ const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
1857
+ const fullTargetKey = Object.keys(unzipped).find((k) => {
1858
+ const norm = k.replace(/^[./\\]+/, "").toLowerCase();
1859
+ return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
1860
+ });
1861
+ if (fullTargetKey && unzipped[fullTargetKey]) {
1862
+ const bytes = unzipped[fullTargetKey];
1863
+ const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
1864
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
1865
+ const blob = new Blob([bytes], { type: mime });
1866
+ const blobUrl = URL.createObjectURL(blob);
1867
+ imageMap[rId] = blobUrl;
1868
+ createdBlobUrls.push(blobUrl);
1869
+ }
1870
+ }
1871
+ }
1872
+ } catch (relsErr) {
1873
+ console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
1874
+ }
1875
+ }
1699
1876
  const card = document.createElement("div");
1700
1877
  card.className = "fp-docx-page-card";
1701
1878
  card.style.backgroundColor = "#ffffff";
@@ -1713,7 +1890,7 @@ var DocxPlugin = class {
1713
1890
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
1714
1891
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
1715
1892
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
1716
- const textContent = this.extractParagraphHtml(child);
1893
+ const textContent = this.extractParagraphHtml(child, imageMap);
1717
1894
  if (!textContent.trim()) {
1718
1895
  html += '<div style="height: 10px;"></div>';
1719
1896
  continue;
@@ -1739,7 +1916,7 @@ var DocxPlugin = class {
1739
1916
  html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
1740
1917
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
1741
1918
  cells.forEach((tc) => {
1742
- const cellText = this.extractParagraphHtml(tc);
1919
+ const cellText = this.extractParagraphHtml(tc, imageMap);
1743
1920
  html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
1744
1921
  });
1745
1922
  html += "</tr>";
@@ -1747,34 +1924,63 @@ var DocxPlugin = class {
1747
1924
  html += "</table>";
1748
1925
  }
1749
1926
  }
1750
- card.innerHTML = DOMPurify6.sanitize(html, {
1751
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "img", "div"],
1927
+ card.innerHTML = DOMPurify2.sanitize(html, {
1928
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
1752
1929
  ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
1753
1930
  });
1754
1931
  wrapper.appendChild(card);
1755
1932
  }
1756
- extractParagraphHtml(pElement) {
1933
+ extractParagraphHtml(pElement, imageMap = {}) {
1757
1934
  let result = "";
1935
+ const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
1936
+ for (const drawing of drawings) {
1937
+ const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
1938
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1939
+ if (rId && imageMap[rId]) {
1940
+ result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
1941
+ }
1942
+ }
1758
1943
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
1759
- if (runs.length === 0) {
1760
- return DOMPurify6.sanitize(pElement.textContent || "");
1944
+ if (runs.length === 0 && !result) {
1945
+ return DOMPurify2.sanitize(pElement.textContent || "");
1761
1946
  }
1762
1947
  for (const r of runs) {
1948
+ const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
1949
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1950
+ if (rId && imageMap[rId]) {
1951
+ result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
1952
+ }
1953
+ const brs = r.getElementsByTagNameNS("*", "br");
1954
+ for (let i = 0; i < brs.length; i++) {
1955
+ result += "<br/>";
1956
+ }
1957
+ const tabs = r.getElementsByTagNameNS("*", "tab");
1958
+ if (tabs.length > 0) {
1959
+ result += "&emsp;";
1960
+ }
1763
1961
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
1764
1962
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
1765
1963
  const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
1766
1964
  const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
1965
+ const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
1767
1966
  const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
1768
1967
  const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
1968
+ const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
1969
+ const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
1769
1970
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
1770
1971
  let text = texts.map((t) => t.textContent || "").join("");
1771
1972
  if (!text) continue;
1772
- text = DOMPurify6.sanitize(text);
1973
+ text = DOMPurify2.sanitize(text);
1773
1974
  let styles = "";
1774
1975
  if (isBold) styles += "font-weight: bold; ";
1775
1976
  if (isItalic) styles += "font-style: italic; ";
1776
1977
  if (isUnderline) styles += "text-decoration: underline; ";
1978
+ if (isStrike) styles += "text-decoration: line-through; ";
1777
1979
  if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
1980
+ if (szVal) {
1981
+ const pt = parseInt(szVal, 10) / 2;
1982
+ if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
1983
+ }
1778
1984
  if (styles) {
1779
1985
  result += `<span style="${styles}">${text}</span>`;
1780
1986
  } else {
@@ -1783,6 +1989,52 @@ var DocxPlugin = class {
1783
1989
  }
1784
1990
  return result;
1785
1991
  }
1992
+ renderBinaryDocFallback(ctx, wrapper) {
1993
+ const card = document.createElement("div");
1994
+ card.className = "fp-docx-page-card";
1995
+ card.style.backgroundColor = "#ffffff";
1996
+ card.style.borderRadius = "6px";
1997
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
1998
+ card.style.padding = "48px 56px";
1999
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2000
+ card.style.color = "#1e293b";
2001
+ card.style.lineHeight = "1.6";
2002
+ try {
2003
+ const cfbf = new CfbfReader(ctx.buffer);
2004
+ const wordDocStream = cfbf.readStream("WordDocument");
2005
+ if (wordDocStream && wordDocStream.length >= 512) {
2006
+ const text2 = this.extractReadableStrings(wordDocStream);
2007
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text2)}</div>`;
2008
+ wrapper.appendChild(card);
2009
+ return;
2010
+ }
2011
+ } catch {
2012
+ }
2013
+ const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2014
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text)}</div>`;
2015
+ wrapper.appendChild(card);
2016
+ }
2017
+ extractReadableStrings(bytes) {
2018
+ let result = "";
2019
+ let current = "";
2020
+ for (let i = 0; i < bytes.length; i++) {
2021
+ const b = bytes[i];
2022
+ if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
2023
+ current += String.fromCharCode(b);
2024
+ } else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
2025
+ continue;
2026
+ } else {
2027
+ if (current.trim().length > 3) {
2028
+ result += current.trim() + "\n\n";
2029
+ }
2030
+ current = "";
2031
+ }
2032
+ }
2033
+ if (current.trim().length > 3) {
2034
+ result += current.trim();
2035
+ }
2036
+ return result;
2037
+ }
1786
2038
  };
1787
2039
  function docxPlugin() {
1788
2040
  return new DocxPlugin();
@@ -1804,7 +2056,10 @@ var ExcelPlugin = class {
1804
2056
  supports(file) {
1805
2057
  const ext = file.metadata.extension?.toLowerCase();
1806
2058
  const mime = file.metadata.mimeType?.toLowerCase();
1807
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
2059
+ if (ext) {
2060
+ return this.extensions.includes(ext);
2061
+ }
2062
+ return this.mimeTypes.includes(mime || "");
1808
2063
  }
1809
2064
  getToolbarActions(instance) {
1810
2065
  return [
@@ -2594,7 +2849,7 @@ var MarkdownPlugin = class {
2594
2849
  gfm: true,
2595
2850
  breaks: true
2596
2851
  });
2597
- const cleanHtml = DOMPurify6.sanitize(rawHtml, {
2852
+ const cleanHtml = DOMPurify2.sanitize(rawHtml, {
2598
2853
  USE_PROFILES: { html: true }
2599
2854
  });
2600
2855
  const wrapper = document.createElement("div");
@@ -2746,7 +3001,10 @@ var PptxPlugin = class {
2746
3001
  supports(file) {
2747
3002
  const ext = file.metadata.extension?.toLowerCase();
2748
3003
  const mime = file.metadata.mimeType?.toLowerCase();
2749
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3004
+ if (ext) {
3005
+ return this.extensions.includes(ext);
3006
+ }
3007
+ return this.mimeTypes.includes(mime || "");
2750
3008
  }
2751
3009
  getToolbarActions(instance) {
2752
3010
  return [
@@ -3191,6 +3449,9 @@ var RtfPlugin = class {
3191
3449
  ctx.container.appendChild(wrapper);
3192
3450
  let scale = 1;
3193
3451
  try {
3452
+ if (typeof RTFJS.loggingEnabled === "function") {
3453
+ RTFJS.loggingEnabled(false);
3454
+ }
3194
3455
  const doc = new RTFJS.Document(ctx.buffer, {});
3195
3456
  const htmlElements = await doc.render();
3196
3457
  for (const el of htmlElements) {
@@ -3309,7 +3570,7 @@ var HtmlPreviewPlugin = class {
3309
3570
  }
3310
3571
  async render(ctx) {
3311
3572
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3312
- const sanitized = DOMPurify6.sanitize(rawHtml, {
3573
+ const sanitized = DOMPurify2.sanitize(rawHtml, {
3313
3574
  WHOLE_DOCUMENT: true,
3314
3575
  ADD_TAGS: ["style", "link"],
3315
3576
  ADD_ATTR: ["target", "rel"]
@@ -3389,7 +3650,10 @@ var OpenDocumentPlugin = class {
3389
3650
  supports(file) {
3390
3651
  const ext = file.metadata.extension?.toLowerCase();
3391
3652
  const mime = file.metadata.mimeType?.toLowerCase();
3392
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3653
+ if (ext) {
3654
+ return this.extensions.includes(ext);
3655
+ }
3656
+ return this.mimeTypes.includes(mime || "");
3393
3657
  }
3394
3658
  getToolbarActions(instance) {
3395
3659
  const isPresentation = instance.isPresentation;
@@ -3552,7 +3816,7 @@ var OpenDocumentPlugin = class {
3552
3816
  wrapper.style.padding = "48px";
3553
3817
  wrapper.style.minHeight = "100%";
3554
3818
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
3555
- wrapper.innerHTML = DOMPurify6.sanitize(bodyHtml, {
3819
+ wrapper.innerHTML = DOMPurify2.sanitize(bodyHtml, {
3556
3820
  ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
3557
3821
  ADD_ATTR: ["style", "colspan", "rowspan"]
3558
3822
  });
@@ -3804,7 +4068,10 @@ var DocPlugin = class {
3804
4068
  supports(file) {
3805
4069
  const ext = file.metadata.extension?.toLowerCase();
3806
4070
  const mime = file.metadata.mimeType?.toLowerCase();
3807
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4071
+ if (ext) {
4072
+ return this.extensions.includes(ext);
4073
+ }
4074
+ return this.mimeTypes.includes(mime || "");
3808
4075
  }
3809
4076
  getToolbarActions(instance) {
3810
4077
  return [
@@ -4027,22 +4294,22 @@ var DocPlugin = class {
4027
4294
  * Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
4028
4295
  */
4029
4296
  extractStringsFromBytes(bytes) {
4030
- const chars = [];
4031
- const len = bytes.length;
4032
- for (let i = 0; i < len; i++) {
4033
- const b = bytes[i];
4034
- if (b === 13 || b === 10 || b === 9 || b >= 32 && b <= 126 || b >= 160 && b <= 255) {
4035
- chars.push(String.fromCharCode(b));
4036
- } else if (b === 0 && i + 1 < len && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
4037
- chars.push(String.fromCharCode(bytes[i + 1]));
4038
- i++;
4039
- } else if (b === 7) {
4040
- chars.push(" ");
4041
- } else if (b === 12) {
4042
- chars.push("\n\n---PAGE---\n\n");
4297
+ const rawAnsi = new TextDecoder("latin1").decode(bytes);
4298
+ const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
4299
+ const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4300
+ const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4301
+ const candidateLines = [];
4302
+ const seen = /* @__PURE__ */ new Set();
4303
+ for (const run of [...ansiRuns, ...utf16Runs]) {
4304
+ const trimmed = run.trim();
4305
+ if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
4306
+ 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)) {
4307
+ seen.add(trimmed);
4308
+ candidateLines.push(trimmed);
4309
+ }
4043
4310
  }
4044
4311
  }
4045
- return chars.join("");
4312
+ return candidateLines.join("\n\n");
4046
4313
  }
4047
4314
  heuristicTextExtraction(buffer) {
4048
4315
  return this.extractStringsFromBytes(new Uint8Array(buffer));
@@ -4072,24 +4339,24 @@ var DocPlugin = class {
4072
4339
  html += "</ul>";
4073
4340
  inList = false;
4074
4341
  }
4075
- html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line)}</h2>`;
4342
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify2.sanitize(line)}</h2>`;
4076
4343
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4077
4344
  if (!inList) {
4078
4345
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4079
4346
  inList = true;
4080
4347
  }
4081
4348
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4082
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify6.sanitize(bulletText)}</li>`;
4349
+ html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2.sanitize(bulletText)}</li>`;
4083
4350
  } else {
4084
4351
  if (inList) {
4085
4352
  html += "</ul>";
4086
4353
  inList = false;
4087
4354
  }
4088
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
4355
+ html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2.sanitize(line)}</p>`;
4089
4356
  }
4090
4357
  }
4091
4358
  if (inList) html += "</ul>";
4092
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6.sanitize(filename)})</p>`;
4359
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2.sanitize(filename)})</p>`;
4093
4360
  }
4094
4361
  };
4095
4362
  function docPlugin() {
@@ -4097,14 +4364,17 @@ function docPlugin() {
4097
4364
  }
4098
4365
  var PptPlugin = class {
4099
4366
  id = "ppt";
4100
- name = "Legacy PowerPoint Presentation (.ppt, .pps, .pot)";
4367
+ name = "PowerPoint Presentation (.ppt, .pps, .pot)";
4101
4368
  extensions = [".ppt", ".pps", ".pot"];
4102
4369
  mimeTypes = ["application/vnd.ms-powerpoint"];
4103
- weight = 75;
4370
+ weight = 85;
4104
4371
  supports(file) {
4105
4372
  const ext = file.metadata.extension?.toLowerCase();
4106
4373
  const mime = file.metadata.mimeType?.toLowerCase();
4107
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4374
+ if (ext) {
4375
+ return this.extensions.includes(ext);
4376
+ }
4377
+ return this.mimeTypes.includes(mime || "");
4108
4378
  }
4109
4379
  getToolbarActions(instance) {
4110
4380
  return [
@@ -4192,19 +4462,15 @@ var PptPlugin = class {
4192
4462
  container.style.alignItems = "center";
4193
4463
  container.style.padding = "32px 16px";
4194
4464
  container.style.backgroundColor = "#0f172a";
4465
+ container.style.boxSizing = "border-box";
4195
4466
  const slideCard = document.createElement("div");
4196
4467
  slideCard.className = "fp-ppt-slide-card";
4197
4468
  slideCard.style.width = "960px";
4198
- slideCard.style.maxWidth = "90%";
4469
+ slideCard.style.maxWidth = "92%";
4199
4470
  slideCard.style.aspectRatio = "16 / 9";
4200
4471
  slideCard.style.backgroundColor = "#ffffff";
4201
- slideCard.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
4472
+ slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4202
4473
  slideCard.style.borderRadius = "8px";
4203
- slideCard.style.padding = "48px";
4204
- slideCard.style.display = "flex";
4205
- slideCard.style.flexDirection = "column";
4206
- slideCard.style.justifyContent = "center";
4207
- slideCard.style.alignItems = "center";
4208
4474
  slideCard.style.boxSizing = "border-box";
4209
4475
  slideCard.style.position = "relative";
4210
4476
  slideCard.style.overflow = "hidden";
@@ -4215,21 +4481,25 @@ var PptPlugin = class {
4215
4481
  let scale = 1;
4216
4482
  let currentSlide = 1;
4217
4483
  let slides = [];
4484
+ const createdBlobUrls = [];
4218
4485
  try {
4219
4486
  const cfbf = new CfbfReader(ctx.buffer);
4220
4487
  const pptStream = cfbf.readStream("PowerPoint Document");
4221
4488
  if (!pptStream || pptStream.length < 512) {
4222
4489
  throw new Error("PowerPoint Document stream not found in CFBF container");
4223
4490
  }
4224
- slides = this.extractSlides(pptStream);
4491
+ const pictures = this.extractPictures(cfbf, createdBlobUrls);
4492
+ slides = this.extractSlides(pptStream, pictures);
4225
4493
  } catch (err) {
4226
4494
  console.warn("[PptPlugin] Error extracting binary slides:", err);
4227
4495
  }
4228
4496
  if (slides.length === 0) {
4229
4497
  slides = [
4230
4498
  {
4499
+ slideIndex: 1,
4231
4500
  title: ctx.metadata.name || "PowerPoint Presentation",
4232
- texts: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"]
4501
+ paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
4502
+ tableColumns: []
4233
4503
  }
4234
4504
  ];
4235
4505
  }
@@ -4238,23 +4508,73 @@ var PptPlugin = class {
4238
4508
  currentSlide = idx;
4239
4509
  const s = slides[idx - 1];
4240
4510
  if (!s) return;
4511
+ let contentHtml = "";
4512
+ if (s.pictureUrl) {
4513
+ contentHtml = `
4514
+ <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
4515
+ <img src="${s.pictureUrl}" alt="${DOMPurify2.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;" />
4516
+ </div>
4517
+ `;
4518
+ } else if (s.tableColumns.length > 0) {
4519
+ const cols = s.tableColumns;
4520
+ contentHtml = `
4521
+ <div style="flex: 1; overflow: auto; padding: 8px 0;">
4522
+ <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
4523
+ <thead>
4524
+ <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
4525
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2.sanitize(c)}</th>`).join("")}
4526
+ </tr>
4527
+ </thead>
4528
+ <tbody>
4529
+ ${[1, 2, 3, 4, 5].map((rowIdx) => `
4530
+ <tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
4531
+ ${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
4532
+ </tr>
4533
+ `).join("")}
4534
+ </tbody>
4535
+ </table>
4536
+ </div>
4537
+ `;
4538
+ } else {
4539
+ const pTags = s.paragraphs.map((p) => {
4540
+ const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
4541
+ return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2.sanitize(l)}</p>`).join("");
4542
+ }).join("");
4543
+ contentHtml = `
4544
+ <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
4545
+ ${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
4546
+ </div>
4547
+ `;
4548
+ }
4241
4549
  slideCard.innerHTML = `
4242
- <div style="position: absolute; top: 20px; right: 24px; font-size: 12px; color: #94a3b8; font-weight: 600;">
4243
- Slide ${idx} of ${totalSlides}
4244
- </div>
4245
- <div style="text-align: center; width: 100%;">
4246
- <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4247
- ${DOMPurify6.sanitize(s.title || `Slide ${idx}`)}
4248
- </h1>
4249
- <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4250
- ${s.texts.map((t) => `<div style="font-size: 18px; color: #334155; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${DOMPurify6.sanitize(t)}</div>`).join("")}
4550
+ <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;">
4551
+ <!-- Header Banner matching PowerPoint design -->
4552
+ <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;">
4553
+ <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
4554
+ ${DOMPurify2.sanitize(s.title)}
4555
+ </h1>
4556
+ ${s.subtitle ? `
4557
+ <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);">
4558
+ ${DOMPurify2.sanitize(s.subtitle)}
4559
+ </span>
4560
+ ` : `
4561
+ <span style="font-size: 12px; color: #365314; font-weight: 600;">
4562
+ Slide ${idx} / ${totalSlides}
4563
+ </span>
4564
+ `}
4251
4565
  </div>
4566
+
4567
+ <!-- Slide Content -->
4568
+ ${contentHtml}
4252
4569
  </div>
4253
4570
  `;
4254
4571
  ctx.emit("page-change", { page: currentSlide, total: totalSlides });
4255
4572
  };
4256
4573
  renderSlide(1);
4257
4574
  const cleanup = () => {
4575
+ for (const u of createdBlobUrls) {
4576
+ URL.revokeObjectURL(u);
4577
+ }
4258
4578
  container.remove();
4259
4579
  ctx.container.innerHTML = "";
4260
4580
  };
@@ -4294,13 +4614,48 @@ var PptPlugin = class {
4294
4614
  if (!ctx2d) return;
4295
4615
  canvas.width = 160;
4296
4616
  canvas.height = 90;
4297
- ctx2d.fillStyle = "#ffffff";
4617
+ ctx2d.fillStyle = "#334155";
4298
4618
  ctx2d.fillRect(0, 0, 160, 90);
4299
- ctx2d.fillStyle = "#1e3a8a";
4300
- ctx2d.font = "bold 11px sans-serif";
4301
- ctx2d.textAlign = "center";
4302
- const title = s.title.slice(0, 18) || `Slide ${idx + 1}`;
4303
- ctx2d.fillText(title, 80, 50);
4619
+ ctx2d.fillStyle = "#ffffff";
4620
+ ctx2d.fillRect(3, 3, 154, 84);
4621
+ ctx2d.fillStyle = "#84cc16";
4622
+ ctx2d.fillRect(6, 6, 148, 18);
4623
+ ctx2d.fillStyle = "#1e293b";
4624
+ ctx2d.font = "bold 9px sans-serif";
4625
+ ctx2d.textAlign = "left";
4626
+ const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
4627
+ ctx2d.fillText(displayTitle, 10, 19);
4628
+ if (s.subtitle) {
4629
+ ctx2d.fillStyle = "#38bdf8";
4630
+ ctx2d.fillRect(116, 9, 34, 12);
4631
+ ctx2d.fillStyle = "#ffffff";
4632
+ ctx2d.font = "bold 7px sans-serif";
4633
+ ctx2d.textAlign = "center";
4634
+ ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
4635
+ }
4636
+ if (s.pictureUrl) {
4637
+ ctx2d.fillStyle = "#3b82f6";
4638
+ ctx2d.fillRect(52, 34, 56, 42);
4639
+ ctx2d.fillStyle = "#ffffff";
4640
+ ctx2d.font = "8px sans-serif";
4641
+ ctx2d.textAlign = "center";
4642
+ ctx2d.fillText("Chart", 80, 58);
4643
+ } else if (s.tableColumns.length > 0) {
4644
+ ctx2d.strokeStyle = "#cbd5e1";
4645
+ ctx2d.lineWidth = 1;
4646
+ ctx2d.strokeRect(14, 32, 132, 46);
4647
+ for (let l = 1; l <= 3; l++) {
4648
+ ctx2d.beginPath();
4649
+ ctx2d.moveTo(14, 32 + l * 11);
4650
+ ctx2d.lineTo(146, 32 + l * 11);
4651
+ ctx2d.stroke();
4652
+ }
4653
+ } else {
4654
+ ctx2d.fillStyle = "#94a3b8";
4655
+ for (let l = 0; l < 4; l++) {
4656
+ ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
4657
+ }
4658
+ }
4304
4659
  }
4305
4660
  }));
4306
4661
  },
@@ -4318,66 +4673,165 @@ var PptPlugin = class {
4318
4673
  }
4319
4674
  };
4320
4675
  }
4676
+ /**
4677
+ * Extract PNG and JPEG images from the Pictures stream
4678
+ */
4679
+ extractPictures(cfbf, createdUrls) {
4680
+ const urls = [];
4681
+ try {
4682
+ const picStream = cfbf.readStream("Pictures");
4683
+ if (!picStream || picStream.length < 32) return urls;
4684
+ const pBuf = new Uint8Array(picStream);
4685
+ const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
4686
+ const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
4687
+ for (let i = 0; i <= pBuf.length - 8; i++) {
4688
+ let match = true;
4689
+ for (let j = 0; j < 8; j++) {
4690
+ if (pBuf[i + j] !== pngSig[j]) {
4691
+ match = false;
4692
+ break;
4693
+ }
4694
+ }
4695
+ if (match) {
4696
+ let endIdx = -1;
4697
+ for (let k = i + 8; k <= pBuf.length - 8; k++) {
4698
+ let endMatch = true;
4699
+ for (let j = 0; j < 8; j++) {
4700
+ if (pBuf[k + j] !== iendSig[j]) {
4701
+ endMatch = false;
4702
+ break;
4703
+ }
4704
+ }
4705
+ if (endMatch) {
4706
+ endIdx = k + 8;
4707
+ break;
4708
+ }
4709
+ }
4710
+ if (endIdx !== -1) {
4711
+ const pngBytes = pBuf.subarray(i, endIdx);
4712
+ const blob = new Blob([pngBytes], { type: "image/png" });
4713
+ const url = URL.createObjectURL(blob);
4714
+ urls.push(url);
4715
+ createdUrls.push(url);
4716
+ i = endIdx;
4717
+ }
4718
+ }
4719
+ }
4720
+ for (let i = 0; i <= pBuf.length - 3; i++) {
4721
+ if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
4722
+ let endIdx = -1;
4723
+ for (let k = i + 3; k < pBuf.length - 1; k++) {
4724
+ if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
4725
+ endIdx = k + 2;
4726
+ break;
4727
+ }
4728
+ }
4729
+ if (endIdx !== -1) {
4730
+ const jpgBytes = pBuf.subarray(i, endIdx);
4731
+ const blob = new Blob([jpgBytes], { type: "image/jpeg" });
4732
+ const url = URL.createObjectURL(blob);
4733
+ urls.push(url);
4734
+ createdUrls.push(url);
4735
+ i = endIdx;
4736
+ }
4737
+ }
4738
+ }
4739
+ } catch (err) {
4740
+ console.warn("[PptPlugin] Error extracting pictures:", err);
4741
+ }
4742
+ return urls;
4743
+ }
4321
4744
  /**
4322
4745
  * Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
4323
4746
  */
4324
- extractSlides(stream) {
4747
+ extractSlides(stream, pictures) {
4325
4748
  const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
4326
4749
  const len = stream.length;
4327
4750
  let offset = 0;
4328
4751
  const slides = [];
4329
- let currentSlideTexts = [];
4752
+ let picIdx = 0;
4330
4753
  while (offset + 8 <= len) {
4331
4754
  const recVerInst = view.getUint16(offset, true);
4332
4755
  const recType = view.getUint16(offset + 2, true);
4333
4756
  const recLen = view.getUint32(offset + 4, true);
4757
+ const isContainer = (recVerInst & 15) === 15;
4334
4758
  if (recType === 1006) {
4335
- if (currentSlideTexts.length > 0) {
4336
- const title = currentSlideTexts[0] || "Slide";
4337
- const texts = currentSlideTexts.slice(1);
4338
- slides.push({ title, texts });
4339
- currentSlideTexts = [];
4759
+ const slideEnd = Math.min(len, offset + 8 + recLen);
4760
+ const rawTexts = [];
4761
+ let hasOle = false;
4762
+ let sOff = offset + 8;
4763
+ while (sOff + 8 <= slideEnd) {
4764
+ const cVerInst = view.getUint16(sOff, true);
4765
+ const cType = view.getUint16(sOff + 2, true);
4766
+ const cLen = view.getUint32(sOff + 4, true);
4767
+ const cIsContainer = (cVerInst & 15) === 15;
4768
+ if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4769
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4770
+ const txt = new TextDecoder("latin1").decode(bytes).trim();
4771
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4772
+ rawTexts.push(txt);
4773
+ }
4774
+ } else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4775
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4776
+ const txt = new TextDecoder("utf-16le").decode(bytes).trim();
4777
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4778
+ rawTexts.push(txt);
4779
+ }
4780
+ } else if (cType === 3009 || cType === 3011) {
4781
+ hasOle = true;
4782
+ }
4783
+ if (cIsContainer) sOff += 8;
4784
+ else sOff += 8 + cLen;
4340
4785
  }
4341
- offset += 8;
4342
- continue;
4343
- }
4344
- if (recType === 3998 && recLen > 0 && offset + 8 + recLen <= len) {
4345
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4346
- const text = new TextDecoder("latin1").decode(bytes).trim();
4347
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4348
- currentSlideTexts.push(text);
4786
+ let title = "";
4787
+ let subtitle = "";
4788
+ const paragraphs = [];
4789
+ const tableColumns = [];
4790
+ for (const t of rawTexts) {
4791
+ if (!title && t.length < 60 && !t.includes("\n")) {
4792
+ title = t;
4793
+ } else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
4794
+ tableColumns.push(t);
4795
+ } else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
4796
+ subtitle = t;
4797
+ } else {
4798
+ paragraphs.push(t);
4799
+ }
4349
4800
  }
4350
- }
4351
- if (recType === 3999 && recLen > 0 && offset + 8 + recLen <= len) {
4352
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4353
- const text = new TextDecoder("utf-16le").decode(bytes).trim();
4354
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4355
- currentSlideTexts.push(text);
4801
+ let pictureUrl = null;
4802
+ if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
4803
+ pictureUrl = pictures[picIdx++];
4356
4804
  }
4805
+ slides.push({
4806
+ slideIndex: slides.length + 1,
4807
+ title: title || `Slide ${slides.length + 1}`,
4808
+ subtitle,
4809
+ paragraphs,
4810
+ tableColumns,
4811
+ pictureUrl,
4812
+ hasOle
4813
+ });
4357
4814
  }
4358
- const isContainer = (recVerInst & 15) === 15;
4359
- if (isContainer) {
4360
- offset += 8;
4361
- } else {
4362
- offset += 8 + recLen;
4363
- }
4364
- }
4365
- if (currentSlideTexts.length > 0) {
4366
- const title = currentSlideTexts[0] || "Slide";
4367
- const texts = currentSlideTexts.slice(1);
4368
- slides.push({ title, texts });
4815
+ if (isContainer) offset += 8;
4816
+ else offset += 8 + recLen;
4369
4817
  }
4370
4818
  if (slides.length === 0) {
4371
4819
  const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
4372
- const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{4,}/g) || [];
4373
- const filtered = matches.map((m) => m.trim()).filter((m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times"));
4374
- if (filtered.length > 0) {
4375
- const chunkSize = 4;
4376
- for (let i = 0; i < filtered.length; i += chunkSize) {
4377
- const chunk = filtered.slice(i, i + chunkSize);
4820
+ const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
4821
+ const clean = matches.map((m) => m.trim()).filter(
4822
+ (m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
4823
+ );
4824
+ if (clean.length > 0) {
4825
+ const chunkSize = 3;
4826
+ for (let i = 0; i < clean.length; i += chunkSize) {
4827
+ const chunk = clean.slice(i, i + chunkSize);
4378
4828
  slides.push({
4829
+ slideIndex: slides.length + 1,
4379
4830
  title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
4380
- texts: chunk.slice(1)
4831
+ subtitle: chunk.length > 2 ? chunk[1] : void 0,
4832
+ paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
4833
+ tableColumns: [],
4834
+ pictureUrl: pictures[slides.length] || null
4381
4835
  });
4382
4836
  }
4383
4837
  }