@open-file-viewer/core 0.1.12 → 0.1.14

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
@@ -682,7 +682,7 @@ function createViewer(options) {
682
682
  if (destroyed || token !== renderToken) {
683
683
  return;
684
684
  }
685
- currentInstance?.destroy();
685
+ destroyPreviewInstance(currentInstance);
686
686
  currentInstance = void 0;
687
687
  viewport.replaceChildren();
688
688
  setLoading(true);
@@ -704,7 +704,7 @@ function createViewer(options) {
704
704
  setError
705
705
  });
706
706
  if (destroyed || token !== renderToken) {
707
- nextInstance.destroy();
707
+ destroyPreviewInstance(nextInstance);
708
708
  return;
709
709
  }
710
710
  currentInstance = nextInstance;
@@ -761,7 +761,7 @@ function createViewer(options) {
761
761
  destroyed = true;
762
762
  renderToken += 1;
763
763
  resizeObserver.destroy();
764
- currentInstance?.destroy();
764
+ destroyPreviewInstance(currentInstance);
765
765
  toolbar?.destroy();
766
766
  theme.destroy();
767
767
  container.replaceChildren();
@@ -772,6 +772,16 @@ function createViewer(options) {
772
772
  }
773
773
  };
774
774
  }
775
+ function destroyPreviewInstance(instance) {
776
+ if (!instance) {
777
+ return;
778
+ }
779
+ try {
780
+ instance.destroy();
781
+ } catch (error) {
782
+ console.error("Failed to destroy file preview instance:", error);
783
+ }
784
+ }
775
785
  function normalizeQueue(options) {
776
786
  if (options.files && options.files.length > 0) {
777
787
  return options.files.map(
@@ -4575,7 +4585,7 @@ async function renderPdfDocumentPreview(options) {
4575
4585
  },
4576
4586
  destroy() {
4577
4587
  options.viewport.classList.remove("ofv-center");
4578
- documentTask?.destroy?.();
4588
+ destroyPdfResource(documentTask);
4579
4589
  if (options.revokeUrlOnDestroy) {
4580
4590
  revokeObjectUrl(options.fileUrl, Boolean(options.isExternal));
4581
4591
  }
@@ -4808,13 +4818,27 @@ async function renderPdfDocumentPreview(options) {
4808
4818
  }
4809
4819
  });
4810
4820
  pageStates.length = 0;
4811
- void pdfDocument.destroy?.();
4821
+ destroyPdfResource(pdfDocument);
4822
+ destroyPdfResource(documentTask);
4812
4823
  if (options.revokeUrlOnDestroy) {
4813
4824
  revokeObjectUrl(options.fileUrl, Boolean(options.isExternal));
4814
4825
  }
4815
4826
  }
4816
4827
  };
4817
4828
  }
4829
+ function destroyPdfResource(resource) {
4830
+ if (!resource || typeof resource !== "object") {
4831
+ return;
4832
+ }
4833
+ const candidate = resource;
4834
+ if (typeof candidate.destroy === "function") {
4835
+ void candidate.destroy();
4836
+ return;
4837
+ }
4838
+ if (typeof candidate.cleanup === "function") {
4839
+ void candidate.cleanup();
4840
+ }
4841
+ }
4818
4842
  function getPdfOutputScale() {
4819
4843
  if (typeof window === "undefined") {
4820
4844
  return 1;
@@ -7105,6 +7129,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
7105
7129
  return;
7106
7130
  }
7107
7131
  const chartPreviews = await readWorkbookCharts(arrayBuffer).catch(() => []);
7132
+ const workbookImages = await readWorkbookSheetImages(arrayBuffer).catch(() => /* @__PURE__ */ new Map());
7108
7133
  const tabs = document.createElement("div");
7109
7134
  tabs.className = "ofv-tabs";
7110
7135
  tabs.setAttribute("role", "tablist");
@@ -7123,6 +7148,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
7123
7148
  const heading = document.createElement("h3");
7124
7149
  heading.textContent = sheetName;
7125
7150
  const sheet = workbook.Sheets[sheetName];
7151
+ const sheetImages = workbookImages.get(sheetName) || [];
7126
7152
  const range = trimWorkbookSheetRange(sheet, xlsx.utils.decode_range(sheet["!ref"] || "A1:A1"), xlsx.utils.decode_cell);
7127
7153
  const rowCount = range.e.r - range.s.r + 1;
7128
7154
  const columnCount = range.e.c - range.s.c + 1;
@@ -7148,7 +7174,8 @@ async function renderSheet(panel, arrayBuffer, extension) {
7148
7174
  xlsx.utils.encode_cell,
7149
7175
  xlsx.utils.format_cell,
7150
7176
  columnSizing,
7151
- renderTableWindow
7177
+ renderTableWindow,
7178
+ sheetImages
7152
7179
  )
7153
7180
  );
