@files-preview-app/preview-file 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var DOMPurify6 = require('dompurify');
3
+ var DOMPurify2 = require('dompurify');
4
4
  var docx = require('docx-preview');
5
5
  var fflate = require('fflate');
6
6
  var XLSX = require('xlsx');
@@ -11,7 +11,7 @@ var THREE = require('three');
11
11
  var STLLoader_js = require('three/examples/jsm/loaders/STLLoader.js');
12
12
  var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
13
13
  var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
14
- var rtf_js = require('rtf.js');
14
+ var RTFJS = require('rtf.js/dist/RTFJS.bundle.js');
15
15
 
16
16
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
17
 
@@ -33,11 +33,12 @@ function _interopNamespace(e) {
33
33
  return Object.freeze(n);
34
34
  }
35
35
 
36
- var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
36
+ var DOMPurify2__default = /*#__PURE__*/_interopDefault(DOMPurify2);
37
37
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
38
38
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
39
39
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
40
40
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
41
+ var RTFJS__namespace = /*#__PURE__*/_interopNamespace(RTFJS);
41
42
 
42
43
  // ../core/dist/index.js
43
44
  var EventEmitter = class {
@@ -293,6 +294,39 @@ function detectOoxmlType(buffer) {
293
294
  }
294
295
  return "application/zip";
295
296
  }
