@open-file-viewer/core 0.1.24 → 0.1.26
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/index.cjs +201 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +201 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6346,6 +6346,7 @@ function decodeXml(value) {
|
|
|
6346
6346
|
// src/plugins/office.ts
|
|
6347
6347
|
import JSZip3 from "jszip";
|
|
6348
6348
|
import DOMPurify2 from "dompurify";
|
|
6349
|
+
import * as docxPreview from "docx-preview";
|
|
6349
6350
|
|
|
6350
6351
|
// src/plugins/msdoc.ts
|
|
6351
6352
|
var CFB_SIGNATURE = [208, 207, 17, 224, 161, 177, 26, 225];
|
|
@@ -7782,6 +7783,7 @@ var presentationExtensions = /* @__PURE__ */ new Set(["pptx", "pptm", "ppt", "pp
|
|
|
7782
7783
|
var packagedOfficeCandidates = /* @__PURE__ */ new Set(["wps", "et", "dps", "numbers", "key"]);
|
|
7783
7784
|
var SHEET_WINDOW_ROWS = 200;
|
|
7784
7785
|
var SHEET_WINDOW_COLUMNS = 80;
|
|
7786
|
+
var DEFAULT_DOCX_RENDER_TIMEOUT_MS = 15e3;
|
|
7785
7787
|
var DEFAULT_PPTX_RENDER_TIMEOUT_MS = 12e3;
|
|
7786
7788
|
var PPTX_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
7787
7789
|
var officeMimeTypes = /* @__PURE__ */ new Set([
|
|
@@ -8090,23 +8092,28 @@ async function renderDocx(panel, arrayBuffer, fit) {
|
|
|
8090
8092
|
document.head.append(styleContainer);
|
|
8091
8093
|
let disposeFit;
|
|
8092
8094
|
try {
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8095
|
+
await withTimeout(
|
|
8096
|
+
(async () => {
|
|
8097
|
+
await docxPreview.renderAsync(arrayBuffer, content, styleContainer, {
|
|
8098
|
+
className: "ofv-docx",
|
|
8099
|
+
inWrapper: true,
|
|
8100
|
+
breakPages: true,
|
|
8101
|
+
ignoreWidth: false,
|
|
8102
|
+
ignoreHeight: false,
|
|
8103
|
+
ignoreFonts: false,
|
|
8104
|
+
renderHeaders: true,
|
|
8105
|
+
renderFooters: true,
|
|
8106
|
+
renderFootnotes: true,
|
|
8107
|
+
renderEndnotes: true,
|
|
8108
|
+
renderComments: true,
|
|
8109
|
+
renderAltChunks: true,
|
|
8110
|
+
experimental: true,
|
|
8111
|
+
useBase64URL: true
|
|
8112
|
+
});
|
|
8113
|
+
})(),
|
|
8114
|
+
docxRenderTimeoutMs(),
|
|
8115
|
+
"DOCX rendering"
|
|
8116
|
+
);
|
|
8110
8117
|
await normalizeDocxLayout(content, arrayBuffer);
|
|
8111
8118
|
const shouldUseTextboxFallback = await docxPreviewLooksBlank(content, arrayBuffer) || await docxPreviewMissesRichTextboxContent(content, arrayBuffer) || await docxShouldPreferTextboxLayoutFallback(arrayBuffer);
|
|
8112
8119
|
if (shouldUseTextboxFallback) {
|
|
@@ -8129,13 +8136,18 @@ async function renderDocx(panel, arrayBuffer, fit) {
|
|
|
8129
8136
|
} catch (error) {
|
|
8130
8137
|
disposeFit?.();
|
|
8131
8138
|
styleContainer.remove();
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8139
|
+
const fallbackContent = document.createElement("div");
|
|
8140
|
+
fallbackContent.className = "ofv-docx-document";
|
|
8141
|
+
await renderDocxContentFallback(fallbackContent, arrayBuffer);
|
|
8142
|
+
panel.append(fallbackContent);
|
|
8135
8143
|
console.warn("DOCX layout preview failed, fell back to Mammoth:", error);
|
|
8136
8144
|
}
|
|
8137
8145
|
return () => void 0;
|
|
8138
8146
|
}
|
|
8147
|
+
function docxRenderTimeoutMs() {
|
|
8148
|
+
const override = globalThis.__OFV_DOCX_RENDER_TIMEOUT_MS__;
|
|
8149
|
+
return typeof override === "number" && override > 0 ? override : DEFAULT_DOCX_RENDER_TIMEOUT_MS;
|
|
8150
|
+
}
|
|
8139
8151
|
async function docxPreviewLooksBlank(container, arrayBuffer) {
|
|
8140
8152
|
if (container.querySelector("img, svg, canvas, table")) {
|
|
8141
8153
|
return false;
|
|
@@ -8228,12 +8240,13 @@ async function renderDocxContentFallback(container, arrayBuffer, options = {}) {
|
|
|
8228
8240
|
return;
|
|
8229
8241
|
}
|
|
8230
8242
|
try {
|
|
8231
|
-
|
|
8232
|
-
|
|
8243
|
+
const mammothContent = document.createElement("div");
|
|
8244
|
+
await withTimeout(renderDocxWithMammoth(mammothContent, arrayBuffer), docxRenderTimeoutMs(), "DOCX fallback rendering");
|
|
8245
|
+
const renderedText = normalizePreviewText(mammothContent.querySelector(".ofv-document")?.textContent || "");
|
|
8233
8246
|
if (renderedText.length >= 24) {
|
|
8247
|
+
container.append(...Array.from(mammothContent.childNodes));
|
|
8234
8248
|
return;
|
|
8235
8249
|
}
|
|
8236
|
-
container.querySelector(".ofv-document")?.remove();
|
|
8237
8250
|
await renderDocxTextFallback(container, arrayBuffer);
|
|
8238
8251
|
} catch (fallbackError) {
|
|
8239
8252
|
await renderDocxTextFallback(container, arrayBuffer);
|
|
@@ -10466,6 +10479,7 @@ async function renderPptx(panel, arrayBuffer) {
|
|
|
10466
10479
|
container.className = "ofv-pptx-viewer";
|
|
10467
10480
|
let insight;
|
|
10468
10481
|
let zip;
|
|
10482
|
+
let placeholderFontCorrections = [];
|
|
10469
10483
|
try {
|
|
10470
10484
|
zip = await JSZip3.loadAsync(arrayBuffer);
|
|
10471
10485
|
insight = await inspectPptxPresentation(zip);
|
|
@@ -10473,11 +10487,18 @@ async function renderPptx(panel, arrayBuffer) {
|
|
|
10473
10487
|
} catch (error) {
|
|
10474
10488
|
console.warn("PPTX structure insight extraction failed:", error);
|
|
10475
10489
|
}
|
|
10490
|
+
if (zip) {
|
|
10491
|
+
try {
|
|
10492
|
+
placeholderFontCorrections = await inspectPptxPlaceholderFontCorrections(zip);
|
|
10493
|
+
} catch (error) {
|
|
10494
|
+
console.warn("PPTX placeholder font extraction failed:", error);
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10476
10497
|
panel.append(container);
|
|
10477
10498
|
try {
|
|
10478
10499
|
const { PptxViewer } = await import("@aiden0z/pptx-renderer");
|
|
10479
10500
|
await withTimeout(PptxViewer.open(arrayBuffer, container), pptxRenderTimeoutMs());
|
|
10480
|
-
normalizePptxLayout(container);
|
|
10501
|
+
normalizePptxLayout(container, placeholderFontCorrections);
|
|
10481
10502
|
} catch (error) {
|
|
10482
10503
|
container.replaceChildren();
|
|
10483
10504
|
if (insight) {
|
|
@@ -10510,13 +10531,13 @@ function renderPptxTextFallback(container, insight) {
|
|
|
10510
10531
|
container.append(article);
|
|
10511
10532
|
}
|
|
10512
10533
|
}
|
|
10513
|
-
async function withTimeout(promise, timeoutMs) {
|
|
10534
|
+
async function withTimeout(promise, timeoutMs, label = "PPTX rendering") {
|
|
10514
10535
|
let timeoutId;
|
|
10515
10536
|
try {
|
|
10516
10537
|
return await Promise.race([
|
|
10517
10538
|
promise,
|
|
10518
10539
|
new Promise((_resolve, reject) => {
|
|
10519
|
-
timeoutId = window.setTimeout(() => reject(new Error(
|
|
10540
|
+
timeoutId = window.setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms.`)), timeoutMs);
|
|
10520
10541
|
})
|
|
10521
10542
|
]);
|
|
10522
10543
|
} finally {
|
|
@@ -10529,15 +10550,168 @@ function pptxRenderTimeoutMs() {
|
|
|
10529
10550
|
const override = globalThis.__OFV_PPTX_RENDER_TIMEOUT_MS__;
|
|
10530
10551
|
return typeof override === "number" && override > 0 ? override : DEFAULT_PPTX_RENDER_TIMEOUT_MS;
|
|
10531
10552
|
}
|
|
10532
|
-
function normalizePptxLayout(container) {
|
|
10553
|
+
function normalizePptxLayout(container, placeholderFontCorrections) {
|
|
10533
10554
|
const slideCanvases = findPptxSlideCanvases(container);
|
|
10534
10555
|
for (const slide of slideCanvases) {
|
|
10535
10556
|
if (!hasInlineBackground(slide)) {
|
|
10536
10557
|
slide.style.backgroundColor = "#FFFFFF";
|
|
10537
10558
|
}
|
|
10538
10559
|
}
|
|
10560
|
+
normalizePptxPlaceholderFonts(container, placeholderFontCorrections);
|
|
10539
10561
|
normalizePptxMirroredText(container);
|
|
10540
10562
|
}
|
|
10563
|
+
async function inspectPptxPlaceholderFontCorrections(zip) {
|
|
10564
|
+
const presentationXml = await zip.file("ppt/presentation.xml")?.async("text");
|
|
10565
|
+
const presentation = presentationXml ? parseOfficeXml(presentationXml) : void 0;
|
|
10566
|
+
const slideSize = presentation ? Array.from(presentation.getElementsByTagName("*")).find((element) => element.localName === "sldSz") : void 0;
|
|
10567
|
+
const slideWidth = Number(slideSize?.getAttribute("cx"));
|
|
10568
|
+
const slideHeight = Number(slideSize?.getAttribute("cy"));
|
|
10569
|
+
if (!(slideWidth > 0) || !(slideHeight > 0)) {
|
|
10570
|
+
return [];
|
|
10571
|
+
}
|
|
10572
|
+
const slideEntries = Object.values(zip.files).filter((entry) => !entry.dir && /^ppt\/slides\/slide\d+\.xml$/i.test(entry.name)).sort((a, b) => slideNumberFromPath(a.name) - slideNumberFromPath(b.name));
|
|
10573
|
+
const corrections = [];
|
|
10574
|
+
for (const [slideIndex, entry] of slideEntries.entries()) {
|
|
10575
|
+
const slideXml = await entry.async("text");
|
|
10576
|
+
const slide = parseOfficeXml(slideXml);
|
|
10577
|
+
if (!slide) {
|
|
10578
|
+
continue;
|
|
10579
|
+
}
|
|
10580
|
+
const relationships = await readPptxRelationships(zip, entry.name);
|
|
10581
|
+
const layoutTarget = resolvePptxRelationshipTarget(
|
|
10582
|
+
entry.name,
|
|
10583
|
+
relationships.find((relationship) => /\/slideLayout$/i.test(relationship.type))?.target
|
|
10584
|
+
);
|
|
10585
|
+
const layoutXml = layoutTarget ? await zip.file(layoutTarget)?.async("text") : void 0;
|
|
10586
|
+
const layout = layoutXml ? parseOfficeXml(layoutXml) : void 0;
|
|
10587
|
+
if (!layout) {
|
|
10588
|
+
continue;
|
|
10589
|
+
}
|
|
10590
|
+
const layoutFontSizes = readPptxLayoutPlaceholderFontSizes(layout);
|
|
10591
|
+
const shapes = Array.from(slide.getElementsByTagName("*")).filter((element) => element.localName === "sp");
|
|
10592
|
+
for (const shape of shapes) {
|
|
10593
|
+
const placeholder = findPptxDescendant(shape, "ph");
|
|
10594
|
+
const placeholderIndex = placeholder?.getAttribute("idx");
|
|
10595
|
+
const fontSizePt = placeholderIndex ? layoutFontSizes.get(placeholderIndex) : void 0;
|
|
10596
|
+
const textBody = findPptxDescendant(shape, "txBody");
|
|
10597
|
+
if (!textBody?.textContent?.trim() || !fontSizePt || hasExplicitPptxTextSize(textBody)) {
|
|
10598
|
+
continue;
|
|
10599
|
+
}
|
|
10600
|
+
const transform = findPptxDescendant(shape, "xfrm");
|
|
10601
|
+
const offset = transform ? findPptxChild(transform, "off") : void 0;
|
|
10602
|
+
const extent = transform ? findPptxChild(transform, "ext") : void 0;
|
|
10603
|
+
const left = Number(offset?.getAttribute("x"));
|
|
10604
|
+
const top = Number(offset?.getAttribute("y"));
|
|
10605
|
+
const width = Number(extent?.getAttribute("cx"));
|
|
10606
|
+
const height = Number(extent?.getAttribute("cy"));
|
|
10607
|
+
if (![left, top, width, height].every((value) => Number.isFinite(value)) || width <= 0 || height <= 0) {
|
|
10608
|
+
continue;
|
|
10609
|
+
}
|
|
10610
|
+
corrections.push({
|
|
10611
|
+
slideIndex,
|
|
10612
|
+
leftRatio: left / slideWidth,
|
|
10613
|
+
topRatio: top / slideHeight,
|
|
10614
|
+
widthRatio: width / slideWidth,
|
|
10615
|
+
heightRatio: height / slideHeight,
|
|
10616
|
+
fontSizePt
|
|
10617
|
+
});
|
|
10618
|
+
}
|
|
10619
|
+
}
|
|
10620
|
+
return corrections;
|
|
10621
|
+
}
|
|
10622
|
+
function readPptxLayoutPlaceholderFontSizes(layout) {
|
|
10623
|
+
const result = /* @__PURE__ */ new Map();
|
|
10624
|
+
const shapes = Array.from(layout.getElementsByTagName("*")).filter((element) => element.localName === "sp");
|
|
10625
|
+
for (const shape of shapes) {
|
|
10626
|
+
const placeholderIndex = findPptxDescendant(shape, "ph")?.getAttribute("idx");
|
|
10627
|
+
if (!placeholderIndex) {
|
|
10628
|
+
continue;
|
|
10629
|
+
}
|
|
10630
|
+
const textBody = findPptxDescendant(shape, "txBody");
|
|
10631
|
+
const defaultRunProperties = textBody ? Array.from(textBody.getElementsByTagName("*")).find(
|
|
10632
|
+
(element) => element.localName === "defRPr" && Number(element.getAttribute("sz")) > 0
|
|
10633
|
+
) : void 0;
|
|
10634
|
+
const size = Number(defaultRunProperties?.getAttribute("sz"));
|
|
10635
|
+
if (size > 0) {
|
|
10636
|
+
result.set(placeholderIndex, size / 100);
|
|
10637
|
+
}
|
|
10638
|
+
}
|
|
10639
|
+
return result;
|
|
10640
|
+
}
|
|
10641
|
+
function hasExplicitPptxTextSize(textBody) {
|
|
10642
|
+
return Array.from(textBody.getElementsByTagName("*")).some(
|
|
10643
|
+
(element) => (element.localName === "rPr" || element.localName === "defRPr" || element.localName === "endParaRPr") && Number(element.getAttribute("sz")) > 0
|
|
10644
|
+
);
|
|
10645
|
+
}
|
|
10646
|
+
function findPptxDescendant(element, localName) {
|
|
10647
|
+
return Array.from(element.getElementsByTagName("*")).find((child) => child.localName === localName);
|
|
10648
|
+
}
|
|
10649
|
+
function findPptxChild(element, localName) {
|
|
10650
|
+
return Array.from(element.children).find((child) => child.localName === localName);
|
|
10651
|
+
}
|
|
10652
|
+
function normalizePptxPlaceholderFonts(container, corrections) {
|
|
10653
|
+
for (const correction of corrections) {
|
|
10654
|
+
const wrapper = container.querySelector(`div[data-slide-index="${correction.slideIndex}"]`);
|
|
10655
|
+
if (!wrapper) {
|
|
10656
|
+
continue;
|
|
10657
|
+
}
|
|
10658
|
+
const match = findPptxPlaceholderElement(wrapper, correction);
|
|
10659
|
+
if (!match) {
|
|
10660
|
+
continue;
|
|
10661
|
+
}
|
|
10662
|
+
const styledText = Array.from(match.querySelectorAll("[style]")).filter(
|
|
10663
|
+
(element) => parseFloat(element.style.fontSize) > 0
|
|
10664
|
+
);
|
|
10665
|
+
for (const element of styledText) {
|
|
10666
|
+
element.style.fontSize = `${correction.fontSizePt}pt`;
|
|
10667
|
+
}
|
|
10668
|
+
if (styledText.length > 0) {
|
|
10669
|
+
match.dataset.ofvPptxPlaceholderFont = String(correction.fontSizePt);
|
|
10670
|
+
}
|
|
10671
|
+
}
|
|
10672
|
+
}
|
|
10673
|
+
function findPptxPlaceholderElement(wrapper, correction) {
|
|
10674
|
+
let best;
|
|
10675
|
+
for (const canvas of findPptxSlideCanvases(wrapper)) {
|
|
10676
|
+
const canvasWidth = parseCssPixelValue(canvas.style.width);
|
|
10677
|
+
const canvasHeight = parseCssPixelValue(canvas.style.height);
|
|
10678
|
+
if (!(canvasWidth > 0) || !(canvasHeight > 0)) {
|
|
10679
|
+
continue;
|
|
10680
|
+
}
|
|
10681
|
+
const expected = {
|
|
10682
|
+
left: correction.leftRatio * canvasWidth,
|
|
10683
|
+
top: correction.topRatio * canvasHeight,
|
|
10684
|
+
width: correction.widthRatio * canvasWidth,
|
|
10685
|
+
height: correction.heightRatio * canvasHeight
|
|
10686
|
+
};
|
|
10687
|
+
const candidates = Array.from(canvas.querySelectorAll("div")).filter(
|
|
10688
|
+
(element) => element.style.position === "absolute" && Boolean(element.textContent?.trim())
|
|
10689
|
+
);
|
|
10690
|
+
for (const element of candidates) {
|
|
10691
|
+
const actual = {
|
|
10692
|
+
left: parseCssPixelValue(element.style.left),
|
|
10693
|
+
top: parseCssPixelValue(element.style.top),
|
|
10694
|
+
width: parseCssPixelValue(element.style.width),
|
|
10695
|
+
height: parseCssPixelValue(element.style.height)
|
|
10696
|
+
};
|
|
10697
|
+
const deltas = [
|
|
10698
|
+
Math.abs(actual.left - expected.left),
|
|
10699
|
+
Math.abs(actual.top - expected.top),
|
|
10700
|
+
Math.abs(actual.width - expected.width),
|
|
10701
|
+
Math.abs(actual.height - expected.height)
|
|
10702
|
+
];
|
|
10703
|
+
const tolerance = Math.max(2, Math.min(canvasWidth, canvasHeight) * 5e-3);
|
|
10704
|
+
if (deltas.some((delta) => delta > tolerance)) {
|
|
10705
|
+
continue;
|
|
10706
|
+
}
|
|
10707
|
+
const score = deltas.reduce((sum, delta) => sum + delta, 0);
|
|
10708
|
+
if (!best || score < best.score) {
|
|
10709
|
+
best = { element, score };
|
|
10710
|
+
}
|
|
10711
|
+
}
|
|
10712
|
+
}
|
|
10713
|
+
return best?.element;
|
|
10714
|
+
}
|
|
10541
10715
|
function hasInlineBackground(element) {
|
|
10542
10716
|
return Boolean(element.style.background || element.style.backgroundColor || element.style.backgroundImage);
|
|
10543
10717
|
}
|