@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 CHANGED
@@ -6458,6 +6458,7 @@ function decodeXml(value) {
6458
6458
  // src/plugins/office.ts
6459
6459
  var import_jszip3 = __toESM(require("jszip"), 1);
6460
6460
  var import_dompurify2 = __toESM(require("dompurify"), 1);
6461
+ var docxPreview = __toESM(require("docx-preview"), 1);
6461
6462
 
6462
6463
  // src/plugins/msdoc.ts
6463
6464
  var CFB_SIGNATURE = [208, 207, 17, 224, 161, 177, 26, 225];
@@ -7894,6 +7895,7 @@ var presentationExtensions = /* @__PURE__ */ new Set(["pptx", "pptm", "ppt", "pp
7894
7895
  var packagedOfficeCandidates = /* @__PURE__ */ new Set(["wps", "et", "dps", "numbers", "key"]);
7895
7896
  var SHEET_WINDOW_ROWS = 200;
7896
7897
  var SHEET_WINDOW_COLUMNS = 80;
7898
+ var DEFAULT_DOCX_RENDER_TIMEOUT_MS = 15e3;
7897
7899
  var DEFAULT_PPTX_RENDER_TIMEOUT_MS = 12e3;
7898
7900
  var PPTX_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
7899
7901
  var officeMimeTypes = /* @__PURE__ */ new Set([
@@ -8202,23 +8204,28 @@ async function renderDocx(panel, arrayBuffer, fit) {
8202
8204
  document.head.append(styleContainer);
8203
8205
  let disposeFit;
8204
8206
  try {
8205
- const docxPreview = await import("docx-preview");
8206
- await docxPreview.renderAsync(arrayBuffer, content, styleContainer, {
8207
- className: "ofv-docx",
8208
- inWrapper: true,
8209
- breakPages: true,
8210
- ignoreWidth: false,
8211
- ignoreHeight: false,
8212
- ignoreFonts: false,
8213
- renderHeaders: true,
8214
- renderFooters: true,
8215
- renderFootnotes: true,
8216
- renderEndnotes: true,
8217
- renderComments: true,
8218
- renderAltChunks: true,
8219
- experimental: true,
8220
- useBase64URL: true
8221
- });
8207
+ await withTimeout(
8208
+ (async () => {
8209
+ await docxPreview.renderAsync(arrayBuffer, content, styleContainer, {
8210
+ className: "ofv-docx",
8211
+ inWrapper: true,
8212
+ breakPages: true,
8213
+ ignoreWidth: false,
8214
+ ignoreHeight: false,
8215
+ ignoreFonts: false,
8216
+ renderHeaders: true,
8217
+ renderFooters: true,
8218
+ renderFootnotes: true,
8219
+ renderEndnotes: true,
8220
+ renderComments: true,
8221
+ renderAltChunks: true,
8222
+ experimental: true,
8223
+ useBase64URL: true
8224
+ });
8225
+ })(),
8226
+ docxRenderTimeoutMs(),
8227
+ "DOCX rendering"
8228
+ );
8222
8229
  await normalizeDocxLayout(content, arrayBuffer);
8223
8230
  const shouldUseTextboxFallback = await docxPreviewLooksBlank(content, arrayBuffer) || await docxPreviewMissesRichTextboxContent(content, arrayBuffer) || await docxShouldPreferTextboxLayoutFallback(arrayBuffer);
8224
8231
  if (shouldUseTextboxFallback) {
@@ -8241,13 +8248,18 @@ async function renderDocx(panel, arrayBuffer, fit) {
8241
8248
  } catch (error) {
8242
8249
  disposeFit?.();
8243
8250
  styleContainer.remove();
8244
- content.replaceChildren();
8245
- await renderDocxContentFallback(content, arrayBuffer);
8246
- panel.append(content);
8251
+ const fallbackContent = document.createElement("div");
8252
+ fallbackContent.className = "ofv-docx-document";
8253
+ await renderDocxContentFallback(fallbackContent, arrayBuffer);
8254
+ panel.append(fallbackContent);
8247
8255
  console.warn("DOCX layout preview failed, fell back to Mammoth:", error);
8248
8256
  }
8249
8257
  return () => void 0;
8250
8258
  }
8259
+ function docxRenderTimeoutMs() {
8260
+ const override = globalThis.__OFV_DOCX_RENDER_TIMEOUT_MS__;
8261
+ return typeof override === "number" && override > 0 ? override : DEFAULT_DOCX_RENDER_TIMEOUT_MS;
8262
+ }
8251
8263
  async function docxPreviewLooksBlank(container, arrayBuffer) {
8252
8264
  if (container.querySelector("img, svg, canvas, table")) {
8253
8265
  return false;
@@ -8340,12 +8352,13 @@ async function renderDocxContentFallback(container, arrayBuffer, options = {}) {
8340
8352
  return;
8341
8353
  }
8342
8354
  try {
8343
- await renderDocxWithMammoth(container, arrayBuffer);
8344
- const renderedText = normalizePreviewText(container.querySelector(".ofv-document")?.textContent || "");
8355
+ const mammothContent = document.createElement("div");
8356
+ await withTimeout(renderDocxWithMammoth(mammothContent, arrayBuffer), docxRenderTimeoutMs(), "DOCX fallback rendering");
8357
+ const renderedText = normalizePreviewText(mammothContent.querySelector(".ofv-document")?.textContent || "");
8345
8358
  if (renderedText.length >= 24) {
8359
+ container.append(...Array.from(mammothContent.childNodes));
8346
8360
  return;
8347
8361
  }
8348
- container.querySelector(".ofv-document")?.remove();
8349
8362
  await renderDocxTextFallback(container, arrayBuffer);
8350
8363
  } catch (fallbackError) {
8351
8364
  await renderDocxTextFallback(container, arrayBuffer);
@@ -10578,6 +10591,7 @@ async function renderPptx(panel, arrayBuffer) {
10578
10591
  container.className = "ofv-pptx-viewer";
10579
10592
  let insight;
10580
10593
  let zip;
10594
+ let placeholderFontCorrections = [];
10581
10595
  try {
10582
10596
  zip = await import_jszip3.default.loadAsync(arrayBuffer);
10583
10597
  insight = await inspectPptxPresentation(zip);
@@ -10585,11 +10599,18 @@ async function renderPptx(panel, arrayBuffer) {
10585
10599
  } catch (error) {
10586
10600
  console.warn("PPTX structure insight extraction failed:", error);
10587
10601
  }
10602
+ if (zip) {
10603
+ try {
10604
+ placeholderFontCorrections = await inspectPptxPlaceholderFontCorrections(zip);
10605
+ } catch (error) {
10606
+ console.warn("PPTX placeholder font extraction failed:", error);
10607
+ }
10608
+ }
10588
10609
  panel.append(container);
10589
10610
  try {
10590
10611
  const { PptxViewer } = await import("@aiden0z/pptx-renderer");
10591
10612
  await withTimeout(PptxViewer.open(arrayBuffer, container), pptxRenderTimeoutMs());
10592
- normalizePptxLayout(container);
10613
+ normalizePptxLayout(container, placeholderFontCorrections);
10593
10614
  } catch (error) {
10594
10615
  container.replaceChildren();
10595
10616
  if (insight) {
@@ -10622,13 +10643,13 @@ function renderPptxTextFallback(container, insight) {
10622
10643
  container.append(article);
10623
10644
  }
10624
10645
  }
10625
- async function withTimeout(promise, timeoutMs) {
10646
+ async function withTimeout(promise, timeoutMs, label = "PPTX rendering") {
10626
10647
  let timeoutId;
10627
10648
  try {
10628
10649
  return await Promise.race([
10629
10650
  promise,
10630
10651
  new Promise((_resolve, reject) => {
10631
- timeoutId = window.setTimeout(() => reject(new Error(`PPTX rendering timed out after ${timeoutMs}ms.`)), timeoutMs);
10652
+ timeoutId = window.setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms.`)), timeoutMs);
10632
10653
  })
10633
10654
  ]);
10634
10655
  } finally {
@@ -10641,15 +10662,168 @@ function pptxRenderTimeoutMs() {
10641
10662
  const override = globalThis.__OFV_PPTX_RENDER_TIMEOUT_MS__;
10642
10663
  return typeof override === "number" && override > 0 ? override : DEFAULT_PPTX_RENDER_TIMEOUT_MS;
10643
10664
  }
10644
- function normalizePptxLayout(container) {
10665
+ function normalizePptxLayout(container, placeholderFontCorrections) {
10645
10666
  const slideCanvases = findPptxSlideCanvases(container);
10646
10667
  for (const slide of slideCanvases) {
10647
10668
  if (!hasInlineBackground(slide)) {
10648
10669
  slide.style.backgroundColor = "#FFFFFF";
10649
10670
  }
10650
10671
  }
10672
+ normalizePptxPlaceholderFonts(container, placeholderFontCorrections);
10651
10673
  normalizePptxMirroredText(container);
10652
10674
  }
10675
+ async function inspectPptxPlaceholderFontCorrections(zip) {
10676
+ const presentationXml = await zip.file("ppt/presentation.xml")?.async("text");
10677
+ const presentation = presentationXml ? parseOfficeXml(presentationXml) : void 0;
10678
+ const slideSize = presentation ? Array.from(presentation.getElementsByTagName("*")).find((element) => element.localName === "sldSz") : void 0;
10679
+ const slideWidth = Number(slideSize?.getAttribute("cx"));
10680
+ const slideHeight = Number(slideSize?.getAttribute("cy"));
10681
+ if (!(slideWidth > 0) || !(slideHeight > 0)) {
10682
+ return [];
10683
+ }
10684
+ 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));
10685
+ const corrections = [];
10686
+ for (const [slideIndex, entry] of slideEntries.entries()) {
10687
+ const slideXml = await entry.async("text");
10688
+ const slide = parseOfficeXml(slideXml);
10689
+ if (!slide) {
10690
+ continue;
10691
+ }
10692
+ const relationships = await readPptxRelationships(zip, entry.name);
10693
+ const layoutTarget = resolvePptxRelationshipTarget(
10694
+ entry.name,
10695
+ relationships.find((relationship) => /\/slideLayout$/i.test(relationship.type))?.target
10696
+ );
10697
+ const layoutXml = layoutTarget ? await zip.file(layoutTarget)?.async("text") : void 0;
10698
+ const layout = layoutXml ? parseOfficeXml(layoutXml) : void 0;
10699
+ if (!layout) {
10700
+ continue;
10701
+ }
10702
+ const layoutFontSizes = readPptxLayoutPlaceholderFontSizes(layout);
10703
+ const shapes = Array.from(slide.getElementsByTagName("*")).filter((element) => element.localName === "sp");
10704
+ for (const shape of shapes) {
10705
+ const placeholder = findPptxDescendant(shape, "ph");
10706
+ const placeholderIndex = placeholder?.getAttribute("idx");
10707
+ const fontSizePt = placeholderIndex ? layoutFontSizes.get(placeholderIndex) : void 0;
10708
+ const textBody = findPptxDescendant(shape, "txBody");
10709
+ if (!textBody?.textContent?.trim() || !fontSizePt || hasExplicitPptxTextSize(textBody)) {
10710
+ continue;
10711
+ }
10712
+ const transform = findPptxDescendant(shape, "xfrm");
10713
+ const offset = transform ? findPptxChild(transform, "off") : void 0;
10714
+ const extent = transform ? findPptxChild(transform, "ext") : void 0;
10715
+ const left = Number(offset?.getAttribute("x"));
10716
+ const top = Number(offset?.getAttribute("y"));
10717
+ const width = Number(extent?.getAttribute("cx"));
10718
+ const height = Number(extent?.getAttribute("cy"));
10719
+ if (![left, top, width, height].every((value) => Number.isFinite(value)) || width <= 0 || height <= 0) {
10720
+ continue;
10721
+ }
10722
+ corrections.push({
10723
+ slideIndex,
10724
+ leftRatio: left / slideWidth,
10725
+ topRatio: top / slideHeight,
10726
+ widthRatio: width / slideWidth,
10727
+ heightRatio: height / slideHeight,
10728
+ fontSizePt
10729
+ });
10730
+ }
10731
+ }
10732
+ return corrections;
10733
+ }
10734
+ function readPptxLayoutPlaceholderFontSizes(layout) {
10735
+ const result = /* @__PURE__ */ new Map();
10736
+ const shapes = Array.from(layout.getElementsByTagName("*")).filter((element) => element.localName === "sp");
10737
+ for (const shape of shapes) {
10738
+ const placeholderIndex = findPptxDescendant(shape, "ph")?.getAttribute("idx");
10739
+ if (!placeholderIndex) {
10740
+ continue;
10741
+ }
10742
+ const textBody = findPptxDescendant(shape, "txBody");
10743
+ const defaultRunProperties = textBody ? Array.from(textBody.getElementsByTagName("*")).find(
10744
+ (element) => element.localName === "defRPr" && Number(element.getAttribute("sz")) > 0
10745
+ ) : void 0;
10746
+ const size = Number(defaultRunProperties?.getAttribute("sz"));
10747
+ if (size > 0) {
10748
+ result.set(placeholderIndex, size / 100);
10749
+ }
10750
+ }
10751
+ return result;
10752
+ }
10753
+ function hasExplicitPptxTextSize(textBody) {
10754
+ return Array.from(textBody.getElementsByTagName("*")).some(
10755
+ (element) => (element.localName === "rPr" || element.localName === "defRPr" || element.localName === "endParaRPr") && Number(element.getAttribute("sz")) > 0
10756
+ );
10757
+ }
10758
+ function findPptxDescendant(element, localName) {
10759
+ return Array.from(element.getElementsByTagName("*")).find((child) => child.localName === localName);
10760
+ }
10761
+ function findPptxChild(element, localName) {
10762
+ return Array.from(element.children).find((child) => child.localName === localName);
10763
+ }
10764
+ function normalizePptxPlaceholderFonts(container, corrections) {
10765
+ for (const correction of corrections) {
10766
+ const wrapper = container.querySelector(`div[data-slide-index="${correction.slideIndex}"]`);
10767
+ if (!wrapper) {
10768
+ continue;
10769
+ }
10770
+ const match = findPptxPlaceholderElement(wrapper, correction);
10771
+ if (!match) {
10772
+ continue;
10773
+ }
10774
+ const styledText = Array.from(match.querySelectorAll("[style]")).filter(
10775
+ (element) => parseFloat(element.style.fontSize) > 0
10776
+ );
10777
+ for (const element of styledText) {
10778
+ element.style.fontSize = `${correction.fontSizePt}pt`;
10779
+ }
10780
+ if (styledText.length > 0) {
10781
+ match.dataset.ofvPptxPlaceholderFont = String(correction.fontSizePt);
10782
+ }
10783
+ }
10784
+ }
10785
+ function findPptxPlaceholderElement(wrapper, correction) {
10786
+ let best;
10787
+ for (const canvas of findPptxSlideCanvases(wrapper)) {
10788
+ const canvasWidth = parseCssPixelValue(canvas.style.width);
10789
+ const canvasHeight = parseCssPixelValue(canvas.style.height);
10790
+ if (!(canvasWidth > 0) || !(canvasHeight > 0)) {
10791
+ continue;
10792
+ }
10793
+ const expected = {
10794
+ left: correction.leftRatio * canvasWidth,
10795
+ top: correction.topRatio * canvasHeight,
10796
+ width: correction.widthRatio * canvasWidth,
10797
+ height: correction.heightRatio * canvasHeight
10798
+ };
10799
+ const candidates = Array.from(canvas.querySelectorAll("div")).filter(
10800
+ (element) => element.style.position === "absolute" && Boolean(element.textContent?.trim())
10801
+ );
10802
+ for (const element of candidates) {
10803
+ const actual = {
10804
+ left: parseCssPixelValue(element.style.left),
10805
+ top: parseCssPixelValue(element.style.top),
10806
+ width: parseCssPixelValue(element.style.width),
10807
+ height: parseCssPixelValue(element.style.height)
10808
+ };
10809
+ const deltas = [
10810
+ Math.abs(actual.left - expected.left),
10811
+ Math.abs(actual.top - expected.top),
10812
+ Math.abs(actual.width - expected.width),
10813
+ Math.abs(actual.height - expected.height)
10814
+ ];
10815
+ const tolerance = Math.max(2, Math.min(canvasWidth, canvasHeight) * 5e-3);
10816
+ if (deltas.some((delta) => delta > tolerance)) {
10817
+ continue;
10818
+ }
10819
+ const score = deltas.reduce((sum, delta) => sum + delta, 0);
10820
+ if (!best || score < best.score) {
10821
+ best = { element, score };
10822
+ }
10823
+ }
10824
+ }
10825
+ return best?.element;
10826
+ }
10653
10827
  function hasInlineBackground(element) {
10654
10828
  return Boolean(element.style.background || element.style.backgroundColor || element.style.backgroundImage);
10655
10829
  }