@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 +338 -87
- package/dist/angular.cjs.map +1 -1
- package/dist/angular.js +336 -86
- package/dist/angular.js.map +1 -1
- package/dist/index.cjs +338 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +336 -86
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +338 -87
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +336 -86
- package/dist/react.js.map +1 -1
- package/dist/vue.cjs +338 -87
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +336 -86
- package/dist/vue.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import * as THREE from 'three';
|
|
|
9
9
|
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
|
|
10
10
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
|
|
11
11
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
12
|
-
import
|
|
12
|
+
import * as RTFJS from 'rtf.js/dist/RTFJS.bundle.js';
|
|
13
13
|
|
|
14
14
|
// ../core/dist/index.js
|
|
15
15
|
var EventEmitter = class {
|
|
@@ -265,6 +265,39 @@ function detectOoxmlType(buffer) {
|
|
|
265
265
|
}
|
|
266
266
|
return "application/zip";
|
|
267
267
|
}
|
|
268
|
+
function detectCfbfType(buffer) {
|
|
269
|
+
const bytes = new Uint8Array(buffer);
|
|
270
|
+
const hasUtf16le = (str) => {
|
|
271
|
+
const target = new Uint8Array(str.length * 2);
|
|
272
|
+
for (let i = 0; i < str.length; i++) {
|
|
273
|
+
target[i * 2] = str.charCodeAt(i);
|
|
274
|
+
target[i * 2 + 1] = 0;
|
|
275
|
+
}
|
|
276
|
+
const targetLen = target.length;
|
|
277
|
+
const max = bytes.length - targetLen;
|
|
278
|
+
for (let i = 0; i <= max; i++) {
|
|
279
|
+
let match = true;
|
|
280
|
+
for (let j = 0; j < targetLen; j++) {
|
|
281
|
+
if (bytes[i + j] !== target[j]) {
|
|
282
|
+
match = false;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (match) return true;
|
|
287
|
+
}
|
|
288
|
+
return false;
|
|
289
|
+
};
|
|
290
|
+
if (hasUtf16le("PowerPoint Document")) {
|
|
291
|
+
return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
|
|
292
|
+
}
|
|
293
|
+
if (hasUtf16le("WordDocument")) {
|
|
294
|
+
return { mime: "application/msword", extension: ".doc" };
|
|
295
|
+
}
|
|
296
|
+
if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
|
|
297
|
+
return { mime: "application/vnd.ms-excel", extension: ".xls" };
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
268
301
|
function extractExtension(nameOrUrl) {
|
|
269
302
|
try {
|
|
270
303
|
const url = new URL(nameOrUrl);
|
|
@@ -329,6 +362,18 @@ async function sourceToArrayBuffer(source, signal) {
|
|
|
329
362
|
} else {
|
|
330
363
|
metadata.mimeType = metadata.mimeType ?? magicMime;
|
|
331
364
|
}
|
|
365
|
+
} else if (magicMime === "application/x-cfbf") {
|
|
366
|
+
if (metadata.extension) {
|
|
367
|
+
metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
|
|
368
|
+
} else {
|
|
369
|
+
const cfbf = detectCfbfType(buffer);
|
|
370
|
+
if (cfbf) {
|
|
371
|
+
metadata.mimeType = cfbf.mime;
|
|
372
|
+
metadata.extension = cfbf.extension;
|
|
373
|
+
} else {
|
|
374
|
+
metadata.mimeType = magicMime;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
332
377
|
} else {
|
|
333
378
|
metadata.mimeType = magicMime;
|
|
334
379
|
}
|
|
@@ -1661,7 +1706,10 @@ var DocxPlugin = class {
|
|
|
1661
1706
|
supports(file) {
|
|
1662
1707
|
const ext = file.metadata.extension?.toLowerCase();
|
|
1663
1708
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
1664
|
-
|
|
1709
|
+
if (ext) {
|
|
1710
|
+
return this.extensions.includes(ext);
|
|
1711
|
+
}
|
|
1712
|
+
return this.mimeTypes.includes(mime || "");
|
|
1665
1713
|
}
|
|
1666
1714
|
getToolbarActions(instance) {
|
|
1667
1715
|
return [
|
|
@@ -2050,7 +2098,10 @@ var ExcelPlugin = class {
|
|
|
2050
2098
|
supports(file) {
|
|
2051
2099
|
const ext = file.metadata.extension?.toLowerCase();
|
|
2052
2100
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
2053
|
-
|
|
2101
|
+
if (ext) {
|
|
2102
|
+
return this.extensions.includes(ext);
|
|
2103
|
+
}
|
|
2104
|
+
return this.mimeTypes.includes(mime || "");
|
|
2054
2105
|
}
|
|
2055
2106
|
getToolbarActions(instance) {
|
|
2056
2107
|
return [
|
|
@@ -2992,7 +3043,10 @@ var PptxPlugin = class {
|
|
|
2992
3043
|
supports(file) {
|
|
2993
3044
|
const ext = file.metadata.extension?.toLowerCase();
|
|
2994
3045
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
2995
|
-
|
|
3046
|
+
if (ext) {
|
|
3047
|
+
return this.extensions.includes(ext);
|
|
3048
|
+
}
|
|
3049
|
+
return this.mimeTypes.includes(mime || "");
|
|
2996
3050
|
}
|
|
2997
3051
|
getToolbarActions(instance) {
|
|
2998
3052
|
return [
|
|
@@ -3437,6 +3491,9 @@ var RtfPlugin = class {
|
|
|
3437
3491
|
ctx.container.appendChild(wrapper);
|
|
3438
3492
|
let scale = 1;
|
|
3439
3493
|
try {
|
|
3494
|
+
if (typeof RTFJS.loggingEnabled === "function") {
|
|
3495
|
+
RTFJS.loggingEnabled(false);
|
|
3496
|
+
}
|
|
3440
3497
|
const doc = new RTFJS.Document(ctx.buffer, {});
|
|
3441
3498
|
const htmlElements = await doc.render();
|
|
3442
3499
|
for (const el of htmlElements) {
|
|
@@ -3635,7 +3692,10 @@ var OpenDocumentPlugin = class {
|
|
|
3635
3692
|
supports(file) {
|
|
3636
3693
|
const ext = file.metadata.extension?.toLowerCase();
|
|
3637
3694
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
3638
|
-
|
|
3695
|
+
if (ext) {
|
|
3696
|
+
return this.extensions.includes(ext);
|
|
3697
|
+
}
|
|
3698
|
+
return this.mimeTypes.includes(mime || "");
|
|
3639
3699
|
}
|
|
3640
3700
|
getToolbarActions(instance) {
|
|
3641
3701
|
const isPresentation = instance.isPresentation;
|
|
@@ -4050,7 +4110,10 @@ var DocPlugin = class {
|
|
|
4050
4110
|
supports(file) {
|
|
4051
4111
|
const ext = file.metadata.extension?.toLowerCase();
|
|
4052
4112
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
4053
|
-
|
|
4113
|
+
if (ext) {
|
|
4114
|
+
return this.extensions.includes(ext);
|
|
4115
|
+
}
|
|
4116
|
+
return this.mimeTypes.includes(mime || "");
|
|
4054
4117
|
}
|
|
4055
4118
|
getToolbarActions(instance) {
|
|
4056
4119
|
return [
|
|
@@ -4273,22 +4336,22 @@ var DocPlugin = class {
|
|
|
4273
4336
|
* Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
|
|
4274
4337
|
*/
|
|
4275
4338
|
extractStringsFromBytes(bytes) {
|
|
4276
|
-
const
|
|
4277
|
-
const
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4339
|
+
const rawAnsi = new TextDecoder("latin1").decode(bytes);
|
|
4340
|
+
const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
|
|
4341
|
+
const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
|
|
4342
|
+
const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
|
|
4343
|
+
const candidateLines = [];
|
|
4344
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4345
|
+
for (const run of [...ansiRuns, ...utf16Runs]) {
|
|
4346
|
+
const trimmed = run.trim();
|
|
4347
|
+
if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
|
|
4348
|
+
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)) {
|
|
4349
|
+
seen.add(trimmed);
|
|
4350
|
+
candidateLines.push(trimmed);
|
|
4351
|
+
}
|
|
4289
4352
|
}
|
|
4290
4353
|
}
|
|
4291
|
-
return
|
|
4354
|
+
return candidateLines.join("\n\n");
|
|
4292
4355
|
}
|
|
4293
4356
|
heuristicTextExtraction(buffer) {
|
|
4294
4357
|
return this.extractStringsFromBytes(new Uint8Array(buffer));
|
|
@@ -4343,14 +4406,17 @@ function docPlugin() {
|
|
|
4343
4406
|
}
|
|
4344
4407
|
var PptPlugin = class {
|
|
4345
4408
|
id = "ppt";
|
|
4346
|
-
name = "
|
|
4409
|
+
name = "PowerPoint Presentation (.ppt, .pps, .pot)";
|
|
4347
4410
|
extensions = [".ppt", ".pps", ".pot"];
|
|
4348
4411
|
mimeTypes = ["application/vnd.ms-powerpoint"];
|
|
4349
|
-
weight =
|
|
4412
|
+
weight = 85;
|
|
4350
4413
|
supports(file) {
|
|
4351
4414
|
const ext = file.metadata.extension?.toLowerCase();
|
|
4352
4415
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
4353
|
-
|
|
4416
|
+
if (ext) {
|
|
4417
|
+
return this.extensions.includes(ext);
|
|
4418
|
+
}
|
|
4419
|
+
return this.mimeTypes.includes(mime || "");
|
|
4354
4420
|
}
|
|
4355
4421
|
getToolbarActions(instance) {
|
|
4356
4422
|
return [
|
|
@@ -4438,19 +4504,15 @@ var PptPlugin = class {
|
|
|
4438
4504
|
container.style.alignItems = "center";
|
|
4439
4505
|
container.style.padding = "32px 16px";
|
|
4440
4506
|
container.style.backgroundColor = "#0f172a";
|
|
4507
|
+
container.style.boxSizing = "border-box";
|
|
4441
4508
|
const slideCard = document.createElement("div");
|
|
4442
4509
|
slideCard.className = "fp-ppt-slide-card";
|
|
4443
4510
|
slideCard.style.width = "960px";
|
|
4444
|
-
slideCard.style.maxWidth = "
|
|
4511
|
+
slideCard.style.maxWidth = "92%";
|
|
4445
4512
|
slideCard.style.aspectRatio = "16 / 9";
|
|
4446
4513
|
slideCard.style.backgroundColor = "#ffffff";
|
|
4447
|
-
slideCard.style.boxShadow = "0
|
|
4514
|
+
slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
|
|
4448
4515
|
slideCard.style.borderRadius = "8px";
|
|
4449
|
-
slideCard.style.padding = "48px";
|
|
4450
|
-
slideCard.style.display = "flex";
|
|
4451
|
-
slideCard.style.flexDirection = "column";
|
|
4452
|
-
slideCard.style.justifyContent = "center";
|
|
4453
|
-
slideCard.style.alignItems = "center";
|
|
4454
4516
|
slideCard.style.boxSizing = "border-box";
|
|
4455
4517
|
slideCard.style.position = "relative";
|
|
4456
4518
|
slideCard.style.overflow = "hidden";
|
|
@@ -4461,21 +4523,25 @@ var PptPlugin = class {
|
|
|
4461
4523
|
let scale = 1;
|
|
4462
4524
|
let currentSlide = 1;
|
|
4463
4525
|
let slides = [];
|
|
4526
|
+
const createdBlobUrls = [];
|
|
4464
4527
|
try {
|
|
4465
4528
|
const cfbf = new CfbfReader(ctx.buffer);
|
|
4466
4529
|
const pptStream = cfbf.readStream("PowerPoint Document");
|
|
4467
4530
|
if (!pptStream || pptStream.length < 512) {
|
|
4468
4531
|
throw new Error("PowerPoint Document stream not found in CFBF container");
|
|
4469
4532
|
}
|
|
4470
|
-
|
|
4533
|
+
const pictures = this.extractPictures(cfbf, createdBlobUrls);
|
|
4534
|
+
slides = this.extractSlides(pptStream, pictures);
|
|
4471
4535
|
} catch (err) {
|
|
4472
4536
|
console.warn("[PptPlugin] Error extracting binary slides:", err);
|
|
4473
4537
|
}
|
|
4474
4538
|
if (slides.length === 0) {
|
|
4475
4539
|
slides = [
|
|
4476
4540
|
{
|
|
4541
|
+
slideIndex: 1,
|
|
4477
4542
|
title: ctx.metadata.name || "PowerPoint Presentation",
|
|
4478
|
-
|
|
4543
|
+
paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
|
|
4544
|
+
tableColumns: []
|
|
4479
4545
|
}
|
|
4480
4546
|
];
|
|
4481
4547
|
}
|
|
@@ -4484,23 +4550,73 @@ var PptPlugin = class {
|
|
|
4484
4550
|
currentSlide = idx;
|
|
4485
4551
|
const s = slides[idx - 1];
|
|
4486
4552
|
if (!s) return;
|
|
4553
|
+
let contentHtml = "";
|
|
4554
|
+
if (s.pictureUrl) {
|
|
4555
|
+
contentHtml = `
|
|
4556
|
+
<div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
|
|
4557
|
+
<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;" />
|
|
4558
|
+
</div>
|
|
4559
|
+
`;
|
|
4560
|
+
} else if (s.tableColumns.length > 0) {
|
|
4561
|
+
const cols = s.tableColumns;
|
|
4562
|
+
contentHtml = `
|
|
4563
|
+
<div style="flex: 1; overflow: auto; padding: 8px 0;">
|
|
4564
|
+
<table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
|
|
4565
|
+
<thead>
|
|
4566
|
+
<tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
|
|
4567
|
+
${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2.sanitize(c)}</th>`).join("")}
|
|
4568
|
+
</tr>
|
|
4569
|
+
</thead>
|
|
4570
|
+
<tbody>
|
|
4571
|
+
${[1, 2, 3, 4, 5].map((rowIdx) => `
|
|
4572
|
+
<tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
|
|
4573
|
+
${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
|
|
4574
|
+
</tr>
|
|
4575
|
+
`).join("")}
|
|
4576
|
+
</tbody>
|
|
4577
|
+
</table>
|
|
4578
|
+
</div>
|
|
4579
|
+
`;
|
|
4580
|
+
} else {
|
|
4581
|
+
const pTags = s.paragraphs.map((p) => {
|
|
4582
|
+
const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
|
|
4583
|
+
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("");
|
|
4584
|
+
}).join("");
|
|
4585
|
+
contentHtml = `
|
|
4586
|
+
<div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
|
|
4587
|
+
${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
|
|
4588
|
+
</div>
|
|
4589
|
+
`;
|
|
4590
|
+
}
|
|
4487
4591
|
slideCard.innerHTML = `
|
|
4488
|
-
<div style="
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4592
|
+
<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;">
|
|
4593
|
+
<!-- Header Banner matching PowerPoint design -->
|
|
4594
|
+
<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;">
|
|
4595
|
+
<h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
|
|
4596
|
+
${DOMPurify2.sanitize(s.title)}
|
|
4597
|
+
</h1>
|
|
4598
|
+
${s.subtitle ? `
|
|
4599
|
+
<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);">
|
|
4600
|
+
${DOMPurify2.sanitize(s.subtitle)}
|
|
4601
|
+
</span>
|
|
4602
|
+
` : `
|
|
4603
|
+
<span style="font-size: 12px; color: #365314; font-weight: 600;">
|
|
4604
|
+
Slide ${idx} / ${totalSlides}
|
|
4605
|
+
</span>
|
|
4606
|
+
`}
|
|
4497
4607
|
</div>
|
|
4608
|
+
|
|
4609
|
+
<!-- Slide Content -->
|
|
4610
|
+
${contentHtml}
|
|
4498
4611
|
</div>
|
|
4499
4612
|
`;
|
|
4500
4613
|
ctx.emit("page-change", { page: currentSlide, total: totalSlides });
|
|
4501
4614
|
};
|
|
4502
4615
|
renderSlide(1);
|
|
4503
4616
|
const cleanup = () => {
|
|
4617
|
+
for (const u of createdBlobUrls) {
|
|
4618
|
+
URL.revokeObjectURL(u);
|
|
4619
|
+
}
|
|
4504
4620
|
container.remove();
|
|
4505
4621
|
ctx.container.innerHTML = "";
|
|
4506
4622
|
};
|
|
@@ -4540,13 +4656,48 @@ var PptPlugin = class {
|
|
|
4540
4656
|
if (!ctx2d) return;
|
|
4541
4657
|
canvas.width = 160;
|
|
4542
4658
|
canvas.height = 90;
|
|
4543
|
-
ctx2d.fillStyle = "#
|
|
4659
|
+
ctx2d.fillStyle = "#334155";
|
|
4544
4660
|
ctx2d.fillRect(0, 0, 160, 90);
|
|
4545
|
-
ctx2d.fillStyle = "#
|
|
4546
|
-
ctx2d.
|
|
4547
|
-
ctx2d.
|
|
4548
|
-
|
|
4549
|
-
ctx2d.
|
|
4661
|
+
ctx2d.fillStyle = "#ffffff";
|
|
4662
|
+
ctx2d.fillRect(3, 3, 154, 84);
|
|
4663
|
+
ctx2d.fillStyle = "#84cc16";
|
|
4664
|
+
ctx2d.fillRect(6, 6, 148, 18);
|
|
4665
|
+
ctx2d.fillStyle = "#1e293b";
|
|
4666
|
+
ctx2d.font = "bold 9px sans-serif";
|
|
4667
|
+
ctx2d.textAlign = "left";
|
|
4668
|
+
const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
|
|
4669
|
+
ctx2d.fillText(displayTitle, 10, 19);
|
|
4670
|
+
if (s.subtitle) {
|
|
4671
|
+
ctx2d.fillStyle = "#38bdf8";
|
|
4672
|
+
ctx2d.fillRect(116, 9, 34, 12);
|
|
4673
|
+
ctx2d.fillStyle = "#ffffff";
|
|
4674
|
+
ctx2d.font = "bold 7px sans-serif";
|
|
4675
|
+
ctx2d.textAlign = "center";
|
|
4676
|
+
ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
|
|
4677
|
+
}
|
|
4678
|
+
if (s.pictureUrl) {
|
|
4679
|
+
ctx2d.fillStyle = "#3b82f6";
|
|
4680
|
+
ctx2d.fillRect(52, 34, 56, 42);
|
|
4681
|
+
ctx2d.fillStyle = "#ffffff";
|
|
4682
|
+
ctx2d.font = "8px sans-serif";
|
|
4683
|
+
ctx2d.textAlign = "center";
|
|
4684
|
+
ctx2d.fillText("Chart", 80, 58);
|
|
4685
|
+
} else if (s.tableColumns.length > 0) {
|
|
4686
|
+
ctx2d.strokeStyle = "#cbd5e1";
|
|
4687
|
+
ctx2d.lineWidth = 1;
|
|
4688
|
+
ctx2d.strokeRect(14, 32, 132, 46);
|
|
4689
|
+
for (let l = 1; l <= 3; l++) {
|
|
4690
|
+
ctx2d.beginPath();
|
|
4691
|
+
ctx2d.moveTo(14, 32 + l * 11);
|
|
4692
|
+
ctx2d.lineTo(146, 32 + l * 11);
|
|
4693
|
+
ctx2d.stroke();
|
|
4694
|
+
}
|
|
4695
|
+
} else {
|
|
4696
|
+
ctx2d.fillStyle = "#94a3b8";
|
|
4697
|
+
for (let l = 0; l < 4; l++) {
|
|
4698
|
+
ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4550
4701
|
}
|
|
4551
4702
|
}));
|
|
4552
4703
|
},
|
|
@@ -4564,66 +4715,165 @@ var PptPlugin = class {
|
|
|
4564
4715
|
}
|
|
4565
4716
|
};
|
|
4566
4717
|
}
|
|
4718
|
+
/**
|
|
4719
|
+
* Extract PNG and JPEG images from the Pictures stream
|
|
4720
|
+
*/
|
|
4721
|
+
extractPictures(cfbf, createdUrls) {
|
|
4722
|
+
const urls = [];
|
|
4723
|
+
try {
|
|
4724
|
+
const picStream = cfbf.readStream("Pictures");
|
|
4725
|
+
if (!picStream || picStream.length < 32) return urls;
|
|
4726
|
+
const pBuf = new Uint8Array(picStream);
|
|
4727
|
+
const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
4728
|
+
const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
|
|
4729
|
+
for (let i = 0; i <= pBuf.length - 8; i++) {
|
|
4730
|
+
let match = true;
|
|
4731
|
+
for (let j = 0; j < 8; j++) {
|
|
4732
|
+
if (pBuf[i + j] !== pngSig[j]) {
|
|
4733
|
+
match = false;
|
|
4734
|
+
break;
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
if (match) {
|
|
4738
|
+
let endIdx = -1;
|
|
4739
|
+
for (let k = i + 8; k <= pBuf.length - 8; k++) {
|
|
4740
|
+
let endMatch = true;
|
|
4741
|
+
for (let j = 0; j < 8; j++) {
|
|
4742
|
+
if (pBuf[k + j] !== iendSig[j]) {
|
|
4743
|
+
endMatch = false;
|
|
4744
|
+
break;
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
if (endMatch) {
|
|
4748
|
+
endIdx = k + 8;
|
|
4749
|
+
break;
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
if (endIdx !== -1) {
|
|
4753
|
+
const pngBytes = pBuf.subarray(i, endIdx);
|
|
4754
|
+
const blob = new Blob([pngBytes], { type: "image/png" });
|
|
4755
|
+
const url = URL.createObjectURL(blob);
|
|
4756
|
+
urls.push(url);
|
|
4757
|
+
createdUrls.push(url);
|
|
4758
|
+
i = endIdx;
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
for (let i = 0; i <= pBuf.length - 3; i++) {
|
|
4763
|
+
if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
|
|
4764
|
+
let endIdx = -1;
|
|
4765
|
+
for (let k = i + 3; k < pBuf.length - 1; k++) {
|
|
4766
|
+
if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
|
|
4767
|
+
endIdx = k + 2;
|
|
4768
|
+
break;
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
if (endIdx !== -1) {
|
|
4772
|
+
const jpgBytes = pBuf.subarray(i, endIdx);
|
|
4773
|
+
const blob = new Blob([jpgBytes], { type: "image/jpeg" });
|
|
4774
|
+
const url = URL.createObjectURL(blob);
|
|
4775
|
+
urls.push(url);
|
|
4776
|
+
createdUrls.push(url);
|
|
4777
|
+
i = endIdx;
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
} catch (err) {
|
|
4782
|
+
console.warn("[PptPlugin] Error extracting pictures:", err);
|
|
4783
|
+
}
|
|
4784
|
+
return urls;
|
|
4785
|
+
}
|
|
4567
4786
|
/**
|
|
4568
4787
|
* Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
|
|
4569
4788
|
*/
|
|
4570
|
-
extractSlides(stream) {
|
|
4789
|
+
extractSlides(stream, pictures) {
|
|
4571
4790
|
const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
|
|
4572
4791
|
const len = stream.length;
|
|
4573
4792
|
let offset = 0;
|
|
4574
4793
|
const slides = [];
|
|
4575
|
-
let
|
|
4794
|
+
let picIdx = 0;
|
|
4576
4795
|
while (offset + 8 <= len) {
|
|
4577
4796
|
const recVerInst = view.getUint16(offset, true);
|
|
4578
4797
|
const recType = view.getUint16(offset + 2, true);
|
|
4579
4798
|
const recLen = view.getUint32(offset + 4, true);
|
|
4799
|
+
const isContainer = (recVerInst & 15) === 15;
|
|
4580
4800
|
if (recType === 1006) {
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4801
|
+
const slideEnd = Math.min(len, offset + 8 + recLen);
|
|
4802
|
+
const rawTexts = [];
|
|
4803
|
+
let hasOle = false;
|
|
4804
|
+
let sOff = offset + 8;
|
|
4805
|
+
while (sOff + 8 <= slideEnd) {
|
|
4806
|
+
const cVerInst = view.getUint16(sOff, true);
|
|
4807
|
+
const cType = view.getUint16(sOff + 2, true);
|
|
4808
|
+
const cLen = view.getUint32(sOff + 4, true);
|
|
4809
|
+
const cIsContainer = (cVerInst & 15) === 15;
|
|
4810
|
+
if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
|
|
4811
|
+
const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
|
|
4812
|
+
const txt = new TextDecoder("latin1").decode(bytes).trim();
|
|
4813
|
+
if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
|
|
4814
|
+
rawTexts.push(txt);
|
|
4815
|
+
}
|
|
4816
|
+
} else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
|
|
4817
|
+
const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
|
|
4818
|
+
const txt = new TextDecoder("utf-16le").decode(bytes).trim();
|
|
4819
|
+
if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
|
|
4820
|
+
rawTexts.push(txt);
|
|
4821
|
+
}
|
|
4822
|
+
} else if (cType === 3009 || cType === 3011) {
|
|
4823
|
+
hasOle = true;
|
|
4824
|
+
}
|
|
4825
|
+
if (cIsContainer) sOff += 8;
|
|
4826
|
+
else sOff += 8 + cLen;
|
|
4586
4827
|
}
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4828
|
+
let title = "";
|
|
4829
|
+
let subtitle = "";
|
|
4830
|
+
const paragraphs = [];
|
|
4831
|
+
const tableColumns = [];
|
|
4832
|
+
for (const t of rawTexts) {
|
|
4833
|
+
if (!title && t.length < 60 && !t.includes("\n")) {
|
|
4834
|
+
title = t;
|
|
4835
|
+
} else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
|
|
4836
|
+
tableColumns.push(t);
|
|
4837
|
+
} else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
|
|
4838
|
+
subtitle = t;
|
|
4839
|
+
} else {
|
|
4840
|
+
paragraphs.push(t);
|
|
4841
|
+
}
|
|
4595
4842
|
}
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
const text = new TextDecoder("utf-16le").decode(bytes).trim();
|
|
4600
|
-
if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
|
|
4601
|
-
currentSlideTexts.push(text);
|
|
4843
|
+
let pictureUrl = null;
|
|
4844
|
+
if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
|
|
4845
|
+
pictureUrl = pictures[picIdx++];
|
|
4602
4846
|
}
|
|
4847
|
+
slides.push({
|
|
4848
|
+
slideIndex: slides.length + 1,
|
|
4849
|
+
title: title || `Slide ${slides.length + 1}`,
|
|
4850
|
+
subtitle,
|
|
4851
|
+
paragraphs,
|
|
4852
|
+
tableColumns,
|
|
4853
|
+
pictureUrl,
|
|
4854
|
+
hasOle
|
|
4855
|
+
});
|
|
4603
4856
|
}
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
offset += 8;
|
|
4607
|
-
} else {
|
|
4608
|
-
offset += 8 + recLen;
|
|
4609
|
-
}
|
|
4610
|
-
}
|
|
4611
|
-
if (currentSlideTexts.length > 0) {
|
|
4612
|
-
const title = currentSlideTexts[0] || "Slide";
|
|
4613
|
-
const texts = currentSlideTexts.slice(1);
|
|
4614
|
-
slides.push({ title, texts });
|
|
4857
|
+
if (isContainer) offset += 8;
|
|
4858
|
+
else offset += 8 + recLen;
|
|
4615
4859
|
}
|
|
4616
4860
|
if (slides.length === 0) {
|
|
4617
4861
|
const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
|
|
4618
|
-
const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{
|
|
4619
|
-
const
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4862
|
+
const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
|
|
4863
|
+
const clean = matches.map((m) => m.trim()).filter(
|
|
4864
|
+
(m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
|
|
4865
|
+
);
|
|
4866
|
+
if (clean.length > 0) {
|
|
4867
|
+
const chunkSize = 3;
|
|
4868
|
+
for (let i = 0; i < clean.length; i += chunkSize) {
|
|
4869
|
+
const chunk = clean.slice(i, i + chunkSize);
|
|
4624
4870
|
slides.push({
|
|
4871
|
+
slideIndex: slides.length + 1,
|
|
4625
4872
|
title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
|
|
4626
|
-
|
|
4873
|
+
subtitle: chunk.length > 2 ? chunk[1] : void 0,
|
|
4874
|
+
paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
|
|
4875
|
+
tableColumns: [],
|
|
4876
|
+
pictureUrl: pictures[slides.length] || null
|
|
4627
4877
|
});
|
|
4628
4878
|
}
|
|
4629
4879
|
}
|