@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/vue.cjs CHANGED
@@ -14,7 +14,7 @@ var THREE = require('three');
14
14
  var STLLoader_js = require('three/examples/jsm/loaders/STLLoader.js');
15
15
  var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
16
16
  var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
17
- var rtf_js = require('rtf.js');
17
+ var RTFJS = require('rtf.js/dist/RTFJS.bundle.js');
18
18
 
19
19
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
20
20
 
@@ -41,6 +41,7 @@ var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
41
41
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
42
42
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
43
43
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
44
+ var RTFJS__namespace = /*#__PURE__*/_interopNamespace(RTFJS);
44
45
 
45
46
  // src/vue.ts
46
47
  var EventEmitter = class {
@@ -296,6 +297,39 @@ function detectOoxmlType(buffer) {
296
297
  }
297
298
  return "application/zip";
298
299
  }
300
+ function detectCfbfType(buffer) {
301
+ const bytes = new Uint8Array(buffer);
302
+ const hasUtf16le = (str) => {
303
+ const target = new Uint8Array(str.length * 2);
304
+ for (let i = 0; i < str.length; i++) {
305
+ target[i * 2] = str.charCodeAt(i);
306
+ target[i * 2 + 1] = 0;
307
+ }
308
+ const targetLen = target.length;
309
+ const max = bytes.length - targetLen;
310
+ for (let i = 0; i <= max; i++) {
311
+ let match = true;
312
+ for (let j = 0; j < targetLen; j++) {
313
+ if (bytes[i + j] !== target[j]) {
314
+ match = false;
315
+ break;
316
+ }
317
+ }
318
+ if (match) return true;
319
+ }
320
+ return false;
321
+ };
322
+ if (hasUtf16le("PowerPoint Document")) {
323
+ return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
324
+ }
325
+ if (hasUtf16le("WordDocument")) {
326
+ return { mime: "application/msword", extension: ".doc" };
327
+ }
328
+ if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
329
+ return { mime: "application/vnd.ms-excel", extension: ".xls" };
330
+ }
331
+ return null;
332
+ }
299
333
  function extractExtension(nameOrUrl) {
300
334
  try {
301
335
  const url = new URL(nameOrUrl);
@@ -360,6 +394,18 @@ async function sourceToArrayBuffer(source, signal) {
360
394
  } else {
361
395
  metadata.mimeType = metadata.mimeType ?? magicMime;
362
396
  }
397
+ } else if (magicMime === "application/x-cfbf") {
398
+ if (metadata.extension) {
399
+ metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
400
+ } else {
401
+ const cfbf = detectCfbfType(buffer);
402
+ if (cfbf) {
403
+ metadata.mimeType = cfbf.mime;
404
+ metadata.extension = cfbf.extension;
405
+ } else {
406
+ metadata.mimeType = magicMime;
407
+ }
408
+ }
363
409
  } else {
364
410
  metadata.mimeType = magicMime;
365
411
  }
@@ -1648,7 +1694,10 @@ var DocxPlugin = class {
1648
1694
  supports(file) {
1649
1695
  const ext = file.metadata.extension?.toLowerCase();
1650
1696
  const mime = file.metadata.mimeType?.toLowerCase();
1651
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
1697
+ if (ext) {
1698
+ return this.extensions.includes(ext);
1699
+ }
1700
+ return this.mimeTypes.includes(mime || "");
1652
1701
  }
1653
1702
  getToolbarActions(instance) {
1654
1703
  return [
@@ -2037,7 +2086,10 @@ var ExcelPlugin = class {
2037
2086
  supports(file) {
2038
2087
  const ext = file.metadata.extension?.toLowerCase();
2039
2088
  const mime = file.metadata.mimeType?.toLowerCase();
2040
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
2089
+ if (ext) {
2090
+ return this.extensions.includes(ext);
2091
+ }
2092
+ return this.mimeTypes.includes(mime || "");
2041
2093
  }
2042
2094
  getToolbarActions(instance) {
2043
2095
  return [
@@ -2979,7 +3031,10 @@ var PptxPlugin = class {
2979
3031
  supports(file) {
2980
3032
  const ext = file.metadata.extension?.toLowerCase();
2981
3033
  const mime = file.metadata.mimeType?.toLowerCase();
2982
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3034
+ if (ext) {
3035
+ return this.extensions.includes(ext);
3036
+ }
3037
+ return this.mimeTypes.includes(mime || "");
2983
3038
  }
2984
3039
  getToolbarActions(instance) {
2985
3040
  return [
@@ -3424,7 +3479,10 @@ var RtfPlugin = class {
3424
3479
  ctx.container.appendChild(wrapper);
3425
3480
  let scale = 1;
3426
3481
  try {
3427
- const doc = new rtf_js.RTFJS.Document(ctx.buffer, {});
3482
+ if (typeof RTFJS__namespace.loggingEnabled === "function") {
3483
+ RTFJS__namespace.loggingEnabled(false);
3484
+ }
3485
+ const doc = new RTFJS__namespace.Document(ctx.buffer, {});
3428
3486
  const htmlElements = await doc.render();
3429
3487
  for (const el of htmlElements) {
3430
3488
  wrapper.appendChild(el);
@@ -3622,7 +3680,10 @@ var OpenDocumentPlugin = class {
3622
3680
  supports(file) {
3623
3681
  const ext = file.metadata.extension?.toLowerCase();
3624
3682
  const mime = file.metadata.mimeType?.toLowerCase();
3625
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3683
+ if (ext) {
3684
+ return this.extensions.includes(ext);
3685
+ }
3686
+ return this.mimeTypes.includes(mime || "");
3626
3687
  }
3627
3688
  getToolbarActions(instance) {
3628
3689
  const isPresentation = instance.isPresentation;
@@ -4037,7 +4098,10 @@ var DocPlugin = class {
4037
4098
  supports(file) {
4038
4099
  const ext = file.metadata.extension?.toLowerCase();
4039
4100
  const mime = file.metadata.mimeType?.toLowerCase();
4040
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4101
+ if (ext) {
4102
+ return this.extensions.includes(ext);
4103
+ }
4104
+ return this.mimeTypes.includes(mime || "");
4041
4105
  }
4042
4106
  getToolbarActions(instance) {
4043
4107
  return [
@@ -4260,22 +4324,22 @@ var DocPlugin = class {
4260
4324
  * Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
4261
4325
  */
4262
4326
  extractStringsFromBytes(bytes) {
4263
- const chars = [];
4264
- const len = bytes.length;
4265
- for (let i = 0; i < len; i++) {
4266
- const b = bytes[i];
4267
- if (b === 13 || b === 10 || b === 9 || b >= 32 && b <= 126 || b >= 160 && b <= 255) {
4268
- chars.push(String.fromCharCode(b));
4269
- } else if (b === 0 && i + 1 < len && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
4270
- chars.push(String.fromCharCode(bytes[i + 1]));
4271
- i++;
4272
- } else if (b === 7) {
4273
- chars.push(" ");
4274
- } else if (b === 12) {
4275
- chars.push("\n\n---PAGE---\n\n");
4327
+ const rawAnsi = new TextDecoder("latin1").decode(bytes);
4328
+ const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
4329
+ const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4330
+ const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
4331
+ const candidateLines = [];
4332
+ const seen = /* @__PURE__ */ new Set();
4333
+ for (const run of [...ansiRuns, ...utf16Runs]) {
4334
+ const trimmed = run.trim();
4335
+ if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
4336
+ if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^[\W_0-9]+$/.test(trimmed)) {
4337
+ seen.add(trimmed);
4338
+ candidateLines.push(trimmed);
4339
+ }
4276
4340
  }
4277
4341
  }
4278
- return chars.join("");
4342
+ return candidateLines.join("\n\n");
4279
4343
  }
4280
4344
  heuristicTextExtraction(buffer) {
4281
4345
  return this.extractStringsFromBytes(new Uint8Array(buffer));
@@ -4330,14 +4394,17 @@ function docPlugin() {
4330
4394
  }
4331
4395
  var PptPlugin = class {
4332
4396
  id = "ppt";
4333
- name = "Legacy PowerPoint Presentation (.ppt, .pps, .pot)";
4397
+ name = "PowerPoint Presentation (.ppt, .pps, .pot)";
4334
4398
  extensions = [".ppt", ".pps", ".pot"];
4335
4399
  mimeTypes = ["application/vnd.ms-powerpoint"];
4336
- weight = 75;
4400
+ weight = 85;
4337
4401
  supports(file) {
4338
4402
  const ext = file.metadata.extension?.toLowerCase();
4339
4403
  const mime = file.metadata.mimeType?.toLowerCase();
4340
- return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
4404
+ if (ext) {
4405
+ return this.extensions.includes(ext);
4406
+ }
4407
+ return this.mimeTypes.includes(mime || "");
4341
4408
  }
4342
4409
  getToolbarActions(instance) {
4343
4410
  return [
@@ -4425,19 +4492,15 @@ var PptPlugin = class {
4425
4492
  container.style.alignItems = "center";
4426
4493
  container.style.padding = "32px 16px";
4427
4494
  container.style.backgroundColor = "#0f172a";
4495
+ container.style.boxSizing = "border-box";
4428
4496
  const slideCard = document.createElement("div");
4429
4497
  slideCard.className = "fp-ppt-slide-card";
4430
4498
  slideCard.style.width = "960px";
4431
- slideCard.style.maxWidth = "90%";
4499
+ slideCard.style.maxWidth = "92%";
4432
4500
  slideCard.style.aspectRatio = "16 / 9";
4433
4501
  slideCard.style.backgroundColor = "#ffffff";
4434
- slideCard.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
4502
+ slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
4435
4503
  slideCard.style.borderRadius = "8px";
4436
- slideCard.style.padding = "48px";
4437
- slideCard.style.display = "flex";
4438
- slideCard.style.flexDirection = "column";
4439
- slideCard.style.justifyContent = "center";
4440
- slideCard.style.alignItems = "center";
4441
4504
  slideCard.style.boxSizing = "border-box";
4442
4505
  slideCard.style.position = "relative";
4443
4506
  slideCard.style.overflow = "hidden";
@@ -4448,21 +4511,25 @@ var PptPlugin = class {
4448
4511
  let scale = 1;
4449
4512
  let currentSlide = 1;
4450
4513
  let slides = [];
4514
+ const createdBlobUrls = [];
4451
4515
  try {
4452
4516
  const cfbf = new CfbfReader(ctx.buffer);
4453
4517
  const pptStream = cfbf.readStream("PowerPoint Document");
4454
4518
  if (!pptStream || pptStream.length < 512) {
4455
4519
  throw new Error("PowerPoint Document stream not found in CFBF container");
4456
4520
  }
4457
- slides = this.extractSlides(pptStream);
4521
+ const pictures = this.extractPictures(cfbf, createdBlobUrls);
4522
+ slides = this.extractSlides(pptStream, pictures);
4458
4523
  } catch (err) {
4459
4524
  console.warn("[PptPlugin] Error extracting binary slides:", err);
4460
4525
  }
4461
4526
  if (slides.length === 0) {
4462
4527
  slides = [
4463
4528
  {
4529
+ slideIndex: 1,
4464
4530
  title: ctx.metadata.name || "PowerPoint Presentation",
4465
- texts: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"]
4531
+ paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
4532
+ tableColumns: []
4466
4533
  }
4467
4534
  ];
4468
4535
  }
@@ -4471,23 +4538,73 @@ var PptPlugin = class {
4471
4538
  currentSlide = idx;
4472
4539
  const s = slides[idx - 1];
4473
4540
  if (!s) return;
4541
+ let contentHtml = "";
4542
+ if (s.pictureUrl) {
4543
+ contentHtml = `
4544
+ <div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
4545
+ <img src="${s.pictureUrl}" alt="${DOMPurify2__default.default.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
4546
+ </div>
4547
+ `;
4548
+ } else if (s.tableColumns.length > 0) {
4549
+ const cols = s.tableColumns;
4550
+ contentHtml = `
4551
+ <div style="flex: 1; overflow: auto; padding: 8px 0;">
4552
+ <table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
4553
+ <thead>
4554
+ <tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
4555
+ ${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2__default.default.sanitize(c)}</th>`).join("")}
4556
+ </tr>
4557
+ </thead>
4558
+ <tbody>
4559
+ ${[1, 2, 3, 4, 5].map((rowIdx) => `
4560
+ <tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
4561
+ ${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
4562
+ </tr>
4563
+ `).join("")}
4564
+ </tbody>
4565
+ </table>
4566
+ </div>
4567
+ `;
4568
+ } else {
4569
+ const pTags = s.paragraphs.map((p) => {
4570
+ const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
4571
+ return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2__default.default.sanitize(l)}</p>`).join("");
4572
+ }).join("");
4573
+ contentHtml = `
4574
+ <div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
4575
+ ${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
4576
+ </div>
4577
+ `;
4578
+ }
4474
4579
  slideCard.innerHTML = `
4475
- <div style="position: absolute; top: 20px; right: 24px; font-size: 12px; color: #94a3b8; font-weight: 600;">
4476
- Slide ${idx} of ${totalSlides}
4477
- </div>
4478
- <div style="text-align: center; width: 100%;">
4479
- <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4480
- ${DOMPurify2__default.default.sanitize(s.title || `Slide ${idx}`)}
4481
- </h1>
4482
- <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4483
- ${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("")}
4580
+ <div style="width: 100%; height: 100%; background: #ffffff; border: 14px solid #334155; box-sizing: border-box; display: flex; flex-direction: column; padding: 24px 32px; position: relative; font-family: Calibri, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; overflow: hidden; border-radius: 4px;">
4581
+ <!-- Header Banner matching PowerPoint design -->
4582
+ <div style="background: linear-gradient(90deg, #a3e635 0%, #84cc16 100%); padding: 12px 24px; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); flex-shrink: 0;">
4583
+ <h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
4584
+ ${DOMPurify2__default.default.sanitize(s.title)}
4585
+ </h1>
4586
+ ${s.subtitle ? `
4587
+ <span style="background: #38bdf8; color: #ffffff; padding: 4px 14px; border-radius: 4px; font-weight: 700; font-size: 13px; letter-spacing: 0.5px; box-shadow: 0 1px 4px rgba(0,0,0,0.15);">
4588
+ ${DOMPurify2__default.default.sanitize(s.subtitle)}
4589
+ </span>
4590
+ ` : `
4591
+ <span style="font-size: 12px; color: #365314; font-weight: 600;">
4592
+ Slide ${idx} / ${totalSlides}
4593
+ </span>
4594
+ `}
4484
4595
  </div>
4596
+
4597
+ <!-- Slide Content -->
4598
+ ${contentHtml}
4485
4599
  </div>
4486
4600
  `;
4487
4601
  ctx.emit("page-change", { page: currentSlide, total: totalSlides });
4488
4602
  };
4489
4603
  renderSlide(1);
4490
4604
  const cleanup = () => {
4605
+ for (const u of createdBlobUrls) {
4606
+ URL.revokeObjectURL(u);
4607
+ }
4491
4608
  container.remove();
4492
4609
  ctx.container.innerHTML = "";
4493
4610
  };
@@ -4527,13 +4644,48 @@ var PptPlugin = class {
4527
4644
  if (!ctx2d) return;
4528
4645
  canvas.width = 160;
4529
4646
  canvas.height = 90;
4530
- ctx2d.fillStyle = "#ffffff";
4647
+ ctx2d.fillStyle = "#334155";
4531
4648
  ctx2d.fillRect(0, 0, 160, 90);
4532
- ctx2d.fillStyle = "#1e3a8a";
4533
- ctx2d.font = "bold 11px sans-serif";
4534
- ctx2d.textAlign = "center";
4535
- const title = s.title.slice(0, 18) || `Slide ${idx + 1}`;
4536
- ctx2d.fillText(title, 80, 50);
4649
+ ctx2d.fillStyle = "#ffffff";
4650
+ ctx2d.fillRect(3, 3, 154, 84);
4651
+ ctx2d.fillStyle = "#84cc16";
4652
+ ctx2d.fillRect(6, 6, 148, 18);
4653
+ ctx2d.fillStyle = "#1e293b";
4654
+ ctx2d.font = "bold 9px sans-serif";
4655
+ ctx2d.textAlign = "left";
4656
+ const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
4657
+ ctx2d.fillText(displayTitle, 10, 19);
4658
+ if (s.subtitle) {
4659
+ ctx2d.fillStyle = "#38bdf8";
4660
+ ctx2d.fillRect(116, 9, 34, 12);
4661
+ ctx2d.fillStyle = "#ffffff";
4662
+ ctx2d.font = "bold 7px sans-serif";
4663
+ ctx2d.textAlign = "center";
4664
+ ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
4665
+ }
4666
+ if (s.pictureUrl) {
4667
+ ctx2d.fillStyle = "#3b82f6";
4668
+ ctx2d.fillRect(52, 34, 56, 42);
4669
+ ctx2d.fillStyle = "#ffffff";
4670
+ ctx2d.font = "8px sans-serif";
4671
+ ctx2d.textAlign = "center";
4672
+ ctx2d.fillText("Chart", 80, 58);
4673
+ } else if (s.tableColumns.length > 0) {
4674
+ ctx2d.strokeStyle = "#cbd5e1";
4675
+ ctx2d.lineWidth = 1;
4676
+ ctx2d.strokeRect(14, 32, 132, 46);
4677
+ for (let l = 1; l <= 3; l++) {
4678
+ ctx2d.beginPath();
4679
+ ctx2d.moveTo(14, 32 + l * 11);
4680
+ ctx2d.lineTo(146, 32 + l * 11);
4681
+ ctx2d.stroke();
4682
+ }
4683
+ } else {
4684
+ ctx2d.fillStyle = "#94a3b8";
4685
+ for (let l = 0; l < 4; l++) {
4686
+ ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
4687
+ }
4688
+ }
4537
4689
  }
4538
4690
  }));
4539
4691
  },
@@ -4551,66 +4703,165 @@ var PptPlugin = class {
4551
4703
  }
4552
4704
  };
4553
4705
  }
4706
+ /**
4707
+ * Extract PNG and JPEG images from the Pictures stream
4708
+ */
4709
+ extractPictures(cfbf, createdUrls) {
4710
+ const urls = [];
4711
+ try {
4712
+ const picStream = cfbf.readStream("Pictures");
4713
+ if (!picStream || picStream.length < 32) return urls;
4714
+ const pBuf = new Uint8Array(picStream);
4715
+ const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
4716
+ const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
4717
+ for (let i = 0; i <= pBuf.length - 8; i++) {
4718
+ let match = true;
4719
+ for (let j = 0; j < 8; j++) {
4720
+ if (pBuf[i + j] !== pngSig[j]) {
4721
+ match = false;
4722
+ break;
4723
+ }
4724
+ }
4725
+ if (match) {
4726
+ let endIdx = -1;
4727
+ for (let k = i + 8; k <= pBuf.length - 8; k++) {
4728
+ let endMatch = true;
4729
+ for (let j = 0; j < 8; j++) {
4730
+ if (pBuf[k + j] !== iendSig[j]) {
4731
+ endMatch = false;
4732
+ break;
4733
+ }
4734
+ }
4735
+ if (endMatch) {
4736
+ endIdx = k + 8;
4737
+ break;
4738
+ }
4739
+ }
4740
+ if (endIdx !== -1) {
4741
+ const pngBytes = pBuf.subarray(i, endIdx);
4742
+ const blob = new Blob([pngBytes], { type: "image/png" });
4743
+ const url = URL.createObjectURL(blob);
4744
+ urls.push(url);
4745
+ createdUrls.push(url);
4746
+ i = endIdx;
4747
+ }
4748
+ }
4749
+ }
4750
+ for (let i = 0; i <= pBuf.length - 3; i++) {
4751
+ if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
4752
+ let endIdx = -1;
4753
+ for (let k = i + 3; k < pBuf.length - 1; k++) {
4754
+ if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
4755
+ endIdx = k + 2;
4756
+ break;
4757
+ }
4758
+ }
4759
+ if (endIdx !== -1) {
4760
+ const jpgBytes = pBuf.subarray(i, endIdx);
4761
+ const blob = new Blob([jpgBytes], { type: "image/jpeg" });
4762
+ const url = URL.createObjectURL(blob);
4763
+ urls.push(url);
4764
+ createdUrls.push(url);
4765
+ i = endIdx;
4766
+ }
4767
+ }
4768
+ }
4769
+ } catch (err) {
4770
+ console.warn("[PptPlugin] Error extracting pictures:", err);
4771
+ }
4772
+ return urls;
4773
+ }
4554
4774
  /**
4555
4775
  * Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
4556
4776
  */
4557
- extractSlides(stream) {
4777
+ extractSlides(stream, pictures) {
4558
4778
  const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
4559
4779
  const len = stream.length;
4560
4780
  let offset = 0;
4561
4781
  const slides = [];
4562
- let currentSlideTexts = [];
4782
+ let picIdx = 0;
4563
4783
  while (offset + 8 <= len) {
4564
4784
  const recVerInst = view.getUint16(offset, true);
4565
4785
  const recType = view.getUint16(offset + 2, true);
4566
4786
  const recLen = view.getUint32(offset + 4, true);
4787
+ const isContainer = (recVerInst & 15) === 15;
4567
4788
  if (recType === 1006) {
4568
- if (currentSlideTexts.length > 0) {
4569
- const title = currentSlideTexts[0] || "Slide";
4570
- const texts = currentSlideTexts.slice(1);
4571
- slides.push({ title, texts });
4572
- currentSlideTexts = [];
4789
+ const slideEnd = Math.min(len, offset + 8 + recLen);
4790
+ const rawTexts = [];
4791
+ let hasOle = false;
4792
+ let sOff = offset + 8;
4793
+ while (sOff + 8 <= slideEnd) {
4794
+ const cVerInst = view.getUint16(sOff, true);
4795
+ const cType = view.getUint16(sOff + 2, true);
4796
+ const cLen = view.getUint32(sOff + 4, true);
4797
+ const cIsContainer = (cVerInst & 15) === 15;
4798
+ if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4799
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4800
+ const txt = new TextDecoder("latin1").decode(bytes).trim();
4801
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4802
+ rawTexts.push(txt);
4803
+ }
4804
+ } else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
4805
+ const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
4806
+ const txt = new TextDecoder("utf-16le").decode(bytes).trim();
4807
+ if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
4808
+ rawTexts.push(txt);
4809
+ }
4810
+ } else if (cType === 3009 || cType === 3011) {
4811
+ hasOle = true;
4812
+ }
4813
+ if (cIsContainer) sOff += 8;
4814
+ else sOff += 8 + cLen;
4573
4815
  }
4574
- offset += 8;
4575
- continue;
4576
- }
4577
- if (recType === 3998 && recLen > 0 && offset + 8 + recLen <= len) {
4578
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4579
- const text = new TextDecoder("latin1").decode(bytes).trim();
4580
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4581
- currentSlideTexts.push(text);
4816
+ let title = "";
4817
+ let subtitle = "";
4818
+ const paragraphs = [];
4819
+ const tableColumns = [];
4820
+ for (const t of rawTexts) {
4821
+ if (!title && t.length < 60 && !t.includes("\n")) {
4822
+ title = t;
4823
+ } else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
4824
+ tableColumns.push(t);
4825
+ } else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
4826
+ subtitle = t;
4827
+ } else {
4828
+ paragraphs.push(t);
4829
+ }
4582
4830
  }
4583
- }
4584
- if (recType === 3999 && recLen > 0 && offset + 8 + recLen <= len) {
4585
- const bytes = stream.subarray(offset + 8, offset + 8 + recLen);
4586
- const text = new TextDecoder("utf-16le").decode(bytes).trim();
4587
- if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
4588
- currentSlideTexts.push(text);
4831
+ let pictureUrl = null;
4832
+ if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
4833
+ pictureUrl = pictures[picIdx++];
4589
4834
  }
4835
+ slides.push({
4836
+ slideIndex: slides.length + 1,
4837
+ title: title || `Slide ${slides.length + 1}`,
4838
+ subtitle,
4839
+ paragraphs,
4840
+ tableColumns,
4841
+ pictureUrl,
4842
+ hasOle
4843
+ });
4590
4844
  }
4591
- const isContainer = (recVerInst & 15) === 15;
4592
- if (isContainer) {
4593
- offset += 8;
4594
- } else {
4595
- offset += 8 + recLen;
4596
- }
4597
- }
4598
- if (currentSlideTexts.length > 0) {
4599
- const title = currentSlideTexts[0] || "Slide";
4600
- const texts = currentSlideTexts.slice(1);
4601
- slides.push({ title, texts });
4845
+ if (isContainer) offset += 8;
4846
+ else offset += 8 + recLen;
4602
4847
  }
4603
4848
  if (slides.length === 0) {
4604
4849
  const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
4605
- const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{4,}/g) || [];
4606
- const filtered = matches.map((m) => m.trim()).filter((m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times"));
4607
- if (filtered.length > 0) {
4608
- const chunkSize = 4;
4609
- for (let i = 0; i < filtered.length; i += chunkSize) {
4610
- const chunk = filtered.slice(i, i + chunkSize);
4850
+ const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
4851
+ const clean = matches.map((m) => m.trim()).filter(
4852
+ (m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
4853
+ );
4854
+ if (clean.length > 0) {
4855
+ const chunkSize = 3;
4856
+ for (let i = 0; i < clean.length; i += chunkSize) {
4857
+ const chunk = clean.slice(i, i + chunkSize);
4611
4858
  slides.push({
4859
+ slideIndex: slides.length + 1,
4612
4860
  title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
4613
- texts: chunk.slice(1)
4861
+ subtitle: chunk.length > 2 ? chunk[1] : void 0,
4862
+ paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
4863
+ tableColumns: [],
4864
+ pictureUrl: pictures[slides.length] || null
4614
4865
  });
4615
4866
  }
4616
4867
  }