@open-file-viewer/core 0.1.5 → 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);
@@ -4031,6 +4074,38 @@ async function readText(source) {
4031
4074
  return String(source);
4032
4075
  }
4033
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
+
4034
4109
  // src/plugins/pdf.ts
4035
4110
  function multiplyMatrices(m1, m2) {
4036
4111
  return [
@@ -4072,7 +4147,11 @@ function pdfPlugin(options = {}) {
4072
4147
  const doc = await documentTask.promise.catch((error) => {
4073
4148
  viewer.remove();
4074
4149
  ctx.viewport.classList.add("ofv-center");
4075
- 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));
4076
4155
  ctx.viewport.append(fallback);
4077
4156
  return void 0;
4078
4157
  });
@@ -4362,9 +4441,6 @@ function normalizePdfError(error) {
4362
4441
  const message = error instanceof Error ? error.message : String(error || "");
4363
4442
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
4364
4443
  const lower = `${name} ${message}`.toLowerCase();
4365
- if (lower.includes("password")) {
4366
- return "\u8BE5 PDF \u53D7\u5BC6\u7801\u4FDD\u62A4\uFF0C\u5F53\u524D\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u5185\u76F4\u63A5\u9884\u89C8\u3002";
4367
- }
4368
4444
  if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
4369
4445
  return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
4370
4446
  }
@@ -5394,6 +5470,10 @@ async function renderSheet(panel, arrayBuffer, extension) {
5394
5470
  }
5395
5471
  }
5396
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
+ }
5397
5477
  const section = createSection("\u8868\u683C\u89E3\u6790\u5931\u8D25");
5398
5478
  const title = document.createElement("p");
5399
5479
  title.textContent = `.${extension || "sheet"} \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790\u4E3A\u53EF\u9884\u89C8\u8868\u683C\u3002`;
