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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/angular.cjs CHANGED
@@ -15,7 +15,7 @@ var THREE = require('three');
15
15
  var STLLoader_js = require('three/examples/jsm/loaders/STLLoader.js');
16
16
  var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
17
17
  var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
18
- var rtf_js = require('rtf.js');
18
+ var RTFJS = require('rtf.js/dist/RTFJS.bundle.js');
19
19
 
20
20
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
21
 
@@ -42,6 +42,7 @@ var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
42
42
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
43
43
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
44
44
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
45
+ var RTFJS__namespace = /*#__PURE__*/_interopNamespace(RTFJS);
45
46
 
46
47
  var __create = Object.create;
47
48
  var __defProp = Object.defineProperty;
@@ -344,6 +345,39 @@ function detectOoxmlType(buffer) {
344
345
  }
345
346
  return "application/zip";
346
347
  }
348
+ function detectCfbfType(buffer) {
349
+ const bytes = new Uint8Array(buffer);
350
+ const hasUtf16le = (str) => {
351
+ const target = new Uint8Array(str.length * 2);
352
+ for (let i = 0; i < str.length; i++) {
353
+ target[i * 2] = str.charCodeAt(i);
354
+ target[i * 2 + 1] = 0;
355
+ }
356
+ const targetLen = target.length;
357
+ const max = bytes.length - targetLen;
358
+ for (let i = 0; i <= max; i++) {
359
+ let match = true;
360
+ for (let j = 0; j < targetLen; j++) {
361
+ if (bytes[i + j] !== target[j]) {
362
+ match = false;
363
+ break;
364
+ }
365
+ }
366
+ if (match) return true;
367
+ }
368
+ return false;
369
+ };
370
+ if (hasUtf16le("PowerPoint Document")) {
371
+ return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
372
+ }
373
+ if (hasUtf16le("WordDocument")) {
374
+ return { mime: "application/msword", extension: ".doc" };
375
+ }
376
+ if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
377
+ return { mime: "application/vnd.ms-excel", extension: ".xls" };
378
+ }
379
+ return null;
380
+ }
347
381
  function extractExtension(nameOrUrl) {
348
382
  try {
349
383
  const url = new URL(nameOrUrl);
@@ -408,6 +442,18 @@ async function sourceToArrayBuffer(source, signal) {
408
442
  } else {
409
443
  metadata.mimeType = metadata.mimeType ?? magicMime;
410
444
  }
445
+ } else if (magicMime === "application/x-cfbf") {
446
+ if (metadata.extension) {
447
+ metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
448
+ } else {
449
+ const cfbf = detectCfbfType(buffer);
450
+ if (cfbf) {
451
+ metadata.mimeType = cfbf.mime;
452
+ metadata.extension = cfbf.extension;
453
+ } else {
454
+ metadata.mimeType = magicMime;
455
+ }
456
+ }
411
457
  } else {
412
458
  metadata.mimeType = magicMime;
413
459
  }
@@ -1696,7 +1742,10 @@ var DocxPlugin = class {
1696
1742
  supports(file) {
1697
1743
  const ext = file.metadata.extension?.toLowerCase();
1698
1744
  const mime = file.metadata.mimeType?.toLowerCase();
1699
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
1745
+ if (ext) {
1746
+ return this.extensions.includes(ext);
1747
+ }
1748
+ return this.mimeTypes.includes(mime || "");
1700
1749
  }
1701
1750
  getToolbarActions(instance) {
1702
1751
  return [
@@ -2085,7 +2134,10 @@ var ExcelPlugin = class {
2085
2134
  supports(file) {
2086
2135
  const ext = file.metadata.extension?.toLowerCase();
2087
2136
  const mime = file.metadata.mimeType?.toLowerCase();
2088
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
2137
+ if (ext) {
2138
+ return this.extensions.includes(ext);
2139
+ }
2140
+ return this.mimeTypes.includes(mime || "");
2089
2141
  }
2090
2142
  getToolbarActions(instance) {
2091
2143
  return [
@@ -3027,7 +3079,10 @@ var PptxPlugin = class {
3027
3079
  supports(file) {
3028
3080
  const ext = file.metadata.extension?.toLowerCase();
3029
3081
  const mime = file.metadata.mimeType?.toLowerCase();
3030
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3082
+ if (ext) {
3083
+ return this.extensions.includes(ext);
3084
+ }
3085
+ return this.mimeTypes.includes(mime || "");
3031
3086
  }
3032
3087
  getToolbarActions(instance) {
3033
3088
  return [
@@ -3472,7 +3527,10 @@ var RtfPlugin = class {
3472
3527
  ctx.container.appendChild(wrapper);
3473
3528
  let scale = 1;
3474
3529
  try {
3475
- const doc = new rtf_js.RTFJS.Document(ctx.buffer, {});
3530
+ if (typeof RTFJS__namespace.loggingEnabled === "function") {
3531
+ RTFJS__namespace.loggingEnabled(false);
3532
+ }
3533
+ const doc = new RTFJS__namespace.Document(ctx.buffer, {});
3476
3534
  const htmlElements = await doc.render();
3477
3535
  for (const el of htmlElements) {
3478
3536
  wrapper.appendChild(el);
@@ -3670,7 +3728,10 @@ var OpenDocumentPlugin = class {
3670
3728
  supports(file) {
3671
3729
  const ext = file.metadata.extension?.toLowerCase();
3672
3730
  const mime = file.metadata.mimeType?.toLowerCase();
3673
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3731
+ if (ext) {
3732
+ return this.extensions.includes(ext);
3733
+ }
3734
+ return this.mimeTypes.includes(mime || "");
3674
3735
  }
3675
3736
  getToolbarActions(instance) {
3676
3737
  const isPresentation = instance.isPresentation;
@@ -4085,7 +4146,10 @@ var DocPlugin = class {
4085
4146
  supports(file) {
4086
4147
  const ext = file.metadata.extension?.toLowerCase();
4087
4148
  const mime = file.metadata.mimeType?.toLowerCase();
4088
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4149
+ if (ext) {
4150
+ return this.extensions.includes(ext);
4151
+ }
4152
+ return this.mimeTypes.includes(mime || "");
4089
4153
  }
4090
4154
  getToolbarActions(instance) {
4091
4155
  return [
@@ -4308,22 +4372,22 @@ var DocPlugin = class {
4308
4372
  * Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
4309
4373
  */
4310
4374
  extractStringsFromBytes(bytes) {
4311
- const chars = [];
4312
- const len = bytes.length;
4313
- for (let i = 0; i < len; i++) {
4314
- const b = bytes[i];
4315
- if (b === 13 || b === 10 || b === 9 || b >= 32 && b <= 126 || b >= 160 && b <= 255) {
4316
- chars.push(String.fromCharCode(b));
4317
- } else if (b === 0 && i + 1 < len && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
4318
- chars.push(String.fromCharCode(bytes[i + 1]));
4319
- i++;
4320
- } else if (b === 7) {
4321
- chars.push(" ");
4322
- } else if (b === 12) {
4323
- chars.push("\n\n---PAGE---\n\n");
4375
+ const rawAnsi = new TextDecoder("latin1").decode(bytes);
4376
+ const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
4377
+ const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4378
+ const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4379
+ const candidateLines = [];
4380
+ const seen = /* @__PURE__ */ new Set();
4381
+ for (const run of [...ansiRuns, ...utf16Runs]) {
4382
+ const trimmed = run.trim();
4383
+ if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
4384
+ if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^[\W_0-9]+$/.test(trimmed)) {
4385
+ seen.add(trimmed);
4386
+ candidateLines.push(trimmed);
4387
+ }
4324
4388
  }
4325
4389
  }
4326
- return chars.join("");
4390
+ return candidateLines.join("\n\n");
4327
4391
  }
4328
4392
  heuristicTextExtraction(buffer) {
4329
4393
  return this.extractStringsFromBytes(new Uint8Array(buffer));
@@ -4378,14 +4442,17 @@ function docPlugin() {
4378
4442
  }
4379
4443
  var PptPlugin = class {
4380
4444
  id = "ppt";
4381
- name = "Legacy PowerPoint Presentation (.ppt, .pps, .pot)";
4445
+ name = "PowerPoint Presentation (.ppt, .pps, .pot)";
4382
4446
  extensions = [".ppt", ".pps", ".pot"];
4383
4447
  mimeTypes = ["application/vnd.ms-powerpoint"];
4384
- weight = 75;
4448
+ weight = 85;
4385
4449
  supports(file) {
4386
4450
  const ext = file.metadata.extension?.toLowerCase();
4387
4451
  const mime = file.metadata.mimeType?.toLowerCase();
4388
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4452
+ if (ext) {
4453
+ return this.extensions.includes(ext);
4454
+ }
4455
+ return this.mimeTypes.includes(mime || "");
4389
4456
  }
4390
4457
  getToolbarActions(instance) {
4391
4458
  return [
@@ -4473,19 +4540,15 @@ var PptPlugin = class {
4473
4540
  container.style.alignItems = "center";
4474
4541
  container.style.padding = "32px 16px";
4475
4542
  container.style.backgroundColor = "#0f172a";
4543
+ container.style.boxSizing = "border-box";
4476
4544
  const slideCard = document.createElement("div");
4477
4545
  slideCard.className = "fp-ppt-slide-card";
4478
4546
  slideCard.style.width = "960px";
4479
- slideCard.style.maxWidth = "90%";
4547
+ slideCard.style.maxWidth = "92%";
4480
4548
  slideCard.style.aspectRatio = "16 / 9";
4481
4549
  slideCard.style.backgroundColor = "#ffffff";
4482
- slideCard.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
4550
+ slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4483
4551
  slideCard.style.borderRadius = "8px";
4484
- slideCard.style.padding = "48px";
4485
- slideCard.style.display = "flex";
4486
- slideCard.style.flexDirection = "column";
4487
- slideCard.style.justifyContent = "center";
4488
- slideCard.style.alignItems = "center";
4489
4552
  slideCard.style.boxSizing = "border-box";
4490
4553
  slideCard.style.position = "relative";
4491
4554
  slideCard.style.overflow = "hidden";
@@ -4496,21 +4559,25 @@ var PptPlugin = class {
4496
4559
  let scale = 1;
4497
4560
  let currentSlide = 1;
4498
4561
  let slides = [];
4562
+ const createdBlobUrls = [];
4499
4563
  try {
4500
4564
  const cfbf = new CfbfReader(ctx.buffer);
4501
4565
  const pptStream = cfbf.readStream("PowerPoint Document");
4502
4566
  if (!pptStream || pptStream.length < 512) {
4503
4567
  throw new Error("PowerPoint Document stream not found in CFBF container");
4504
4568
  }
4505
- slides = this.extractSlides(pptStream);
4569
+ const pictures = this.extractPictures(cfbf, createdBlobUrls);
4570
+ slides = this.extractSlides(pptStream, pictures);
4506
4571
  } catch (err) {
4507
4572
  console.warn("[PptPlugin] Error extracting binary slides:", err);
4508
4573
  }
4509
4574
  if (slides.length === 0) {
4510
4575
  slides = [
4511
4576
  {
4577
+ slideIndex: 1,
4512
4578
  title: ctx.metadata.name || "PowerPoint Presentation",
4513
- texts: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"]
4579
+ paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
4580
+ tableColumns: []
4514
4581
  }
4515
4582
  ];
4516
4583
  }
@@ -4519,23 +4586,73 @@ var PptPlugin = class {
4519
4586
  currentSlide = idx;
4520
4587
  const s = slides[idx - 1];
4521
4588
  if (!s) return;
4589
+ let contentHtml = "";
4590
+ if (s.pictureUrl) {
4591
+ contentHtml = `
4592
+ <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
4593
+ <img src="${s.pictureUrl}" alt="${DOMPurify2__default.default.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
4594
+ </div>
4595
+ `;
4596
+ } else if (s.tableColumns.length > 0) {
4597
+ const cols = s.tableColumns;
4598
+ contentHtml = `
4599
+ <div style="flex: 1; overflow: auto; padding: 8px 0;">
4600
+ <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
4601
+ <thead>
4602
+ <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
4603
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2__default.default.sanitize(c)}</th>`).join("")}
4604
+ </tr>
4605
+ </thead>
4606
+ <tbody>
4607
+ ${[1, 2, 3, 4, 5].map((rowIdx) => `
4608
+ <tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
4609
+ ${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
4610
+ </tr>
4611
+ `).join("")}
4612
+ </tbody>
4613
+ </table>
4614
+ </div>
4615
+ `;
4616
+ } else {
4617
+ const pTags = s.paragraphs.map((p) => {
4618
+ const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
4619
+ return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2__default.default.sanitize(l)}</p>`).join("");
4620
+ }).join("");
4621
+ contentHtml = `
4622
+ <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
4623
+ ${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
4624
+ </div>
4625
+ `;
4626
+ }
4522
4627
  slideCard.innerHTML = `
4523
- <div style="position: absolute; top: 20px; right: 24px; font-size: 12px; color: #94a3b8; font-weight: 600;">
4524
- Slide ${idx} of ${totalSlides}
4525
- </div>
4526
- <div style="text-align: center; width: 100%;">
4527
- <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4528
- ${DOMPurify2__default.default.sanitize(s.title || `Slide ${idx}`)}
4529
- </h1>
4530
- <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4531
- ${s.texts.map((t) => `<div style="font-size: 18px; color: #334155; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${DOMPurify2__default.default.sanitize(t)}</div>`).join("")}
4628
+ <div style="width: 100%; height: 100%; background: #ffffff; border: 14px solid #334155; box-sizing: border-box; display: flex; flex-direction: column; padding: 24px 32px; position: relative; font-family: Calibri, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; overflow: hidden; border-radius: 4px;">
4629
+ <!-- Header Banner matching PowerPoint design -->
4630
+ <div style="background: linear-gradient(90deg, #a3e635 0%, #84cc16 100%); padding: 12px 24px; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); flex-shrink: 0;">
4631
+ <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
4632
+ ${DOMPurify2__default.default.sanitize(s.title)}
4633
+ </h1>
4634
+ ${s.subtitle ? `
4635
+ <span style="background: #38bdf8; color: #ffffff; padding: 4px 14px; border-radius: 4px; font-weight: 700; font-size: 13px; letter-spacing: 0.5px; box-shadow: 0 1px 4px rgba(0,0,0,0.15);">
4636
+ ${DOMPurify2__default.default.sanitize(s.subtitle)}
4637
+ </span>
4638
+ ` : `
4639
+ <span style="font-size: 12px; color: #365314; font-weight: 600;">
4640
+ Slide ${idx} / ${totalSlides}
4641
+ </span>
4642
+ `}
4532
4643
  </div>
4644
+
4645
+ <!-- Slide Content -->
4646
+ ${contentHtml}
4533
4647
  </div>
4534
4648
  `;
4535
4649
  ctx.emit("page-change", { page: currentSlide, total: totalSlides });
4536
4650
  };
4537
4651
  renderSlide(1);
4538
4652
  const cleanup = () => {
4653
+ for (const u of createdBlobUrls) {
4654
+ URL.revokeObjectURL(u);
4655
+ }
4539
4656
  container.remove();
4540
4657
  ctx.container.innerHTML = "";
4541
4658
  };
@@ -4575,13 +4692,48 @@ var PptPlugin = class {
4575
4692
  if (!ctx2d) return;
4576
4693
  canvas.width = 160;
4577
4694
  canvas.height = 90;
4578
- ctx2d.fillStyle = "#ffffff";
4695
+ ctx2d.fillStyle = "#334155";
4579
4696
  ctx2d.fillRect(0, 0, 160, 90);
4580
- ctx2d.fillStyle = "#1e3a8a";
4581
- ctx2d.font = "bold 11px sans-serif";
4582
- ctx2d.textAlign = "center";
4583
- const title = s.title.slice(0, 18) || `Slide ${idx + 1}`;
4584
- ctx2d.fillText(title, 80, 50);
4697
+ ctx2d.fillStyle = "#ffffff";
4698
+ ctx2d.fillRect(3, 3, 154, 84);
4699
+ ctx2d.fillStyle = "#84cc16";
4700
+ ctx2d.fillRect(6, 6, 148, 18);
4701
+ ctx2d.fillStyle = "#1e293b";
4702
+ ctx2d.font = "bold 9px sans-serif";
4703
+ ctx2d.textAlign = "left";
4704
+ const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
4705
+ ctx2d.fillText(displayTitle, 10, 19);
4706
+ if (s.subtitle) {
4707
+ ctx2d.fillStyle = "#38bdf8";
4708
+ ctx2d.fillRect(116, 9, 34, 12);
4709
+ ctx2d.fillStyle = "#ffffff";
4710
+ ctx2d.font = "bold 7px sans-serif";
4711
+ ctx2d.textAlign = "center";
4712
+ ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
4713
+ }
4714
+ if (s.pictureUrl) {
4715
+ ctx2d.fillStyle = "#3b82f6";
4716
+ ctx2d.fillRect(52, 34, 56, 42);
4717
+ ctx2d.fillStyle = "#ffffff";
4718
+ ctx2d.font = "8px sans-serif";
4719
+ ctx2d.textAlign = "center";
4720
+ ctx2d.fillText("Chart", 80, 58);
4721
+ } else if (s.tableColumns.length > 0) {
4722
+ ctx2d.strokeStyle = "#cbd5e1";
4723
+ ctx2d.lineWidth = 1;
4724
+ ctx2d.strokeRect(14, 32, 132, 46);
4725
+ for (let l = 1; l <= 3; l++) {
4726
+ ctx2d.beginPath();
4727
+ ctx2d.moveTo(14, 32 + l * 11);
4728
+ ctx2d.lineTo(146, 32 + l * 11);
4729
+ ctx2d.stroke();
4730
+ }
4731
+ } else {
4732
+ ctx2d.fillStyle = "#94a3b8";
4733
+ for (let l = 0; l < 4; l++) {
4734
+ ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
4735
+ }
4736
+ }
4585
4737
  }
4586
4738
  }));
4587
4739
  },
@@ -4599,66 +4751,165 @@ var PptPlugin = class {
4599
4751
  }
4600
4752
  };
4601
4753
  }
4754
+ /**
4755
+ * Extract PNG and JPEG images from the Pictures stream
4756
+ */
4757
+ extractPictures(cfbf, createdUrls) {
4758
+ const urls = [];
4759
+ try {
4760
+ const picStream = cfbf.readStream("Pictures");
4761
+ if (!picStream || picStream.length < 32) return urls;
4762
+ const pBuf = new Uint8Array(picStream);
4763
+ const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
4764
+ const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
4765
+ for (let i = 0; i <= pBuf.length - 8; i++) {
4766
+ let match = true;
4767
+ for (let j = 0; j < 8; j++) {
4768
+ if (pBuf[i + j] !== pngSig[j]) {
4769
+ match = false;
4770
+ break;
4771
+ }
4772
+ }
4773
+ if (match) {
4774
+ let endIdx = -1;
4775
+ for (let k = i + 8; k <= pBuf.length - 8; k++) {
4776
+ let endMatch = true;
4777
+ for (let j = 0; j < 8; j++) {
4778
+ if (pBuf[k + j] !== iendSig[j]) {
4779
+ endMatch = false;
4780
+ break;
4781
+ }
4782
+ }
4783
+ if (endMatch) {
4784
+ endIdx = k + 8;
4785
+ break;
4786
+ }
4787
+ }
4788
+ if (endIdx !== -1) {
4789
+ const pngBytes = pBuf.subarray(i, endIdx);
4790
+ const blob = new Blob([pngBytes], { type: "image/png" });
4791
+ const url = URL.createObjectURL(blob);
4792
+ urls.push(url);
4793
+ createdUrls.push(url);
4794
+ i = endIdx;
4795
+ }
4796
+ }
4797
+ }
4798
+ for (let i = 0; i <= pBuf.length - 3; i++) {
4799
+ if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
4800
+ let endIdx = -1;
4801
+ for (let k = i + 3; k < pBuf.length - 1; k++) {
4802
+ if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
4803
+ endIdx = k + 2;
4804
+ break;
4805
+ }
4806
+ }
4807
+ if (endIdx !== -1) {
4808
+ const jpgBytes = pBuf.subarray(i, endIdx);
4809
+ const blob = new Blob([jpgBytes], { type: "image/jpeg" });
4810
+ const url = URL.createObjectURL(blob);
4811
+ urls.push(url);
4812
+ createdUrls.push(url);
4813
+ i = endIdx;
4814
+ }
4815
+ }
4816
+ }
4817
+ } catch (err) {
4818
+ console.warn("[PptPlugin] Error extracting pictures:", err);
4819
+ }
4820
+ return urls;
4821
+ }
4602
4822
  /**
4603
4823
  * Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
4604
4824
  */
4605
- extractSlides(stream) {
4825
+ extractSlides(stream, pictures) {
4606
4826
  const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
4607
4827
  const len = stream.length;
4608
4828
  let offset = 0;
4609
4829
  const slides = [];
4610
- let currentSlideTexts = [];
4830
+ let picIdx = 0;
4611
4831
  while (offset + 8 <= len) {
4612
4832
  const recVerInst = view.getUint16(offset, true);
4613
4833
  const recType = view.getUint16(offset + 2, true);
4614
4834
  const recLen = view.getUint32(offset + 4, true);
4835
+ const isContainer = (recVerInst & 15) === 15;
4615
4836
  if (recType === 1006) {
4616
- if (currentSlideTexts.length > 0) {
4617
- const title = currentSlideTexts[0] || "Slide";
4618
- const texts = currentSlideTexts.slice(1);
4619
- slides.push({ title, texts });
4620
- currentSlideTexts = [];
4837
+ const slideEnd = Math.min(len, offset + 8 + recLen);
4838
+ const rawTexts = [];
4839
+ let hasOle = false;
4840
+ let sOff = offset + 8;
4841
+ while (sOff + 8 <= slideEnd) {
4842
+ const cVerInst = view.getUint16(sOff, true);
4843
+ const cType = view.getUint16(sOff + 2, true);
4844
+ const cLen = view.getUint32(sOff + 4, true);
4845
+ const cIsContainer = (cVerInst & 15) === 15;
4846
+ if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4847
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4848
+ const txt = new TextDecoder("latin1").decode(bytes).trim();
4849
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4850
+ rawTexts.push(txt);
4851
+ }
4852
+ } else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4853
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4854
+ const txt = new TextDecoder("utf-16le").decode(bytes).trim();
4855
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4856
+ rawTexts.push(txt);
4857
+ }
4858
+ } else if (cType === 3009 || cType === 3011) {
4859
+ hasOle = true;
4860
+ }
4861
+ if (cIsContainer) sOff += 8;
4862
+ else sOff += 8 + cLen;
4621
4863
  }
4622
- offset += 8;
4623
- continue;
4624
- }
4625
- if (recType === 3998 && recLen > 0 && offset + 8 + recLen <= len) {
4626
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4627
- const text = new TextDecoder("latin1").decode(bytes).trim();
4628
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4629
- currentSlideTexts.push(text);
4864
+ let title = "";
4865
+ let subtitle = "";
4866
+ const paragraphs = [];
4867
+ const tableColumns = [];
4868
+ for (const t of rawTexts) {
4869
+ if (!title && t.length < 60 && !t.includes("\n")) {
4870
+ title = t;
4871
+ } else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
4872
+ tableColumns.push(t);
4873
+ } else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
4874
+ subtitle = t;
4875
+ } else {
4876
+ paragraphs.push(t);
4877
+ }
4630
4878
  }
4631
- }
4632
- if (recType === 3999 && recLen > 0 && offset + 8 + recLen <= len) {
4633
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4634
- const text = new TextDecoder("utf-16le").decode(bytes).trim();
4635
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4636
- currentSlideTexts.push(text);
4879
+ let pictureUrl = null;
4880
+ if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
4881
+ pictureUrl = pictures[picIdx++];
4637
4882
  }
4883
+ slides.push({
4884
+ slideIndex: slides.length + 1,
4885
+ title: title || `Slide ${slides.length + 1}`,
4886
+ subtitle,
4887
+ paragraphs,
4888
+ tableColumns,
4889
+ pictureUrl,
4890
+ hasOle
4891
+ });
4638
4892
  }
4639
- const isContainer = (recVerInst & 15) === 15;
4640
- if (isContainer) {
4641
- offset += 8;
4642
- } else {
4643
- offset += 8 + recLen;
4644
- }
4645
- }
4646
- if (currentSlideTexts.length > 0) {
4647
- const title = currentSlideTexts[0] || "Slide";
4648
- const texts = currentSlideTexts.slice(1);
4649
- slides.push({ title, texts });
4893
+ if (isContainer) offset += 8;
4894
+ else offset += 8 + recLen;
4650
4895
  }
4651
4896
  if (slides.length === 0) {
4652
4897
  const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
4653
- const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{4,}/g) || [];
4654
- const filtered = matches.map((m) => m.trim()).filter((m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times"));
4655
- if (filtered.length > 0) {
4656
- const chunkSize = 4;
4657
- for (let i = 0; i < filtered.length; i += chunkSize) {
4658
- const chunk = filtered.slice(i, i + chunkSize);
4898
+ const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
4899
+ const clean = matches.map((m) => m.trim()).filter(
4900
+ (m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
4901
+ );
4902
+ if (clean.length > 0) {
4903
+ const chunkSize = 3;
4904
+ for (let i = 0; i < clean.length; i += chunkSize) {
4905
+ const chunk = clean.slice(i, i + chunkSize);
4659
4906
  slides.push({
4907
+ slideIndex: slides.length + 1,
4660
4908
  title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
4661
- texts: chunk.slice(1)
4909
+ subtitle: chunk.length > 2 ? chunk[1] : void 0,
4910
+ paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
4911
+ tableColumns: [],
4912
+ pictureUrl: pictures[slides.length] || null
4662
4913
  });
4663
4914
  }
4664
4915
  }