@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 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
  }