@@ -5404,6 +5484,17 @@ function renderSheetFallback(panel, extension, detail) {
5404
5484
  section.append(title, meta, support);
5405
5485
  panel.append(section);
5406
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
+ }
5407
5498
  function renderFlatOds(panel, xml) {
5408
5499
  const sheets = parseFlatOds(xml);
5409
5500
  renderParsedSheets(panel, sheets, "FODS \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002");
@@ -6712,7 +6803,7 @@ function ofdPlugin() {
6712
6803
  try {
6713
6804
  zip = await JSZip4.loadAsync(await readArrayBuffer(ctx.file));
6714
6805
  } catch (error) {
6715
- panel.append(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
6806
+ panel.append(createOfdFailure(ctx.file, url, error));
6716
6807
  return {
6717
6808
  destroy() {
6718
6809
  panel.remove();
@@ -6729,7 +6820,7 @@ function ofdPlugin() {
6729
6820
  textFragments.push(...matches);
6730
6821
  }
6731
6822
  } catch (error) {
6732
- panel.append(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
6823
+ panel.append(createOfdFailure(ctx.file, url, error));
6733
6824
  return {
6734
6825
  destroy() {
6735
6826
  panel.remove();
@@ -7354,6 +7445,16 @@ function finiteNumber2(value, fallback) {
7354
7445
  function formatOfdCssNumber(value) {
7355
7446
  return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7356
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
+ }
7357
7458
  function createOfdFallback(fileName, url, detail) {
7358
7459
  const fallback = document.createElement("div");
7359
7460
  fallback.className = "ofv-fallback";
@@ -7422,7 +7523,9 @@ function archivePlugin() {
7422
7523
  try {
7423
7524
  if (ext === "zip") {
7424
7525
  try {
7425
- const zip = await JSZip5.loadAsync(await readArrayBuffer(ctx.file));
7526
+ const zip = await JSZip5.loadAsync(await readArrayBuffer(ctx.file), {
7527
+ decodeFileName: decodeZipFileName
7528
+ });
7426
7529
  archiveEntries = Object.values(zip.files).map((entry) => ({
7427
7530
  name: entry.name,
7428
7531
  unsafeName: entry.unsafeOriginalName,
@@ -7431,7 +7534,7 @@ function archivePlugin() {
7431
7534
  read: () => entry.async("arraybuffer")
7432
7535
  }));
7433
7536
  } catch (zipErr) {
7434
- if (zipErr.message && (zipErr.message.includes("encrypted") || zipErr.message.includes("password") || zipErr.message.includes("protected"))) {
7537
+ if (isEncryptedError(zipErr)) {
7435
7538
  isEncrypted = true;
7436
7539
  } else {
7437
7540
  throw zipErr;
@@ -7464,17 +7567,11 @@ function archivePlugin() {
7464
7567
  parseError = `\u538B\u7F29\u5305\u89E3\u6790\u5931\u8D25\uFF1A${err.message || err}`;
7465
7568
  }
7466
7569
  if (isEncrypted) {
7467
- const fallback = document.createElement("div");
7468
- fallback.className = "ofv-fallback";
7469
- const title = document.createElement("strong");
7470
- title.textContent = "\u8BE5\u538B\u7F29\u5305\u5DF2\u88AB\u52A0\u5BC6\u4FDD\u62A4";
7471
- const meta = document.createElement("span");
7472
- 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";
7473
- const download = document.createElement("a");
7474
- download.href = url;
7475
- download.download = ctx.file.name;
7476
- download.textContent = "\u4E0B\u8F7D\u6587\u4EF6";
7477
- 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
+ });
7478
7575
  panel.append(fallback);
7479
7576
  ctx.viewport.classList.add("ofv-center");
7480
7577
  return {
@@ -7723,6 +7820,28 @@ async function findSubPreviewPlugin(plugins, file) {
7723
7820
  }
7724
7821
  return fallbackPlugin();
7725
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
+ }
7726
7845
  function createInlineError(titleText, detailText) {
7727
7846
  const fallback = document.createElement("div");
7728
7847
  fallback.className = "ofv-fallback";
@@ -8144,13 +8263,14 @@ function emailPlugin() {
8144
8263
  } else {
8145
8264
  const PostalMime = (await import("postal-mime")).default;
8146
8265
  const parser = new PostalMime();
8147
- let rawText = await readTextFile(ctx.file);
8266
+ let rawSource = await readArrayBuffer(ctx.file);
8148
8267
  if (ext === "mbox") {
8268
+ let rawText = await readTextFile(ctx.file);
8149
8269
  const messages = splitMboxMessages(rawText);
8150
8270
  mboxSummary = messages.map(summarizeMboxMessage);
8151
- rawText = messages[0] || rawText;
8271
+ rawSource = messages[0] || rawText;
8152
8272
  }
8153
- const parsed = await parser.parse(rawText);
8273
+ const parsed = await parser.parse(rawSource);
8154
8274
  const from = parsed.from ? `${parsed.from.name || ""} <${parsed.from.address || ""}>`.trim() : "";
8155
8275
  const to = Array.isArray(parsed.to) ? parsed.to.map((t) => `${t.name || ""} <${t.address || ""}>`.trim()).join("; ") : "";
8156
8276
  const cc = Array.isArray(parsed.cc) ? parsed.cc.map((c) => `${c.name || ""} <${c.address || ""}>`.trim()).join("; ") : "";
@@ -8220,13 +8340,17 @@ function emailPlugin() {
8220
8340
  const sanitizedHtml = sanitizeEmailHtml(html);
8221
8341
  const iframe = document.createElement("iframe");
8222
8342
  iframe.className = "ofv-email-body-iframe";
8223
- iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
8343
+ iframe.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
8224
8344
  iframe.style.cssText = "width: 100%; border: none; background: #fff; min-height: 200px;";
8225
- bodySection.append(iframe);
8226
- iframe.onload = () => {
8345
+ let renderedHtmlBody = false;
8346
+ const renderHtmlBody = () => {
8347
+ if (renderedHtmlBody) {
8348
+ return;
8349
+ }
8227
8350
  try {
8228
8351
  const idoc = iframe.contentDocument || iframe.contentWindow?.document;
8229
8352
  if (idoc) {
8353
+ renderedHtmlBody = true;
8230
8354
  idoc.open();
8231
8355
  idoc.write(`
8232
8356
  <!doctype html>
@@ -8277,6 +8401,9 @@ function emailPlugin() {
8277
8401
  console.error("Failed to write html body to email iframe:", err);
8278
8402
  }
8279
8403
  };
8404
+ iframe.addEventListener("load", renderHtmlBody, { once: true });
8405
+ bodySection.append(iframe);
8406
+ renderHtmlBody();
8280
8407
  } else {
8281
8408
  const pre = document.createElement("pre");
8282
8409
  pre.className = "ofv-text-block";
@@ -9798,6 +9925,7 @@ function decodeDrawioDiagram(value) {
9798
9925
  }
9799
9926
 
9800
9927
  // src/plugins/cad.ts
9928
+ import pako3 from "pako";
9801
9929
  var cadExtensions = /* @__PURE__ */ new Set([
9802
9930
  "dxf",
9803
9931
  "dwg",
@@ -9814,7 +9942,10 @@ var cadExtensions = /* @__PURE__ */ new Set([
9814
9942
  "3dm",
9815
9943
  "skp",
9816
9944
  "sldprt",
9817
- "sldasm"
9945
+ "sldasm",
9946
+ "gds",
9947
+ "oas",
9948
+ "oasis"
9818
9949
  ]);
9819
9950
  var cadMimeTypes = /* @__PURE__ */ new Set([
9820
9951
  "application/acad",
@@ -9831,7 +9962,11 @@ var cadMimeTypes = /* @__PURE__ */ new Set([
9831
9962
  "application/x-parasolid",
9832
9963
  "model/vnd.3dm",
9833
9964
  "application/vnd.sketchup.skp",
9834
- "application/sldworks"
9965
+ "application/sldworks",
9966
+ "application/vnd.gds",
9967
+ "application/x-gdsii",
9968
+ "application/vnd.oasis.layout",
9969
+ "application/x-oasis-layout"
9835
9970
  ]);
9836
9971
  var cadMimeFormatMap = {
9837
9972
  "application/acad": "dwg",
@@ -9848,7 +9983,11 @@ var cadMimeFormatMap = {
9848
9983
  "application/x-parasolid": "x_t",
9849
9984
  "model/vnd.3dm": "3dm",
9850
9985
  "application/vnd.sketchup.skp": "skp",
9851
- "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"
9852
9991
  };
9853
9992
  function cadPlugin() {
9854
9993
  return {
@@ -9876,6 +10015,36 @@ function cadPlugin() {
9876
10015
  renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
9877
10016
  return { destroy: () => panel.remove() };
9878
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
+ }
9879
10048
  if (extension !== "dxf") {
9880
10049
  const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
9881
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`);
@@ -9974,6 +10143,560 @@ function renderIfc(panel, text) {
9974
10143
  section.append(table);
9975
10144
  panel.append(section);
9976
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
+ }
9977
10700
  function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
9978
10701
  const bytes = new Uint8Array(arrayBuffer);
9979
10702
  const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);