@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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import DOMPurify6 from 'dompurify';
1
+ import DOMPurify2 from 'dompurify';
2
2
  import * as docx from 'docx-preview';
3
3
  import { unzipSync, strFromU8, unzip } from 'fflate';
4
4
  import * as XLSX from 'xlsx';
@@ -9,7 +9,7 @@ import * as THREE from 'three';
9
9
  import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
10
10
  import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
11
11
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
12
- import { RTFJS } from 'rtf.js';
12
+ import * as RTFJS from 'rtf.js/dist/RTFJS.bundle.js';
13
13
 
14
14
  // ../core/dist/index.js
15
15
  var EventEmitter = class {
@@ -265,6 +265,39 @@ function detectOoxmlType(buffer) {
265
265
  }
266
266
  return "application/zip";
267
267
  }
268
+ function detectCfbfType(buffer) {
269
+ const bytes = new Uint8Array(buffer);
270
+ const hasUtf16le = (str) => {
271
+ const target = new Uint8Array(str.length * 2);
272
+ for (let i = 0; i < str.length; i++) {
273
+ target[i * 2] = str.charCodeAt(i);
274
+ target[i * 2 + 1] = 0;
275
+ }
276
+ const targetLen = target.length;
277
+ const max = bytes.length - targetLen;
278
+ for (let i = 0; i <= max; i++) {
279
+ let match = true;
280
+ for (let j = 0; j < targetLen; j++) {
281
+ if (bytes[i + j] !== target[j]) {
282
+ match = false;
283
+ break;
284
+ }
285
+ }
286
+ if (match) return true;
287
+ }
288
+ return false;
289
+ };
290
+ if (hasUtf16le("PowerPoint Document")) {
291
+ return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
292
+ }
293
+ if (hasUtf16le("WordDocument")) {
294
+ return { mime: "application/msword", extension: ".doc" };
295
+ }
296
+ if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
297
+ return { mime: "application/vnd.ms-excel", extension: ".xls" };
298
+ }
299
+ return null;
300
+ }
268
301
  function extractExtension(nameOrUrl) {
269
302
  try {
270
303
  const url = new URL(nameOrUrl);
@@ -329,6 +362,18 @@ async function sourceToArrayBuffer(source, signal) {
329
362
  } else {
330
363
  metadata.mimeType = metadata.mimeType ?? magicMime;
331
364
  }
365
+ } else if (magicMime === "application/x-cfbf") {
366
+ if (metadata.extension) {
367
+ metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
368
+ } else {
369
+ const cfbf = detectCfbfType(buffer);
370
+ if (cfbf) {
371
+ metadata.mimeType = cfbf.mime;
372
+ metadata.extension = cfbf.extension;
373
+ } else {
374
+ metadata.mimeType = magicMime;
375
+ }
376
+ }
332
377
  } else {
333
378
  metadata.mimeType = magicMime;
334
379
  }
@@ -339,12 +384,12 @@ async function sourceToArrayBuffer(source, signal) {
339
384
  return { buffer, metadata };
340
385
  }
341
386
  function sanitizeHTML(html) {
342
- return DOMPurify6.sanitize(html, {
387
+ return DOMPurify2.sanitize(html, {
343
388
  USE_PROFILES: { html: true }
344
389
  });
345
390
  }
346
391
  function sanitizeSVG(svg) {
347
- return DOMPurify6.sanitize(svg, {
392
+ return DOMPurify2.sanitize(svg, {
348
393
  USE_PROFILES: { svg: true, svgFilters: true }
349
394
  });
350
395
  }
@@ -443,6 +488,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
443
488
  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>`;
444
489
  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>`;
445
490
  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>`;
491
+ 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>`;
492
+ 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>`;
493
+ 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>`;
446
494
  var ICON_MAP = {
447
495
  "zoom-in": ICON_ZOOM_IN,
448
496
  "zoom-out": ICON_ZOOM_OUT,
@@ -463,7 +511,12 @@ var ICON_MAP = {
463
511
  "page-prev": ICON_PAGE_PREV,
464
512
  "page-next": ICON_PAGE_NEXT,
465
513
  "chevron-left": ICON_PAGE_PREV,
466
- "chevron-right": ICON_PAGE_NEXT
514
+ "chevron-right": ICON_PAGE_NEXT,
515
+ "fast-forward": ICON_FAST_FORWARD,
516
+ "forward-10": ICON_FAST_FORWARD,
517
+ "rewind": ICON_REWIND,
518
+ "replay-10": ICON_REWIND,
519
+ "speed": ICON_SPEED
467
520
  };
468
521
  var ToolbarController = class {
469
522
  el;
@@ -1431,6 +1484,16 @@ var MediaPlugin = class {
1431
1484
  }
1432
1485
  if (instance.play) {
1433
1486
  actions.push(
1487
+ {
1488
+ id: "replay-10",
1489
+ icon: "replay-10",
1490
+ label: "Rewind 10s",
1491
+ type: "button",
1492
+ group: "actions",
1493
+ execute: () => {
1494
+ instance.rewind?.(10);
1495
+ }
1496
+ },
1434
1497
  {
1435
1498
  id: "play",
1436
1499
  icon: "play",
@@ -1450,6 +1513,26 @@ var MediaPlugin = class {
1450
1513
  execute: () => {
1451
1514
  instance.pause?.();
1452
1515
  }
1516
+ },
1517
+ {
1518
+ id: "forward-10",
1519
+ icon: "forward-10",
1520
+ label: "Fast Forward 10s",
1521
+ type: "button",
1522
+ group: "actions",
1523
+ execute: () => {
1524
+ instance.fastForward?.(10);
1525
+ }
1526
+ },
1527
+ {
1528
+ id: "speed",
1529
+ icon: "speed",
1530
+ label: "Playback Speed",
1531
+ type: "button",
1532
+ group: "actions",
1533
+ execute: () => {
1534
+ instance.cycleSpeed?.();
1535
+ }
1453
1536
  }
1454
1537
  );
1455
1538
  }
@@ -1572,6 +1655,28 @@ var MediaPlugin = class {
1572
1655
  play: mediaElement ? () => mediaElement?.play() : void 0,
1573
1656
  pause: mediaElement ? () => mediaElement?.pause() : void 0,
1574
1657
  isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1658
+ fastForward: mediaElement ? (seconds = 10) => {
1659
+ if (mediaElement) {
1660
+ mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
1661
+ }
1662
+ } : void 0,
1663
+ rewind: mediaElement ? (seconds = 10) => {
1664
+ if (mediaElement) {
1665
+ mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
1666
+ }
1667
+ } : void 0,
1668
+ cycleSpeed: mediaElement ? () => {
1669
+ if (mediaElement) {
1670
+ const speeds = [1, 1.25, 1.5, 2, 0.5];
1671
+ const curIndex = speeds.indexOf(mediaElement.playbackRate);
1672
+ const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
1673
+ mediaElement.playbackRate = speeds[nextIndex];
1674
+ }
1675
+ } : void 0,
1676
+ setPlaybackRate: mediaElement ? (rate) => {
1677
+ if (mediaElement) mediaElement.playbackRate = rate;
1678
+ } : void 0,
1679
+ getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
1575
1680
  download: () => {
1576
1681
  const a = document.createElement("a");
1577
1682
  a.href = url;
@@ -1601,7 +1706,10 @@ var DocxPlugin = class {
1601
1706
  supports(file) {
1602
1707
  const ext = file.metadata.extension?.toLowerCase();
1603
1708
  const mime = file.metadata.mimeType?.toLowerCase();
1604
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
1709
+ if (ext) {
1710
+ return this.extensions.includes(ext);
1711
+ }
1712
+ return this.mimeTypes.includes(mime || "");
1605
1713
  }
1606
1714
  getToolbarActions(instance) {
1607
1715
  return [
@@ -1653,16 +1761,35 @@ var DocxPlugin = class {
1653
1761
  wrapper.style.transformOrigin = "top center";
1654
1762
  wrapper.style.transition = "transform 0.2s ease";
1655
1763
  wrapper.style.padding = "24px 16px";
1656
- wrapper.style.maxWidth = "900px";
1764
+ wrapper.style.maxWidth = "950px";
1657
1765
  wrapper.style.margin = "0 auto";
1658
1766
  wrapper.style.boxSizing = "border-box";
1659
1767
  ctx.container.style.overflow = "auto";
1660
1768
  ctx.container.style.backgroundColor = "#f1f5f9";
1661
1769
  ctx.container.appendChild(wrapper);
1770
+ const styleOverride = document.createElement("style");
1771
+ styleOverride.textContent = `
1772
+ .fp-docx-wrapper .docx-wrapper {
1773
+ background: transparent !important;
1774
+ padding: 0 !important;
1775
+ display: flex !important;
1776
+ flex-direction: column !important;
1777
+ align-items: center !important;
1778
+ box-sizing: border-box !important;
1779
+ }
1780
+ .fp-docx-wrapper section.docx {
1781
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
1782
+ border-radius: 4px !important;
1783
+ margin-bottom: 24px !important;
1784
+ background-color: #ffffff !important;
1785
+ }
1786
+ `;
1787
+ ctx.container.appendChild(styleOverride);
1662
1788
  let scale = 1;
1663
1789
  let renderedSuccessfully = false;
1790
+ const createdBlobUrls = [];
1664
1791
  try {
1665
- await docx.renderAsync(ctx.buffer, wrapper, ctx.container, {
1792
+ await docx.renderAsync(ctx.buffer, wrapper, wrapper, {
1666
1793
  inWrapper: true,
1667
1794
  ignoreWidth: false,
1668
1795
  ignoreHeight: false,
@@ -1671,19 +1798,25 @@ var DocxPlugin = class {
1671
1798
  breakPages: true,
1672
1799
  experimental: true
1673
1800
  });
1801
+ if (!ctx.container.contains(wrapper)) {
1802
+ ctx.container.appendChild(wrapper);
1803
+ }
1674
1804
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
1675
1805
  renderedSuccessfully = true;
1676
1806
  }
1677
1807
  } catch (err) {
1678
- console.warn("[DocxPlugin] docx-preview failed, triggering native XML fallback:", err);
1808
+ console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
1679
1809
  }
1680
1810
  if (!renderedSuccessfully) {
1681
1811
  try {
1682
1812
  wrapper.innerHTML = "";
1683
- this.renderXmlFallback(ctx, wrapper);
1813
+ this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
1814
+ if (!ctx.container.contains(wrapper)) {
1815
+ ctx.container.appendChild(wrapper);
1816
+ }
1684
1817
  renderedSuccessfully = true;
1685
1818
  } catch (fallbackErr) {
1686
- console.error("[DocxPlugin] XML fallback failed:", fallbackErr);
1819
+ console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
1687
1820
  wrapper.innerHTML = `
1688
1821
  <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
1689
1822
  <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
@@ -1691,10 +1824,17 @@ var DocxPlugin = class {
1691
1824
  <p style="color: #64748b; margin: 0;">Could not parse document content</p>
1692
1825
  </div>
1693
1826
  `;
1827
+ if (!ctx.container.contains(wrapper)) {
1828
+ ctx.container.appendChild(wrapper);
1829
+ }
1694
1830
  }
1695
1831
  }
1696
1832
  const cleanup = () => {
1833
+ for (const url of createdBlobUrls) {
1834
+ URL.revokeObjectURL(url);
1835
+ }
1697
1836
  wrapper.remove();
1837
+ styleOverride.remove();
1698
1838
  ctx.container.innerHTML = "";
1699
1839
  };
1700
1840
  ctx.signal.addEventListener("abort", cleanup);
@@ -1731,13 +1871,50 @@ var DocxPlugin = class {
1731
1871
  }
1732
1872
  };
1733
1873
  }
1734
- renderXmlFallback(ctx, wrapper) {
1874
+ renderXmlFallback(ctx, wrapper, createdBlobUrls) {
1875
+ const magic = new Uint8Array(ctx.buffer.slice(0, 8));
1876
+ if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
1877
+ this.renderBinaryDocFallback(ctx, wrapper);
1878
+ return;
1879
+ }
1735
1880
  const unzipped = unzipSync(new Uint8Array(ctx.buffer));
1736
- const docXmlEntry = unzipped["word/document.xml"];
1881
+ const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
1882
+ const docXmlEntry = docKey ? unzipped[docKey] : void 0;
1737
1883
  if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
1738
1884
  const xmlStr = strFromU8(docXmlEntry);
1739
1885
  const parser = new DOMParser();
1740
1886
  const doc = parser.parseFromString(xmlStr, "application/xml");
1887
+ const imageMap = {};
1888
+ const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
1889
+ if (relsKey && unzipped[relsKey]) {
1890
+ try {
1891
+ const relsXml = strFromU8(unzipped[relsKey]);
1892
+ const relsDoc = parser.parseFromString(relsXml, "application/xml");
1893
+ const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
1894
+ for (const rel of relEls) {
1895
+ const rId = rel.getAttribute("Id");
1896
+ const target = rel.getAttribute("Target");
1897
+ if (rId && target) {
1898
+ const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
1899
+ const fullTargetKey = Object.keys(unzipped).find((k) => {
1900
+ const norm = k.replace(/^[./\\]+/, "").toLowerCase();
1901
+ return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
1902
+ });
1903
+ if (fullTargetKey && unzipped[fullTargetKey]) {
1904
+ const bytes = unzipped[fullTargetKey];
1905
+ const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
1906
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
1907
+ const blob = new Blob([bytes], { type: mime });
1908
+ const blobUrl = URL.createObjectURL(blob);
1909
+ imageMap[rId] = blobUrl;
1910
+ createdBlobUrls.push(blobUrl);
1911
+ }
1912
+ }
1913
+ }
1914
+ } catch (relsErr) {
1915
+ console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
1916
+ }
1917
+ }
1741
1918
  const card = document.createElement("div");
1742
1919
  card.className = "fp-docx-page-card";
1743
1920
  card.style.backgroundColor = "#ffffff";
@@ -1755,7 +1932,7 @@ var DocxPlugin = class {
1755
1932
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
1756
1933
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
1757
1934
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
1758
- const textContent = this.extractParagraphHtml(child);
1935
+ const textContent = this.extractParagraphHtml(child, imageMap);
1759
1936
  if (!textContent.trim()) {
1760
1937
  html += '<div style="height: 10px;"></div>';
1761
1938
  continue;
@@ -1781,7 +1958,7 @@ var DocxPlugin = class {
1781
1958
  html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
1782
1959
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
1783
1960
  cells.forEach((tc) => {
1784
- const cellText = this.extractParagraphHtml(tc);
1961
+ const cellText = this.extractParagraphHtml(tc, imageMap);
1785
1962
  html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
1786
1963
  });
1787
1964
  html += "</tr>";
@@ -1789,34 +1966,63 @@ var DocxPlugin = class {
1789
1966
  html += "</table>";
1790
1967
  }
1791
1968
  }
1792
- card.innerHTML = DOMPurify6.sanitize(html, {
1793
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "img", "div"],
1969
+ card.innerHTML = DOMPurify2.sanitize(html, {
1970
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
1794
1971
  ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
1795
1972
  });
1796
1973
  wrapper.appendChild(card);
1797
1974
  }
1798
- extractParagraphHtml(pElement) {
1975
+ extractParagraphHtml(pElement, imageMap = {}) {
1799
1976
  let result = "";
1977
+ const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
1978
+ for (const drawing of drawings) {
1979
+ const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
1980
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1981
+ if (rId && imageMap[rId]) {
1982
+ result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
1983
+ }
1984
+ }
1800
1985
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
1801
- if (runs.length === 0) {
1802
- return DOMPurify6.sanitize(pElement.textContent || "");
1986
+ if (runs.length === 0 && !result) {
1987
+ return DOMPurify2.sanitize(pElement.textContent || "");
1803
1988
  }
1804
1989
  for (const r of runs) {
1990
+ const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
1991
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1992
+ if (rId && imageMap[rId]) {
1993
+ result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
1994
+ }
1995
+ const brs = r.getElementsByTagNameNS("*", "br");
1996
+ for (let i = 0; i < brs.length; i++) {
1997
+ result += "<br/>";
1998
+ }
1999
+ const tabs = r.getElementsByTagNameNS("*", "tab");
2000
+ if (tabs.length > 0) {
2001
+ result += "&emsp;";
2002
+ }
1805
2003
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
1806
2004
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
1807
2005
  const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
1808
2006
  const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
2007
+ const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
1809
2008
  const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
1810
2009
  const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
2010
+ const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
2011
+ const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
1811
2012
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
1812
2013
  let text = texts.map((t) => t.textContent || "").join("");
1813
2014
  if (!text) continue;
1814
- text = DOMPurify6.sanitize(text);
2015
+ text = DOMPurify2.sanitize(text);
1815
2016
  let styles = "";
1816
2017
  if (isBold) styles += "font-weight: bold; ";
1817
2018
  if (isItalic) styles += "font-style: italic; ";
1818
2019
  if (isUnderline) styles += "text-decoration: underline; ";
2020
+ if (isStrike) styles += "text-decoration: line-through; ";
1819
2021
  if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
2022
+ if (szVal) {
2023
+ const pt = parseInt(szVal, 10) / 2;
2024
+ if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2025
+ }
1820
2026
  if (styles) {
1821
2027
  result += `<span style="${styles}">${text}</span>`;
1822
2028
  } else {
@@ -1825,6 +2031,52 @@ var DocxPlugin = class {
1825
2031
  }
1826
2032
  return result;
1827
2033
  }
2034
+ renderBinaryDocFallback(ctx, wrapper) {
2035
+ const card = document.createElement("div");
2036
+ card.className = "fp-docx-page-card";
2037
+ card.style.backgroundColor = "#ffffff";
2038
+ card.style.borderRadius = "6px";
2039
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2040
+ card.style.padding = "48px 56px";
2041
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2042
+ card.style.color = "#1e293b";
2043
+ card.style.lineHeight = "1.6";
2044
+ try {
2045
+ const cfbf = new CfbfReader(ctx.buffer);
2046
+ const wordDocStream = cfbf.readStream("WordDocument");
2047
+ if (wordDocStream && wordDocStream.length >= 512) {
2048
+ const text2 = this.extractReadableStrings(wordDocStream);
2049
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text2)}</div>`;
2050
+ wrapper.appendChild(card);
2051
+ return;
2052
+ }
2053
+ } catch {
2054
+ }
2055
+ const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2056
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text)}</div>`;
2057
+ wrapper.appendChild(card);
2058
+ }
2059
+ extractReadableStrings(bytes) {
2060
+ let result = "";
2061
+ let current = "";
2062
+ for (let i = 0; i < bytes.length; i++) {
2063
+ const b = bytes[i];
2064
+ if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
2065
+ current += String.fromCharCode(b);
2066
+ } else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
2067
+ continue;
2068
+ } else {
2069
+ if (current.trim().length > 3) {
2070
+ result += current.trim() + "\n\n";
2071
+ }
2072
+ current = "";
2073
+ }
2074
+ }
2075
+ if (current.trim().length > 3) {
2076
+ result += current.trim();
2077
+ }
2078
+ return result;
2079
+ }
1828
2080
  };
1829
2081
  function docxPlugin() {
1830
2082
  return new DocxPlugin();
@@ -1846,7 +2098,10 @@ var ExcelPlugin = class {
1846
2098
  supports(file) {
1847
2099
  const ext = file.metadata.extension?.toLowerCase();
1848
2100
  const mime = file.metadata.mimeType?.toLowerCase();
1849
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
2101
+ if (ext) {
2102
+ return this.extensions.includes(ext);
2103
+ }
2104
+ return this.mimeTypes.includes(mime || "");
1850
2105
  }
1851
2106
  getToolbarActions(instance) {
1852
2107
  return [
@@ -2636,7 +2891,7 @@ var MarkdownPlugin = class {
2636
2891
  gfm: true,
2637
2892
  breaks: true
2638
2893
  });
2639
- const cleanHtml = DOMPurify6.sanitize(rawHtml, {
2894
+ const cleanHtml = DOMPurify2.sanitize(rawHtml, {
2640
2895
  USE_PROFILES: { html: true }
2641
2896
  });
2642
2897
  const wrapper = document.createElement("div");
@@ -2788,7 +3043,10 @@ var PptxPlugin = class {
2788
3043
  supports(file) {
2789
3044
  const ext = file.metadata.extension?.toLowerCase();
2790
3045
  const mime = file.metadata.mimeType?.toLowerCase();
2791
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3046
+ if (ext) {
3047
+ return this.extensions.includes(ext);
3048
+ }
3049
+ return this.mimeTypes.includes(mime || "");
2792
3050
  }
2793
3051
  getToolbarActions(instance) {
2794
3052
  return [
@@ -3233,6 +3491,9 @@ var RtfPlugin = class {
3233
3491
  ctx.container.appendChild(wrapper);
3234
3492
  let scale = 1;
3235
3493
  try {
3494
+ if (typeof RTFJS.loggingEnabled === "function") {
3495
+ RTFJS.loggingEnabled(false);
3496
+ }
3236
3497
  const doc = new RTFJS.Document(ctx.buffer, {});
3237
3498
  const htmlElements = await doc.render();
3238
3499
  for (const el of htmlElements) {
@@ -3351,7 +3612,7 @@ var HtmlPreviewPlugin = class {
3351
3612
  }
3352
3613
  async render(ctx) {
3353
3614
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3354
- const sanitized = DOMPurify6.sanitize(rawHtml, {
3615
+ const sanitized = DOMPurify2.sanitize(rawHtml, {
3355
3616
  WHOLE_DOCUMENT: true,
3356
3617
  ADD_TAGS: ["style", "link"],
3357
3618
  ADD_ATTR: ["target", "rel"]
@@ -3431,7 +3692,10 @@ var OpenDocumentPlugin = class {
3431
3692
  supports(file) {
3432
3693
  const ext = file.metadata.extension?.toLowerCase();
3433
3694
  const mime = file.metadata.mimeType?.toLowerCase();
3434
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3695
+ if (ext) {
3696
+ return this.extensions.includes(ext);
3697
+ }
3698
+ return this.mimeTypes.includes(mime || "");
3435
3699
  }
3436
3700
  getToolbarActions(instance) {
3437
3701
  const isPresentation = instance.isPresentation;
@@ -3594,7 +3858,7 @@ var OpenDocumentPlugin = class {
3594
3858
  wrapper.style.padding = "48px";
3595
3859
  wrapper.style.minHeight = "100%";
3596
3860
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
3597
- wrapper.innerHTML = DOMPurify6.sanitize(bodyHtml, {
3861
+ wrapper.innerHTML = DOMPurify2.sanitize(bodyHtml, {
3598
3862
  ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
3599
3863
  ADD_ATTR: ["style", "colspan", "rowspan"]
3600
3864
  });
@@ -3846,7 +4110,10 @@ var DocPlugin = class {
3846
4110
  supports(file) {
3847
4111
  const ext = file.metadata.extension?.toLowerCase();
3848
4112
  const mime = file.metadata.mimeType?.toLowerCase();
3849
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4113
+ if (ext) {
4114
+ return this.extensions.includes(ext);
4115
+ }
4116
+ return this.mimeTypes.includes(mime || "");
3850
4117
  }
3851
4118
  getToolbarActions(instance) {
3852
4119
  return [
@@ -4069,22 +4336,22 @@ var DocPlugin = class {
4069
4336
  * Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
4070
4337
  */
4071
4338
  extractStringsFromBytes(bytes) {
4072
- const chars = [];
4073
- const len = bytes.length;
4074
- for (let i = 0; i < len; i++) {
4075
- const b = bytes[i];
4076
- if (b === 13 || b === 10 || b === 9 || b >= 32 && b <= 126 || b >= 160 && b <= 255) {
4077
- chars.push(String.fromCharCode(b));
4078
- } else if (b === 0 && i + 1 < len && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
4079
- chars.push(String.fromCharCode(bytes[i + 1]));
4080
- i++;
4081
- } else if (b === 7) {
4082
- chars.push(" ");
4083
- } else if (b === 12) {
4084
- chars.push("\n\n---PAGE---\n\n");
4339
+ const rawAnsi = new TextDecoder("latin1").decode(bytes);
4340
+ const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
4341
+ const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4342
+ const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4343
+ const candidateLines = [];
4344
+ const seen = /* @__PURE__ */ new Set();
4345
+ for (const run of [...ansiRuns, ...utf16Runs]) {
4346
+ const trimmed = run.trim();
4347
+ if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
4348
+ 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)) {
4349
+ seen.add(trimmed);
4350
+ candidateLines.push(trimmed);
4351
+ }
4085
4352
  }
4086
4353
  }
4087
- return chars.join("");
4354
+ return candidateLines.join("\n\n");
4088
4355
  }
4089
4356
  heuristicTextExtraction(buffer) {
4090
4357
  return this.extractStringsFromBytes(new Uint8Array(buffer));
@@ -4114,24 +4381,24 @@ var DocPlugin = class {
4114
4381
  html += "</ul>";
4115
4382
  inList = false;
4116
4383
  }
4117
- 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>`;
4384
+ 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>`;
4118
4385
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4119
4386
  if (!inList) {
4120
4387
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4121
4388
  inList = true;
4122
4389
  }
4123
4390
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4124
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify6.sanitize(bulletText)}</li>`;
4391
+ html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2.sanitize(bulletText)}</li>`;
4125
4392
  } else {
4126
4393
  if (inList) {
4127
4394
  html += "</ul>";
4128
4395
  inList = false;
4129
4396
  }
4130
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
4397
+ html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2.sanitize(line)}</p>`;
4131
4398
  }
4132
4399
  }
4133
4400
  if (inList) html += "</ul>";
4134
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6.sanitize(filename)})</p>`;
4401
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2.sanitize(filename)})</p>`;
4135
4402
  }
4136
4403
  };
4137
4404
  function docPlugin() {
@@ -4139,14 +4406,17 @@ function docPlugin() {
4139
4406
  }
4140
4407
  var PptPlugin = class {
4141
4408
  id = "ppt";
4142
- name = "Legacy PowerPoint Presentation (.ppt, .pps, .pot)";
4409
+ name = "PowerPoint Presentation (.ppt, .pps, .pot)";
4143
4410
  extensions = [".ppt", ".pps", ".pot"];
4144
4411
  mimeTypes = ["application/vnd.ms-powerpoint"];
4145
- weight = 75;
4412
+ weight = 85;
4146
4413
  supports(file) {
4147
4414
  const ext = file.metadata.extension?.toLowerCase();
4148
4415
  const mime = file.metadata.mimeType?.toLowerCase();
4149
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4416
+ if (ext) {
4417
+ return this.extensions.includes(ext);
4418
+ }
4419
+ return this.mimeTypes.includes(mime || "");
4150
4420
  }
4151
4421
  getToolbarActions(instance) {
4152
4422
  return [
@@ -4234,19 +4504,15 @@ var PptPlugin = class {
4234
4504
  container.style.alignItems = "center";
4235
4505
  container.style.padding = "32px 16px";
4236
4506
  container.style.backgroundColor = "#0f172a";
4507
+ container.style.boxSizing = "border-box";
4237
4508
  const slideCard = document.createElement("div");
4238
4509
  slideCard.className = "fp-ppt-slide-card";
4239
4510
  slideCard.style.width = "960px";
4240
- slideCard.style.maxWidth = "90%";
4511
+ slideCard.style.maxWidth = "92%";
4241
4512
  slideCard.style.aspectRatio = "16 / 9";
4242
4513
  slideCard.style.backgroundColor = "#ffffff";
4243
- slideCard.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
4514
+ slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4244
4515
  slideCard.style.borderRadius = "8px";
4245
- slideCard.style.padding = "48px";
4246
- slideCard.style.display = "flex";
4247
- slideCard.style.flexDirection = "column";
4248
- slideCard.style.justifyContent = "center";
4249
- slideCard.style.alignItems = "center";
4250
4516
  slideCard.style.boxSizing = "border-box";
4251
4517
  slideCard.style.position = "relative";
4252
4518
  slideCard.style.overflow = "hidden";
@@ -4257,21 +4523,25 @@ var PptPlugin = class {
4257
4523
  let scale = 1;
4258
4524
  let currentSlide = 1;
4259
4525
  let slides = [];
4526
+ const createdBlobUrls = [];
4260
4527
  try {
4261
4528
  const cfbf = new CfbfReader(ctx.buffer);
4262
4529
  const pptStream = cfbf.readStream("PowerPoint Document");
4263
4530
  if (!pptStream || pptStream.length < 512) {
4264
4531
  throw new Error("PowerPoint Document stream not found in CFBF container");
4265
4532
  }
4266
- slides = this.extractSlides(pptStream);
4533
+ const pictures = this.extractPictures(cfbf, createdBlobUrls);
4534
+ slides = this.extractSlides(pptStream, pictures);
4267
4535
  } catch (err) {
4268
4536
  console.warn("[PptPlugin] Error extracting binary slides:", err);
4269
4537
  }
4270
4538
  if (slides.length === 0) {
4271
4539
  slides = [
4272
4540
  {
4541
+ slideIndex: 1,
4273
4542
  title: ctx.metadata.name || "PowerPoint Presentation",
4274
- texts: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"]
4543
+ paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
4544
+ tableColumns: []
4275
4545
  }
4276
4546
  ];
4277
4547
  }
@@ -4280,23 +4550,73 @@ var PptPlugin = class {
4280
4550
  currentSlide = idx;
4281
4551
  const s = slides[idx - 1];
4282
4552
  if (!s) return;
4553
+ let contentHtml = "";
4554
+ if (s.pictureUrl) {
4555
+ contentHtml = `
4556
+ <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
4557
+ <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;" />
4558
+ </div>
4559
+ `;
4560
+ } else if (s.tableColumns.length > 0) {
4561
+ const cols = s.tableColumns;
4562
+ contentHtml = `
4563
+ <div style="flex: 1; overflow: auto; padding: 8px 0;">
4564
+ <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
4565
+ <thead>
4566
+ <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
4567
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2.sanitize(c)}</th>`).join("")}
4568
+ </tr>
4569
+ </thead>
4570
+ <tbody>
4571
+ ${[1, 2, 3, 4, 5].map((rowIdx) => `
4572
+ <tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
4573
+ ${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
4574
+ </tr>
4575
+ `).join("")}
4576
+ </tbody>
4577
+ </table>
4578
+ </div>
4579
+ `;
4580
+ } else {
4581
+ const pTags = s.paragraphs.map((p) => {
4582
+ const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
4583
+ 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("");
4584
+ }).join("");
4585
+ contentHtml = `
4586
+ <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
4587
+ ${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
4588
+ </div>
4589
+ `;
4590
+ }
4283
4591
  slideCard.innerHTML = `
4284
- <div style="position: absolute; top: 20px; right: 24px; font-size: 12px; color: #94a3b8; font-weight: 600;">
4285
- Slide ${idx} of ${totalSlides}
4286
- </div>
4287
- <div style="text-align: center; width: 100%;">
4288
- <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4289
- ${DOMPurify6.sanitize(s.title || `Slide ${idx}`)}
4290
- </h1>
4291
- <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4292
- ${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("")}
4592
+ <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;">
4593
+ <!-- Header Banner matching PowerPoint design -->
4594
+ <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;">
4595
+ <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
4596
+ ${DOMPurify2.sanitize(s.title)}
4597
+ </h1>
4598
+ ${s.subtitle ? `
4599
+ <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);">
4600
+ ${DOMPurify2.sanitize(s.subtitle)}
4601
+ </span>
4602
+ ` : `
4603
+ <span style="font-size: 12px; color: #365314; font-weight: 600;">
4604
+ Slide ${idx} / ${totalSlides}
4605
+ </span>
4606
+ `}
4293
4607
  </div>
4608
+
4609
+ <!-- Slide Content -->
4610
+ ${contentHtml}
4294
4611
  </div>
4295
4612
  `;
4296
4613
  ctx.emit("page-change", { page: currentSlide, total: totalSlides });
4297
4614
  };
4298
4615
  renderSlide(1);
4299
4616
  const cleanup = () => {
4617
+ for (const u of createdBlobUrls) {
4618
+ URL.revokeObjectURL(u);
4619
+ }
4300
4620
  container.remove();
4301
4621
  ctx.container.innerHTML = "";
4302
4622
  };
@@ -4336,13 +4656,48 @@ var PptPlugin = class {
4336
4656
  if (!ctx2d) return;
4337
4657
  canvas.width = 160;
4338
4658
  canvas.height = 90;
4339
- ctx2d.fillStyle = "#ffffff";
4659
+ ctx2d.fillStyle = "#334155";
4340
4660
  ctx2d.fillRect(0, 0, 160, 90);
4341
- ctx2d.fillStyle = "#1e3a8a";
4342
- ctx2d.font = "bold 11px sans-serif";
4343
- ctx2d.textAlign = "center";
4344
- const title = s.title.slice(0, 18) || `Slide ${idx + 1}`;
4345
- ctx2d.fillText(title, 80, 50);
4661
+ ctx2d.fillStyle = "#ffffff";
4662
+ ctx2d.fillRect(3, 3, 154, 84);
4663
+ ctx2d.fillStyle = "#84cc16";
4664
+ ctx2d.fillRect(6, 6, 148, 18);
4665
+ ctx2d.fillStyle = "#1e293b";
4666
+ ctx2d.font = "bold 9px sans-serif";
4667
+ ctx2d.textAlign = "left";
4668
+ const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
4669
+ ctx2d.fillText(displayTitle, 10, 19);
4670
+ if (s.subtitle) {
4671
+ ctx2d.fillStyle = "#38bdf8";
4672
+ ctx2d.fillRect(116, 9, 34, 12);
4673
+ ctx2d.fillStyle = "#ffffff";
4674
+ ctx2d.font = "bold 7px sans-serif";
4675
+ ctx2d.textAlign = "center";
4676
+ ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
4677
+ }
4678
+ if (s.pictureUrl) {
4679
+ ctx2d.fillStyle = "#3b82f6";
4680
+ ctx2d.fillRect(52, 34, 56, 42);
4681
+ ctx2d.fillStyle = "#ffffff";
4682
+ ctx2d.font = "8px sans-serif";
4683
+ ctx2d.textAlign = "center";
4684
+ ctx2d.fillText("Chart", 80, 58);
4685
+ } else if (s.tableColumns.length > 0) {
4686
+ ctx2d.strokeStyle = "#cbd5e1";
4687
+ ctx2d.lineWidth = 1;
4688
+ ctx2d.strokeRect(14, 32, 132, 46);
4689
+ for (let l = 1; l <= 3; l++) {
4690
+ ctx2d.beginPath();
4691
+ ctx2d.moveTo(14, 32 + l * 11);
4692
+ ctx2d.lineTo(146, 32 + l * 11);
4693
+ ctx2d.stroke();
4694
+ }
4695
+ } else {
4696
+ ctx2d.fillStyle = "#94a3b8";
4697
+ for (let l = 0; l < 4; l++) {
4698
+ ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
4699
+ }
4700
+ }
4346
4701
  }
4347
4702
  }));
4348
4703
  },
@@ -4360,66 +4715,165 @@ var PptPlugin = class {
4360
4715
  }
4361
4716
  };
4362
4717
  }
4718
+ /**
4719
+ * Extract PNG and JPEG images from the Pictures stream
4720
+ */
4721
+ extractPictures(cfbf, createdUrls) {
4722
+ const urls = [];
4723
+ try {
4724
+ const picStream = cfbf.readStream("Pictures");
4725
+ if (!picStream || picStream.length < 32) return urls;
4726
+ const pBuf = new Uint8Array(picStream);
4727
+ const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
4728
+ const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
4729
+ for (let i = 0; i <= pBuf.length - 8; i++) {
4730
+ let match = true;
4731
+ for (let j = 0; j < 8; j++) {
4732
+ if (pBuf[i + j] !== pngSig[j]) {
4733
+ match = false;
4734
+ break;
4735
+ }
4736
+ }
4737
+ if (match) {
4738
+ let endIdx = -1;
4739
+ for (let k = i + 8; k <= pBuf.length - 8; k++) {
4740
+ let endMatch = true;
4741
+ for (let j = 0; j < 8; j++) {
4742
+ if (pBuf[k + j] !== iendSig[j]) {
4743
+ endMatch = false;
4744
+ break;
4745
+ }
4746
+ }
4747
+ if (endMatch) {
4748
+ endIdx = k + 8;
4749
+ break;
4750
+ }
4751
+ }
4752
+ if (endIdx !== -1) {
4753
+ const pngBytes = pBuf.subarray(i, endIdx);
4754
+ const blob = new Blob([pngBytes], { type: "image/png" });
4755
+ const url = URL.createObjectURL(blob);
4756
+ urls.push(url);
4757
+ createdUrls.push(url);
4758
+ i = endIdx;
4759
+ }
4760
+ }
4761
+ }
4762
+ for (let i = 0; i <= pBuf.length - 3; i++) {
4763
+ if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
4764
+ let endIdx = -1;
4765
+ for (let k = i + 3; k < pBuf.length - 1; k++) {
4766
+ if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
4767
+ endIdx = k + 2;
4768
+ break;
4769
+ }
4770
+ }
4771
+ if (endIdx !== -1) {
4772
+ const jpgBytes = pBuf.subarray(i, endIdx);
4773
+ const blob = new Blob([jpgBytes], { type: "image/jpeg" });
4774
+ const url = URL.createObjectURL(blob);
4775
+ urls.push(url);
4776
+ createdUrls.push(url);
4777
+ i = endIdx;
4778
+ }
4779
+ }
4780
+ }
4781
+ } catch (err) {
4782
+ console.warn("[PptPlugin] Error extracting pictures:", err);
4783
+ }
4784
+ return urls;
4785
+ }
4363
4786
  /**
4364
4787
  * Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
4365
4788
  */
4366
- extractSlides(stream) {
4789
+ extractSlides(stream, pictures) {
4367
4790
  const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
4368
4791
  const len = stream.length;
4369
4792
  let offset = 0;
4370
4793
  const slides = [];
4371
- let currentSlideTexts = [];
4794
+ let picIdx = 0;
4372
4795
  while (offset + 8 <= len) {
4373
4796
  const recVerInst = view.getUint16(offset, true);
4374
4797
  const recType = view.getUint16(offset + 2, true);
4375
4798
  const recLen = view.getUint32(offset + 4, true);
4799
+ const isContainer = (recVerInst & 15) === 15;
4376
4800
  if (recType === 1006) {
4377
- if (currentSlideTexts.length > 0) {
4378
- const title = currentSlideTexts[0] || "Slide";
4379
- const texts = currentSlideTexts.slice(1);
4380
- slides.push({ title, texts });
4381
- currentSlideTexts = [];
4801
+ const slideEnd = Math.min(len, offset + 8 + recLen);
4802
+ const rawTexts = [];
4803
+ let hasOle = false;
4804
+ let sOff = offset + 8;
4805
+ while (sOff + 8 <= slideEnd) {
4806
+ const cVerInst = view.getUint16(sOff, true);
4807
+ const cType = view.getUint16(sOff + 2, true);
4808
+ const cLen = view.getUint32(sOff + 4, true);
4809
+ const cIsContainer = (cVerInst & 15) === 15;
4810
+ if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4811
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4812
+ const txt = new TextDecoder("latin1").decode(bytes).trim();
4813
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4814
+ rawTexts.push(txt);
4815
+ }
4816
+ } else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4817
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4818
+ const txt = new TextDecoder("utf-16le").decode(bytes).trim();
4819
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4820
+ rawTexts.push(txt);
4821
+ }
4822
+ } else if (cType === 3009 || cType === 3011) {
4823
+ hasOle = true;
4824
+ }
4825
+ if (cIsContainer) sOff += 8;
4826
+ else sOff += 8 + cLen;
4382
4827
  }
4383
- offset += 8;
4384
- continue;
4385
- }
4386
- if (recType === 3998 && recLen > 0 && offset + 8 + recLen <= len) {
4387
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4388
- const text = new TextDecoder("latin1").decode(bytes).trim();
4389
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4390
- currentSlideTexts.push(text);
4828
+ let title = "";
4829
+ let subtitle = "";
4830
+ const paragraphs = [];
4831
+ const tableColumns = [];
4832
+ for (const t of rawTexts) {
4833
+ if (!title && t.length < 60 && !t.includes("\n")) {
4834
+ title = t;
4835
+ } else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
4836
+ tableColumns.push(t);
4837
+ } else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
4838
+ subtitle = t;
4839
+ } else {
4840
+ paragraphs.push(t);
4841
+ }
4391
4842
  }
4392
- }
4393
- if (recType === 3999 && recLen > 0 && offset + 8 + recLen <= len) {
4394
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4395
- const text = new TextDecoder("utf-16le").decode(bytes).trim();
4396
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4397
- currentSlideTexts.push(text);
4843
+ let pictureUrl = null;
4844
+ if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
4845
+ pictureUrl = pictures[picIdx++];
4398
4846
  }
4847
+ slides.push({
4848
+ slideIndex: slides.length + 1,
4849
+ title: title || `Slide ${slides.length + 1}`,
4850
+ subtitle,
4851
+ paragraphs,
4852
+ tableColumns,
4853
+ pictureUrl,
4854
+ hasOle
4855
+ });
4399
4856
  }
4400
- const isContainer = (recVerInst & 15) === 15;
4401
- if (isContainer) {
4402
- offset += 8;
4403
- } else {
4404
- offset += 8 + recLen;
4405
- }
4406
- }
4407
- if (currentSlideTexts.length > 0) {
4408
- const title = currentSlideTexts[0] || "Slide";
4409
- const texts = currentSlideTexts.slice(1);
4410
- slides.push({ title, texts });
4857
+ if (isContainer) offset += 8;
4858
+ else offset += 8 + recLen;
4411
4859
  }
4412
4860
  if (slides.length === 0) {
4413
4861
  const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
4414
- const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{4,}/g) || [];
4415
- const filtered = matches.map((m) => m.trim()).filter((m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times"));
4416
- if (filtered.length > 0) {
4417
- const chunkSize = 4;
4418
- for (let i = 0; i < filtered.length; i += chunkSize) {
4419
- const chunk = filtered.slice(i, i + chunkSize);
4862
+ const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
4863
+ const clean = matches.map((m) => m.trim()).filter(
4864
+ (m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
4865
+ );
4866
+ if (clean.length > 0) {
4867
+ const chunkSize = 3;
4868
+ for (let i = 0; i < clean.length; i += chunkSize) {
4869
+ const chunk = clean.slice(i, i + chunkSize);
4420
4870
  slides.push({
4871
+ slideIndex: slides.length + 1,
4421
4872
  title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
4422
- texts: chunk.slice(1)
4873
+ subtitle: chunk.length > 2 ? chunk[1] : void 0,
4874
+ paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
4875
+ tableColumns: [],
4876
+ pictureUrl: pictures[slides.length] || null
4423
4877
  });
4424
4878
  }
4425
4879
  }