@open-file-viewer/core 0.1.3 → 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, {
@@ -5036,6 +5090,7 @@ async function renderDocx(panel, arrayBuffer) {
5036
5090
  experimental: true,
5037
5091
  useBase64URL: true
5038
5092
  });
5093
+ normalizeDocxLayout(content);
5039
5094
  return fitDocxPages(content);
5040
5095
  } catch (error) {
5041
5096
  content.replaceChildren();
@@ -5053,6 +5108,29 @@ async function renderDocx(panel, arrayBuffer) {
5053
5108
  }
5054
5109
  return () => void 0;
5055
5110
  }
5111
+ function normalizeDocxLayout(container) {
5112
+ const pages = container.querySelectorAll("section.ofv-docx");
5113
+ for (const page of pages) {
5114
+ for (const element of page.querySelectorAll("[style*='line-height']")) {
5115
+ const lineHeight = parseCssLineHeight(element.style.lineHeight);
5116
+ if (lineHeight > 0 && lineHeight < 1) {
5117
+ element.style.lineHeight = "1.2";
5118
+ }
5119
+ }
5120
+ }
5121
+ }
5122
+ function parseCssLineHeight(value) {
5123
+ const trimmed = value.trim();
5124
+ if (!trimmed || trimmed === "normal") {
5125
+ return 0;
5126
+ }
5127
+ if (trimmed.endsWith("%")) {
5128
+ const parsedPercent = Number.parseFloat(trimmed);
5129
+ return Number.isFinite(parsedPercent) ? parsedPercent / 100 : 0;
5130
+ }
5131
+ const parsed = Number.parseFloat(trimmed);
5132
+ return Number.isFinite(parsed) ? parsed : 0;
5133
+ }
5056
5134
  function fitDocxPages(container) {
5057
5135
  const wrapper = container.querySelector(".ofv-docx-wrapper");
5058
5136
  if (!wrapper) {
@@ -5214,7 +5292,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5214
5292
  const xlsx = await import("xlsx");
5215
5293
  let workbook;
5216
5294
  try {
5217
- 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" });
5218
5296
  } catch (error) {
5219
5297
  if (isLegacyOfficeBinary(extension)) {
5220
5298
  renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
@@ -5847,10 +5925,30 @@ async function renderPptx(panel, arrayBuffer) {
5847
5925
  try {
5848
5926
  const { PptxViewer } = await import("@aiden0z/pptx-renderer");
5849
5927
  await PptxViewer.open(arrayBuffer, container);
5928
+ normalizePptxLayout(container);
5850
5929
  } catch {
5851
5930
  container.textContent = "PPTX \u6E32\u67D3\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u635F\u574F\u3002";
5852
5931
  }
5853
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
+ }
5854
5952
  async function renderOdp(panel, arrayBuffer) {
5855
5953
  const zip = await JSZip3.loadAsync(arrayBuffer);
5856
5954
  const content = zip.file("content.xml");
@@ -5878,34 +5976,28 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
5878
5976
  const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
5879
5977
  const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
5880
5978
  if (hasEntry("word/document.xml")) {
5881
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Word \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 DOCX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5882
5979
  await renderDocx(panel, arrayBuffer);
5883
5980
  return true;
5884
5981
  }
5885
5982
  if (hasEntry("xl/workbook.xml")) {
5886
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Workbook \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 XLSX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5887
5983
  await renderSheet(panel, arrayBuffer, extension);
5888
5984
  return true;
5889
5985
  }
5890
5986
  if (hasEntry("ppt/presentation.xml")) {
5891
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 PPTX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5892
5987
  await renderPptx(panel, arrayBuffer);
5893
5988
  return true;
5894
5989
  }
5895
5990
  if (contentXml) {
5896
5991
  const xml = await contentXml.async("text");
5897
5992
  if (/<office:spreadsheet\b|<table:table\b/i.test(xml)) {
5898
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Spreadsheet \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODS \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5899
5993
  renderParsedSheets(panel, parseFlatOds(xml), `${extension.toUpperCase()} \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002`);
5900
5994
  return true;
5901
5995
  }
5902
5996
  if (/<office:presentation\b|<draw:page\b/i.test(xml)) {
5903
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODP \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5904
5997
  renderOpenDocumentPresentation(panel, `${extension.toUpperCase()} \u6F14\u793A\u6587\u7A3F`, xml, await extractZipImages(zip, /^Pictures\//));
5905
5998
  return true;
5906
5999
  }
5907
6000
  if (/<office:text\b|<text:p\b/i.test(xml)) {
5908
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Text \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODT \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5909
6001
  renderOpenDocumentXml(panel, `${extension.toUpperCase()} \u6587\u6863`, xml);
5910
6002
  return true;
5911
6003
  }
@@ -5931,14 +6023,6 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
5931
6023
  }
5932
6024
  return false;
5933
6025
  }
5934
- function renderOfficePackageNotice(panel, extension, message) {
5935
- const section = createSection("\u517C\u5BB9\u5305\u8BC6\u522B");
5936
- const note = document.createElement("p");
5937
- note.className = "ofv-office-package-note";
5938
- note.textContent = `.${extension} ${message}`;
5939
- section.append(note);
5940
- panel.append(section);
5941
- }
5942
6026
  function renderOfficePackageStructure(panel, extension, entries, message, metadata) {
5943
6027
  const section = createSection("Office \u5305\u7ED3\u6784\u9884\u89C8");
5944
6028
  const note = document.createElement("p");
@@ -6586,7 +6670,7 @@ function rtfToText(rtf) {
6586
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();
6587
6671
  }
6588
6672
  async function readTextFromBuffer(arrayBuffer) {
6589
- return new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
6673
+ return decodeTextBuffer(arrayBuffer);
6590
6674
  }
6591
6675
  function mimeTypeFromPath(path) {
6592
6676
  const extension = path.split(".").pop()?.toLowerCase();
@@ -6653,36 +6737,72 @@ function ofdPlugin() {
6653
6737
  }
6654
6738
  };
6655
6739
  }
6656
- const images = await readOfdImages(entries);
6657
- const pages = await readOfdPages(entries, images);
6658
- const section = createSection("OFD \u57FA\u7840\u9884\u89C8");
6659
- const note = document.createElement("p");
6660
- 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";
6661
- section.append(note);
6662
- 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
+ };
6663
6753
  if (pages.length > 0) {
6664
6754
  const pagesWrap = document.createElement("div");
6665
6755
  pagesWrap.className = "ofv-ofd-pages";
6666
6756
  for (const page of pages) {
6667
6757
  pagesWrap.append(renderOfdPage(page));
6668
6758
  }
6669
- section.append(pagesWrap);
6670
- }
6671
- const content = document.createElement("pre");
6672
- content.className = "ofv-text-block";
6673
- content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
6674
- section.append(content);
6675
- const list = createSection(`\u6587\u4EF6\u7ED3\u6784 ${entries.length}`);
6676
- const ul = document.createElement("ul");
6677
- for (const entry of entries.slice(0, 200)) {
6678
- const li = document.createElement("li");
6679
- li.textContent = entry.name;
6680
- ul.append(li);
6681
- }
6682
- list.append(ul);
6683
- 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
+ }
6684
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
+ },
6685
6804
  destroy() {
6805
+ ctx.toolbar?.setZoom(void 0);
6686
6806
  panel.remove();
6687
6807
  revokeObjectUrl(url, isExternal);
6688
6808
  }
@@ -6690,76 +6810,71 @@ function ofdPlugin() {
6690
6810
  }
6691
6811
  };
6692
6812
  }
6693
- function createOfdSummary(entries, pages, images) {
6694
- const summary = document.createElement("div");
6695
- summary.className = "ofv-ofd-summary";
6696
- const xmlEntries = entries.filter((entry) => entry.name.endsWith(".xml")).length;
6697
- const textCount = pages.reduce((count, page) => count + page.texts.length, 0);
6698
- const pathCount = pages.reduce((count, page) => count + page.paths.length, 0);
6699
- const lineCount = pages.reduce((count, page) => count + page.lines.length, 0);
6700
- const imageCount = pages.reduce((count, page) => count + page.images.length, 0);
6701
- const textLength = pages.reduce((count, page) => count + page.texts.reduce((inner, item) => inner + item.text.length, 0), 0);
6702
- appendOfdSummary(summary, "\u6587\u4EF6", String(entries.length));
6703
- appendOfdSummary(summary, "XML", String(xmlEntries));
6704
- appendOfdSummary(summary, "\u9875\u9762", String(pages.length));
6705
- appendOfdSummary(summary, "\u6587\u672C", String(textCount));
6706
- appendOfdSummary(summary, "\u8DEF\u5F84", String(pathCount));
6707
- appendOfdSummary(summary, "\u7EBF\u6761", String(lineCount));
6708
- appendOfdSummary(summary, "\u56FE\u7247\u5BF9\u8C61", String(imageCount));
6709
- appendOfdSummary(summary, "\u56FE\u7247\u8D44\u6E90", String(uniqueOfdImageResources(images)));
6710
- if (textLength > 0) {
6711
- appendOfdSummary(summary, "\u6587\u5B57\u957F\u5EA6", String(textLength));
6712
- }
6713
- const sizes = formatOfdPageSizes(pages);
6714
- if (sizes) {
6715
- appendOfdSummary(summary, "\u9875\u9762\u5C3A\u5BF8", sizes);
6716
- }
6717
- return summary;
6718
- }
6719
- function appendOfdSummary(parent, label, value) {
6720
- const item = document.createElement("span");
6721
- const key = document.createElement("span");
6722
- key.textContent = label;
6723
- const content = document.createElement("strong");
6724
- content.textContent = value;
6725
- item.append(key, content);
6726
- parent.append(item);
6727
- }
6728
- function uniqueOfdImageResources(images) {
6729
- return new Set(images.values()).size;
6730
- }
6731
- function formatOfdPageSizes(pages) {
6732
- const counts = /* @__PURE__ */ new Map();
6733
- for (const page of pages) {
6734
- const key = `${Math.round(page.width)} x ${Math.round(page.height)}`;
6735
- counts.set(key, (counts.get(key) || 0) + 1);
6736
- }
6737
- return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([size, count]) => count > 1 ? `${size} (${count})` : size).join(", ");
6738
- }
6739
- async function readOfdPages(entries, images) {
6813
+ async function readOfdPages(entries, context) {
6740
6814
  const pages = [];
6741
6815
  const pageEntries = entries.filter((entry) => /(^|\/)Pages\/Page_[^/]+\/Content\.xml$/i.test(entry.name) || /(^|\/)Page_[^/]+\/Content\.xml$/i.test(entry.name)).slice(0, 80);
6742
6816
  for (const entry of pageEntries) {
6743
6817
  const xml = await entry.async("text");
6744
- 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);
6745
6820
  if (page.texts.length > 0 || page.paths.length > 0 || page.lines.length > 0 || page.images.length > 0) {
6746
6821
  pages.push(page);
6747
6822
  }
6748
6823
  }
6749
6824
  return pages;
6750
6825
  }
