@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.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, {
@@ -5089,6 +5143,7 @@ async function renderDocx(panel, arrayBuffer) {
5089
5143
  experimental: true,
5090
5144
  useBase64URL: true
5091
5145
  });
5146
+ normalizeDocxLayout(content);
5092
5147
  return fitDocxPages(content);
5093
5148
  } catch (error) {
5094
5149
  content.replaceChildren();
@@ -5106,6 +5161,29 @@ async function renderDocx(panel, arrayBuffer) {
5106
5161
  }
5107
5162
  return () => void 0;
5108
5163
  }
5164
+ function normalizeDocxLayout(container) {
5165
+ const pages = container.querySelectorAll("section.ofv-docx");
5166
+ for (const page of pages) {
5167
+ for (const element of page.querySelectorAll("[style*='line-height']")) {
5168
+ const lineHeight = parseCssLineHeight(element.style.lineHeight);
5169
+ if (lineHeight > 0 && lineHeight < 1) {
5170
+ element.style.lineHeight = "1.2";
5171
+ }
5172
+ }
5173
+ }
5174
+ }
5175
+ function parseCssLineHeight(value) {
5176
+ const trimmed = value.trim();
5177
+ if (!trimmed || trimmed === "normal") {
5178
+ return 0;
5179
+ }
5180
+ if (trimmed.endsWith("%")) {
5181
+ const parsedPercent = Number.parseFloat(trimmed);
5182
+ return Number.isFinite(parsedPercent) ? parsedPercent / 100 : 0;
5183
+ }
5184
+ const parsed = Number.parseFloat(trimmed);
5185
+ return Number.isFinite(parsed) ? parsed : 0;
5186
+ }
5109
5187
  function fitDocxPages(container) {
5110
5188
  const wrapper = container.querySelector(".ofv-docx-wrapper");
5111
5189
  if (!wrapper) {
@@ -5267,7 +5345,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5267
5345
  const xlsx = await import("xlsx");
5268
5346
  let workbook;
5269
5347
  try {
5270
- 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" });
5271
5349
  } catch (error) {
5272
5350
  if (isLegacyOfficeBinary(extension)) {
5273
5351
  renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
@@ -5900,10 +5978,30 @@ async function renderPptx(panel, arrayBuffer) {
5900
5978
  try {
5901
5979
  const { PptxViewer } = await import("@aiden0z/pptx-renderer");
5902
5980
  await PptxViewer.open(arrayBuffer, container);
5981
+ normalizePptxLayout(container);
5903
5982
  } catch {
5904
5983
  container.textContent = "PPTX \u6E32\u67D3\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u635F\u574F\u3002";
5905
5984
  }
5906
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
+ }
5907
6005
  async function renderOdp(panel, arrayBuffer) {
5908
6006
  const zip = await import_jszip3.default.loadAsync(arrayBuffer);
5909
6007
  const content = zip.file("content.xml");
@@ -5931,34 +6029,28 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
5931
6029
  const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
5932
6030
  const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
5933
6031
  if (hasEntry("word/document.xml")) {
5934
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Word \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 DOCX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5935
6032
  await renderDocx(panel, arrayBuffer);
5936
6033
  return true;
5937
6034
  }
5938
6035
  if (hasEntry("xl/workbook.xml")) {
5939
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Workbook \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 XLSX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5940
6036
  await renderSheet(panel, arrayBuffer, extension);
5941
6037
  return true;
5942
6038
  }
5943
6039
  if (hasEntry("ppt/presentation.xml")) {
5944
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 PPTX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5945
6040
  await renderPptx(panel, arrayBuffer);
5946
6041
  return true;
5947
6042
  }
5948
6043
  if (contentXml) {
5949
6044
  const xml = await contentXml.async("text");
5950
6045
  if (/<office:spreadsheet\b|<table:table\b/i.test(xml)) {
5951
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Spreadsheet \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODS \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5952
6046
  renderParsedSheets(panel, parseFlatOds(xml), `${extension.toUpperCase()} \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002`);
5953
6047
  return true;
5954
6048
  }
5955
6049
  if (/<office:presentation\b|<draw:page\b/i.test(xml)) {
5956
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODP \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5957
6050
  renderOpenDocumentPresentation(panel, `${extension.toUpperCase()} \u6F14\u793A\u6587\u7A3F`, xml, await extractZipImages(zip, /^Pictures\//));
5958
6051
  return true;
5959
6052
  }
5960
6053
  if (/<office:text\b|<text:p\b/i.test(xml)) {
5961
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Text \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODT \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5962
6054
  renderOpenDocumentXml(panel, `${extension.toUpperCase()} \u6587\u6863`, xml);
5963
6055
  return true;
5964
6056
  }
@@ -5984,14 +6076,6 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
5984
6076
  }
5985
6077
  return false;
5986
6078
  }
5987
- function renderOfficePackageNotice(panel, extension, message) {
5988
- const section = createSection("\u517C\u5BB9\u5305\u8BC6\u522B");
5989
- const note = document.createElement("p");
5990
- note.className = "ofv-office-package-note";
5991
- note.textContent = `.${extension} ${message}`;
5992
- section.append(note);
5993
- panel.append(section);
5994
- }
5995
6079
  function renderOfficePackageStructure(panel, extension, entries, message, metadata) {
5996
6080
  const section = createSection("Office \u5305\u7ED3\u6784\u9884\u89C8");
5997
6081
  const note = document.createElement("p");
@@ -6639,7 +6723,7 @@ function rtfToText(rtf) {
6639
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();
6640
6724
  }
6641
6725
  async function readTextFromBuffer(arrayBuffer) {
6642
- return new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
6726
+ return decodeTextBuffer(arrayBuffer);
6643
6727
  }
6644
6728
  function mimeTypeFromPath(path) {
6645
6729
  const extension = path.split(".").pop()?.toLowerCase();
@@ -6706,36 +6790,72 @@ function ofdPlugin() {
6706
6790
  }
6707
6791
  };
6708
6792
  }
6709
- const images = await readOfdImages(entries);
6710
- const pages = await readOfdPages(entries, images);
6711
- const section = createSection("OFD \u57FA\u7840\u9884\u89C8");
6712
- const note = document.createElement("p");
6713
- 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";
6714
- section.append(note);
6715
- 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
+ };
6716
6806
  if (pages.length > 0) {
6717
6807
  const pagesWrap = document.createElement("div");
6718
6808
  pagesWrap.className = "ofv-ofd-pages";
6719
6809
  for (const page of pages) {
6720
6810
  pagesWrap.append(renderOfdPage(page));
6721
6811
  }
6722
- section.append(pagesWrap);
6723
- }
6724
- const content = document.createElement("pre");
6725
- content.className = "ofv-text-block";
6726
- content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
6727
- section.append(content);
6728
- const list = createSection(`\u6587\u4EF6\u7ED3\u6784 ${entries.length}`);
6729
- const ul = document.createElement("ul");
6730
- for (const entry of entries.slice(0, 200)) {
6731
- const li = document.createElement("li");
6732
- li.textContent = entry.name;
6733
- ul.append(li);
6734
- }
6735
- list.append(ul);
6736
- 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
+ }
6737
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
+ },
6738
6857
  destroy() {
6858
+ ctx.toolbar?.setZoom(void 0);
6739
6859
  panel.remove();
6740
6860
  revokeObjectUrl(url, isExternal);
6741
6861
  }
@@ -6743,76 +6863,71 @@ function ofdPlugin() {
6743
6863
  }
6744
6864
  };
6745
6865
  }
6746
- function createOfdSummary(entries, pages, images) {
6747
- const summary = document.createElement("div");
6748
- summary.className = "ofv-ofd-summary";
6749
- const xmlEntries = entries.filter((entry) => entry.name.endsWith(".xml")).length;
6750
- const textCount = pages.reduce((count, page) => count + page.texts.length, 0);
6751
- const pathCount = pages.reduce((count, page) => count + page.paths.length, 0);
6752
- const lineCount = pages.reduce((count, page) => count + page.lines.length, 0);
6753
- const imageCount = pages.reduce((count, page) => count + page.images.length, 0);
6754
- const textLength = pages.reduce((count, page) => count + page.texts.reduce((inner, item) => inner + item.text.length, 0), 0);
6755
- appendOfdSummary(summary, "\u6587\u4EF6", String(entries.length));
6756
- appendOfdSummary(summary, "XML", String(xmlEntries));
6757
- appendOfdSummary(summary, "\u9875\u9762", String(pages.length));
6758
- appendOfdSummary(summary, "\u6587\u672C", String(textCount));
6759
- appendOfdSummary(summary, "\u8DEF\u5F84", String(pathCount));
6760
- appendOfdSummary(summary, "\u7EBF\u6761", String(lineCount));
6761
- appendOfdSummary(summary, "\u56FE\u7247\u5BF9\u8C61", String(imageCount));
6762
- appendOfdSummary(summary, "\u56FE\u7247\u8D44\u6E90", String(uniqueOfdImageResources(images)));
6763
- if (textLength > 0) {
6764
- appendOfdSummary(summary, "\u6587\u5B57\u957F\u5EA6", String(textLength));
6765
- }
6766
- const sizes = formatOfdPageSizes(pages);
6767
- if (sizes) {
6768
- appendOfdSummary(summary, "\u9875\u9762\u5C3A\u5BF8", sizes);
6769
- }
6770
- return summary;
6771
- }
6772
- function appendOfdSummary(parent, label, value) {
6773
- const item = document.createElement("span");
6774
- const key = document.createElement("span");
6775
- key.textContent = label;
6776
- const content = document.createElement("strong");
6777
- content.textContent = value;
6778
- item.append(key, content);
6779
- parent.append(item);
6780
- }
6781
- function uniqueOfdImageResources(images) {
6782
- return new Set(images.values()).size;
6783
- }
6784
- function formatOfdPageSizes(pages) {
6785
- const counts = /* @__PURE__ */ new Map();
6786
- for (const page of pages) {
6787
- const key = `${Math.round(page.width)} x ${Math.round(page.height)}`;
6788
- counts.set(key, (counts.get(key) || 0) + 1);
6789
- }
6790
- return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([size, count]) => count > 1 ? `${size} (${count})` : size).join(", ");
6791
- }
6792
- async function readOfdPages(entries, images) {
6866
+ async function readOfdPages(entries, context) {
6793
6867
  const pages = [];
6794
6868
  const pageEntries = entries.filter((entry) => /(^|\/)Pages\/Page_[^/]+\/Content\.xml$/i.test(entry.name) || /(^|\/)Page_[^/]+\/Content\.xml$/i.test(entry.name)).slice(0, 80);
6795
6869
  for (const entry of pageEntries) {
6796
6870
  const xml = await entry.async("text");
6797
- 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);
6798
6873
  if (page.texts.length > 0 || page.paths.length > 0 || page.lines.length > 0 || page.images.length > 0) {
6799
6874
  pages.push(page);
6800
6875
  }
6801
6876
  }
6802
6877
  return pages;
6803
6878
  }
