@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.cjs CHANGED
@@ -3328,6 +3328,95 @@ function readAiffExtended(bytes, offset) {
3328
3328
  return mantissa * Math.pow(2, exponent - 16383 - 63);
3329
3329
  }
3330
3330
 
3331
+ // src/plugins/utils.ts
3332
+ async function readArrayBuffer(file) {
3333
+ if (file.source instanceof ArrayBuffer) {
3334
+ return file.source;
3335
+ }
3336
+ if (file.blob) {
3337
+ return file.blob.arrayBuffer();
3338
+ }
3339
+ if (typeof file.source === "string") {
3340
+ const response = await fetch(file.source);
3341
+ if (!response.ok) {
3342
+ throw new Error(`Failed to fetch file: ${response.status}`);
3343
+ }
3344
+ return response.arrayBuffer();
3345
+ }
3346
+ throw new Error("Unsupported file source.");
3347
+ }
3348
+ async function readTextFile(file) {
3349
+ const decode = (buffer) => decodeTextBuffer(buffer);
3350
+ if (typeof file.source === "string") {
3351
+ const response = await fetch(file.source);
3352
+ if (!response.ok) {
3353
+ throw new Error(`Failed to fetch text file: ${response.status}`);
3354
+ }
3355
+ return decode(await response.arrayBuffer());
3356
+ }
3357
+ if (file.blob) {
3358
+ return decode(await file.blob.arrayBuffer());
3359
+ }
3360
+ if (file.source instanceof ArrayBuffer) {
3361
+ return decode(file.source);
3362
+ }
3363
+ return String(file.source);
3364
+ }
3365
+ function createPanel(className = "") {
3366
+ const panel = document.createElement("div");
3367
+ panel.className = `ofv-panel ${className}`.trim();
3368
+ return panel;
3369
+ }
3370
+ function createSection(title) {
3371
+ const section = document.createElement("section");
3372
+ section.className = "ofv-section";
3373
+ const heading = document.createElement("h3");
3374
+ heading.textContent = title;
3375
+ section.append(heading);
3376
+ return section;
3377
+ }
3378
+ function appendMeta(parent, label, value) {
3379
+ const row = document.createElement("div");
3380
+ row.className = "ofv-meta-row";
3381
+ const key = document.createElement("span");
3382
+ key.textContent = label;
3383
+ const content = document.createElement("strong");
3384
+ content.textContent = String(value);
3385
+ row.append(key, content);
3386
+ parent.append(row);
3387
+ }
3388
+ function decodeTextBuffer(buffer) {
3389
+ return decodeTextBytes(new Uint8Array(buffer));
3390
+ }
3391
+ function decodeTextBytes(bytes) {
3392
+ if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
3393
+ return new TextDecoder("utf-8").decode(bytes.subarray(3));
3394
+ }
3395
+ if (bytes.length >= 2) {
3396
+ if (bytes[0] === 255 && bytes[1] === 254) {
3397
+ return new TextDecoder("utf-16le").decode(bytes.subarray(2));
3398
+ }
3399
+ if (bytes[0] === 254 && bytes[1] === 255) {
3400
+ return new TextDecoder("utf-16be").decode(bytes.subarray(2));
3401
+ }
3402
+ }
3403
+ try {
3404
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
3405
+ } catch {
3406
+ return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
3407
+ }
3408
+ }
3409
+ function decodeWithFallback(bytes, encoding) {
3410
+ try {
3411
+ return new TextDecoder(encoding).decode(bytes);
3412
+ } catch {
3413
+ return void 0;
3414
+ }
3415
+ }
3416
+ function resolveFormat(file, mimeMap) {
3417
+ return file.extension || mimeMap[file.mimeType] || "";
3418
+ }
3419
+
3331
3420
  // src/plugins/text.ts
3332
3421
  var langMap = {
3333
3422
  js: "javascript",
@@ -3984,13 +4073,13 @@ async function readText(source) {
3984
4073
  if (!response.ok) {
3985
4074
  throw new Error(`Failed to fetch text file: ${response.status}`);
3986
4075
  }
3987
- return response.text();
4076
+ return decodeTextBuffer(await response.arrayBuffer());
3988
4077
  }
3989
4078
  if (source instanceof Blob) {
3990
- return source.text();
4079
+ return decodeTextBuffer(await source.arrayBuffer());
3991
4080
  }
3992
4081
  if (source instanceof ArrayBuffer) {
3993
- return new TextDecoder().decode(source);
4082
+ return decodeTextBuffer(source);
3994
4083
  }
3995
4084
  return String(source);
3996
4085
  }
