@open-file-viewer/core 0.1.13 → 0.1.15

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
@@ -553,6 +553,48 @@ function revokeObjectUrl(url, isExternal) {
553
553
  }
554
554
  }
555
555
 
556
+ // src/messages.ts
557
+ var defaultMessages = {
558
+ "zh-CN": {
559
+ loading: "\u6B63\u5728\u52A0\u8F7D\u9884\u89C8...",
560
+ unsupportedTitle: "\u5F53\u524D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u5728\u7EBF\u9884\u89C8",
561
+ downloadTitle: "\u5F53\u524D\u6587\u4EF6\u53EF\u4E0B\u8F7D\u540E\u67E5\u770B",
562
+ downloadFile: "\u4E0B\u8F7D\u6587\u4EF6",
563
+ file: "\u6587\u4EF6",
564
+ unnamedFile: "\u672A\u547D\u540D\u6587\u4EF6",
565
+ format: "\u683C\u5F0F",
566
+ unknown: "\u672A\u77E5",
567
+ mime: "MIME",
568
+ undeclared: "\u672A\u58F0\u660E",
569
+ size: "\u5927\u5C0F",
570
+ source: "\u6765\u6E90",
571
+ remoteUrl: "\u8FDC\u7A0B URL",
572
+ localFile: "\u672C\u5730/\u5185\u5B58\u6587\u4EF6"
573
+ },
574
+ "en-US": {
575
+ loading: "Loading preview...",
576
+ unsupportedTitle: "Preview is not available for this file",
577
+ downloadTitle: "This file can be downloaded and opened locally",
578
+ downloadFile: "Download file",
579
+ file: "File",
580
+ unnamedFile: "Untitled file",
581
+ format: "Format",
582
+ unknown: "Unknown",
583
+ mime: "MIME",
584
+ undeclared: "Not declared",
585
+ size: "Size",
586
+ source: "Source",
587
+ remoteUrl: "Remote URL",
588
+ localFile: "Local or in-memory file"
589
+ }
590
+ };
591
+ function resolveMessages(options) {
592
+ return {
593
+ ...defaultMessages[options.locale || "zh-CN"],
594
+ ...options.messages
595
+ };
596
+ }
597
+
556
598
  // src/plugins/fallback.ts
