@open-file-viewer/core 0.1.13 → 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.cjs CHANGED
@@ -7182,6 +7182,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
7182
7182
  return;
7183
7183
  }
7184
7184
  const chartPreviews = await readWorkbookCharts(arrayBuffer).catch(() => []);
7185
+ const workbookImages = await readWorkbookSheetImages(arrayBuffer).catch(() => /* @__PURE__ */ new Map());
7185
7186
  const tabs = document.createElement("div");
7186
7187
  tabs.className = "ofv-tabs";
7187
7188
  tabs.setAttribute("role", "tablist");
@@ -7200,6 +7201,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
7200
7201
  const heading = document.createElement("h3");
7201
7202
  heading.textContent = sheetName;
7202
7203
  const sheet = workbook.Sheets[sheetName];
7204
+ const sheetImages = workbookImages.get(sheetName) || [];
7203
7205
  const range = trimWorkbookSheetRange(sheet, xlsx.utils.decode_range(sheet["!ref"] || "A1:A1"), xlsx.utils.decode_cell);
7204
7206
  const rowCount = range.e.r - range.s.r + 1;
7205
7207
  const columnCount = range.e.c - range.s.c + 1;
@@ -7225,7 +7227,8 @@ async function renderSheet(panel, arrayBuffer, extension) {
7225
7227
  xlsx.utils.encode_cell,
7226
7228
  xlsx.utils.format_cell,
7227
7229
  columnSizing,
7228
- renderTableWindow
7230
+ renderTableWindow,
7231
+ sheetImages
7229
7232
  )
7230
7233
  );
7231
7234
  windowControls?.update();
@@ -7295,6 +7298,143 @@ function renderSheetFallback(panel, extension, detail) {
7295
7298
  section.append(title, meta, support);
7296
7299
  panel.append(section);
7297
7300
  }