7154
7181
  windowControls?.update();
@@ -7218,6 +7245,143 @@ function renderSheetFallback(panel, extension, detail) {
7218
7245
  section.append(title, meta, support);
7219
7246
  panel.append(section);
7220
7247
  }
7248
+ async function readWorkbookSheetImages(arrayBuffer) {
7249
+ const zip = await JSZip3.loadAsync(arrayBuffer);
7250
+ const fileNames = Object.keys(zip.files);
7251
+ if (!fileNames.some((name) => /^xl\/drawings\/.+\.xml$/i.test(name)) || !fileNames.some((name) => /^xl\/media\//i.test(name))) {
7252
+ return /* @__PURE__ */ new Map();
7253
+ }
7254
+ const workbookXml = await zip.file("xl/workbook.xml")?.async("text");
7255
+ if (!workbookXml || typeof DOMParser === "undefined") {
7256
+ return /* @__PURE__ */ new Map();
7257
+ }
7258
+ const workbookDoc = parseOfficeXml(workbookXml);
7259
+ if (!workbookDoc) {
7260
+ return /* @__PURE__ */ new Map();
7261
+ }
7262
+ const workbookRels = await readOfficeRelationships(zip, "xl/workbook.xml");
7263
+ const result = /* @__PURE__ */ new Map();
7264
+ const sheetElements = Array.from(workbookDoc.getElementsByTagName("*")).filter((element) => element.localName === "sheet");
7265
+ for (const sheetElement of sheetElements) {
7266
+ const sheetName = sheetElement.getAttribute("name") || "";
7267
+ const relationshipId = getXmlAttribute3(sheetElement, "id");
7268
+ const sheetRel = workbookRels.find((rel) => rel.id === relationshipId && /\/worksheet$/i.test(rel.type));
7269
+ const sheetPath = resolveOfficeRelationshipTarget("xl/workbook.xml", sheetRel?.target);
7270
+ if (!sheetName || !sheetPath) {
7271
+ continue;
7272
+ }
7273
+ const images = await readWorksheetImages(zip, sheetPath);
7274
+ if (images.length > 0) {
7275
+ result.set(sheetName, images);
7276
+ }
7277
+ }
7278
+ return result;
7279
+ }
7280
+ async function readWorksheetImages(zip, sheetPath) {
7281
+ const sheetXml = await zip.file(sheetPath)?.async("text");
7282
+ const sheetDoc = sheetXml ? parseOfficeXml(sheetXml) : void 0;
7283
+ if (!sheetDoc) {
7284
+ return [];
7285
+ }
7286
+ const sheetRels = await readOfficeRelationships(zip, sheetPath);
7287
+ const drawingIds = Array.from(sheetDoc.getElementsByTagName("*")).filter((element) => element.localName === "drawing").map((element) => getXmlAttribute3(element, "id")).filter((id) => Boolean(id));
7288
+ const images = [];
7289
+ for (const drawingId of drawingIds) {
7290
+ const drawingRel = sheetRels.find((rel) => rel.id === drawingId && /\/drawing$/i.test(rel.type));
7291
+ const drawingPath = resolveOfficeRelationshipTarget(sheetPath, drawingRel?.target);
7292
+ if (drawingPath) {
7293
+ images.push(...await readWorksheetDrawingImages(zip, drawingPath));
7294
+ }
7295
+ }
7296
+ return images;
7297
+ }
7298
+ async function readWorksheetDrawingImages(zip, drawingPath) {
7299
+ const drawingXml = await zip.file(drawingPath)?.async("text");
7300
+ const drawingDoc = drawingXml ? parseOfficeXml(drawingXml) : void 0;
7301
+ if (!drawingDoc) {
7302
+ return [];
7303
+ }
7304
+ const drawingRels = await readOfficeRelationships(zip, drawingPath);
7305
+ const anchors = Array.from(drawingDoc.getElementsByTagName("*")).filter(
7306
+ (element) => element.localName === "twoCellAnchor" || element.localName === "oneCellAnchor"
7307
+ );
7308
+ const images = [];
7309
+ for (const anchor of anchors) {
7310
+ const from = Array.from(anchor.children).find((element) => element.localName === "from");
7311
+ const embedId = findDrawingImageRelationshipId(anchor);
7312
+ const mediaRel = drawingRels.find((rel) => rel.id === embedId && /\/image$/i.test(rel.type));
7313
+ const mediaPath = resolveOfficeRelationshipTarget(drawingPath, mediaRel?.target);
7314
+ const mediaFile = mediaPath ? zip.file(mediaPath) : void 0;
7315
+ if (!from || !mediaPath || !mediaFile) {
7316
+ continue;
7317
+ }
7318
+ const mimeType = mimeTypeFromImagePath(mediaPath);
7319
+ images.push({
7320
+ row: readDrawingMarkerIndex(from, "row"),
7321
+ column: readDrawingMarkerIndex(from, "col"),
7322
+ fileName: mediaPath.split("/").pop() || "image",
7323
+ mimeType,
7324
+ dataUrl: `data:${mimeType};base64,${await mediaFile.async("base64")}`,
7325
+ title: readDrawingImageTitle(anchor)
7326
+ });
7327
+ }
7328
+ return images;
7329
+ }
7330
+ function findDrawingImageRelationshipId(anchor) {
7331
+ for (const element of Array.from(anchor.getElementsByTagName("*"))) {
7332
+ if (element.localName === "blip") {
7333
+ return getXmlAttribute3(element, "embed") || getXmlAttribute3(element, "link") || void 0;
7334
+ }
7335
+ }
7336
+ return void 0;
7337
+ }
7338
+ function readDrawingImageTitle(anchor) {
7339
+ const nonVisualProperties = Array.from(anchor.getElementsByTagName("*")).find((element) => element.localName === "cNvPr");
7340
+ return nonVisualProperties?.getAttribute("descr") || nonVisualProperties?.getAttribute("name") || void 0;
7341
+ }
7342
+ function readDrawingMarkerIndex(marker, localName) {
7343
+ const element = Array.from(marker.children).find((child) => child.localName === localName);
7344
+ const value = Number.parseInt(element?.textContent || "0", 10);
7345
+ return Number.isFinite(value) && value >= 0 ? value : 0;
7346
+ }
7347
+ async function readOfficeRelationships(zip, partPath) {
7348
+ const xml = await zip.file(relationshipPathForPart(partPath))?.async("text");
7349
+ const doc = xml ? parseOfficeXml(xml) : void 0;
7350
+ if (!doc) {
7351
+ return [];
7352
+ }
7353
+ return Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Relationship").map((element) => ({
7354
+ id: element.getAttribute("Id") || "",
7355
+ type: element.getAttribute("Type") || "",
7356
+ target: element.getAttribute("Target") || ""
7357
+ }));
7358
+ }
7359
+ function parseOfficeXml(xml) {
7360
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7361
+ return doc.querySelector("parsererror") ? void 0 : doc;
7362
+ }
7363
+ function resolveOfficeRelationshipTarget(sourcePath, target) {
7364
+ return resolvePptxRelationshipTarget(sourcePath, target);
7365
+ }
7366
+ function mimeTypeFromImagePath(path) {
7367
+ const extension = path.split(".").pop()?.toLowerCase();
7368
+ switch (extension) {
7369
+ case "jpg":
7370
+ case "jpeg":
7371
+ return "image/jpeg";
7372
+ case "gif":
7373
+ return "image/gif";
7374
+ case "webp":
7375
+ return "image/webp";
7376
+ case "bmp":
7377
+ return "image/bmp";
7378
+ case "svg":
7379
+ return "image/svg+xml";
7380
+ case "png":
7381
+ default:
7382
+ return "image/png";
7383
+ }
7384
+ }
7221
7385
  function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
