@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.cjs
CHANGED
|
@@ -10578,6 +10578,7 @@ async function renderPptx(panel, arrayBuffer) {
|
|
|
10578
10578
|
container.className = "ofv-pptx-viewer";
|
|
10579
10579
|
let insight;
|
|
10580
10580
|
let zip;
|
|
10581
|
+
let placeholderFontCorrections = [];
|
|
10581
10582
|
try {
|
|
10582
10583
|
zip = await import_jszip3.default.loadAsync(arrayBuffer);
|
|
10583
10584
|
insight = await inspectPptxPresentation(zip);
|
|
@@ -10585,11 +10586,18 @@ async function renderPptx(panel, arrayBuffer) {
|
|
|
10585
10586
|
} catch (error) {
|
|
10586
10587
|
console.warn("PPTX structure insight extraction failed:", error);
|
|
10587
10588
|
}
|
|
10589
|
+
if (zip) {
|
|
10590
|
+
try {
|
|
10591
|
+
placeholderFontCorrections = await inspectPptxPlaceholderFontCorrections(zip);
|
|
10592
|
+
} catch (error) {
|
|
10593
|
+
console.warn("PPTX placeholder font extraction failed:", error);
|
|
10594
|
+
}
|
|
10595
|
+
}
|
|
10588
10596
|
panel.append(container);
|
|
10589
10597
|
try {
|
|
10590
10598
|
const { PptxViewer } = await import("@aiden0z/pptx-renderer");
|
|
10591
10599
|
await withTimeout(PptxViewer.open(arrayBuffer, container), pptxRenderTimeoutMs());
|
|
10592
|
-
normalizePptxLayout(container);
|
|
10600
|
+
normalizePptxLayout(container, placeholderFontCorrections);
|
|
10593
10601
|
} catch (error) {
|
|
10594
10602
|
container.replaceChildren();
|
|
10595
10603
|
if (insight) {
|
|
@@ -10641,15 +10649,168 @@ function pptxRenderTimeoutMs() {
|
|
|
10641
10649
|
const override = globalThis.__OFV_PPTX_RENDER_TIMEOUT_MS__;
|
|
10642
10650
|
return typeof override === "number" && override > 0 ? override : DEFAULT_PPTX_RENDER_TIMEOUT_MS;
|
|
10643
10651
|
}
|
|
10644
|
-
function normalizePptxLayout(container) {
|
|
10652
|
+
function normalizePptxLayout(container, placeholderFontCorrections) {
|
|
10645
10653
|
const slideCanvases = findPptxSlideCanvases(container);
|
|
10646
10654
|
for (const slide of slideCanvases) {
|
|
10647
10655
|
if (!hasInlineBackground(slide)) {
|
|
10648
10656
|
slide.style.backgroundColor = "#FFFFFF";
|
|
10649
10657
|
}
|
|
10650
10658
|
}
|
|
10659
|
+
normalizePptxPlaceholderFonts(container, placeholderFontCorrections);
|
|
10651
10660
|
normalizePptxMirroredText(container);
|
|
10652
10661
|
}
|
|
10662
|
+
async function inspectPptxPlaceholderFontCorrections(zip) {
|
|
10663
|
+
const presentationXml = await zip.file("ppt/presentation.xml")?.async("text");
|
|
10664
|
+
const presentation = presentationXml ? parseOfficeXml(presentationXml) : void 0;
|
|
10665
|
+
const slideSize = presentation ? Array.from(presentation.getElementsByTagName("*")).find((element) => element.localName === "sldSz") : void 0;
|
|
10666
|
+
const slideWidth = Number(slideSize?.getAttribute("cx"));
|
|
10667
|
+
const slideHeight = Number(slideSize?.getAttribute("cy"));
|
|
10668
|
+
if (!(slideWidth > 0) || !(slideHeight > 0)) {
|
|
10669
|
+
return [];
|
|
10670
|
+
}
|
|
10671
|
+
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));
|
|
10672
|
+
const corrections = [];
|
|
10673
|
+
for (const [slideIndex, entry] of slideEntries.entries()) {
|
|
10674
|
+
const slideXml = await entry.async("text");
|
|
10675
|
+
const slide = parseOfficeXml(slideXml);
|
|
10676
|
+
if (!slide) {
|
|
10677
|
+
continue;
|
|
10678
|
+
}
|
|
10679
|
+
const relationships = await readPptxRelationships(zip, entry.name);
|
|
10680
|
+
const layoutTarget = resolvePptxRelationshipTarget(
|
|
10681
|
+
entry.name,
|
|
10682
|
+
relationships.find((relationship) => /\/slideLayout$/i.test(relationship.type))?.target
|
|
10683
|
+
);
|
|
10684
|
+
const layoutXml = layoutTarget ? await zip.file(layoutTarget)?.async("text") : void 0;
|
|
10685
|
+
const layout = layoutXml ? parseOfficeXml(layoutXml) : void 0;
|
|
10686
|
+
if (!layout) {
|
|
10687
|
+
continue;
|
|
10688
|
+
}
|
|
10689
|
+
const layoutFontSizes = readPptxLayoutPlaceholderFontSizes(layout);
|
|
10690
|
+
const shapes = Array.from(slide.getElementsByTagName("*")).filter((element) => element.localName === "sp");
|
|
10691
|
+
for (const shape of shapes) {
|
|
10692
|
+
const placeholder = findPptxDescendant(shape, "ph");
|
|
10693
|
+
const placeholderIndex = placeholder?.getAttribute("idx");
|
|
10694
|
+
const fontSizePt = placeholderIndex ? layoutFontSizes.get(placeholderIndex) : void 0;
|
|
10695
|
+
const textBody = findPptxDescendant(shape, "txBody");
|
|
10696
|
+
if (!textBody?.textContent?.trim() || !fontSizePt || hasExplicitPptxTextSize(textBody)) {
|
|
10697
|
+
continue;
|
|
10698
|
+
}
|
|
10699
|
+
const transform = findPptxDescendant(shape, "xfrm");
|
|
10700
|
+
const offset = transform ? findPptxChild(transform, "off") : void 0;
|
|
10701
|
+
const extent = transform ? findPptxChild(transform, "ext") : void 0;
|
|
10702
|
+
const left = Number(offset?.getAttribute("x"));
|
|
10703
|
+
const top = Number(offset?.getAttribute("y"));
|
|
10704
|
+
const width = Number(extent?.getAttribute("cx"));
|
|
10705
|
+
const height = Number(extent?.getAttribute("cy"));
|
|
10706
|
+
if (![left, top, width, height].every((value) => Number.isFinite(value)) || width <= 0 || height <= 0) {
|
|
10707
|
+
continue;
|
|
10708
|
+
}
|
|
10709
|
+
corrections.push({
|
|
10710
|
+
slideIndex,
|
|
10711
|
+
leftRatio: left / slideWidth,
|
|
10712
|
+
topRatio: top / slideHeight,
|
|
10713
|
+
widthRatio: width / slideWidth,
|
|
10714
|
+
heightRatio: height / slideHeight,
|
|
10715
|
+
fontSizePt
|
|
10716
|
+
});
|
|
10717
|
+
}
|
|
10718
|
+
}
|
|
10719
|
+
return corrections;
|
|
10720
|
+
}
|
|
10721
|
+
function readPptxLayoutPlaceholderFontSizes(layout) {
|
|
10722
|
+
const result = /* @__PURE__ */ new Map();
|
|
10723
|
+
const shapes = Array.from(layout.getElementsByTagName("*")).filter((element) => element.localName === "sp");
|
|
10724
|
+
for (const shape of shapes) {
|
|
10725
|
+
const placeholderIndex = findPptxDescendant(shape, "ph")?.getAttribute("idx");
|
|
10726
|
+
if (!placeholderIndex) {
|
|
10727
|
+
continue;
|
|
10728
|
+
}
|
|
10729
|
+
const textBody = findPptxDescendant(shape, "txBody");
|
|
10730
|
+
const defaultRunProperties = textBody ? Array.from(textBody.getElementsByTagName("*")).find(
|
|
10731
|
+
(element) => element.localName === "defRPr" && Number(element.getAttribute("sz")) > 0
|
|
10732
|
+
) : void 0;
|
|
10733
|
+
const size = Number(defaultRunProperties?.getAttribute("sz"));
|
|
10734
|
+
if (size > 0) {
|
|
10735
|
+
result.set(placeholderIndex, size / 100);
|
|
10736
|
+
}
|
|
10737
|
+
}
|
|
10738
|
+
return result;
|
|
10739
|
+
}
|
|
10740
|
+
function hasExplicitPptxTextSize(textBody) {
|
|
10741
|
+
return Array.from(textBody.getElementsByTagName("*")).some(
|
|
10742
|
+
(element) => (element.localName === "rPr" || element.localName === "defRPr" || element.localName === "endParaRPr") && Number(element.getAttribute("sz")) > 0
|
|
10743
|
+
);
|
|
10744
|
+
}
|
|
10745
|
+
function findPptxDescendant(element, localName) {
|
|
10746
|
+
return Array.from(element.getElementsByTagName("*")).find((child) => child.localName === localName);
|
|
10747
|
+
}
|
|
10748
|
+
function findPptxChild(element, localName) {
|
|
10749
|
+
return Array.from(element.children).find((child) => child.localName === localName);
|
|
10750
|
+
}
|
|
10751
|
+
function normalizePptxPlaceholderFonts(container, corrections) {
|
|
10752
|
+
for (const correction of corrections) {
|
|
10753
|
+
const wrapper = container.querySelector(`div[data-slide-index="${correction.slideIndex}"]`);
|
|
10754
|
+
if (!wrapper) {
|
|
10755
|
+
continue;
|
|
10756
|
+
}
|
|
10757
|
+
const match = findPptxPlaceholderElement(wrapper, correction);
|
|
10758
|
+
if (!match) {
|
|
10759
|
+
continue;
|
|
10760
|
+
}
|
|
10761
|
+
const styledText = Array.from(match.querySelectorAll("[style]")).filter(
|
|
10762
|
+
(element) => parseFloat(element.style.fontSize) > 0
|
|
10763
|
+
);
|
|
10764
|
+
for (const element of styledText) {
|
|
10765
|
+
element.style.fontSize = `${correction.fontSizePt}pt`;
|
|
10766
|
+
}
|
|
10767
|
+
if (styledText.length > 0) {
|
|
10768
|
+
match.dataset.ofvPptxPlaceholderFont = String(correction.fontSizePt);
|
|
10769
|
+
}
|
|
10770
|
+
}
|
|
10771
|
+
}
|
|
10772
|
+
function findPptxPlaceholderElement(wrapper, correction) {
|
|
10773
|
+
let best;
|
|
10774
|
+
for (const canvas of findPptxSlideCanvases(wrapper)) {
|
|
10775
|
+
const canvasWidth = parseCssPixelValue(canvas.style.width);
|
|
10776
|
+
const canvasHeight = parseCssPixelValue(canvas.style.height);
|
|
10777
|
+
if (!(canvasWidth > 0) || !(canvasHeight > 0)) {
|
|
10778
|
+
continue;
|
|
10779
|
+
}
|
|
10780
|
+
const expected = {
|
|
10781
|
+
left: correction.leftRatio * canvasWidth,
|
|
10782
|
+
top: correction.topRatio * canvasHeight,
|
|
10783
|
+
width: correction.widthRatio * canvasWidth,
|
|
10784
|
+
height: correction.heightRatio * canvasHeight
|
|
10785
|
+
};
|
|
10786
|
+
const candidates = Array.from(canvas.querySelectorAll("div")).filter(
|
|
10787
|
+
(element) => element.style.position === "absolute" && Boolean(element.textContent?.trim())
|
|
10788
|
+
);
|
|
10789
|
+
for (const element of candidates) {
|
|
10790
|
+
const actual = {
|
|
10791
|
+
left: parseCssPixelValue(element.style.left),
|
|
10792
|
+
top: parseCssPixelValue(element.style.top),
|
|
10793
|
+
width: parseCssPixelValue(element.style.width),
|
|
10794
|
+
height: parseCssPixelValue(element.style.height)
|
|
10795
|
+
};
|
|
10796
|
+
const deltas = [
|
|
10797
|
+
Math.abs(actual.left - expected.left),
|
|
10798
|
+
Math.abs(actual.top - expected.top),
|
|
10799
|
+
Math.abs(actual.width - expected.width),
|
|
10800
|
+
Math.abs(actual.height - expected.height)
|
|
10801
|
+
];
|
|
10802
|
+
const tolerance = Math.max(2, Math.min(canvasWidth, canvasHeight) * 5e-3);
|
|
10803
|
+
if (deltas.some((delta) => delta > tolerance)) {
|
|
10804
|
+
continue;
|
|
10805
|
+
}
|
|
10806
|
+
const score = deltas.reduce((sum, delta) => sum + delta, 0);
|
|
10807
|
+
if (!best || score < best.score) {
|
|
10808
|
+
best = { element, score };
|
|
10809
|
+
}
|
|
10810
|
+
}
|
|
10811
|
+
}
|
|
10812
|
+
return best?.element;
|
|
10813
|
+
}
|
|
10653
10814
|
function hasInlineBackground(element) {
|
|
10654
10815
|
return Boolean(element.style.background || element.style.backgroundColor || element.style.backgroundImage);
|
|
10655
10816
|
}
|