7301
+ async function readWorkbookSheetImages(arrayBuffer) {
7302
+ const zip = await import_jszip3.default.loadAsync(arrayBuffer);
7303
+ const fileNames = Object.keys(zip.files);
7304
+ if (!fileNames.some((name) => /^xl\/drawings\/.+\.xml$/i.test(name)) || !fileNames.some((name) => /^xl\/media\//i.test(name))) {
7305
+ return /* @__PURE__ */ new Map();
7306
+ }
7307
+ const workbookXml = await zip.file("xl/workbook.xml")?.async("text");
7308
+ if (!workbookXml || typeof DOMParser === "undefined") {
7309
+ return /* @__PURE__ */ new Map();
7310
+ }
7311
+ const workbookDoc = parseOfficeXml(workbookXml);
7312
+ if (!workbookDoc) {
7313
+ return /* @__PURE__ */ new Map();
7314
+ }
7315
+ const workbookRels = await readOfficeRelationships(zip, "xl/workbook.xml");
7316
+ const result = /* @__PURE__ */ new Map();
7317
+ const sheetElements = Array.from(workbookDoc.getElementsByTagName("*")).filter((element) => element.localName === "sheet");
7318
+ for (const sheetElement of sheetElements) {
7319
+ const sheetName = sheetElement.getAttribute("name") || "";
7320
+ const relationshipId = getXmlAttribute3(sheetElement, "id");
7321
+ const sheetRel = workbookRels.find((rel) => rel.id === relationshipId && /\/worksheet$/i.test(rel.type));
7322
+ const sheetPath = resolveOfficeRelationshipTarget("xl/workbook.xml", sheetRel?.target);
7323
+ if (!sheetName || !sheetPath) {
7324
+ continue;
7325
+ }
7326
+ const images = await readWorksheetImages(zip, sheetPath);
7327
+ if (images.length > 0) {
7328
+ result.set(sheetName, images);
7329
+ }
7330
+ }
7331
+ return result;
7332
+ }
7333
+ async function readWorksheetImages(zip, sheetPath) {
7334
+ const sheetXml = await zip.file(sheetPath)?.async("text");
7335
+ const sheetDoc = sheetXml ? parseOfficeXml(sheetXml) : void 0;
7336
+ if (!sheetDoc) {
7337
+ return [];
7338
+ }
7339
+ const sheetRels = await readOfficeRelationships(zip, sheetPath);
7340
+ const drawingIds = Array.from(sheetDoc.getElementsByTagName("*")).filter((element) => element.localName === "drawing").map((element) => getXmlAttribute3(element, "id")).filter((id) => Boolean(id));
7341
+ const images = [];
7342
+ for (const drawingId of drawingIds) {
7343
+ const drawingRel = sheetRels.find((rel) => rel.id === drawingId && /\/drawing$/i.test(rel.type));
7344
+ const drawingPath = resolveOfficeRelationshipTarget(sheetPath, drawingRel?.target);
7345
+ if (drawingPath) {
7346
+ images.push(...await readWorksheetDrawingImages(zip, drawingPath));
7347
+ }
7348
+ }
7349
+ return images;
7350
+ }
7351
+ async function readWorksheetDrawingImages(zip, drawingPath) {
7352
+ const drawingXml = await zip.file(drawingPath)?.async("text");
7353
+ const drawingDoc = drawingXml ? parseOfficeXml(drawingXml) : void 0;
7354
+ if (!drawingDoc) {
7355
+ return [];
7356
+ }
7357
+ const drawingRels = await readOfficeRelationships(zip, drawingPath);
7358
+ const anchors = Array.from(drawingDoc.getElementsByTagName("*")).filter(
7359
+ (element) => element.localName === "twoCellAnchor" || element.localName === "oneCellAnchor"
7360
+ );
7361
+ const images = [];
7362
+ for (const anchor of anchors) {
7363
+ const from = Array.from(anchor.children).find((element) => element.localName === "from");
7364
+ const embedId = findDrawingImageRelationshipId(anchor);
7365
+ const mediaRel = drawingRels.find((rel) => rel.id === embedId && /\/image$/i.test(rel.type));
7366
+ const mediaPath = resolveOfficeRelationshipTarget(drawingPath, mediaRel?.target);
7367
+ const mediaFile = mediaPath ? zip.file(mediaPath) : void 0;
7368
+ if (!from || !mediaPath || !mediaFile) {
7369
+ continue;
7370
+ }
7371
+ const mimeType = mimeTypeFromImagePath(mediaPath);
7372
+ images.push({
7373
+ row: readDrawingMarkerIndex(from, "row"),
7374
+ column: readDrawingMarkerIndex(from, "col"),
7375
+ fileName: mediaPath.split("/").pop() || "image",
7376
+ mimeType,
7377
+ dataUrl: `data:${mimeType};base64,${await mediaFile.async("base64")}`,
7378
+ title: readDrawingImageTitle(anchor)
7379
+ });
7380
+ }
7381
+ return images;
7382
+ }
7383
+ function findDrawingImageRelationshipId(anchor) {
7384
+ for (const element of Array.from(anchor.getElementsByTagName("*"))) {
7385
+ if (element.localName === "blip") {
7386
+ return getXmlAttribute3(element, "embed") || getXmlAttribute3(element, "link") || void 0;
7387
+ }
7388
+ }
7389
+ return void 0;
7390
+ }
7391
+ function readDrawingImageTitle(anchor) {
7392
+ const nonVisualProperties = Array.from(anchor.getElementsByTagName("*")).find((element) => element.localName === "cNvPr");
7393
+ return nonVisualProperties?.getAttribute("descr") || nonVisualProperties?.getAttribute("name") || void 0;
7394
+ }
7395
+ function readDrawingMarkerIndex(marker, localName) {
7396
+ const element = Array.from(marker.children).find((child) => child.localName === localName);
7397
+ const value = Number.parseInt(element?.textContent || "0", 10);
7398
+ return Number.isFinite(value) && value >= 0 ? value : 0;
7399
+ }
7400
+ async function readOfficeRelationships(zip, partPath) {
7401
+ const xml = await zip.file(relationshipPathForPart(partPath))?.async("text");
7402
+ const doc = xml ? parseOfficeXml(xml) : void 0;
7403
+ if (!doc) {
7404
+ return [];
7405
+ }
7406
+ return Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Relationship").map((element) => ({
7407
+ id: element.getAttribute("Id") || "",
7408
+ type: element.getAttribute("Type") || "",
7409
+ target: element.getAttribute("Target") || ""
7410
+ }));
7411
+ }
7412
+ function parseOfficeXml(xml) {
7413
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7414
+ return doc.querySelector("parsererror") ? void 0 : doc;
7415
+ }
7416
+ function resolveOfficeRelationshipTarget(sourcePath, target) {
7417
+ return resolvePptxRelationshipTarget(sourcePath, target);
7418
+ }
7419
+ function mimeTypeFromImagePath(path) {
7420
+ const extension = path.split(".").pop()?.toLowerCase();
7421
+ switch (extension) {
7422
+ case "jpg":
7423
+ case "jpeg":
7424
+ return "image/jpeg";
7425
+ case "gif":
7426
+ return "image/gif";
7427
+ case "webp":
7428
+ return "image/webp";
7429
+ case "bmp":
7430
+ return "image/bmp";
7431
+ case "svg":
7432
+ return "image/svg+xml";
7433
+ case "png":
7434
+ default:
7435
+ return "image/png";
7436
+ }
7437
+ }
7298
7438
  function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