6751
- 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) {
6752
6843
  const doc = new DOMParser().parseFromString(xml, "application/xml");
6753
6844
  if (doc.querySelector("parsererror")) {
6754
6845
  return { name, width: 210, height: 297, texts: [], paths: [], lines: [], images: [] };
6755
6846
  }
6756
- 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) {
6757
6866
  const textObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TextObject");
6758
- const texts = textObjects.flatMap((element) => parseOfdTextObject(element));
6867
+ const texts = textObjects.flatMap((element) => parseOfdTextObject(element, fonts));
6759
6868
  const paths = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "PathObject").flatMap((element) => parseOfdPathObject(element));
6760
6869
  const lines = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "LineObject").flatMap((element) => parseOfdLineObject(element));
6761
6870
  const imageObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ImageObject").flatMap((element) => parseOfdImageObject(element, images));
6762
- 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 [
6763
6878
  ...texts.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
6764
6879
  ...paths.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
6765
6880
  ...lines.map((item) => ({
@@ -6770,38 +6885,63 @@ function parseOfdPage(name, xml, images) {
6770
6885
  })),
6771
6886
  ...imageObjects.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height }))
6772
6887
  ];
6773
- const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
6774
- const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
6775
- return { name, width, height, texts, paths, lines, images: imageObjects };
6776
6888
  }