6804
- 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) {
6805
6896
  const doc = new DOMParser().parseFromString(xml, "application/xml");
6806
6897
  if (doc.querySelector("parsererror")) {
6807
6898
  return { name, width: 210, height: 297, texts: [], paths: [], lines: [], images: [] };
6808
6899
  }
6809
- 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) {
6810
6919
  const textObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TextObject");
6811
- const texts = textObjects.flatMap((element) => parseOfdTextObject(element));
6920
+ const texts = textObjects.flatMap((element) => parseOfdTextObject(element, fonts));
6812
6921
  const paths = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "PathObject").flatMap((element) => parseOfdPathObject(element));
6813
6922
  const lines = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "LineObject").flatMap((element) => parseOfdLineObject(element));
6814
6923
  const imageObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ImageObject").flatMap((element) => parseOfdImageObject(element, images));
6815
- 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 [
6816
6931
  ...texts.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
6817
6932
  ...paths.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
6818
6933
  ...lines.map((item) => ({
@@ -6823,38 +6938,63 @@ function parseOfdPage(name, xml, images) {
6823
6938
  })),
6824
6939
  ...imageObjects.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height }))
6825
6940
  ];
6826
- const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
6827
- const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
6828
- return { name, width, height, texts, paths, lines, images: imageObjects };
6829
6941
  }
