@open-file-viewer/core 0.1.4 → 0.1.5

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
@@ -3275,6 +3275,95 @@ function readAiffExtended(bytes, offset) {
3275
3275
  return mantissa * Math.pow(2, exponent - 16383 - 63);
3276
3276
  }
3277
3277
 
3278
+ // src/plugins/utils.ts
3279
+ async function readArrayBuffer(file) {
3280
+ if (file.source instanceof ArrayBuffer) {
3281
+ return file.source;
3282
+ }
3283
+ if (file.blob) {
3284
+ return file.blob.arrayBuffer();
3285
+ }
3286
+ if (typeof file.source === "string") {
3287
+ const response = await fetch(file.source);
3288
+ if (!response.ok) {
3289
+ throw new Error(`Failed to fetch file: ${response.status}`);
3290
+ }
3291
+ return response.arrayBuffer();
3292
+ }
3293
+ throw new Error("Unsupported file source.");
3294
+ }
3295
+ async function readTextFile(file) {
3296
+ const decode = (buffer) => decodeTextBuffer(buffer);
3297
+ if (typeof file.source === "string") {
3298
+ const response = await fetch(file.source);
3299
+ if (!response.ok) {
3300
+ throw new Error(`Failed to fetch text file: ${response.status}`);
3301
+ }
3302
+ return decode(await response.arrayBuffer());
3303
+ }
3304
+ if (file.blob) {
3305
+ return decode(await file.blob.arrayBuffer());
3306
+ }
3307
+ if (file.source instanceof ArrayBuffer) {
3308
+ return decode(file.source);
3309
+ }
3310
+ return String(file.source);
3311
+ }
3312
+ function createPanel(className = "") {
3313
+ const panel = document.createElement("div");
3314
+ panel.className = `ofv-panel ${className}`.trim();
3315
+ return panel;
3316
+ }
3317
+ function createSection(title) {
3318
+ const section = document.createElement("section");
3319
+ section.className = "ofv-section";
3320
+ const heading = document.createElement("h3");
3321
+ heading.textContent = title;
3322
+ section.append(heading);
3323
+ return section;
3324
+ }
3325
+ function appendMeta(parent, label, value) {
3326
+ const row = document.createElement("div");
3327
+ row.className = "ofv-meta-row";
3328
+ const key = document.createElement("span");
3329
+ key.textContent = label;
3330
+ const content = document.createElement("strong");
3331
+ content.textContent = String(value);
3332
+ row.append(key, content);
3333
+ parent.append(row);
3334
+ }
3335
+ function decodeTextBuffer(buffer) {
3336
+ return decodeTextBytes(new Uint8Array(buffer));
3337
+ }
3338
+ function decodeTextBytes(bytes) {
3339
+ if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
3340
+ return new TextDecoder("utf-8").decode(bytes.subarray(3));
3341
+ }
3342
+ if (bytes.length >= 2) {
3343
+ if (bytes[0] === 255 && bytes[1] === 254) {
3344
+ return new TextDecoder("utf-16le").decode(bytes.subarray(2));
3345
+ }
3346
+ if (bytes[0] === 254 && bytes[1] === 255) {
3347
+ return new TextDecoder("utf-16be").decode(bytes.subarray(2));
3348
+ }
3349
+ }
3350
+ try {
3351
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
3352
+ } catch {
3353
+ return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
3354
+ }
3355
+ }
3356
+ function decodeWithFallback(bytes, encoding) {
3357
+ try {
3358
+ return new TextDecoder(encoding).decode(bytes);
3359
+ } catch {
3360
+ return void 0;
3361
+ }
3362
+ }
3363
+ function resolveFormat(file, mimeMap) {
3364
+ return file.extension || mimeMap[file.mimeType] || "";
3365
+ }
3366
+
3278
3367
  // src/plugins/text.ts
3279
3368
  var langMap = {
3280
3369
  js: "javascript",
@@ -3931,13 +4020,13 @@ async function readText(source) {
3931
4020
  if (!response.ok) {
3932
4021
  throw new Error(`Failed to fetch text file: ${response.status}`);
3933
4022
  }
3934
- return response.text();
4023
+ return decodeTextBuffer(await response.arrayBuffer());
3935
4024
  }
3936
4025
  if (source instanceof Blob) {
3937
- return source.text();
4026
+ return decodeTextBuffer(await source.arrayBuffer());
3938
4027
  }
3939
4028
  if (source instanceof ArrayBuffer) {
3940
- return new TextDecoder().decode(source);
4029
+ return decodeTextBuffer(source);
3941
4030
  }
3942
4031
  return String(source);
3943
4032
  }
