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