7222
7386
  const section = createSection(title);
7223
7387
  section.classList.add("ofv-encrypted");
@@ -7595,7 +7759,7 @@ function createWindowButton(label, onClick) {
7595
7759
  function maxStart(total, size) {
7596
7760
  return Math.max(0, total - size);
7597
7761
  }
7598
- function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender) {
7762
+ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender, images = []) {
7599
7763
  const table = document.createElement("table");
7600
7764
  table.id = `ofv-sheet-${sheetIndex + 1}`;
7601
7765
  table.className = "ofv-workbook-table";
@@ -7604,6 +7768,7 @@ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell
7604
7768
  const columnStart = range.s.c + viewport.columnStart;
7605
7769
  const rowStart = range.s.r + viewport.rowStart;
7606
7770
  const mergePlan = createSheetMergePlan(sheet["!merges"] || [], rowStart, rowEnd, columnStart, columnEnd);
7771
+ const imagesByCell = groupWorkbookImagesByCell(images);
7607
7772
  const colGroup = document.createElement("colgroup");
7608
7773
  let tableWidth = 0;
7609
7774
  for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex += 1) {
@@ -7658,6 +7823,7 @@ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell
7658
7823
  if (text.includes("\n")) {
7659
7824
  cell.classList.add("ofv-cell-multiline");
7660
7825
  }
7826
+ appendWorkbookCellImages(cell, imagesByCell.get(`${rowIndex}:${columnIndex}`), text);
7661
7827
  appendColumnResizeHandle(cell, columnIndex, columnSizing);
7662
7828
  row.append(cell);
7663
7829
  }