6830
- function parseOfdTextObject(element) {
6942
+ function parseOfdTextObject(element, fonts) {
6831
6943
  const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
6832
6944
  const size = finiteNumber2(getOfdAttribute(element, "Size"), Math.max(4, boundary.height || 5));
6833
6945
  const color = parseOfdColor(element, "#111827");
6834
6946
  const weight = finiteNumber2(getOfdAttribute(element, "Weight"), 400) >= 600 ? "700" : "400";
6835
- 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;
6836
6949
  const textCodes = Array.from(element.getElementsByTagName("*")).filter((child) => child.localName === "TextCode");
6837
6950
  if (textCodes.length === 0) {
6838
6951
  return [];
6839
6952
  }
6840
- return textCodes.map((code) => {
6953
+ return textCodes.flatMap((code) => {
6841
6954
  const x = boundary.x + finiteNumber2(getOfdAttribute(code, "X"), 0);
6842
6955
  const y = boundary.y + finiteNumber2(getOfdAttribute(code, "Y"), 0);
6843
- return {
6844
- text: code.textContent?.trim() || "",
6845
- x,
6846
- y,
6847
- width: boundary.width,
6848
- height: boundary.height,
6849
- size,
6850
- color,
6851
- weight,
6852
- letterSpacing
6853
- };
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
+ ];
6854
6993
  }).filter((item) => item.text);
6855
6994
  }
6856
6995
  function parseOfdPathObject(element) {
6857
6996
  const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
6997
+ const ctm = parseOfdCtm(getOfdAttribute(element, "CTM"));
6858
6998
  const commands = Array.from(element.getElementsByTagName("*")).filter(
6859
6999
  (child) => child.localName === "AbbreviatedData" || child.localName === "PathData"
6860
7000
  );
@@ -6869,9 +7009,10 @@ function parseOfdPathObject(element) {
6869
7009
  y: boundary.y,
6870
7010
  width: boundary.width,
6871
7011
  height: boundary.height,
6872
- stroke: parseOfdColor(element, "#334155", "StrokeColor"),
7012
+ stroke: parseOfdColor(element, "#111827", "StrokeColor"),
6873
7013
  fill: parseOfdFill(element),
6874
- strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
7014
+ strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1),
7015
+ transform: createOfdPathTransform(boundary.x, boundary.y, ctm)
6875
7016
  }
6876
7017
  ];
6877
7018
  }
