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