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