@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/angular.cjs CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var core = require('@angular/core');
6
6
  var common = require('@angular/common');
7
- var DOMPurify6 = require('dompurify');
7
+ var DOMPurify2 = require('dompurify');
8
8
  var docx = require('docx-preview');
9
9
  var fflate = require('fflate');
10
10
  var XLSX = require('xlsx');
@@ -15,7 +15,7 @@ var THREE = require('three');
15
15
  var STLLoader_js = require('three/examples/jsm/loaders/STLLoader.js');
16
16
  var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
17
17
  var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
18
- var rtf_js = require('rtf.js');
18
+ var RTFJS = require('rtf.js/dist/RTFJS.bundle.js');
19
19
 
20
20
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
21
 
@@ -37,11 +37,12 @@ function _interopNamespace(e) {
37
37
  return Object.freeze(n);
38
38
  }
39
39
 
40
- var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
40
+ var DOMPurify2__default = /*#__PURE__*/_interopDefault(DOMPurify2);
41
41
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
42
42
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
43
43
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
44
44
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
45
+ var RTFJS__namespace = /*#__PURE__*/_interopNamespace(RTFJS);
45
46
 
46
47
  var __create = Object.create;
47
48
  var __defProp = Object.defineProperty;
@@ -344,6 +345,39 @@ function detectOoxmlType(buffer) {
344
345
  }
345
346
  return "application/zip";
346
347
  }
