@open-file-viewer/core 0.1.24 → 0.1.25
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 +163 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +163 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10466,6 +10466,7 @@ async function renderPptx(panel, arrayBuffer) {
|
|
|
10466
10466
|
container.className = "ofv-pptx-viewer";
|
|
10467
10467
|
let insight;
|
|
10468
10468
|
let zip;
|
|
10469
|
+
let placeholderFontCorrections = [];
|
|
10469
10470
|
try {
|
|
10470
10471
|
zip = await JSZip3.loadAsync(arrayBuffer);
|
|
10471
10472
|
insight = await inspectPptxPresentation(zip);
|
|
@@ -10473,11 +10474,18 @@ async function renderPptx(panel, arrayBuffer) {
|
|
|
10473
10474
|
} catch (error) {
|
|
10474
10475
|
console.warn("PPTX structure insight extraction failed:", error);
|
|
10475
10476
|
}
|
|
10477
|
+
if (zip) {
|
|
10478
|
+
try {
|
|
10479
|
+
placeholderFontCorrections = await inspectPptxPlaceholderFontCorrections(zip);
|
|
10480
|
+
} catch (error) {
|
|
10481
|
+
console.warn("PPTX placeholder font extraction failed:", error);
|
|
10482
|
+
}
|
|
10483
|
+
}
|
|
10476
10484
|
panel.append(container);
|
|
10477
10485
|
try {
|
|
10478
10486
|
const { PptxViewer } = await import("@aiden0z/pptx-renderer");
|
|
10479
10487
|
await withTimeout(PptxViewer.open(arrayBuffer, container), pptxRenderTimeoutMs());
|
|
10480
|
-
normalizePptxLayout(container);
|
|
10488
|
+
normalizePptxLayout(container, placeholderFontCorrections);
|
|
10481
10489
|
} catch (error) {
|
|
10482
10490
|
container.replaceChildren();
|
|
10483
10491
|
if (insight) {
|
|
@@ -10529,15 +10537,168 @@ function pptxRenderTimeoutMs() {
|
|
|
10529
10537
|
const override = globalThis.__OFV_PPTX_RENDER_TIMEOUT_MS__;
|
|
10530
10538
|
return typeof override === "number" && override > 0 ? override : DEFAULT_PPTX_RENDER_TIMEOUT_MS;
|
|
10531
10539
|
}
|
|
10532
|
-
function normalizePptxLayout(container) {
|
|
10540
|
+
function normalizePptxLayout(container, placeholderFontCorrections) {
|
|
10533
10541
|
const slideCanvases = findPptxSlideCanvases(container);
|
|
10534
10542
|
for (const slide of slideCanvases) {
|
|
10535
10543
|
if (!hasInlineBackground(slide)) {
|
|
10536
10544
|
slide.style.backgroundColor = "#FFFFFF";
|
|
10537
10545
|
}
|
|
10538
10546
|
}
|
|
10547
|
+
normalizePptxPlaceholderFonts(container, placeholderFontCorrections);
|
|
10539
10548
|
normalizePptxMirroredText(container);
|
|
10540
10549
|
}
|
|
10550
|
+
async function inspectPptxPlaceholderFontCorrections(zip) {
|
|
10551
|
+
const presentationXml = await zip.file("ppt/presentation.xml")?.async("text");
|
|
10552
|
+
const presentation = presentationXml ? parseOfficeXml(presentationXml) : void 0;
|
|
10553
|
+
const slideSize = presentation ? Array.from(presentation.getElementsByTagName("*")).find((element) => element.localName === "sldSz") : void 0;
|
|
10554
|
+
const slideWidth = Number(slideSize?.getAttribute("cx"));
|
|
10555
|
+
const slideHeight = Number(slideSize?.getAttribute("cy"));
|
|
10556
|
+
if (!(slideWidth > 0) || !(slideHeight > 0)) {
|
|
10557
|
+
return [];
|
|
10558
|
+
}
|
|
10559
|
+
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));
|
|
10560
|
+
const corrections = [];
|
|
10561
|
+
for (const [slideIndex, entry] of slideEntries.entries()) {
|
|
10562
|
+
const slideXml = await entry.async("text");
|
|
10563
|
+
const slide = parseOfficeXml(slideXml);
|
|
10564
|
+
if (!slide) {
|
|
10565
|
+
continue;
|
|
10566
|
+
}
|
|
10567
|
+
const relationships = await readPptxRelationships(zip, entry.name);
|
|
10568
|
+
const layoutTarget = resolvePptxRelationshipTarget(
|
|
10569
|
+
entry.name,
|
|
10570
|
+
relationships.find((relationship) => /\/slideLayout$/i.test(relationship.type))?.target
|
|
10571
|
+
);
|
|
10572
|
+
const layoutXml = layoutTarget ? await zip.file(layoutTarget)?.async("text") : void 0;
|
|
10573
|
+
const layout = layoutXml ? parseOfficeXml(layoutXml) : void 0;
|
|
10574
|
+
if (!layout) {
|
|
10575
|
+
continue;
|
|
10576
|
+
}
|
|
10577
|
+
const layoutFontSizes = readPptxLayoutPlaceholderFontSizes(layout);
|
|
10578
|
+
const shapes = Array.from(slide.getElementsByTagName("*")).filter((element) => element.localName === "sp");
|
|
10579
|
+
for (const shape of shapes) {
|
|
10580
|
+
const placeholder = findPptxDescendant(shape, "ph");
|
|
10581
|
+
const placeholderIndex = placeholder?.getAttribute("idx");
|
|
10582
|
+
const fontSizePt = placeholderIndex ? layoutFontSizes.get(placeholderIndex) : void 0;
|
|
10583
|
+
const textBody = findPptxDescendant(shape, "txBody");
|
|
10584
|
+
if (!textBody?.textContent?.trim() || !fontSizePt || hasExplicitPptxTextSize(textBody)) {
|
|
10585
|
+
continue;
|
|
10586
|
+
}
|
|
10587
|
+
const transform = findPptxDescendant(shape, "xfrm");
|
|
10588
|
+
const offset = transform ? findPptxChild(transform, "off") : void 0;
|
|
10589
|
+
const extent = transform ? findPptxChild(transform, "ext") : void 0;
|
|
10590
|
+
const left = Number(offset?.getAttribute("x"));
|
|
10591
|
+
const top = Number(offset?.getAttribute("y"));
|
|
10592
|
+
const width = Number(extent?.getAttribute("cx"));
|
|
10593
|
+
const height = Number(extent?.getAttribute("cy"));
|
|
10594
|
+
if (![left, top, width, height].every((value) => Number.isFinite(value)) || width <= 0 || height <= 0) {
|
|
10595
|
+
continue;
|
|
10596
|
+
}
|
|
10597
|
+
corrections.push({
|
|
10598
|
+
slideIndex,
|
|
10599
|
+
leftRatio: left / slideWidth,
|
|
10600
|
+
topRatio: top / slideHeight,
|
|
10601
|
+
widthRatio: width / slideWidth,
|
|
10602
|
+
heightRatio: height / slideHeight,
|
|
10603
|
+
fontSizePt
|
|
10604
|
+
});
|
|
10605
|
+
}
|
|
10606
|
+
}
|
|
10607
|
+
return corrections;
|
|
10608
|
+
}
|
|
10609
|
+
function readPptxLayoutPlaceholderFontSizes(layout) {
|
|
10610
|
+
const result = /* @__PURE__ */ new Map();
|
|
10611
|
+
const shapes = Array.from(layout.getElementsByTagName("*")).filter((element) => element.localName === "sp");
|
|
10612
|
+
for (const shape of shapes) {
|
|
10613
|
+
const placeholderIndex = findPptxDescendant(shape, "ph")?.getAttribute("idx");
|
|
10614
|
+
if (!placeholderIndex) {
|
|
10615
|
+
continue;
|
|
10616
|
+
}
|
|
10617
|
+
const textBody = findPptxDescendant(shape, "txBody");
|
|
10618
|
+
const defaultRunProperties = textBody ? Array.from(textBody.getElementsByTagName("*")).find(
|
|
10619
|
+
(element) => element.localName === "defRPr" && Number(element.getAttribute("sz")) > 0
|
|
10620
|
+
) : void 0;
|
|
10621
|
+
const size = Number(defaultRunProperties?.getAttribute("sz"));
|
|
10622
|
+
if (size > 0) {
|
|
10623
|
+
result.set(placeholderIndex, size / 100);
|
|
10624
|
+
}
|
|
10625
|
+
}
|
|
10626
|
+
return result;
|
|
10627
|
+
}
|
|
10628
|
+
function hasExplicitPptxTextSize(textBody) {
|
|
10629
|
+
return Array.from(textBody.getElementsByTagName("*")).some(
|
|
10630
|
+
(element) => (element.localName === "rPr" || element.localName === "defRPr" || element.localName === "endParaRPr") && Number(element.getAttribute("sz")) > 0
|
|
10631
|
+
);
|
|
10632
|
+
}
|
|
10633
|
+
function findPptxDescendant(element, localName) {
|
|
10634
|
+
return Array.from(element.getElementsByTagName("*")).find((child) => child.localName === localName);
|
|
10635
|
+
}
|
|
10636
|
+
function findPptxChild(element, localName) {
|
|
10637
|
+
return Array.from(element.children).find((child) => child.localName === localName);
|
|
10638
|
+
}
|
|
10639
|
+
function normalizePptxPlaceholderFonts(container, corrections) {
|
|
10640
|
+
for (const correction of corrections) {
|
|
10641
|
+
const wrapper = container.querySelector(`div[data-slide-index="${correction.slideIndex}"]`);
|
|
10642
|
+
if (!wrapper) {
|
|
10643
|
+
continue;
|
|
10644
|
+
}
|
|
10645
|
+
const match = findPptxPlaceholderElement(wrapper, correction);
|
|
10646
|
+
if (!match) {
|
|
10647
|
+
continue;
|
|
10648
|
+
}
|
|
10649
|
+
const styledText = Array.from(match.querySelectorAll("[style]")).filter(
|
|
10650
|
+
(element) => parseFloat(element.style.fontSize) > 0
|
|
10651
|
+
);
|
|
10652
|
+
for (const element of styledText) {
|
|
10653
|
+
element.style.fontSize = `${correction.fontSizePt}pt`;
|
|
10654
|
+
}
|
|
10655
|
+
if (styledText.length > 0) {
|
|
10656
|
+
match.dataset.ofvPptxPlaceholderFont = String(correction.fontSizePt);
|
|
10657
|
+
}
|
|
10658
|
+
}
|
|
10659
|
+
}
|
|
10660
|
+
function findPptxPlaceholderElement(wrapper, correction) {
|
|
10661
|
+
let best;
|
|
10662
|
+
for (const canvas of findPptxSlideCanvases(wrapper)) {
|
|
10663
|
+
const canvasWidth = parseCssPixelValue(canvas.style.width);
|
|
10664
|
+
const canvasHeight = parseCssPixelValue(canvas.style.height);
|
|
10665
|
+
if (!(canvasWidth > 0) || !(canvasHeight > 0)) {
|
|
10666
|
+
continue;
|
|
10667
|
+
}
|
|
10668
|
+
const expected = {
|
|
10669
|
+
left: correction.leftRatio * canvasWidth,
|
|
10670
|
+
top: correction.topRatio * canvasHeight,
|
|
10671
|
+
width: correction.widthRatio * canvasWidth,
|
|
10672
|
+
height: correction.heightRatio * canvasHeight
|
|
10673
|
+
};
|
|
10674
|
+
const candidates = Array.from(canvas.querySelectorAll("div")).filter(
|
|
10675
|
+
(element) => element.style.position === "absolute" && Boolean(element.textContent?.trim())
|
|
10676
|
+
);
|
|
10677
|
+
for (const element of candidates) {
|
|
10678
|
+
const actual = {
|
|
10679
|
+
left: parseCssPixelValue(element.style.left),
|
|
10680
|
+
top: parseCssPixelValue(element.style.top),
|
|
10681
|
+
width: parseCssPixelValue(element.style.width),
|
|
10682
|
+
height: parseCssPixelValue(element.style.height)
|
|
10683
|
+
};
|
|
10684
|
+
const deltas = [
|
|
10685
|
+
Math.abs(actual.left - expected.left),
|
|
10686
|
+
Math.abs(actual.top - expected.top),
|
|
10687
|
+
Math.abs(actual.width - expected.width),
|
|
10688
|
+
Math.abs(actual.height - expected.height)
|
|
10689
|
+
];
|
|
10690
|
+
const tolerance = Math.max(2, Math.min(canvasWidth, canvasHeight) * 5e-3);
|
|
10691
|
+
if (deltas.some((delta) => delta > tolerance)) {
|
|
10692
|
+
continue;
|
|
10693
|
+
}
|
|
10694
|
+
const score = deltas.reduce((sum, delta) => sum + delta, 0);
|
|
10695
|
+
if (!best || score < best.score) {
|
|
10696
|
+
best = { element, score };
|
|
10697
|
+
}
|
|
10698
|
+
}
|
|
10699
|
+
}
|
|
10700
|
+
return best?.element;
|
|
10701
|
+
}
|
|
10541
10702
|
function hasInlineBackground(element) {
|
|
10542
10703
|
return Boolean(element.style.background || element.style.backgroundColor || element.style.backgroundImage);
|
|
10543
10704
|
}
|