@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.js CHANGED
@@ -7129,6 +7129,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
7129
7129
  return;
7130
7130
  }
7131
7131
  const chartPreviews = await readWorkbookCharts(arrayBuffer).catch(() => []);
7132
+ const workbookImages = await readWorkbookSheetImages(arrayBuffer).catch(() => /* @__PURE__ */ new Map());
7132
7133
  const tabs = document.createElement("div");
7133
7134
  tabs.className = "ofv-tabs";
7134
7135
  tabs.setAttribute("role", "tablist");
@@ -7147,6 +7148,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
7147
7148
  const heading = document.createElement("h3");
7148
7149
  heading.textContent = sheetName;
7149
7150
  const sheet = workbook.Sheets[sheetName];
7151
+ const sheetImages = workbookImages.get(sheetName) || [];
7150
7152
  const range = trimWorkbookSheetRange(sheet, xlsx.utils.decode_range(sheet["!ref"] || "A1:A1"), xlsx.utils.decode_cell);
7151
7153
  const rowCount = range.e.r - range.s.r + 1;
7152
7154
  const columnCount = range.e.c - range.s.c + 1;
@@ -7172,7 +7174,8 @@ async function renderSheet(panel, arrayBuffer, extension) {
7172
7174
  xlsx.utils.encode_cell,
7173
7175
  xlsx.utils.format_cell,
7174
7176
  columnSizing,
7175
- renderTableWindow
7177
+ renderTableWindow,
7178
+ sheetImages
7176
7179
  )
7177
7180
  );
7178
7181
  windowControls?.update();
@@ -7242,6 +7245,143 @@ function renderSheetFallback(panel, extension, detail) {
7242
7245
  section.append(title, meta, support);
7243
7246
  panel.append(section);
7244
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
+ }
7245
7385
  function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
7246
7386
  const section = createSection(title);
7247
7387
  section.classList.add("ofv-encrypted");
@@ -7619,7 +7759,7 @@ function createWindowButton(label, onClick) {
7619
7759
  function maxStart(total, size) {
7620
7760
  return Math.max(0, total - size);
7621
7761
  }
7622
- function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender) {
7762
+ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender, images = []) {
7623
7763
  const table = document.createElement("table");
7624
7764
  table.id = `ofv-sheet-${sheetIndex + 1}`;
7625
7765
  table.className = "ofv-workbook-table";
@@ -7628,6 +7768,7 @@ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell
7628
7768
  const columnStart = range.s.c + viewport.columnStart;
7629
7769
  const rowStart = range.s.r + viewport.rowStart;
7630
7770
  const mergePlan = createSheetMergePlan(sheet["!merges"] || [], rowStart, rowEnd, columnStart, columnEnd);
7771
+ const imagesByCell = groupWorkbookImagesByCell(images);
7631
7772
  const colGroup = document.createElement("colgroup");
7632
7773
  let tableWidth = 0;
7633
7774
  for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex += 1) {
@@ -7682,6 +7823,7 @@ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell
7682
7823
  if (text.includes("\n")) {
7683
7824
  cell.classList.add("ofv-cell-multiline");
7684
7825
  }
7826
+ appendWorkbookCellImages(cell, imagesByCell.get(`${rowIndex}:${columnIndex}`), text);
7685
7827
  appendColumnResizeHandle(cell, columnIndex, columnSizing);
7686
7828
  row.append(cell);
7687
7829
  }
@@ -7764,6 +7906,39 @@ function createSheetMergePlan(merges, rowStart, rowEnd, columnStart, columnEnd)
7764
7906
  }
7765
7907
  return { anchors, covered };
7766
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
+ }
7767
7942
  function getSheetColumnWidth(column) {
7768
7943
  if (column?.hidden) {
7769
7944
  return 0;
@@ -8086,6 +8261,69 @@ function normalizePptxLayout(container) {
8086
8261
  for (const slide of slideCanvases) {
8087
8262
  slide.style.backgroundColor = "#FFFFFF";
8088
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" : ""}`;
8089
8327
  }
8090
8328
  function findPptxSlideCanvases(container) {
8091
8329
  const slideWrappers = Array.from(container.querySelectorAll("div[data-slide-index]"));