6777
- function parseOfdTextObject(element) {
6889
+ function parseOfdTextObject(element, fonts) {
6778
6890
  const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
6779
6891
  const size = finiteNumber2(getOfdAttribute(element, "Size"), Math.max(4, boundary.height || 5));
6780
6892
  const color = parseOfdColor(element, "#111827");
6781
6893
  const weight = finiteNumber2(getOfdAttribute(element, "Weight"), 400) >= 600 ? "700" : "400";
6782
- 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;
6783
6896
  const textCodes = Array.from(element.getElementsByTagName("*")).filter((child) => child.localName === "TextCode");
6784
6897
  if (textCodes.length === 0) {
6785
6898
  return [];
6786
6899
  }
6787
- return textCodes.map((code) => {
6900
+ return textCodes.flatMap((code) => {
6788
6901
  const x = boundary.x + finiteNumber2(getOfdAttribute(code, "X"), 0);
6789
6902
  const y = boundary.y + finiteNumber2(getOfdAttribute(code, "Y"), 0);
6790
- return {
6791
- text: code.textContent?.trim() || "",
6792
- x,
6793
- y,
6794
- width: boundary.width,
6795
- height: boundary.height,
6796
- size,
6797
- color,
6798
- weight,
6799
- letterSpacing
6800
- };
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
+ ];
6801
6940
  }).filter((item) => item.text);
6802
6941
  }
6803
6942
  function parseOfdPathObject(element) {
6804
6943
  const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
6944
+ const ctm = parseOfdCtm(getOfdAttribute(element, "CTM"));
6805
6945
  const commands = Array.from(element.getElementsByTagName("*")).filter(
6806
6946
  (child) => child.localName === "AbbreviatedData" || child.localName === "PathData"
6807
6947
  );
@@ -6816,9 +6956,10 @@ function parseOfdPathObject(element) {
6816
6956
  y: boundary.y,
6817
6957
  width: boundary.width,
6818
6958
  height: boundary.height,
6819
- stroke: parseOfdColor(element, "#334155", "StrokeColor"),
6959
+ stroke: parseOfdColor(element, "#111827", "StrokeColor"),
6820
6960
  fill: parseOfdFill(element),
6821
- strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
6961
+ strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1),
6962
+ transform: createOfdPathTransform(boundary.x, boundary.y, ctm)
6822
6963
  }
6823
6964
  ];
6824
6965
  }