297
+ function detectCfbfType(buffer) {
298
+ const bytes = new Uint8Array(buffer);
299
+ const hasUtf16le = (str) => {
300
+ const target = new Uint8Array(str.length * 2);
301
+ for (let i = 0; i < str.length; i++) {
302
+ target[i * 2] = str.charCodeAt(i);
303
+ target[i * 2 + 1] = 0;
304
+ }
305
+ const targetLen = target.length;
306
+ const max = bytes.length - targetLen;
307
+ for (let i = 0; i <= max; i++) {
308
+ let match = true;
309
+ for (let j = 0; j < targetLen; j++) {
310
+ if (bytes[i + j] !== target[j]) {
311
+ match = false;
312
+ break;
313
+ }
314
+ }
315
+ if (match) return true;
316
+ }
317
+ return false;
318
+ };
319
+ if (hasUtf16le("PowerPoint Document")) {
320
+ return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
321
+ }
322
+ if (hasUtf16le("WordDocument")) {
323
+ return { mime: "application/msword", extension: ".doc" };
324
+ }
325
+ if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
326
+ return { mime: "application/vnd.ms-excel", extension: ".xls" };
327
+ }
328
+ return null;
329
+ }
296
330
  function extractExtension(nameOrUrl) {
297
331
  try {
298
332
  const url = new URL(nameOrUrl);
@@ -357,6 +391,18 @@ async function sourceToArrayBuffer(source, signal) {
357
391
  } else {
358
392
  metadata.mimeType = metadata.mimeType ?? magicMime;
359
393
  }
394
+ } else if (magicMime === "application/x-cfbf") {
395
+ if (metadata.extension) {
396
+ metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
397
+ } else {
398
+ const cfbf = detectCfbfType(buffer);
399
+ if (cfbf) {
400
+ metadata.mimeType = cfbf.mime;
401
+ metadata.extension = cfbf.extension;
402
+ } else {
403
+ metadata.mimeType = magicMime;
404
+ }
405
+ }
360
406
  } else {
361
407
  metadata.mimeType = magicMime;
362
408
  }
@@ -367,12 +413,12 @@ async function sourceToArrayBuffer(source, signal) {
367
413
  return { buffer, metadata };
368
414
  }
369
415
  function sanitizeHTML(html) {
370
- return DOMPurify6__default.default.sanitize(html, {
416
+ return DOMPurify2__default.default.sanitize(html, {
371
417
  USE_PROFILES: { html: true }
372
418
  });
373
419
  }
374
420
  function sanitizeSVG(svg) {
375
- return DOMPurify6__default.default.sanitize(svg, {
421
+ return DOMPurify2__default.default.sanitize(svg, {
376
422
  USE_PROFILES: { svg: true, svgFilters: true }
377
423
  });
378
424
  }
@@ -471,6 +517,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
471
517
  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>`;
472
518
  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>`;
473
519
  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>`;
520
+ 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>`;
521
+ 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>`;
522
+ 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>`;
474
523
  var ICON_MAP = {
475
524
  "zoom-in": ICON_ZOOM_IN,
476
525
  "zoom-out": ICON_ZOOM_OUT,
@@ -491,7 +540,12 @@ var ICON_MAP = {
491
540
  "page-prev": ICON_PAGE_PREV,
492
541
  "page-next": ICON_PAGE_NEXT,
493
542
  "chevron-left": ICON_PAGE_PREV,
494
- "chevron-right": ICON_PAGE_NEXT
543
+ "chevron-right": ICON_PAGE_NEXT,
544
+ "fast-forward": ICON_FAST_FORWARD,
545
+ "forward-10": ICON_FAST_FORWARD,
546
+ "rewind": ICON_REWIND,
547
+ "replay-10": ICON_REWIND,
548
+ "speed": ICON_SPEED
495
549
  };
496
550
  var ToolbarController = class {
497
551
  el;
@@ -1459,6 +1513,16 @@ var MediaPlugin = class {
1459
1513
  }
1460
1514
  if (instance.play) {
1461
1515
  actions.push(
1516
+ {
1517
+ id: "replay-10",
1518
+ icon: "replay-10",
1519
+ label: "Rewind 10s",
1520
+ type: "button",
1521
+ group: "actions",
1522
+ execute: () => {
1523
+ instance.rewind?.(10);
1524
+ }
1525
+ },
1462
1526
  {
1463
1527
  id: "play",
1464
1528
  icon: "play",
@@ -1478,6 +1542,26 @@ var MediaPlugin = class {
1478
1542
  execute: () => {
1479
1543
  instance.pause?.();
1480
1544
  }
1545
+ },
1546
+ {
1547
+ id: "forward-10",
1548
+ icon: "forward-10",
1549
+ label: "Fast Forward 10s",
1550
+ type: "button",
1551
+ group: "actions",
1552
+ execute: () => {
1553
+ instance.fastForward?.(10);
1554
+ }
1555
+ },
1556
+ {
1557
+ id: "speed",
1558
+ icon: "speed",
1559
+ label: "Playback Speed",
1560
+ type: "button",
1561
+ group: "actions",
1562
+ execute: () => {
1563
+ instance.cycleSpeed?.();
1564
+ }
1481
1565
  }
1482
1566
  );
1483
1567
  }
@@ -1600,6 +1684,28 @@ var MediaPlugin = class {
1600
1684
  play: mediaElement ? () => mediaElement?.play() : void 0,
1601
1685
  pause: mediaElement ? () => mediaElement?.pause() : void 0,
1602
1686
  isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1687
+ fastForward: mediaElement ? (seconds = 10) => {
1688
+ if (mediaElement) {
1689
+ mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
1690
+ }
1691
+ } : void 0,
1692
+ rewind: mediaElement ? (seconds = 10) => {
1693
+ if (mediaElement) {
1694
+ mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
1695
+ }
1696
+ } : void 0,
1697
+ cycleSpeed: mediaElement ? () => {
1698
+ if (mediaElement) {
1699
+ const speeds = [1, 1.25, 1.5, 2, 0.5];
1700
+ const curIndex = speeds.indexOf(mediaElement.playbackRate);
1701
+ const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
1702
+ mediaElement.playbackRate = speeds[nextIndex];
1703
+ }
1704
+ } : void 0,
1705
+ setPlaybackRate: mediaElement ? (rate) => {
1706
+ if (mediaElement) mediaElement.playbackRate = rate;
1707
+ } : void 0,
1708
+ getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
1603
1709
  download: () => {
1604
1710
  const a = document.createElement("a");
1605
1711
  a.href = url;
@@ -1629,7 +1735,10 @@ var DocxPlugin = class {
1629
1735
  supports(file) {
1630
1736
  const ext = file.metadata.extension?.toLowerCase();
1631
1737
  const mime = file.metadata.mimeType?.toLowerCase();
1632
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
1738
+ if (ext) {
1739
+ return this.extensions.includes(ext);
1740
+ }
1741
+ return this.mimeTypes.includes(mime || "");
1633
1742
  }
1634
1743
  getToolbarActions(instance) {
1635
1744
  return [
@@ -1681,16 +1790,35 @@ var DocxPlugin = class {
1681
1790
  wrapper.style.transformOrigin = "top center";
1682
1791
  wrapper.style.transition = "transform 0.2s ease";
1683
1792
  wrapper.style.padding = "24px 16px";
1684
- wrapper.style.maxWidth = "900px";
1793
+ wrapper.style.maxWidth = "950px";
1685
1794
  wrapper.style.margin = "0 auto";
1686
1795
  wrapper.style.boxSizing = "border-box";
1687
1796
  ctx.container.style.overflow = "auto";
1688
1797
  ctx.container.style.backgroundColor = "#f1f5f9";
1689
1798
  ctx.container.appendChild(wrapper);
1799
+ const styleOverride = document.createElement("style");
1800
+ styleOverride.textContent = `
1801
+ .fp-docx-wrapper .docx-wrapper {
1802
+ background: transparent !important;
1803
+ padding: 0 !important;
1804
+ display: flex !important;
1805
+ flex-direction: column !important;
1806
+ align-items: center !important;
1807
+ box-sizing: border-box !important;
1808
+ }
1809
+ .fp-docx-wrapper section.docx {
1810
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
1811
+ border-radius: 4px !important;
1812
+ margin-bottom: 24px !important;
1813
+ background-color: #ffffff !important;
1814
+ }
1815
+ `;
1816
+ ctx.container.appendChild(styleOverride);
1690
1817
  let scale = 1;
1691
1818
  let renderedSuccessfully = false;
1819
+ const createdBlobUrls = [];
1692
1820
  try {
1693
- await docx__namespace.renderAsync(ctx.buffer, wrapper, ctx.container, {
1821
+ await docx__namespace.renderAsync(ctx.buffer, wrapper, wrapper, {
1694
1822
  inWrapper: true,
1695
1823
  ignoreWidth: false,
1696
1824
  ignoreHeight: false,
@@ -1699,19 +1827,25 @@ var DocxPlugin = class {
1699
1827
  breakPages: true,
1700
1828
  experimental: true
1701
1829
  });
1830
+ if (!ctx.container.contains(wrapper)) {
1831
+ ctx.container.appendChild(wrapper);
1832
+ }
1702
1833
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
1703
1834
  renderedSuccessfully = true;
1704
1835
  }
1705
1836
  } catch (err) {
1706
- console.warn("[DocxPlugin] docx-preview failed, triggering native XML fallback:", err);
1837
+ console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
1707
1838
  }
1708
1839
  if (!renderedSuccessfully) {
1709
1840
  try {
1710
1841
  wrapper.innerHTML = "";
1711
- this.renderXmlFallback(ctx, wrapper);
1842
+ this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
1843
+ if (!ctx.container.contains(wrapper)) {
1844
+ ctx.container.appendChild(wrapper);
1845
+ }
1712
1846
  renderedSuccessfully = true;
1713
1847
  } catch (fallbackErr) {
1714
- console.error("[DocxPlugin] XML fallback failed:", fallbackErr);
1848
+ console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
1715
1849
  wrapper.innerHTML = `
1716
1850
  <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
1717
1851
  <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
@@ -1719,10 +1853,17 @@ var DocxPlugin = class {
1719
1853
  <p style="color: #64748b; margin: 0;">Could not parse document content</p>
1720
1854
  </div>
1721
1855
  `;
1856
+ if (!ctx.container.contains(wrapper)) {
1857
+ ctx.container.appendChild(wrapper);
1858
+ }
1722
1859
  }
1723
1860
  }
1724
1861
  const cleanup = () => {
1862
+ for (const url of createdBlobUrls) {
1863
+ URL.revokeObjectURL(url);
1864
+ }
1725
1865
  wrapper.remove();
1866
+ styleOverride.remove();
1726
1867
  ctx.container.innerHTML = "";
1727
1868
  };
1728
1869
  ctx.signal.addEventListener("abort", cleanup);
@@ -1759,13 +1900,50 @@ var DocxPlugin = class {
1759
1900
  }
1760
1901
  };
1761
1902
  }
1762
- renderXmlFallback(ctx, wrapper) {
1903
+ renderXmlFallback(ctx, wrapper, createdBlobUrls) {
1904
+ const magic = new Uint8Array(ctx.buffer.slice(0, 8));
1905
+ if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
1906
+ this.renderBinaryDocFallback(ctx, wrapper);
1907
+ return;
1908
+ }
1763
1909
  const unzipped = fflate.unzipSync(new Uint8Array(ctx.buffer));
1764
- const docXmlEntry = unzipped["word/document.xml"];
1910
+ const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
1911
+ const docXmlEntry = docKey ? unzipped[docKey] : void 0;
1765
1912
  if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
1766
1913
  const xmlStr = fflate.strFromU8(docXmlEntry);
1767
1914
  const parser = new DOMParser();
1768
1915
  const doc = parser.parseFromString(xmlStr, "application/xml");
1916
+ const imageMap = {};
1917
+ const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
1918
+ if (relsKey && unzipped[relsKey]) {
1919
+ try {
1920
+ const relsXml = fflate.strFromU8(unzipped[relsKey]);
1921
+ const relsDoc = parser.parseFromString(relsXml, "application/xml");
1922
+ const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
1923
+ for (const rel of relEls) {
1924
+ const rId = rel.getAttribute("Id");
1925
+ const target = rel.getAttribute("Target");
1926
+ if (rId && target) {
1927
+ const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
1928
+ const fullTargetKey = Object.keys(unzipped).find((k) => {
1929
+ const norm = k.replace(/^[./\\]+/, "").toLowerCase();
1930
+ return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
1931
+ });
1932
+ if (fullTargetKey && unzipped[fullTargetKey]) {
1933
+ const bytes = unzipped[fullTargetKey];
1934
+ const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
1935
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
1936
+ const blob = new Blob([bytes], { type: mime });
1937
+ const blobUrl = URL.createObjectURL(blob);
1938
+ imageMap[rId] = blobUrl;
1939
+ createdBlobUrls.push(blobUrl);
1940
+ }
1941
+ }
1942
+ }
1943
+ } catch (relsErr) {
1944
+ console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
1945
+ }
1946
+ }
1769
1947
  const card = document.createElement("div");
1770
1948
  card.className = "fp-docx-page-card";
1771
1949
  card.style.backgroundColor = "#ffffff";
@@ -1783,7 +1961,7 @@ var DocxPlugin = class {
1783
1961
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
1784
1962
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
1785
1963
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
1786
- const textContent = this.extractParagraphHtml(child);
1964
+ const textContent = this.extractParagraphHtml(child, imageMap);
1787
1965
  if (!textContent.trim()) {
1788
1966
  html += '<div style="height: 10px;"></div>';
1789
1967
  continue;
@@ -1809,7 +1987,7 @@ var DocxPlugin = class {
1809
1987
  html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
1810
1988
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
1811
1989
  cells.forEach((tc) => {
1812
- const cellText = this.extractParagraphHtml(tc);
1990
+ const cellText = this.extractParagraphHtml(tc, imageMap);
1813
1991
  html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
1814
1992
  });
1815
1993
  html += "</tr>";
@@ -1817,34 +1995,63 @@ var DocxPlugin = class {
1817
1995
  html += "</table>";
1818
1996
  }
1819
1997
  }
1820
- card.innerHTML = DOMPurify6__default.default.sanitize(html, {
1821
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "img", "div"],
1998
+ card.innerHTML = DOMPurify2__default.default.sanitize(html, {
1999
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
1822
2000
  ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
1823
2001
  });
1824
2002
  wrapper.appendChild(card);
1825
2003
  }
1826
- extractParagraphHtml(pElement) {
2004
+ extractParagraphHtml(pElement, imageMap = {}) {
1827
2005
  let result = "";
2006
+ const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
2007
+ for (const drawing of drawings) {
2008
+ const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
2009
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2010
+ if (rId && imageMap[rId]) {
2011
+ result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
2012
+ }
2013
+ }
1828
2014
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
1829
- if (runs.length === 0) {
1830
- return DOMPurify6__default.default.sanitize(pElement.textContent || "");
2015
+ if (runs.length === 0 && !result) {
2016
+ return DOMPurify2__default.default.sanitize(pElement.textContent || "");
1831
2017
  }
1832
2018
  for (const r of runs) {
2019
+ const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
2020
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
2021
+ if (rId && imageMap[rId]) {
2022
+ result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
2023
+ }
2024
+ const brs = r.getElementsByTagNameNS("*", "br");
2025
+ for (let i = 0; i < brs.length; i++) {
2026
+ result += "<br/>";
2027
+ }
2028
+ const tabs = r.getElementsByTagNameNS("*", "tab");
2029
+ if (tabs.length > 0) {
2030
+ result += "&emsp;";
2031
+ }
1833
2032
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
1834
2033
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
1835
2034
  const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
1836
2035
  const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
2036
+ const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
1837
2037
  const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
1838
2038
  const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
2039
+ const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
2040
+ const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
1839
2041
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
1840
2042
  let text = texts.map((t) => t.textContent || "").join("");
1841
2043
  if (!text) continue;
1842
- text = DOMPurify6__default.default.sanitize(text);
2044
+ text = DOMPurify2__default.default.sanitize(text);
1843
2045
  let styles = "";
1844
2046
  if (isBold) styles += "font-weight: bold; ";
1845
2047
  if (isItalic) styles += "font-style: italic; ";
1846
2048
  if (isUnderline) styles += "text-decoration: underline; ";
2049
+ if (isStrike) styles += "text-decoration: line-through; ";
1847
2050
  if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
2051
+ if (szVal) {
2052
+ const pt = parseInt(szVal, 10) / 2;
2053
+ if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2054
+ }
1848
2055
  if (styles) {
1849
2056
  result += `<span style="${styles}">${text}</span>`;
1850
2057
  } else {
@@ -1853,6 +2060,52 @@ var DocxPlugin = class {
1853
2060
  }
1854
2061
  return result;
1855
2062
  }
2063
+ renderBinaryDocFallback(ctx, wrapper) {
2064
+ const card = document.createElement("div");
2065
+ card.className = "fp-docx-page-card";
2066
+ card.style.backgroundColor = "#ffffff";
2067
+ card.style.borderRadius = "6px";
2068
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2069
+ card.style.padding = "48px 56px";
2070
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2071
+ card.style.color = "#1e293b";
2072
+ card.style.lineHeight = "1.6";
2073
+ try {
2074
+ const cfbf = new CfbfReader(ctx.buffer);
2075
+ const wordDocStream = cfbf.readStream("WordDocument");
2076
+ if (wordDocStream && wordDocStream.length >= 512) {
2077
+ const text2 = this.extractReadableStrings(wordDocStream);
2078
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text2)}</div>`;
2079
+ wrapper.appendChild(card);
2080
+ return;
2081
+ }
2082
+ } catch {
2083
+ }
2084
+ const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2085
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text)}</div>`;
2086
+ wrapper.appendChild(card);
2087
+ }
2088
+ extractReadableStrings(bytes) {
2089
+ let result = "";
2090
+ let current = "";
2091
+ for (let i = 0; i < bytes.length; i++) {
2092
+ const b = bytes[i];
2093
+ if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
2094
+ current += String.fromCharCode(b);
2095
+ } else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
2096
+ continue;
2097
+ } else {
2098
+ if (current.trim().length > 3) {
2099
+ result += current.trim() + "\n\n";
2100
+ }
2101
+ current = "";
2102
+ }
2103
+ }
2104
+ if (current.trim().length > 3) {
2105
+ result += current.trim();
2106
+ }
2107
+ return result;
2108
+ }
1856
2109
  };
1857
2110
  function docxPlugin() {
1858
2111
  return new DocxPlugin();
@@ -1874,7 +2127,10 @@ var ExcelPlugin = class {
1874
2127
  supports(file) {
1875
2128
  const ext = file.metadata.extension?.toLowerCase();
1876
2129
  const mime = file.metadata.mimeType?.toLowerCase();
1877
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
2130
+ if (ext) {
2131
+ return this.extensions.includes(ext);
2132
+ }
2133
+ return this.mimeTypes.includes(mime || "");
1878
2134
  }
1879
2135
  getToolbarActions(instance) {
1880
2136
  return [
@@ -2664,7 +2920,7 @@ var MarkdownPlugin = class {
2664
2920
  gfm: true,
2665
2921
  breaks: true
2666
2922
  });
2667
- const cleanHtml = DOMPurify6__default.default.sanitize(rawHtml, {
2923
+ const cleanHtml = DOMPurify2__default.default.sanitize(rawHtml, {
2668
2924
  USE_PROFILES: { html: true }
2669
2925
  });
2670
2926
  const wrapper = document.createElement("div");
@@ -2816,7 +3072,10 @@ var PptxPlugin = class {
2816
3072
  supports(file) {
2817
3073
  const ext = file.metadata.extension?.toLowerCase();
2818
3074
  const mime = file.metadata.mimeType?.toLowerCase();
2819
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3075
+ if (ext) {
3076
+ return this.extensions.includes(ext);
3077
+ }
3078
+ return this.mimeTypes.includes(mime || "");
2820
3079
  }
2821
3080
  getToolbarActions(instance) {
2822
3081
  return [
@@ -3261,7 +3520,10 @@ var RtfPlugin = class {
3261
3520
  ctx.container.appendChild(wrapper);
3262
3521
  let scale = 1;
3263
3522
  try {
3264
- const doc = new rtf_js.RTFJS.Document(ctx.buffer, {});
3523
+ if (typeof RTFJS__namespace.loggingEnabled === "function") {
3524
+ RTFJS__namespace.loggingEnabled(false);
3525
+ }
3526
+ const doc = new RTFJS__namespace.Document(ctx.buffer, {});
3265
3527
  const htmlElements = await doc.render();
3266
3528
  for (const el of htmlElements) {
3267
3529
  wrapper.appendChild(el);
@@ -3379,7 +3641,7 @@ var HtmlPreviewPlugin = class {
3379
3641
  }
3380
3642
  async render(ctx) {
3381
3643
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3382
- const sanitized = DOMPurify6__default.default.sanitize(rawHtml, {
3644
+ const sanitized = DOMPurify2__default.default.sanitize(rawHtml, {
3383
3645
  WHOLE_DOCUMENT: true,
3384
3646
  ADD_TAGS: ["style", "link"],
3385
3647
  ADD_ATTR: ["target", "rel"]
@@ -3459,7 +3721,10 @@ var OpenDocumentPlugin = class {
3459
3721
  supports(file) {
3460
3722
  const ext = file.metadata.extension?.toLowerCase();
3461
3723
  const mime = file.metadata.mimeType?.toLowerCase();
3462
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3724
+ if (ext) {
3725
+ return this.extensions.includes(ext);
3726
+ }
3727
+ return this.mimeTypes.includes(mime || "");
3463
3728
  }
3464
3729
  getToolbarActions(instance) {
3465
3730
  const isPresentation = instance.isPresentation;
@@ -3622,7 +3887,7 @@ var OpenDocumentPlugin = class {
3622
3887
  wrapper.style.padding = "48px";
3623
3888
  wrapper.style.minHeight = "100%";
3624
3889
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
3625
- wrapper.innerHTML = DOMPurify6__default.default.sanitize(bodyHtml, {
3890
+ wrapper.innerHTML = DOMPurify2__default.default.sanitize(bodyHtml, {
3626
3891
  ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
3627
3892
  ADD_ATTR: ["style", "colspan", "rowspan"]
3628
3893
  });
@@ -3874,7 +4139,10 @@ var DocPlugin = class {
3874
4139
  supports(file) {
3875
4140
  const ext = file.metadata.extension?.toLowerCase();
3876
4141
  const mime = file.metadata.mimeType?.toLowerCase();
3877
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4142
+ if (ext) {
4143
+ return this.extensions.includes(ext);
4144
+ }
4145
+ return this.mimeTypes.includes(mime || "");
3878
4146
  }
3879
4147
  getToolbarActions(instance) {
3880
4148
  return [
@@ -4097,22 +4365,22 @@ var DocPlugin = class {
4097
4365
  * Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
4098
4366
  */
4099
4367
  extractStringsFromBytes(bytes) {
4100
- const chars = [];
4101
- const len = bytes.length;
4102
- for (let i = 0; i < len; i++) {
4103
- const b = bytes[i];
4104
- if (b === 13 || b === 10 || b === 9 || b >= 32 && b <= 126 || b >= 160 && b <= 255) {
4105
- chars.push(String.fromCharCode(b));
4106
- } else if (b === 0 && i + 1 < len && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
4107
- chars.push(String.fromCharCode(bytes[i + 1]));
4108
- i++;
4109
- } else if (b === 7) {
4110
- chars.push(" ");
4111
- } else if (b === 12) {
4112
- chars.push("\n\n---PAGE---\n\n");
4368
+ const rawAnsi = new TextDecoder("latin1").decode(bytes);
4369
+ const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
4370
+ const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4371
+ const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4372
+ const candidateLines = [];
4373
+ const seen = /* @__PURE__ */ new Set();
4374
+ for (const run of [...ansiRuns, ...utf16Runs]) {
4375
+ const trimmed = run.trim();
4376
+ if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
4377
+ 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)) {
4378
+ seen.add(trimmed);
4379
+ candidateLines.push(trimmed);
4380
+ }
4113
4381
  }
4114
4382
  }
4115
- return chars.join("");
4383
+ return candidateLines.join("\n\n");
4116
4384
  }
4117
4385
  heuristicTextExtraction(buffer) {
4118
4386
  return this.extractStringsFromBytes(new Uint8Array(buffer));
@@ -4142,24 +4410,24 @@ var DocPlugin = class {
4142
4410
  html += "</ul>";
4143
4411
  inList = false;
4144
4412
  }
4145
- 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>`;
4413
+ 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>`;
4146
4414
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4147
4415
  if (!inList) {
4148
4416
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4149
4417
  inList = true;
4150
4418
  }
4151
4419
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4152
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify6__default.default.sanitize(bulletText)}</li>`;
4420
+ html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2__default.default.sanitize(bulletText)}</li>`;
4153
4421
  } else {
4154
4422
  if (inList) {
4155
4423
  html += "</ul>";
4156
4424
  inList = false;
4157
4425
  }
4158
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify6__default.default.sanitize(line)}</p>`;
4426
+ html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2__default.default.sanitize(line)}</p>`;
4159
4427
  }
4160
4428
  }
4161
4429
  if (inList) html += "</ul>";
4162
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6__default.default.sanitize(filename)})</p>`;
4430
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2__default.default.sanitize(filename)})</p>`;
4163
4431
  }
4164
4432
  };
4165
4433
  function docPlugin() {
@@ -4167,14 +4435,17 @@ function docPlugin() {
4167
4435
  }
4168
4436
  var PptPlugin = class {
4169
4437
  id = "ppt";
4170
- name = "Legacy PowerPoint Presentation (.ppt, .pps, .pot)";
4438
+ name = "PowerPoint Presentation (.ppt, .pps, .pot)";
4171
4439
  extensions = [".ppt", ".pps", ".pot"];
4172
4440
  mimeTypes = ["application/vnd.ms-powerpoint"];
4173
- weight = 75;
4441
+ weight = 85;
4174
4442
  supports(file) {
4175
4443
  const ext = file.metadata.extension?.toLowerCase();
4176
4444
  const mime = file.metadata.mimeType?.toLowerCase();
4177
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4445
+ if (ext) {
4446
+ return this.extensions.includes(ext);
4447
+ }
4448
+ return this.mimeTypes.includes(mime || "");
4178
4449
  }
4179
4450
  getToolbarActions(instance) {
4180
4451
  return [
@@ -4262,19 +4533,15 @@ var PptPlugin = class {
4262
4533
  container.style.alignItems = "center";
4263
4534
  container.style.padding = "32px 16px";
4264
4535
  container.style.backgroundColor = "#0f172a";
4536
+ container.style.boxSizing = "border-box";
4265
4537
  const slideCard = document.createElement("div");
4266
4538
  slideCard.className = "fp-ppt-slide-card";
4267
4539
  slideCard.style.width = "960px";
4268
- slideCard.style.maxWidth = "90%";
4540
+ slideCard.style.maxWidth = "92%";
4269
4541
  slideCard.style.aspectRatio = "16 / 9";
4270
4542
  slideCard.style.backgroundColor = "#ffffff";
4271
- slideCard.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
4543
+ slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4272
4544
  slideCard.style.borderRadius = "8px";
4273
- slideCard.style.padding = "48px";
4274
- slideCard.style.display = "flex";
4275
- slideCard.style.flexDirection = "column";
4276
- slideCard.style.justifyContent = "center";
4277
- slideCard.style.alignItems = "center";
4278
4545
  slideCard.style.boxSizing = "border-box";
4279
4546
  slideCard.style.position = "relative";
4280
4547
  slideCard.style.overflow = "hidden";
@@ -4285,21 +4552,25 @@ var PptPlugin = class {
4285
4552
  let scale = 1;
4286
4553
  let currentSlide = 1;
4287
4554
  let slides = [];
4555
+ const createdBlobUrls = [];
4288
4556
  try {
4289
4557
  const cfbf = new CfbfReader(ctx.buffer);
4290
4558
  const pptStream = cfbf.readStream("PowerPoint Document");
4291
4559
  if (!pptStream || pptStream.length < 512) {
4292
4560
  throw new Error("PowerPoint Document stream not found in CFBF container");
4293
4561
  }
4294
- slides = this.extractSlides(pptStream);
4562
+ const pictures = this.extractPictures(cfbf, createdBlobUrls);
4563
+ slides = this.extractSlides(pptStream, pictures);
4295
4564
  } catch (err) {
4296
4565
  console.warn("[PptPlugin] Error extracting binary slides:", err);
4297
4566
  }
4298
4567
  if (slides.length === 0) {
4299
4568
  slides = [
4300
4569
  {
4570
+ slideIndex: 1,
4301
4571
  title: ctx.metadata.name || "PowerPoint Presentation",
4302
- texts: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"]
4572
+ paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
4573
+ tableColumns: []
4303
4574
  }
4304
4575
  ];
4305
4576
  }
@@ -4308,23 +4579,73 @@ var PptPlugin = class {
4308
4579
  currentSlide = idx;
4309
4580
  const s = slides[idx - 1];
4310
4581
  if (!s) return;
4582
+ let contentHtml = "";
4583
+ if (s.pictureUrl) {
4584
+ contentHtml = `
4585
+ <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
4586
+ <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;" />
4587
+ </div>
4588
+ `;
4589
+ } else if (s.tableColumns.length > 0) {
4590
+ const cols = s.tableColumns;
4591
+ contentHtml = `
4592
+ <div style="flex: 1; overflow: auto; padding: 8px 0;">
4593
+ <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
4594
+ <thead>
4595
+ <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
4596
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2__default.default.sanitize(c)}</th>`).join("")}
4597
+ </tr>
4598
+ </thead>
4599
+ <tbody>
4600
+ ${[1, 2, 3, 4, 5].map((rowIdx) => `
4601
+ <tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
4602
+ ${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
4603
+ </tr>
4604
+ `).join("")}
4605
+ </tbody>
4606
+ </table>
4607
+ </div>
4608
+ `;
4609
+ } else {
4610
+ const pTags = s.paragraphs.map((p) => {
4611
+ const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
4612
+ 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("");
4613
+ }).join("");
4614
+ contentHtml = `
4615
+ <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
4616
+ ${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
4617
+ </div>
4618
+ `;
4619
+ }
4311
4620
  slideCard.innerHTML = `
4312
- <div style="position: absolute; top: 20px; right: 24px; font-size: 12px; color: #94a3b8; font-weight: 600;">
4313
- Slide ${idx} of ${totalSlides}
4314
- </div>
4315
- <div style="text-align: center; width: 100%;">
4316
- <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4317
- ${DOMPurify6__default.default.sanitize(s.title || `Slide ${idx}`)}
4318
- </h1>
4319
- <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4320
- ${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("")}
4621
+ <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;">
4622
+ <!-- Header Banner matching PowerPoint design -->
4623
+ <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;">
4624
+ <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
4625
+ ${DOMPurify2__default.default.sanitize(s.title)}
4626
+ </h1>
4627
+ ${s.subtitle ? `
4628
+ <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);">
4629
+ ${DOMPurify2__default.default.sanitize(s.subtitle)}
4630
+ </span>
4631
+ ` : `
4632
+ <span style="font-size: 12px; color: #365314; font-weight: 600;">
4633
+ Slide ${idx} / ${totalSlides}
4634
+ </span>
4635
+ `}
4321
4636
  </div>
4637
+
4638
+ <!-- Slide Content -->
4639
+ ${contentHtml}
4322
4640
  </div>
4323
4641
  `;
4324
4642
  ctx.emit("page-change", { page: currentSlide, total: totalSlides });
4325
4643
  };
4326
4644
  renderSlide(1);
4327
4645
  const cleanup = () => {
4646
+ for (const u of createdBlobUrls) {
4647
+ URL.revokeObjectURL(u);
4648
+ }
4328
4649
  container.remove();
4329
4650
  ctx.container.innerHTML = "";
4330
4651
  };
@@ -4364,13 +4685,48 @@ var PptPlugin = class {
4364
4685
  if (!ctx2d) return;
4365
4686
  canvas.width = 160;
4366
4687
  canvas.height = 90;
4367
- ctx2d.fillStyle = "#ffffff";
4688
+ ctx2d.fillStyle = "#334155";
4368
4689
  ctx2d.fillRect(0, 0, 160, 90);
4369
- ctx2d.fillStyle = "#1e3a8a";
4370
- ctx2d.font = "bold 11px sans-serif";
4371
- ctx2d.textAlign = "center";
4372
- const title = s.title.slice(0, 18) || `Slide ${idx + 1}`;
4373
- ctx2d.fillText(title, 80, 50);
4690
+ ctx2d.fillStyle = "#ffffff";
4691
+ ctx2d.fillRect(3, 3, 154, 84);
4692
+ ctx2d.fillStyle = "#84cc16";
4693
+ ctx2d.fillRect(6, 6, 148, 18);
4694
+ ctx2d.fillStyle = "#1e293b";
4695
+ ctx2d.font = "bold 9px sans-serif";
4696
+ ctx2d.textAlign = "left";
4697
+ const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
4698
+ ctx2d.fillText(displayTitle, 10, 19);
4699
+ if (s.subtitle) {
4700
+ ctx2d.fillStyle = "#38bdf8";
4701
+ ctx2d.fillRect(116, 9, 34, 12);
4702
+ ctx2d.fillStyle = "#ffffff";
4703
+ ctx2d.font = "bold 7px sans-serif";
4704
+ ctx2d.textAlign = "center";
4705
+ ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
4706
+ }
4707
+ if (s.pictureUrl) {
4708
+ ctx2d.fillStyle = "#3b82f6";
4709
+ ctx2d.fillRect(52, 34, 56, 42);
4710
+ ctx2d.fillStyle = "#ffffff";
4711
+ ctx2d.font = "8px sans-serif";
4712
+ ctx2d.textAlign = "center";
4713
+ ctx2d.fillText("Chart", 80, 58);
4714
+ } else if (s.tableColumns.length > 0) {
4715
+ ctx2d.strokeStyle = "#cbd5e1";
4716
+ ctx2d.lineWidth = 1;
4717
+ ctx2d.strokeRect(14, 32, 132, 46);
4718
+ for (let l = 1; l <= 3; l++) {
4719
+ ctx2d.beginPath();
4720
+ ctx2d.moveTo(14, 32 + l * 11);
4721
+ ctx2d.lineTo(146, 32 + l * 11);
4722
+ ctx2d.stroke();
4723
+ }
4724
+ } else {
4725
+ ctx2d.fillStyle = "#94a3b8";
4726
+ for (let l = 0; l < 4; l++) {
4727
+ ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
4728
+ }
4729
+ }
4374
4730
  }
4375
4731
  }));
4376
4732
  },
@@ -4388,66 +4744,165 @@ var PptPlugin = class {
4388
4744
  }
4389
4745
  };
4390
4746
  }
4747
+ /**
4748
+ * Extract PNG and JPEG images from the Pictures stream
4749
+ */
4750
+ extractPictures(cfbf, createdUrls) {
4751
+ const urls = [];
4752
+ try {
4753
+ const picStream = cfbf.readStream("Pictures");
4754
+ if (!picStream || picStream.length < 32) return urls;
4755
+ const pBuf = new Uint8Array(picStream);
4756
+ const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
4757
+ const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
4758
+ for (let i = 0; i <= pBuf.length - 8; i++) {
4759
+ let match = true;
4760
+ for (let j = 0; j < 8; j++) {
4761
+ if (pBuf[i + j] !== pngSig[j]) {
4762
+ match = false;
4763
+ break;
4764
+ }
4765
+ }
4766
+ if (match) {
4767
+ let endIdx = -1;
4768
+ for (let k = i + 8; k <= pBuf.length - 8; k++) {
4769
+ let endMatch = true;
4770
+ for (let j = 0; j < 8; j++) {
4771
+ if (pBuf[k + j] !== iendSig[j]) {
4772
+ endMatch = false;
4773
+ break;
4774
+ }
4775
+ }
4776
+ if (endMatch) {
4777
+ endIdx = k + 8;
4778
+ break;
4779
+ }
4780
+ }
4781
+ if (endIdx !== -1) {
4782
+ const pngBytes = pBuf.subarray(i, endIdx);
4783
+ const blob = new Blob([pngBytes], { type: "image/png" });
4784
+ const url = URL.createObjectURL(blob);
4785
+ urls.push(url);
4786
+ createdUrls.push(url);
4787
+ i = endIdx;
4788
+ }
4789
+ }
4790
+ }
4791
+ for (let i = 0; i <= pBuf.length - 3; i++) {
4792
+ if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
4793
+ let endIdx = -1;
4794
+ for (let k = i + 3; k < pBuf.length - 1; k++) {
4795
+ if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
4796
+ endIdx = k + 2;
4797
+ break;
4798
+ }
4799
+ }
4800
+ if (endIdx !== -1) {
4801
+ const jpgBytes = pBuf.subarray(i, endIdx);
4802
+ const blob = new Blob([jpgBytes], { type: "image/jpeg" });
4803
+ const url = URL.createObjectURL(blob);
4804
+ urls.push(url);
4805
+ createdUrls.push(url);
4806
+ i = endIdx;
4807
+ }
4808
+ }
4809
+ }
4810
+ } catch (err) {
4811
+ console.warn("[PptPlugin] Error extracting pictures:", err);
4812
+ }
4813
+ return urls;
4814
+ }
4391
4815
  /**
4392
4816
  * Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
4393
4817
  */
4394
- extractSlides(stream) {
4818
+ extractSlides(stream, pictures) {
4395
4819
  const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
4396
4820
  const len = stream.length;
4397
4821
  let offset = 0;
4398
4822
  const slides = [];
4399
- let currentSlideTexts = [];
4823
+ let picIdx = 0;
4400
4824
  while (offset + 8 <= len) {
4401
4825
  const recVerInst = view.getUint16(offset, true);
4402
4826
  const recType = view.getUint16(offset + 2, true);
4403
4827
  const recLen = view.getUint32(offset + 4, true);
4828
+ const isContainer = (recVerInst & 15) === 15;
4404
4829
  if (recType === 1006) {
4405
- if (currentSlideTexts.length > 0) {
4406
- const title = currentSlideTexts[0] || "Slide";
4407
- const texts = currentSlideTexts.slice(1);
4408
- slides.push({ title, texts });
4409
- currentSlideTexts = [];
4830
+ const slideEnd = Math.min(len, offset + 8 + recLen);
4831
+ const rawTexts = [];
4832
+ let hasOle = false;
4833
+ let sOff = offset + 8;
4834
+ while (sOff + 8 <= slideEnd) {
4835
+ const cVerInst = view.getUint16(sOff, true);
4836
+ const cType = view.getUint16(sOff + 2, true);
4837
+ const cLen = view.getUint32(sOff + 4, true);
4838
+ const cIsContainer = (cVerInst & 15) === 15;
4839
+ if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4840
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4841
+ const txt = new TextDecoder("latin1").decode(bytes).trim();
4842
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4843
+ rawTexts.push(txt);
4844
+ }
4845
+ } else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4846
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4847
+ const txt = new TextDecoder("utf-16le").decode(bytes).trim();
4848
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4849
+ rawTexts.push(txt);
4850
+ }
4851
+ } else if (cType === 3009 || cType === 3011) {
4852
+ hasOle = true;
4853
+ }
4854
+ if (cIsContainer) sOff += 8;
4855
+ else sOff += 8 + cLen;
4410
4856
  }
4411
- offset += 8;
4412
- continue;
4413
- }
4414
- if (recType === 3998 && recLen > 0 && offset + 8 + recLen <= len) {
4415
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4416
- const text = new TextDecoder("latin1").decode(bytes).trim();
4417
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4418
- currentSlideTexts.push(text);
4857
+ let title = "";
4858
+ let subtitle = "";
4859
+ const paragraphs = [];
4860
+ const tableColumns = [];
4861
+ for (const t of rawTexts) {
4862
+ if (!title && t.length < 60 && !t.includes("\n")) {
4863
+ title = t;
4864
+ } else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
4865
+ tableColumns.push(t);
4866
+ } else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
4867
+ subtitle = t;
4868
+ } else {
4869
+ paragraphs.push(t);
4870
+ }
4419
4871
  }
4420
- }
4421
- if (recType === 3999 && recLen > 0 && offset + 8 + recLen <= len) {
4422
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4423
- const text = new TextDecoder("utf-16le").decode(bytes).trim();
4424
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4425
- currentSlideTexts.push(text);
4872
+ let pictureUrl = null;
4873
+ if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
4874
+ pictureUrl = pictures[picIdx++];
4426
4875
  }
4876
+ slides.push({
4877
+ slideIndex: slides.length + 1,
4878
+ title: title || `Slide ${slides.length + 1}`,
4879
+ subtitle,
4880
+ paragraphs,
4881
+ tableColumns,
4882
+ pictureUrl,
4883
+ hasOle
4884
+ });
4427
4885
  }
4428
- const isContainer = (recVerInst & 15) === 15;
4429
- if (isContainer) {
4430
- offset += 8;
4431
- } else {
4432
- offset += 8 + recLen;
4433
- }
4434
- }
4435
- if (currentSlideTexts.length > 0) {
4436
- const title = currentSlideTexts[0] || "Slide";
4437
- const texts = currentSlideTexts.slice(1);
4438
- slides.push({ title, texts });
4886
+ if (isContainer) offset += 8;
4887
+ else offset += 8 + recLen;
4439
4888
  }
4440
4889
  if (slides.length === 0) {
4441
4890
  const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
4442
- const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{4,}/g) || [];
4443
- const filtered = matches.map((m) => m.trim()).filter((m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times"));
4444
- if (filtered.length > 0) {
4445
- const chunkSize = 4;
4446
- for (let i = 0; i < filtered.length; i += chunkSize) {
4447
- const chunk = filtered.slice(i, i + chunkSize);
4891
+ const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
4892
+ const clean = matches.map((m) => m.trim()).filter(
4893
+ (m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
4894
+ );
4895
+ if (clean.length > 0) {
4896
+ const chunkSize = 3;
4897
+ for (let i = 0; i < clean.length; i += chunkSize) {
4898
+ const chunk = clean.slice(i, i + chunkSize);
4448
4899
  slides.push({
4900
+ slideIndex: slides.length + 1,
4449
4901
  title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
4450
- texts: chunk.slice(1)
4902
+ subtitle: chunk.length > 2 ? chunk[1] : void 0,
4903
+ paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
4904
+ tableColumns: [],
4905
+ pictureUrl: pictures[slides.length] || null
4451
4906
  });
4452
4907
  }
4453
4908
  }