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