7299
7439
  const section = createSection(title);
7300
7440
  section.classList.add("ofv-encrypted");
@@ -7672,7 +7812,7 @@ function createWindowButton(label, onClick) {
7672
7812
  function maxStart(total, size) {
7673
7813
  return Math.max(0, total - size);
7674
7814
  }
7675
- function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender) {
7815
+ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender, images = []) {
7676
7816
  const table = document.createElement("table");
7677
7817
  table.id = `ofv-sheet-${sheetIndex + 1}`;
7678
7818
  table.className = "ofv-workbook-table";
@@ -7681,6 +7821,7 @@ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell
7681
7821
  const columnStart = range.s.c + viewport.columnStart;
7682
7822
  const rowStart = range.s.r + viewport.rowStart;
7683
7823
  const mergePlan = createSheetMergePlan(sheet["!merges"] || [], rowStart, rowEnd, columnStart, columnEnd);
7824
+ const imagesByCell = groupWorkbookImagesByCell(images);
7684
7825
  const colGroup = document.createElement("colgroup");
7685
7826
  let tableWidth = 0;
7686
7827
  for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex += 1) {
@@ -7735,6 +7876,7 @@ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell
7735
7876
  if (text.includes("\n")) {
7736
7877
  cell.classList.add("ofv-cell-multiline");
7737
7878
  }
7879
+ appendWorkbookCellImages(cell, imagesByCell.get(`${rowIndex}:${columnIndex}`), text);
7738
7880
  appendColumnResizeHandle(cell, columnIndex, columnSizing);
7739
7881
  row.append(cell);
7740
7882
  }
@@ -7817,6 +7959,39 @@ function createSheetMergePlan(merges, rowStart, rowEnd, columnStart, columnEnd)
7817
7959
  }
7818
7960
  return { anchors, covered };
7819
7961
  }
