@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/react.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as THREE from 'three';
|
|
|
10
10
|
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
|
|
11
11
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
|
|
12
12
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
13
|
-
import
|
|
13
|
+
import * as RTFJS from 'rtf.js/dist/RTFJS.bundle.js';
|
|
14
14
|
import { jsx } from 'react/jsx-runtime';
|
|
15
15
|
|
|
16
16
|
// src/react.tsx
|
|
@@ -267,6 +267,39 @@ function detectOoxmlType(buffer) {
|
|
|
267
267
|
}
|
|
268
268
|
return "application/zip";
|
|
269
269
|
}
|
|
270
|
+
function detectCfbfType(buffer) {
|
|
271
|
+
const bytes = new Uint8Array(buffer);
|
|
272
|
+
const hasUtf16le = (str) => {
|
|
273
|
+
const target = new Uint8Array(str.length * 2);
|
|
274
|
+
for (let i = 0; i < str.length; i++) {
|
|
275
|
+
target[i * 2] = str.charCodeAt(i);
|
|
276
|
+
target[i * 2 + 1] = 0;
|
|
277
|
+
}
|
|
278
|
+
const targetLen = target.length;
|
|
279
|
+
const max = bytes.length - targetLen;
|
|
280
|
+
for (let i = 0; i <= max; i++) {
|
|
281
|
+
let match = true;
|
|
282
|
+
for (let j = 0; j < targetLen; j++) {
|
|
283
|
+
if (bytes[i + j] !== target[j]) {
|
|
284
|
+
match = false;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (match) return true;
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
};
|
|
292
|
+
if (hasUtf16le("PowerPoint Document")) {
|
|
293
|
+
return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
|
|
294
|
+
}
|
|
295
|
+
if (hasUtf16le("WordDocument")) {
|
|
296
|
+
return { mime: "application/msword", extension: ".doc" };
|
|
297
|
+
}
|
|
298
|
+
if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
|
|
299
|
+
return { mime: "application/vnd.ms-excel", extension: ".xls" };
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
270
303
|
function extractExtension(nameOrUrl) {
|
|
271
304
|
try {
|
|
272
305
|
const url = new URL(nameOrUrl);
|
|
@@ -331,6 +364,18 @@ async function sourceToArrayBuffer(source, signal) {
|
|
|
331
364
|
} else {
|
|
332
365
|
metadata.mimeType = metadata.mimeType ?? magicMime;
|
|
333
366
|
}
|
|
367
|
+
} else if (magicMime === "application/x-cfbf") {
|
|
368
|
+
if (metadata.extension) {
|
|
369
|
+
metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
|
|
370
|
+
} else {
|
|
371
|
+
const cfbf = detectCfbfType(buffer);
|
|
372
|
+
if (cfbf) {
|
|
373
|
+
metadata.mimeType = cfbf.mime;
|
|
374
|
+
metadata.extension = cfbf.extension;
|
|
375
|
+
} else {
|
|
376
|
+
metadata.mimeType = magicMime;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
334
379
|
} else {
|
|
335
380
|
metadata.mimeType = magicMime;
|
|
336
381
|
}
|
|
@@ -1619,7 +1664,10 @@ var DocxPlugin = class {
|
|
|
1619
1664
|
supports(file) {
|
|
1620
1665
|
const ext = file.metadata.extension?.toLowerCase();
|
|
1621
1666
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
1622
|
-
|
|
1667
|
+
if (ext) {
|
|
1668
|
+
return this.extensions.includes(ext);
|
|
1669
|
+
}
|
|
1670
|
+
return this.mimeTypes.includes(mime || "");
|
|
1623
1671
|
}
|
|
1624
1672
|
getToolbarActions(instance) {
|
|
1625
1673
|
return [
|
|
@@ -2008,7 +2056,10 @@ var ExcelPlugin = class {
|
|
|
2008
2056
|
supports(file) {
|
|
2009
2057
|
const ext = file.metadata.extension?.toLowerCase();
|
|
2010
2058
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
2011
|
-
|
|
2059
|
+
if (ext) {
|
|
2060
|
+
return this.extensions.includes(ext);
|
|
2061
|
+
}
|
|
2062
|
+
return this.mimeTypes.includes(mime || "");
|
|
2012
2063
|
}
|
|
2013
2064
|
getToolbarActions(instance) {
|
|
2014
2065
|
return [
|
|
@@ -2950,7 +3001,10 @@ var PptxPlugin = class {
|
|
|
2950
3001
|
supports(file) {
|
|
2951
3002
|
const ext = file.metadata.extension?.toLowerCase();
|
|
2952
3003
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
2953
|
-
|
|
3004
|
+
if (ext) {
|
|
3005
|
+
return this.extensions.includes(ext);
|
|
3006
|
+
}
|
|
3007
|
+
return this.mimeTypes.includes(mime || "");
|
|
2954
3008
|
}
|
|
2955
3009
|
getToolbarActions(instance) {
|
|
2956
3010
|
return [
|
|
@@ -3395,6 +3449,9 @@ var RtfPlugin = class {
|
|
|
3395
3449
|
ctx.container.appendChild(wrapper);
|
|
3396
3450
|
let scale = 1;
|
|
3397
3451
|
try {
|
|
3452
|
+
if (typeof RTFJS.loggingEnabled === "function") {
|
|
3453
|
+
RTFJS.loggingEnabled(false);
|
|
3454
|
+
}
|
|
3398
3455
|
const doc = new RTFJS.Document(ctx.buffer, {});
|
|
3399
3456
|
const htmlElements = await doc.render();
|
|
3400
3457
|
for (const el of htmlElements) {
|
|
@@ -3593,7 +3650,10 @@ var OpenDocumentPlugin = class {
|
|
|
3593
3650
|
supports(file) {
|
|
3594
3651
|
const ext = file.metadata.extension?.toLowerCase();
|
|
3595
3652
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
3596
|
-
|
|
3653
|
+
if (ext) {
|
|
3654
|
+
return this.extensions.includes(ext);
|
|
3655
|
+
}
|
|
3656
|
+
return this.mimeTypes.includes(mime || "");
|
|
3597
3657
|
}
|
|
3598
3658
|
getToolbarActions(instance) {
|
|
3599
3659
|
const isPresentation = instance.isPresentation;
|
|
@@ -4008,7 +4068,10 @@ var DocPlugin = class {
|
|
|
4008
4068
|
supports(file) {
|
|
4009
4069
|
const ext = file.metadata.extension?.toLowerCase();
|
|
4010
4070
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
4011
|
-
|
|
4071
|
+
if (ext) {
|
|
4072
|
+
return this.extensions.includes(ext);
|
|
4073
|
+
}
|
|
4074
|
+
return this.mimeTypes.includes(mime || "");
|
|
4012
4075
|
}
|
|
4013
4076
|
getToolbarActions(instance) {
|
|
4014
4077
|
return [
|
|
@@ -4231,22 +4294,22 @@ var DocPlugin = class {
|
|
|
4231
4294
|
* Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
|
|
4232
4295
|
*/
|
|
4233
4296
|
extractStringsFromBytes(bytes) {
|
|
4234
|
-
const
|
|
4235
|
-
const
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4297
|
+
const rawAnsi = new TextDecoder("latin1").decode(bytes);
|
|
4298
|
+
const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
|
|
4299
|
+
const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
|
|
4300
|
+
const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
|
|
4301
|
+
const candidateLines = [];
|
|
4302
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4303
|
+
for (const run of [...ansiRuns, ...utf16Runs]) {
|
|
4304
|
+
const trimmed = run.trim();
|
|
4305
|
+
if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
|
|
4306
|
+
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)) {
|
|
4307
|
+
seen.add(trimmed);
|
|
4308
|
+
candidateLines.push(trimmed);
|
|
4309
|
+
}
|
|
4247
4310
|
}
|
|
4248
4311
|
}
|
|
4249
|
-
return
|
|
4312
|
+
return candidateLines.join("\n\n");
|
|
4250
4313
|
}
|
|
4251
4314
|
heuristicTextExtraction(buffer) {
|
|
4252
4315
|
return this.extractStringsFromBytes(new Uint8Array(buffer));
|
|
@@ -4301,14 +4364,17 @@ function docPlugin() {
|
|
|
4301
4364
|
}
|
|
4302
4365
|
var PptPlugin = class {
|
|
4303
4366
|
id = "ppt";
|
|
4304
|
-
name = "
|
|
4367
|
+
name = "PowerPoint Presentation (.ppt, .pps, .pot)";
|
|
4305
4368
|
extensions = [".ppt", ".pps", ".pot"];
|
|
4306
4369
|
mimeTypes = ["application/vnd.ms-powerpoint"];
|
|
4307
|
-
weight =
|
|
4370
|
+
weight = 85;
|
|
4308
4371
|
supports(file) {
|
|
4309
4372
|
const ext = file.metadata.extension?.toLowerCase();
|
|
4310
4373
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
4311
|
-
|
|
4374
|
+
if (ext) {
|
|
4375
|
+
return this.extensions.includes(ext);
|
|
4376
|
+
}
|
|
4377
|
+
return this.mimeTypes.includes(mime || "");
|
|
4312
4378
|
}
|
|
4313
4379
|
getToolbarActions(instance) {
|
|
4314
4380
|
return [
|
|
@@ -4396,19 +4462,15 @@ var PptPlugin = class {
|
|
|
4396
4462
|
container.style.alignItems = "center";
|
|
4397
4463
|
container.style.padding = "32px 16px";
|
|
4398
4464
|
container.style.backgroundColor = "#0f172a";
|
|
4465
|
+
container.style.boxSizing = "border-box";
|
|
4399
4466
|
const slideCard = document.createElement("div");
|
|
4400
4467
|
slideCard.className = "fp-ppt-slide-card";
|
|
4401
4468
|
slideCard.style.width = "960px";
|
|
4402
|
-
slideCard.style.maxWidth = "
|
|
4469
|
+
slideCard.style.maxWidth = "92%";
|
|
4403
4470
|
slideCard.style.aspectRatio = "16 / 9";
|
|
4404
4471
|
slideCard.style.backgroundColor = "#ffffff";
|
|
4405
|
-
slideCard.style.boxShadow = "0
|
|
4472
|
+
slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
|
|
4406
4473
|
slideCard.style.borderRadius = "8px";
|
|
4407
|
-
slideCard.style.padding = "48px";
|
|
4408
|
-
slideCard.style.display = "flex";
|
|
4409
|
-
slideCard.style.flexDirection = "column";
|
|
4410
|
-
slideCard.style.justifyContent = "center";
|
|
4411
|
-
slideCard.style.alignItems = "center";
|
|
4412
4474
|
slideCard.style.boxSizing = "border-box";
|
|
4413
4475
|
slideCard.style.position = "relative";
|
|
4414
4476
|
slideCard.style.overflow = "hidden";
|
|
@@ -4419,21 +4481,25 @@ var PptPlugin = class {
|
|
|
4419
4481
|
let scale = 1;
|
|
4420
4482
|
let currentSlide = 1;
|
|
4421
4483
|
let slides = [];
|
|
4484
|
+
const createdBlobUrls = [];
|
|
4422
4485
|
try {
|
|
4423
4486
|
const cfbf = new CfbfReader(ctx.buffer);
|
|
4424
4487
|
const pptStream = cfbf.readStream("PowerPoint Document");
|
|
4425
4488
|
if (!pptStream || pptStream.length < 512) {
|
|
4426
4489
|
throw new Error("PowerPoint Document stream not found in CFBF container");
|
|
4427
4490
|
}
|
|
4428
|
-
|
|
4491
|
+
const pictures = this.extractPictures(cfbf, createdBlobUrls);
|
|
4492
|
+
slides = this.extractSlides(pptStream, pictures);
|
|
4429
4493
|
} catch (err) {
|
|
4430
4494
|
console.warn("[PptPlugin] Error extracting binary slides:", err);
|
|
4431
4495
|
}
|
|
4432
4496
|
if (slides.length === 0) {
|
|
4433
4497
|
slides = [
|
|
4434
4498
|
{
|
|
4499
|
+
slideIndex: 1,
|
|
4435
4500
|
title: ctx.metadata.name || "PowerPoint Presentation",
|
|
4436
|
-
|
|
4501
|
+
paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
|
|
4502
|
+
tableColumns: []
|
|
4437
4503
|
}
|
|
4438
4504
|
];
|
|
4439
4505
|
}
|
|
@@ -4442,23 +4508,73 @@ var PptPlugin = class {
|
|
|
4442
4508
|
currentSlide = idx;
|
|
4443
4509
|
const s = slides[idx - 1];
|
|
4444
4510
|
if (!s) return;
|
|
4511
|
+
let contentHtml = "";
|
|
4512
|
+
if (s.pictureUrl) {
|
|
4513
|
+
contentHtml = `
|
|
4514
|
+
<div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
|
|
4515
|
+
<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;" />
|
|
4516
|
+
</div>
|
|
4517
|
+
`;
|
|
4518
|
+
} else if (s.tableColumns.length > 0) {
|
|
4519
|
+
const cols = s.tableColumns;
|
|
4520
|
+
contentHtml = `
|
|
4521
|
+
<div style="flex: 1; overflow: auto; padding: 8px 0;">
|
|
4522
|
+
<table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
|
|
4523
|
+
<thead>
|
|
4524
|
+
<tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
|
|
4525
|
+
${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2.sanitize(c)}</th>`).join("")}
|
|
4526
|
+
</tr>
|
|
4527
|
+
</thead>
|
|
4528
|
+
<tbody>
|
|
4529
|
+
${[1, 2, 3, 4, 5].map((rowIdx) => `
|
|
4530
|
+
<tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
|
|
4531
|
+
${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
|
|
4532
|
+
</tr>
|
|
4533
|
+
`).join("")}
|
|
4534
|
+
</tbody>
|
|
4535
|
+
</table>
|
|
4536
|
+
</div>
|
|
4537
|
+
`;
|
|
4538
|
+
} else {
|
|
4539
|
+
const pTags = s.paragraphs.map((p) => {
|
|
4540
|
+
const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
|
|
4541
|
+
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("");
|
|
4542
|
+
}).join("");
|
|
4543
|
+
contentHtml = `
|
|
4544
|
+
<div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
|
|
4545
|
+
${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
|
|
4546
|
+
</div>
|
|
4547
|
+
`;
|
|
4548
|
+
}
|
|
4445
4549
|
slideCard.innerHTML = `
|
|
4446
|
-
<div style="
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4550
|
+
<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;">
|
|
4551
|
+
<!-- Header Banner matching PowerPoint design -->
|
|
4552
|
+
<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;">
|
|
4553
|
+
<h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
|
|
4554
|
+
${DOMPurify2.sanitize(s.title)}
|
|
4555
|
+
</h1>
|
|
4556
|
+
${s.subtitle ? `
|
|
4557
|
+
<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);">
|
|
4558
|
+
${DOMPurify2.sanitize(s.subtitle)}
|
|
4559
|
+
</span>
|
|
4560
|
+
` : `
|
|
4561
|
+
<span style="font-size: 12px; color: #365314; font-weight: 600;">
|
|
4562
|
+
Slide ${idx} / ${totalSlides}
|
|
4563
|
+
</span>
|
|
4564
|
+
`}
|
|
4455
4565
|
</div>
|
|
4566
|
+
|
|
4567
|
+
<!-- Slide Content -->
|
|
4568
|
+
${contentHtml}
|
|
4456
4569
|
</div>
|
|
4457
4570
|
`;
|
|
4458
4571
|
ctx.emit("page-change", { page: currentSlide, total: totalSlides });
|
|
4459
4572
|
};
|
|
4460
4573
|
renderSlide(1);
|
|
4461
4574
|
const cleanup = () => {
|
|
4575
|
+
for (const u of createdBlobUrls) {
|
|
4576
|
+
URL.revokeObjectURL(u);
|
|
4577
|
+
}
|
|
4462
4578
|
container.remove();
|
|
4463
4579
|
ctx.container.innerHTML = "";
|
|
4464
4580
|
};
|
|
@@ -4498,13 +4614,48 @@ var PptPlugin = class {
|
|
|
4498
4614
|
if (!ctx2d) return;
|
|
4499
4615
|
canvas.width = 160;
|
|
4500
4616
|
canvas.height = 90;
|
|
4501
|
-
ctx2d.fillStyle = "#
|
|
4617
|
+
ctx2d.fillStyle = "#334155";
|
|
4502
4618
|
ctx2d.fillRect(0, 0, 160, 90);
|
|
4503
|
-
ctx2d.fillStyle = "#
|
|
4504
|
-
ctx2d.
|
|
4505
|
-
ctx2d.
|
|
4506
|
-
|
|
4507
|
-
ctx2d.
|
|
4619
|
+
ctx2d.fillStyle = "#ffffff";
|
|
4620
|
+
ctx2d.fillRect(3, 3, 154, 84);
|
|
4621
|
+
ctx2d.fillStyle = "#84cc16";
|
|
4622
|
+
ctx2d.fillRect(6, 6, 148, 18);
|
|
4623
|
+
ctx2d.fillStyle = "#1e293b";
|
|
4624
|
+
ctx2d.font = "bold 9px sans-serif";
|
|
4625
|
+
ctx2d.textAlign = "left";
|
|
4626
|
+
const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
|
|
4627
|
+
ctx2d.fillText(displayTitle, 10, 19);
|
|
4628
|
+
if (s.subtitle) {
|
|
4629
|
+
ctx2d.fillStyle = "#38bdf8";
|
|
4630
|
+
ctx2d.fillRect(116, 9, 34, 12);
|
|
4631
|
+
ctx2d.fillStyle = "#ffffff";
|
|
4632
|
+
ctx2d.font = "bold 7px sans-serif";
|
|
4633
|
+
ctx2d.textAlign = "center";
|
|
4634
|
+
ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
|
|
4635
|
+
}
|
|
4636
|
+
if (s.pictureUrl) {
|
|
4637
|
+
ctx2d.fillStyle = "#3b82f6";
|
|
4638
|
+
ctx2d.fillRect(52, 34, 56, 42);
|
|
4639
|
+
ctx2d.fillStyle = "#ffffff";
|
|
4640
|
+
ctx2d.font = "8px sans-serif";
|
|
4641
|
+
ctx2d.textAlign = "center";
|
|
4642
|
+
ctx2d.fillText("Chart", 80, 58);
|
|
4643
|
+
} else if (s.tableColumns.length > 0) {
|
|
4644
|
+
ctx2d.strokeStyle = "#cbd5e1";
|
|
4645
|
+
ctx2d.lineWidth = 1;
|
|
4646
|
+
ctx2d.strokeRect(14, 32, 132, 46);
|
|
4647
|
+
for (let l = 1; l <= 3; l++) {
|
|
4648
|
+
ctx2d.beginPath();
|
|
4649
|
+
ctx2d.moveTo(14, 32 + l * 11);
|
|
4650
|
+
ctx2d.lineTo(146, 32 + l * 11);
|
|
4651
|
+
ctx2d.stroke();
|
|
4652
|
+
}
|
|
4653
|
+
} else {
|
|
4654
|
+
ctx2d.fillStyle = "#94a3b8";
|
|
4655
|
+
for (let l = 0; l < 4; l++) {
|
|
4656
|
+
ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4508
4659
|
}
|
|
4509
4660
|
}));
|
|
4510
4661
|
},
|
|
@@ -4522,66 +4673,165 @@ var PptPlugin = class {
|
|
|
4522
4673
|
}
|
|
4523
4674
|
};
|
|
4524
4675
|
}
|
|
4676
|
+
/**
|
|
4677
|
+
* Extract PNG and JPEG images from the Pictures stream
|
|
4678
|
+
*/
|
|
4679
|
+
extractPictures(cfbf, createdUrls) {
|
|
4680
|
+
const urls = [];
|
|
4681
|
+
try {
|
|
4682
|
+
const picStream = cfbf.readStream("Pictures");
|
|
4683
|
+
if (!picStream || picStream.length < 32) return urls;
|
|
4684
|
+
const pBuf = new Uint8Array(picStream);
|
|
4685
|
+
const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
4686
|
+
const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
|
|
4687
|
+
for (let i = 0; i <= pBuf.length - 8; i++) {
|
|
4688
|
+
let match = true;
|
|
4689
|
+
for (let j = 0; j < 8; j++) {
|
|
4690
|
+
if (pBuf[i + j] !== pngSig[j]) {
|
|
4691
|
+
match = false;
|
|
4692
|
+
break;
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
if (match) {
|
|
4696
|
+
let endIdx = -1;
|
|
4697
|
+
for (let k = i + 8; k <= pBuf.length - 8; k++) {
|
|
4698
|
+
let endMatch = true;
|
|
4699
|
+
for (let j = 0; j < 8; j++) {
|
|
4700
|
+
if (pBuf[k + j] !== iendSig[j]) {
|
|
4701
|
+
endMatch = false;
|
|
4702
|
+
break;
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
if (endMatch) {
|
|
4706
|
+
endIdx = k + 8;
|
|
4707
|
+
break;
|
|
4708
|
+
}
|
|
4709
|
+
}
|
|
4710
|
+
if (endIdx !== -1) {
|
|
4711
|
+
const pngBytes = pBuf.subarray(i, endIdx);
|
|
4712
|
+
const blob = new Blob([pngBytes], { type: "image/png" });
|
|
4713
|
+
const url = URL.createObjectURL(blob);
|
|
4714
|
+
urls.push(url);
|
|
4715
|
+
createdUrls.push(url);
|
|
4716
|
+
i = endIdx;
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
for (let i = 0; i <= pBuf.length - 3; i++) {
|
|
4721
|
+
if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
|
|
4722
|
+
let endIdx = -1;
|
|
4723
|
+
for (let k = i + 3; k < pBuf.length - 1; k++) {
|
|
4724
|
+
if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
|
|
4725
|
+
endIdx = k + 2;
|
|
4726
|
+
break;
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
if (endIdx !== -1) {
|
|
4730
|
+
const jpgBytes = pBuf.subarray(i, endIdx);
|
|
4731
|
+
const blob = new Blob([jpgBytes], { type: "image/jpeg" });
|
|
4732
|
+
const url = URL.createObjectURL(blob);
|
|
4733
|
+
urls.push(url);
|
|
4734
|
+
createdUrls.push(url);
|
|
4735
|
+
i = endIdx;
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
} catch (err) {
|
|
4740
|
+
console.warn("[PptPlugin] Error extracting pictures:", err);
|
|
4741
|
+
}
|
|
4742
|
+
return urls;
|
|
4743
|
+
}
|
|
4525
4744
|
/**
|
|
4526
4745
|
* Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
|
|
4527
4746
|
*/
|
|
4528
|
-
extractSlides(stream) {
|
|
4747
|
+
extractSlides(stream, pictures) {
|
|
4529
4748
|
const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
|
|
4530
4749
|
const len = stream.length;
|
|
4531
4750
|
let offset = 0;
|
|
4532
4751
|
const slides = [];
|
|
4533
|
-
let
|
|
4752
|
+
let picIdx = 0;
|
|
4534
4753
|
while (offset + 8 <= len) {
|
|
4535
4754
|
const recVerInst = view.getUint16(offset, true);
|
|
4536
4755
|
const recType = view.getUint16(offset + 2, true);
|
|
4537
4756
|
const recLen = view.getUint32(offset + 4, true);
|
|
4757
|
+
const isContainer = (recVerInst & 15) === 15;
|
|
4538
4758
|
if (recType === 1006) {
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4759
|
+
const slideEnd = Math.min(len, offset + 8 + recLen);
|
|
4760
|
+
const rawTexts = [];
|
|
4761
|
+
let hasOle = false;
|
|
4762
|
+
let sOff = offset + 8;
|
|
4763
|
+
while (sOff + 8 <= slideEnd) {
|
|
4764
|
+
const cVerInst = view.getUint16(sOff, true);
|
|
4765
|
+
const cType = view.getUint16(sOff + 2, true);
|
|
4766
|
+
const cLen = view.getUint32(sOff + 4, true);
|
|
4767
|
+
const cIsContainer = (cVerInst & 15) === 15;
|
|
4768
|
+
if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
|
|
4769
|
+
const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
|
|
4770
|
+
const txt = new TextDecoder("latin1").decode(bytes).trim();
|
|
4771
|
+
if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
|
|
4772
|
+
rawTexts.push(txt);
|
|
4773
|
+
}
|
|
4774
|
+
} else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
|
|
4775
|
+
const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
|
|
4776
|
+
const txt = new TextDecoder("utf-16le").decode(bytes).trim();
|
|
4777
|
+
if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
|
|
4778
|
+
rawTexts.push(txt);
|
|
4779
|
+
}
|
|
4780
|
+
} else if (cType === 3009 || cType === 3011) {
|
|
4781
|
+
hasOle = true;
|
|
4782
|
+
}
|
|
4783
|
+
if (cIsContainer) sOff += 8;
|
|
4784
|
+
else sOff += 8 + cLen;
|
|
4544
4785
|
}
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4786
|
+
let title = "";
|
|
4787
|
+
let subtitle = "";
|
|
4788
|
+
const paragraphs = [];
|
|
4789
|
+
const tableColumns = [];
|
|
4790
|
+
for (const t of rawTexts) {
|
|
4791
|
+
if (!title && t.length < 60 && !t.includes("\n")) {
|
|
4792
|
+
title = t;
|
|
4793
|
+
} else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
|
|
4794
|
+
tableColumns.push(t);
|
|
4795
|
+
} else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
|
|
4796
|
+
subtitle = t;
|
|
4797
|
+
} else {
|
|
4798
|
+
paragraphs.push(t);
|
|
4799
|
+
}
|
|
4553
4800
|
}
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
const text = new TextDecoder("utf-16le").decode(bytes).trim();
|
|
4558
|
-
if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
|
|
4559
|
-
currentSlideTexts.push(text);
|
|
4801
|
+
let pictureUrl = null;
|
|
4802
|
+
if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
|
|
4803
|
+
pictureUrl = pictures[picIdx++];
|
|
4560
4804
|
}
|
|
4805
|
+
slides.push({
|
|
4806
|
+
slideIndex: slides.length + 1,
|
|
4807
|
+
title: title || `Slide ${slides.length + 1}`,
|
|
4808
|
+
subtitle,
|
|
4809
|
+
paragraphs,
|
|
4810
|
+
tableColumns,
|
|
4811
|
+
pictureUrl,
|
|
4812
|
+
hasOle
|
|
4813
|
+
});
|
|
4561
4814
|
}
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
offset += 8;
|
|
4565
|
-
} else {
|
|
4566
|
-
offset += 8 + recLen;
|
|
4567
|
-
}
|
|
4568
|
-
}
|
|
4569
|
-
if (currentSlideTexts.length > 0) {
|
|
4570
|
-
const title = currentSlideTexts[0] || "Slide";
|
|
4571
|
-
const texts = currentSlideTexts.slice(1);
|
|
4572
|
-
slides.push({ title, texts });
|
|
4815
|
+
if (isContainer) offset += 8;
|
|
4816
|
+
else offset += 8 + recLen;
|
|
4573
4817
|
}
|
|
4574
4818
|
if (slides.length === 0) {
|
|
4575
4819
|
const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
|
|
4576
|
-
const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{
|
|
4577
|
-
const
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4820
|
+
const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
|
|
4821
|
+
const clean = matches.map((m) => m.trim()).filter(
|
|
4822
|
+
(m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
|
|
4823
|
+
);
|
|
4824
|
+
if (clean.length > 0) {
|
|
4825
|
+
const chunkSize = 3;
|
|
4826
|
+
for (let i = 0; i < clean.length; i += chunkSize) {
|
|
4827
|
+
const chunk = clean.slice(i, i + chunkSize);
|
|
4582
4828
|
slides.push({
|
|
4829
|
+
slideIndex: slides.length + 1,
|
|
4583
4830
|
title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
|
|
4584
|
-
|
|
4831
|
+
subtitle: chunk.length > 2 ? chunk[1] : void 0,
|
|
4832
|
+
paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
|
|
4833
|
+
tableColumns: [],
|
|
4834
|
+
pictureUrl: pictures[slides.length] || null
|
|
4585
4835
|
});
|
|
4586
4836
|
}
|
|
4587
4837
|
}
|