348
+ function detectCfbfType(buffer) {
349
+ const bytes = new Uint8Array(buffer);
350
+ const hasUtf16le = (str) => {
351
+ const target = new Uint8Array(str.length * 2);
352
+ for (let i = 0; i < str.length; i++) {
353
+ target[i * 2] = str.charCodeAt(i);
354
+ target[i * 2 + 1] = 0;
355
+ }
356
+ const targetLen = target.length;
357
+ const max = bytes.length - targetLen;
358
+ for (let i = 0; i <= max; i++) {
359
+ let match = true;
360
+ for (let j = 0; j < targetLen; j++) {
361
+ if (bytes[i + j] !== target[j]) {
362
+ match = false;
363
+ break;
364
+ }
365
+ }
366
+ if (match) return true;
367
+ }
368
+ return false;
369
+ };
370
+ if (hasUtf16le("PowerPoint Document")) {
371
+ return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
372
+ }
373
+ if (hasUtf16le("WordDocument")) {
374
+ return { mime: "application/msword", extension: ".doc" };
375
+ }
376
+ if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
377
+ return { mime: "application/vnd.ms-excel", extension: ".xls" };
378
+ }
379
+ return null;
380
+ }
347
381
  function extractExtension(nameOrUrl) {
348
382
  try {
349
383
  const url = new URL(nameOrUrl);
@@ -408,6 +442,18 @@ async function sourceToArrayBuffer(source, signal) {
408
442
  } else {
409
443
  metadata.mimeType = metadata.mimeType ?? magicMime;
410
444
  }
445
+ } else if (magicMime === "application/x-cfbf") {
446
+ if (metadata.extension) {
447
+ metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
448
+ } else {
449
+ const cfbf = detectCfbfType(buffer);
450
+ if (cfbf) {
451
+ metadata.mimeType = cfbf.mime;
452
+ metadata.extension = cfbf.extension;
453
+ } else {
454
+ metadata.mimeType = magicMime;
455
+ }
456
+ }
411
457
  } else {
412
458
  metadata.mimeType = magicMime;
413
459
  }
@@ -418,7 +464,7 @@ async function sourceToArrayBuffer(source, signal) {
418
464
  return { buffer, metadata };
419
465
  }
420
466
  function sanitizeSVG(svg) {
421
- return DOMPurify6__default.default.sanitize(svg, {
467
+ return DOMPurify2__default.default.sanitize(svg, {
422
468
  USE_PROFILES: { svg: true, svgFilters: true }
423
469
  });
424
470
  }
@@ -478,6 +524,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
478
524
  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>`;
479
525
  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>`;
480
526
  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>`;
527
+ 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>`;
528
+ 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>`;
529
+ 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>`;
481
530
  var ICON_MAP = {
482
531
  "zoom-in": ICON_ZOOM_IN,
483
532
  "zoom-out": ICON_ZOOM_OUT,
@@ -498,7 +547,12 @@ var ICON_MAP = {
498
547
  "page-prev": ICON_PAGE_PREV,
499
548
  "page-next": ICON_PAGE_NEXT,
500
549
  "chevron-left": ICON_PAGE_PREV,
501
- "chevron-right": ICON_PAGE_NEXT
550
+ "chevron-right": ICON_PAGE_NEXT,
551
+ "fast-forward": ICON_FAST_FORWARD,
552
+ "forward-10": ICON_FAST_FORWARD,
553
+ "rewind": ICON_REWIND,
554
+ "replay-10": ICON_REWIND,
555
+ "speed": ICON_SPEED
502
556
  };
503
557
  var ToolbarController = class {
504
558
  el;
@@ -1466,6 +1520,16 @@ var MediaPlugin = class {
1466
1520
  }
1467
1521
  if (instance.play) {
1468
1522
  actions.push(
1523
+ {
1524
+ id: "replay-10",
1525
+ icon: "replay-10",
1526
+ label: "Rewind 10s",
1527
+ type: "button",
1528
+ group: "actions",
1529
+ execute: () => {
1530
+ instance.rewind?.(10);
1531
+ }
1532
+ },
1469
1533
  {
1470
1534
  id: "play",
1471
1535
  icon: "play",
@@ -1485,6 +1549,26 @@ var MediaPlugin = class {
1485
1549
  execute: () => {
1486
1550
  instance.pause?.();
1487
1551
  }
1552
+ },
1553
+ {
1554
+ id: "forward-10",
1555
+ icon: "forward-10",
1556
+ label: "Fast Forward 10s",
1557
+ type: "button",
1558
+ group: "actions",
1559
+ execute: () => {
1560
+ instance.fastForward?.(10);
1561
+ }
1562
+ },
1563
+ {
1564
+ id: "speed",
1565
+ icon: "speed",
1566
+ label: "Playback Speed",
1567
+ type: "button",
1568
+ group: "actions",
1569
+ execute: () => {
1570
+ instance.cycleSpeed?.();
1571
+ }
1488
1572
  }
1489
1573
  );
1490
1574
  }
@@ -1607,6 +1691,28 @@ var MediaPlugin = class {
1607
1691
  play: mediaElement ? () => mediaElement?.play() : void 0,
1608
1692
  pause: mediaElement ? () => mediaElement?.pause() : void 0,
1609
1693
  isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1694
+ fastForward: mediaElement ? (seconds = 10) => {
1695
+ if (mediaElement) {
1696
+ mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
1697
+ }
1698
+ } : void 0,
1699
+ rewind: mediaElement ? (seconds = 10) => {
1700
+ if (mediaElement) {
1701
+ mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
1702
+ }
1703
+ } : void 0,
1704
+ cycleSpeed: mediaElement ? () => {
1705
+ if (mediaElement) {
1706
+ const speeds = [1, 1.25, 1.5, 2, 0.5];
1707
+ const curIndex = speeds.indexOf(mediaElement.playbackRate);
1708
+ const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
1709
+ mediaElement.playbackRate = speeds[nextIndex];
1710
+ }
1711
+ } : void 0,
1712
+ setPlaybackRate: mediaElement ? (rate) => {
1713
+ if (mediaElement) mediaElement.playbackRate = rate;
1714
+ } : void 0,
1715
+ getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
1610
1716
  download: () => {
1611
1717
  const a = document.createElement("a");
1612
1718
  a.href = url;
@@ -1636,7 +1742,10 @@ var DocxPlugin = class {
1636
1742
  supports(file) {
1637
1743
  const ext = file.metadata.extension?.toLowerCase();
1638
1744
  const mime = file.metadata.mimeType?.toLowerCase();
1639
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
1745
+ if (ext) {
1746
+ return this.extensions.includes(ext);
1747
+ }
1748
+ return this.mimeTypes.includes(mime || "");
1640
1749
  }
1641
1750
  getToolbarActions(instance) {
1642
1751
  return [
@@ -1688,16 +1797,35 @@ var DocxPlugin = class {
1688
1797
  wrapper.style.transformOrigin = "top center";
1689
1798
  wrapper.style.transition = "transform 0.2s ease";
1690
1799
  wrapper.style.padding = "24px 16px";
1691
- wrapper.style.maxWidth = "900px";
1800
+ wrapper.style.maxWidth = "950px";
1692
1801
  wrapper.style.margin = "0 auto";
1693
1802
  wrapper.style.boxSizing = "border-box";
1694
1803
  ctx.container.style.overflow = "auto";
1695
1804
  ctx.container.style.backgroundColor = "#f1f5f9";
1696
1805
  ctx.container.appendChild(wrapper);
1806
+ const styleOverride = document.createElement("style");
1807
+ styleOverride.textContent = `
1808
+ .fp-docx-wrapper .docx-wrapper {
1809
+ background: transparent !important;
1810
+ padding: 0 !important;
1811
+ display: flex !important;
1812
+ flex-direction: column !important;
1813
+ align-items: center !important;
1814
+ box-sizing: border-box !important;
1815
+ }
1816
+ .fp-docx-wrapper section.docx {
1817
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
1818
+ border-radius: 4px !important;
1819
+ margin-bottom: 24px !important;
1820
+ background-color: #ffffff !important;
1821
+ }
1822
+ `;
1823
+ ctx.container.appendChild(styleOverride);
1697
1824
  let scale = 1;
1698
1825
  let renderedSuccessfully = false;
1826
+ const createdBlobUrls = [];
1699
1827
  try {
1700
- await docx__namespace.renderAsync(ctx.buffer, wrapper, ctx.container, {
1828
+ await docx__namespace.renderAsync(ctx.buffer, wrapper, wrapper, {
1701
1829
  inWrapper: true,
1702
1830
  ignoreWidth: false,
1703
1831
  ignoreHeight: false,
@@ -1706,19 +1834,25 @@ var DocxPlugin = class {
1706
1834
  breakPages: true,
1707
1835
  experimental: true
1708
1836
  });
1837
+ if (!ctx.container.contains(wrapper)) {
1838
+ ctx.container.appendChild(wrapper);
1839
+ }
1709
1840
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
1710
1841
  renderedSuccessfully = true;
1711
1842
  }
1712
1843
  } catch (err) {
1713
- console.warn("[DocxPlugin] docx-preview failed, triggering native XML fallback:", err);
1844
+ console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
1714
1845
  }
1715
1846
  if (!renderedSuccessfully) {
1716
1847
  try {
1717
1848
  wrapper.innerHTML = "";
1718
- this.renderXmlFallback(ctx, wrapper);
1849
+ this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
1850
+ if (!ctx.container.contains(wrapper)) {
1851
+ ctx.container.appendChild(wrapper);
1852
+ }
1719
1853
  renderedSuccessfully = true;
1720
1854
  } catch (fallbackErr) {
1721
- console.error("[DocxPlugin] XML fallback failed:", fallbackErr);
1855
+ console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
1722
1856
  wrapper.innerHTML = `
1723
1857
  <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
1724
1858
  <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
@@ -1726,10 +1860,17 @@ var DocxPlugin = class {
1726
1860
  <p style="color: #64748b; margin: 0;">Could not parse document content</p>
1727
1861
  </div>
1728
1862
  `;
1863
+ if (!ctx.container.contains(wrapper)) {
1864
+ ctx.container.appendChild(wrapper);
1865
+ }
1729
1866
  }
1730
1867
  }
1731
1868
  const cleanup = () => {
1869
+ for (const url of createdBlobUrls) {
1870
+ URL.revokeObjectURL(url);
1871
+ }
1732
1872
  wrapper.remove();
1873
+ styleOverride.remove();
1733
1874
  ctx.container.innerHTML = "";
1734
1875
  };
1735
1876
  ctx.signal.addEventListener("abort", cleanup);
@@ -1766,13 +1907,50 @@ var DocxPlugin = class {
1766
1907
  }
1767
1908
  };
1768
1909
  }
1769
- renderXmlFallback(ctx, wrapper) {
1910
+ renderXmlFallback(ctx, wrapper, createdBlobUrls) {
1911
+ const magic = new Uint8Array(ctx.buffer.slice(0, 8));
1912
+ if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
1913
+ this.renderBinaryDocFallback(ctx, wrapper);
1914
+ return;
1915
+ }
1770
1916
  const unzipped = fflate.unzipSync(new Uint8Array(ctx.buffer));
1771
- const docXmlEntry = unzipped["word/document.xml"];
1917
+ const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
1918
+ const docXmlEntry = docKey ? unzipped[docKey] : void 0;
1772
1919
  if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
1773
1920
  const xmlStr = fflate.strFromU8(docXmlEntry);
1774
1921
  const parser = new DOMParser();
1775
1922
  const doc = parser.parseFromString(xmlStr, "application/xml");
1923
+ const imageMap = {};
1924
+ const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
1925
+ if (relsKey && unzipped[relsKey]) {
1926
+ try {
1927
+ const relsXml = fflate.strFromU8(unzipped[relsKey]);
1928
+ const relsDoc = parser.parseFromString(relsXml, "application/xml");
1929
+ const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
1930
+ for (const rel of relEls) {
1931
+ const rId = rel.getAttribute("Id");
1932
+ const target = rel.getAttribute("Target");
1933
+ if (rId && target) {
1934
+ const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
1935
+ const fullTargetKey = Object.keys(unzipped).find((k) => {
1936
+ const norm = k.replace(/^[./\\]+/, "").toLowerCase();
1937
+ return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
1938
+ });
1939
+ if (fullTargetKey && unzipped[fullTargetKey]) {
1940
+ const bytes = unzipped[fullTargetKey];
1941
+ const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
1942
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
1943
+ const blob = new Blob([bytes], { type: mime });
1944
+ const blobUrl = URL.createObjectURL(blob);
1945
+ imageMap[rId] = blobUrl;
1946
+ createdBlobUrls.push(blobUrl);
1947
+ }
1948
+ }
1949
+ }
1950
+ } catch (relsErr) {
1951
+ console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
1952
+ }
1953
+ }
1776
1954
  const card = document.createElement("div");
1777
1955
  card.className = "fp-docx-page-card";
1778
1956
  card.style.backgroundColor = "#ffffff";
@@ -1790,7 +1968,7 @@ var DocxPlugin = class {
1790
1968
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
1791
1969
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
1792
1970
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
1793
- const textContent = this.extractParagraphHtml(child);
1971
+ const textContent = this.extractParagraphHtml(child, imageMap);
1794
1972
  if (!textContent.trim()) {
1795
1973
  html += '<div style="height: 10px;"></div>';
1796
1974
  continue;
@@ -1816,7 +1994,7 @@ var DocxPlugin = class {
1816
1994
  html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
1817
1995
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
1818
1996
  cells.forEach((tc) => {
1819
- const cellText = this.extractParagraphHtml(tc);
1997
+ const cellText = this.extractParagraphHtml(tc, imageMap);
1820
1998
  html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
1821
1999
  });
1822
2000
  html += "</tr>";
@@ -1824,34 +2002,63 @@ var DocxPlugin = class {
1824
2002
  html += "</table>";
1825
2003
  }
1826
2004
  }
1827
- card.innerHTML = DOMPurify6__default.default.sanitize(html, {
1828
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "img", "div"],
2005
+ card.innerHTML = DOMPurify2__default.default.sanitize(html, {
2006
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
1829
2007
  ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
1830
2008
  });
1831
2009
  wrapper.appendChild(card);
1832
2010
  }
1833
- extractParagraphHtml(pElement) {
2011
+ extractParagraphHtml(pElement, imageMap = {}) {
1834
2012
  let result = "";
2013
+ const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
2014
+ for (const drawing of drawings) {
2015
+ const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
2016
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2017
+ if (rId && imageMap[rId]) {
2018
+ result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2019
+ }
2020
+ }
1835
2021
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
1836
- if (runs.length === 0) {
1837
- return DOMPurify6__default.default.sanitize(pElement.textContent || "");
2022
+ if (runs.length === 0 && !result) {
2023
+ return DOMPurify2__default.default.sanitize(pElement.textContent || "");
1838
2024
  }
1839
2025
  for (const r of runs) {
2026
+ const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
2027
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2028
+ if (rId && imageMap[rId]) {
2029
+ result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2030
+ }
2031
+ const brs = r.getElementsByTagNameNS("*", "br");
2032
+ for (let i = 0; i < brs.length; i++) {
2033
+ result += "<br/>";
2034
+ }
2035
+ const tabs = r.getElementsByTagNameNS("*", "tab");
2036
+ if (tabs.length > 0) {
2037
+ result += "&emsp;";
2038
+ }
1840
2039
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
1841
2040
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
1842
2041
  const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
1843
2042
  const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
2043
+ const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
1844
2044
  const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
1845
2045
  const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
2046
+ const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
2047
+ const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
1846
2048
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
1847
2049
  let text = texts.map((t) => t.textContent || "").join("");
1848
2050
  if (!text) continue;
1849
- text = DOMPurify6__default.default.sanitize(text);
2051
+ text = DOMPurify2__default.default.sanitize(text);
1850
2052
  let styles = "";
1851
2053
  if (isBold) styles += "font-weight: bold; ";
1852
2054
  if (isItalic) styles += "font-style: italic; ";
1853
2055
  if (isUnderline) styles += "text-decoration: underline; ";
2056
+ if (isStrike) styles += "text-decoration: line-through; ";
1854
2057
  if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
2058
+ if (szVal) {
2059
+ const pt = parseInt(szVal, 10) / 2;
2060
+ if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2061
+ }
1855
2062
  if (styles) {
1856
2063
  result += `<span style="${styles}">${text}</span>`;
1857
2064
  } else {
@@ -1860,6 +2067,52 @@ var DocxPlugin = class {
1860
2067
  }
1861
2068
  return result;
1862
2069
  }
2070
+ renderBinaryDocFallback(ctx, wrapper) {
2071
+ const card = document.createElement("div");
2072
+ card.className = "fp-docx-page-card";
2073
+ card.style.backgroundColor = "#ffffff";
2074
+ card.style.borderRadius = "6px";
2075
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2076
+ card.style.padding = "48px 56px";
2077
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2078
+ card.style.color = "#1e293b";
2079
+ card.style.lineHeight = "1.6";
2080
+ try {
2081
+ const cfbf = new CfbfReader(ctx.buffer);
2082
+ const wordDocStream = cfbf.readStream("WordDocument");
2083
+ if (wordDocStream && wordDocStream.length >= 512) {
2084
+ const text2 = this.extractReadableStrings(wordDocStream);
2085
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text2)}</div>`;
2086
+ wrapper.appendChild(card);
2087
+ return;
2088
+ }
2089
+ } catch {
2090
+ }
2091
+ const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2092
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text)}</div>`;
2093
+ wrapper.appendChild(card);
2094
+ }
2095
+ extractReadableStrings(bytes) {
2096
+ let result = "";
2097
+ let current = "";
2098
+ for (let i = 0; i < bytes.length; i++) {
2099
+ const b = bytes[i];
2100
+ if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
2101
+ current += String.fromCharCode(b);
2102
+ } else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
2103
+ continue;
2104
+ } else {
2105
+ if (current.trim().length > 3) {
2106
+ result += current.trim() + "\n\n";
2107
+ }
2108
+ current = "";
2109
+ }
2110
+ }
2111
+ if (current.trim().length > 3) {
2112
+ result += current.trim();
2113
+ }
2114
+ return result;
2115
+ }
1863
2116
  };
1864
2117
  function docxPlugin() {
1865
2118
  return new DocxPlugin();
@@ -1881,7 +2134,10 @@ var ExcelPlugin = class {
1881
2134
  supports(file) {
1882
2135
  const ext = file.metadata.extension?.toLowerCase();
1883
2136
  const mime = file.metadata.mimeType?.toLowerCase();
1884
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
2137
+ if (ext) {
2138
+ return this.extensions.includes(ext);
2139
+ }
2140
+ return this.mimeTypes.includes(mime || "");
1885
2141
  }
1886
2142
  getToolbarActions(instance) {
1887
2143
  return [
@@ -2671,7 +2927,7 @@ var MarkdownPlugin = class {
2671
2927
  gfm: true,
2672
2928
  breaks: true
2673
2929
  });
2674
- const cleanHtml = DOMPurify6__default.default.sanitize(rawHtml, {
2930
+ const cleanHtml = DOMPurify2__default.default.sanitize(rawHtml, {
2675
2931
  USE_PROFILES: { html: true }
2676
2932
  });
2677
2933
  const wrapper = document.createElement("div");
@@ -2823,7 +3079,10 @@ var PptxPlugin = class {
2823
3079
  supports(file) {
2824
3080
  const ext = file.metadata.extension?.toLowerCase();
2825
3081
  const mime = file.metadata.mimeType?.toLowerCase();
2826
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3082
+ if (ext) {
3083
+ return this.extensions.includes(ext);
3084
+ }
3085
+ return this.mimeTypes.includes(mime || "");
2827
3086
  }
2828
3087
  getToolbarActions(instance) {
2829
3088
  return [
@@ -3268,7 +3527,10 @@ var RtfPlugin = class {
3268
3527
  ctx.container.appendChild(wrapper);
3269
3528
  let scale = 1;
3270
3529
  try {
3271
- const doc = new rtf_js.RTFJS.Document(ctx.buffer, {});
3530
+ if (typeof RTFJS__namespace.loggingEnabled === "function") {
3531
+ RTFJS__namespace.loggingEnabled(false);
3532
+ }
3533
+ const doc = new RTFJS__namespace.Document(ctx.buffer, {});
3272
3534
  const htmlElements = await doc.render();
3273
3535
  for (const el of htmlElements) {
3274
3536
  wrapper.appendChild(el);
@@ -3386,7 +3648,7 @@ var HtmlPreviewPlugin = class {
3386
3648
  }
3387
3649
  async render(ctx) {
3388
3650
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3389
- const sanitized = DOMPurify6__default.default.sanitize(rawHtml, {
3651
+ const sanitized = DOMPurify2__default.default.sanitize(rawHtml, {
3390
3652
  WHOLE_DOCUMENT: true,
3391
3653
  ADD_TAGS: ["style", "link"],
3392
3654
  ADD_ATTR: ["target", "rel"]
@@ -3466,7 +3728,10 @@ var OpenDocumentPlugin = class {
3466
3728
  supports(file) {
3467
3729
  const ext = file.metadata.extension?.toLowerCase();
3468
3730
  const mime = file.metadata.mimeType?.toLowerCase();
3469
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3731
+ if (ext) {
3732
+ return this.extensions.includes(ext);
3733
+ }
3734
+ return this.mimeTypes.includes(mime || "");
3470
3735
  }
3471
3736
  getToolbarActions(instance) {
3472
3737
  const isPresentation = instance.isPresentation;
@@ -3629,7 +3894,7 @@ var OpenDocumentPlugin = class {
3629
3894
  wrapper.style.padding = "48px";
3630
3895
  wrapper.style.minHeight = "100%";
3631
3896
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
3632
- wrapper.innerHTML = DOMPurify6__default.default.sanitize(bodyHtml, {
3897
+ wrapper.innerHTML = DOMPurify2__default.default.sanitize(bodyHtml, {
3633
3898
  ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
3634
3899
  ADD_ATTR: ["style", "colspan", "rowspan"]
3635
3900
  });
@@ -3881,7 +4146,10 @@ var DocPlugin = class {
3881
4146
  supports(file) {
3882
4147
  const ext = file.metadata.extension?.toLowerCase();
3883
4148
  const mime = file.metadata.mimeType?.toLowerCase();
3884
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4149
+ if (ext) {
4150
+ return this.extensions.includes(ext);
4151
+ }
4152
+ return this.mimeTypes.includes(mime || "");
3885
4153
  }
3886
4154
  getToolbarActions(instance) {
3887
4155
  return [
@@ -4104,22 +4372,22 @@ var DocPlugin = class {
4104
4372
  * Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
4105
4373
  */
4106
4374
  extractStringsFromBytes(bytes) {
4107
- const chars = [];
4108
- const len = bytes.length;
4109
- for (let i = 0; i < len; i++) {
4110
- const b = bytes[i];
4111
- if (b === 13 || b === 10 || b === 9 || b >= 32 && b <= 126 || b >= 160 && b <= 255) {
4112
- chars.push(String.fromCharCode(b));
4113
- } else if (b === 0 && i + 1 < len && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
4114
- chars.push(String.fromCharCode(bytes[i + 1]));
4115
- i++;
4116
- } else if (b === 7) {
4117
- chars.push(" ");
4118
- } else if (b === 12) {
4119
- chars.push("\n\n---PAGE---\n\n");
4375
+ const rawAnsi = new TextDecoder("latin1").decode(bytes);
4376
+ const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
4377
+ const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4378
+ const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4379
+ const candidateLines = [];
4380
+ const seen = /* @__PURE__ */ new Set();
4381
+ for (const run of [...ansiRuns, ...utf16Runs]) {
4382
+ const trimmed = run.trim();
4383
+ if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
4384
+ 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)) {
4385
+ seen.add(trimmed);
4386
+ candidateLines.push(trimmed);
4387
+ }
4120
4388
  }
4121
4389
  }
4122
- return chars.join("");
4390
+ return candidateLines.join("\n\n");
4123
4391
  }
4124
4392
  heuristicTextExtraction(buffer) {
4125
4393
  return this.extractStringsFromBytes(new Uint8Array(buffer));
@@ -4149,24 +4417,24 @@ var DocPlugin = class {
4149
4417
  html += "</ul>";
4150
4418
  inList = false;
4151
4419
  }
4152
- 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>`;
4420
+ 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>`;
4153
4421
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4154
4422
  if (!inList) {
4155
4423
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4156
4424
  inList = true;
4157
4425
  }
4158
4426
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4159
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify6__default.default.sanitize(bulletText)}</li>`;
4427
+ html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2__default.default.sanitize(bulletText)}</li>`;
4160
4428
  } else {
4161
4429
  if (inList) {
4162
4430
  html += "</ul>";
4163
4431
  inList = false;
4164
4432
  }
4165
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify6__default.default.sanitize(line)}</p>`;
4433
+ html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2__default.default.sanitize(line)}</p>`;
4166
4434
  }
4167
4435
  }
4168
4436
  if (inList) html += "</ul>";
4169
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6__default.default.sanitize(filename)})</p>`;
4437
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2__default.default.sanitize(filename)})</p>`;
4170
4438
  }
4171
4439
  };
4172
4440
  function docPlugin() {
@@ -4174,14 +4442,17 @@ function docPlugin() {
4174
4442
  }
4175
4443
  var PptPlugin = class {
4176
4444
  id = "ppt";
4177
- name = "Legacy PowerPoint Presentation (.ppt, .pps, .pot)";
4445
+ name = "PowerPoint Presentation (.ppt, .pps, .pot)";
4178
4446
  extensions = [".ppt", ".pps", ".pot"];
4179
4447
  mimeTypes = ["application/vnd.ms-powerpoint"];
4180
- weight = 75;
4448
+ weight = 85;
4181
4449
  supports(file) {
4182
4450
  const ext = file.metadata.extension?.toLowerCase();
4183
4451
  const mime = file.metadata.mimeType?.toLowerCase();
4184
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4452
+ if (ext) {
4453
+ return this.extensions.includes(ext);
4454
+ }
4455
+ return this.mimeTypes.includes(mime || "");
4185
4456
  }
4186
4457
  getToolbarActions(instance) {
4187
4458
  return [
@@ -4269,19 +4540,15 @@ var PptPlugin = class {
4269
4540
  container.style.alignItems = "center";
4270
4541
  container.style.padding = "32px 16px";
4271
4542
  container.style.backgroundColor = "#0f172a";
4543
+ container.style.boxSizing = "border-box";
4272
4544
  const slideCard = document.createElement("div");
4273
4545
  slideCard.className = "fp-ppt-slide-card";
4274
4546
  slideCard.style.width = "960px";
4275
- slideCard.style.maxWidth = "90%";
4547
+ slideCard.style.maxWidth = "92%";
4276
4548
  slideCard.style.aspectRatio = "16 / 9";
4277
4549
  slideCard.style.backgroundColor = "#ffffff";
4278
- slideCard.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
4550
+ slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4279
4551
  slideCard.style.borderRadius = "8px";
4280
- slideCard.style.padding = "48px";
4281
- slideCard.style.display = "flex";
4282
- slideCard.style.flexDirection = "column";
4283
- slideCard.style.justifyContent = "center";
4284
- slideCard.style.alignItems = "center";
4285
4552
  slideCard.style.boxSizing = "border-box";
4286
4553
  slideCard.style.position = "relative";
4287
4554
  slideCard.style.overflow = "hidden";
@@ -4292,21 +4559,25 @@ var PptPlugin = class {
4292
4559
  let scale = 1;
4293
4560
  let currentSlide = 1;
4294
4561
  let slides = [];
4562
+ const createdBlobUrls = [];
4295
4563
  try {
4296
4564
  const cfbf = new CfbfReader(ctx.buffer);
4297
4565
  const pptStream = cfbf.readStream("PowerPoint Document");
4298
4566
  if (!pptStream || pptStream.length < 512) {
4299
4567
  throw new Error("PowerPoint Document stream not found in CFBF container");
4300
4568
  }
4301
- slides = this.extractSlides(pptStream);
4569
+ const pictures = this.extractPictures(cfbf, createdBlobUrls);
4570
+ slides = this.extractSlides(pptStream, pictures);
4302
4571
  } catch (err) {
4303
4572
  console.warn("[PptPlugin] Error extracting binary slides:", err);
4304
4573
  }
4305
4574
  if (slides.length === 0) {
4306
4575
  slides = [
4307
4576
  {
4577
+ slideIndex: 1,
4308
4578
  title: ctx.metadata.name || "PowerPoint Presentation",
4309
- texts: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"]
4579
+ paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
4580
+ tableColumns: []
4310
4581
  }
4311
4582
  ];
4312
4583
  }
@@ -4315,23 +4586,73 @@ var PptPlugin = class {
4315
4586
  currentSlide = idx;
4316
4587
  const s = slides[idx - 1];
4317
4588
  if (!s) return;
4589
+ let contentHtml = "";
4590
+ if (s.pictureUrl) {
4591
+ contentHtml = `
4592
+ <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
4593
+ <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;" />
4594
+ </div>
4595
+ `;
4596
+ } else if (s.tableColumns.length > 0) {
4597
+ const cols = s.tableColumns;
4598
+ contentHtml = `
4599
+ <div style="flex: 1; overflow: auto; padding: 8px 0;">
4600
+ <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
4601
+ <thead>
4602
+ <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
4603
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2__default.default.sanitize(c)}</th>`).join("")}
4604
+ </tr>
4605
+ </thead>
4606
+ <tbody>
4607
+ ${[1, 2, 3, 4, 5].map((rowIdx) => `
4608
+ <tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
4609
+ ${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
4610
+ </tr>
4611
+ `).join("")}
4612
+ </tbody>
4613
+ </table>
4614
+ </div>
4615
+ `;
4616
+ } else {
4617
+ const pTags = s.paragraphs.map((p) => {
4618
+ const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
4619
+ 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("");
4620
+ }).join("");
4621
+ contentHtml = `
4622
+ <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
4623
+ ${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
4624
+ </div>
4625
+ `;
4626
+ }
4318
4627
  slideCard.innerHTML = `
4319
- <div style="position: absolute; top: 20px; right: 24px; font-size: 12px; color: #94a3b8; font-weight: 600;">
4320
- Slide ${idx} of ${totalSlides}
4321
- </div>
4322
- <div style="text-align: center; width: 100%;">
4323
- <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4324
- ${DOMPurify6__default.default.sanitize(s.title || `Slide ${idx}`)}
4325
- </h1>
4326
- <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4327
- ${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("")}
4628
+ <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;">
4629
+ <!-- Header Banner matching PowerPoint design -->
4630
+ <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;">
4631
+ <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
4632
+ ${DOMPurify2__default.default.sanitize(s.title)}
4633
+ </h1>
4634
+ ${s.subtitle ? `
4635
+ <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);">
4636
+ ${DOMPurify2__default.default.sanitize(s.subtitle)}
4637
+ </span>
4638
+ ` : `
4639
+ <span style="font-size: 12px; color: #365314; font-weight: 600;">
4640
+ Slide ${idx} / ${totalSlides}
4641
+ </span>
4642
+ `}
4328
4643
  </div>
4644
+
4645
+ <!-- Slide Content -->
4646
+ ${contentHtml}
4329
4647
  </div>
4330
4648
  `;
4331
4649
  ctx.emit("page-change", { page: currentSlide, total: totalSlides });
4332
4650
  };
4333
4651
  renderSlide(1);
4334
4652
  const cleanup = () => {
4653
+ for (const u of createdBlobUrls) {
4654
+ URL.revokeObjectURL(u);
4655
+ }
4335
4656
  container.remove();
4336
4657
  ctx.container.innerHTML = "";
4337
4658
  };
@@ -4371,13 +4692,48 @@ var PptPlugin = class {
4371
4692
  if (!ctx2d) return;
4372
4693
  canvas.width = 160;
4373
4694
  canvas.height = 90;
4374
- ctx2d.fillStyle = "#ffffff";
4695
+ ctx2d.fillStyle = "#334155";
4375
4696
  ctx2d.fillRect(0, 0, 160, 90);
4376
- ctx2d.fillStyle = "#1e3a8a";
4377
- ctx2d.font = "bold 11px sans-serif";
4378
- ctx2d.textAlign = "center";
4379
- const title = s.title.slice(0, 18) || `Slide ${idx + 1}`;
4380
- ctx2d.fillText(title, 80, 50);
4697
+ ctx2d.fillStyle = "#ffffff";
4698
+ ctx2d.fillRect(3, 3, 154, 84);
4699
+ ctx2d.fillStyle = "#84cc16";
4700
+ ctx2d.fillRect(6, 6, 148, 18);
4701
+ ctx2d.fillStyle = "#1e293b";
4702
+ ctx2d.font = "bold 9px sans-serif";
4703
+ ctx2d.textAlign = "left";
4704
+ const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
4705
+ ctx2d.fillText(displayTitle, 10, 19);
4706
+ if (s.subtitle) {
4707
+ ctx2d.fillStyle = "#38bdf8";
4708
+ ctx2d.fillRect(116, 9, 34, 12);
4709
+ ctx2d.fillStyle = "#ffffff";
4710
+ ctx2d.font = "bold 7px sans-serif";
4711
+ ctx2d.textAlign = "center";
4712
+ ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
4713
+ }
4714
+ if (s.pictureUrl) {
4715
+ ctx2d.fillStyle = "#3b82f6";
4716
+ ctx2d.fillRect(52, 34, 56, 42);
4717
+ ctx2d.fillStyle = "#ffffff";
4718
+ ctx2d.font = "8px sans-serif";
4719
+ ctx2d.textAlign = "center";
4720
+ ctx2d.fillText("Chart", 80, 58);
4721
+ } else if (s.tableColumns.length > 0) {
4722
+ ctx2d.strokeStyle = "#cbd5e1";
4723
+ ctx2d.lineWidth = 1;
4724
+ ctx2d.strokeRect(14, 32, 132, 46);
4725
+ for (let l = 1; l <= 3; l++) {
4726
+ ctx2d.beginPath();
4727
+ ctx2d.moveTo(14, 32 + l * 11);
4728
+ ctx2d.lineTo(146, 32 + l * 11);
4729
+ ctx2d.stroke();
4730
+ }
4731
+ } else {
4732
+ ctx2d.fillStyle = "#94a3b8";
4733
+ for (let l = 0; l < 4; l++) {
4734
+ ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
4735
+ }
4736
+ }
4381
4737
  }
4382
4738
  }));
4383
4739
  },
@@ -4395,66 +4751,165 @@ var PptPlugin = class {
4395
4751
  }
4396
4752
  };
4397
4753
  }
4754
+ /**
4755
+ * Extract PNG and JPEG images from the Pictures stream
4756
+ */
4757
+ extractPictures(cfbf, createdUrls) {
4758
+ const urls = [];
4759
+ try {
4760
+ const picStream = cfbf.readStream("Pictures");
4761
+ if (!picStream || picStream.length < 32) return urls;
4762
+ const pBuf = new Uint8Array(picStream);
4763
+ const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
4764
+ const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
4765
+ for (let i = 0; i <= pBuf.length - 8; i++) {
4766
+ let match = true;
4767
+ for (let j = 0; j < 8; j++) {
4768
+ if (pBuf[i + j] !== pngSig[j]) {
4769
+ match = false;
4770
+ break;
4771
+ }
4772
+ }
4773
+ if (match) {
4774
+ let endIdx = -1;
4775
+ for (let k = i + 8; k <= pBuf.length - 8; k++) {
4776
+ let endMatch = true;
4777
+ for (let j = 0; j < 8; j++) {
4778
+ if (pBuf[k + j] !== iendSig[j]) {
4779
+ endMatch = false;
4780
+ break;
4781
+ }
4782
+ }
4783
+ if (endMatch) {
4784
+ endIdx = k + 8;
4785
+ break;
4786
+ }
4787
+ }
4788
+ if (endIdx !== -1) {
4789
+ const pngBytes = pBuf.subarray(i, endIdx);
4790
+ const blob = new Blob([pngBytes], { type: "image/png" });
4791
+ const url = URL.createObjectURL(blob);
4792
+ urls.push(url);
4793
+ createdUrls.push(url);
4794
+ i = endIdx;
4795
+ }
4796
+ }
4797
+ }
4798
+ for (let i = 0; i <= pBuf.length - 3; i++) {
4799
+ if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
4800
+ let endIdx = -1;
4801
+ for (let k = i + 3; k < pBuf.length - 1; k++) {
4802
+ if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
4803
+ endIdx = k + 2;
4804
+ break;
4805
+ }
4806
+ }
4807
+ if (endIdx !== -1) {
4808
+ const jpgBytes = pBuf.subarray(i, endIdx);
4809
+ const blob = new Blob([jpgBytes], { type: "image/jpeg" });
4810
+ const url = URL.createObjectURL(blob);
4811
+ urls.push(url);
4812
+ createdUrls.push(url);
4813
+ i = endIdx;
4814
+ }
4815
+ }
4816
+ }
4817
+ } catch (err) {
4818
+ console.warn("[PptPlugin] Error extracting pictures:", err);
4819
+ }
4820
+ return urls;
4821
+ }
4398
4822
  /**
4399
4823
  * Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
4400
4824
  */
4401
- extractSlides(stream) {
4825
+ extractSlides(stream, pictures) {
4402
4826
  const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
4403
4827
  const len = stream.length;
4404
4828
  let offset = 0;
4405
4829
  const slides = [];
4406
- let currentSlideTexts = [];
4830
+ let picIdx = 0;
4407
4831
  while (offset + 8 <= len) {
4408
4832
  const recVerInst = view.getUint16(offset, true);
4409
4833
  const recType = view.getUint16(offset + 2, true);
4410
4834
  const recLen = view.getUint32(offset + 4, true);
4835
+ const isContainer = (recVerInst & 15) === 15;
4411
4836
  if (recType === 1006) {
4412
- if (currentSlideTexts.length > 0) {
4413
- const title = currentSlideTexts[0] || "Slide";
4414
- const texts = currentSlideTexts.slice(1);
4415
- slides.push({ title, texts });
4416
- currentSlideTexts = [];
4837
+ const slideEnd = Math.min(len, offset + 8 + recLen);
4838
+ const rawTexts = [];
4839
+ let hasOle = false;
4840
+ let sOff = offset + 8;
4841
+ while (sOff + 8 <= slideEnd) {
4842
+ const cVerInst = view.getUint16(sOff, true);
4843
+ const cType = view.getUint16(sOff + 2, true);
4844
+ const cLen = view.getUint32(sOff + 4, true);
4845
+ const cIsContainer = (cVerInst & 15) === 15;
4846
+ if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4847
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4848
+ const txt = new TextDecoder("latin1").decode(bytes).trim();
4849
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4850
+ rawTexts.push(txt);
4851
+ }
4852
+ } else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4853
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4854
+ const txt = new TextDecoder("utf-16le").decode(bytes).trim();
4855
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4856
+ rawTexts.push(txt);
4857
+ }
4858
+ } else if (cType === 3009 || cType === 3011) {
4859
+ hasOle = true;
4860
+ }
4861
+ if (cIsContainer) sOff += 8;
4862
+ else sOff += 8 + cLen;
4417
4863
  }
4418
- offset += 8;
4419
- continue;
4420
- }
4421
- if (recType === 3998 && recLen > 0 && offset + 8 + recLen <= len) {
4422
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4423
- const text = new TextDecoder("latin1").decode(bytes).trim();
4424
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4425
- currentSlideTexts.push(text);
4864
+ let title = "";
4865
+ let subtitle = "";
4866
+ const paragraphs = [];
4867
+ const tableColumns = [];
4868
+ for (const t of rawTexts) {
4869
+ if (!title && t.length < 60 && !t.includes("\n")) {
4870
+ title = t;
4871
+ } else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
4872
+ tableColumns.push(t);
4873
+ } else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
4874
+ subtitle = t;
4875
+ } else {
4876
+ paragraphs.push(t);
4877
+ }
4426
4878
  }
4427
- }
4428
- if (recType === 3999 && recLen > 0 && offset + 8 + recLen <= len) {
4429
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4430
- const text = new TextDecoder("utf-16le").decode(bytes).trim();
4431
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4432
- currentSlideTexts.push(text);
4879
+ let pictureUrl = null;
4880
+ if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
4881
+ pictureUrl = pictures[picIdx++];
4433
4882
  }
4883
+ slides.push({
4884
+ slideIndex: slides.length + 1,
4885
+ title: title || `Slide ${slides.length + 1}`,
4886
+ subtitle,
4887
+ paragraphs,
4888
+ tableColumns,
4889
+ pictureUrl,
4890
+ hasOle
4891
+ });
4434
4892
  }
4435
- const isContainer = (recVerInst & 15) === 15;
4436
- if (isContainer) {
4437
- offset += 8;
4438
- } else {
4439
- offset += 8 + recLen;
4440
- }
4441
- }
4442
- if (currentSlideTexts.length > 0) {
4443
- const title = currentSlideTexts[0] || "Slide";
4444
- const texts = currentSlideTexts.slice(1);
4445
- slides.push({ title, texts });
4893
+ if (isContainer) offset += 8;
4894
+ else offset += 8 + recLen;
4446
4895
  }
4447
4896
  if (slides.length === 0) {
4448
4897
  const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
4449
- const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{4,}/g) || [];
4450
- const filtered = matches.map((m) => m.trim()).filter((m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times"));
4451
- if (filtered.length > 0) {
4452
- const chunkSize = 4;
4453
- for (let i = 0; i < filtered.length; i += chunkSize) {
4454
- const chunk = filtered.slice(i, i + chunkSize);
4898
+ const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
4899
+ const clean = matches.map((m) => m.trim()).filter(
4900
+ (m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
4901
+ );
4902
+ if (clean.length > 0) {
4903
+ const chunkSize = 3;
4904
+ for (let i = 0; i < clean.length; i += chunkSize) {
4905
+ const chunk = clean.slice(i, i + chunkSize);
4455
4906
  slides.push({
4907
+ slideIndex: slides.length + 1,
4456
4908
  title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
4457
- texts: chunk.slice(1)
4909
+ subtitle: chunk.length > 2 ? chunk[1] : void 0,
4910
+ paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
4911
+ tableColumns: [],
4912
+ pictureUrl: pictures[slides.length] || null
4458
4913
  });
4459
4914
  }
4460
4915
  }