@@ -6835,7 +6976,7 @@ function parseOfdLineObject(element) {
6835
6976
  y1: boundary.y + start.y,
6836
6977
  x2: boundary.x + end.x,
6837
6978
  y2: boundary.y + end.y,
6838
- stroke: parseOfdColor(element, "#334155"),
6979
+ stroke: parseOfdColor(element, "#111827"),
6839
6980
  strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
6840
6981
  }
6841
6982
  ];
@@ -6857,6 +6998,8 @@ function parseOfdImageObject(element, images) {
6857
6998
  function renderOfdPage(page) {
6858
6999
  const figure = document.createElement("figure");
6859
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`);
6860
7003
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
6861
7004
  svg.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`);
6862
7005
  svg.setAttribute("role", "img");
@@ -6893,7 +7036,7 @@ function renderOfdPage(page) {
6893
7036
  for (const item of page.paths) {
6894
7037
  const path = document.createElementNS(svg.namespaceURI, "path");
6895
7038
  path.setAttribute("d", item.d);
6896
- path.setAttribute("transform", `translate(${item.x} ${item.y})`);
7039
+ path.setAttribute("transform", item.transform);
6897
7040
  path.setAttribute("fill", item.fill);
6898
7041
  path.setAttribute("stroke", item.stroke);
6899
7042
  path.setAttribute("stroke-width", String(item.strokeWidth));
@@ -6912,22 +7055,63 @@ function renderOfdPage(page) {
6912
7055
  }
6913
7056
  for (const item of page.texts) {
6914
7057
  const text = document.createElementNS(svg.namespaceURI, "text");
6915
- text.setAttribute("x", String(item.x));
6916
- 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));
6917
7060
  text.setAttribute("font-size", String(item.size));
6918
7061
  text.setAttribute("fill", item.color);
6919
7062
  text.setAttribute("font-weight", item.weight);
7063
+ text.setAttribute("font-family", item.fontFamily);
6920
7064
  if (item.letterSpacing !== void 0) {
6921
7065
  text.setAttribute("letter-spacing", String(item.letterSpacing));
6922
7066
  }
6923
- 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
+ }
6924
7086
  svg.append(text);
6925
7087
  }