@@ -6888,7 +7029,7 @@ function parseOfdLineObject(element) {
6888
7029
  y1: boundary.y + start.y,
6889
7030
  x2: boundary.x + end.x,
6890
7031
  y2: boundary.y + end.y,
6891
- stroke: parseOfdColor(element, "#334155"),
7032
+ stroke: parseOfdColor(element, "#111827"),
6892
7033
  strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
6893
7034
  }
6894
7035
  ];
@@ -6910,6 +7051,8 @@ function parseOfdImageObject(element, images) {
6910
7051
  function renderOfdPage(page) {
6911
7052
  const figure = document.createElement("figure");
6912
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`);
6913
7056
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
6914
7057
  svg.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`);
6915
7058
  svg.setAttribute("role", "img");
@@ -6946,7 +7089,7 @@ function renderOfdPage(page) {
6946
7089
  for (const item of page.paths) {
6947
7090
  const path = document.createElementNS(svg.namespaceURI, "path");
6948
7091
  path.setAttribute("d", item.d);
6949
- path.setAttribute("transform", `translate(${item.x} ${item.y})`);
7092
+ path.setAttribute("transform", item.transform);
6950
7093
  path.setAttribute("fill", item.fill);
6951
7094
  path.setAttribute("stroke", item.stroke);
6952
7095
  path.setAttribute("stroke-width", String(item.strokeWidth));
@@ -6965,22 +7108,63 @@ function renderOfdPage(page) {
6965
7108
  }
6966
7109
  for (const item of page.texts) {
6967
7110
  const text = document.createElementNS(svg.namespaceURI, "text");
6968
- text.setAttribute("x", String(item.x));
6969
- 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));
6970
7113
  text.setAttribute("font-size", String(item.size));
6971
7114
  text.setAttribute("fill", item.color);
6972
7115
  text.setAttribute("font-weight", item.weight);
7116
+ text.setAttribute("font-family", item.fontFamily);
6973
7117
  if (item.letterSpacing !== void 0) {
6974
7118
  text.setAttribute("letter-spacing", String(item.letterSpacing));
6975
7119
  }
6976
- 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
+ }
6977
7139
  svg.append(text);
6978
7140
  }
