@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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Component, ChangeDetectionStrategy, Input, Output, ViewChild, EventEmitter as EventEmitter$1 } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
- import DOMPurify6 from 'dompurify';
3
+ import DOMPurify2 from 'dompurify';
4
4
  import * as docx from 'docx-preview';
5
5
  import { unzipSync, strFromU8, unzip } from 'fflate';
6
6
  import * as XLSX from 'xlsx';
@@ -11,7 +11,7 @@ import * as THREE from 'three';
11
11
  import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
12
12
  import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
13
13
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
14
- import { RTFJS } from 'rtf.js';
14
+ import * as RTFJS from 'rtf.js/dist/RTFJS.bundle.js';
15
15
 
16
16
  var __create = Object.create;
17
17
  var __defProp = Object.defineProperty;
@@ -314,6 +314,39 @@ function detectOoxmlType(buffer) {
314
314
  }
315
315
  return "application/zip";
316
316
  }
317
+ function detectCfbfType(buffer) {
318
+ const bytes = new Uint8Array(buffer);
319
+ const hasUtf16le = (str) => {
320
+ const target = new Uint8Array(str.length * 2);
321
+ for (let i = 0; i < str.length; i++) {
322
+ target[i * 2] = str.charCodeAt(i);
323
+ target[i * 2 + 1] = 0;
324
+ }
325
+ const targetLen = target.length;
326
+ const max = bytes.length - targetLen;
327
+ for (let i = 0; i <= max; i++) {
328
+ let match = true;
329
+ for (let j = 0; j < targetLen; j++) {
330
+ if (bytes[i + j] !== target[j]) {
331
+ match = false;
332
+ break;
333
+ }
334
+ }
335
+ if (match) return true;
336
+ }
337
+ return false;
338
+ };
339
+ if (hasUtf16le("PowerPoint Document")) {
340
+ return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
341
+ }
342
+ if (hasUtf16le("WordDocument")) {
343
+ return { mime: "application/msword", extension: ".doc" };
344
+ }
345
+ if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
346
+ return { mime: "application/vnd.ms-excel", extension: ".xls" };
347
+ }
348
+ return null;
349
+ }
317
350
  function extractExtension(nameOrUrl) {
318
351
  try {
319
352
  const url = new URL(nameOrUrl);
@@ -378,6 +411,18 @@ async function sourceToArrayBuffer(source, signal) {
378
411
  } else {
379
412
  metadata.mimeType = metadata.mimeType ?? magicMime;
380
413
  }
414
+ } else if (magicMime === "application/x-cfbf") {
415
+ if (metadata.extension) {
416
+ metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
417
+ } else {
418
+ const cfbf = detectCfbfType(buffer);
419
+ if (cfbf) {
420
+ metadata.mimeType = cfbf.mime;
421
+ metadata.extension = cfbf.extension;
422
+ } else {
423
+ metadata.mimeType = magicMime;
424
+ }
425
+ }
381
426
  } else {
382
427
  metadata.mimeType = magicMime;
383
428
  }
@@ -388,7 +433,7 @@ async function sourceToArrayBuffer(source, signal) {
388
433
  return { buffer, metadata };
389
434
  }
390
435
  function sanitizeSVG(svg) {
391
- return DOMPurify6.sanitize(svg, {
436
+ return DOMPurify2.sanitize(svg, {
392
437
  USE_PROFILES: { svg: true, svgFilters: true }
393
438
  });
394
439
  }
@@ -448,6 +493,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
448
493
  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>`;
449
494
  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>`;
450
495
  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>`;
496
+ 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>`;
497
+ 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>`;
498
+ 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>`;
451
499
  var ICON_MAP = {
452
500
  "zoom-in": ICON_ZOOM_IN,
453
501
  "zoom-out": ICON_ZOOM_OUT,
@@ -468,7 +516,12 @@ var ICON_MAP = {
468
516
  "page-prev": ICON_PAGE_PREV,
469
517
  "page-next": ICON_PAGE_NEXT,
470
518
  "chevron-left": ICON_PAGE_PREV,
471
- "chevron-right": ICON_PAGE_NEXT
519
+ "chevron-right": ICON_PAGE_NEXT,
520
+ "fast-forward": ICON_FAST_FORWARD,
521
+ "forward-10": ICON_FAST_FORWARD,
522
+ "rewind": ICON_REWIND,
523
+ "replay-10": ICON_REWIND,
524
+ "speed": ICON_SPEED
472
525
  };
473
526
  var ToolbarController = class {
474
527
  el;
@@ -1436,6 +1489,16 @@ var MediaPlugin = class {
1436
1489
  }
1437
1490
  if (instance.play) {
1438
1491
  actions.push(
1492
+ {
1493
+ id: "replay-10",
1494
+ icon: "replay-10",
1495
+ label: "Rewind 10s",
1496
+ type: "button",
1497
+ group: "actions",
1498
+ execute: () => {
1499
+ instance.rewind?.(10);
1500
+ }
1501
+ },
1439
1502
  {
1440
1503
  id: "play",
1441
1504
  icon: "play",
@@ -1455,6 +1518,26 @@ var MediaPlugin = class {
1455
1518
  execute: () => {
1456
1519
  instance.pause?.();
1457
1520
  }
1521
+ },
1522
+ {
1523
+ id: "forward-10",
1524
+ icon: "forward-10",
1525
+ label: "Fast Forward 10s",
1526
+ type: "button",
1527
+ group: "actions",
1528
+ execute: () => {
1529
+ instance.fastForward?.(10);
1530
+ }
1531
+ },
1532
+ {
1533
+ id: "speed",
1534
+ icon: "speed",
1535
+ label: "Playback Speed",
1536
+ type: "button",
1537
+ group: "actions",
1538
+ execute: () => {
1539
+ instance.cycleSpeed?.();
1540
+ }
1458
1541
  }
1459
1542
  );
1460
1543
  }
@@ -1577,6 +1660,28 @@ var MediaPlugin = class {
1577
1660
  play: mediaElement ? () => mediaElement?.play() : void 0,
1578
1661
  pause: mediaElement ? () => mediaElement?.pause() : void 0,
1579
1662
  isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1663
+ fastForward: mediaElement ? (seconds = 10) => {
1664
+ if (mediaElement) {
1665
+ mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
1666
+ }
1667
+ } : void 0,
1668
+ rewind: mediaElement ? (seconds = 10) => {
1669
+ if (mediaElement) {
1670
+ mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
1671
+ }
1672
+ } : void 0,
1673
+ cycleSpeed: mediaElement ? () => {
1674
+ if (mediaElement) {
1675
+ const speeds = [1, 1.25, 1.5, 2, 0.5];
1676
+ const curIndex = speeds.indexOf(mediaElement.playbackRate);
1677
+ const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
1678
+ mediaElement.playbackRate = speeds[nextIndex];
1679
+ }
1680
+ } : void 0,
1681
+ setPlaybackRate: mediaElement ? (rate) => {
1682
+ if (mediaElement) mediaElement.playbackRate = rate;
1683
+ } : void 0,
1684
+ getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
1580
1685
  download: () => {
1581
1686
  const a = document.createElement("a");
1582
1687
  a.href = url;
@@ -1606,7 +1711,10 @@ var DocxPlugin = class {
1606
1711
  supports(file) {
1607
1712
  const ext = file.metadata.extension?.toLowerCase();
1608
1713
  const mime = file.metadata.mimeType?.toLowerCase();
1609
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
1714
+ if (ext) {
1715
+ return this.extensions.includes(ext);
1716
+ }
1717
+ return this.mimeTypes.includes(mime || "");
1610
1718
  }
1611
1719
  getToolbarActions(instance) {
1612
1720
  return [
@@ -1658,16 +1766,35 @@ var DocxPlugin = class {
1658
1766
  wrapper.style.transformOrigin = "top center";
1659
1767
  wrapper.style.transition = "transform 0.2s ease";
1660
1768
  wrapper.style.padding = "24px 16px";
1661
- wrapper.style.maxWidth = "900px";
1769
+ wrapper.style.maxWidth = "950px";
1662
1770
  wrapper.style.margin = "0 auto";
1663
1771
  wrapper.style.boxSizing = "border-box";
1664
1772
  ctx.container.style.overflow = "auto";
1665
1773
  ctx.container.style.backgroundColor = "#f1f5f9";
1666
1774
  ctx.container.appendChild(wrapper);
1775
+ const styleOverride = document.createElement("style");
1776
+ styleOverride.textContent = `
1777
+ .fp-docx-wrapper .docx-wrapper {
1778
+ background: transparent !important;
1779
+ padding: 0 !important;
1780
+ display: flex !important;
1781
+ flex-direction: column !important;
1782
+ align-items: center !important;
1783
+ box-sizing: border-box !important;
1784
+ }
1785
+ .fp-docx-wrapper section.docx {
1786
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
1787
+ border-radius: 4px !important;
1788
+ margin-bottom: 24px !important;
1789
+ background-color: #ffffff !important;
1790
+ }
1791
+ `;
1792
+ ctx.container.appendChild(styleOverride);
1667
1793
  let scale = 1;
1668
1794
  let renderedSuccessfully = false;
1795
+ const createdBlobUrls = [];
1669
1796
  try {
1670
- await docx.renderAsync(ctx.buffer, wrapper, ctx.container, {
1797
+ await docx.renderAsync(ctx.buffer, wrapper, wrapper, {
1671
1798
  inWrapper: true,
1672
1799
  ignoreWidth: false,
1673
1800
  ignoreHeight: false,
@@ -1676,19 +1803,25 @@ var DocxPlugin = class {
1676
1803
  breakPages: true,
1677
1804
  experimental: true
1678
1805
  });
1806
+ if (!ctx.container.contains(wrapper)) {
1807
+ ctx.container.appendChild(wrapper);
1808
+ }
1679
1809
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
1680
1810
  renderedSuccessfully = true;
1681
1811
  }
1682
1812
  } catch (err) {
1683
- console.warn("[DocxPlugin] docx-preview failed, triggering native XML fallback:", err);
1813
+ console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
1684
1814
  }
1685
1815
  if (!renderedSuccessfully) {
1686
1816
  try {
1687
1817
  wrapper.innerHTML = "";
1688
- this.renderXmlFallback(ctx, wrapper);
1818
+ this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
1819
+ if (!ctx.container.contains(wrapper)) {
1820
+ ctx.container.appendChild(wrapper);
1821
+ }
1689
1822
  renderedSuccessfully = true;
1690
1823
  } catch (fallbackErr) {
1691
- console.error("[DocxPlugin] XML fallback failed:", fallbackErr);
1824
+ console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
1692
1825
  wrapper.innerHTML = `
1693
1826
  <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
1694
1827
  <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
@@ -1696,10 +1829,17 @@ var DocxPlugin = class {
1696
1829
  <p style="color: #64748b; margin: 0;">Could not parse document content</p>
1697
1830
  </div>
1698
1831
  `;
1832
+ if (!ctx.container.contains(wrapper)) {
1833
+ ctx.container.appendChild(wrapper);
1834
+ }
1699
1835
  }
1700
1836
  }
1701
1837
  const cleanup = () => {
1838
+ for (const url of createdBlobUrls) {
1839
+ URL.revokeObjectURL(url);
1840
+ }
1702
1841
  wrapper.remove();
1842
+ styleOverride.remove();
1703
1843
  ctx.container.innerHTML = "";
1704
1844
  };
1705
1845
  ctx.signal.addEventListener("abort", cleanup);
@@ -1736,13 +1876,50 @@ var DocxPlugin = class {
1736
1876
  }
1737
1877
  };
1738
1878
  }
1739
- renderXmlFallback(ctx, wrapper) {
1879
+ renderXmlFallback(ctx, wrapper, createdBlobUrls) {
1880
+ const magic = new Uint8Array(ctx.buffer.slice(0, 8));
1881
+ if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
1882
+ this.renderBinaryDocFallback(ctx, wrapper);
1883
+ return;
1884
+ }
1740
1885
  const unzipped = unzipSync(new Uint8Array(ctx.buffer));
1741
- const docXmlEntry = unzipped["word/document.xml"];
1886
+ const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
1887
+ const docXmlEntry = docKey ? unzipped[docKey] : void 0;
1742
1888
  if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
1743
1889
  const xmlStr = strFromU8(docXmlEntry);
1744
1890
  const parser = new DOMParser();
1745
1891
  const doc = parser.parseFromString(xmlStr, "application/xml");
1892
+ const imageMap = {};
1893
+ const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
1894
+ if (relsKey && unzipped[relsKey]) {
1895
+ try {
1896
+ const relsXml = strFromU8(unzipped[relsKey]);
1897
+ const relsDoc = parser.parseFromString(relsXml, "application/xml");
1898
+ const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
1899
+ for (const rel of relEls) {
1900
+ const rId = rel.getAttribute("Id");
1901
+ const target = rel.getAttribute("Target");
1902
+ if (rId && target) {
1903
+ const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
1904
+ const fullTargetKey = Object.keys(unzipped).find((k) => {
1905
+ const norm = k.replace(/^[./\\]+/, "").toLowerCase();
1906
+ return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
1907
+ });
1908
+ if (fullTargetKey && unzipped[fullTargetKey]) {
1909
+ const bytes = unzipped[fullTargetKey];
1910
+ const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
1911
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
1912
+ const blob = new Blob([bytes], { type: mime });
1913
+ const blobUrl = URL.createObjectURL(blob);
1914
+ imageMap[rId] = blobUrl;
1915
+ createdBlobUrls.push(blobUrl);
1916
+ }
1917
+ }
1918
+ }
1919
+ } catch (relsErr) {
1920
+ console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
1921
+ }
1922
+ }
1746
1923
  const card = document.createElement("div");
1747
1924
  card.className = "fp-docx-page-card";
1748
1925
  card.style.backgroundColor = "#ffffff";
@@ -1760,7 +1937,7 @@ var DocxPlugin = class {
1760
1937
  const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
1761
1938
  const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
1762
1939
  const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
1763
- const textContent = this.extractParagraphHtml(child);
1940
+ const textContent = this.extractParagraphHtml(child, imageMap);
1764
1941
  if (!textContent.trim()) {
1765
1942
  html += '<div style="height: 10px;"></div>';
1766
1943
  continue;
@@ -1786,7 +1963,7 @@ var DocxPlugin = class {
1786
1963
  html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
1787
1964
  const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
1788
1965
  cells.forEach((tc) => {
1789
- const cellText = this.extractParagraphHtml(tc);
1966
+ const cellText = this.extractParagraphHtml(tc, imageMap);
1790
1967
  html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
1791
1968
  });
1792
1969
  html += "</tr>";
@@ -1794,34 +1971,63 @@ var DocxPlugin = class {
1794
1971
  html += "</table>";
1795
1972
  }
1796
1973
  }
1797
- card.innerHTML = DOMPurify6.sanitize(html, {
1798
- ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "img", "div"],
1974
+ card.innerHTML = DOMPurify2.sanitize(html, {
1975
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
1799
1976
  ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
1800
1977
  });
1801
1978
  wrapper.appendChild(card);
1802
1979
  }
1803
- extractParagraphHtml(pElement) {
1980
+ extractParagraphHtml(pElement, imageMap = {}) {
1804
1981
  let result = "";
1982
+ const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
1983
+ for (const drawing of drawings) {
1984
+ const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
1985
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1986
+ if (rId && imageMap[rId]) {
1987
+ result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
1988
+ }
1989
+ }
1805
1990
  const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
1806
- if (runs.length === 0) {
1807
- return DOMPurify6.sanitize(pElement.textContent || "");
1991
+ if (runs.length === 0 && !result) {
1992
+ return DOMPurify2.sanitize(pElement.textContent || "");
1808
1993
  }
1809
1994
  for (const r of runs) {
1995
+ const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
1996
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1997
+ if (rId && imageMap[rId]) {
1998
+ result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
1999
+ }
2000
+ const brs = r.getElementsByTagNameNS("*", "br");
2001
+ for (let i = 0; i < brs.length; i++) {
2002
+ result += "<br/>";
2003
+ }
2004
+ const tabs = r.getElementsByTagNameNS("*", "tab");
2005
+ if (tabs.length > 0) {
2006
+ result += "&emsp;";
2007
+ }
1810
2008
  const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
1811
2009
  const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
1812
2010
  const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
1813
2011
  const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
2012
+ const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
1814
2013
  const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
1815
2014
  const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
2015
+ const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
2016
+ const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
1816
2017
  const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
1817
2018
  let text = texts.map((t) => t.textContent || "").join("");
1818
2019
  if (!text) continue;
1819
- text = DOMPurify6.sanitize(text);
2020
+ text = DOMPurify2.sanitize(text);
1820
2021
  let styles = "";
1821
2022
  if (isBold) styles += "font-weight: bold; ";
1822
2023
  if (isItalic) styles += "font-style: italic; ";
1823
2024
  if (isUnderline) styles += "text-decoration: underline; ";
2025
+ if (isStrike) styles += "text-decoration: line-through; ";
1824
2026
  if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
2027
+ if (szVal) {
2028
+ const pt = parseInt(szVal, 10) / 2;
2029
+ if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2030
+ }
1825
2031
  if (styles) {
1826
2032
  result += `<span style="${styles}">${text}</span>`;
1827
2033
  } else {
@@ -1830,6 +2036,52 @@ var DocxPlugin = class {
1830
2036
  }
1831
2037
  return result;
1832
2038
  }
2039
+ renderBinaryDocFallback(ctx, wrapper) {
2040
+ const card = document.createElement("div");
2041
+ card.className = "fp-docx-page-card";
2042
+ card.style.backgroundColor = "#ffffff";
2043
+ card.style.borderRadius = "6px";
2044
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2045
+ card.style.padding = "48px 56px";
2046
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2047
+ card.style.color = "#1e293b";
2048
+ card.style.lineHeight = "1.6";
2049
+ try {
2050
+ const cfbf = new CfbfReader(ctx.buffer);
2051
+ const wordDocStream = cfbf.readStream("WordDocument");
2052
+ if (wordDocStream && wordDocStream.length >= 512) {
2053
+ const text2 = this.extractReadableStrings(wordDocStream);
2054
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text2)}</div>`;
2055
+ wrapper.appendChild(card);
2056
+ return;
2057
+ }
2058
+ } catch {
2059
+ }
2060
+ const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2061
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text)}</div>`;
2062
+ wrapper.appendChild(card);
2063
+ }
2064
+ extractReadableStrings(bytes) {
2065
+ let result = "";
2066
+ let current = "";
2067
+ for (let i = 0; i < bytes.length; i++) {
2068
+ const b = bytes[i];
2069
+ if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
2070
+ current += String.fromCharCode(b);
2071
+ } else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
2072
+ continue;
2073
+ } else {
2074
+ if (current.trim().length > 3) {
2075
+ result += current.trim() + "\n\n";
2076
+ }
2077
+ current = "";
2078
+ }
2079
+ }
2080
+ if (current.trim().length > 3) {
2081
+ result += current.trim();
2082
+ }
2083
+ return result;
2084
+ }
1833
2085
  };
1834
2086
  function docxPlugin() {
1835
2087
  return new DocxPlugin();
@@ -1851,7 +2103,10 @@ var ExcelPlugin = class {
1851
2103
  supports(file) {
1852
2104
  const ext = file.metadata.extension?.toLowerCase();
1853
2105
  const mime = file.metadata.mimeType?.toLowerCase();
1854
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
2106
+ if (ext) {
2107
+ return this.extensions.includes(ext);
2108
+ }
2109
+ return this.mimeTypes.includes(mime || "");
1855
2110
  }
1856
2111
  getToolbarActions(instance) {
1857
2112
  return [
@@ -2641,7 +2896,7 @@ var MarkdownPlugin = class {
2641
2896
  gfm: true,
2642
2897
  breaks: true
2643
2898
  });
2644
- const cleanHtml = DOMPurify6.sanitize(rawHtml, {
2899
+ const cleanHtml = DOMPurify2.sanitize(rawHtml, {
2645
2900
  USE_PROFILES: { html: true }
2646
2901
  });
2647
2902
  const wrapper = document.createElement("div");
@@ -2793,7 +3048,10 @@ var PptxPlugin = class {
2793
3048
  supports(file) {
2794
3049
  const ext = file.metadata.extension?.toLowerCase();
2795
3050
  const mime = file.metadata.mimeType?.toLowerCase();
2796
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3051
+ if (ext) {
3052
+ return this.extensions.includes(ext);
3053
+ }
3054
+ return this.mimeTypes.includes(mime || "");
2797
3055
  }
2798
3056
  getToolbarActions(instance) {
2799
3057
  return [
@@ -3238,6 +3496,9 @@ var RtfPlugin = class {
3238
3496
  ctx.container.appendChild(wrapper);
3239
3497
  let scale = 1;
3240
3498
  try {
3499
+ if (typeof RTFJS.loggingEnabled === "function") {
3500
+ RTFJS.loggingEnabled(false);
3501
+ }
3241
3502
  const doc = new RTFJS.Document(ctx.buffer, {});
3242
3503
  const htmlElements = await doc.render();
3243
3504
  for (const el of htmlElements) {
@@ -3356,7 +3617,7 @@ var HtmlPreviewPlugin = class {
3356
3617
  }
3357
3618
  async render(ctx) {
3358
3619
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3359
- const sanitized = DOMPurify6.sanitize(rawHtml, {
3620
+ const sanitized = DOMPurify2.sanitize(rawHtml, {
3360
3621
  WHOLE_DOCUMENT: true,
3361
3622
  ADD_TAGS: ["style", "link"],
3362
3623
  ADD_ATTR: ["target", "rel"]
@@ -3436,7 +3697,10 @@ var OpenDocumentPlugin = class {
3436
3697
  supports(file) {
3437
3698
  const ext = file.metadata.extension?.toLowerCase();
3438
3699
  const mime = file.metadata.mimeType?.toLowerCase();
3439
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3700
+ if (ext) {
3701
+ return this.extensions.includes(ext);
3702
+ }
3703
+ return this.mimeTypes.includes(mime || "");
3440
3704
  }
3441
3705
  getToolbarActions(instance) {
3442
3706
  const isPresentation = instance.isPresentation;
@@ -3599,7 +3863,7 @@ var OpenDocumentPlugin = class {
3599
3863
  wrapper.style.padding = "48px";
3600
3864
  wrapper.style.minHeight = "100%";
3601
3865
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
3602
- wrapper.innerHTML = DOMPurify6.sanitize(bodyHtml, {
3866
+ wrapper.innerHTML = DOMPurify2.sanitize(bodyHtml, {
3603
3867
  ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
3604
3868
  ADD_ATTR: ["style", "colspan", "rowspan"]
3605
3869
  });
@@ -3851,7 +4115,10 @@ var DocPlugin = class {
3851
4115
  supports(file) {
3852
4116
  const ext = file.metadata.extension?.toLowerCase();
3853
4117
  const mime = file.metadata.mimeType?.toLowerCase();
3854
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4118
+ if (ext) {
4119
+ return this.extensions.includes(ext);
4120
+ }
4121
+ return this.mimeTypes.includes(mime || "");
3855
4122
  }
3856
4123
  getToolbarActions(instance) {
3857
4124
  return [
@@ -4074,22 +4341,22 @@ var DocPlugin = class {
4074
4341
  * Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
4075
4342
  */
4076
4343
  extractStringsFromBytes(bytes) {
4077
- const chars = [];
4078
- const len = bytes.length;
4079
- for (let i = 0; i < len; i++) {
4080
- const b = bytes[i];
4081
- if (b === 13 || b === 10 || b === 9 || b >= 32 && b <= 126 || b >= 160 && b <= 255) {
4082
- chars.push(String.fromCharCode(b));
4083
- } else if (b === 0 && i + 1 < len && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
4084
- chars.push(String.fromCharCode(bytes[i + 1]));
4085
- i++;
4086
- } else if (b === 7) {
4087
- chars.push(" ");
4088
- } else if (b === 12) {
4089
- chars.push("\n\n---PAGE---\n\n");
4344
+ const rawAnsi = new TextDecoder("latin1").decode(bytes);
4345
+ const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
4346
+ const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4347
+ const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4348
+ const candidateLines = [];
4349
+ const seen = /* @__PURE__ */ new Set();
4350
+ for (const run of [...ansiRuns, ...utf16Runs]) {
4351
+ const trimmed = run.trim();
4352
+ if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
4353
+ 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)) {
4354
+ seen.add(trimmed);
4355
+ candidateLines.push(trimmed);
4356
+ }
4090
4357
  }
4091
4358
  }
4092
- return chars.join("");
4359
+ return candidateLines.join("\n\n");
4093
4360
  }
4094
4361
  heuristicTextExtraction(buffer) {
4095
4362
  return this.extractStringsFromBytes(new Uint8Array(buffer));
@@ -4119,24 +4386,24 @@ var DocPlugin = class {
4119
4386
  html += "</ul>";
4120
4387
  inList = false;
4121
4388
  }
4122
- html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line)}</h2>`;
4389
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify2.sanitize(line)}</h2>`;
4123
4390
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4124
4391
  if (!inList) {
4125
4392
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4126
4393
  inList = true;
4127
4394
  }
4128
4395
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4129
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify6.sanitize(bulletText)}</li>`;
4396
+ html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2.sanitize(bulletText)}</li>`;
4130
4397
  } else {
4131
4398
  if (inList) {
4132
4399
  html += "</ul>";
4133
4400
  inList = false;
4134
4401
  }
4135
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify6.sanitize(line)}</p>`;
4402
+ html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2.sanitize(line)}</p>`;
4136
4403
  }
4137
4404
  }
4138
4405
  if (inList) html += "</ul>";
4139
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify6.sanitize(filename)})</p>`;
4406
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2.sanitize(filename)})</p>`;
4140
4407
  }
4141
4408
  };
4142
4409
  function docPlugin() {
@@ -4144,14 +4411,17 @@ function docPlugin() {
4144
4411
  }
4145
4412
  var PptPlugin = class {
4146
4413
  id = "ppt";
4147
- name = "Legacy PowerPoint Presentation (.ppt, .pps, .pot)";
4414
+ name = "PowerPoint Presentation (.ppt, .pps, .pot)";
4148
4415
  extensions = [".ppt", ".pps", ".pot"];
4149
4416
  mimeTypes = ["application/vnd.ms-powerpoint"];
4150
- weight = 75;
4417
+ weight = 85;
4151
4418
  supports(file) {
4152
4419
  const ext = file.metadata.extension?.toLowerCase();
4153
4420
  const mime = file.metadata.mimeType?.toLowerCase();
4154
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4421
+ if (ext) {
4422
+ return this.extensions.includes(ext);
4423
+ }
4424
+ return this.mimeTypes.includes(mime || "");
4155
4425
  }
4156
4426
  getToolbarActions(instance) {
4157
4427
  return [
@@ -4239,19 +4509,15 @@ var PptPlugin = class {
4239
4509
  container.style.alignItems = "center";
4240
4510
  container.style.padding = "32px 16px";
4241
4511
  container.style.backgroundColor = "#0f172a";
4512
+ container.style.boxSizing = "border-box";
4242
4513
  const slideCard = document.createElement("div");
4243
4514
  slideCard.className = "fp-ppt-slide-card";
4244
4515
  slideCard.style.width = "960px";
4245
- slideCard.style.maxWidth = "90%";
4516
+ slideCard.style.maxWidth = "92%";
4246
4517
  slideCard.style.aspectRatio = "16 / 9";
4247
4518
  slideCard.style.backgroundColor = "#ffffff";
4248
- slideCard.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
4519
+ slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4249
4520
  slideCard.style.borderRadius = "8px";
4250
- slideCard.style.padding = "48px";
4251
- slideCard.style.display = "flex";
4252
- slideCard.style.flexDirection = "column";
4253
- slideCard.style.justifyContent = "center";
4254
- slideCard.style.alignItems = "center";
4255
4521
  slideCard.style.boxSizing = "border-box";
4256
4522
  slideCard.style.position = "relative";
4257
4523
  slideCard.style.overflow = "hidden";
@@ -4262,21 +4528,25 @@ var PptPlugin = class {
4262
4528
  let scale = 1;
4263
4529
  let currentSlide = 1;
4264
4530
  let slides = [];
4531
+ const createdBlobUrls = [];
4265
4532
  try {
4266
4533
  const cfbf = new CfbfReader(ctx.buffer);
4267
4534
  const pptStream = cfbf.readStream("PowerPoint Document");
4268
4535
  if (!pptStream || pptStream.length < 512) {
4269
4536
  throw new Error("PowerPoint Document stream not found in CFBF container");
4270
4537
  }
4271
- slides = this.extractSlides(pptStream);
4538
+ const pictures = this.extractPictures(cfbf, createdBlobUrls);
4539
+ slides = this.extractSlides(pptStream, pictures);
4272
4540
  } catch (err) {
4273
4541
  console.warn("[PptPlugin] Error extracting binary slides:", err);
4274
4542
  }
4275
4543
  if (slides.length === 0) {
4276
4544
  slides = [
4277
4545
  {
4546
+ slideIndex: 1,
4278
4547
  title: ctx.metadata.name || "PowerPoint Presentation",
4279
- texts: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"]
4548
+ paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
4549
+ tableColumns: []
4280
4550
  }
4281
4551
  ];
4282
4552
  }
@@ -4285,23 +4555,73 @@ var PptPlugin = class {
4285
4555
  currentSlide = idx;
4286
4556
  const s = slides[idx - 1];
4287
4557
  if (!s) return;
4558
+ let contentHtml = "";
4559
+ if (s.pictureUrl) {
4560
+ contentHtml = `
4561
+ <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
4562
+ <img src="${s.pictureUrl}" alt="${DOMPurify2.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
4563
+ </div>
4564
+ `;
4565
+ } else if (s.tableColumns.length > 0) {
4566
+ const cols = s.tableColumns;
4567
+ contentHtml = `
4568
+ <div style="flex: 1; overflow: auto; padding: 8px 0;">
4569
+ <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
4570
+ <thead>
4571
+ <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
4572
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2.sanitize(c)}</th>`).join("")}
4573
+ </tr>
4574
+ </thead>
4575
+ <tbody>
4576
+ ${[1, 2, 3, 4, 5].map((rowIdx) => `
4577
+ <tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
4578
+ ${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
4579
+ </tr>
4580
+ `).join("")}
4581
+ </tbody>
4582
+ </table>
4583
+ </div>
4584
+ `;
4585
+ } else {
4586
+ const pTags = s.paragraphs.map((p) => {
4587
+ const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
4588
+ return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2.sanitize(l)}</p>`).join("");
4589
+ }).join("");
4590
+ contentHtml = `
4591
+ <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
4592
+ ${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
4593
+ </div>
4594
+ `;
4595
+ }
4288
4596
  slideCard.innerHTML = `
4289
- <div style="position: absolute; top: 20px; right: 24px; font-size: 12px; color: #94a3b8; font-weight: 600;">
4290
- Slide ${idx} of ${totalSlides}
4291
- </div>
4292
- <div style="text-align: center; width: 100%;">
4293
- <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4294
- ${DOMPurify6.sanitize(s.title || `Slide ${idx}`)}
4295
- </h1>
4296
- <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4297
- ${s.texts.map((t) => `<div style="font-size: 18px; color: #334155; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${DOMPurify6.sanitize(t)}</div>`).join("")}
4597
+ <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;">
4598
+ <!-- Header Banner matching PowerPoint design -->
4599
+ <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;">
4600
+ <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
4601
+ ${DOMPurify2.sanitize(s.title)}
4602
+ </h1>
4603
+ ${s.subtitle ? `
4604
+ <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);">
4605
+ ${DOMPurify2.sanitize(s.subtitle)}
4606
+ </span>
4607
+ ` : `
4608
+ <span style="font-size: 12px; color: #365314; font-weight: 600;">
4609
+ Slide ${idx} / ${totalSlides}
4610
+ </span>
4611
+ `}
4298
4612
  </div>
4613
+
4614
+ <!-- Slide Content -->
4615
+ ${contentHtml}
4299
4616
  </div>
4300
4617
  `;
4301
4618
  ctx.emit("page-change", { page: currentSlide, total: totalSlides });
4302
4619
  };
4303
4620
  renderSlide(1);
4304
4621
  const cleanup = () => {
4622
+ for (const u of createdBlobUrls) {
4623
+ URL.revokeObjectURL(u);
4624
+ }
4305
4625
  container.remove();
4306
4626
  ctx.container.innerHTML = "";
4307
4627
  };
@@ -4341,13 +4661,48 @@ var PptPlugin = class {
4341
4661
  if (!ctx2d) return;
4342
4662
  canvas.width = 160;
4343
4663
  canvas.height = 90;
4344
- ctx2d.fillStyle = "#ffffff";
4664
+ ctx2d.fillStyle = "#334155";
4345
4665
  ctx2d.fillRect(0, 0, 160, 90);
4346
- ctx2d.fillStyle = "#1e3a8a";
4347
- ctx2d.font = "bold 11px sans-serif";
4348
- ctx2d.textAlign = "center";
4349
- const title = s.title.slice(0, 18) || `Slide ${idx + 1}`;
4350
- ctx2d.fillText(title, 80, 50);
4666
+ ctx2d.fillStyle = "#ffffff";
4667
+ ctx2d.fillRect(3, 3, 154, 84);
4668
+ ctx2d.fillStyle = "#84cc16";
4669
+ ctx2d.fillRect(6, 6, 148, 18);
4670
+ ctx2d.fillStyle = "#1e293b";
4671
+ ctx2d.font = "bold 9px sans-serif";
4672
+ ctx2d.textAlign = "left";
4673
+ const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
4674
+ ctx2d.fillText(displayTitle, 10, 19);
4675
+ if (s.subtitle) {
4676
+ ctx2d.fillStyle = "#38bdf8";
4677
+ ctx2d.fillRect(116, 9, 34, 12);
4678
+ ctx2d.fillStyle = "#ffffff";
4679
+ ctx2d.font = "bold 7px sans-serif";
4680
+ ctx2d.textAlign = "center";
4681
+ ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
4682
+ }
4683
+ if (s.pictureUrl) {
4684
+ ctx2d.fillStyle = "#3b82f6";
4685
+ ctx2d.fillRect(52, 34, 56, 42);
4686
+ ctx2d.fillStyle = "#ffffff";
4687
+ ctx2d.font = "8px sans-serif";
4688
+ ctx2d.textAlign = "center";
4689
+ ctx2d.fillText("Chart", 80, 58);
4690
+ } else if (s.tableColumns.length > 0) {
4691
+ ctx2d.strokeStyle = "#cbd5e1";
4692
+ ctx2d.lineWidth = 1;
4693
+ ctx2d.strokeRect(14, 32, 132, 46);
4694
+ for (let l = 1; l <= 3; l++) {
4695
+ ctx2d.beginPath();
4696
+ ctx2d.moveTo(14, 32 + l * 11);
4697
+ ctx2d.lineTo(146, 32 + l * 11);
4698
+ ctx2d.stroke();
4699
+ }
4700
+ } else {
4701
+ ctx2d.fillStyle = "#94a3b8";
4702
+ for (let l = 0; l < 4; l++) {
4703
+ ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
4704
+ }
4705
+ }
4351
4706
  }
4352
4707
  }));
4353
4708
  },
@@ -4365,66 +4720,165 @@ var PptPlugin = class {
4365
4720
  }
4366
4721
  };
4367
4722
  }
4723
+ /**
4724
+ * Extract PNG and JPEG images from the Pictures stream
4725
+ */
4726
+ extractPictures(cfbf, createdUrls) {
4727
+ const urls = [];
4728
+ try {
4729
+ const picStream = cfbf.readStream("Pictures");
4730
+ if (!picStream || picStream.length < 32) return urls;
4731
+ const pBuf = new Uint8Array(picStream);
4732
+ const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
4733
+ const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
4734
+ for (let i = 0; i <= pBuf.length - 8; i++) {
4735
+ let match = true;
4736
+ for (let j = 0; j < 8; j++) {
4737
+ if (pBuf[i + j] !== pngSig[j]) {
4738
+ match = false;
4739
+ break;
4740
+ }
4741
+ }
4742
+ if (match) {
4743
+ let endIdx = -1;
4744
+ for (let k = i + 8; k <= pBuf.length - 8; k++) {
4745
+ let endMatch = true;
4746
+ for (let j = 0; j < 8; j++) {
4747
+ if (pBuf[k + j] !== iendSig[j]) {
4748
+ endMatch = false;
4749
+ break;
4750
+ }
4751
+ }
4752
+ if (endMatch) {
4753
+ endIdx = k + 8;
4754
+ break;
4755
+ }
4756
+ }
4757
+ if (endIdx !== -1) {
4758
+ const pngBytes = pBuf.subarray(i, endIdx);
4759
+ const blob = new Blob([pngBytes], { type: "image/png" });
4760
+ const url = URL.createObjectURL(blob);
4761
+ urls.push(url);
4762
+ createdUrls.push(url);
4763
+ i = endIdx;
4764
+ }
4765
+ }
4766
+ }
4767
+ for (let i = 0; i <= pBuf.length - 3; i++) {
4768
+ if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
4769
+ let endIdx = -1;
4770
+ for (let k = i + 3; k < pBuf.length - 1; k++) {
4771
+ if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
4772
+ endIdx = k + 2;
4773
+ break;
4774
+ }
4775
+ }
4776
+ if (endIdx !== -1) {
4777
+ const jpgBytes = pBuf.subarray(i, endIdx);
4778
+ const blob = new Blob([jpgBytes], { type: "image/jpeg" });
4779
+ const url = URL.createObjectURL(blob);
4780
+ urls.push(url);
4781
+ createdUrls.push(url);
4782
+ i = endIdx;
4783
+ }
4784
+ }
4785
+ }
4786
+ } catch (err) {
4787
+ console.warn("[PptPlugin] Error extracting pictures:", err);
4788
+ }
4789
+ return urls;
4790
+ }
4368
4791
  /**
4369
4792
  * Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
4370
4793
  */
4371
- extractSlides(stream) {
4794
+ extractSlides(stream, pictures) {
4372
4795
  const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
4373
4796
  const len = stream.length;
4374
4797
  let offset = 0;
4375
4798
  const slides = [];
4376
- let currentSlideTexts = [];
4799
+ let picIdx = 0;
4377
4800
  while (offset + 8 <= len) {
4378
4801
  const recVerInst = view.getUint16(offset, true);
4379
4802
  const recType = view.getUint16(offset + 2, true);
4380
4803
  const recLen = view.getUint32(offset + 4, true);
4804
+ const isContainer = (recVerInst & 15) === 15;
4381
4805
  if (recType === 1006) {
4382
- if (currentSlideTexts.length > 0) {
4383
- const title = currentSlideTexts[0] || "Slide";
4384
- const texts = currentSlideTexts.slice(1);
4385
- slides.push({ title, texts });
4386
- currentSlideTexts = [];
4806
+ const slideEnd = Math.min(len, offset + 8 + recLen);
4807
+ const rawTexts = [];
4808
+ let hasOle = false;
4809
+ let sOff = offset + 8;
4810
+ while (sOff + 8 <= slideEnd) {
4811
+ const cVerInst = view.getUint16(sOff, true);
4812
+ const cType = view.getUint16(sOff + 2, true);
4813
+ const cLen = view.getUint32(sOff + 4, true);
4814
+ const cIsContainer = (cVerInst & 15) === 15;
4815
+ if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4816
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4817
+ const txt = new TextDecoder("latin1").decode(bytes).trim();
4818
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4819
+ rawTexts.push(txt);
4820
+ }
4821
+ } else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4822
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4823
+ const txt = new TextDecoder("utf-16le").decode(bytes).trim();
4824
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4825
+ rawTexts.push(txt);
4826
+ }
4827
+ } else if (cType === 3009 || cType === 3011) {
4828
+ hasOle = true;
4829
+ }
4830
+ if (cIsContainer) sOff += 8;
4831
+ else sOff += 8 + cLen;
4387
4832
  }
4388
- offset += 8;
4389
- continue;
4390
- }
4391
- if (recType === 3998 && recLen > 0 && offset + 8 + recLen <= len) {
4392
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4393
- const text = new TextDecoder("latin1").decode(bytes).trim();
4394
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4395
- currentSlideTexts.push(text);
4833
+ let title = "";
4834
+ let subtitle = "";
4835
+ const paragraphs = [];
4836
+ const tableColumns = [];
4837
+ for (const t of rawTexts) {
4838
+ if (!title && t.length < 60 && !t.includes("\n")) {
4839
+ title = t;
4840
+ } else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
4841
+ tableColumns.push(t);
4842
+ } else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
4843
+ subtitle = t;
4844
+ } else {
4845
+ paragraphs.push(t);
4846
+ }
4396
4847
  }
4397
- }
4398
- if (recType === 3999 && recLen > 0 && offset + 8 + recLen <= len) {
4399
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4400
- const text = new TextDecoder("utf-16le").decode(bytes).trim();
4401
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4402
- currentSlideTexts.push(text);
4848
+ let pictureUrl = null;
4849
+ if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
4850
+ pictureUrl = pictures[picIdx++];
4403
4851
  }
4852
+ slides.push({
4853
+ slideIndex: slides.length + 1,
4854
+ title: title || `Slide ${slides.length + 1}`,
4855
+ subtitle,
4856
+ paragraphs,
4857
+ tableColumns,
4858
+ pictureUrl,
4859
+ hasOle
4860
+ });
4404
4861
  }
4405
- const isContainer = (recVerInst & 15) === 15;
4406
- if (isContainer) {
4407
- offset += 8;
4408
- } else {
4409
- offset += 8 + recLen;
4410
- }
4411
- }
4412
- if (currentSlideTexts.length > 0) {
4413
- const title = currentSlideTexts[0] || "Slide";
4414
- const texts = currentSlideTexts.slice(1);
4415
- slides.push({ title, texts });
4862
+ if (isContainer) offset += 8;
4863
+ else offset += 8 + recLen;
4416
4864
  }
4417
4865
  if (slides.length === 0) {
4418
4866
  const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
4419
- const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{4,}/g) || [];
4420
- const filtered = matches.map((m) => m.trim()).filter((m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times"));
4421
- if (filtered.length > 0) {
4422
- const chunkSize = 4;
4423
- for (let i = 0; i < filtered.length; i += chunkSize) {
4424
- const chunk = filtered.slice(i, i + chunkSize);
4867
+ const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
4868
+ const clean = matches.map((m) => m.trim()).filter(
4869
+ (m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
4870
+ );
4871
+ if (clean.length > 0) {
4872
+ const chunkSize = 3;
4873
+ for (let i = 0; i < clean.length; i += chunkSize) {
4874
+ const chunk = clean.slice(i, i + chunkSize);
4425
4875
  slides.push({
4876
+ slideIndex: slides.length + 1,
4426
4877
  title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
4427
- texts: chunk.slice(1)
4878
+ subtitle: chunk.length > 2 ? chunk[1] : void 0,
4879
+ paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
4880
+ tableColumns: [],
4881
+ pictureUrl: pictures[slides.length] || null
4428
4882
  });
4429
4883
  }
4430
4884
  }