@@ -4294,68 +4383,6 @@ function configurePdfWorker(pdf, workerSrc) {
4294
4383
  // src/plugins/epub.ts
4295
4384
  import JSZip from "jszip";
4296
4385
  import DOMPurify from "dompurify";
4297
-
4298
- // src/plugins/utils.ts
4299
- async function readArrayBuffer(file) {
4300
- if (file.source instanceof ArrayBuffer) {
4301
- return file.source;
4302
- }
4303
- if (file.blob) {
4304
- return file.blob.arrayBuffer();
4305
- }
4306
- if (typeof file.source === "string") {
4307
- const response = await fetch(file.source);
4308
- if (!response.ok) {
4309
- throw new Error(`Failed to fetch file: ${response.status}`);
4310
- }
4311
- return response.arrayBuffer();
4312
- }
4313
- throw new Error("Unsupported file source.");
4314
- }
4315
- async function readTextFile(file) {
4316
- if (typeof file.source === "string") {
4317
- const response = await fetch(file.source);
4318
- if (!response.ok) {
4319
- throw new Error(`Failed to fetch text file: ${response.status}`);
4320
- }
4321
- return response.text();
4322
- }
4323
- if (file.blob) {
4324
- return file.blob.text();
4325
- }
4326
- if (file.source instanceof ArrayBuffer) {
4327
- return new TextDecoder().decode(file.source);
4328
- }
4329
- return String(file.source);
4330
- }
4331
- function createPanel(className = "") {
4332
- const panel = document.createElement("div");
4333
- panel.className = `ofv-panel ${className}`.trim();
4334
- return panel;
4335
- }
4336
- function createSection(title) {
4337
- const section = document.createElement("section");
4338
- section.className = "ofv-section";
4339
- const heading = document.createElement("h3");
4340
- heading.textContent = title;
4341
- section.append(heading);
4342
- return section;
4343
- }
4344
- function appendMeta(parent, label, value) {
4345
- const row = document.createElement("div");
4346
- row.className = "ofv-meta-row";
4347
- const key = document.createElement("span");
4348
- key.textContent = label;
4349
- const content = document.createElement("strong");
4350
- content.textContent = String(value);
4351
- row.append(key, content);
4352
- parent.append(row);
4353
- }
4354
- function resolveFormat(file, mimeMap) {
4355
- return file.extension || mimeMap[file.mimeType] || "";
4356
- }
4357
-
4358
- // src/plugins/epub.ts
4359
4386
  var epubMimeTypes = /* @__PURE__ */ new Set(["application/epub+zip", "application/x-epub+zip"]);
4360
4387
  function epubPlugin() {
4361
4388
  return {
@@ -4975,8 +5002,15 @@ function officePlugin() {
4975
5002
  ctx.viewport.append(panel);
4976
5003
  const extension = resolveFormat(ctx.file, officeMimeFormatMap);
4977
5004
  const arrayBuffer = await readArrayBuffer(ctx.file);
5005
+ const packageFormat = shouldSniffPackagedOffice(extension) ? await detectPackagedOfficeFormat(arrayBuffer) : void 0;
4978
5006
  let disposeDocxFit;
4979
- if (fileIsDocx(extension)) {
5007
+ if (packageFormat === "docx" && !fileIsDocx(extension)) {
5008
+ disposeDocxFit = await renderDocx(panel, arrayBuffer);
5009
+ } else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
5010
+ await renderSheet(panel, arrayBuffer, "xlsx");
5011
+ } else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
5012
+ await renderPptx(panel, arrayBuffer);
5013
+ } else if (fileIsDocx(extension)) {
4980
5014
  disposeDocxFit = await renderDocx(panel, arrayBuffer);
4981
5015
  } else if (extension === "rtf") {
4982
5016
  renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
@@ -5012,12 +5046,32 @@ function officePlugin() {
5012
5046
  function fileIsDocx(extension) {
5013
5047
  return extension === "docx" || extension === "docm" || extension === "dotx" || extension === "dotm";
5014
5048
  }
5049
+ function shouldSniffPackagedOffice(extension) {
5050
+ return isLegacyOfficeBinary(extension) || packagedOfficeCandidates.has(extension) || extension === "";
5051
+ }
5052
+ async function detectPackagedOfficeFormat(arrayBuffer) {
5053
+ try {
5054
+ const zip = await JSZip3.loadAsync(arrayBuffer);
5055
+ const entries = Object.values(zip.files).filter((entry) => !entry.dir);
5056
+ const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
5057
+ if (hasEntry("word/document.xml")) {
5058
+ return "docx";
5059
+ }
5060
+ if (hasEntry("xl/workbook.xml")) {
5061
+ return "xlsx";
5062
+ }
5063
+ if (hasEntry("ppt/presentation.xml")) {
5064
+ return "pptx";
5065
+ }
5066
+ } catch {
5067
+ return void 0;
5068
+ }
5069
+ return void 0;
5070
+ }
5015
5071
  async function renderDocx(panel, arrayBuffer) {
5016
- const section = createSection("Word \u6587\u6863");
5017
5072
  const content = document.createElement("div");
5018
5073
  content.className = "ofv-docx-document";
5019
- section.append(content);
5020
- panel.append(section);
5074
+ panel.append(content);
5021
5075
  try {
5022
5076
  const docxPreview = await import("docx-preview");
5023
5077
  await docxPreview.renderAsync(arrayBuffer, content, content, {
@@ -5238,7 +5292,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5238
5292
  const xlsx = await import("xlsx");
5239
5293
  let workbook;
5240
5294
  try {
5241
- workbook = xlsx.read(arrayBuffer, { type: "array" });
5295
+ workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), { type: "string", FS: extension === "tsv" ? " " : "," }) : xlsx.read(arrayBuffer, { type: "array" });
5242
5296
  } catch (error) {
5243
5297
  if (isLegacyOfficeBinary(extension)) {
5244
5298
  renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
@@ -5871,10 +5925,30 @@ async function renderPptx(panel, arrayBuffer) {
5871
5925
  try {
5872
5926
  const { PptxViewer } = await import("@aiden0z/pptx-renderer");
5873
5927
  await PptxViewer.open(arrayBuffer, container);
5928
+ normalizePptxLayout(container);
5874
5929
  } catch {
5875
5930
  container.textContent = "PPTX \u6E32\u67D3\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u635F\u574F\u3002";
5876
5931
  }
5877
5932
  }
5933
+ function normalizePptxLayout(container) {
5934
+ const slideCanvases = findPptxSlideCanvases(container);
5935
+ for (const slide of slideCanvases) {
5936
+ slide.style.backgroundColor = "#FFFFFF";
5937
+ }
5938
+ }
5939
+ function findPptxSlideCanvases(container) {
5940
+ const slideWrappers = Array.from(container.querySelectorAll("div[data-slide-index]"));
5941
+ const candidates = slideWrappers.flatMap(
5942
+ (wrapper) => Array.from(wrapper.querySelectorAll("div")).filter(isPptxSlideCanvas)
5943
+ );
5944
+ if (candidates.length > 0) {
5945
+ return Array.from(new Set(candidates));
5946
+ }
5947
+ return Array.from(container.querySelectorAll("div")).filter(isPptxSlideCanvas);
5948
+ }
5949
+ function isPptxSlideCanvas(element) {
5950
+ return element.style.position === "relative" && parseCssPixelValue(element.style.width) > 0 && parseCssPixelValue(element.style.height) > 0;
5951
+ }
5878
5952
  async function renderOdp(panel, arrayBuffer) {
5879
5953
  const zip = await JSZip3.loadAsync(arrayBuffer);
5880
5954
  const content = zip.file("content.xml");
@@ -5902,34 +5976,28 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
5902
5976
  const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
5903
5977
  const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
5904
5978
  if (hasEntry("word/document.xml")) {
5905
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Word \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 DOCX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5906
5979
  await renderDocx(panel, arrayBuffer);
5907
5980
  return true;
5908
5981
  }
5909
5982
  if (hasEntry("xl/workbook.xml")) {
5910
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Workbook \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 XLSX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5911
5983
  await renderSheet(panel, arrayBuffer, extension);
5912
5984
  return true;
5913
5985
  }
5914
5986
  if (hasEntry("ppt/presentation.xml")) {
5915
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 PPTX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5916
5987
  await renderPptx(panel, arrayBuffer);
5917
5988
  return true;
5918
5989
  }
5919
5990
  if (contentXml) {
5920
5991
  const xml = await contentXml.async("text");
5921
5992
  if (/<office:spreadsheet\b|<table:table\b/i.test(xml)) {
5922
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Spreadsheet \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODS \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5923
5993
  renderParsedSheets(panel, parseFlatOds(xml), `${extension.toUpperCase()} \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002`);
5924
5994
  return true;
5925
5995
  }
5926
5996
  if (/<office:presentation\b|<draw:page\b/i.test(xml)) {
5927
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODP \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5928
5997
  renderOpenDocumentPresentation(panel, `${extension.toUpperCase()} \u6F14\u793A\u6587\u7A3F`, xml, await extractZipImages(zip, /^Pictures\//));
5929
5998
  return true;
5930
5999
  }
5931
6000
  if (/<office:text\b|<text:p\b/i.test(xml)) {
5932
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Text \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODT \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5933
6001
  renderOpenDocumentXml(panel, `${extension.toUpperCase()} \u6587\u6863`, xml);
5934
6002
  return true;
5935
6003
  }
@@ -5955,14 +6023,6 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
5955
6023
  }
5956
6024
  return false;
5957
6025
  }
5958
- function renderOfficePackageNotice(panel, extension, message) {
5959
- const section = createSection("\u517C\u5BB9\u5305\u8BC6\u522B");
5960
- const note = document.createElement("p");
5961
- note.className = "ofv-office-package-note";
5962
- note.textContent = `.${extension} ${message}`;
5963
- section.append(note);
5964
- panel.append(section);
5965
- }
5966
6026
  function renderOfficePackageStructure(panel, extension, entries, message, metadata) {
5967
6027
  const section = createSection("Office \u5305\u7ED3\u6784\u9884\u89C8");
5968
6028
  const note = document.createElement("p");
@@ -6610,7 +6670,7 @@ function rtfToText(rtf) {
6610
6670
  return rtf.replace(/\\'[0-9a-fA-F]{2}/g, "").replace(/\\par[d]?/g, "\n").replace(/\\tab/g, " ").replace(/\\[a-zA-Z]+\d* ?/g, "").replace(/[{}]/g, "").replace(/\n{3,}/g, "\n\n").trim();
6611
6671
  }
6612
6672
  async function readTextFromBuffer(arrayBuffer) {
6613
- return new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
6673
+ return decodeTextBuffer(arrayBuffer);
6614
6674
  }
6615
6675
  function mimeTypeFromPath(path) {
6616
6676
  const extension = path.split(".").pop()?.toLowerCase();
@@ -6677,36 +6737,72 @@ function ofdPlugin() {
6677
6737
  }
6678
6738
  };
6679
6739
  }
6680
- const images = await readOfdImages(entries);
6681
- const pages = await readOfdPages(entries, images);
6682
- const section = createSection("OFD \u57FA\u7840\u9884\u89C8");
6683
- const note = document.createElement("p");
6684
- note.textContent = pages.length > 0 ? "\u5F53\u524D\u7248\u672C\u63D0\u53D6 OFD \u9875\u9762\u6587\u672C\u3001\u8DEF\u5F84\u3001\u76F4\u7EBF\u548C\u56FE\u7247\u5BF9\u8C61\uFF0C\u5E76\u6309 Boundary \u5750\u6807\u751F\u6210\u8F7B\u91CF SVG \u7248\u5F0F\u9884\u89C8\u3002\u590D\u6742\u5B57\u4F53\u3001\u7B7E\u7AE0\u548C\u989C\u8272\u7A7A\u95F4\u53EF\u540E\u7EED\u63A5\u5165\u4E13\u7528 OFD \u6E32\u67D3\u5668\u3002" : "\u5F53\u524D\u7248\u672C\u63D0\u53D6 OFD \u5305\u5185 XML \u6587\u672C\u548C\u6587\u4EF6\u7ED3\u6784\u3002\u7248\u5F0F\u7EA7\u6E32\u67D3\u53EF\u5728\u540E\u7EED\u63A5\u5165\u4E13\u7528 OFD \u6E32\u67D3\u5668\u3002";
6685
- section.append(note);
6686
- section.append(createOfdSummary(entries, pages, images));
6740
+ const context = await readOfdContext(entries);
6741
+ const pages = await readOfdPages(entries, context);
6742
+ let zoom = 1;
6743
+ let rotation = 0;
6744
+ const applyZoom = () => {
6745
+ panel.style.setProperty("--ofv-ofd-zoom", formatOfdCssNumber(zoom));
6746
+ ctx.toolbar?.setZoom(zoom);
6747
+ };
6748
+ const applyRotation = () => {
6749
+ const normalizedRotation = (rotation % 360 + 360) % 360;
6750
+ panel.style.setProperty("--ofv-ofd-rotation", `${normalizedRotation}deg`);
6751
+ panel.classList.toggle("is-ofd-rotated-sideways", normalizedRotation === 90 || normalizedRotation === 270);
6752
+ };
6687
6753
  if (pages.length > 0) {
6688
6754
  const pagesWrap = document.createElement("div");
6689
6755
  pagesWrap.className = "ofv-ofd-pages";
6690
6756
  for (const page of pages) {
6691
6757
  pagesWrap.append(renderOfdPage(page));
6692
6758
  }
6693
- section.append(pagesWrap);
6694
- }
6695
- const content = document.createElement("pre");
6696
- content.className = "ofv-text-block";
6697
- content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
6698
- section.append(content);
6699
- const list = createSection(`\u6587\u4EF6\u7ED3\u6784 ${entries.length}`);
6700
- const ul = document.createElement("ul");
6701
- for (const entry of entries.slice(0, 200)) {
6702
- const li = document.createElement("li");
6703
- li.textContent = entry.name;
6704
- ul.append(li);
6705
- }
6706
- list.append(ul);
6707
- panel.append(section, list);
6759
+ panel.append(pagesWrap);
6760
+ applyZoom();
6761
+ applyRotation();
6762
+ }
6763
+ if (pages.length === 0) {
6764
+ const content = document.createElement("pre");
6765
+ content.className = "ofv-text-block";
6766
+ content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
6767
+ panel.append(content);
6768
+ }
6708
6769
  return {
6770
+ canCommand(command) {
6771
+ return pages.length > 0 && (command === "zoom-in" || command === "zoom-out" || command === "zoom-reset" || command === "rotate-right" || command === "rotate-left");
6772
+ },
6773
+ command(command) {
6774
+ if (pages.length === 0) {
6775
+ return false;
6776
+ }
6777
+ if (command === "zoom-in") {
6778
+ zoom = Math.min(4, zoom + 0.15);
6779
+ applyZoom();
6780
+ return true;
6781
+ }
6782
+ if (command === "zoom-out") {
6783
+ zoom = Math.max(0.25, zoom - 0.15);
6784
+ applyZoom();
6785
+ return true;
6786
+ }
6787
+ if (command === "zoom-reset") {
6788
+ zoom = 1;
6789
+ applyZoom();
6790
+ return true;
6791
+ }
6792
+ if (command === "rotate-right") {
6793
+ rotation += 90;
6794
+ applyRotation();
6795
+ return true;
6796
+ }
6797
+ if (command === "rotate-left") {
6798
+ rotation -= 90;
6799
+ applyRotation();
6800
+ return true;
6801
+ }
6802
+ return false;
6803
+ },
6709
6804
  destroy() {
6805
+ ctx.toolbar?.setZoom(void 0);
6710
6806
  panel.remove();
6711
6807
  revokeObjectUrl(url, isExternal);
6712
6808
  }
@@ -6714,76 +6810,71 @@ function ofdPlugin() {
6714
6810
  }
6715
6811
  };
6716
6812
  }
6717
- function createOfdSummary(entries, pages, images) {
6718
- const summary = document.createElement("div");
6719
- summary.className = "ofv-ofd-summary";
6720
- const xmlEntries = entries.filter((entry) => entry.name.endsWith(".xml")).length;
6721
- const textCount = pages.reduce((count, page) => count + page.texts.length, 0);
6722
- const pathCount = pages.reduce((count, page) => count + page.paths.length, 0);
6723
- const lineCount = pages.reduce((count, page) => count + page.lines.length, 0);
6724
- const imageCount = pages.reduce((count, page) => count + page.images.length, 0);
6725
- const textLength = pages.reduce((count, page) => count + page.texts.reduce((inner, item) => inner + item.text.length, 0), 0);
6726
- appendOfdSummary(summary, "\u6587\u4EF6", String(entries.length));
6727
- appendOfdSummary(summary, "XML", String(xmlEntries));
6728
- appendOfdSummary(summary, "\u9875\u9762", String(pages.length));
6729
- appendOfdSummary(summary, "\u6587\u672C", String(textCount));
6730
- appendOfdSummary(summary, "\u8DEF\u5F84", String(pathCount));
6731
- appendOfdSummary(summary, "\u7EBF\u6761", String(lineCount));
6732
- appendOfdSummary(summary, "\u56FE\u7247\u5BF9\u8C61", String(imageCount));
6733
- appendOfdSummary(summary, "\u56FE\u7247\u8D44\u6E90", String(uniqueOfdImageResources(images)));
6734
- if (textLength > 0) {
6735
- appendOfdSummary(summary, "\u6587\u5B57\u957F\u5EA6", String(textLength));
6736
- }
6737
- const sizes = formatOfdPageSizes(pages);
6738
- if (sizes) {
6739
- appendOfdSummary(summary, "\u9875\u9762\u5C3A\u5BF8", sizes);
6740
- }
6741
- return summary;
6742
- }
6743
- function appendOfdSummary(parent, label, value) {
6744
- const item = document.createElement("span");
6745
- const key = document.createElement("span");
6746
- key.textContent = label;
6747
- const content = document.createElement("strong");
6748
- content.textContent = value;
6749
- item.append(key, content);
6750
- parent.append(item);
6751
- }
6752
- function uniqueOfdImageResources(images) {
6753
- return new Set(images.values()).size;
6754
- }
6755
- function formatOfdPageSizes(pages) {
6756
- const counts = /* @__PURE__ */ new Map();
6757
- for (const page of pages) {
6758
- const key = `${Math.round(page.width)} x ${Math.round(page.height)}`;
6759
- counts.set(key, (counts.get(key) || 0) + 1);
6760
- }
6761
- return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([size, count]) => count > 1 ? `${size} (${count})` : size).join(", ");
6762
- }
6763
- async function readOfdPages(entries, images) {
6813
+ async function readOfdPages(entries, context) {
6764
6814
  const pages = [];
6765
6815
  const pageEntries = entries.filter((entry) => /(^|\/)Pages\/Page_[^/]+\/Content\.xml$/i.test(entry.name) || /(^|\/)Page_[^/]+\/Content\.xml$/i.test(entry.name)).slice(0, 80);
6766
6816
  for (const entry of pageEntries) {
6767
6817
  const xml = await entry.async("text");
6768
- const page = parseOfdPage(entry.name, xml, images);
6818
+ const templates = await readPageTemplates(xml, context, entries);
6819
+ const page = parseOfdPage(entry.name, xml, context.images, context.fonts, templates, context.pageSize);
6769
6820
  if (page.texts.length > 0 || page.paths.length > 0 || page.lines.length > 0 || page.images.length > 0) {
6770
6821
  pages.push(page);
6771
6822
  }
6772
6823
  }
6773
6824
  return pages;
6774
6825
  }
6775
- function parseOfdPage(name, xml, images) {
6826
+ async function readPageTemplates(xml, context, entries) {
6827
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
6828
+ if (doc.querySelector("parsererror")) {
6829
+ return [];
6830
+ }
6831
+ const templateIds = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Template").map((element) => getOfdAttribute(element, "TemplateID") || getOfdAttribute(element, "ID")).filter((id) => Boolean(id));
6832
+ const templates = [];
6833
+ for (const id of templateIds) {
6834
+ const path = context.templates.get(id);
6835
+ const entry = path ? findOfdEntry(entries, path) : void 0;
6836
+ if (entry) {
6837
+ templates.push(await entry.async("text"));
6838
+ }
6839
+ }
6840
+ return templates;
6841
+ }
6842
+ function parseOfdPage(name, xml, images, fonts, templateXmls = [], defaultPageSize) {
6776
6843
  const doc = new DOMParser().parseFromString(xml, "application/xml");
6777
6844
  if (doc.querySelector("parsererror")) {
6778
6845
  return { name, width: 210, height: 297, texts: [], paths: [], lines: [], images: [] };
6779
6846
  }
6780
- const pageSize = parseOfdPageSize(doc);
6847
+ const pageSize = parseOfdPageSize(doc, defaultPageSize);
6848
+ const templatePages = templateXmls.map((templateXml) => {
6849
+ const templateDoc = new DOMParser().parseFromString(templateXml, "application/xml");
6850
+ return templateDoc.querySelector("parsererror") ? emptyOfdPageContent() : parseOfdPageContent(templateDoc, images, fonts);
6851
+ });
6852
+ const pageContent = parseOfdPageContent(doc, images, fonts);
6853
+ const texts = [...templatePages.flatMap((page) => page.texts), ...pageContent.texts];
6854
+ const paths = [...templatePages.flatMap((page) => page.paths), ...pageContent.paths];
6855
+ const lines = [...templatePages.flatMap((page) => page.lines), ...pageContent.lines];
6856
+ const imageObjects = [...templatePages.flatMap((page) => page.images), ...pageContent.images];
6857
+ if (pageSize.explicit) {
6858
+ return { name, width: pageSize.width, height: pageSize.height, texts, paths, lines, images: imageObjects };
6859
+ }
6860
+ const bounds = createOfdBounds(texts, paths, lines, imageObjects);
6861
+ const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
6862
+ const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
6863
+ return { name, width, height, texts, paths, lines, images: imageObjects };
6864
+ }
6865
+ function parseOfdPageContent(doc, images, fonts) {
6781
6866
  const textObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TextObject");
6782
- const texts = textObjects.flatMap((element) => parseOfdTextObject(element));
6867
+ const texts = textObjects.flatMap((element) => parseOfdTextObject(element, fonts));
6783
6868
  const paths = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "PathObject").flatMap((element) => parseOfdPathObject(element));
6784
6869
  const lines = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "LineObject").flatMap((element) => parseOfdLineObject(element));
6785
6870
  const imageObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ImageObject").flatMap((element) => parseOfdImageObject(element, images));
6786
- const bounds = [
6871
+ return { texts, paths, lines, images: imageObjects };
6872
+ }
6873
+ function emptyOfdPageContent() {
6874
+ return { texts: [], paths: [], lines: [], images: [] };
6875
+ }
6876
+ function createOfdBounds(texts, paths, lines, imageObjects) {
6877
+ return [
6787
6878
  ...texts.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
6788
6879
  ...paths.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
6789
6880
  ...lines.map((item) => ({
@@ -6794,38 +6885,63 @@ function parseOfdPage(name, xml, images) {
6794
6885
  })),
6795
6886
  ...imageObjects.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height }))
6796
6887
  ];
6797
- const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
6798
- const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
6799
- return { name, width, height, texts, paths, lines, images: imageObjects };
6800
6888
  }
6801
- function parseOfdTextObject(element) {
6889
+ function parseOfdTextObject(element, fonts) {
6802
6890
  const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
6803
6891
  const size = finiteNumber2(getOfdAttribute(element, "Size"), Math.max(4, boundary.height || 5));
6804
6892
  const color = parseOfdColor(element, "#111827");
6805
6893
  const weight = finiteNumber2(getOfdAttribute(element, "Weight"), 400) >= 600 ? "700" : "400";
6806
- const letterSpacing = getOfdAttribute(element, "DeltaX") ? 0.5 : void 0;
6894
+ const fontFamily = fontStackForOfdFont(fonts.get(getOfdAttribute(element, "Font") || ""));
6895
+ const objectLetterSpacing = getOfdAttribute(element, "DeltaX") ? 0.5 : void 0;
6807
6896
  const textCodes = Array.from(element.getElementsByTagName("*")).filter((child) => child.localName === "TextCode");
6808
6897
  if (textCodes.length === 0) {
6809
6898
  return [];
6810
6899
  }
6811
- return textCodes.map((code) => {
6900
+ return textCodes.flatMap((code) => {
6812
6901
  const x = boundary.x + finiteNumber2(getOfdAttribute(code, "X"), 0);
6813
6902
  const y = boundary.y + finiteNumber2(getOfdAttribute(code, "Y"), 0);
6814
- return {
6815
- text: code.textContent?.trim() || "",
6816
- x,
6817
- y,
6818
- width: boundary.width,
6819
- height: boundary.height,
6820
- size,
6821
- color,
6822
- weight,
6823
- letterSpacing
6824
- };
6903
+ const text = code.textContent?.trim() || "";
6904
+ const deltaX = parseOfdDeltaX(getOfdAttribute(code, "DeltaX"));
6905
+ const align = deltaX ? "start" : inferOfdTextAlign(text, boundary);
6906
+ const deltaY = getOfdAttribute(code, "DeltaY");
6907
+ if (deltaY && text.length > 1) {
6908
+ const step = parseOfdDeltaStep(deltaY, size);
6909
+ return Array.from(text).map((char, index) => ({
6910
+ text: char,
6911
+ x,
6912
+ y: y + index * step,
6913
+ width: boundary.width,
6914
+ height: boundary.height,
6915
+ size,
6916
+ color,
6917
+ weight,
6918
+ fontFamily,
6919
+ letterSpacing: objectLetterSpacing,
6920
+ vertical: true,
6921
+ align
6922
+ }));
6923
+ }
6924
+ return [
6925
+ {
6926
+ text,
6927
+ x,
6928
+ y,
6929
+ width: boundary.width,
6930
+ height: boundary.height,
6931
+ size,
6932
+ color,
6933
+ weight,
6934
+ fontFamily,
6935
+ letterSpacing: deltaX ? void 0 : objectLetterSpacing,
6936
+ deltaX,
6937
+ align
6938
+ }
6939
+ ];
6825
6940
  }).filter((item) => item.text);
6826
6941
  }
6827
6942
  function parseOfdPathObject(element) {
6828
6943
  const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
6944
+ const ctm = parseOfdCtm(getOfdAttribute(element, "CTM"));
6829
6945
  const commands = Array.from(element.getElementsByTagName("*")).filter(
6830
6946
  (child) => child.localName === "AbbreviatedData" || child.localName === "PathData"
6831
6947
  );
@@ -6840,9 +6956,10 @@ function parseOfdPathObject(element) {
6840
6956
  y: boundary.y,
6841
6957
  width: boundary.width,
6842
6958
  height: boundary.height,
6843
- stroke: parseOfdColor(element, "#334155", "StrokeColor"),
6959
+ stroke: parseOfdColor(element, "#111827", "StrokeColor"),
6844
6960
  fill: parseOfdFill(element),
6845
- strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
6961
+ strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1),
6962
+ transform: createOfdPathTransform(boundary.x, boundary.y, ctm)
6846
6963
  }
6847
6964
  ];
6848
6965
  }
@@ -6859,7 +6976,7 @@ function parseOfdLineObject(element) {
6859
6976
  y1: boundary.y + start.y,
6860
6977
  x2: boundary.x + end.x,
6861
6978
  y2: boundary.y + end.y,
6862
- stroke: parseOfdColor(element, "#334155"),
6979
+ stroke: parseOfdColor(element, "#111827"),
6863
6980
  strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
6864
6981
  }
6865
6982
  ];
@@ -6881,6 +6998,8 @@ function parseOfdImageObject(element, images) {
6881
6998
  function renderOfdPage(page) {
6882
6999
  const figure = document.createElement("figure");
6883
7000
  figure.className = "ofv-ofd-page";
7001
+ figure.style.setProperty("--ofv-ofd-page-width", `${formatOfdCssNumber(page.width)}mm`);
7002
+ figure.style.setProperty("--ofv-ofd-page-height", `${formatOfdCssNumber(page.height)}mm`);
6884
7003
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
6885
7004
  svg.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`);
6886
7005
  svg.setAttribute("role", "img");
@@ -6917,7 +7036,7 @@ function renderOfdPage(page) {
6917
7036
  for (const item of page.paths) {
6918
7037
  const path = document.createElementNS(svg.namespaceURI, "path");
6919
7038
  path.setAttribute("d", item.d);
6920
- path.setAttribute("transform", `translate(${item.x} ${item.y})`);
7039
+ path.setAttribute("transform", item.transform);
6921
7040
  path.setAttribute("fill", item.fill);
6922
7041
  path.setAttribute("stroke", item.stroke);
6923
7042
  path.setAttribute("stroke-width", String(item.strokeWidth));
@@ -6936,22 +7055,63 @@ function renderOfdPage(page) {
6936
7055
  }
6937
7056
  for (const item of page.texts) {
6938
7057
  const text = document.createElementNS(svg.namespaceURI, "text");
6939
- text.setAttribute("x", String(item.x));
6940
- text.setAttribute("y", String(item.y + item.size));
7058
+ text.setAttribute("x", String(item.align === "end" ? item.x + item.width : item.x));
7059
+ text.setAttribute("y", String(item.y));
6941
7060
  text.setAttribute("font-size", String(item.size));
6942
7061
  text.setAttribute("fill", item.color);
6943
7062
  text.setAttribute("font-weight", item.weight);
7063
+ text.setAttribute("font-family", item.fontFamily);
6944
7064
  if (item.letterSpacing !== void 0) {
6945
7065
  text.setAttribute("letter-spacing", String(item.letterSpacing));
6946
7066
  }
6947
- text.textContent = item.text;
7067
+ if (item.deltaX && item.deltaX.length > 0 && item.align !== "end") {
7068
+ const chars = Array.from(item.text);
7069
+ let x = item.x;
7070
+ for (let index = 0; index < chars.length; index += 1) {
7071
+ const span = document.createElementNS(svg.namespaceURI, "tspan");
7072
+ span.setAttribute("x", String(x));
7073
+ span.setAttribute("y", String(item.y));
7074
+ if (index < chars.length - 1) {
7075
+ x += item.deltaX[Math.min(index, item.deltaX.length - 1)] || item.size;
7076
+ }
7077
+ span.textContent = chars[index];
7078
+ text.append(span);
7079
+ }
7080
+ } else {
7081
+ if (item.align === "end") {
7082
+ text.setAttribute("text-anchor", "end");
7083
+ }
7084
+ text.textContent = item.text;
7085
+ }
6948
7086
  svg.append(text);
6949
7087
  }
6950
- const caption = document.createElement("figcaption");
6951
- caption.textContent = `${page.name} \xB7 ${page.texts.length} text \xB7 ${page.paths.length} path \xB7 ${page.lines.length} line \xB7 ${page.images.length} image`;
6952
- figure.append(svg, caption);
7088
+ figure.append(svg);
6953
7089
  return figure;
6954
7090
  }
7091
+ async function readOfdContext(entries) {
7092
+ const images = await readOfdImages(entries);
7093
+ const fonts = await readOfdFonts(entries);
7094
+ const { templates, pageSize } = await readOfdDocumentInfo(entries);
7095
+ return { images, templates, fonts, pageSize };
7096
+ }
7097
+ async function readOfdFonts(entries) {
7098
+ const fonts = /* @__PURE__ */ new Map();
7099
+ for (const entry of entries.filter((item) => /(?:^|\/)(?:DocumentRes|PublicRes)\.xml$/i.test(item.name))) {
7100
+ const xml = await entry.async("text");
7101
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7102
+ if (doc.querySelector("parsererror")) {
7103
+ continue;
7104
+ }
7105
+ for (const font of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Font")) {
7106
+ const id = getOfdAttribute(font, "ID");
7107
+ const name = getOfdAttribute(font, "FontName") || getOfdAttribute(font, "FamilyName");
7108
+ if (id && name) {
7109
+ fonts.set(id, name.trim());
7110
+ }
7111
+ }
7112
+ }
7113
+ return fonts;
7114
+ }
6955
7115
  async function readOfdImages(entries) {
6956
7116
  const images = /* @__PURE__ */ new Map();
6957
7117
  for (const entry of entries.filter((item) => /\.(?:png|jpe?g|gif|bmp|webp)$/i.test(item.name)).slice(0, 80)) {
@@ -6966,17 +7126,69 @@ async function readOfdImages(entries) {
6966
7126
  images.set(entry.name, href);
6967
7127
  images.set(entry.name.split("/").pop() || entry.name, href);
6968
7128
  }
7129
+ for (const entry of entries.filter((item) => /(?:^|\/)(?:DocumentRes|PublicRes)\.xml$/i.test(item.name))) {
7130
+ const xml = await entry.async("text");
7131
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7132
+ if (doc.querySelector("parsererror")) {
7133
+ continue;
7134
+ }
7135
+ const baseLoc = getOfdAttribute(doc.documentElement, "BaseLoc") || "";
7136
+ const resourceDir = joinOfdPath(directoryName2(entry.name), baseLoc);
7137
+ for (const media of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "MultiMedia")) {
7138
+ const id = getOfdAttribute(media, "ID");
7139
+ const mediaFile = findOfdChild(media, "MediaFile")?.textContent?.trim();
7140
+ if (!id || !mediaFile) {
7141
+ continue;
7142
+ }
7143
+ const imageEntry = findOfdEntry(entries, joinOfdPath(resourceDir, mediaFile)) || findOfdEntry(entries, mediaFile);
7144
+ const href = imageEntry ? images.get(imageEntry.name) || images.get(imageEntry.name.split("/").pop() || imageEntry.name) : void 0;
7145
+ if (href) {
7146
+ images.set(id, href);
7147
+ }
7148
+ }
7149
+ }
6969
7150
  return images;
6970
7151
  }
6971
- function parseOfdPageSize(doc) {
7152
+ async function readOfdDocumentInfo(entries) {
7153
+ const templates = /* @__PURE__ */ new Map();
7154
+ let pageSize;
7155
+ for (const entry of entries.filter((item) => /(?:^|\/)Document\.xml$/i.test(item.name))) {
7156
+ const xml = await entry.async("text");
7157
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7158
+ if (doc.querySelector("parsererror")) {
7159
+ continue;
7160
+ }
7161
+ const documentDir = directoryName2(entry.name);
7162
+ const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PageArea")?.getElementsByTagName("*");
7163
+ const pageAreaBox = physicalBox ? Array.from(physicalBox).find((element) => element.localName === "PhysicalBox") : void 0;
7164
+ if (pageAreaBox?.textContent) {
7165
+ const box = parseBoundary(pageAreaBox.textContent);
7166
+ if (box.width > 0 && box.height > 0) {
7167
+ pageSize = { width: box.width, height: box.height };
7168
+ }
7169
+ }
7170
+ for (const template of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TemplatePage")) {
7171
+ const id = getOfdAttribute(template, "ID");
7172
+ const baseLoc = getOfdAttribute(template, "BaseLoc");
7173
+ if (id && baseLoc) {
7174
+ templates.set(id, joinOfdPath(documentDir, baseLoc));
7175
+ }
7176
+ }
7177
+ }
7178
+ return { templates, pageSize };
7179
+ }
7180
+ function parseOfdPageSize(doc, defaultPageSize) {
7181
+ if (defaultPageSize) {
7182
+ return { ...defaultPageSize, explicit: true };
7183
+ }
6972
7184
  const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PhysicalBox");
6973
7185
  if (physicalBox?.textContent) {
6974
7186
  const box = parseBoundary(physicalBox.textContent);
6975
7187
  if (box.width > 0 && box.height > 0) {
6976
- return { width: box.width, height: box.height };
7188
+ return { width: box.width, height: box.height, explicit: true };
6977
7189
  }
6978
7190
  }
6979
- return { width: 210, height: 297 };
7191
+ return { width: 210, height: 297, explicit: false };
6980
7192
  }
6981
7193
  function parseOfdColor(element, fallback, preferredLocalName = "FillColor") {
6982
7194
  const colorElement = findOfdChild(element, preferredLocalName) || findOfdChild(element, "StrokeColor") || findOfdChild(element, "FillColor");
@@ -7007,6 +7219,101 @@ function parsePoint(value, fallback) {
7007
7219
  function normalizeOfdPathData(value) {
7008
7220
  return value.replace(/\bM\s+/gi, "M ").replace(/\bL\s+/gi, "L ").replace(/\bC\s+/gi, "C ").replace(/\bQ\s+/gi, "Q ").replace(/\bA\s+/gi, "A ").replace(/\bB\s+/gi, "C ").replace(/\bZ\b/gi, "Z").replace(/\s+/g, " ").trim();
7009
7221
  }
7222
+ function parseOfdCtm(value) {
7223
+ const parts = (value || "").trim().split(/\s+/).map((part) => Number(part));
7224
+ if (parts.length !== 6 || parts.some((part) => !Number.isFinite(part))) {
7225
+ return void 0;
7226
+ }
7227
+ return parts;
7228
+ }
7229
+ function createOfdPathTransform(x, y, ctm) {
7230
+ if (!ctm) {
7231
+ return `translate(${x} ${y})`;
7232
+ }
7233
+ const [a, b, c, d, e, f] = ctm;
7234
+ return `translate(${x} ${y}) matrix(${a} ${b} ${c} ${d} ${e} ${f})`;
7235
+ }
7236
+ function parseOfdDeltaStep(value, fallback) {
7237
+ const numbers = value.match(/-?\d+(?:\.\d+)?/g)?.map((part) => Number(part)).filter((part) => Number.isFinite(part)) || [];
7238
+ return numbers.length > 0 ? numbers[numbers.length - 1] : fallback;
7239
+ }
7240
+ function parseOfdDeltaX(value) {
7241
+ if (!value) {
7242
+ return void 0;
7243
+ }
7244
+ const parts = value.match(/[a-z]+|-?\d+(?:\.\d+)?/gi) || [];
7245
+ const deltas = [];
7246
+ for (let index = 0; index < parts.length; index += 1) {
7247
+ const token = parts[index];
7248
+ if (/^g$/i.test(token)) {
7249
+ const count = Number(parts[index + 1]);
7250
+ const step = Number(parts[index + 2]);
7251
+ if (Number.isFinite(count) && Number.isFinite(step)) {
7252
+ deltas.push(...Array.from({ length: Math.max(0, Math.floor(count)) }, () => step));
7253
+ }
7254
+ index += 2;
7255
+ continue;
7256
+ }
7257
+ const numeric = Number(token);
7258
+ if (Number.isFinite(numeric)) {
7259
+ deltas.push(numeric);
7260
+ }
7261
+ }
7262
+ return deltas.length > 0 ? deltas : void 0;
7263
+ }
7264
+ function fontStackForOfdFont(fontName) {
7265
+ const normalized = (fontName || "").trim().toLowerCase();
7266
+ if (normalized.includes("courier")) {
7267
+ return '"Courier New", Courier, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
7268
+ }
7269
+ if (normalized.includes("kaiti") || normalized.includes("kai") || normalized.includes("\u6977")) {
7270
+ return '"STKaiti", "Kaiti SC", "KaiTi", "\u6977\u4F53", serif';
7271
+ }
7272
+ if (normalized.includes("simsun") || normalized.includes("simsong") || normalized.includes("song") || normalized.includes("\u5B8B")) {
7273
+ return '"SimSong", "Songti SC", "STSong", SimSun, "\u5B8B\u4F53", serif';
7274
+ }
7275
+ if (normalized.includes("hei") || normalized.includes("\u9ED1")) {
7276
+ return '"PingFang SC", "Microsoft YaHei", SimHei, sans-serif';
7277
+ }
7278
+ return '"SimSong", "Songti SC", "STSong", SimSun, "Noto Serif CJK SC", serif';
7279
+ }
7280
+ function inferOfdTextAlign(text, boundary) {
7281
+ const normalized = text.trim();
7282
+ if (!/^[¥¥]?\d+(?:\.\d+)?%?$/.test(normalized)) {
7283
+ return "start";
7284
+ }
7285
+ if (boundary.x >= 75 || boundary.width <= 30) {
7286
+ return "end";
7287
+ }
7288
+ return "start";
7289
+ }
7290
+ function findOfdEntry(entries, path) {
7291
+ const normalized = normalizeOfdPath(path);
7292
+ return entries.find((entry) => normalizeOfdPath(entry.name) === normalized || normalizeOfdPath(entry.name).endsWith(`/${normalized}`));
7293
+ }
7294
+ function joinOfdPath(...parts) {
7295
+ const joined = parts.filter(Boolean).join("/");
7296
+ const segments = [];
7297
+ for (const segment of joined.split("/")) {
7298
+ if (!segment || segment === ".") {
7299
+ continue;
7300
+ }
7301
+ if (segment === "..") {
7302
+ segments.pop();
7303
+ continue;
7304
+ }
7305
+ segments.push(segment);
7306
+ }
7307
+ return segments.join("/");
7308
+ }
7309
+ function directoryName2(path) {
7310
+ const normalized = normalizeOfdPath(path);
7311
+ const index = normalized.lastIndexOf("/");
7312
+ return index >= 0 ? normalized.slice(0, index) : "";
7313
+ }
7314
+ function normalizeOfdPath(path) {
7315
+ return path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+/g, "/");
7316
+ }
7010
7317
  function mimeTypeFromPath2(path) {
7011
7318
  const extension = path.split(".").pop()?.toLowerCase();
7012
7319
  const map = {
@@ -7044,6 +7351,9 @@ function finiteNumber2(value, fallback) {
7044
7351
  const parsed = value === null ? Number.NaN : Number(value);
7045
7352
  return Number.isFinite(parsed) ? parsed : fallback;
7046
7353
  }
7354
+ function formatOfdCssNumber(value) {
7355
+ return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7356
+ }
7047
7357
  function createOfdFallback(fileName, url, detail) {
7048
7358
  const fallback = document.createElement("div");
7049
7359
  fallback.className = "ofv-fallback";