6979
- const caption = document.createElement("figcaption");
6980
- caption.textContent = `${page.name} \xB7 ${page.texts.length} text \xB7 ${page.paths.length} path \xB7 ${page.lines.length} line \xB7 ${page.images.length} image`;
6981
- figure.append(svg, caption);
7141
+ figure.append(svg);
6982
7142
  return figure;
6983
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
+ }
6984
7168
  async function readOfdImages(entries) {
6985
7169
  const images = /* @__PURE__ */ new Map();
6986
7170
  for (const entry of entries.filter((item) => /\.(?:png|jpe?g|gif|bmp|webp)$/i.test(item.name)).slice(0, 80)) {
@@ -6995,17 +7179,69 @@ async function readOfdImages(entries) {
6995
7179
  images.set(entry.name, href);
6996
7180
  images.set(entry.name.split("/").pop() || entry.name, href);
6997
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
+ }
6998
7203
  return images;
6999
7204
  }
7000
- 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
+ }
7001
7237
  const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PhysicalBox");
7002
7238
  if (physicalBox?.textContent) {
7003
7239
  const box = parseBoundary(physicalBox.textContent);
7004
7240
  if (box.width > 0 && box.height > 0) {
7005
- return { width: box.width, height: box.height };
7241
+ return { width: box.width, height: box.height, explicit: true };
7006
7242
  }
7007
7243
  }
7008
- return { width: 210, height: 297 };
7244
+ return { width: 210, height: 297, explicit: false };
7009
7245
  }
7010
7246
  function parseOfdColor(element, fallback, preferredLocalName = "FillColor") {
7011
7247
  const colorElement = findOfdChild(element, preferredLocalName) || findOfdChild(element, "StrokeColor") || findOfdChild(element, "FillColor");
@@ -7036,6 +7272,101 @@ function parsePoint(value, fallback) {
7036
7272
  function normalizeOfdPathData(value) {
7037
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();
7038
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
+ }
7039
7370
  function mimeTypeFromPath2(path) {
7040
7371
  const extension = path.split(".").pop()?.toLowerCase();
7041
7372
  const map = {
@@ -7073,6 +7404,9 @@ function finiteNumber2(value, fallback) {
7073
7404
  const parsed = value === null ? Number.NaN : Number(value);
7074
7405
  return Number.isFinite(parsed) ? parsed : fallback;
7075
7406
  }
7407
+ function formatOfdCssNumber(value) {
7408
+ return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7409
+ }
7076
7410
  function createOfdFallback(fileName, url, detail) {
7077
7411
  const fallback = document.createElement("div");
7078
7412
  fallback.className = "ofv-fallback";