557
599
  function fallbackPlugin() {
558
600
  return {
@@ -570,12 +612,12 @@ function fallbackPlugin() {
570
612
  const panel = document.createElement("div");
571
613
  panel.className = "ofv-fallback";
572
614
  const title = document.createElement("strong");
573
- title.textContent = ctx.options.fallback === "download" ? "\u5F53\u524D\u6587\u4EF6\u53EF\u4E0B\u8F7D\u540E\u67E5\u770B" : "\u5F53\u524D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u5728\u7EBF\u9884\u89C8";
574
- const meta = createFallbackMeta(ctx.file);
615
+ title.textContent = ctx.options.fallback === "download" ? ctx.options.messages.downloadTitle : ctx.options.messages.unsupportedTitle;
616
+ const meta = createFallbackMeta(ctx.file, ctx.options.messages);
575
617
  const download = document.createElement("a");
576
618
  download.href = url;
577
619
  download.download = ctx.file.name;
578
- download.textContent = "\u4E0B\u8F7D\u6587\u4EF6";
620
+ download.textContent = ctx.options.messages.downloadFile;
579
621
  panel.append(title, meta, download);
580
622
  ctx.viewport.classList.add("ofv-center");
581
623
  ctx.viewport.append(panel);
@@ -591,14 +633,14 @@ function fallbackPlugin() {
591
633
  }
592
634
  };
593
635
  }
594
- function createFallbackMeta(file) {
636
+ function createFallbackMeta(file, messages) {
595
637
  const meta = document.createElement("dl");
596
638
  meta.className = "ofv-fallback-meta";
597
- appendFallbackMeta(meta, "\u6587\u4EF6", file.name || "\u672A\u547D\u540D\u6587\u4EF6");
598
- appendFallbackMeta(meta, "\u683C\u5F0F", file.extension ? `.${file.extension}` : "\u672A\u77E5");
599
- appendFallbackMeta(meta, "MIME", file.mimeType || "\u672A\u58F0\u660E");
600
- appendFallbackMeta(meta, "\u5927\u5C0F", file.size === void 0 ? "\u672A\u77E5" : formatFallbackBytes(file.size));
601
- appendFallbackMeta(meta, "\u6765\u6E90", file.url ? "\u8FDC\u7A0B URL" : "\u672C\u5730/\u5185\u5B58\u6587\u4EF6");
639
+ appendFallbackMeta(meta, messages.file, file.name || messages.unnamedFile);
640
+ appendFallbackMeta(meta, messages.format, file.extension ? `.${file.extension}` : messages.unknown);
641
+ appendFallbackMeta(meta, messages.mime, file.mimeType || messages.undeclared);
642
+ appendFallbackMeta(meta, messages.size, file.size === void 0 ? messages.unknown : formatFallbackBytes(file.size));
643
+ appendFallbackMeta(meta, messages.source, file.url ? messages.remoteUrl : messages.localFile);
602
644
  return meta;
603
645
  }
604
646
  function appendFallbackMeta(parent, label, value) {
@@ -656,15 +698,17 @@ function createViewer(options) {
656
698
  host.append(status, viewport);
657
699
  container.replaceChildren(host);
658
700
  const normalizedOptions = {
701
+ ...options,
659
702
  fit: options.fit || "contain",
660
703
  fallback: options.fallback || "inline",
661
- ...options
704
+ zoom: normalizeInitialZoom(options.zoom),
705
+ messages: resolveMessages(options)
662
706
  };
663
707
  let destroyed = false;
664
708
  let renderToken = 0;
665
709
  const setLoading = (loading) => {
666
710
  status.hidden = !loading;
667
- status.textContent = loading ? "Loading preview..." : "";
711
+ status.textContent = loading ? normalizedOptions.messages.loading : "";
668
712
  };
669
713
  const setError = (error) => {
670
714
  status.hidden = false;
@@ -820,6 +864,9 @@ function clampIndex(index, length) {
820
864
  }
821
865
  return Math.min(Math.max(index, 0), length - 1);
822
866
  }
867
+ function normalizeInitialZoom(zoom) {
868
+ return typeof zoom === "number" && Number.isFinite(zoom) && zoom > 0 ? zoom : 1;
869
+ }
823
870
  function applyTheme(container, theme) {
824
871
  const media = window.matchMedia?.("(prefers-color-scheme: dark)");
825
872
  const classes = ["ofv-theme-light", "ofv-theme-dark"];
@@ -1696,6 +1743,98 @@ async function findPlugin(plugins, file) {
1696
1743
  return fallbackPlugin();
1697
1744
  }
1698
1745
 
1746
+ // src/plugins/utils.ts
1747
+ async function readArrayBuffer(file) {
1748
+ if (file.source instanceof ArrayBuffer) {
1749
+ return file.source;
1750
+ }
1751
+ if (file.blob) {
1752
+ return file.blob.arrayBuffer();
1753
+ }
1754
+ if (typeof file.source === "string") {
1755
+ const response = await fetch(file.source);
1756
+ if (!response.ok) {
1757
+ throw new Error(`Failed to fetch file: ${response.status}`);
1758
+ }
1759
+ return response.arrayBuffer();
1760
+ }
1761
+ throw new Error("Unsupported file source.");
1762
+ }
1763
+ async function readTextFile(file) {
1764
+ const decode = (buffer) => decodeTextBuffer(buffer);
1765
+ if (typeof file.source === "string") {
1766
+ const response = await fetch(file.source);
1767
+ if (!response.ok) {
1768
+ throw new Error(`Failed to fetch text file: ${response.status}`);
1769
+ }
1770
+ return decode(await response.arrayBuffer());
1771
+ }
1772
+ if (file.blob) {
1773
+ return decode(await file.blob.arrayBuffer());
1774
+ }
1775
+ if (file.source instanceof ArrayBuffer) {
1776
+ return decode(file.source);
1777
+ }
1778
+ return String(file.source);
1779
+ }
1780
+ function createPanel(className = "") {
1781
+ const panel = document.createElement("div");
1782
+ panel.className = `ofv-panel ${className}`.trim();
1783
+ return panel;
1784
+ }
1785
+ function getInitialZoom(ctx, min = 0.1, max = 8) {
1786
+ return Math.min(max, Math.max(min, ctx.options.zoom));
1787
+ }
1788
+ function createSection(title) {
1789
+ const section = document.createElement("section");
1790
+ section.className = "ofv-section";
1791
+ const heading = document.createElement("h3");
1792
+ heading.textContent = title;
1793
+ section.append(heading);
1794
+ return section;
1795
+ }
1796
+ function appendMeta(parent, label, value) {
1797
+ const row = document.createElement("div");
1798
+ row.className = "ofv-meta-row";
1799
+ const key = document.createElement("span");
1800
+ key.textContent = label;
1801
+ const content = document.createElement("strong");
1802
+ content.textContent = String(value);
1803
+ row.append(key, content);
1804
+ parent.append(row);
1805
+ }
1806
+ function decodeTextBuffer(buffer) {
1807
+ return decodeTextBytes(new Uint8Array(buffer));
1808
+ }
1809
+ function decodeTextBytes(bytes) {
1810
+ if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
1811
+ return new TextDecoder("utf-8").decode(bytes.subarray(3));
1812
+ }
1813
+ if (bytes.length >= 2) {
1814
+ if (bytes[0] === 255 && bytes[1] === 254) {
1815
+ return new TextDecoder("utf-16le").decode(bytes.subarray(2));
1816
+ }
1817
+ if (bytes[0] === 254 && bytes[1] === 255) {
1818
+ return new TextDecoder("utf-16be").decode(bytes.subarray(2));
1819
+ }
1820
+ }
1821
+ try {
1822
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1823
+ } catch {
1824
+ return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
1825
+ }
1826
+ }
1827
+ function decodeWithFallback(bytes, encoding) {
1828
+ try {
1829
+ return new TextDecoder(encoding).decode(bytes);
1830
+ } catch {
1831
+ return void 0;
1832
+ }
1833
+ }
1834
+ function resolveFormat(file, mimeMap) {
1835
+ return file.extension || mimeMap[file.mimeType] || "";
1836
+ }
1837
+
1699
1838
  // src/plugins/image.ts
1700
1839
  var imageExtensions = /* @__PURE__ */ new Set([
1701
1840
  "jpg",
@@ -1809,7 +1948,7 @@ function imagePlugin() {
1809
1948
  canvasSource.setAttribute("role", "img");
1810
1949
  canvasSource.setAttribute("aria-label", ctx.file.name);
1811
1950
  }
1812
- let scale = 1;
1951
+ let scale = getInitialZoom(ctx);
1813
1952
  let rotation = 0;
1814
1953
  let offsetX = 0;
1815
1954
  let offsetY = 0;
@@ -3637,95 +3776,6 @@ function readAiffExtended(bytes, offset) {
3637
3776
  return mantissa * Math.pow(2, exponent - 16383 - 63);
3638
3777
  }
3639
3778
 
3640
- // src/plugins/utils.ts
3641
- async function readArrayBuffer(file) {
3642
- if (file.source instanceof ArrayBuffer) {
3643
- return file.source;
3644
- }
3645
- if (file.blob) {
3646
- return file.blob.arrayBuffer();
3647
- }
3648
- if (typeof file.source === "string") {
3649
- const response = await fetch(file.source);
3650
- if (!response.ok) {
3651
- throw new Error(`Failed to fetch file: ${response.status}`);
3652
- }
3653
- return response.arrayBuffer();
3654
- }
3655
- throw new Error("Unsupported file source.");
3656
- }
3657
- async function readTextFile(file) {
3658
- const decode = (buffer) => decodeTextBuffer(buffer);
3659
- if (typeof file.source === "string") {
3660
- const response = await fetch(file.source);
3661
- if (!response.ok) {
3662
- throw new Error(`Failed to fetch text file: ${response.status}`);
3663
- }
3664
- return decode(await response.arrayBuffer());
3665
- }
3666
- if (file.blob) {
3667
- return decode(await file.blob.arrayBuffer());
3668
- }
3669
- if (file.source instanceof ArrayBuffer) {
3670
- return decode(file.source);
3671
- }
3672
- return String(file.source);
3673
- }
3674
- function createPanel(className = "") {
3675
- const panel = document.createElement("div");
3676
- panel.className = `ofv-panel ${className}`.trim();
3677
- return panel;
3678
- }
3679
- function createSection(title) {
3680
- const section = document.createElement("section");
3681
- section.className = "ofv-section";
3682
- const heading = document.createElement("h3");
3683
- heading.textContent = title;
3684
- section.append(heading);
3685
- return section;
3686
- }
3687
- function appendMeta(parent, label, value) {
3688
- const row = document.createElement("div");
3689
- row.className = "ofv-meta-row";
3690
- const key = document.createElement("span");
3691
- key.textContent = label;
3692
- const content = document.createElement("strong");
3693
- content.textContent = String(value);
3694
- row.append(key, content);
3695
- parent.append(row);
3696
- }
3697
- function decodeTextBuffer(buffer) {
3698
- return decodeTextBytes(new Uint8Array(buffer));
3699
- }
3700
- function decodeTextBytes(bytes) {
3701
- if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
3702
- return new TextDecoder("utf-8").decode(bytes.subarray(3));
3703
- }
3704
- if (bytes.length >= 2) {
3705
- if (bytes[0] === 255 && bytes[1] === 254) {
3706
- return new TextDecoder("utf-16le").decode(bytes.subarray(2));
3707
- }
3708
- if (bytes[0] === 254 && bytes[1] === 255) {
3709
- return new TextDecoder("utf-16be").decode(bytes.subarray(2));
3710
- }
3711
- }
3712
- try {
3713
- return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
3714
- } catch {
3715
- return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
3716
- }
3717
- }
3718
- function decodeWithFallback(bytes, encoding) {
3719
- try {
3720
- return new TextDecoder(encoding).decode(bytes);
3721
- } catch {
3722
- return void 0;
3723
- }
3724
- }
3725
- function resolveFormat(file, mimeMap) {
3726
- return file.extension || mimeMap[file.mimeType] || "";
3727
- }
3728
-
3729
3779
  // src/plugins/text.ts
3730
3780
  var langMap = {
3731
3781
  js: "javascript",
@@ -4194,7 +4244,7 @@ function textPlugin() {
4194
4244
  };
4195
4245
  }
4196
4246
  function createTextZoomController(target, cssVariable, ctx) {
4197
- let zoom = 1;
4247
+ let zoom = getInitialZoom(ctx, 0.5, 3);
4198
4248
  const apply = () => {
4199
4249
  const normalized = Math.round(zoom * 100) / 100;
4200
4250
  target.style.setProperty(cssVariable, String(normalized));
@@ -4504,6 +4554,7 @@ function pdfPlugin(options = {}) {
4504
4554
  viewport: ctx.viewport,
4505
4555
  size: ctx.size,
4506
4556
  fit: ctx.options.fit,
4557
+ zoom: ctx.options.zoom,
4507
4558
  toolbar: ctx.toolbar,
4508
4559
  encryptedTitle: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4509
4560
  encryptedMessage: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
@@ -4609,7 +4660,7 @@ async function renderPdfDocumentPreview(options) {
4609
4660
  const pageStates = [];
4610
4661
  let observer = null;
4611
4662
  let currentSize = options.size;
4612
- let zoomFactor = 1;
4663
+ let zoomFactor = getInitialZoom({ options: { zoom: options.zoom ?? 1 } }, 0.25, 4);
4613
4664
  let rotation = 0;
4614
4665
  const updateSummary = () => {
4615
4666
  renderPdfSummary(summary, pdfDocument.numPages, pagesMeta, options.fit, zoomFactor);
@@ -5934,6 +5985,7 @@ async function renderConvertedOfficePreview(panel, ctx, options, conversionConte
5934
5985
  viewport: panel,
5935
5986
  size: ctx.size,
5936
5987
  fit: ctx.options.fit,
5988
+ zoom: ctx.options.zoom,
5937
5989
  toolbar: ctx.toolbar,
5938
5990
  title: "Office \u9AD8\u4FDD\u771F\u8F6C\u6362\u9884\u89C8",
5939
5991
  fallbackTitle: "Office \u8F6C\u6362\u540E\u7684 PDF \u65E0\u6CD5\u9884\u89C8",
@@ -5997,7 +6049,7 @@ function createOfficeZoomController(panel, ctx) {
5997
6049
  if (!canZoom) {
5998
6050
  return void 0;
5999
6051
  }
6000
- let zoom = 1;
6052
+ let zoom = getInitialZoom(ctx, 0.5, 3);
6001
6053
  const apply = () => {
6002
6054
  panel.style.setProperty("--ofv-office-zoom", String(zoom));
6003
6055
  panel.dispatchEvent(new CustomEvent("ofv-office-zoom"));
@@ -7129,6 +7181,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
7129
7181
  return;
7130
7182
  }
7131
7183
  const chartPreviews = await readWorkbookCharts(arrayBuffer).catch(() => []);
7184
+ const workbookImages = await readWorkbookSheetImages(arrayBuffer).catch(() => /* @__PURE__ */ new Map());
7132
7185
  const tabs = document.createElement("div");
7133
7186
  tabs.className = "ofv-tabs";
7134
7187
  tabs.setAttribute("role", "tablist");
@@ -7147,6 +7200,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
7147
7200
  const heading = document.createElement("h3");
7148
7201
  heading.textContent = sheetName;
7149
7202
  const sheet = workbook.Sheets[sheetName];
7203
+ const sheetImages = workbookImages.get(sheetName) || [];
7150
7204
  const range = trimWorkbookSheetRange(sheet, xlsx.utils.decode_range(sheet["!ref"] || "A1:A1"), xlsx.utils.decode_cell);
7151
7205
  const rowCount = range.e.r - range.s.r + 1;
7152
7206
  const columnCount = range.e.c - range.s.c + 1;
@@ -7172,7 +7226,8 @@ async function renderSheet(panel, arrayBuffer, extension) {
7172
7226
  xlsx.utils.encode_cell,
7173
7227
  xlsx.utils.format_cell,
7174
7228
  columnSizing,
7175
- renderTableWindow
7229
+ renderTableWindow,
7230
+ sheetImages
7176
7231
  )
7177
7232
  );
7178
7233
  windowControls?.update();
@@ -7242,6 +7297,143 @@ function renderSheetFallback(panel, extension, detail) {
7242
7297
  section.append(title, meta, support);
7243
7298
  panel.append(section);
7244
7299
  }
7300
+ async function readWorkbookSheetImages(arrayBuffer) {
7301
+ const zip = await JSZip3.loadAsync(arrayBuffer);
7302
+ const fileNames = Object.keys(zip.files);
7303
+ if (!fileNames.some((name) => /^xl\/drawings\/.+\.xml$/i.test(name)) || !fileNames.some((name) => /^xl\/media\//i.test(name))) {
7304
+ return /* @__PURE__ */ new Map();
7305
+ }
7306
+ const workbookXml = await zip.file("xl/workbook.xml")?.async("text");
7307
+ if (!workbookXml || typeof DOMParser === "undefined") {
7308
+ return /* @__PURE__ */ new Map();
7309
+ }
7310
+ const workbookDoc = parseOfficeXml(workbookXml);
7311
+ if (!workbookDoc) {
7312
+ return /* @__PURE__ */ new Map();
7313
+ }
7314
+ const workbookRels = await readOfficeRelationships(zip, "xl/workbook.xml");
7315
+ const result = /* @__PURE__ */ new Map();
7316
+ const sheetElements = Array.from(workbookDoc.getElementsByTagName("*")).filter((element) => element.localName === "sheet");
7317
+ for (const sheetElement of sheetElements) {
7318
+ const sheetName = sheetElement.getAttribute("name") || "";
7319
+ const relationshipId = getXmlAttribute3(sheetElement, "id");
7320
+ const sheetRel = workbookRels.find((rel) => rel.id === relationshipId && /\/worksheet$/i.test(rel.type));
7321
+ const sheetPath = resolveOfficeRelationshipTarget("xl/workbook.xml", sheetRel?.target);
7322
+ if (!sheetName || !sheetPath) {
7323
+ continue;
7324
+ }
7325
+ const images = await readWorksheetImages(zip, sheetPath);
7326
+ if (images.length > 0) {
7327
+ result.set(sheetName, images);
7328
+ }
7329
+ }
7330
+ return result;
7331
+ }
7332
+ async function readWorksheetImages(zip, sheetPath) {
7333
+ const sheetXml = await zip.file(sheetPath)?.async("text");
7334
+ const sheetDoc = sheetXml ? parseOfficeXml(sheetXml) : void 0;
7335
+ if (!sheetDoc) {
7336
+ return [];
7337
+ }
7338
+ const sheetRels = await readOfficeRelationships(zip, sheetPath);
7339
+ const drawingIds = Array.from(sheetDoc.getElementsByTagName("*")).filter((element) => element.localName === "drawing").map((element) => getXmlAttribute3(element, "id")).filter((id) => Boolean(id));
7340
+ const images = [];
7341
+ for (const drawingId of drawingIds) {
7342
+ const drawingRel = sheetRels.find((rel) => rel.id === drawingId && /\/drawing$/i.test(rel.type));
7343
+ const drawingPath = resolveOfficeRelationshipTarget(sheetPath, drawingRel?.target);
7344
+ if (drawingPath) {
7345
+ images.push(...await readWorksheetDrawingImages(zip, drawingPath));
7346
+ }
7347
+ }
7348
+ return images;
7349
+ }
7350
+ async function readWorksheetDrawingImages(zip, drawingPath) {
7351
+ const drawingXml = await zip.file(drawingPath)?.async("text");
7352
+ const drawingDoc = drawingXml ? parseOfficeXml(drawingXml) : void 0;
7353
+ if (!drawingDoc) {
7354
+ return [];
7355
+ }
7356
+ const drawingRels = await readOfficeRelationships(zip, drawingPath);
7357
+ const anchors = Array.from(drawingDoc.getElementsByTagName("*")).filter(
7358
+ (element) => element.localName === "twoCellAnchor" || element.localName === "oneCellAnchor"
7359
+ );
7360
+ const images = [];
7361
+ for (const anchor of anchors) {
7362
+ const from = Array.from(anchor.children).find((element) => element.localName === "from");
7363
+ const embedId = findDrawingImageRelationshipId(anchor);
7364
+ const mediaRel = drawingRels.find((rel) => rel.id === embedId && /\/image$/i.test(rel.type));
7365
+ const mediaPath = resolveOfficeRelationshipTarget(drawingPath, mediaRel?.target);
7366
+ const mediaFile = mediaPath ? zip.file(mediaPath) : void 0;
7367
+ if (!from || !mediaPath || !mediaFile) {
7368
+ continue;
7369
+ }
7370
+ const mimeType = mimeTypeFromImagePath(mediaPath);
7371
+ images.push({
7372
+ row: readDrawingMarkerIndex(from, "row"),
7373
+ column: readDrawingMarkerIndex(from, "col"),
7374
+ fileName: mediaPath.split("/").pop() || "image",
7375
+ mimeType,
7376
+ dataUrl: `data:${mimeType};base64,${await mediaFile.async("base64")}`,
7377
+ title: readDrawingImageTitle(anchor)
7378
+ });
7379
+ }
7380
+ return images;
7381
+ }
7382
+ function findDrawingImageRelationshipId(anchor) {
7383
+ for (const element of Array.from(anchor.getElementsByTagName("*"))) {
7384
+ if (element.localName === "blip") {
7385
+ return getXmlAttribute3(element, "embed") || getXmlAttribute3(element, "link") || void 0;
7386
+ }
7387
+ }
7388
+ return void 0;
7389
+ }
7390
+ function readDrawingImageTitle(anchor) {
7391
+ const nonVisualProperties = Array.from(anchor.getElementsByTagName("*")).find((element) => element.localName === "cNvPr");
7392
+ return nonVisualProperties?.getAttribute("descr") || nonVisualProperties?.getAttribute("name") || void 0;
7393
+ }
7394
+ function readDrawingMarkerIndex(marker, localName) {
7395
+ const element = Array.from(marker.children).find((child) => child.localName === localName);
7396
+ const value = Number.parseInt(element?.textContent || "0", 10);
7397
+ return Number.isFinite(value) && value >= 0 ? value : 0;
7398
+ }
7399
+ async function readOfficeRelationships(zip, partPath) {
7400
+ const xml = await zip.file(relationshipPathForPart(partPath))?.async("text");
7401
+ const doc = xml ? parseOfficeXml(xml) : void 0;
7402
+ if (!doc) {
7403
+ return [];
7404
+ }
7405
+ return Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Relationship").map((element) => ({
7406
+ id: element.getAttribute("Id") || "",
7407
+ type: element.getAttribute("Type") || "",
7408
+ target: element.getAttribute("Target") || ""
7409
+ }));
7410
+ }
7411
+ function parseOfficeXml(xml) {
7412
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7413
+ return doc.querySelector("parsererror") ? void 0 : doc;
7414
+ }
7415
+ function resolveOfficeRelationshipTarget(sourcePath, target) {
7416
+ return resolvePptxRelationshipTarget(sourcePath, target);
7417
+ }
7418
+ function mimeTypeFromImagePath(path) {
7419
+ const extension = path.split(".").pop()?.toLowerCase();
7420
+ switch (extension) {
7421
+ case "jpg":
7422
+ case "jpeg":
7423
+ return "image/jpeg";
7424
+ case "gif":
7425
+ return "image/gif";
7426
+ case "webp":
7427
+ return "image/webp";
7428
+ case "bmp":
7429
+ return "image/bmp";
7430
+ case "svg":
7431
+ return "image/svg+xml";
7432
+ case "png":
7433
+ default:
7434
+ return "image/png";
7435
+ }
7436
+ }
7245
7437
  function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
7246
7438
  const section = createSection(title);
7247
7439
  section.classList.add("ofv-encrypted");
@@ -7619,7 +7811,7 @@ function createWindowButton(label, onClick) {
7619
7811
  function maxStart(total, size) {
7620
7812
  return Math.max(0, total - size);
7621
7813
  }
7622
- function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender) {
7814
+ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender, images = []) {
7623
7815
  const table = document.createElement("table");
7624
7816
  table.id = `ofv-sheet-${sheetIndex + 1}`;
7625
7817
  table.className = "ofv-workbook-table";
@@ -7628,6 +7820,7 @@ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell
7628
7820
  const columnStart = range.s.c + viewport.columnStart;
7629
7821
  const rowStart = range.s.r + viewport.rowStart;
7630
7822
  const mergePlan = createSheetMergePlan(sheet["!merges"] || [], rowStart, rowEnd, columnStart, columnEnd);
7823
+ const imagesByCell = groupWorkbookImagesByCell(images);
7631
7824
  const colGroup = document.createElement("colgroup");
7632
7825
  let tableWidth = 0;
7633
7826
  for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex += 1) {
@@ -7682,6 +7875,7 @@ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell
7682
7875
  if (text.includes("\n")) {
7683
7876
  cell.classList.add("ofv-cell-multiline");
7684
7877
  }
7878
+ appendWorkbookCellImages(cell, imagesByCell.get(`${rowIndex}:${columnIndex}`), text);
7685
7879
  appendColumnResizeHandle(cell, columnIndex, columnSizing);
7686
7880
  row.append(cell);
7687
7881
  }
@@ -7764,6 +7958,39 @@ function createSheetMergePlan(merges, rowStart, rowEnd, columnStart, columnEnd)
7764
7958
  }
7765
7959
  return { anchors, covered };
7766
7960
  }
7961
+ function groupWorkbookImagesByCell(images) {
7962
+ const grouped = /* @__PURE__ */ new Map();
7963
+ for (const image of images) {
7964
+ const key = `${image.row}:${image.column}`;
7965
+ const items = grouped.get(key) || [];
7966
+ items.push(image);
7967
+ grouped.set(key, items);
7968
+ }
7969
+ return grouped;
7970
+ }
7971
+ function appendWorkbookCellImages(cell, images, text) {
7972
+ if (!images?.length) {
7973
+ return;
7974
+ }
7975
+ if (isWorkbookImagePlaceholderValue(text)) {
7976
+ cell.textContent = "";
7977
+ cell.removeAttribute("title");
7978
+ }
7979
+ cell.classList.add("ofv-cell-image");
7980
+ for (const image of images) {
7981
+ const figure = document.createElement("figure");
7982
+ figure.className = "ofv-workbook-image";
7983
+ const element = document.createElement("img");
7984
+ element.src = image.dataUrl;
7985
+ element.alt = image.title || image.fileName || "Excel embedded image";
7986
+ element.loading = "lazy";
7987
+ figure.append(element);
7988
+ cell.append(figure);
7989
+ }
7990
+ }
7991
+ function isWorkbookImagePlaceholderValue(text) {
7992
+ return /^#(?:VALUE|NAME|REF|N\/A|NULL|NUM|DIV\/0)!?$/i.test(text.trim());
7993
+ }
7767
7994
  function getSheetColumnWidth(column) {
7768
7995
  if (column?.hidden) {
7769
7996
  return 0;
@@ -8086,6 +8313,69 @@ function normalizePptxLayout(container) {
8086
8313
  for (const slide of slideCanvases) {
8087
8314
  slide.style.backgroundColor = "#FFFFFF";
8088
8315
  }
8316
+ normalizePptxMirroredText(container);
8317
+ }
8318
+ function normalizePptxMirroredText(container) {
8319
+ const mirroredContainers = Array.from(container.querySelectorAll("div")).filter((element) => {
8320
+ const text = element.textContent?.trim();
8321
+ if (!text || element.children.length === 0) {
8322
+ return false;
8323
+ }
8324
+ const styleTransform = element.style.transform;
8325
+ return hasPptxMirrorTransform(styleTransform, "x") || hasPptxMirrorTransform(styleTransform, "y");
8326
+ });
8327
+ for (const element of mirroredContainers) {
8328
+ const flipX = hasPptxMirrorTransform(element.style.transform, "x");
8329
+ const flipY = hasPptxMirrorTransform(element.style.transform, "y");
8330
+ const targets = findPptxMirroredTextTargets(element);
8331
+ for (const target of targets) {
8332
+ counterMirrorPptxTextTarget(target, flipX, flipY);
8333
+ }
8334
+ }
8335
+ }
8336
+ function findPptxMirroredTextTargets(element) {
8337
+ const children = Array.from(element.children).filter((child) => child instanceof HTMLElement);
8338
+ const absoluteTextChildren = children.filter((child) => Boolean(child.textContent?.trim()) && child.style.position === "absolute");
8339
+ if (absoluteTextChildren.length > 0) {
8340
+ return absoluteTextChildren;
8341
+ }
8342
+ return children.filter((child) => Boolean(child.textContent?.trim()));
8343
+ }
8344
+ function hasPptxMirrorTransform(transform, axis) {
8345
+ if (!transform) {
8346
+ return false;
8347
+ }
8348
+ if (axis === "x" && /scaleX\(\s*-1\s*\)/i.test(transform)) {
8349
+ return true;
8350
+ }
8351
+ if (axis === "y" && /scaleY\(\s*-1\s*\)/i.test(transform)) {
8352
+ return true;
8353
+ }
8354
+ const matrix = transform.match(/matrix\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s]+)/i);
8355
+ if (!matrix) {
8356
+ return false;
8357
+ }
8358
+ const xScale = Number(matrix[1]);
8359
+ const yScale = Number(matrix[4]);
8360
+ return axis === "x" ? xScale < 0 : yScale < 0;
8361
+ }
8362
+ function counterMirrorPptxTextTarget(target, flipX, flipY) {
8363
+ const applied = target.dataset.ofvPptxCounterMirror ?? "";
8364
+ const transforms = [];
8365
+ if (flipX && !applied.includes("x")) {
8366
+ transforms.push("scaleX(-1)");
8367
+ }
8368
+ if (flipY && !applied.includes("y")) {
8369
+ transforms.push("scaleY(-1)");
8370
+ }
8371
+ if (transforms.length === 0) {
8372
+ return;
8373
+ }
8374
+ target.style.transform = `${target.style.transform || ""} ${transforms.join(" ")}`.trim();
8375
+ if (!target.style.transformOrigin) {
8376
+ target.style.transformOrigin = "center center";
8377
+ }
8378
+ target.dataset.ofvPptxCounterMirror = `${applied}${flipX ? "x" : ""}${flipY ? "y" : ""}`;
8089
8379
  }
8090
8380
  function findPptxSlideCanvases(container) {
8091
8381
  const slideWrappers = Array.from(container.querySelectorAll("div[data-slide-index]"));
@@ -18579,7 +18869,7 @@ async function createPostScriptPreview(bytes, url, fileName, size, fit, toolbar)
18579
18869
  preview.append(summary);
18580
18870
  return { element: preview };
18581
18871
  }
18582
- async function createPdfCompatibleAiPreview(bytes, url, fileName, size, fit, toolbar, pdfOffset = 0) {
18872
+ async function createPdfCompatibleAiPreview(bytes, url, fileName, size, fit, toolbar, pdfOffset = 0, zoom = 1) {
18583
18873
  const wrapper = document.createElement("div");
18584
18874
  wrapper.className = "ofv-ai-pdf-preview";
18585
18875
  let pdfUrl = url;
@@ -18594,6 +18884,7 @@ async function createPdfCompatibleAiPreview(bytes, url, fileName, size, fit, too
18594
18884
  viewport: wrapper,
18595
18885
  size,
18596
18886
  fit,
18887
+ zoom,
18597
18888
  toolbar,
18598
18889
  fallbackTitle: "AI PDF \u517C\u5BB9\u9884\u89C8\u5931\u8D25",
18599
18890
  revokeUrlOnDestroy: false