@@ -7740,6 +7906,39 @@ function createSheetMergePlan(merges, rowStart, rowEnd, columnStart, columnEnd)
7740
7906
  }
7741
7907
  return { anchors, covered };
7742
7908
  }
7909
+ function groupWorkbookImagesByCell(images) {
7910
+ const grouped = /* @__PURE__ */ new Map();
7911
+ for (const image of images) {
7912
+ const key = `${image.row}:${image.column}`;
7913
+ const items = grouped.get(key) || [];
7914
+ items.push(image);
7915
+ grouped.set(key, items);
7916
+ }
7917
+ return grouped;
7918
+ }
7919
+ function appendWorkbookCellImages(cell, images, text) {
7920
+ if (!images?.length) {
7921
+ return;
7922
+ }
7923
+ if (isWorkbookImagePlaceholderValue(text)) {
7924
+ cell.textContent = "";
7925
+ cell.removeAttribute("title");
7926
+ }
7927
+ cell.classList.add("ofv-cell-image");
7928
+ for (const image of images) {
7929
+ const figure = document.createElement("figure");
7930
+ figure.className = "ofv-workbook-image";
7931
+ const element = document.createElement("img");
7932
+ element.src = image.dataUrl;
7933
+ element.alt = image.title || image.fileName || "Excel embedded image";
7934
+ element.loading = "lazy";
7935
+ figure.append(element);
7936
+ cell.append(figure);
7937
+ }
7938
+ }
7939
+ function isWorkbookImagePlaceholderValue(text) {
7940
+ return /^#(?:VALUE|NAME|REF|N\/A|NULL|NUM|DIV\/0)!?$/i.test(text.trim());
7941
+ }
7743
7942
  function getSheetColumnWidth(column) {
7744
7943
  if (column?.hidden) {
7745
7944
  return 0;
@@ -8062,6 +8261,69 @@ function normalizePptxLayout(container) {
8062
8261
  for (const slide of slideCanvases) {
8063
8262
  slide.style.backgroundColor = "#FFFFFF";
8064
8263
  }
8264
+ normalizePptxMirroredText(container);
8265
+ }
8266
+ function normalizePptxMirroredText(container) {
8267
+ const mirroredContainers = Array.from(container.querySelectorAll("div")).filter((element) => {
8268
+ const text = element.textContent?.trim();
8269
+ if (!text || element.children.length === 0) {
8270
+ return false;
8271
+ }
8272
+ const styleTransform = element.style.transform;
8273
+ return hasPptxMirrorTransform(styleTransform, "x") || hasPptxMirrorTransform(styleTransform, "y");
8274
+ });
8275
+ for (const element of mirroredContainers) {
8276
+ const flipX = hasPptxMirrorTransform(element.style.transform, "x");
8277
+ const flipY = hasPptxMirrorTransform(element.style.transform, "y");
8278
+ const targets = findPptxMirroredTextTargets(element);
8279
+ for (const target of targets) {
8280
+ counterMirrorPptxTextTarget(target, flipX, flipY);
8281
+ }
8282
+ }
8283
+ }
8284
+ function findPptxMirroredTextTargets(element) {
8285
+ const children = Array.from(element.children).filter((child) => child instanceof HTMLElement);
8286
+ const absoluteTextChildren = children.filter((child) => Boolean(child.textContent?.trim()) && child.style.position === "absolute");
8287
+ if (absoluteTextChildren.length > 0) {
8288
+ return absoluteTextChildren;
8289
+ }
8290
+ return children.filter((child) => Boolean(child.textContent?.trim()));
8291
+ }
8292
+ function hasPptxMirrorTransform(transform, axis) {
8293
+ if (!transform) {
8294
+ return false;
8295
+ }
8296
+ if (axis === "x" && /scaleX\(\s*-1\s*\)/i.test(transform)) {
8297
+ return true;
8298
+ }
8299
+ if (axis === "y" && /scaleY\(\s*-1\s*\)/i.test(transform)) {
8300
+ return true;
8301
+ }
8302
+ const matrix = transform.match(/matrix\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s]+)/i);
8303
+ if (!matrix) {
8304
+ return false;
8305
+ }
8306
+ const xScale = Number(matrix[1]);
8307
+ const yScale = Number(matrix[4]);
8308
+ return axis === "x" ? xScale < 0 : yScale < 0;
8309
+ }
8310
+ function counterMirrorPptxTextTarget(target, flipX, flipY) {
8311
+ const applied = target.dataset.ofvPptxCounterMirror ?? "";
8312
+ const transforms = [];
8313
+ if (flipX && !applied.includes("x")) {
8314
+ transforms.push("scaleX(-1)");
8315
+ }
8316
+ if (flipY && !applied.includes("y")) {
8317
+ transforms.push("scaleY(-1)");
8318
+ }
8319
+ if (transforms.length === 0) {
8320
+ return;
8321
+ }
8322
+ target.style.transform = `${target.style.transform || ""} ${transforms.join(" ")}`.trim();
8323
+ if (!target.style.transformOrigin) {
8324
+ target.style.transformOrigin = "center center";
8325
+ }
8326
+ target.dataset.ofvPptxCounterMirror = `${applied}${flipX ? "x" : ""}${flipY ? "y" : ""}`;
8065
8327
  }
8066
8328
  function findPptxSlideCanvases(container) {
8067
8329
  const slideWrappers = Array.from(container.querySelectorAll("div[data-slide-index]"));