@open-file-viewer/core 0.1.4 → 0.1.6

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
@@ -248,6 +248,9 @@ var extensionMimeMap = {
248
248
  skp: "application/vnd.sketchup.skp",
249
249
  sldprt: "application/sldworks",
250
250
  sldasm: "application/sldworks",
251
+ gds: "application/vnd.gds",
252
+ oas: "application/vnd.oasis.layout",
253
+ oasis: "application/vnd.oasis.layout",
251
254
  ttf: "font/ttf",
252
255
  otf: "font/otf",
253
256
  woff: "font/woff",
@@ -1752,6 +1755,7 @@ function imagePlugin() {
1752
1755
  let dragStartY = 0;
1753
1756
  let startOffsetX = 0;
1754
1757
  let startOffsetY = 0;
1758
+ let activePointerId = null;
1755
1759
  const zoomLabel = document.createElement("span");
1756
1760
  zoomLabel.className = "ofv-image-zoom";
1757
1761
  const updateTransform = () => {
@@ -1799,29 +1803,61 @@ function imagePlugin() {
1799
1803
  if (event.button !== 0) {
1800
1804
  return;
1801
1805
  }
1806
+ if (activePointerId !== null && activePointerId !== event.pointerId) {
1807
+ finishDrag(activePointerId);
1808
+ }
1802
1809
  dragging = true;
1810
+ activePointerId = event.pointerId;
1803
1811
  dragStartX = event.clientX;
1804
1812
  dragStartY = event.clientY;
1805
1813
  startOffsetX = offsetX;
1806
1814
  startOffsetY = offsetY;
1807
1815
  stage.classList.add("is-dragging");
1808
- stage.setPointerCapture(event.pointerId);
1816
+ try {
1817
+ stage.setPointerCapture(event.pointerId);
1818
+ } catch {
1819
+ }
1809
1820
  };
1810
1821
  const onPointerMove = (event) => {
1811
- if (!dragging) {
1822
+ if (!dragging || event.pointerId !== activePointerId) {
1812
1823
  return;
1813
1824
  }
1814
1825
  offsetX = startOffsetX + event.clientX - dragStartX;
1815
1826
  offsetY = startOffsetY + event.clientY - dragStartY;
1816
1827
  updateTransform();
1817
1828
  };
1818
- const onPointerUp = (event) => {
1829
+ const finishDrag = (pointerId) => {
1830
+ const captureId = pointerId ?? activePointerId;
1819
1831
  dragging = false;
1832
+ activePointerId = null;
1820
1833
  stage.classList.remove("is-dragging");
1821
- if (stage.hasPointerCapture(event.pointerId)) {
1822
- stage.releasePointerCapture(event.pointerId);
1834
+ if (captureId !== null && captureId !== void 0) {
1835
+ try {
1836
+ if (stage.hasPointerCapture(captureId)) {
1837
+ stage.releasePointerCapture(captureId);
1838
+ }
1839
+ } catch {
1840
+ }
1823
1841
  }
1824
1842
  };
1843
+ const onPointerUp = (event) => {
1844
+ if (event.pointerId === activePointerId) {
1845
+ finishDrag(event.pointerId);
1846
+ }
1847
+ };
1848
+ const onLostPointerCapture = (event) => {
1849
+ if (event.pointerId === activePointerId) {
1850
+ finishDrag(null);
1851
+ }
1852
+ };
1853
+ const onPointerLeave = (event) => {
1854
+ if (event.pointerId === activePointerId && event.buttons === 0) {
1855
+ finishDrag(event.pointerId);
1856
+ }
1857
+ };
1858
+ const onWindowBlur = () => {
1859
+ finishDrag();
1860
+ };
1825
1861
  const onWheel = (event) => {
1826
1862
  if (!event.ctrlKey && !event.metaKey) {
1827
1863
  return;
@@ -1833,8 +1869,11 @@ function imagePlugin() {
1833
1869
  stage.addEventListener("pointermove", onPointerMove);
1834
1870
  stage.addEventListener("pointerup", onPointerUp);
1835
1871
  stage.addEventListener("pointercancel", onPointerUp);
1872
+ stage.addEventListener("lostpointercapture", onLostPointerCapture);
1873
+ stage.addEventListener("pointerleave", onPointerLeave);
1836
1874
  stage.addEventListener("wheel", onWheel, { passive: false });
1837
1875
  image.addEventListener("error", showImageFallback);
1876
+ window.addEventListener("blur", onWindowBlur);
1838
1877
  stage.append(image);
1839
1878
  wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
1840
1879
  ctx.viewport.append(wrapper);
@@ -1881,8 +1920,12 @@ function imagePlugin() {
1881
1920
  stage.removeEventListener("pointermove", onPointerMove);
1882
1921
  stage.removeEventListener("pointerup", onPointerUp);
1883
1922
  stage.removeEventListener("pointercancel", onPointerUp);
1923
+ stage.removeEventListener("lostpointercapture", onLostPointerCapture);
1924
+ stage.removeEventListener("pointerleave", onPointerLeave);
1884
1925
  stage.removeEventListener("wheel", onWheel);
1885
1926
  image.removeEventListener("error", showImageFallback);
1927
+ window.removeEventListener("blur", onWindowBlur);
1928
+ finishDrag();
1886
1929
  wrapper.remove();
1887
1930
  if (convertedBlob) {
1888
1931
  URL.revokeObjectURL(url);
@@ -3275,6 +3318,95 @@ function readAiffExtended(bytes, offset) {
3275
3318
  return mantissa * Math.pow(2, exponent - 16383 - 63);
3276
3319
  }
3277
3320
 
3321
+ // src/plugins/utils.ts
3322
+ async function readArrayBuffer(file) {
3323
+ if (file.source instanceof ArrayBuffer) {
3324
+ return file.source;
3325
+ }
3326
+ if (file.blob) {
3327
+ return file.blob.arrayBuffer();
3328
+ }
3329
+ if (typeof file.source === "string") {
3330
+ const response = await fetch(file.source);
3331
+ if (!response.ok) {
3332
+ throw new Error(`Failed to fetch file: ${response.status}`);
3333
+ }
3334
+ return response.arrayBuffer();
3335
+ }
3336
+ throw new Error("Unsupported file source.");
3337
+ }
3338
+ async function readTextFile(file) {
3339
+ const decode = (buffer) => decodeTextBuffer(buffer);
3340
+ if (typeof file.source === "string") {
3341
+ const response = await fetch(file.source);
3342
+ if (!response.ok) {
3343
+ throw new Error(`Failed to fetch text file: ${response.status}`);
3344
+ }
3345
+ return decode(await response.arrayBuffer());
3346
+ }
3347
+ if (file.blob) {
3348
+ return decode(await file.blob.arrayBuffer());
3349
+ }
3350
+ if (file.source instanceof ArrayBuffer) {
3351
+ return decode(file.source);
3352
+ }
3353
+ return String(file.source);
3354
+ }
3355
+ function createPanel(className = "") {
3356
+ const panel = document.createElement("div");
3357
+ panel.className = `ofv-panel ${className}`.trim();
3358
+ return panel;
3359
+ }
3360
+ function createSection(title) {
3361
+ const section = document.createElement("section");
3362
+ section.className = "ofv-section";
3363
+ const heading = document.createElement("h3");
3364
+ heading.textContent = title;
3365
+ section.append(heading);
3366
+ return section;
3367
+ }
3368
+ function appendMeta(parent, label, value) {
3369
+ const row = document.createElement("div");
3370
+ row.className = "ofv-meta-row";
3371
+ const key = document.createElement("span");
3372
+ key.textContent = label;
3373
+ const content = document.createElement("strong");
3374
+ content.textContent = String(value);
3375
+ row.append(key, content);
3376
+ parent.append(row);
3377
+ }
3378
+ function decodeTextBuffer(buffer) {
3379
+ return decodeTextBytes(new Uint8Array(buffer));
3380
+ }
3381
+ function decodeTextBytes(bytes) {
3382
+ if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
3383
+ return new TextDecoder("utf-8").decode(bytes.subarray(3));
3384
+ }
3385
+ if (bytes.length >= 2) {
3386
+ if (bytes[0] === 255 && bytes[1] === 254) {
3387
+ return new TextDecoder("utf-16le").decode(bytes.subarray(2));
3388
+ }
3389
+ if (bytes[0] === 254 && bytes[1] === 255) {
3390
+ return new TextDecoder("utf-16be").decode(bytes.subarray(2));
3391
+ }
3392
+ }
3393
+ try {
3394
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
3395
+ } catch {
3396
+ return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
3397
+ }
3398
+ }
3399
+ function decodeWithFallback(bytes, encoding) {
3400
+ try {
3401
+ return new TextDecoder(encoding).decode(bytes);
3402
+ } catch {
3403
+ return void 0;
3404
+ }
3405
+ }
3406
+ function resolveFormat(file, mimeMap) {
3407
+ return file.extension || mimeMap[file.mimeType] || "";
3408
+ }
3409
+
3278
3410
  // src/plugins/text.ts
3279
3411
  var langMap = {
3280
3412
  js: "javascript",
@@ -3931,17 +4063,49 @@ async function readText(source) {
3931
4063
  if (!response.ok) {
3932
4064
  throw new Error(`Failed to fetch text file: ${response.status}`);
3933
4065
  }
3934
- return response.text();
4066
+ return decodeTextBuffer(await response.arrayBuffer());
3935
4067
  }
3936
4068
  if (source instanceof Blob) {
3937
- return source.text();
4069
+ return decodeTextBuffer(await source.arrayBuffer());
3938
4070
  }
3939
4071
  if (source instanceof ArrayBuffer) {
3940
- return new TextDecoder().decode(source);
4072
+ return decodeTextBuffer(source);
3941
4073
  }
3942
4074
  return String(source);
3943
4075
  }
3944
4076
 
4077
+ // src/plugins/encrypted.ts
4078
+ function createEncryptedFallback(file, url, copy = {}) {
4079
+ const fallback = document.createElement("div");
4080
+ fallback.className = "ofv-fallback ofv-encrypted";
4081
+ const title = document.createElement("strong");
4082
+ title.textContent = copy.title || "\u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8";
4083
+ const message = document.createElement("span");
4084
+ message.textContent = copy.message || "\u8BF7\u4E0B\u8F7D\u540E\u5728\u672C\u5730\u8F93\u5165\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684\u6587\u4EF6\u3002";
4085
+ const meta = document.createElement("dl");
4086
+ meta.className = "ofv-fallback-meta ofv-encrypted-meta";
4087
+ appendEncryptedMeta(meta, "\u6587\u4EF6", file.name || "\u672A\u547D\u540D\u6587\u4EF6");
4088
+ appendEncryptedMeta(meta, "\u683C\u5F0F", file.extension ? `.${file.extension}` : file.mimeType || "\u672A\u77E5");
4089
+ const download = document.createElement("a");
4090
+ download.href = url;
4091
+ download.download = file.name;
4092
+ download.textContent = copy.action || "\u4E0B\u8F7D\u6587\u4EF6";
4093
+ fallback.append(title, message, meta, download);
4094
+ return fallback;
4095
+ }
4096
+ function isEncryptedError(error) {
4097
+ const message = error instanceof Error ? error.message : String(error || "");
4098
+ const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
4099
+ return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(`${name} ${message}`);
4100
+ }
4101
+ function appendEncryptedMeta(parent, label, value) {
4102
+ const key = document.createElement("dt");
4103
+ key.textContent = label;
4104
+ const content = document.createElement("dd");
4105
+ content.textContent = value;
4106
+ parent.append(key, content);
4107
+ }
4108
+
3945
4109
  // src/plugins/pdf.ts
3946
4110
  function multiplyMatrices(m1, m2) {
3947
4111
  return [
@@ -3983,7 +4147,11 @@ function pdfPlugin(options = {}) {
3983
4147
  const doc = await documentTask.promise.catch((error) => {
3984
4148
  viewer.remove();
3985
4149
  ctx.viewport.classList.add("ofv-center");
3986
- const fallback = createPdfFallback(ctx.file.name, url, normalizePdfError(error));
4150
+ const fallback = isEncryptedError(error) ? createEncryptedFallback(ctx.file, url, {
4151
+ title: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4152
+ message: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
4153
+ action: "\u4E0B\u8F7D PDF"
4154
+ }) : createPdfFallback(ctx.file.name, url, normalizePdfError(error));
3987
4155
  ctx.viewport.append(fallback);
3988
4156
  return void 0;
3989
4157
  });
@@ -4273,9 +4441,6 @@ function normalizePdfError(error) {
4273
4441
  const message = error instanceof Error ? error.message : String(error || "");
4274
4442
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
4275
4443
  const lower = `${name} ${message}`.toLowerCase();
4276
- if (lower.includes("password")) {
4277
- return "\u8BE5 PDF \u53D7\u5BC6\u7801\u4FDD\u62A4\uFF0C\u5F53\u524D\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u5185\u76F4\u63A5\u9884\u89C8\u3002";
4278
- }
4279
4444
  if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
4280
4445
  return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
4281
4446
  }
@@ -4294,68 +4459,6 @@ function configurePdfWorker(pdf, workerSrc) {
4294
4459
  // src/plugins/epub.ts
4295
4460
  import JSZip from "jszip";
4296
4461
  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
4462
  var epubMimeTypes = /* @__PURE__ */ new Set(["application/epub+zip", "application/x-epub+zip"]);
4360
4463
  function epubPlugin() {
4361
4464
  return {
@@ -4975,8 +5078,15 @@ function officePlugin() {
4975
5078
  ctx.viewport.append(panel);
4976
5079
  const extension = resolveFormat(ctx.file, officeMimeFormatMap);
4977
5080
  const arrayBuffer = await readArrayBuffer(ctx.file);
5081
+ const packageFormat = shouldSniffPackagedOffice(extension) ? await detectPackagedOfficeFormat(arrayBuffer) : void 0;
4978
5082
  let disposeDocxFit;
4979
- if (fileIsDocx(extension)) {
5083
+ if (packageFormat === "docx" && !fileIsDocx(extension)) {
5084
+ disposeDocxFit = await renderDocx(panel, arrayBuffer);
5085
+ } else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
5086
+ await renderSheet(panel, arrayBuffer, "xlsx");
5087
+ } else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
5088
+ await renderPptx(panel, arrayBuffer);
5089
+ } else if (fileIsDocx(extension)) {
4980
5090
  disposeDocxFit = await renderDocx(panel, arrayBuffer);
4981
5091
  } else if (extension === "rtf") {
4982
5092
  renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
@@ -5012,12 +5122,32 @@ function officePlugin() {
5012
5122
  function fileIsDocx(extension) {
5013
5123
  return extension === "docx" || extension === "docm" || extension === "dotx" || extension === "dotm";
5014
5124
  }
5125
+ function shouldSniffPackagedOffice(extension) {
5126
+ return isLegacyOfficeBinary(extension) || packagedOfficeCandidates.has(extension) || extension === "";
5127
+ }
5128
+ async function detectPackagedOfficeFormat(arrayBuffer) {
5129
+ try {
5130
+ const zip = await JSZip3.loadAsync(arrayBuffer);
5131
+ const entries = Object.values(zip.files).filter((entry) => !entry.dir);
5132
+ const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
5133
+ if (hasEntry("word/document.xml")) {
5134
+ return "docx";
5135
+ }
5136
+ if (hasEntry("xl/workbook.xml")) {
5137
+ return "xlsx";
5138
+ }
5139
+ if (hasEntry("ppt/presentation.xml")) {
5140
+ return "pptx";
5141
+ }
5142
+ } catch {
5143
+ return void 0;
5144
+ }
5145
+ return void 0;
5146
+ }
5015
5147
  async function renderDocx(panel, arrayBuffer) {
5016
- const section = createSection("Word \u6587\u6863");
5017
5148
  const content = document.createElement("div");
5018
5149
  content.className = "ofv-docx-document";
5019
- section.append(content);
5020
- panel.append(section);
5150
+ panel.append(content);
5021
5151
  try {
5022
5152
  const docxPreview = await import("docx-preview");
5023
5153
  await docxPreview.renderAsync(arrayBuffer, content, content, {
@@ -5238,7 +5368,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5238
5368
  const xlsx = await import("xlsx");
5239
5369
  let workbook;
5240
5370
  try {
5241
- workbook = xlsx.read(arrayBuffer, { type: "array" });
5371
+ workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), { type: "string", FS: extension === "tsv" ? " " : "," }) : xlsx.read(arrayBuffer, { type: "array" });
5242
5372
  } catch (error) {
5243
5373
  if (isLegacyOfficeBinary(extension)) {
5244
5374
  renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
@@ -5340,6 +5470,10 @@ async function renderSheet(panel, arrayBuffer, extension) {
5340
5470
  }
5341
5471
  }
5342
5472
  function renderSheetFallback(panel, extension, detail) {
5473
+ if (isEncryptedText(detail)) {
5474
+ renderEncryptedOfficeByFileInfo(panel, `.${extension || "sheet"}`, "Office \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8");
5475
+ return;
5476
+ }
5343
5477
  const section = createSection("\u8868\u683C\u89E3\u6790\u5931\u8D25");
5344
5478
  const title = document.createElement("p");
5345
5479
  title.textContent = `.${extension || "sheet"} \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790\u4E3A\u53EF\u9884\u89C8\u8868\u683C\u3002`;
@@ -5350,6 +5484,17 @@ function renderSheetFallback(panel, extension, detail) {
5350
5484
  section.append(title, meta, support);
5351
5485
  panel.append(section);
5352
5486
  }
5487
+ function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
5488
+ const section = createSection(title);
5489
+ section.classList.add("ofv-encrypted");
5490
+ const message = document.createElement("p");
5491
+ message.textContent = `${fileLabel} \u53EF\u80FD\u5DF2\u52A0\u5BC6\u6216\u53D7\u4FDD\u62A4\u3002\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528 Office/WPS \u8F93\u5165\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684\u6587\u4EF6\u3002`;
5492
+ section.append(message);
5493
+ panel.append(section);
5494
+ }
5495
+ function isEncryptedText(value) {
5496
+ return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(value);
5497
+ }
5353
5498
  function renderFlatOds(panel, xml) {
5354
5499
  const sheets = parseFlatOds(xml);
5355
5500
  renderParsedSheets(panel, sheets, "FODS \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002");
@@ -5871,10 +6016,30 @@ async function renderPptx(panel, arrayBuffer) {
5871
6016
  try {
5872
6017
  const { PptxViewer } = await import("@aiden0z/pptx-renderer");
5873
6018
  await PptxViewer.open(arrayBuffer, container);
6019
+ normalizePptxLayout(container);
5874
6020
  } catch {
5875
6021
  container.textContent = "PPTX \u6E32\u67D3\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u635F\u574F\u3002";
5876
6022
  }
5877
6023
  }
6024
+ function normalizePptxLayout(container) {
6025
+ const slideCanvases = findPptxSlideCanvases(container);
6026
+ for (const slide of slideCanvases) {
6027
+ slide.style.backgroundColor = "#FFFFFF";
6028
+ }
6029
+ }
6030
+ function findPptxSlideCanvases(container) {
6031
+ const slideWrappers = Array.from(container.querySelectorAll("div[data-slide-index]"));
6032
+ const candidates = slideWrappers.flatMap(
6033
+ (wrapper) => Array.from(wrapper.querySelectorAll("div")).filter(isPptxSlideCanvas)
6034
+ );
6035
+ if (candidates.length > 0) {
6036
+ return Array.from(new Set(candidates));
6037
+ }
6038
+ return Array.from(container.querySelectorAll("div")).filter(isPptxSlideCanvas);
6039
+ }
6040
+ function isPptxSlideCanvas(element) {
6041
+ return element.style.position === "relative" && parseCssPixelValue(element.style.width) > 0 && parseCssPixelValue(element.style.height) > 0;
6042
+ }
5878
6043
  async function renderOdp(panel, arrayBuffer) {
5879
6044
  const zip = await JSZip3.loadAsync(arrayBuffer);
5880
6045
  const content = zip.file("content.xml");
@@ -5902,34 +6067,28 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
5902
6067
  const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
5903
6068
  const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
5904
6069
  if (hasEntry("word/document.xml")) {
5905
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Word \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 DOCX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5906
6070
  await renderDocx(panel, arrayBuffer);
5907
6071
  return true;
5908
6072
  }
5909
6073
  if (hasEntry("xl/workbook.xml")) {
5910
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Workbook \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 XLSX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5911
6074
  await renderSheet(panel, arrayBuffer, extension);
5912
6075
  return true;
5913
6076
  }
5914
6077
  if (hasEntry("ppt/presentation.xml")) {
5915
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 PPTX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5916
6078
  await renderPptx(panel, arrayBuffer);
5917
6079
  return true;
5918
6080
  }
5919
6081
  if (contentXml) {
5920
6082
  const xml = await contentXml.async("text");
5921
6083
  if (/<office:spreadsheet\b|<table:table\b/i.test(xml)) {
5922
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Spreadsheet \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODS \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5923
6084
  renderParsedSheets(panel, parseFlatOds(xml), `${extension.toUpperCase()} \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002`);
5924
6085
  return true;
5925
6086
  }
5926
6087
  if (/<office:presentation\b|<draw:page\b/i.test(xml)) {
5927
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODP \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5928
6088
  renderOpenDocumentPresentation(panel, `${extension.toUpperCase()} \u6F14\u793A\u6587\u7A3F`, xml, await extractZipImages(zip, /^Pictures\//));
5929
6089
  return true;
5930
6090
  }
5931
6091
  if (/<office:text\b|<text:p\b/i.test(xml)) {
5932
- renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Text \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODT \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
5933
6092
  renderOpenDocumentXml(panel, `${extension.toUpperCase()} \u6587\u6863`, xml);
5934
6093
  return true;
5935
6094
  }
@@ -5955,14 +6114,6 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
5955
6114
  }
5956
6115
  return false;
5957
6116
  }
5958
- function renderOfficePackageNotice(panel, extension, message) {
5959
- const section = createSection("\u517C\u5BB9\u5305\u8BC6\u522B");
5960
- const note = document.createElement("p");
5961
- note.className = "ofv-office-package-note";
5962
- note.textContent = `.${extension} ${message}`;
5963
- section.append(note);
5964
- panel.append(section);
5965
- }
5966
6117
  function renderOfficePackageStructure(panel, extension, entries, message, metadata) {
5967
6118
  const section = createSection("Office \u5305\u7ED3\u6784\u9884\u89C8");
5968
6119
  const note = document.createElement("p");
@@ -6610,7 +6761,7 @@ function rtfToText(rtf) {
6610
6761
  return rtf.replace(/\\'[0-9a-fA-F]{2}/g, "").replace(/\\par[d]?/g, "\n").replace(/\\tab/g, " ").replace(/\\[a-zA-Z]+\d* ?/g, "").replace(/[{}]/g, "").replace(/\n{3,}/g, "\n\n").trim();
6611
6762
  }
6612
6763
  async function readTextFromBuffer(arrayBuffer) {
6613
- return new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
6764
+ return decodeTextBuffer(arrayBuffer);
6614
6765
  }
6615
6766
  function mimeTypeFromPath(path) {
6616
6767
  const extension = path.split(".").pop()?.toLowerCase();
@@ -6652,7 +6803,7 @@ function ofdPlugin() {
6652
6803
  try {
6653
6804
  zip = await JSZip4.loadAsync(await readArrayBuffer(ctx.file));
6654
6805
  } catch (error) {
6655
- panel.append(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
6806
+ panel.append(createOfdFailure(ctx.file, url, error));
6656
6807
  return {
6657
6808
  destroy() {
6658
6809
  panel.remove();
@@ -6669,7 +6820,7 @@ function ofdPlugin() {
6669
6820
  textFragments.push(...matches);
6670
6821
  }
6671
6822
  } catch (error) {
6672
- panel.append(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
6823
+ panel.append(createOfdFailure(ctx.file, url, error));
6673
6824
  return {
6674
6825
  destroy() {
6675
6826
  panel.remove();
@@ -6677,36 +6828,72 @@ function ofdPlugin() {
6677
6828
  }
6678
6829
  };
6679
6830
  }
6680
- const images = await readOfdImages(entries);
6681
- const pages = await readOfdPages(entries, images);
6682
- const section = createSection("OFD \u57FA\u7840\u9884\u89C8");
6683
- const note = document.createElement("p");
6684
- note.textContent = pages.length > 0 ? "\u5F53\u524D\u7248\u672C\u63D0\u53D6 OFD \u9875\u9762\u6587\u672C\u3001\u8DEF\u5F84\u3001\u76F4\u7EBF\u548C\u56FE\u7247\u5BF9\u8C61\uFF0C\u5E76\u6309 Boundary \u5750\u6807\u751F\u6210\u8F7B\u91CF SVG \u7248\u5F0F\u9884\u89C8\u3002\u590D\u6742\u5B57\u4F53\u3001\u7B7E\u7AE0\u548C\u989C\u8272\u7A7A\u95F4\u53EF\u540E\u7EED\u63A5\u5165\u4E13\u7528 OFD \u6E32\u67D3\u5668\u3002" : "\u5F53\u524D\u7248\u672C\u63D0\u53D6 OFD \u5305\u5185 XML \u6587\u672C\u548C\u6587\u4EF6\u7ED3\u6784\u3002\u7248\u5F0F\u7EA7\u6E32\u67D3\u53EF\u5728\u540E\u7EED\u63A5\u5165\u4E13\u7528 OFD \u6E32\u67D3\u5668\u3002";
6685
- section.append(note);
6686
- section.append(createOfdSummary(entries, pages, images));
6831
+ const context = await readOfdContext(entries);
6832
+ const pages = await readOfdPages(entries, context);
6833
+ let zoom = 1;
6834
+ let rotation = 0;
6835
+ const applyZoom = () => {
6836
+ panel.style.setProperty("--ofv-ofd-zoom", formatOfdCssNumber(zoom));
6837
+ ctx.toolbar?.setZoom(zoom);
6838
+ };
6839
+ const applyRotation = () => {
6840
+ const normalizedRotation = (rotation % 360 + 360) % 360;
6841
+ panel.style.setProperty("--ofv-ofd-rotation", `${normalizedRotation}deg`);
6842
+ panel.classList.toggle("is-ofd-rotated-sideways", normalizedRotation === 90 || normalizedRotation === 270);
6843
+ };
6687
6844
  if (pages.length > 0) {
6688
6845
  const pagesWrap = document.createElement("div");
6689
6846
  pagesWrap.className = "ofv-ofd-pages";
6690
6847
  for (const page of pages) {
6691
6848
  pagesWrap.append(renderOfdPage(page));
6692
6849
  }
6693
- section.append(pagesWrap);
6694
- }
6695
- const content = document.createElement("pre");
6696
- content.className = "ofv-text-block";
6697
- content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
6698
- section.append(content);
6699
- const list = createSection(`\u6587\u4EF6\u7ED3\u6784 ${entries.length}`);
6700
- const ul = document.createElement("ul");
6701
- for (const entry of entries.slice(0, 200)) {
6702
- const li = document.createElement("li");
6703
- li.textContent = entry.name;
6704
- ul.append(li);
6705
- }
6706
- list.append(ul);
6707
- panel.append(section, list);
6850
+ panel.append(pagesWrap);
6851
+ applyZoom();
6852
+ applyRotation();
6853
+ }
6854
+ if (pages.length === 0) {
6855
+ const content = document.createElement("pre");
6856
+ content.className = "ofv-text-block";
6857
+ content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
6858
+ panel.append(content);
6859
+ }
6708
6860
  return {
6861
+ canCommand(command) {
6862
+ return pages.length > 0 && (command === "zoom-in" || command === "zoom-out" || command === "zoom-reset" || command === "rotate-right" || command === "rotate-left");
6863
+ },
6864
+ command(command) {
6865
+ if (pages.length === 0) {
6866
+ return false;
6867
+ }
6868
+ if (command === "zoom-in") {
6869
+ zoom = Math.min(4, zoom + 0.15);
6870
+ applyZoom();
6871
+ return true;
6872
+ }
6873
+ if (command === "zoom-out") {
6874
+ zoom = Math.max(0.25, zoom - 0.15);
6875
+ applyZoom();
6876
+ return true;
6877
+ }
6878
+ if (command === "zoom-reset") {
6879
+ zoom = 1;
6880
+ applyZoom();
6881
+ return true;
6882
+ }
6883
+ if (command === "rotate-right") {
6884
+ rotation += 90;
6885
+ applyRotation();
6886
+ return true;
6887
+ }
6888
+ if (command === "rotate-left") {
6889
+ rotation -= 90;
6890
+ applyRotation();
6891
+ return true;
6892
+ }
6893
+ return false;
6894
+ },
6709
6895
  destroy() {
6896
+ ctx.toolbar?.setZoom(void 0);
6710
6897
  panel.remove();
6711
6898
  revokeObjectUrl(url, isExternal);
6712
6899
  }
@@ -6714,76 +6901,71 @@ function ofdPlugin() {
6714
6901
  }
6715
6902
  };
6716
6903
  }
6717
- function createOfdSummary(entries, pages, images) {
6718
- const summary = document.createElement("div");
6719
- summary.className = "ofv-ofd-summary";
6720
- const xmlEntries = entries.filter((entry) => entry.name.endsWith(".xml")).length;
6721
- const textCount = pages.reduce((count, page) => count + page.texts.length, 0);
6722
- const pathCount = pages.reduce((count, page) => count + page.paths.length, 0);
6723
- const lineCount = pages.reduce((count, page) => count + page.lines.length, 0);
6724
- const imageCount = pages.reduce((count, page) => count + page.images.length, 0);
6725
- const textLength = pages.reduce((count, page) => count + page.texts.reduce((inner, item) => inner + item.text.length, 0), 0);
6726
- appendOfdSummary(summary, "\u6587\u4EF6", String(entries.length));
6727
- appendOfdSummary(summary, "XML", String(xmlEntries));
6728
- appendOfdSummary(summary, "\u9875\u9762", String(pages.length));
6729
- appendOfdSummary(summary, "\u6587\u672C", String(textCount));
6730
- appendOfdSummary(summary, "\u8DEF\u5F84", String(pathCount));
6731
- appendOfdSummary(summary, "\u7EBF\u6761", String(lineCount));
6732
- appendOfdSummary(summary, "\u56FE\u7247\u5BF9\u8C61", String(imageCount));
6733
- appendOfdSummary(summary, "\u56FE\u7247\u8D44\u6E90", String(uniqueOfdImageResources(images)));
6734
- if (textLength > 0) {
6735
- appendOfdSummary(summary, "\u6587\u5B57\u957F\u5EA6", String(textLength));
6736
- }
6737
- const sizes = formatOfdPageSizes(pages);
6738
- if (sizes) {
6739
- appendOfdSummary(summary, "\u9875\u9762\u5C3A\u5BF8", sizes);
6740
- }
6741
- return summary;
6742
- }
6743
- function appendOfdSummary(parent, label, value) {
6744
- const item = document.createElement("span");
6745
- const key = document.createElement("span");
6746
- key.textContent = label;
6747
- const content = document.createElement("strong");
6748
- content.textContent = value;
6749
- item.append(key, content);
6750
- parent.append(item);
6751
- }
6752
- function uniqueOfdImageResources(images) {
6753
- return new Set(images.values()).size;
6754
- }
6755
- function formatOfdPageSizes(pages) {
6756
- const counts = /* @__PURE__ */ new Map();
6757
- for (const page of pages) {
6758
- const key = `${Math.round(page.width)} x ${Math.round(page.height)}`;
6759
- counts.set(key, (counts.get(key) || 0) + 1);
6760
- }
6761
- return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([size, count]) => count > 1 ? `${size} (${count})` : size).join(", ");
6762
- }
6763
- async function readOfdPages(entries, images) {
6904
+ async function readOfdPages(entries, context) {
6764
6905
  const pages = [];
6765
6906
  const pageEntries = entries.filter((entry) => /(^|\/)Pages\/Page_[^/]+\/Content\.xml$/i.test(entry.name) || /(^|\/)Page_[^/]+\/Content\.xml$/i.test(entry.name)).slice(0, 80);
6766
6907
  for (const entry of pageEntries) {
6767
6908
  const xml = await entry.async("text");
6768
- const page = parseOfdPage(entry.name, xml, images);
6909
+ const templates = await readPageTemplates(xml, context, entries);
6910
+ const page = parseOfdPage(entry.name, xml, context.images, context.fonts, templates, context.pageSize);
6769
6911
  if (page.texts.length > 0 || page.paths.length > 0 || page.lines.length > 0 || page.images.length > 0) {
6770
6912
  pages.push(page);
6771
6913
  }
6772
6914
  }
6773
6915
  return pages;
6774
6916
  }
6775
- function parseOfdPage(name, xml, images) {
6917
+ async function readPageTemplates(xml, context, entries) {
6918
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
6919
+ if (doc.querySelector("parsererror")) {
6920
+ return [];
6921
+ }
6922
+ const templateIds = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Template").map((element) => getOfdAttribute(element, "TemplateID") || getOfdAttribute(element, "ID")).filter((id) => Boolean(id));
6923
+ const templates = [];
6924
+ for (const id of templateIds) {
6925
+ const path = context.templates.get(id);
6926
+ const entry = path ? findOfdEntry(entries, path) : void 0;
6927
+ if (entry) {
6928
+ templates.push(await entry.async("text"));
6929
+ }
6930
+ }
6931
+ return templates;
6932
+ }
6933
+ function parseOfdPage(name, xml, images, fonts, templateXmls = [], defaultPageSize) {
6776
6934
  const doc = new DOMParser().parseFromString(xml, "application/xml");
6777
6935
  if (doc.querySelector("parsererror")) {
6778
6936
  return { name, width: 210, height: 297, texts: [], paths: [], lines: [], images: [] };
6779
6937
  }
6780
- const pageSize = parseOfdPageSize(doc);
6781
- const textObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TextObject");
6782
- const texts = textObjects.flatMap((element) => parseOfdTextObject(element));
6783
- const paths = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "PathObject").flatMap((element) => parseOfdPathObject(element));
6938
+ const pageSize = parseOfdPageSize(doc, defaultPageSize);
6939
+ const templatePages = templateXmls.map((templateXml) => {
6940
+ const templateDoc = new DOMParser().parseFromString(templateXml, "application/xml");
6941
+ return templateDoc.querySelector("parsererror") ? emptyOfdPageContent() : parseOfdPageContent(templateDoc, images, fonts);
6942
+ });
6943
+ const pageContent = parseOfdPageContent(doc, images, fonts);
6944
+ const texts = [...templatePages.flatMap((page) => page.texts), ...pageContent.texts];
6945
+ const paths = [...templatePages.flatMap((page) => page.paths), ...pageContent.paths];
6946
+ const lines = [...templatePages.flatMap((page) => page.lines), ...pageContent.lines];
6947
+ const imageObjects = [...templatePages.flatMap((page) => page.images), ...pageContent.images];
6948
+ if (pageSize.explicit) {
6949
+ return { name, width: pageSize.width, height: pageSize.height, texts, paths, lines, images: imageObjects };
6950
+ }
6951
+ const bounds = createOfdBounds(texts, paths, lines, imageObjects);
6952
+ const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
6953
+ const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
6954
+ return { name, width, height, texts, paths, lines, images: imageObjects };
6955
+ }
6956
+ function parseOfdPageContent(doc, images, fonts) {
6957
+ const textObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TextObject");
6958
+ const texts = textObjects.flatMap((element) => parseOfdTextObject(element, fonts));
6959
+ const paths = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "PathObject").flatMap((element) => parseOfdPathObject(element));
6784
6960
  const lines = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "LineObject").flatMap((element) => parseOfdLineObject(element));
6785
6961
  const imageObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ImageObject").flatMap((element) => parseOfdImageObject(element, images));
6786
- const bounds = [
6962
+ return { texts, paths, lines, images: imageObjects };
6963
+ }
6964
+ function emptyOfdPageContent() {
6965
+ return { texts: [], paths: [], lines: [], images: [] };
6966
+ }
6967
+ function createOfdBounds(texts, paths, lines, imageObjects) {
6968
+ return [
6787
6969
  ...texts.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
6788
6970
  ...paths.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
6789
6971
  ...lines.map((item) => ({
@@ -6794,38 +6976,63 @@ function parseOfdPage(name, xml, images) {
6794
6976
  })),
6795
6977
  ...imageObjects.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height }))
6796
6978
  ];
6797
- const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
6798
- const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
6799
- return { name, width, height, texts, paths, lines, images: imageObjects };
6800
6979
  }
6801
- function parseOfdTextObject(element) {
6980
+ function parseOfdTextObject(element, fonts) {
6802
6981
  const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
6803
6982
  const size = finiteNumber2(getOfdAttribute(element, "Size"), Math.max(4, boundary.height || 5));
6804
6983
  const color = parseOfdColor(element, "#111827");
6805
6984
  const weight = finiteNumber2(getOfdAttribute(element, "Weight"), 400) >= 600 ? "700" : "400";
6806
- const letterSpacing = getOfdAttribute(element, "DeltaX") ? 0.5 : void 0;
6985
+ const fontFamily = fontStackForOfdFont(fonts.get(getOfdAttribute(element, "Font") || ""));
6986
+ const objectLetterSpacing = getOfdAttribute(element, "DeltaX") ? 0.5 : void 0;
6807
6987
  const textCodes = Array.from(element.getElementsByTagName("*")).filter((child) => child.localName === "TextCode");
6808
6988
  if (textCodes.length === 0) {
6809
6989
  return [];
6810
6990
  }
6811
- return textCodes.map((code) => {
6991
+ return textCodes.flatMap((code) => {
6812
6992
  const x = boundary.x + finiteNumber2(getOfdAttribute(code, "X"), 0);
6813
6993
  const y = boundary.y + finiteNumber2(getOfdAttribute(code, "Y"), 0);
6814
- return {
6815
- text: code.textContent?.trim() || "",
6816
- x,
6817
- y,
6818
- width: boundary.width,
6819
- height: boundary.height,
6820
- size,
6821
- color,
6822
- weight,
6823
- letterSpacing
6824
- };
6994
+ const text = code.textContent?.trim() || "";
6995
+ const deltaX = parseOfdDeltaX(getOfdAttribute(code, "DeltaX"));
6996
+ const align = deltaX ? "start" : inferOfdTextAlign(text, boundary);
6997
+ const deltaY = getOfdAttribute(code, "DeltaY");
6998
+ if (deltaY && text.length > 1) {
6999
+ const step = parseOfdDeltaStep(deltaY, size);
7000
+ return Array.from(text).map((char, index) => ({
7001
+ text: char,
7002
+ x,
7003
+ y: y + index * step,
7004
+ width: boundary.width,
7005
+ height: boundary.height,
7006
+ size,
7007
+ color,
7008
+ weight,
7009
+ fontFamily,
7010
+ letterSpacing: objectLetterSpacing,
7011
+ vertical: true,
7012
+ align
7013
+ }));
7014
+ }
7015
+ return [
7016
+ {
7017
+ text,
7018
+ x,
7019
+ y,
7020
+ width: boundary.width,
7021
+ height: boundary.height,
7022
+ size,
7023
+ color,
7024
+ weight,
7025
+ fontFamily,
7026
+ letterSpacing: deltaX ? void 0 : objectLetterSpacing,
7027
+ deltaX,
7028
+ align
7029
+ }
7030
+ ];
6825
7031
  }).filter((item) => item.text);
6826
7032
  }
6827
7033
  function parseOfdPathObject(element) {
6828
7034
  const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
7035
+ const ctm = parseOfdCtm(getOfdAttribute(element, "CTM"));
6829
7036
  const commands = Array.from(element.getElementsByTagName("*")).filter(
6830
7037
  (child) => child.localName === "AbbreviatedData" || child.localName === "PathData"
6831
7038
  );
@@ -6840,9 +7047,10 @@ function parseOfdPathObject(element) {
6840
7047
  y: boundary.y,
6841
7048
  width: boundary.width,
6842
7049
  height: boundary.height,
6843
- stroke: parseOfdColor(element, "#334155", "StrokeColor"),
7050
+ stroke: parseOfdColor(element, "#111827", "StrokeColor"),
6844
7051
  fill: parseOfdFill(element),
6845
- strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
7052
+ strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1),
7053
+ transform: createOfdPathTransform(boundary.x, boundary.y, ctm)
6846
7054
  }
6847
7055
  ];
6848
7056
  }
@@ -6859,7 +7067,7 @@ function parseOfdLineObject(element) {
6859
7067
  y1: boundary.y + start.y,
6860
7068
  x2: boundary.x + end.x,
6861
7069
  y2: boundary.y + end.y,
6862
- stroke: parseOfdColor(element, "#334155"),
7070
+ stroke: parseOfdColor(element, "#111827"),
6863
7071
  strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
6864
7072
  }
6865
7073
  ];
@@ -6881,6 +7089,8 @@ function parseOfdImageObject(element, images) {
6881
7089
  function renderOfdPage(page) {
6882
7090
  const figure = document.createElement("figure");
6883
7091
  figure.className = "ofv-ofd-page";
7092
+ figure.style.setProperty("--ofv-ofd-page-width", `${formatOfdCssNumber(page.width)}mm`);
7093
+ figure.style.setProperty("--ofv-ofd-page-height", `${formatOfdCssNumber(page.height)}mm`);
6884
7094
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
6885
7095
  svg.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`);
6886
7096
  svg.setAttribute("role", "img");
@@ -6917,7 +7127,7 @@ function renderOfdPage(page) {
6917
7127
  for (const item of page.paths) {
6918
7128
  const path = document.createElementNS(svg.namespaceURI, "path");
6919
7129
  path.setAttribute("d", item.d);
6920
- path.setAttribute("transform", `translate(${item.x} ${item.y})`);
7130
+ path.setAttribute("transform", item.transform);
6921
7131
  path.setAttribute("fill", item.fill);
6922
7132
  path.setAttribute("stroke", item.stroke);
6923
7133
  path.setAttribute("stroke-width", String(item.strokeWidth));
@@ -6936,22 +7146,63 @@ function renderOfdPage(page) {
6936
7146
  }
6937
7147
  for (const item of page.texts) {
6938
7148
  const text = document.createElementNS(svg.namespaceURI, "text");
6939
- text.setAttribute("x", String(item.x));
6940
- text.setAttribute("y", String(item.y + item.size));
7149
+ text.setAttribute("x", String(item.align === "end" ? item.x + item.width : item.x));
7150
+ text.setAttribute("y", String(item.y));
6941
7151
  text.setAttribute("font-size", String(item.size));
6942
7152
  text.setAttribute("fill", item.color);
6943
7153
  text.setAttribute("font-weight", item.weight);
7154
+ text.setAttribute("font-family", item.fontFamily);
6944
7155
  if (item.letterSpacing !== void 0) {
6945
7156
  text.setAttribute("letter-spacing", String(item.letterSpacing));
6946
7157
  }
6947
- text.textContent = item.text;
7158
+ if (item.deltaX && item.deltaX.length > 0 && item.align !== "end") {
7159
+ const chars = Array.from(item.text);
7160
+ let x = item.x;
7161
+ for (let index = 0; index < chars.length; index += 1) {
7162
+ const span = document.createElementNS(svg.namespaceURI, "tspan");
7163
+ span.setAttribute("x", String(x));
7164
+ span.setAttribute("y", String(item.y));
7165
+ if (index < chars.length - 1) {
7166
+ x += item.deltaX[Math.min(index, item.deltaX.length - 1)] || item.size;
7167
+ }
7168
+ span.textContent = chars[index];
7169
+ text.append(span);
7170
+ }
7171
+ } else {
7172
+ if (item.align === "end") {
7173
+ text.setAttribute("text-anchor", "end");
7174
+ }
7175
+ text.textContent = item.text;
7176
+ }
6948
7177
  svg.append(text);
6949
7178
  }
6950
- const caption = document.createElement("figcaption");
6951
- caption.textContent = `${page.name} \xB7 ${page.texts.length} text \xB7 ${page.paths.length} path \xB7 ${page.lines.length} line \xB7 ${page.images.length} image`;
6952
- figure.append(svg, caption);
7179
+ figure.append(svg);
6953
7180
  return figure;
6954
7181
  }
7182
+ async function readOfdContext(entries) {
7183
+ const images = await readOfdImages(entries);
7184
+ const fonts = await readOfdFonts(entries);
7185
+ const { templates, pageSize } = await readOfdDocumentInfo(entries);
7186
+ return { images, templates, fonts, pageSize };
7187
+ }
7188
+ async function readOfdFonts(entries) {
7189
+ const fonts = /* @__PURE__ */ new Map();
7190
+ for (const entry of entries.filter((item) => /(?:^|\/)(?:DocumentRes|PublicRes)\.xml$/i.test(item.name))) {
7191
+ const xml = await entry.async("text");
7192
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7193
+ if (doc.querySelector("parsererror")) {
7194
+ continue;
7195
+ }
7196
+ for (const font of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Font")) {
7197
+ const id = getOfdAttribute(font, "ID");
7198
+ const name = getOfdAttribute(font, "FontName") || getOfdAttribute(font, "FamilyName");
7199
+ if (id && name) {
7200
+ fonts.set(id, name.trim());
7201
+ }
7202
+ }
7203
+ }
7204
+ return fonts;
7205
+ }
6955
7206
  async function readOfdImages(entries) {
6956
7207
  const images = /* @__PURE__ */ new Map();
6957
7208
  for (const entry of entries.filter((item) => /\.(?:png|jpe?g|gif|bmp|webp)$/i.test(item.name)).slice(0, 80)) {
@@ -6966,17 +7217,69 @@ async function readOfdImages(entries) {
6966
7217
  images.set(entry.name, href);
6967
7218
  images.set(entry.name.split("/").pop() || entry.name, href);
6968
7219
  }
7220
+ for (const entry of entries.filter((item) => /(?:^|\/)(?:DocumentRes|PublicRes)\.xml$/i.test(item.name))) {
7221
+ const xml = await entry.async("text");
7222
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7223
+ if (doc.querySelector("parsererror")) {
7224
+ continue;
7225
+ }
7226
+ const baseLoc = getOfdAttribute(doc.documentElement, "BaseLoc") || "";
7227
+ const resourceDir = joinOfdPath(directoryName2(entry.name), baseLoc);
7228
+ for (const media of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "MultiMedia")) {
7229
+ const id = getOfdAttribute(media, "ID");
7230
+ const mediaFile = findOfdChild(media, "MediaFile")?.textContent?.trim();
7231
+ if (!id || !mediaFile) {
7232
+ continue;
7233
+ }
7234
+ const imageEntry = findOfdEntry(entries, joinOfdPath(resourceDir, mediaFile)) || findOfdEntry(entries, mediaFile);
7235
+ const href = imageEntry ? images.get(imageEntry.name) || images.get(imageEntry.name.split("/").pop() || imageEntry.name) : void 0;
7236
+ if (href) {
7237
+ images.set(id, href);
7238
+ }
7239
+ }
7240
+ }
6969
7241
  return images;
6970
7242
  }
6971
- function parseOfdPageSize(doc) {
7243
+ async function readOfdDocumentInfo(entries) {
7244
+ const templates = /* @__PURE__ */ new Map();
7245
+ let pageSize;
7246
+ for (const entry of entries.filter((item) => /(?:^|\/)Document\.xml$/i.test(item.name))) {
7247
+ const xml = await entry.async("text");
7248
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
7249
+ if (doc.querySelector("parsererror")) {
7250
+ continue;
7251
+ }
7252
+ const documentDir = directoryName2(entry.name);
7253
+ const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PageArea")?.getElementsByTagName("*");
7254
+ const pageAreaBox = physicalBox ? Array.from(physicalBox).find((element) => element.localName === "PhysicalBox") : void 0;
7255
+ if (pageAreaBox?.textContent) {
7256
+ const box = parseBoundary(pageAreaBox.textContent);
7257
+ if (box.width > 0 && box.height > 0) {
7258
+ pageSize = { width: box.width, height: box.height };
7259
+ }
7260
+ }
7261
+ for (const template of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TemplatePage")) {
7262
+ const id = getOfdAttribute(template, "ID");
7263
+ const baseLoc = getOfdAttribute(template, "BaseLoc");
7264
+ if (id && baseLoc) {
7265
+ templates.set(id, joinOfdPath(documentDir, baseLoc));
7266
+ }
7267
+ }
7268
+ }
7269
+ return { templates, pageSize };
7270
+ }
7271
+ function parseOfdPageSize(doc, defaultPageSize) {
7272
+ if (defaultPageSize) {
7273
+ return { ...defaultPageSize, explicit: true };
7274
+ }
6972
7275
  const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PhysicalBox");
6973
7276
  if (physicalBox?.textContent) {
6974
7277
  const box = parseBoundary(physicalBox.textContent);
6975
7278
  if (box.width > 0 && box.height > 0) {
6976
- return { width: box.width, height: box.height };
7279
+ return { width: box.width, height: box.height, explicit: true };
6977
7280
  }
6978
7281
  }
6979
- return { width: 210, height: 297 };
7282
+ return { width: 210, height: 297, explicit: false };
6980
7283
  }
6981
7284
  function parseOfdColor(element, fallback, preferredLocalName = "FillColor") {
6982
7285
  const colorElement = findOfdChild(element, preferredLocalName) || findOfdChild(element, "StrokeColor") || findOfdChild(element, "FillColor");
@@ -7007,6 +7310,101 @@ function parsePoint(value, fallback) {
7007
7310
  function normalizeOfdPathData(value) {
7008
7311
  return value.replace(/\bM\s+/gi, "M ").replace(/\bL\s+/gi, "L ").replace(/\bC\s+/gi, "C ").replace(/\bQ\s+/gi, "Q ").replace(/\bA\s+/gi, "A ").replace(/\bB\s+/gi, "C ").replace(/\bZ\b/gi, "Z").replace(/\s+/g, " ").trim();
7009
7312
  }
7313
+ function parseOfdCtm(value) {
7314
+ const parts = (value || "").trim().split(/\s+/).map((part) => Number(part));
7315
+ if (parts.length !== 6 || parts.some((part) => !Number.isFinite(part))) {
7316
+ return void 0;
7317
+ }
7318
+ return parts;
7319
+ }
7320
+ function createOfdPathTransform(x, y, ctm) {
7321
+ if (!ctm) {
7322
+ return `translate(${x} ${y})`;
7323
+ }
7324
+ const [a, b, c, d, e, f] = ctm;
7325
+ return `translate(${x} ${y}) matrix(${a} ${b} ${c} ${d} ${e} ${f})`;
7326
+ }
7327
+ function parseOfdDeltaStep(value, fallback) {
7328
+ const numbers = value.match(/-?\d+(?:\.\d+)?/g)?.map((part) => Number(part)).filter((part) => Number.isFinite(part)) || [];
7329
+ return numbers.length > 0 ? numbers[numbers.length - 1] : fallback;
7330
+ }
7331
+ function parseOfdDeltaX(value) {
7332
+ if (!value) {
7333
+ return void 0;
7334
+ }
7335
+ const parts = value.match(/[a-z]+|-?\d+(?:\.\d+)?/gi) || [];
7336
+ const deltas = [];
7337
+ for (let index = 0; index < parts.length; index += 1) {
7338
+ const token = parts[index];
7339
+ if (/^g$/i.test(token)) {
7340
+ const count = Number(parts[index + 1]);
7341
+ const step = Number(parts[index + 2]);
7342
+ if (Number.isFinite(count) && Number.isFinite(step)) {
7343
+ deltas.push(...Array.from({ length: Math.max(0, Math.floor(count)) }, () => step));
7344
+ }
7345
+ index += 2;
7346
+ continue;
7347
+ }
7348
+ const numeric = Number(token);
7349
+ if (Number.isFinite(numeric)) {
7350
+ deltas.push(numeric);
7351
+ }
7352
+ }
7353
+ return deltas.length > 0 ? deltas : void 0;
7354
+ }
7355
+ function fontStackForOfdFont(fontName) {
7356
+ const normalized = (fontName || "").trim().toLowerCase();
7357
+ if (normalized.includes("courier")) {
7358
+ return '"Courier New", Courier, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
7359
+ }
7360
+ if (normalized.includes("kaiti") || normalized.includes("kai") || normalized.includes("\u6977")) {
7361
+ return '"STKaiti", "Kaiti SC", "KaiTi", "\u6977\u4F53", serif';
7362
+ }
7363
+ if (normalized.includes("simsun") || normalized.includes("simsong") || normalized.includes("song") || normalized.includes("\u5B8B")) {
7364
+ return '"SimSong", "Songti SC", "STSong", SimSun, "\u5B8B\u4F53", serif';
7365
+ }
7366
+ if (normalized.includes("hei") || normalized.includes("\u9ED1")) {
7367
+ return '"PingFang SC", "Microsoft YaHei", SimHei, sans-serif';
7368
+ }
7369
+ return '"SimSong", "Songti SC", "STSong", SimSun, "Noto Serif CJK SC", serif';
7370
+ }
7371
+ function inferOfdTextAlign(text, boundary) {
7372
+ const normalized = text.trim();
7373
+ if (!/^[¥¥]?\d+(?:\.\d+)?%?$/.test(normalized)) {
7374
+ return "start";
7375
+ }
7376
+ if (boundary.x >= 75 || boundary.width <= 30) {
7377
+ return "end";
7378
+ }
7379
+ return "start";
7380
+ }
7381
+ function findOfdEntry(entries, path) {
7382
+ const normalized = normalizeOfdPath(path);
7383
+ return entries.find((entry) => normalizeOfdPath(entry.name) === normalized || normalizeOfdPath(entry.name).endsWith(`/${normalized}`));
7384
+ }
7385
+ function joinOfdPath(...parts) {
7386
+ const joined = parts.filter(Boolean).join("/");
7387
+ const segments = [];
7388
+ for (const segment of joined.split("/")) {
7389
+ if (!segment || segment === ".") {
7390
+ continue;
7391
+ }
7392
+ if (segment === "..") {
7393
+ segments.pop();
7394
+ continue;
7395
+ }
7396
+ segments.push(segment);
7397
+ }
7398
+ return segments.join("/");
7399
+ }
7400
+ function directoryName2(path) {
7401
+ const normalized = normalizeOfdPath(path);
7402
+ const index = normalized.lastIndexOf("/");
7403
+ return index >= 0 ? normalized.slice(0, index) : "";
7404
+ }
7405
+ function normalizeOfdPath(path) {
7406
+ return path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+/g, "/");
7407
+ }
7010
7408
  function mimeTypeFromPath2(path) {
7011
7409
  const extension = path.split(".").pop()?.toLowerCase();
7012
7410
  const map = {
@@ -7044,6 +7442,19 @@ function finiteNumber2(value, fallback) {
7044
7442
  const parsed = value === null ? Number.NaN : Number(value);
7045
7443
  return Number.isFinite(parsed) ? parsed : fallback;
7046
7444
  }
7445
+ function formatOfdCssNumber(value) {
7446
+ return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7447
+ }
7448
+ function createOfdFailure(file, url, error) {
7449
+ if (isEncryptedError(error)) {
7450
+ return createEncryptedFallback(file, url, {
7451
+ title: "OFD \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
7452
+ message: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u672C\u5730 OFD \u9605\u8BFB\u5668\u8F93\u5165\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 OFD \u6587\u4EF6\u3002",
7453
+ action: "\u4E0B\u8F7D OFD"
7454
+ });
7455
+ }
7456
+ return createOfdFallback(file.name, url, normalizeOfdError(error));
7457
+ }
7047
7458
  function createOfdFallback(fileName, url, detail) {
7048
7459
  const fallback = document.createElement("div");
7049
7460
  fallback.className = "ofv-fallback";
@@ -7112,7 +7523,9 @@ function archivePlugin() {
7112
7523
  try {
7113
7524
  if (ext === "zip") {
7114
7525
  try {
7115
- const zip = await JSZip5.loadAsync(await readArrayBuffer(ctx.file));
7526
+ const zip = await JSZip5.loadAsync(await readArrayBuffer(ctx.file), {
7527
+ decodeFileName: decodeZipFileName
7528
+ });
7116
7529
  archiveEntries = Object.values(zip.files).map((entry) => ({
7117
7530
  name: entry.name,
7118
7531
  unsafeName: entry.unsafeOriginalName,
@@ -7121,7 +7534,7 @@ function archivePlugin() {
7121
7534
  read: () => entry.async("arraybuffer")
7122
7535
  }));
7123
7536
  } catch (zipErr) {
7124
- if (zipErr.message && (zipErr.message.includes("encrypted") || zipErr.message.includes("password") || zipErr.message.includes("protected"))) {
7537
+ if (isEncryptedError(zipErr)) {
7125
7538
  isEncrypted = true;
7126
7539
  } else {
7127
7540
  throw zipErr;
@@ -7154,17 +7567,11 @@ function archivePlugin() {
7154
7567
  parseError = `\u538B\u7F29\u5305\u89E3\u6790\u5931\u8D25\uFF1A${err.message || err}`;
7155
7568
  }
7156
7569
  if (isEncrypted) {
7157
- const fallback = document.createElement("div");
7158
- fallback.className = "ofv-fallback";
7159
- const title = document.createElement("strong");
7160
- title.textContent = "\u8BE5\u538B\u7F29\u5305\u5DF2\u88AB\u52A0\u5BC6\u4FDD\u62A4";
7161
- const meta = document.createElement("span");
7162
- meta.textContent = "\u4E3A\u4E86\u60A8\u7684\u6570\u636E\u5B89\u5168\uFF0C\u672C\u9884\u89C8\u5668\u4E0D\u652F\u6301\u76F4\u63A5\u5728\u7EBF\u89E3\u5BC6\u3002\u8BF7\u4E0B\u8F7D\u6587\u4EF6\u540E\u5728\u672C\u5730\u8F93\u5165\u5BC6\u7801\u89E3\u538B\u3002";
7163
- const download = document.createElement("a");
7164
- download.href = url;
7165
- download.download = ctx.file.name;
7166
- download.textContent = "\u4E0B\u8F7D\u6587\u4EF6";
7167
- fallback.append(title, meta, download);
7570
+ const fallback = createEncryptedFallback(ctx.file, url, {
7571
+ title: "\u538B\u7F29\u5305\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
7572
+ message: "\u8BF7\u4E0B\u8F7D\u540E\u5728\u672C\u5730\u8F93\u5165\u5BC6\u7801\u89E3\u538B\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684\u538B\u7F29\u5305\u3002",
7573
+ action: "\u4E0B\u8F7D\u538B\u7F29\u5305"
7574
+ });
7168
7575
  panel.append(fallback);
7169
7576
  ctx.viewport.classList.add("ofv-center");
7170
7577
  return {
@@ -7413,6 +7820,28 @@ async function findSubPreviewPlugin(plugins, file) {
7413
7820
  }
7414
7821
  return fallbackPlugin();
7415
7822
  }
7823
+ function decodeZipFileName(bytes) {
7824
+ const data = Array.isArray(bytes) ? Uint8Array.from(bytes.map((value) => value.charCodeAt(0) & 255)) : bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);
7825
+ const utf8 = decodeZipNameWith(data, "utf-8", true);
7826
+ if (utf8 && !looksMojibake(utf8)) {
7827
+ return utf8;
7828
+ }
7829
+ const gb18030 = decodeZipNameWith(data, "gb18030", false) || decodeZipNameWith(data, "gbk", false);
7830
+ if (gb18030 && !looksMojibake(gb18030)) {
7831
+ return gb18030;
7832
+ }
7833
+ return utf8 || new TextDecoder("latin1").decode(data);
7834
+ }
7835
+ function decodeZipNameWith(bytes, encoding, fatal) {
7836
+ try {
7837
+ return new TextDecoder(encoding, { fatal }).decode(bytes);
7838
+ } catch {
7839
+ return void 0;
7840
+ }
7841
+ }
7842
+ function looksMojibake(value) {
7843
+ return /[\uFFFDÃÂÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß]/.test(value);
7844
+ }
7416
7845
  function createInlineError(titleText, detailText) {
7417
7846
  const fallback = document.createElement("div");
7418
7847
  fallback.className = "ofv-fallback";
@@ -7834,13 +8263,14 @@ function emailPlugin() {
7834
8263
  } else {
7835
8264
  const PostalMime = (await import("postal-mime")).default;
7836
8265
  const parser = new PostalMime();
7837
- let rawText = await readTextFile(ctx.file);
8266
+ let rawSource = await readArrayBuffer(ctx.file);
7838
8267
  if (ext === "mbox") {
8268
+ let rawText = await readTextFile(ctx.file);
7839
8269
  const messages = splitMboxMessages(rawText);
7840
8270
  mboxSummary = messages.map(summarizeMboxMessage);
7841
- rawText = messages[0] || rawText;
8271
+ rawSource = messages[0] || rawText;
7842
8272
  }
7843
- const parsed = await parser.parse(rawText);
8273
+ const parsed = await parser.parse(rawSource);
7844
8274
  const from = parsed.from ? `${parsed.from.name || ""} <${parsed.from.address || ""}>`.trim() : "";
7845
8275
  const to = Array.isArray(parsed.to) ? parsed.to.map((t) => `${t.name || ""} <${t.address || ""}>`.trim()).join("; ") : "";
7846
8276
  const cc = Array.isArray(parsed.cc) ? parsed.cc.map((c) => `${c.name || ""} <${c.address || ""}>`.trim()).join("; ") : "";
@@ -7910,13 +8340,17 @@ function emailPlugin() {
7910
8340
  const sanitizedHtml = sanitizeEmailHtml(html);
7911
8341
  const iframe = document.createElement("iframe");
7912
8342
  iframe.className = "ofv-email-body-iframe";
7913
- iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
8343
+ iframe.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
7914
8344
  iframe.style.cssText = "width: 100%; border: none; background: #fff; min-height: 200px;";
7915
- bodySection.append(iframe);
7916
- iframe.onload = () => {
8345
+ let renderedHtmlBody = false;
8346
+ const renderHtmlBody = () => {
8347
+ if (renderedHtmlBody) {
8348
+ return;
8349
+ }
7917
8350
  try {
7918
8351
  const idoc = iframe.contentDocument || iframe.contentWindow?.document;
7919
8352
  if (idoc) {
8353
+ renderedHtmlBody = true;
7920
8354
  idoc.open();
7921
8355
  idoc.write(`
7922
8356
  <!doctype html>
@@ -7967,6 +8401,9 @@ function emailPlugin() {
7967
8401
  console.error("Failed to write html body to email iframe:", err);
7968
8402
  }
7969
8403
  };
8404
+ iframe.addEventListener("load", renderHtmlBody, { once: true });
8405
+ bodySection.append(iframe);
8406
+ renderHtmlBody();
7970
8407
  } else {
7971
8408
  const pre = document.createElement("pre");
7972
8409
  pre.className = "ofv-text-block";
@@ -9488,6 +9925,7 @@ function decodeDrawioDiagram(value) {
9488
9925
  }
9489
9926
 
9490
9927
  // src/plugins/cad.ts
9928
+ import pako3 from "pako";
9491
9929
  var cadExtensions = /* @__PURE__ */ new Set([
9492
9930
  "dxf",
9493
9931
  "dwg",
@@ -9504,7 +9942,10 @@ var cadExtensions = /* @__PURE__ */ new Set([
9504
9942
  "3dm",
9505
9943
  "skp",
9506
9944
  "sldprt",
9507
- "sldasm"
9945
+ "sldasm",
9946
+ "gds",
9947
+ "oas",
9948
+ "oasis"
9508
9949
  ]);
9509
9950
  var cadMimeTypes = /* @__PURE__ */ new Set([
9510
9951
  "application/acad",
@@ -9521,7 +9962,11 @@ var cadMimeTypes = /* @__PURE__ */ new Set([
9521
9962
  "application/x-parasolid",
9522
9963
  "model/vnd.3dm",
9523
9964
  "application/vnd.sketchup.skp",
9524
- "application/sldworks"
9965
+ "application/sldworks",
9966
+ "application/vnd.gds",
9967
+ "application/x-gdsii",
9968
+ "application/vnd.oasis.layout",
9969
+ "application/x-oasis-layout"
9525
9970
  ]);
9526
9971
  var cadMimeFormatMap = {
9527
9972
  "application/acad": "dwg",
@@ -9538,7 +9983,11 @@ var cadMimeFormatMap = {
9538
9983
  "application/x-parasolid": "x_t",
9539
9984
  "model/vnd.3dm": "3dm",
9540
9985
  "application/vnd.sketchup.skp": "skp",
9541
- "application/sldworks": "sldprt"
9986
+ "application/sldworks": "sldprt",
9987
+ "application/vnd.gds": "gds",
9988
+ "application/x-gdsii": "gds",
9989
+ "application/vnd.oasis.layout": "oas",
9990
+ "application/x-oasis-layout": "oas"
9542
9991
  };
9543
9992
  function cadPlugin() {
9544
9993
  return {
@@ -9566,6 +10015,36 @@ function cadPlugin() {
9566
10015
  renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
9567
10016
  return { destroy: () => panel.remove() };
9568
10017
  }
10018
+ if (extension === "gds") {
10019
+ const viewer2 = renderLayoutPreview(panel, parseGdsLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
10020
+ return {
10021
+ canCommand(command) {
10022
+ return viewer2.canCommand(command);
10023
+ },
10024
+ command(command) {
10025
+ return viewer2.command(command);
10026
+ },
10027
+ destroy() {
10028
+ viewer2.destroy();
10029
+ panel.remove();
10030
+ }
10031
+ };
10032
+ }
10033
+ if (extension === "oas" || extension === "oasis") {
10034
+ const viewer2 = renderLayoutPreview(panel, parseOasisLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
10035
+ return {
10036
+ canCommand(command) {
10037
+ return viewer2.canCommand(command);
10038
+ },
10039
+ command(command) {
10040
+ return viewer2.command(command);
10041
+ },
10042
+ destroy() {
10043
+ viewer2.destroy();
10044
+ panel.remove();
10045
+ }
10046
+ };
10047
+ }
9569
10048
  if (extension !== "dxf") {
9570
10049
  const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
9571
10050
  section.append(`.${extension || "cad"} \u5DF2\u8BC6\u522B\u4E3A\u56FE\u7EB8/\u5DE5\u7A0B\u683C\u5F0F\uFF0C\u5F53\u524D\u524D\u7AEF\u63D2\u4EF6\u4F18\u5148\u6E32\u67D3 DXF\u3002\u8BE5\u683C\u5F0F\u5EFA\u8BAE\u63A5\u5165\u670D\u52A1\u7AEF\u8F6C\u6362\u6216 WASM \u4E13\u7528\u5F15\u64CE\u3002`);
@@ -9664,6 +10143,560 @@ function renderIfc(panel, text) {
9664
10143
  section.append(table);
9665
10144
  panel.append(section);
9666
10145
  }
10146
+ var layoutPalette = [
10147
+ "#2563eb",
10148
+ "#dc2626",
10149
+ "#059669",
10150
+ "#7c3aed",
10151
+ "#d97706",
10152
+ "#0891b2",
10153
+ "#be123c",
10154
+ "#4f46e5",
10155
+ "#15803d",
10156
+ "#a16207"
10157
+ ];
10158
+ var gdsRecordNames = {
10159
+ 0: "HEADER",
10160
+ 1: "BGNLIB",
10161
+ 2: "LIBNAME",
10162
+ 3: "UNITS",
10163
+ 4: "ENDLIB",
10164
+ 5: "BGNSTR",
10165
+ 6: "STRNAME",
10166
+ 7: "ENDSTR",
10167
+ 8: "BOUNDARY",
10168
+ 9: "PATH",
10169
+ 10: "SREF",
10170
+ 11: "AREF",
10171
+ 12: "TEXT",
10172
+ 13: "LAYER",
10173
+ 14: "DATATYPE",
10174
+ 15: "WIDTH",
10175
+ 16: "XY",
10176
+ 17: "ENDEL",
10177
+ 18: "SNAME",
10178
+ 22: "TEXTTYPE",
10179
+ 25: "STRING",
10180
+ 45: "BOX"
10181
+ };
10182
+ var oasisRecordNames = {
10183
+ 0: "PAD",
10184
+ 1: "START",
10185
+ 2: "END",
10186
+ 3: "CELLNAME",
10187
+ 4: "CELLNAME-REF",
10188
+ 5: "TEXTSTRING",
10189
+ 6: "TEXTSTRING-REF",
10190
+ 7: "PROPNAME",
10191
+ 8: "PROPNAME-REF",
10192
+ 9: "PROPSTRING",
10193
+ 10: "PROPSTRING-REF",
10194
+ 11: "LAYERNAME",
10195
+ 12: "LAYERNAME-REF",
10196
+ 13: "CELL",
10197
+ 14: "XYABSOLUTE",
10198
+ 15: "XYRELATIVE",
10199
+ 16: "PLACEMENT",
10200
+ 17: "PLACEMENT",
10201
+ 18: "TEXT",
10202
+ 19: "RECTANGLE",
10203
+ 20: "POLYGON",
10204
+ 21: "PATH",
10205
+ 22: "TRAPEZOID",
10206
+ 23: "TRAPEZOID",
10207
+ 24: "TRAPEZOID",
10208
+ 25: "CTRAPEZOID",
10209
+ 26: "CIRCLE",
10210
+ 27: "PROPERTY",
10211
+ 28: "PROPERTY",
10212
+ 29: "XNAME",
10213
+ 30: "XNAME-REF",
10214
+ 31: "XELEMENT",
10215
+ 32: "XGEOMETRY",
10216
+ 33: "CBLOCK"
10217
+ };
10218
+ function renderLayoutPreview(panel, data, ctx) {
10219
+ const section = createSection(`${data.format} \u7248\u56FE\u9884\u89C8`);
10220
+ const summary = document.createElement("div");
10221
+ summary.className = "ofv-cad-summary ofv-layout-summary";
10222
+ appendMeta5(summary, "\u6587\u4EF6", data.fileName);
10223
+ appendMeta5(summary, "\u683C\u5F0F", data.format);
10224
+ if (data.libraryName) {
10225
+ appendMeta5(summary, "\u5E93", data.libraryName);
10226
+ }
10227
+ if (data.version) {
10228
+ appendMeta5(summary, "\u7248\u672C", data.version);
10229
+ }
10230
+ if (data.unit) {
10231
+ appendMeta5(summary, "\u5355\u4F4D", data.unit);
10232
+ }
10233
+ appendMeta5(summary, "Cell", data.cells.length);
10234
+ appendMeta5(summary, "\u51E0\u4F55", data.shapes.length);
10235
+ appendMeta5(summary, "\u5F15\u7528", data.references.length);
10236
+ appendMeta5(summary, "\u6587\u5B57", data.labels.length);
10237
+ for (const [label, value] of data.metadata) {
10238
+ appendMeta5(summary, label, value);
10239
+ }
10240
+ section.append(summary);
10241
+ for (const noteText of [...data.notes, ...data.warnings]) {
10242
+ const note = document.createElement("p");
10243
+ note.className = data.warnings.includes(noteText) ? "ofv-layout-warning" : "ofv-layout-note";
10244
+ note.textContent = noteText;
10245
+ section.append(note);
10246
+ }
10247
+ const bounds = computeLayoutBounds(data.shapes, data.labels, data.references);
10248
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
10249
+ svg.setAttribute("class", "ofv-svg-stage ofv-layout-stage");
10250
+ let currentViewBox = { x: bounds.minX, y: bounds.minY, width: bounds.width, height: bounds.height };
10251
+ const initialViewBox = { ...currentViewBox };
10252
+ const applyViewBox = () => {
10253
+ svg.setAttribute("viewBox", `${currentViewBox.x} ${currentViewBox.y} ${currentViewBox.width} ${currentViewBox.height}`);
10254
+ };
10255
+ applyViewBox();
10256
+ if (data.shapes.length === 0) {
10257
+ const empty = document.createElementNS(svg.namespaceURI, "text");
10258
+ empty.setAttribute("x", String(bounds.minX + bounds.width * 0.5));
10259
+ empty.setAttribute("y", String(bounds.minY + bounds.height * 0.5));
10260
+ empty.setAttribute("text-anchor", "middle");
10261
+ empty.setAttribute("font-size", String(Math.max(bounds.width, bounds.height) / 34));
10262
+ empty.setAttribute("fill", "currentColor");
10263
+ empty.textContent = "\u5DF2\u8BC6\u522B\u7248\u56FE\u6587\u4EF6\uFF0C\u5F53\u524D\u6587\u4EF6\u672A\u89E3\u6790\u51FA\u53EF\u7ED8\u5236\u51E0\u4F55";
10264
+ svg.append(empty);
10265
+ }
10266
+ const layerIndex = new Map([...data.layers.keys()].sort((a, b) => a.localeCompare(b)).map((layer, index) => [layer, index]));
10267
+ for (const shape of data.shapes.slice(0, 6e3)) {
10268
+ const color = layoutPalette[(layerIndex.get(shape.layer) || 0) % layoutPalette.length];
10269
+ if (shape.kind === "path") {
10270
+ const polyline = document.createElementNS(svg.namespaceURI, "polyline");
10271
+ polyline.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
10272
+ polyline.setAttribute("fill", "none");
10273
+ polyline.setAttribute("stroke", color);
10274
+ polyline.setAttribute("stroke-width", String(Math.max(bounds.stroke, Math.abs(shape.width || 0))));
10275
+ polyline.setAttribute("stroke-linecap", "round");
10276
+ polyline.setAttribute("stroke-linejoin", "round");
10277
+ applyLayer(polyline, shape.layer);
10278
+ svg.append(polyline);
10279
+ continue;
10280
+ }
10281
+ const polygon = document.createElementNS(svg.namespaceURI, "polygon");
10282
+ polygon.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
10283
+ polygon.setAttribute("fill", color);
10284
+ polygon.setAttribute("fill-opacity", "0.18");
10285
+ polygon.setAttribute("stroke", color);
10286
+ polygon.setAttribute("stroke-width", String(bounds.stroke));
10287
+ polygon.setAttribute("vector-effect", "non-scaling-stroke");
10288
+ applyLayer(polygon, shape.layer);
10289
+ svg.append(polygon);
10290
+ }
10291
+ for (const label of data.labels.slice(0, 400)) {
10292
+ const text = document.createElementNS(svg.namespaceURI, "text");
10293
+ text.setAttribute("x", String(label.x));
10294
+ text.setAttribute("y", String(-label.y));
10295
+ text.setAttribute("font-size", String(Math.max(bounds.stroke * 12, Math.max(bounds.width, bounds.height) / 120)));
10296
+ text.setAttribute("fill", "currentColor");
10297
+ text.textContent = label.text;
10298
+ applyLayer(text, label.layer);
10299
+ svg.append(text);
10300
+ }
10301
+ const layers = [...data.layers.keys()].sort((a, b) => a.localeCompare(b));
10302
+ if (layers.length > 0) {
10303
+ section.append(createLayoutLayerControls(svg, layers, data.layers));
10304
+ }
10305
+ section.append(svg);
10306
+ if (data.cells.length > 0) {
10307
+ section.append(createLayoutCellList(data.cells, data.references));
10308
+ }
10309
+ panel.append(section);
10310
+ const updateToolbarZoom = () => ctx.toolbar?.setZoom(initialViewBox.width / currentViewBox.width);
10311
+ updateToolbarZoom();
10312
+ return {
10313
+ canCommand(command) {
10314
+ return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
10315
+ },
10316
+ command(command) {
10317
+ if (command === "zoom-in" || command === "zoom-out") {
10318
+ const factor = command === "zoom-in" ? 0.82 : 1.18;
10319
+ const centerX = currentViewBox.x + currentViewBox.width / 2;
10320
+ const centerY = currentViewBox.y + currentViewBox.height / 2;
10321
+ currentViewBox.width *= factor;
10322
+ currentViewBox.height *= factor;
10323
+ currentViewBox.x = centerX - currentViewBox.width / 2;
10324
+ currentViewBox.y = centerY - currentViewBox.height / 2;
10325
+ applyViewBox();
10326
+ updateToolbarZoom();
10327
+ return true;
10328
+ }
10329
+ if (command === "zoom-reset") {
10330
+ currentViewBox = { ...initialViewBox };
10331
+ applyViewBox();
10332
+ updateToolbarZoom();
10333
+ return true;
10334
+ }
10335
+ return false;
10336
+ },
10337
+ destroy() {
10338
+ ctx.toolbar?.setZoom(void 0);
10339
+ }
10340
+ };
10341
+ }
10342
+ function parseGdsLayout(bytes, fileName) {
10343
+ const shapes = [];
10344
+ const labels = [];
10345
+ const references = [];
10346
+ const cells = [];
10347
+ const layers = /* @__PURE__ */ new Map();
10348
+ const recordCounts = /* @__PURE__ */ new Map();
10349
+ const warnings = [];
10350
+ let libraryName = "";
10351
+ let version = "";
10352
+ let unit = "";
10353
+ let offset = 0;
10354
+ let current = {};
10355
+ let currentKind = "";
10356
+ while (offset + 4 <= bytes.length) {
10357
+ const length = readUInt16(bytes, offset);
10358
+ const recordType = bytes[offset + 2];
10359
+ const data = bytes.slice(offset + 4, offset + length);
10360
+ const name = gdsRecordNames[recordType] || `0x${recordType.toString(16).padStart(2, "0")}`;
10361
+ recordCounts.set(name, (recordCounts.get(name) || 0) + 1);
10362
+ if (length < 4 || offset + length > bytes.length) {
10363
+ warnings.push(`GDS \u8BB0\u5F55\u5728 ${offset} \u5B57\u8282\u5904\u957F\u5EA6\u5F02\u5E38\uFF0C\u5DF2\u505C\u6B62\u89E3\u6790\u3002`);
10364
+ break;
10365
+ }
10366
+ if (recordType === 0 && data.length >= 2) {
10367
+ version = String(readUInt16(data, 0));
10368
+ } else if (recordType === 2) {
10369
+ libraryName = readGdsString(data);
10370
+ } else if (recordType === 3 && data.length >= 16) {
10371
+ unit = `${formatGdsReal(data, 0)} / ${formatGdsReal(data, 8)}`;
10372
+ } else if (recordType === 6) {
10373
+ cells.push(readGdsString(data));
10374
+ } else if (recordType === 8 || recordType === 9 || recordType === 45) {
10375
+ currentKind = recordType === 9 ? "path" : recordType === 45 ? "box" : "boundary";
10376
+ current = { kind: currentKind, layer: "0", datatype: "0", points: [] };
10377
+ } else if (recordType === 12) {
10378
+ currentKind = "text";
10379
+ current = { layer: "0", text: "", x: 0, y: 0 };
10380
+ } else if (recordType === 10 || recordType === 11) {
10381
+ currentKind = "reference";
10382
+ current = { cell: "", x: 0, y: 0 };
10383
+ } else if (recordType === 13 && data.length >= 2) {
10384
+ current.layer = String(readInt16(data, 0));
10385
+ } else if ((recordType === 14 || recordType === 22) && data.length >= 2) {
10386
+ current.datatype = String(readInt16(data, 0));
10387
+ } else if (recordType === 15 && data.length >= 4) {
10388
+ current.width = Math.abs(readInt32(data, 0));
10389
+ } else if (recordType === 16) {
10390
+ const points = readGdsPoints(data);
10391
+ if (currentKind === "text" && points[0]) {
10392
+ current.x = points[0][0];
10393
+ current.y = points[0][1];
10394
+ } else if (currentKind === "reference" && points[0]) {
10395
+ current.x = points[0][0];
10396
+ current.y = points[0][1];
10397
+ } else {
10398
+ current.points = points;
10399
+ }
10400
+ } else if (recordType === 18) {
10401
+ current.cell = readGdsString(data);
10402
+ } else if (recordType === 25) {
10403
+ current.text = readGdsString(data);
10404
+ } else if (recordType === 17) {
10405
+ if ((currentKind === "boundary" || currentKind === "path" || currentKind === "box") && current.points && current.points.length > 1) {
10406
+ const shape = {
10407
+ kind: current.kind || "boundary",
10408
+ layer: String(current.layer || "0"),
10409
+ datatype: current.datatype,
10410
+ points: current.points,
10411
+ width: current.width
10412
+ };
10413
+ shapes.push(shape);
10414
+ layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
10415
+ } else if (currentKind === "text" && current.text) {
10416
+ const label = {
10417
+ layer: String(current.layer || "0"),
10418
+ text: String(current.text),
10419
+ x: Number(current.x || 0),
10420
+ y: Number(current.y || 0)
10421
+ };
10422
+ labels.push(label);
10423
+ layers.set(label.layer, (layers.get(label.layer) || 0) + 1);
10424
+ } else if (currentKind === "reference" && current.cell) {
10425
+ references.push({ cell: String(current.cell), x: Number(current.x || 0), y: Number(current.y || 0) });
10426
+ }
10427
+ current = {};
10428
+ currentKind = "";
10429
+ }
10430
+ offset += length;
10431
+ }
10432
+ return {
10433
+ format: "GDSII",
10434
+ fileName,
10435
+ libraryName,
10436
+ version: version ? `Stream ${version}` : void 0,
10437
+ unit,
10438
+ cells,
10439
+ shapes,
10440
+ labels,
10441
+ references,
10442
+ layers,
10443
+ metadata: [
10444
+ ["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
10445
+ ["\u8BB0\u5F55", sumCounts(recordCounts)],
10446
+ ["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size]
10447
+ ],
10448
+ notes: [
10449
+ `\u5DF2\u4ECE GDSII Stream \u4E2D\u89E3\u6790 ${shapes.length} \u4E2A\u51E0\u4F55\u3001${references.length} \u4E2A cell \u5F15\u7528\u548C ${labels.length} \u6BB5\u6587\u5B57\u3002`
10450
+ ],
10451
+ warnings
10452
+ };
10453
+ }
10454
+ function parseOasisLayout(bytes, fileName) {
10455
+ const chunks = extractOasisCblocks(bytes);
10456
+ const expanded = chunks.flatMap((chunk) => [...chunk.bytes]);
10457
+ const cellNames = uniqueHints([...extractAsciiRuns(bytes), ...chunks.flatMap((chunk) => extractAsciiRuns(chunk.bytes))]).filter((item) => /^[A-Za-z_][\w$.-]{1,80}$/.test(item)).filter((item) => !item.startsWith("S_"));
10458
+ const propertyNames = uniqueHints(chunks.flatMap((chunk) => extractAsciiRuns(chunk.bytes)).filter((item) => item.startsWith("S_")));
10459
+ const recordCounts = scanOasisRecordCounts(bytes);
10460
+ const expandedCounts = scanOasisRecordCounts(new Uint8Array(expanded));
10461
+ const layers = /* @__PURE__ */ new Map();
10462
+ const shapes = [];
10463
+ const labels = [];
10464
+ const references = [];
10465
+ for (const name of cellNames) {
10466
+ references.push({ cell: name, x: 0, y: 0 });
10467
+ }
10468
+ const pseudo = createOasisStructureShapes(cellNames, chunks.length || recordCounts.size || 1);
10469
+ for (const shape of pseudo) {
10470
+ shapes.push(shape);
10471
+ layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
10472
+ }
10473
+ for (let index = 0; index < cellNames.length; index++) {
10474
+ labels.push({ layer: "cell", text: cellNames[index], x: 12, y: -(18 + index * 18) });
10475
+ }
10476
+ if (cellNames.length > 0) {
10477
+ layers.set("cell", (layers.get("cell") || 0) + cellNames.length);
10478
+ }
10479
+ const version = readOasisVersion(bytes);
10480
+ const cblockText = chunks.length ? `${chunks.length} \u4E2A\uFF0C\u5C55\u5F00 ${formatBytes3(chunks.reduce((sum, chunk) => sum + chunk.bytes.byteLength, 0))}` : "\u672A\u53D1\u73B0";
10481
+ const notes = [
10482
+ "OASIS \u662F\u9AD8\u538B\u7F29\u82AF\u7247\u7248\u56FE\u683C\u5F0F\uFF0C\u5F53\u524D\u7248\u672C\u63D0\u4F9B\u6D4F\u89C8\u5668\u7AEF\u8BC6\u522B\u3001CBLOCK \u89E3\u538B\u3001cell/\u5C5E\u6027\u7ED3\u6784\u548C\u8F7B\u91CF\u7ED3\u6784\u793A\u610F\uFF1B\u5B8C\u6574\u51E0\u4F55\u9AD8\u4FDD\u771F\u6E32\u67D3\u5EFA\u8BAE\u540E\u7EED\u63A5\u5165\u4E13\u7528 OASIS \u89E3\u6790\u5668\u3002"
10483
+ ];
10484
+ if (propertyNames.length > 0) {
10485
+ notes.push(`\u8BC6\u522B\u5230\u5C5E\u6027\uFF1A${propertyNames.slice(0, 5).join("\u3001")}`);
10486
+ }
10487
+ return {
10488
+ format: "OASIS",
10489
+ fileName,
10490
+ version,
10491
+ cells: cellNames,
10492
+ shapes,
10493
+ labels,
10494
+ references: [],
10495
+ layers,
10496
+ metadata: [
10497
+ ["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
10498
+ ["CBLOCK", cblockText],
10499
+ ["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size + expandedCounts.size],
10500
+ ["\u53EF\u8BFB\u7247\u6BB5", cellNames.length + propertyNames.length]
10501
+ ],
10502
+ notes,
10503
+ warnings: cellNames.length === 0 ? ["\u5F53\u524D OASIS \u6587\u4EF6\u672A\u63D0\u53D6\u5230 cell \u540D\u79F0\uFF0C\u53EF\u80FD\u4F7F\u7528\u4E86\u66F4\u590D\u6742\u7684\u7D22\u5F15\u6216\u52A0\u5BC6/\u538B\u7F29\u5E03\u5C40\u3002"] : []
10504
+ };
10505
+ }
10506
+ function computeLayoutBounds(shapes, labels, references) {
10507
+ const xs = [];
10508
+ const ys = [];
10509
+ for (const shape of shapes) {
10510
+ for (const [x, y] of shape.points) {
10511
+ xs.push(x);
10512
+ ys.push(-y);
10513
+ }
10514
+ }
10515
+ for (const label of labels) {
10516
+ xs.push(label.x, label.x + label.text.length * 12);
10517
+ ys.push(-label.y, -label.y - 16);
10518
+ }
10519
+ for (const reference of references) {
10520
+ xs.push(reference.x);
10521
+ ys.push(-reference.y);
10522
+ }
10523
+ const minX = Math.min(...xs, 0);
10524
+ const maxX = Math.max(...xs, 100);
10525
+ const minY = Math.min(...ys, 0);
10526
+ const maxY = Math.max(...ys, 100);
10527
+ const width = Math.max(1, maxX - minX);
10528
+ const height = Math.max(1, maxY - minY);
10529
+ const padding = Math.max(width, height) * 0.06;
10530
+ return {
10531
+ minX: minX - padding,
10532
+ minY: minY - padding,
10533
+ width: width + padding * 2,
10534
+ height: height + padding * 2,
10535
+ stroke: Math.max(width, height) / 900
10536
+ };
10537
+ }
10538
+ function createLayoutLayerControls(svg, layers, counts) {
10539
+ const controls = document.createElement("div");
10540
+ controls.className = "ofv-cad-layers ofv-layout-layers";
10541
+ const title = document.createElement("strong");
10542
+ title.textContent = `\u56FE\u5C42 ${layers.length}`;
10543
+ controls.append(title);
10544
+ for (const layer of layers) {
10545
+ const label = document.createElement("label");
10546
+ const checkbox = document.createElement("input");
10547
+ checkbox.type = "checkbox";
10548
+ checkbox.checked = true;
10549
+ checkbox.addEventListener("change", () => {
10550
+ for (const element of svg.querySelectorAll(`[data-layer="${escapeCssAttribute(layer)}"]`)) {
10551
+ element.style.display = checkbox.checked ? "" : "none";
10552
+ }
10553
+ });
10554
+ const name = document.createElement("span");
10555
+ name.textContent = `${layer} (${counts.get(layer) || 0})`;
10556
+ label.append(checkbox, name);
10557
+ controls.append(label);
10558
+ }
10559
+ return controls;
10560
+ }
10561
+ function createLayoutCellList(cells, references) {
10562
+ const details = document.createElement("details");
10563
+ details.className = "ofv-details ofv-layout-cells";
10564
+ details.open = true;
10565
+ const summary = document.createElement("summary");
10566
+ summary.textContent = `Cell \u7ED3\u6784 ${cells.length}`;
10567
+ const list = document.createElement("ul");
10568
+ const refCounts = countBy(references.map((reference) => reference.cell));
10569
+ for (const cell of cells.slice(0, 120)) {
10570
+ const item = document.createElement("li");
10571
+ const count = refCounts.get(cell) || 0;
10572
+ item.textContent = count > 0 ? `${cell} \xB7 \u5F15\u7528 ${count}` : cell;
10573
+ list.append(item);
10574
+ }
10575
+ details.append(summary, list);
10576
+ return details;
10577
+ }
10578
+ function readUInt16(bytes, offset) {
10579
+ return bytes[offset] << 8 | bytes[offset + 1];
10580
+ }
10581
+ function readInt16(bytes, offset) {
10582
+ const value = readUInt16(bytes, offset);
10583
+ return value & 32768 ? value - 65536 : value;
10584
+ }
10585
+ function readInt32(bytes, offset) {
10586
+ const value = bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
10587
+ return value | 0;
10588
+ }
10589
+ function readGdsString(bytes) {
10590
+ return new TextDecoder("ascii").decode(bytes).replace(/\0+$/g, "").trim();
10591
+ }
10592
+ function readGdsPoints(bytes) {
10593
+ const points = [];
10594
+ for (let offset = 0; offset + 7 < bytes.length; offset += 8) {
10595
+ points.push([readInt32(bytes, offset), readInt32(bytes, offset + 4)]);
10596
+ }
10597
+ return points;
10598
+ }
10599
+ function formatGdsReal(bytes, offset) {
10600
+ const value = readGdsReal(bytes, offset);
10601
+ if (!Number.isFinite(value) || value === 0) {
10602
+ return "0";
10603
+ }
10604
+ if (Math.abs(value) < 1e-3 || Math.abs(value) >= 1e4) {
10605
+ return value.toExponential(4);
10606
+ }
10607
+ return String(Number(value.toPrecision(6)));
10608
+ }
10609
+ function readGdsReal(bytes, offset) {
10610
+ const first = bytes[offset];
10611
+ if (!first) {
10612
+ return 0;
10613
+ }
10614
+ const sign = first & 128 ? -1 : 1;
10615
+ const exponent = (first & 127) - 64;
10616
+ let mantissa = 0;
10617
+ for (let index = 1; index < 8; index++) {
10618
+ mantissa = mantissa * 256 + bytes[offset + index];
10619
+ }
10620
+ return sign * (mantissa / Math.pow(2, 56)) * Math.pow(16, exponent);
10621
+ }
10622
+ function sumCounts(counts) {
10623
+ return [...counts.values()].reduce((sum, count) => sum + count, 0);
10624
+ }
10625
+ function extractOasisCblocks(bytes) {
10626
+ const chunks = [];
10627
+ const seen = /* @__PURE__ */ new Set();
10628
+ const limit = Math.min(bytes.length, 25e4);
10629
+ for (let offset = 0; offset < limit; offset++) {
10630
+ try {
10631
+ const inflated = pako3.inflateRaw(bytes.slice(offset));
10632
+ if (inflated.byteLength < 4) {
10633
+ continue;
10634
+ }
10635
+ const ascii = extractAsciiRuns(inflated);
10636
+ const hasLayoutSignal = ascii.some((item) => item.startsWith("S_") || /TOP|CELL|DIE|SIZE/i.test(item));
10637
+ const hasRecordSignal = inflated.some((byte) => byte >= 13 && byte <= 34);
10638
+ if (!hasLayoutSignal && !hasRecordSignal) {
10639
+ continue;
10640
+ }
10641
+ const signature = `${inflated.byteLength}:${Array.from(inflated.slice(0, 12)).join(",")}`;
10642
+ if (seen.has(signature)) {
10643
+ continue;
10644
+ }
10645
+ seen.add(signature);
10646
+ chunks.push({ offset, bytes: inflated });
10647
+ if (chunks.length >= 12) {
10648
+ break;
10649
+ }
10650
+ } catch {
10651
+ }
10652
+ }
10653
+ return chunks;
10654
+ }
10655
+ function scanOasisRecordCounts(bytes) {
10656
+ const counts = /* @__PURE__ */ new Map();
10657
+ for (const byte of bytes.slice(0, Math.min(bytes.length, 12e3))) {
10658
+ const name = oasisRecordNames[byte];
10659
+ if (name) {
10660
+ counts.set(name, (counts.get(name) || 0) + 1);
10661
+ }
10662
+ }
10663
+ return counts;
10664
+ }
10665
+ function readOasisVersion(bytes) {
10666
+ const magic = "%SEMI-OASIS\r\n";
10667
+ const header = new TextDecoder("ascii").decode(bytes.slice(0, Math.min(bytes.length, 48)));
10668
+ if (!header.startsWith(magic)) {
10669
+ return void 0;
10670
+ }
10671
+ const start = magic.length;
10672
+ if (bytes[start] !== 1) {
10673
+ return "OASIS";
10674
+ }
10675
+ const length = bytes[start + 1];
10676
+ const version = new TextDecoder("ascii").decode(bytes.slice(start + 2, start + 2 + length));
10677
+ return version ? `OASIS ${version}` : "OASIS";
10678
+ }
10679
+ function createOasisStructureShapes(cellNames, fallbackCount) {
10680
+ const rows = Math.max(1, cellNames.length || fallbackCount);
10681
+ const shapes = [];
10682
+ for (let index = 0; index < rows; index++) {
10683
+ const top = -(index * 18);
10684
+ const height = 12;
10685
+ const width = 88 + Math.min((cellNames[index]?.length || 5) * 4, 90);
10686
+ shapes.push({
10687
+ kind: "box",
10688
+ layer: "cell",
10689
+ points: [
10690
+ [0, top],
10691
+ [width, top],
10692
+ [width, top - height],
10693
+ [0, top - height],
10694
+ [0, top]
10695
+ ]
10696
+ });
10697
+ }
10698
+ return shapes;
10699
+ }
9667
10700
  function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
9668
10701
  const bytes = new Uint8Array(arrayBuffer);
9669
10702
  const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);