6926
- const caption = document.createElement("figcaption");
6927
- caption.textContent = `${page.name} \xB7 ${page.texts.length} text \xB7 ${page.paths.length} path \xB7 ${page.lines.length} line \xB7 ${page.images.length} image`;
6928
- figure.append(svg, caption);
7088
+ figure.append(svg);
6929
7089
  return figure;
6930
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
+ }
6931
7115
  async function readOfdImages(entries) {
6932
7116
  const images = /* @__PURE__ */ new Map();
6933
7117
  for (const entry of entries.filter((item) => /\.(?:png|jpe?g|gif|bmp|webp)$/i.test(item.name)).slice(0, 80)) {
@@ -6942,17 +7126,69 @@ async function readOfdImages(entries) {
6942
7126
  images.set(entry.name, href);
6943
7127
  images.set(entry.name.split("/").pop() || entry.name, href);
6944
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
+ }
6945
7150
  return images;
6946
7151
  }
6947
- 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
+ }
6948
7184
  const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PhysicalBox");
6949
7185
  if (physicalBox?.textContent) {
6950
7186
  const box = parseBoundary(physicalBox.textContent);
6951
7187
  if (box.width > 0 && box.height > 0) {
6952
- return { width: box.width, height: box.height };
7188
+ return { width: box.width, height: box.height, explicit: true };
6953
7189
  }
6954
7190
  }
6955
- return { width: 210, height: 297 };
7191
+ return { width: 210, height: 297, explicit: false };
6956
7192
  }
6957
7193
  function parseOfdColor(element, fallback, preferredLocalName = "FillColor") {
6958
7194
  const colorElement = findOfdChild(element, preferredLocalName) || findOfdChild(element, "StrokeColor") || findOfdChild(element, "FillColor");
@@ -6983,6 +7219,101 @@ function parsePoint(value, fallback) {
6983
7219
  function normalizeOfdPathData(value) {
6984
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();
6985
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
+ }
6986
7317
  function mimeTypeFromPath2(path) {
6987
7318
  const extension = path.split(".").pop()?.toLowerCase();
6988
7319
  const map = {
@@ -7020,6 +7351,9 @@ function finiteNumber2(value, fallback) {
7020
7351
  const parsed = value === null ? Number.NaN : Number(value);
7021
7352
  return Number.isFinite(parsed) ? parsed : fallback;
7022
7353
  }
7354
+ function formatOfdCssNumber(value) {
7355
+ return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7356
+ }
7023
7357
  function createOfdFallback(fileName, url, detail) {
7024
7358
  const fallback = document.createElement("div");
7025
7359
  fallback.className = "ofv-fallback";