@@ -4347,68 +4436,6 @@ function configurePdfWorker(pdf, workerSrc) {
4347
4436
  // src/plugins/epub.ts
4348
4437
  var import_jszip = __toESM(require("jszip"), 1);
4349
4438
  var import_dompurify = __toESM(require("dompurify"), 1);
4350
-
4351
- // src/plugins/utils.ts
4352
- async function readArrayBuffer(file) {
4353
- if (file.source instanceof ArrayBuffer) {
4354
- return file.source;
4355
- }
4356
- if (file.blob) {
4357
- return file.blob.arrayBuffer();
4358
- }
4359
- if (typeof file.source === "string") {
4360
- const response = await fetch(file.source);
4361
- if (!response.ok) {
4362
- throw new Error(`Failed to fetch file: ${response.status}`);
4363
- }
4364
- return response.arrayBuffer();
4365
- }
4366
- throw new Error("Unsupported file source.");
4367
- }
4368
- async function readTextFile(file) {
4369
- if (typeof file.source === "string") {
4370
- const response = await fetch(file.source);
4371
- if (!response.ok) {
4372
- throw new Error(`Failed to fetch text file: ${response.status}`);
4373
- }
4374
- return response.text();
4375
- }
4376
- if (file.blob) {
4377
- return file.blob.text();
4378
- }
4379
- if (file.source instanceof ArrayBuffer) {
4380
- return new TextDecoder().decode(file.source);
4381
- }
4382
- return String(file.source);
4383
- }
4384
- function createPanel(className = "") {
4385
- const panel = document.createElement("div");
4386
- panel.className = `ofv-panel ${className}`.trim();
4387
- return panel;
4388
- }
4389
- function createSection(title) {
4390
- const section = document.createElement("section");
4391
- section.className = "ofv-section";
4392
- const heading = document.createElement("h3");
4393
- heading.textContent = title;
4394
- section.append(heading);
4395
- return section;
4396
- }
4397
- function appendMeta(parent, label, value) {
4398
- const row = document.createElement("div");
4399
- row.className = "ofv-meta-row";
4400
- const key = document.createElement("span");
4401
- key.textContent = label;
4402
- const content = document.createElement("strong");
4403
- content.textContent = String(value);
4404
- row.append(key, content);
4405
- parent.append(row);
4406
- }
4407
- function resolveFormat(file, mimeMap) {
4408
- return file.extension || mimeMap[file.mimeType] || "";
4409
- }
4410
-
4411
- // src/plugins/epub.ts
4412
4439
  var epubMimeTypes = /* @__PURE__ */ new Set(["application/epub+zip", "application/x-epub+zip"]);
4413
4440
  function epubPlugin() {
4414
4441
  return {
@@ -5028,8 +5055,15 @@ function officePlugin() {
5028
5055
  ctx.viewport.append(panel);
5029
5056
  const extension = resolveFormat(ctx.file, officeMimeFormatMap);
5030
5057
  const arrayBuffer = await readArrayBuffer(ctx.file);
5058
+ const packageFormat = shouldSniffPackagedOffice(extension) ? await detectPackagedOfficeFormat(arrayBuffer) : void 0;
5031
5059
  let disposeDocxFit;
5032
- if (fileIsDocx(extension)) {
5060
+ if (packageFormat === "docx" && !fileIsDocx(extension)) {
5061
+ disposeDocxFit = await renderDocx(panel, arrayBuffer);
5062
+ } else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
5063
+ await renderSheet(panel, arrayBuffer, "xlsx");
5064
+ } else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
5065
+ await renderPptx(panel, arrayBuffer);
5066
+ } else if (fileIsDocx(extension)) {
5033
5067
  disposeDocxFit = await renderDocx(panel, arrayBuffer);
5034
5068
  } else if (extension === "rtf") {
5035
5069
  renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
@@ -5065,12 +5099,32 @@ function officePlugin() {
5065
5099
  function fileIsDocx(extension) {
5066
5100
  return extension === "docx" || extension === "docm" || extension === "dotx" || extension === "dotm";
5067
5101
  }
5102
+ function shouldSniffPackagedOffice(extension) {
5103
+ return isLegacyOfficeBinary(extension) || packagedOfficeCandidates.has(extension) || extension === "";
5104
+ }
5105
+ async function detectPackagedOfficeFormat(arrayBuffer) {
5106
+ try {
5107
+ const zip = await import_jszip3.default.loadAsync(arrayBuffer);
5108
+ const entries = Object.values(zip.files).filter((entry) => !entry.dir);
5109
+ const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
5110
+ if (hasEntry("word/document.xml")) {
5111
+ return "docx";
5112
+ }
5113
+ if (hasEntry("xl/workbook.xml")) {
5114
+ return "xlsx";
5115
+ }
5116
+ if (hasEntry("ppt/presentation.xml")) {
5117
+ return "pptx";
5118
+ }
5119
+ } catch {
5120
+ return void 0;
5121
+ }
5122
+ return void 0;
5123
+ }
5068
5124
  async function renderDocx(panel, arrayBuffer) {
5069
- const section = createSection("Word \u6587\u6863");
5070
5125
  const content = document.createElement("div");
5071
5126
  content.className = "ofv-docx-document";
5072
- section.append(content);
5073
- panel.append(section);
5127
+ panel.append(content);
5074
5128
  try {
5075
5129
  const docxPreview = await import("docx-preview");
5076
5130
  await docxPreview.renderAsync(arrayBuffer, content, content, {
@@ -5291,7 +5345,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5291
5345
  const xlsx = await import("xlsx");
5292
5346
  let workbook;
5293
5347
  try {
5294
- workbook = xlsx.read(arrayBuffer, { type: "array" });
5348
+ workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), { type: "string", FS: extension === "tsv" ? " " : "," }) : xlsx.read(arrayBuffer, { type: "array" });
5295
5349
  } catch (error) {
5296
5350
  if (isLegacyOfficeBinary(extension)) {
5297
5351
  renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
@@ -5924,10 +5978,30 @@ async function renderPptx(panel, arrayBuffer) {
5924
5978
  try {
5925
5979
  const { PptxViewer } = await import("@aiden0z/pptx-renderer");
5926
5980
  await PptxViewer.open(arrayBuffer, container);
5981
+ normalizePptxLayout(container);
5927
5982
  } catch {
5928
5983
  container.textContent = "PPTX \u6E32\u67D3\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u635F\u574F\u3002";
5929
5984
  }
5930
5985
  }
5986
+ function normalizePptxLayout(container) {
5987
+ const slideCanvases = findPptxSlideCanvases(container);
5988
+ for (const slide of slideCanvases) {
5989
+ slide.style.backgroundColor = "#FFFFFF";
5990
+ }
5991
+ }
5992
+ function findPptxSlideCanvases(container) {
5993
+ const slideWrappers = Array.from(container.querySelectorAll("div[data-slide-index]"));
5994
+ const candidates = slideWrappers.flatMap(
5995
+ (wrapper) => Array.from(wrapper.querySelectorAll("div")).filter(isPptxSlideCanvas)
5996
+ );
5997
+ if (candidates.length > 0) {
5998
+ return Array.from(new Set(candidates));
5999
+ }
6000
+ return Array.from(container.querySelectorAll("div")).filter(isPptxSlideCanvas);
6001
+ }
6002
+ function isPptxSlideCanvas(element) {
6003
+ return element.style.position === "relative" && parseCssPixelValue(element.style.width) > 0 && parseCssPixelValue(element.style.height) > 0;
6004
+ }
5931
6005
  async function renderOdp(panel, arrayBuffer) {
5932
6006
  const zip = await import_jszip3.default.loadAsync(arrayBuffer);
5933
6007
  const content = zip.file("content.xml");
@@ -5955,34 +6029,28 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
5955
6029
  const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
5956
6030
  const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
5957
6031
  if (hasEntry("word/document.xml")) {
5958
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Word \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 DOCX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5959
6032
  await renderDocx(panel, arrayBuffer);
5960
6033
  return true;
5961
6034
  }
5962
6035
  if (hasEntry("xl/workbook.xml")) {
5963
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Workbook \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 XLSX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5964
6036
  await renderSheet(panel, arrayBuffer, extension);
5965
6037
  return true;
5966
6038
  }
5967
6039
  if (hasEntry("ppt/presentation.xml")) {
5968
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 PPTX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5969
6040
  await renderPptx(panel, arrayBuffer);
5970
6041
  return true;
5971
6042
  }
5972
6043
  if (contentXml) {
5973
6044
  const xml = await contentXml.async("text");
5974
6045
  if (/<office:spreadsheet\b|<table:table\b/i.test(xml)) {
5975
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Spreadsheet \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODS \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5976
6046
  renderParsedSheets(panel, parseFlatOds(xml), `${extension.toUpperCase()} \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002`);
5977
6047
  return true;
5978
6048
  }
5979
6049
  if (/<office:presentation\b|<draw:page\b/i.test(xml)) {
5980
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODP \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5981
6050
  renderOpenDocumentPresentation(panel, `${extension.toUpperCase()} \u6F14\u793A\u6587\u7A3F`, xml, await extractZipImages(zip, /^Pictures\//));
5982
6051
  return true;
5983
6052
  }
5984
6053
  if (/<office:text\b|<text:p\b/i.test(xml)) {
5985
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Text \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODT \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5986
6054
  renderOpenDocumentXml(panel, `${extension.toUpperCase()} \u6587\u6863`, xml);
5987
6055
  return true;
5988
6056
  }
@@ -6008,14 +6076,6 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
6008
6076
  }
6009
6077
  return false;
6010
6078
  }
6011
- function renderOfficePackageNotice(panel, extension, message) {
6012
- const section = createSection("\u517C\u5BB9\u5305\u8BC6\u522B");
6013
- const note = document.createElement("p");
6014
- note.className = "ofv-office-package-note";
6015
- note.textContent = `.${extension} ${message}`;
6016
- section.append(note);
6017
- panel.append(section);
6018
- }
6019
6079
  function renderOfficePackageStructure(panel, extension, entries, message, metadata) {
6020
6080
  const section = createSection("Office \u5305\u7ED3\u6784\u9884\u89C8");
6021
6081
  const note = document.createElement("p");
@@ -6663,7 +6723,7 @@ function rtfToText(rtf) {
6663
6723
  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();
6664
6724
  }
6665
6725
  async function readTextFromBuffer(arrayBuffer) {
6666
- return new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
6726
+ return decodeTextBuffer(arrayBuffer);
6667
6727
  }
6668
6728
  function mimeTypeFromPath(path) {
6669
6729
  const extension = path.split(".").pop()?.toLowerCase();
@@ -6730,36 +6790,72 @@ function ofdPlugin() {
6730
6790
  }
6731
6791
  };
6732
6792
  }
6733
- const images = await readOfdImages(entries);
6734
- const pages = await readOfdPages(entries, images);
6735
- const section = createSection("OFD \u57FA\u7840\u9884\u89C8");
6736
- const note = document.createElement("p");
6737
- 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";
6738
- section.append(note);
6739
- section.append(createOfdSummary(entries, pages, images));
6793
+ const context = await readOfdContext(entries);
6794
+ const pages = await readOfdPages(entries, context);
6795
+ let zoom = 1;
6796
+ let rotation = 0;
6797
+ const applyZoom = () => {
6798
+ panel.style.setProperty("--ofv-ofd-zoom", formatOfdCssNumber(zoom));
6799
+ ctx.toolbar?.setZoom(zoom);
6800
+ };
6801
+ const applyRotation = () => {
6802
+ const normalizedRotation = (rotation % 360 + 360) % 360;
6803
+ panel.style.setProperty("--ofv-ofd-rotation", `${normalizedRotation}deg`);
6804
+ panel.classList.toggle("is-ofd-rotated-sideways", normalizedRotation === 90 || normalizedRotation === 270);
6805
+ };
6740
6806
  if (pages.length > 0) {
6741
6807
  const pagesWrap = document.createElement("div");
6742
6808
  pagesWrap.className = "ofv-ofd-pages";
6743
6809
  for (const page of pages) {
6744
6810
  pagesWrap.append(renderOfdPage(page));
6745
6811
  }
6746
- section.append(pagesWrap);
6747
- }
6748
- const content = document.createElement("pre");
6749
- content.className = "ofv-text-block";
6750
- content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
6751
- section.append(content);
6752
- const list = createSection(`\u6587\u4EF6\u7ED3\u6784 ${entries.length}`);
6753
- const ul = document.createElement("ul");
6754
- for (const entry of entries.slice(0, 200)) {
6755
- const li = document.createElement("li");
6756
- li.textContent = entry.name;
6757
- ul.append(li);
6758
- }
6759
- list.append(ul);
6760
- panel.append(section, list);
6812
+ panel.append(pagesWrap);
6813
+ applyZoom();
6814
+ applyRotation();
6815
+ }
6816
+ if (pages.length === 0) {
6817
+ const content = document.createElement("pre");
6818
+ content.className = "ofv-text-block";
6819
+ content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
6820
+ panel.append(content);
6821
+ }
6761
6822
  return {
6823
+ canCommand(command) {
6824
+ return pages.length > 0 && (command === "zoom-in" || command === "zoom-out" || command === "zoom-reset" || command === "rotate-right" || command === "rotate-left");
6825
+ },
6826
+ command(command) {
6827
+ if (pages.length === 0) {
6828
+ return false;
6829
+ }
6830
+ if (command === "zoom-in") {
6831
+ zoom = Math.min(4, zoom + 0.15);
6832
+ applyZoom();
6833
+ return true;
6834
+ }
6835
+ if (command === "zoom-out") {
6836
+ zoom = Math.max(0.25, zoom - 0.15);
6837
+ applyZoom();
6838
+ return true;
6839
+ }
6840
+ if (command === "zoom-reset") {
6841
+ zoom = 1;
6842
+ applyZoom();
6843
+ return true;
6844
+ }
6845
+ if (command === "rotate-right") {
6846
+ rotation += 90;
6847
+ applyRotation();
6848
+ return true;
6849
+ }
6850
+ if (command === "rotate-left") {
6851
+ rotation -= 90;
6852
+ applyRotation();
6853
+ return true;
6854
+ }
6855
+ return false;
6856
+ },
6762
6857
  destroy() {
6858
+ ctx.toolbar?.setZoom(void 0);
6763
6859
  panel.remove();
6764
6860
  revokeObjectUrl(url, isExternal);
6765
6861
  }
@@ -6767,76 +6863,71 @@ function ofdPlugin() {
6767
6863
  }
6768
6864
  };
6769
6865
  }
6770
- function createOfdSummary(entries, pages, images) {
6771
- const summary = document.createElement("div");
6772
- summary.className = "ofv-ofd-summary";
6773
- const xmlEntries = entries.filter((entry) => entry.name.endsWith(".xml")).length;
6774
- const textCount = pages.reduce((count, page) => count + page.texts.length, 0);
6775
- const pathCount = pages.reduce((count, page) => count + page.paths.length, 0);
6776
- const lineCount = pages.reduce((count, page) => count + page.lines.length, 0);
6777
- const imageCount = pages.reduce((count, page) => count + page.images.length, 0);
6778
- const textLength = pages.reduce((count, page) => count + page.texts.reduce((inner, item) => inner + item.text.length, 0), 0);
6779
- appendOfdSummary(summary, "\u6587\u4EF6", String(entries.length));
6780
- appendOfdSummary(summary, "XML", String(xmlEntries));
6781
- appendOfdSummary(summary, "\u9875\u9762", String(pages.length));
6782
- appendOfdSummary(summary, "\u6587\u672C", String(textCount));
6783
- appendOfdSummary(summary, "\u8DEF\u5F84", String(pathCount));
6784
- appendOfdSummary(summary, "\u7EBF\u6761", String(lineCount));
6785
- appendOfdSummary(summary, "\u56FE\u7247\u5BF9\u8C61", String(imageCount));
6786
- appendOfdSummary(summary, "\u56FE\u7247\u8D44\u6E90", String(uniqueOfdImageResources(images)));
6787
- if (textLength > 0) {
6788
- appendOfdSummary(summary, "\u6587\u5B57\u957F\u5EA6", String(textLength));
6789
- }
6790
- const sizes = formatOfdPageSizes(pages);
6791
- if (sizes) {
6792
- appendOfdSummary(summary, "\u9875\u9762\u5C3A\u5BF8", sizes);
6793
- }
6794
- return summary;
6795
- }
6796
- function appendOfdSummary(parent, label, value) {
6797
- const item = document.createElement("span");
6798
- const key = document.createElement("span");
6799
- key.textContent = label;
6800
- const content = document.createElement("strong");
6801
- content.textContent = value;
6802
- item.append(key, content);
6803
- parent.append(item);
6804
- }
6805
- function uniqueOfdImageResources(images) {
6806
- return new Set(images.values()).size;
6807
- }
6808
- function formatOfdPageSizes(pages) {
6809
- const counts = /* @__PURE__ */ new Map();
6810
- for (const page of pages) {
6811
- const key = `${Math.round(page.width)} x ${Math.round(page.height)}`;
6812
- counts.set(key, (counts.get(key) || 0) + 1);
6813
- }
6814
- return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([size, count]) => count > 1 ? `${size} (${count})` : size).join(", ");
6815
- }
6816
- async function readOfdPages(entries, images) {
6866
+ async function readOfdPages(entries, context) {
6817
6867
  const pages = [];
6818
6868
  const pageEntries = entries.filter((entry) => /(^|\/)Pages\/Page_[^/]+\/Content\.xml$/i.test(entry.name) || /(^|\/)Page_[^/]+\/Content\.xml$/i.test(entry.name)).slice(0, 80);
6819
6869
  for (const entry of pageEntries) {
6820
6870
  const xml = await entry.async("text");
6821
- const page = parseOfdPage(entry.name, xml, images);
6871
+ const templates = await readPageTemplates(xml, context, entries);
6872
+ const page = parseOfdPage(entry.name, xml, context.images, context.fonts, templates, context.pageSize);
6822
6873
  if (page.texts.length > 0 || page.paths.length > 0 || page.lines.length > 0 || page.images.length > 0) {
6823
6874
  pages.push(page);
6824
6875
  }
6825
6876
  }
6826
6877
  return pages;
6827
6878
  }
6828
- function parseOfdPage(name, xml, images) {
6879
+ async function readPageTemplates(xml, context, entries) {
6880
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
6881
+ if (doc.querySelector("parsererror")) {
6882
+ return [];
6883
+ }
6884
+ const templateIds = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Template").map((element) => getOfdAttribute(element, "TemplateID") || getOfdAttribute(element, "ID")).filter((id) => Boolean(id));
6885
+ const templates = [];
6886
+ for (const id of templateIds) {
6887
+ const path = context.templates.get(id);
6888
+ const entry = path ? findOfdEntry(entries, path) : void 0;
6889
+ if (entry) {
6890
+ templates.push(await entry.async("text"));
6891
+ }
6892
+ }
6893
+ return templates;
6894
+ }
6895
+ function parseOfdPage(name, xml, images, fonts, templateXmls = [], defaultPageSize) {
6829
6896
  const doc = new DOMParser().parseFromString(xml, "application/xml");
6830
6897
  if (doc.querySelector("parsererror")) {
6831
6898
  return { name, width: 210, height: 297, texts: [], paths: [], lines: [], images: [] };
6832
6899
  }
6833
- const pageSize = parseOfdPageSize(doc);
6900
+ const pageSize = parseOfdPageSize(doc, defaultPageSize);
6901
+ const templatePages = templateXmls.map((templateXml) => {
6902
+ const templateDoc = new DOMParser().parseFromString(templateXml, "application/xml");
6903
+ return templateDoc.querySelector("parsererror") ? emptyOfdPageContent() : parseOfdPageContent(templateDoc, images, fonts);
6904
+ });
6905
+ const pageContent = parseOfdPageContent(doc, images, fonts);
6906
+ const texts = [...templatePages.flatMap((page) => page.texts), ...pageContent.texts];
6907
+ const paths = [...templatePages.flatMap((page) => page.paths), ...pageContent.paths];
6908
+ const lines = [...templatePages.flatMap((page) => page.lines), ...pageContent.lines];
6909
+ const imageObjects = [...templatePages.flatMap((page) => page.images), ...pageContent.images];
6910
+ if (pageSize.explicit) {
6911
+ return { name, width: pageSize.width, height: pageSize.height, texts, paths, lines, images: imageObjects };
6912
+ }
6913
+ const bounds = createOfdBounds(texts, paths, lines, imageObjects);
6914
+ const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
6915
+ const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
6916
+ return { name, width, height, texts, paths, lines, images: imageObjects };
6917
+ }
6918
+ function parseOfdPageContent(doc, images, fonts) {
6834
6919
  const textObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TextObject");
6835
- const texts = textObjects.flatMap((element) => parseOfdTextObject(element));
6920
+ const texts = textObjects.flatMap((element) => parseOfdTextObject(element, fonts));
6836
6921
  const paths = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "PathObject").flatMap((element) => parseOfdPathObject(element));
6837
6922
  const lines = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "LineObject").flatMap((element) => parseOfdLineObject(element));
6838
6923
  const imageObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ImageObject").flatMap((element) => parseOfdImageObject(element, images));
6839
- const bounds = [
6924
+ return { texts, paths, lines, images: imageObjects };
6925
+ }
6926
+ function emptyOfdPageContent() {
6927
+ return { texts: [], paths: [], lines: [], images: [] };
6928
+ }
6929
+ function createOfdBounds(texts, paths, lines, imageObjects) {
6930
+ return [
6840
6931
  ...texts.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
6841
6932
  ...paths.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
6842
6933
  ...lines.map((item) => ({
@@ -6847,38 +6938,63 @@ function parseOfdPage(name, xml, images) {
6847
6938
  })),
6848
6939
  ...imageObjects.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height }))
6849
6940
  ];
6850
- const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
6851
- const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
6852
- return { name, width, height, texts, paths, lines, images: imageObjects };
6853
6941
  }
6854
- function parseOfdTextObject(element) {
6942
+ function parseOfdTextObject(element, fonts) {
6855
6943
  const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
6856
6944
  const size = finiteNumber2(getOfdAttribute(element, "Size"), Math.max(4, boundary.height || 5));
6857
6945
  const color = parseOfdColor(element, "#111827");
6858
6946
  const weight = finiteNumber2(getOfdAttribute(element, "Weight"), 400) >= 600 ? "700" : "400";
6859
- const letterSpacing = getOfdAttribute(element, "DeltaX") ? 0.5 : void 0;
6947
+ const fontFamily = fontStackForOfdFont(fonts.get(getOfdAttribute(element, "Font") || ""));
6948
+ const objectLetterSpacing = getOfdAttribute(element, "DeltaX") ? 0.5 : void 0;
6860
6949
  const textCodes = Array.from(element.getElementsByTagName("*")).filter((child) => child.localName === "TextCode");
6861
6950
  if (textCodes.length === 0) {
6862
6951
  return [];
6863
6952
  }
6864
- return textCodes.map((code) => {
6953
+ return textCodes.flatMap((code) => {
6865
6954
  const x = boundary.x + finiteNumber2(getOfdAttribute(code, "X"), 0);
6866
6955
  const y = boundary.y + finiteNumber2(getOfdAttribute(code, "Y"), 0);
6867
- return {
6868
- text: code.textContent?.trim() || "",
6869
- x,
6870
- y,
6871
- width: boundary.width,
6872
- height: boundary.height,
6873
- size,
6874
- color,
6875
- weight,
6876
- letterSpacing
6877
- };
6956
+ const text = code.textContent?.trim() || "";
6957
+ const deltaX = parseOfdDeltaX(getOfdAttribute(code, "DeltaX"));
6958
+ const align = deltaX ? "start" : inferOfdTextAlign(text, boundary);
6959
+ const deltaY = getOfdAttribute(code, "DeltaY");
6960
+ if (deltaY && text.length > 1) {
6961
+ const step = parseOfdDeltaStep(deltaY, size);
6962
+ return Array.from(text).map((char, index) => ({
6963
+ text: char,
6964
+ x,
6965
+ y: y + index * step,
6966
+ width: boundary.width,
6967
+ height: boundary.height,
6968
+ size,
6969
+ color,
6970
+ weight,
6971
+ fontFamily,
6972
+ letterSpacing: objectLetterSpacing,
6973
+ vertical: true,
6974
+ align
6975
+ }));
6976
+ }
6977
+ return [
6978
+ {
6979
+ text,
6980
+ x,
6981
+ y,
6982
+ width: boundary.width,
6983
+ height: boundary.height,
6984
+ size,
6985
+ color,
6986
+ weight,
6987
+ fontFamily,
6988
+ letterSpacing: deltaX ? void 0 : objectLetterSpacing,
6989
+ deltaX,
6990
+ align
6991
+ }
6992
+ ];
6878
6993
  }).filter((item) => item.text);
6879
6994
  }
6880
6995
  function parseOfdPathObject(element) {
6881
6996
  const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
6997
+ const ctm = parseOfdCtm(getOfdAttribute(element, "CTM"));
6882
6998
  const commands = Array.from(element.getElementsByTagName("*")).filter(
6883
6999
  (child) => child.localName === "AbbreviatedData" || child.localName === "PathData"
6884
7000
  );
@@ -6893,9 +7009,10 @@ function parseOfdPathObject(element) {
6893
7009
  y: boundary.y,
6894
7010
  width: boundary.width,
6895
7011
  height: boundary.height,
6896
- stroke: parseOfdColor(element, "#334155", "StrokeColor"),
7012
+ stroke: parseOfdColor(element, "#111827", "StrokeColor"),
6897
7013
  fill: parseOfdFill(element),
6898
- strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
7014
+ strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1),
7015
+ transform: createOfdPathTransform(boundary.x, boundary.y, ctm)
6899
7016
  }
6900
7017
  ];
6901
7018
  }
@@ -6912,7 +7029,7 @@ function parseOfdLineObject(element) {
6912
7029
  y1: boundary.y + start.y,
6913
7030
  x2: boundary.x + end.x,
6914
7031
  y2: boundary.y + end.y,
6915
- stroke: parseOfdColor(element, "#334155"),
7032
+ stroke: parseOfdColor(element, "#111827"),
6916
7033
  strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
6917
7034
  }
6918
7035
  ];
@@ -6934,6 +7051,8 @@ function parseOfdImageObject(element, images) {
6934
7051
  function renderOfdPage(page) {
6935
7052
  const figure = document.createElement("figure");
6936
7053
  figure.className = "ofv-ofd-page";
7054
+ figure.style.setProperty("--ofv-ofd-page-width", `${formatOfdCssNumber(page.width)}mm`);
7055
+ figure.style.setProperty("--ofv-ofd-page-height", `${formatOfdCssNumber(page.height)}mm`);
6937
7056
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
6938
7057
  svg.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`);
6939
7058
  svg.setAttribute("role", "img");
@@ -6970,7 +7089,7 @@ function renderOfdPage(page) {
6970
7089
  for (const item of page.paths) {
6971
7090
  const path = document.createElementNS(svg.namespaceURI, "path");
6972
7091
  path.setAttribute("d", item.d);
6973
- path.setAttribute("transform", `translate(${item.x} ${item.y})`);
7092
+ path.setAttribute("transform", item.transform);
6974
7093
  path.setAttribute("fill", item.fill);
6975
7094
  path.setAttribute("stroke", item.stroke);
6976
7095
  path.setAttribute("stroke-width", String(item.strokeWidth));
@@ -6989,22 +7108,63 @@ function renderOfdPage(page) {
6989
7108
  }
6990
7109
  for (const item of page.texts) {
6991
7110
  const text = document.createElementNS(svg.namespaceURI, "text");
6992
- text.setAttribute("x", String(item.x));
6993
- text.setAttribute("y", String(item.y + item.size));
7111
+ text.setAttribute("x", String(item.align === "end" ? item.x + item.width : item.x));
7112
+ text.setAttribute("y", String(item.y));
6994
7113
  text.setAttribute("font-size", String(item.size));
6995
7114
  text.setAttribute("fill", item.color);
6996
7115
  text.setAttribute("font-weight", item.weight);
7116
+ text.setAttribute("font-family", item.fontFamily);
6997
7117
  if (item.letterSpacing !== void 0) {
6998
7118
  text.setAttribute("letter-spacing", String(item.letterSpacing));
6999
7119
  }
7000
- text.textContent = item.text;
7120
+ if (item.deltaX && item.deltaX.length > 0 && item.align !== "end") {
7121
+ const chars = Array.from(item.text);
7122
+ let x = item.x;
7123
+ for (let index = 0; index < chars.length; index += 1) {
7124
+ const span = document.createElementNS(svg.namespaceURI, "tspan");
7125
+ span.setAttribute("x", String(x));
7126
+ span.setAttribute("y", String(item.y));
7127
+ if (index < chars.length - 1) {
7128
+ x += item.deltaX[Math.min(index, item.deltaX.length - 1)] || item.size;
7129
+ }
7130
+ span.textContent = chars[index];
7131
+ text.append(span);
7132
+ }
7133
+ } else {
7134
+ if (item.align === "end") {
7135
+ text.setAttribute("text-anchor", "end");
7136
+ }
7137
+ text.textContent = item.text;
7138
+ }
7001
7139
  svg.append(text);
7002
7140
  }
7003
- const caption = document.createElement("figcaption");
7004
- caption.textContent = `${page.name} \xB7 ${page.texts.length} text \xB7 ${page.paths.length} path \xB7 ${page.lines.length} line \xB7 ${page.images.length} image`;
7005
- figure.append(svg, caption);
7141
+ figure.append(svg);
7006
7142
  return figure;
7007
7143
  }
7144
+ async function readOfdContext(entries) {
7145
+ const images = await readOfdImages(entries);
7146
+ const fonts = await readOfdFonts(entries);
7147
+ const { templates, pageSize } = await readOfdDocumentInfo(entries);
7148
+ return { images, templates, fonts, pageSize };
7149
+ }
7150
+ async function readOfdFonts(entries) {
7151
+ const fonts = /* @__PURE__ */ new Map();
7152
+ for (const entry of entries.filter((item) => /(?:^|\/)(?:DocumentRes|PublicRes)\.xml$/i.test(item.name))) {
7153
+ const xml = await entry.async("text");
7154
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7155
+ if (doc.querySelector("parsererror")) {
7156
+ continue;
7157
+ }
7158
+ for (const font of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Font")) {
7159
+ const id = getOfdAttribute(font, "ID");
7160
+ const name = getOfdAttribute(font, "FontName") || getOfdAttribute(font, "FamilyName");
7161
+ if (id && name) {
7162
+ fonts.set(id, name.trim());
7163
+ }
7164
+ }
7165
+ }
7166
+ return fonts;
7167
+ }
7008
7168
  async function readOfdImages(entries) {
7009
7169
  const images = /* @__PURE__ */ new Map();
7010
7170
  for (const entry of entries.filter((item) => /\.(?:png|jpe?g|gif|bmp|webp)$/i.test(item.name)).slice(0, 80)) {
@@ -7019,17 +7179,69 @@ async function readOfdImages(entries) {
7019
7179
  images.set(entry.name, href);
7020
7180
  images.set(entry.name.split("/").pop() || entry.name, href);
7021
7181
  }
7182
+ for (const entry of entries.filter((item) => /(?:^|\/)(?:DocumentRes|PublicRes)\.xml$/i.test(item.name))) {
7183
+ const xml = await entry.async("text");
7184
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7185
+ if (doc.querySelector("parsererror")) {
7186
+ continue;
7187
+ }
7188
+ const baseLoc = getOfdAttribute(doc.documentElement, "BaseLoc") || "";
7189
+ const resourceDir = joinOfdPath(directoryName2(entry.name), baseLoc);
7190
+ for (const media of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "MultiMedia")) {
7191
+ const id = getOfdAttribute(media, "ID");
7192
+ const mediaFile = findOfdChild(media, "MediaFile")?.textContent?.trim();
7193
+ if (!id || !mediaFile) {
7194
+ continue;
7195
+ }
7196
+ const imageEntry = findOfdEntry(entries, joinOfdPath(resourceDir, mediaFile)) || findOfdEntry(entries, mediaFile);
7197
+ const href = imageEntry ? images.get(imageEntry.name) || images.get(imageEntry.name.split("/").pop() || imageEntry.name) : void 0;
7198
+ if (href) {
7199
+ images.set(id, href);
7200
+ }
7201
+ }
7202
+ }
7022
7203
  return images;
7023
7204
  }
7024
- function parseOfdPageSize(doc) {
7205
+ async function readOfdDocumentInfo(entries) {
7206
+ const templates = /* @__PURE__ */ new Map();
7207
+ let pageSize;
7208
+ for (const entry of entries.filter((item) => /(?:^|\/)Document\.xml$/i.test(item.name))) {
7209
+ const xml = await entry.async("text");
7210
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7211
+ if (doc.querySelector("parsererror")) {
7212
+ continue;
7213
+ }
7214
+ const documentDir = directoryName2(entry.name);
7215
+ const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PageArea")?.getElementsByTagName("*");
7216
+ const pageAreaBox = physicalBox ? Array.from(physicalBox).find((element) => element.localName === "PhysicalBox") : void 0;
7217
+ if (pageAreaBox?.textContent) {
7218
+ const box = parseBoundary(pageAreaBox.textContent);
7219
+ if (box.width > 0 && box.height > 0) {
7220
+ pageSize = { width: box.width, height: box.height };
7221
+ }
7222
+ }
7223
+ for (const template of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TemplatePage")) {
7224
+ const id = getOfdAttribute(template, "ID");
7225
+ const baseLoc = getOfdAttribute(template, "BaseLoc");
7226
+ if (id && baseLoc) {
7227
+ templates.set(id, joinOfdPath(documentDir, baseLoc));
7228
+ }
7229
+ }
7230
+ }
7231
+ return { templates, pageSize };
7232
+ }
7233
+ function parseOfdPageSize(doc, defaultPageSize) {
7234
+ if (defaultPageSize) {
7235
+ return { ...defaultPageSize, explicit: true };
7236
+ }
7025
7237
  const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PhysicalBox");
7026
7238
  if (physicalBox?.textContent) {
7027
7239
  const box = parseBoundary(physicalBox.textContent);
7028
7240
  if (box.width > 0 && box.height > 0) {
7029
- return { width: box.width, height: box.height };
7241
+ return { width: box.width, height: box.height, explicit: true };
7030
7242
  }
7031
7243
  }
7032
- return { width: 210, height: 297 };
7244
+ return { width: 210, height: 297, explicit: false };
7033
7245
  }
7034
7246
  function parseOfdColor(element, fallback, preferredLocalName = "FillColor") {
7035
7247
  const colorElement = findOfdChild(element, preferredLocalName) || findOfdChild(element, "StrokeColor") || findOfdChild(element, "FillColor");
@@ -7060,6 +7272,101 @@ function parsePoint(value, fallback) {
7060
7272
  function normalizeOfdPathData(value) {
7061
7273
  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();
7062
7274
  }
7275
+ function parseOfdCtm(value) {
7276
+ const parts = (value || "").trim().split(/\s+/).map((part) => Number(part));
7277
+ if (parts.length !== 6 || parts.some((part) => !Number.isFinite(part))) {
7278
+ return void 0;
7279
+ }
7280
+ return parts;
7281
+ }
7282
+ function createOfdPathTransform(x, y, ctm) {
7283
+ if (!ctm) {
7284
+ return `translate(${x} ${y})`;
7285
+ }
7286
+ const [a, b, c, d, e, f] = ctm;
7287
+ return `translate(${x} ${y}) matrix(${a} ${b} ${c} ${d} ${e} ${f})`;
7288
+ }
7289
+ function parseOfdDeltaStep(value, fallback) {
7290
+ const numbers = value.match(/-?\d+(?:\.\d+)?/g)?.map((part) => Number(part)).filter((part) => Number.isFinite(part)) || [];
7291
+ return numbers.length > 0 ? numbers[numbers.length - 1] : fallback;
7292
+ }
7293
+ function parseOfdDeltaX(value) {
7294
+ if (!value) {
7295
+ return void 0;
7296
+ }
7297
+ const parts = value.match(/[a-z]+|-?\d+(?:\.\d+)?/gi) || [];
7298
+ const deltas = [];
7299
+ for (let index = 0; index < parts.length; index += 1) {
7300
+ const token = parts[index];
7301
+ if (/^g$/i.test(token)) {
7302
+ const count = Number(parts[index + 1]);
7303
+ const step = Number(parts[index + 2]);
7304
+ if (Number.isFinite(count) && Number.isFinite(step)) {
7305
+ deltas.push(...Array.from({ length: Math.max(0, Math.floor(count)) }, () => step));
7306
+ }
7307
+ index += 2;
7308
+ continue;
7309
+ }
7310
+ const numeric = Number(token);
7311
+ if (Number.isFinite(numeric)) {
7312
+ deltas.push(numeric);
7313
+ }
7314
+ }
7315
+ return deltas.length > 0 ? deltas : void 0;
7316
+ }
7317
+ function fontStackForOfdFont(fontName) {
7318
+ const normalized = (fontName || "").trim().toLowerCase();
7319
+ if (normalized.includes("courier")) {
7320
+ return '"Courier New", Courier, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
7321
+ }
7322
+ if (normalized.includes("kaiti") || normalized.includes("kai") || normalized.includes("\u6977")) {
7323
+ return '"STKaiti", "Kaiti SC", "KaiTi", "\u6977\u4F53", serif';
7324
+ }
7325
+ if (normalized.includes("simsun") || normalized.includes("simsong") || normalized.includes("song") || normalized.includes("\u5B8B")) {
7326
+ return '"SimSong", "Songti SC", "STSong", SimSun, "\u5B8B\u4F53", serif';
7327
+ }
7328
+ if (normalized.includes("hei") || normalized.includes("\u9ED1")) {
7329
+ return '"PingFang SC", "Microsoft YaHei", SimHei, sans-serif';
7330
+ }
7331
+ return '"SimSong", "Songti SC", "STSong", SimSun, "Noto Serif CJK SC", serif';
7332
+ }
7333
+ function inferOfdTextAlign(text, boundary) {
7334
+ const normalized = text.trim();
7335
+ if (!/^[¥¥]?\d+(?:\.\d+)?%?$/.test(normalized)) {
7336
+ return "start";
7337
+ }
7338
+ if (boundary.x >= 75 || boundary.width <= 30) {
7339
+ return "end";
7340
+ }
7341
+ return "start";
7342
+ }
7343
+ function findOfdEntry(entries, path) {
7344
+ const normalized = normalizeOfdPath(path);
7345
+ return entries.find((entry) => normalizeOfdPath(entry.name) === normalized || normalizeOfdPath(entry.name).endsWith(`/${normalized}`));
7346
+ }
7347
+ function joinOfdPath(...parts) {
7348
+ const joined = parts.filter(Boolean).join("/");
7349
+ const segments = [];
7350
+ for (const segment of joined.split("/")) {
7351
+ if (!segment || segment === ".") {
7352
+ continue;
7353
+ }
7354
+ if (segment === "..") {
7355
+ segments.pop();
7356
+ continue;
7357
+ }
7358
+ segments.push(segment);
7359
+ }
7360
+ return segments.join("/");
7361
+ }
7362
+ function directoryName2(path) {
7363
+ const normalized = normalizeOfdPath(path);
7364
+ const index = normalized.lastIndexOf("/");
7365
+ return index >= 0 ? normalized.slice(0, index) : "";
7366
+ }
7367
+ function normalizeOfdPath(path) {
7368
+ return path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+/g, "/");
7369
+ }
7063
7370
  function mimeTypeFromPath2(path) {
7064
7371
  const extension = path.split(".").pop()?.toLowerCase();
7065
7372
  const map = {
@@ -7097,6 +7404,9 @@ function finiteNumber2(value, fallback) {
7097
7404
  const parsed = value === null ? Number.NaN : Number(value);
7098
7405
  return Number.isFinite(parsed) ? parsed : fallback;
7099
7406
  }
7407
+ function formatOfdCssNumber(value) {
7408
+ return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7409
+ }
7100
7410
  function createOfdFallback(fileName, url, detail) {
7101
7411
  const fallback = document.createElement("div");
7102
7412
  fallback.className = "ofv-fallback";