@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.js CHANGED
@@ -6346,6 +6346,7 @@ function decodeXml(value) {
6346
6346
  // src/plugins/office.ts
6347
6347
  import JSZip3 from "jszip";
6348
6348
  import DOMPurify2 from "dompurify";
6349
+ import * as docxPreview from "docx-preview";
6349
6350
 
6350
6351
  // src/plugins/msdoc.ts
6351
6352
  var CFB_SIGNATURE = [208, 207, 17, 224, 161, 177, 26, 225];
@@ -7782,6 +7783,7 @@ var presentationExtensions = /* @__PURE__ */ new Set(["pptx", "pptm", "ppt", "pp
7782
7783
  var packagedOfficeCandidates = /* @__PURE__ */ new Set(["wps", "et", "dps", "numbers", "key"]);
7783
7784
  var SHEET_WINDOW_ROWS = 200;
7784
7785
  var SHEET_WINDOW_COLUMNS = 80;
7786
+ var DEFAULT_DOCX_RENDER_TIMEOUT_MS = 15e3;
7785
7787
  var DEFAULT_PPTX_RENDER_TIMEOUT_MS = 12e3;
7786
7788
  var PPTX_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
7787
7789
  var officeMimeTypes = /* @__PURE__ */ new Set([
@@ -8090,23 +8092,28 @@ async function renderDocx(panel, arrayBuffer, fit) {
8090
8092
  document.head.append(styleContainer);
8091
8093
  let disposeFit;
8092
8094
  try {
8093
- const docxPreview = await import("docx-preview");
8094
- await docxPreview.renderAsync(arrayBuffer, content, styleContainer, {
8095
- className: "ofv-docx",
8096
- inWrapper: true,
8097
- breakPages: true,
8098
- ignoreWidth: false,
8099
- ignoreHeight: false,
8100
- ignoreFonts: false,
8101
- renderHeaders: true,
8102
- renderFooters: true,
8103
- renderFootnotes: true,
8104
- renderEndnotes: true,
8105
- renderComments: true,
8106
- renderAltChunks: true,
8107
- experimental: true,
8108
- useBase64URL: true
8109
- });
8095
+ await withTimeout(
8096
+ (async () => {
8097
+ await docxPreview.renderAsync(arrayBuffer, content, styleContainer, {
8098
+ className: "ofv-docx",
8099
+ inWrapper: true,
8100
+ breakPages: true,
8101
+ ignoreWidth: false,
8102
+ ignoreHeight: false,
8103
+ ignoreFonts: false,
8104
+ renderHeaders: true,
8105
+ renderFooters: true,
8106
+ renderFootnotes: true,
8107
+ renderEndnotes: true,
8108
+ renderComments: true,
8109
+ renderAltChunks: true,
8110
+ experimental: true,
8111
+ useBase64URL: true
8112
+ });
8113
+ })(),
8114
+ docxRenderTimeoutMs(),
8115
+ "DOCX rendering"
8116
+ );
8110
8117
  await normalizeDocxLayout(content, arrayBuffer);
8111
8118
  const shouldUseTextboxFallback = await docxPreviewLooksBlank(content, arrayBuffer) || await docxPreviewMissesRichTextboxContent(content, arrayBuffer) || await docxShouldPreferTextboxLayoutFallback(arrayBuffer);
8112
8119
  if (shouldUseTextboxFallback) {
@@ -8129,13 +8136,18 @@ async function renderDocx(panel, arrayBuffer, fit) {
8129
8136
  } catch (error) {
8130
8137
  disposeFit?.();
8131
8138
  styleContainer.remove();
8132
- content.replaceChildren();
8133
- await renderDocxContentFallback(content, arrayBuffer);
8134
- panel.append(content);
8139
+ const fallbackContent = document.createElement("div");
8140
+ fallbackContent.className = "ofv-docx-document";
8141
+ await renderDocxContentFallback(fallbackContent, arrayBuffer);
8142
+ panel.append(fallbackContent);
8135
8143
  console.warn("DOCX layout preview failed, fell back to Mammoth:", error);
8136
8144
  }
8137
8145
  return () => void 0;
8138
8146
  }
8147
+ function docxRenderTimeoutMs() {
8148
+ const override = globalThis.__OFV_DOCX_RENDER_TIMEOUT_MS__;
8149
+ return typeof override === "number" && override > 0 ? override : DEFAULT_DOCX_RENDER_TIMEOUT_MS;
8150
+ }
8139
8151
  async function docxPreviewLooksBlank(container, arrayBuffer) {
8140
8152
  if (container.querySelector("img, svg, canvas, table")) {
8141
8153
  return false;
@@ -8228,12 +8240,13 @@ async function renderDocxContentFallback(container, arrayBuffer, options = {}) {
8228
8240
  return;
8229
8241
  }
8230
8242
  try {
8231
- await renderDocxWithMammoth(container, arrayBuffer);
8232
- const renderedText = normalizePreviewText(container.querySelector(".ofv-document")?.textContent || "");
8243
+ const mammothContent = document.createElement("div");
8244
+ await withTimeout(renderDocxWithMammoth(mammothContent, arrayBuffer), docxRenderTimeoutMs(), "DOCX fallback rendering");
8245
+ const renderedText = normalizePreviewText(mammothContent.querySelector(".ofv-document")?.textContent || "");
8233
8246
  if (renderedText.length >= 24) {
8247
+ container.append(...Array.from(mammothContent.childNodes));
8234
8248
  return;
8235
8249
  }
8236
- container.querySelector(".ofv-document")?.remove();
8237
8250
  await renderDocxTextFallback(container, arrayBuffer);
8238
8251
  } catch (fallbackError) {
8239
8252
  await renderDocxTextFallback(container, arrayBuffer);
@@ -10466,6 +10479,7 @@ async function renderPptx(panel, arrayBuffer) {
10466
10479
  container.className = "ofv-pptx-viewer";
10467
10480
  let insight;
10468
10481
  let zip;
10482
+ let placeholderFontCorrections = [];
10469
10483
  try {
10470
10484
  zip = await JSZip3.loadAsync(arrayBuffer);
10471
10485
  insight = await inspectPptxPresentation(zip);
@@ -10473,11 +10487,18 @@ async function renderPptx(panel, arrayBuffer) {
10473
10487
  } catch (error) {
10474
10488
  console.warn("PPTX structure insight extraction failed:", error);
10475
10489
  }
10490
+ if (zip) {
10491
+ try {
10492
+ placeholderFontCorrections = await inspectPptxPlaceholderFontCorrections(zip);
10493
+ } catch (error) {
10494
+ console.warn("PPTX placeholder font extraction failed:", error);
10495
+ }
10496
+ }
10476
10497
  panel.append(container);
10477
10498
  try {
10478
10499
  const { PptxViewer } = await import("@aiden0z/pptx-renderer");
10479
10500
  await withTimeout(PptxViewer.open(arrayBuffer, container), pptxRenderTimeoutMs());
10480
- normalizePptxLayout(container);
10501
+ normalizePptxLayout(container, placeholderFontCorrections);
10481
10502
  } catch (error) {
10482
10503
  container.replaceChildren();
10483
10504
  if (insight) {
@@ -10510,13 +10531,13 @@ function renderPptxTextFallback(container, insight) {
10510
10531
  container.append(article);
10511
10532
  }
10512
10533
  }
10513
- async function withTimeout(promise, timeoutMs) {
10534
+ async function withTimeout(promise, timeoutMs, label = "PPTX rendering") {
10514
10535
  let timeoutId;
10515
10536
  try {
10516
10537
  return await Promise.race([
10517
10538
  promise,
10518
10539
  new Promise((_resolve, reject) => {
10519
- timeoutId = window.setTimeout(() => reject(new Error(`PPTX rendering timed out after ${timeoutMs}ms.`)), timeoutMs);
10540
+ timeoutId = window.setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms.`)), timeoutMs);
10520
10541
  })
10521
10542
  ]);
10522
10543
  } finally {
@@ -10529,15 +10550,168 @@ function pptxRenderTimeoutMs() {
10529
10550
  const override = globalThis.__OFV_PPTX_RENDER_TIMEOUT_MS__;
10530
10551
  return typeof override === "number" && override > 0 ? override : DEFAULT_PPTX_RENDER_TIMEOUT_MS;
10531
10552
  }
10532
- function normalizePptxLayout(container) {
10553
+ function normalizePptxLayout(container, placeholderFontCorrections) {
10533
10554
  const slideCanvases = findPptxSlideCanvases(container);
10534
10555
  for (const slide of slideCanvases) {
10535
10556
  if (!hasInlineBackground(slide)) {
10536
10557
  slide.style.backgroundColor = "#FFFFFF";
10537
10558
  }
10538
10559
  }
10560
+ normalizePptxPlaceholderFonts(container, placeholderFontCorrections);
10539
10561
  normalizePptxMirroredText(container);
10540
10562
  }
10563
+ async function inspectPptxPlaceholderFontCorrections(zip) {
10564
+ const presentationXml = await zip.file("ppt/presentation.xml")?.async("text");
10565
+ const presentation = presentationXml ? parseOfficeXml(presentationXml) : void 0;
10566
+ const slideSize = presentation ? Array.from(presentation.getElementsByTagName("*")).find((element) => element.localName === "sldSz") : void 0;
10567
+ const slideWidth = Number(slideSize?.getAttribute("cx"));
10568
+ const slideHeight = Number(slideSize?.getAttribute("cy"));
10569
+ if (!(slideWidth > 0) || !(slideHeight > 0)) {
10570
+ return [];
10571
+ }
10572
+ 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));
10573
+ const corrections = [];
10574
+ for (const [slideIndex, entry] of slideEntries.entries()) {
10575
+ const slideXml = await entry.async("text");
10576
+ const slide = parseOfficeXml(slideXml);
10577
+ if (!slide) {
10578
+ continue;
10579
+ }
10580
+ const relationships = await readPptxRelationships(zip, entry.name);
10581
+ const layoutTarget = resolvePptxRelationshipTarget(
10582
+ entry.name,
10583
+ relationships.find((relationship) => /\/slideLayout$/i.test(relationship.type))?.target
10584
+ );
10585
+ const layoutXml = layoutTarget ? await zip.file(layoutTarget)?.async("text") : void 0;
10586
+ const layout = layoutXml ? parseOfficeXml(layoutXml) : void 0;
10587
+ if (!layout) {
10588
+ continue;
10589
+ }
10590
+ const layoutFontSizes = readPptxLayoutPlaceholderFontSizes(layout);
10591
+ const shapes = Array.from(slide.getElementsByTagName("*")).filter((element) => element.localName === "sp");
10592
+ for (const shape of shapes) {
10593
+ const placeholder = findPptxDescendant(shape, "ph");
10594
+ const placeholderIndex = placeholder?.getAttribute("idx");
10595
+ const fontSizePt = placeholderIndex ? layoutFontSizes.get(placeholderIndex) : void 0;
10596
+ const textBody = findPptxDescendant(shape, "txBody");
10597
+ if (!textBody?.textContent?.trim() || !fontSizePt || hasExplicitPptxTextSize(textBody)) {
10598
+ continue;
10599
+ }
10600
+ const transform = findPptxDescendant(shape, "xfrm");
10601
+ const offset = transform ? findPptxChild(transform, "off") : void 0;
10602
+ const extent = transform ? findPptxChild(transform, "ext") : void 0;
10603
+ const left = Number(offset?.getAttribute("x"));
10604
+ const top = Number(offset?.getAttribute("y"));
10605
+ const width = Number(extent?.getAttribute("cx"));
10606
+ const height = Number(extent?.getAttribute("cy"));
10607
+ if (![left, top, width, height].every((value) => Number.isFinite(value)) || width <= 0 || height <= 0) {
10608
+ continue;
10609
+ }
10610
+ corrections.push({
10611
+ slideIndex,
10612
+ leftRatio: left / slideWidth,
10613
+ topRatio: top / slideHeight,
10614
+ widthRatio: width / slideWidth,
10615
+ heightRatio: height / slideHeight,
10616
+ fontSizePt
10617
+ });
10618
+ }
10619
+ }
10620
+ return corrections;
10621
+ }
10622
+ function readPptxLayoutPlaceholderFontSizes(layout) {
10623
+ const result = /* @__PURE__ */ new Map();
10624
+ const shapes = Array.from(layout.getElementsByTagName("*")).filter((element) => element.localName === "sp");
10625
+ for (const shape of shapes) {
10626
+ const placeholderIndex = findPptxDescendant(shape, "ph")?.getAttribute("idx");
10627
+ if (!placeholderIndex) {
10628
+ continue;
10629
+ }
10630
+ const textBody = findPptxDescendant(shape, "txBody");
10631
+ const defaultRunProperties = textBody ? Array.from(textBody.getElementsByTagName("*")).find(
10632
+ (element) => element.localName === "defRPr" && Number(element.getAttribute("sz")) > 0
10633
+ ) : void 0;
10634
+ const size = Number(defaultRunProperties?.getAttribute("sz"));
10635
+ if (size > 0) {
10636
+ result.set(placeholderIndex, size / 100);
10637
+ }
10638
+ }
10639
+ return result;
10640
+ }
10641
+ function hasExplicitPptxTextSize(textBody) {
10642
+ return Array.from(textBody.getElementsByTagName("*")).some(
10643
+ (element) => (element.localName === "rPr" || element.localName === "defRPr" || element.localName === "endParaRPr") && Number(element.getAttribute("sz")) > 0
10644
+ );
10645
+ }
10646
+ function findPptxDescendant(element, localName) {
10647
+ return Array.from(element.getElementsByTagName("*")).find((child) => child.localName === localName);
10648
+ }
10649
+ function findPptxChild(element, localName) {
10650
+ return Array.from(element.children).find((child) => child.localName === localName);
10651
+ }
10652
+ function normalizePptxPlaceholderFonts(container, corrections) {
10653
+ for (const correction of corrections) {
10654
+ const wrapper = container.querySelector(`div[data-slide-index="${correction.slideIndex}"]`);
10655
+ if (!wrapper) {
10656
+ continue;
10657
+ }
10658
+ const match = findPptxPlaceholderElement(wrapper, correction);
10659
+ if (!match) {
10660
+ continue;
10661
+ }
10662
+ const styledText = Array.from(match.querySelectorAll("[style]")).filter(
10663
+ (element) => parseFloat(element.style.fontSize) > 0
10664
+ );
10665
+ for (const element of styledText) {
10666
+ element.style.fontSize = `${correction.fontSizePt}pt`;
10667
+ }
10668
+ if (styledText.length > 0) {
10669
+ match.dataset.ofvPptxPlaceholderFont = String(correction.fontSizePt);
10670
+ }
10671
+ }
10672
+ }
10673
+ function findPptxPlaceholderElement(wrapper, correction) {
10674
+ let best;
10675
+ for (const canvas of findPptxSlideCanvases(wrapper)) {
10676
+ const canvasWidth = parseCssPixelValue(canvas.style.width);
10677
+ const canvasHeight = parseCssPixelValue(canvas.style.height);
10678
+ if (!(canvasWidth > 0) || !(canvasHeight > 0)) {
10679
+ continue;
10680
+ }
10681
+ const expected = {
10682
+ left: correction.leftRatio * canvasWidth,
10683
+ top: correction.topRatio * canvasHeight,
10684
+ width: correction.widthRatio * canvasWidth,
10685
+ height: correction.heightRatio * canvasHeight
10686
+ };
10687
+ const candidates = Array.from(canvas.querySelectorAll("div")).filter(
10688
+ (element) => element.style.position === "absolute" && Boolean(element.textContent?.trim())
10689
+ );
10690
+ for (const element of candidates) {
10691
+ const actual = {
10692
+ left: parseCssPixelValue(element.style.left),
10693
+ top: parseCssPixelValue(element.style.top),
10694
+ width: parseCssPixelValue(element.style.width),
10695
+ height: parseCssPixelValue(element.style.height)
10696
+ };
10697
+ const deltas = [
10698
+ Math.abs(actual.left - expected.left),
10699
+ Math.abs(actual.top - expected.top),
10700
+ Math.abs(actual.width - expected.width),
10701
+ Math.abs(actual.height - expected.height)
10702
+ ];
10703
+ const tolerance = Math.max(2, Math.min(canvasWidth, canvasHeight) * 5e-3);
10704
+ if (deltas.some((delta) => delta > tolerance)) {
10705
+ continue;
10706
+ }
10707
+ const score = deltas.reduce((sum, delta) => sum + delta, 0);
10708
+ if (!best || score < best.score) {
10709
+ best = { element, score };
10710
+ }
10711
+ }
10712
+ }
10713
+ return best?.element;
10714
+ }
10541
10715
  function hasInlineBackground(element) {
10542
10716
  return Boolean(element.style.background || element.style.backgroundColor || element.style.backgroundImage);
10543
10717
  }