7962
+ function groupWorkbookImagesByCell(images) {
7963
+ const grouped = /* @__PURE__ */ new Map();
7964
+ for (const image of images) {
7965
+ const key = `${image.row}:${image.column}`;
7966
+ const items = grouped.get(key) || [];
7967
+ items.push(image);
7968
+ grouped.set(key, items);
7969
+ }
7970
+ return grouped;
7971
+ }
7972
+ function appendWorkbookCellImages(cell, images, text) {
7973
+ if (!images?.length) {
7974
+ return;
7975
+ }
7976
+ if (isWorkbookImagePlaceholderValue(text)) {
7977
+ cell.textContent = "";
7978
+ cell.removeAttribute("title");
7979
+ }
7980
+ cell.classList.add("ofv-cell-image");
7981
+ for (const image of images) {
7982
+ const figure = document.createElement("figure");
7983
+ figure.className = "ofv-workbook-image";
7984
+ const element = document.createElement("img");
7985
+ element.src = image.dataUrl;
7986
+ element.alt = image.title || image.fileName || "Excel embedded image";
7987
+ element.loading = "lazy";
7988
+ figure.append(element);
7989
+ cell.append(figure);
7990
+ }
7991
+ }
7992
+ function isWorkbookImagePlaceholderValue(text) {
7993
+ return /^#(?:VALUE|NAME|REF|N\/A|NULL|NUM|DIV\/0)!?$/i.test(text.trim());
7994
+ }
7820
7995
  function getSheetColumnWidth(column) {
7821
7996
  if (column?.hidden) {
7822
7997
  return 0;
@@ -8139,6 +8314,69 @@ function normalizePptxLayout(container) {
8139
8314
  for (const slide of slideCanvases) {
8140
8315
  slide.style.backgroundColor = "#FFFFFF";
8141
8316
  }
8317
+ normalizePptxMirroredText(container);
8318
+ }
8319
+ function normalizePptxMirroredText(container) {
8320
+ const mirroredContainers = Array.from(container.querySelectorAll("div")).filter((element) => {
8321
+ const text = element.textContent?.trim();
8322
+ if (!text || element.children.length === 0) {
8323
+ return false;
8324
+ }
8325
+ const styleTransform = element.style.transform;
8326
+ return hasPptxMirrorTransform(styleTransform, "x") || hasPptxMirrorTransform(styleTransform, "y");
8327
+ });
8328
+ for (const element of mirroredContainers) {
8329
+ const flipX = hasPptxMirrorTransform(element.style.transform, "x");
8330
+ const flipY = hasPptxMirrorTransform(element.style.transform, "y");
8331
+ const targets = findPptxMirroredTextTargets(element);
8332
+ for (const target of targets) {
8333
+ counterMirrorPptxTextTarget(target, flipX, flipY);
8334
+ }
8335
+ }
8336
+ }
8337
+ function findPptxMirroredTextTargets(element) {
8338
+ const children = Array.from(element.children).filter((child) => child instanceof HTMLElement);
8339
+ const absoluteTextChildren = children.filter((child) => Boolean(child.textContent?.trim()) && child.style.position === "absolute");
8340
+ if (absoluteTextChildren.length > 0) {
8341
+ return absoluteTextChildren;
8342
+ }
8343
+ return children.filter((child) => Boolean(child.textContent?.trim()));
8344
+ }
8345
+ function hasPptxMirrorTransform(transform, axis) {
8346
+ if (!transform) {
8347
+ return false;
8348
+ }
8349
+ if (axis === "x" && /scaleX\(\s*-1\s*\)/i.test(transform)) {
8350
+ return true;
8351
+ }
8352
+ if (axis === "y" && /scaleY\(\s*-1\s*\)/i.test(transform)) {
8353
+ return true;
8354
+ }
8355
+ const matrix = transform.match(/matrix\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s]+)/i);
8356
+ if (!matrix) {
8357
+ return false;
8358
+ }
8359
+ const xScale = Number(matrix[1]);
8360
+ const yScale = Number(matrix[4]);
8361
+ return axis === "x" ? xScale < 0 : yScale < 0;
8362
+ }
8363
+ function counterMirrorPptxTextTarget(target, flipX, flipY) {
8364
+ const applied = target.dataset.ofvPptxCounterMirror ?? "";
8365
+ const transforms = [];
8366
+ if (flipX && !applied.includes("x")) {
8367
+ transforms.push("scaleX(-1)");
8368
+ }
8369
+ if (flipY && !applied.includes("y")) {
8370
+ transforms.push("scaleY(-1)");
8371
+ }
8372
+ if (transforms.length === 0) {
8373
+ return;
8374
+ }
8375
+ target.style.transform = `${target.style.transform || ""} ${transforms.join(" ")}`.trim();
8376
+ if (!target.style.transformOrigin) {
8377
+ target.style.transformOrigin = "center center";
8378
+ }
8379
+ target.dataset.ofvPptxCounterMirror = `${applied}${flipX ? "x" : ""}${flipY ? "y" : ""}`;
8142
8380
  }
8143
8381
  function findPptxSlideCanvases(container) {
8144
8382
  const slideWrappers = Array.from(container.querySelectorAll("div[data-slide-index]"));