@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.cjs CHANGED
@@ -301,6 +301,9 @@ var extensionMimeMap = {
301
301
  skp: "application/vnd.sketchup.skp",
302
302
  sldprt: "application/sldworks",
303
303
  sldasm: "application/sldworks",
304
+ gds: "application/vnd.gds",
305
+ oas: "application/vnd.oasis.layout",
306
+ oasis: "application/vnd.oasis.layout",
304
307
  ttf: "font/ttf",
305
308
  otf: "font/otf",
306
309
  woff: "font/woff",
@@ -1805,6 +1808,7 @@ function imagePlugin() {
1805
1808
  let dragStartY = 0;
1806
1809
  let startOffsetX = 0;
1807
1810
  let startOffsetY = 0;
1811
+ let activePointerId = null;
1808
1812
  const zoomLabel = document.createElement("span");
1809
1813
  zoomLabel.className = "ofv-image-zoom";
1810
1814
  const updateTransform = () => {
@@ -1852,29 +1856,61 @@ function imagePlugin() {
1852
1856
  if (event.button !== 0) {
1853
1857
  return;
1854
1858
  }
1859
+ if (activePointerId !== null && activePointerId !== event.pointerId) {
1860
+ finishDrag(activePointerId);
1861
+ }
1855
1862
  dragging = true;
1863
+ activePointerId = event.pointerId;
1856
1864
  dragStartX = event.clientX;
1857
1865
  dragStartY = event.clientY;
1858
1866
  startOffsetX = offsetX;
1859
1867
  startOffsetY = offsetY;
1860
1868
  stage.classList.add("is-dragging");
1861
- stage.setPointerCapture(event.pointerId);
1869
+ try {
1870
+ stage.setPointerCapture(event.pointerId);
1871
+ } catch {
1872
+ }
1862
1873
  };
1863
1874
  const onPointerMove = (event) => {
1864
- if (!dragging) {
1875
+ if (!dragging || event.pointerId !== activePointerId) {
1865
1876
  return;
1866
1877
  }
1867
1878
  offsetX = startOffsetX + event.clientX - dragStartX;
1868
1879
  offsetY = startOffsetY + event.clientY - dragStartY;
1869
1880
  updateTransform();
1870
1881
  };
1871
- const onPointerUp = (event) => {
1882
+ const finishDrag = (pointerId) => {
1883
+ const captureId = pointerId ?? activePointerId;
1872
1884
  dragging = false;
1885
+ activePointerId = null;
1873
1886
  stage.classList.remove("is-dragging");
1874
- if (stage.hasPointerCapture(event.pointerId)) {
1875
- stage.releasePointerCapture(event.pointerId);
1887
+ if (captureId !== null && captureId !== void 0) {
1888
+ try {
1889
+ if (stage.hasPointerCapture(captureId)) {
1890
+ stage.releasePointerCapture(captureId);
1891
+ }
1892
+ } catch {
1893
+ }
1876
1894
  }
1877
1895
  };
1896
+ const onPointerUp = (event) => {
1897
+ if (event.pointerId === activePointerId) {
1898
+ finishDrag(event.pointerId);
1899
+ }
1900
+ };
1901
+ const onLostPointerCapture = (event) => {
1902
+ if (event.pointerId === activePointerId) {
1903
+ finishDrag(null);
1904
+ }
1905
+ };
1906
+ const onPointerLeave = (event) => {
1907
+ if (event.pointerId === activePointerId && event.buttons === 0) {
1908
+ finishDrag(event.pointerId);
1909
+ }
1910
+ };
1911
+ const onWindowBlur = () => {
1912
+ finishDrag();
1913
+ };
1878
1914
  const onWheel = (event) => {
1879
1915
  if (!event.ctrlKey && !event.metaKey) {
1880
1916
  return;
@@ -1886,8 +1922,11 @@ function imagePlugin() {
1886
1922
  stage.addEventListener("pointermove", onPointerMove);
1887
1923
  stage.addEventListener("pointerup", onPointerUp);
1888
1924
  stage.addEventListener("pointercancel", onPointerUp);
1925
+ stage.addEventListener("lostpointercapture", onLostPointerCapture);
1926
+ stage.addEventListener("pointerleave", onPointerLeave);
1889
1927
  stage.addEventListener("wheel", onWheel, { passive: false });
1890
1928
  image.addEventListener("error", showImageFallback);
1929
+ window.addEventListener("blur", onWindowBlur);
1891
1930
  stage.append(image);
1892
1931
  wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
1893
1932
  ctx.viewport.append(wrapper);
@@ -1934,8 +1973,12 @@ function imagePlugin() {
1934
1973
  stage.removeEventListener("pointermove", onPointerMove);
1935
1974
  stage.removeEventListener("pointerup", onPointerUp);
1936
1975
  stage.removeEventListener("pointercancel", onPointerUp);
1976
+ stage.removeEventListener("lostpointercapture", onLostPointerCapture);
1977
+ stage.removeEventListener("pointerleave", onPointerLeave);
1937
1978
  stage.removeEventListener("wheel", onWheel);
1938
1979
  image.removeEventListener("error", showImageFallback);
1980
+ window.removeEventListener("blur", onWindowBlur);
1981
+ finishDrag();
1939
1982
  wrapper.remove();
1940
1983
  if (convertedBlob) {
1941
1984
  URL.revokeObjectURL(url);
@@ -4084,6 +4127,38 @@ async function readText(source) {
4084
4127
  return String(source);
4085
4128
  }
4086
4129
 
4130
+ // src/plugins/encrypted.ts
4131
+ function createEncryptedFallback(file, url, copy = {}) {
4132
+ const fallback = document.createElement("div");
4133
+ fallback.className = "ofv-fallback ofv-encrypted";
4134
+ const title = document.createElement("strong");
4135
+ title.textContent = copy.title || "\u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8";
4136
+ const message = document.createElement("span");
4137
+ 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";
4138
+ const meta = document.createElement("dl");
4139
+ meta.className = "ofv-fallback-meta ofv-encrypted-meta";
4140
+ appendEncryptedMeta(meta, "\u6587\u4EF6", file.name || "\u672A\u547D\u540D\u6587\u4EF6");
4141
+ appendEncryptedMeta(meta, "\u683C\u5F0F", file.extension ? `.${file.extension}` : file.mimeType || "\u672A\u77E5");
4142
+ const download = document.createElement("a");
4143
+ download.href = url;
4144
+ download.download = file.name;
4145
+ download.textContent = copy.action || "\u4E0B\u8F7D\u6587\u4EF6";
4146
+ fallback.append(title, message, meta, download);
4147
+ return fallback;
4148
+ }
4149
+ function isEncryptedError(error) {
4150
+ const message = error instanceof Error ? error.message : String(error || "");
4151
+ const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
4152
+ return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(`${name} ${message}`);
4153
+ }
4154
+ function appendEncryptedMeta(parent, label, value) {
4155
+ const key = document.createElement("dt");
4156
+ key.textContent = label;
4157
+ const content = document.createElement("dd");
4158
+ content.textContent = value;
4159
+ parent.append(key, content);
4160
+ }
4161
+
4087
4162
  // src/plugins/pdf.ts
4088
4163
  function multiplyMatrices(m1, m2) {
4089
4164
  return [
@@ -4125,7 +4200,11 @@ function pdfPlugin(options = {}) {
4125
4200
  const doc = await documentTask.promise.catch((error) => {
4126
4201
  viewer.remove();
4127
4202
  ctx.viewport.classList.add("ofv-center");
4128
- const fallback = createPdfFallback(ctx.file.name, url, normalizePdfError(error));
4203
+ const fallback = isEncryptedError(error) ? createEncryptedFallback(ctx.file, url, {
4204
+ title: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4205
+ message: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
4206
+ action: "\u4E0B\u8F7D PDF"
4207
+ }) : createPdfFallback(ctx.file.name, url, normalizePdfError(error));
4129
4208
  ctx.viewport.append(fallback);
4130
4209
  return void 0;
4131
4210
  });
@@ -4415,9 +4494,6 @@ function normalizePdfError(error) {
4415
4494
  const message = error instanceof Error ? error.message : String(error || "");
4416
4495
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
4417
4496
  const lower = `${name} ${message}`.toLowerCase();
4418
- if (lower.includes("password")) {
4419
- return "\u8BE5 PDF \u53D7\u5BC6\u7801\u4FDD\u62A4\uFF0C\u5F53\u524D\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u5185\u76F4\u63A5\u9884\u89C8\u3002";
4420
- }
4421
4497
  if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
4422
4498
  return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
4423
4499
  }
@@ -5447,6 +5523,10 @@ async function renderSheet(panel, arrayBuffer, extension) {
5447
5523
  }
5448
5524
  }
5449
5525
  function renderSheetFallback(panel, extension, detail) {
5526
+ if (isEncryptedText(detail)) {
5527
+ renderEncryptedOfficeByFileInfo(panel, `.${extension || "sheet"}`, "Office \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8");
5528
+ return;
5529
+ }
5450
5530
  const section = createSection("\u8868\u683C\u89E3\u6790\u5931\u8D25");
5451
5531
  const title = document.createElement("p");
5452
5532
  title.textContent = `.${extension || "sheet"} \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790\u4E3A\u53EF\u9884\u89C8\u8868\u683C\u3002`;
@@ -5457,6 +5537,17 @@ function renderSheetFallback(panel, extension, detail) {
5457
5537
  section.append(title, meta, support);
5458
5538
  panel.append(section);
5459
5539
  }
5540
+ function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
5541
+ const section = createSection(title);
5542
+ section.classList.add("ofv-encrypted");
5543
+ const message = document.createElement("p");
5544
+ 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`;
5545
+ section.append(message);
5546
+ panel.append(section);
5547
+ }
5548
+ function isEncryptedText(value) {
5549
+ return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(value);
5550
+ }
5460
5551
  function renderFlatOds(panel, xml) {
5461
5552
  const sheets = parseFlatOds(xml);
5462
5553
  renderParsedSheets(panel, sheets, "FODS \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002");
@@ -6765,7 +6856,7 @@ function ofdPlugin() {
6765
6856
  try {
6766
6857
  zip = await import_jszip4.default.loadAsync(await readArrayBuffer(ctx.file));
6767
6858
  } catch (error) {
6768
- panel.append(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
6859
+ panel.append(createOfdFailure(ctx.file, url, error));
6769
6860
  return {
6770
6861
  destroy() {
6771
6862
  panel.remove();
@@ -6782,7 +6873,7 @@ function ofdPlugin() {
6782
6873
  textFragments.push(...matches);
6783
6874
  }
6784
6875
  } catch (error) {
6785
- panel.append(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
6876
+ panel.append(createOfdFailure(ctx.file, url, error));
6786
6877
  return {
6787
6878
  destroy() {
6788
6879
  panel.remove();
@@ -7407,6 +7498,16 @@ function finiteNumber2(value, fallback) {
7407
7498
  function formatOfdCssNumber(value) {
7408
7499
  return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7409
7500
  }
7501
+ function createOfdFailure(file, url, error) {
7502
+ if (isEncryptedError(error)) {
7503
+ return createEncryptedFallback(file, url, {
7504
+ title: "OFD \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
7505
+ 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",
7506
+ action: "\u4E0B\u8F7D OFD"
7507
+ });
7508
+ }
7509
+ return createOfdFallback(file.name, url, normalizeOfdError(error));
7510
+ }
7410
7511
  function createOfdFallback(fileName, url, detail) {
7411
7512
  const fallback = document.createElement("div");
7412
7513
  fallback.className = "ofv-fallback";
@@ -7475,7 +7576,9 @@ function archivePlugin() {
7475
7576
  try {
7476
7577
  if (ext === "zip") {
7477
7578
  try {
7478
- const zip = await import_jszip5.default.loadAsync(await readArrayBuffer(ctx.file));
7579
+ const zip = await import_jszip5.default.loadAsync(await readArrayBuffer(ctx.file), {
7580
+ decodeFileName: decodeZipFileName
7581
+ });
7479
7582
  archiveEntries = Object.values(zip.files).map((entry) => ({
7480
7583
  name: entry.name,
7481
7584
  unsafeName: entry.unsafeOriginalName,
@@ -7484,7 +7587,7 @@ function archivePlugin() {
7484
7587
  read: () => entry.async("arraybuffer")
7485
7588
  }));
7486
7589
  } catch (zipErr) {
7487
- if (zipErr.message && (zipErr.message.includes("encrypted") || zipErr.message.includes("password") || zipErr.message.includes("protected"))) {
7590
+ if (isEncryptedError(zipErr)) {
7488
7591
  isEncrypted = true;
7489
7592
  } else {
7490
7593
  throw zipErr;
@@ -7517,17 +7620,11 @@ function archivePlugin() {
7517
7620
  parseError = `\u538B\u7F29\u5305\u89E3\u6790\u5931\u8D25\uFF1A${err.message || err}`;
7518
7621
  }
7519
7622
  if (isEncrypted) {
7520
- const fallback = document.createElement("div");
7521
- fallback.className = "ofv-fallback";
7522
- const title = document.createElement("strong");
7523
- title.textContent = "\u8BE5\u538B\u7F29\u5305\u5DF2\u88AB\u52A0\u5BC6\u4FDD\u62A4";
7524
- const meta = document.createElement("span");
7525
- 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";
7526
- const download = document.createElement("a");
7527
- download.href = url;
7528
- download.download = ctx.file.name;
7529
- download.textContent = "\u4E0B\u8F7D\u6587\u4EF6";
7530
- fallback.append(title, meta, download);
7623
+ const fallback = createEncryptedFallback(ctx.file, url, {
7624
+ title: "\u538B\u7F29\u5305\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
7625
+ 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",
7626
+ action: "\u4E0B\u8F7D\u538B\u7F29\u5305"
7627
+ });
7531
7628
  panel.append(fallback);
7532
7629
  ctx.viewport.classList.add("ofv-center");
7533
7630
  return {
@@ -7776,6 +7873,28 @@ async function findSubPreviewPlugin(plugins, file) {
7776
7873
  }
7777
7874
  return fallbackPlugin();
7778
7875
  }
7876
+ function decodeZipFileName(bytes) {
7877
+ const data = Array.isArray(bytes) ? Uint8Array.from(bytes.map((value) => value.charCodeAt(0) & 255)) : bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);
7878
+ const utf8 = decodeZipNameWith(data, "utf-8", true);
7879
+ if (utf8 && !looksMojibake(utf8)) {
7880
+ return utf8;
7881
+ }
7882
+ const gb18030 = decodeZipNameWith(data, "gb18030", false) || decodeZipNameWith(data, "gbk", false);
7883
+ if (gb18030 && !looksMojibake(gb18030)) {
7884
+ return gb18030;
7885
+ }
7886
+ return utf8 || new TextDecoder("latin1").decode(data);
7887
+ }
7888
+ function decodeZipNameWith(bytes, encoding, fatal) {
7889
+ try {
7890
+ return new TextDecoder(encoding, { fatal }).decode(bytes);
7891
+ } catch {
7892
+ return void 0;
7893
+ }
7894
+ }
7895
+ function looksMojibake(value) {
7896
+ return /[\uFFFDÃÂÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß]/.test(value);
7897
+ }
7779
7898
  function createInlineError(titleText, detailText) {
7780
7899
  const fallback = document.createElement("div");
7781
7900
  fallback.className = "ofv-fallback";
@@ -8197,13 +8316,14 @@ function emailPlugin() {
8197
8316
  } else {
8198
8317
  const PostalMime = (await import("postal-mime")).default;
8199
8318
  const parser = new PostalMime();
8200
- let rawText = await readTextFile(ctx.file);
8319
+ let rawSource = await readArrayBuffer(ctx.file);
8201
8320
  if (ext === "mbox") {
8321
+ let rawText = await readTextFile(ctx.file);
8202
8322
  const messages = splitMboxMessages(rawText);
8203
8323
  mboxSummary = messages.map(summarizeMboxMessage);
8204
- rawText = messages[0] || rawText;
8324
+ rawSource = messages[0] || rawText;
8205
8325
  }
8206
- const parsed = await parser.parse(rawText);
8326
+ const parsed = await parser.parse(rawSource);
8207
8327
  const from = parsed.from ? `${parsed.from.name || ""} <${parsed.from.address || ""}>`.trim() : "";
8208
8328
  const to = Array.isArray(parsed.to) ? parsed.to.map((t) => `${t.name || ""} <${t.address || ""}>`.trim()).join("; ") : "";
8209
8329
  const cc = Array.isArray(parsed.cc) ? parsed.cc.map((c) => `${c.name || ""} <${c.address || ""}>`.trim()).join("; ") : "";
@@ -8273,13 +8393,17 @@ function emailPlugin() {
8273
8393
  const sanitizedHtml = sanitizeEmailHtml(html);
8274
8394
  const iframe = document.createElement("iframe");
8275
8395
  iframe.className = "ofv-email-body-iframe";
8276
- iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
8396
+ iframe.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
8277
8397
  iframe.style.cssText = "width: 100%; border: none; background: #fff; min-height: 200px;";
8278
- bodySection.append(iframe);
8279
- iframe.onload = () => {
8398
+ let renderedHtmlBody = false;
8399
+ const renderHtmlBody = () => {
8400
+ if (renderedHtmlBody) {
8401
+ return;
8402
+ }
8280
8403
  try {
8281
8404
  const idoc = iframe.contentDocument || iframe.contentWindow?.document;
8282
8405
  if (idoc) {
8406
+ renderedHtmlBody = true;
8283
8407
  idoc.open();
8284
8408
  idoc.write(`
8285
8409
  <!doctype html>
@@ -8330,6 +8454,9 @@ function emailPlugin() {
8330
8454
  console.error("Failed to write html body to email iframe:", err);
8331
8455
  }
8332
8456
  };
8457
+ iframe.addEventListener("load", renderHtmlBody, { once: true });
8458
+ bodySection.append(iframe);
8459
+ renderHtmlBody();
8333
8460
  } else {
8334
8461
  const pre = document.createElement("pre");
8335
8462
  pre.className = "ofv-text-block";
@@ -9851,6 +9978,7 @@ function decodeDrawioDiagram(value) {
9851
9978
  }
9852
9979
 
9853
9980
  // src/plugins/cad.ts
9981
+ var import_pako3 = __toESM(require("pako"), 1);
9854
9982
  var cadExtensions = /* @__PURE__ */ new Set([
9855
9983
  "dxf",
9856
9984
  "dwg",
@@ -9867,7 +9995,10 @@ var cadExtensions = /* @__PURE__ */ new Set([
9867
9995
  "3dm",
9868
9996
  "skp",
9869
9997
  "sldprt",
9870
- "sldasm"
9998
+ "sldasm",
9999
+ "gds",
10000
+ "oas",
10001
+ "oasis"
9871
10002
  ]);
9872
10003
  var cadMimeTypes = /* @__PURE__ */ new Set([
9873
10004
  "application/acad",
@@ -9884,7 +10015,11 @@ var cadMimeTypes = /* @__PURE__ */ new Set([
9884
10015
  "application/x-parasolid",
9885
10016
  "model/vnd.3dm",
9886
10017
  "application/vnd.sketchup.skp",
9887
- "application/sldworks"
10018
+ "application/sldworks",
10019
+ "application/vnd.gds",
10020
+ "application/x-gdsii",
10021
+ "application/vnd.oasis.layout",
10022
+ "application/x-oasis-layout"
9888
10023
  ]);
9889
10024
  var cadMimeFormatMap = {
9890
10025
  "application/acad": "dwg",
@@ -9901,7 +10036,11 @@ var cadMimeFormatMap = {
9901
10036
  "application/x-parasolid": "x_t",
9902
10037
  "model/vnd.3dm": "3dm",
9903
10038
  "application/vnd.sketchup.skp": "skp",
9904
- "application/sldworks": "sldprt"
10039
+ "application/sldworks": "sldprt",
10040
+ "application/vnd.gds": "gds",
10041
+ "application/x-gdsii": "gds",
10042
+ "application/vnd.oasis.layout": "oas",
10043
+ "application/x-oasis-layout": "oas"
9905
10044
  };
9906
10045
  function cadPlugin() {
9907
10046
  return {
@@ -9929,6 +10068,36 @@ function cadPlugin() {
9929
10068
  renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
9930
10069
  return { destroy: () => panel.remove() };
9931
10070
  }
10071
+ if (extension === "gds") {
10072
+ const viewer2 = renderLayoutPreview(panel, parseGdsLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
10073
+ return {
10074
+ canCommand(command) {
10075
+ return viewer2.canCommand(command);
10076
+ },
10077
+ command(command) {
10078
+ return viewer2.command(command);
10079
+ },
10080
+ destroy() {
10081
+ viewer2.destroy();
10082
+ panel.remove();
10083
+ }
10084
+ };
10085
+ }
10086
+ if (extension === "oas" || extension === "oasis") {
10087
+ const viewer2 = renderLayoutPreview(panel, parseOasisLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
10088
+ return {
10089
+ canCommand(command) {
10090
+ return viewer2.canCommand(command);
10091
+ },
10092
+ command(command) {
10093
+ return viewer2.command(command);
10094
+ },
10095
+ destroy() {
10096
+ viewer2.destroy();
10097
+ panel.remove();
10098
+ }
10099
+ };
10100
+ }
9932
10101
  if (extension !== "dxf") {
9933
10102
  const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
9934
10103
  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`);
@@ -10027,6 +10196,560 @@ function renderIfc(panel, text) {
10027
10196
  section.append(table);
10028
10197
  panel.append(section);
10029
10198
  }
10199
+ var layoutPalette = [
10200
+ "#2563eb",
10201
+ "#dc2626",
10202
+ "#059669",
10203
+ "#7c3aed",
10204
+ "#d97706",
10205
+ "#0891b2",
10206
+ "#be123c",
10207
+ "#4f46e5",
10208
+ "#15803d",
10209
+ "#a16207"
10210
+ ];
10211
+ var gdsRecordNames = {
10212
+ 0: "HEADER",
10213
+ 1: "BGNLIB",
10214
+ 2: "LIBNAME",
10215
+ 3: "UNITS",
10216
+ 4: "ENDLIB",
10217
+ 5: "BGNSTR",
10218
+ 6: "STRNAME",
10219
+ 7: "ENDSTR",
10220
+ 8: "BOUNDARY",
10221
+ 9: "PATH",
10222
+ 10: "SREF",
10223
+ 11: "AREF",
10224
+ 12: "TEXT",
10225
+ 13: "LAYER",
10226
+ 14: "DATATYPE",
10227
+ 15: "WIDTH",
10228
+ 16: "XY",
10229
+ 17: "ENDEL",
10230
+ 18: "SNAME",
10231
+ 22: "TEXTTYPE",
10232
+ 25: "STRING",
10233
+ 45: "BOX"
10234
+ };
10235
+ var oasisRecordNames = {
10236
+ 0: "PAD",
10237
+ 1: "START",
10238
+ 2: "END",
10239
+ 3: "CELLNAME",
10240
+ 4: "CELLNAME-REF",
10241
+ 5: "TEXTSTRING",
10242
+ 6: "TEXTSTRING-REF",
10243
+ 7: "PROPNAME",
10244
+ 8: "PROPNAME-REF",
10245
+ 9: "PROPSTRING",
10246
+ 10: "PROPSTRING-REF",
10247
+ 11: "LAYERNAME",
10248
+ 12: "LAYERNAME-REF",
10249
+ 13: "CELL",
10250
+ 14: "XYABSOLUTE",
10251
+ 15: "XYRELATIVE",
10252
+ 16: "PLACEMENT",
10253
+ 17: "PLACEMENT",
10254
+ 18: "TEXT",
10255
+ 19: "RECTANGLE",
10256
+ 20: "POLYGON",
10257
+ 21: "PATH",
10258
+ 22: "TRAPEZOID",
10259
+ 23: "TRAPEZOID",
10260
+ 24: "TRAPEZOID",
10261
+ 25: "CTRAPEZOID",
10262
+ 26: "CIRCLE",
10263
+ 27: "PROPERTY",
10264
+ 28: "PROPERTY",
10265
+ 29: "XNAME",
10266
+ 30: "XNAME-REF",
10267
+ 31: "XELEMENT",
10268
+ 32: "XGEOMETRY",
10269
+ 33: "CBLOCK"
10270
+ };
10271
+ function renderLayoutPreview(panel, data, ctx) {
10272
+ const section = createSection(`${data.format} \u7248\u56FE\u9884\u89C8`);
10273
+ const summary = document.createElement("div");
10274
+ summary.className = "ofv-cad-summary ofv-layout-summary";
10275
+ appendMeta5(summary, "\u6587\u4EF6", data.fileName);
10276
+ appendMeta5(summary, "\u683C\u5F0F", data.format);
10277
+ if (data.libraryName) {
10278
+ appendMeta5(summary, "\u5E93", data.libraryName);
10279
+ }
10280
+ if (data.version) {
10281
+ appendMeta5(summary, "\u7248\u672C", data.version);
10282
+ }
10283
+ if (data.unit) {
10284
+ appendMeta5(summary, "\u5355\u4F4D", data.unit);
10285
+ }
10286
+ appendMeta5(summary, "Cell", data.cells.length);
10287
+ appendMeta5(summary, "\u51E0\u4F55", data.shapes.length);
10288
+ appendMeta5(summary, "\u5F15\u7528", data.references.length);
10289
+ appendMeta5(summary, "\u6587\u5B57", data.labels.length);
10290
+ for (const [label, value] of data.metadata) {
10291
+ appendMeta5(summary, label, value);
10292
+ }
10293
+ section.append(summary);
10294
+ for (const noteText of [...data.notes, ...data.warnings]) {
10295
+ const note = document.createElement("p");
10296
+ note.className = data.warnings.includes(noteText) ? "ofv-layout-warning" : "ofv-layout-note";
10297
+ note.textContent = noteText;
10298
+ section.append(note);
10299
+ }
10300
+ const bounds = computeLayoutBounds(data.shapes, data.labels, data.references);
10301
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
10302
+ svg.setAttribute("class", "ofv-svg-stage ofv-layout-stage");
10303
+ let currentViewBox = { x: bounds.minX, y: bounds.minY, width: bounds.width, height: bounds.height };
10304
+ const initialViewBox = { ...currentViewBox };
10305
+ const applyViewBox = () => {
10306
+ svg.setAttribute("viewBox", `${currentViewBox.x} ${currentViewBox.y} ${currentViewBox.width} ${currentViewBox.height}`);
10307
+ };
10308
+ applyViewBox();
10309
+ if (data.shapes.length === 0) {
10310
+ const empty = document.createElementNS(svg.namespaceURI, "text");
10311
+ empty.setAttribute("x", String(bounds.minX + bounds.width * 0.5));
10312
+ empty.setAttribute("y", String(bounds.minY + bounds.height * 0.5));
10313
+ empty.setAttribute("text-anchor", "middle");
10314
+ empty.setAttribute("font-size", String(Math.max(bounds.width, bounds.height) / 34));
10315
+ empty.setAttribute("fill", "currentColor");
10316
+ empty.textContent = "\u5DF2\u8BC6\u522B\u7248\u56FE\u6587\u4EF6\uFF0C\u5F53\u524D\u6587\u4EF6\u672A\u89E3\u6790\u51FA\u53EF\u7ED8\u5236\u51E0\u4F55";
10317
+ svg.append(empty);
10318
+ }
10319
+ const layerIndex = new Map([...data.layers.keys()].sort((a, b) => a.localeCompare(b)).map((layer, index) => [layer, index]));
10320
+ for (const shape of data.shapes.slice(0, 6e3)) {
10321
+ const color = layoutPalette[(layerIndex.get(shape.layer) || 0) % layoutPalette.length];
10322
+ if (shape.kind === "path") {
10323
+ const polyline = document.createElementNS(svg.namespaceURI, "polyline");
10324
+ polyline.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
10325
+ polyline.setAttribute("fill", "none");
10326
+ polyline.setAttribute("stroke", color);
10327
+ polyline.setAttribute("stroke-width", String(Math.max(bounds.stroke, Math.abs(shape.width || 0))));
10328
+ polyline.setAttribute("stroke-linecap", "round");
10329
+ polyline.setAttribute("stroke-linejoin", "round");
10330
+ applyLayer(polyline, shape.layer);
10331
+ svg.append(polyline);
10332
+ continue;
10333
+ }
10334
+ const polygon = document.createElementNS(svg.namespaceURI, "polygon");
10335
+ polygon.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
10336
+ polygon.setAttribute("fill", color);
10337
+ polygon.setAttribute("fill-opacity", "0.18");
10338
+ polygon.setAttribute("stroke", color);
10339
+ polygon.setAttribute("stroke-width", String(bounds.stroke));
10340
+ polygon.setAttribute("vector-effect", "non-scaling-stroke");
10341
+ applyLayer(polygon, shape.layer);
10342
+ svg.append(polygon);
10343
+ }
10344
+ for (const label of data.labels.slice(0, 400)) {
10345
+ const text = document.createElementNS(svg.namespaceURI, "text");
10346
+ text.setAttribute("x", String(label.x));
10347
+ text.setAttribute("y", String(-label.y));
10348
+ text.setAttribute("font-size", String(Math.max(bounds.stroke * 12, Math.max(bounds.width, bounds.height) / 120)));
10349
+ text.setAttribute("fill", "currentColor");
10350
+ text.textContent = label.text;
10351
+ applyLayer(text, label.layer);
10352
+ svg.append(text);
10353
+ }
10354
+ const layers = [...data.layers.keys()].sort((a, b) => a.localeCompare(b));
10355
+ if (layers.length > 0) {
10356
+ section.append(createLayoutLayerControls(svg, layers, data.layers));
10357
+ }
10358
+ section.append(svg);
10359
+ if (data.cells.length > 0) {
10360
+ section.append(createLayoutCellList(data.cells, data.references));
10361
+ }
10362
+ panel.append(section);
10363
+ const updateToolbarZoom = () => ctx.toolbar?.setZoom(initialViewBox.width / currentViewBox.width);
10364
+ updateToolbarZoom();
10365
+ return {
10366
+ canCommand(command) {
10367
+ return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
10368
+ },
10369
+ command(command) {
10370
+ if (command === "zoom-in" || command === "zoom-out") {
10371
+ const factor = command === "zoom-in" ? 0.82 : 1.18;
10372
+ const centerX = currentViewBox.x + currentViewBox.width / 2;
10373
+ const centerY = currentViewBox.y + currentViewBox.height / 2;
10374
+ currentViewBox.width *= factor;
10375
+ currentViewBox.height *= factor;
10376
+ currentViewBox.x = centerX - currentViewBox.width / 2;
10377
+ currentViewBox.y = centerY - currentViewBox.height / 2;
10378
+ applyViewBox();
10379
+ updateToolbarZoom();
10380
+ return true;
10381
+ }
10382
+ if (command === "zoom-reset") {
10383
+ currentViewBox = { ...initialViewBox };
10384
+ applyViewBox();
10385
+ updateToolbarZoom();
10386
+ return true;
10387
+ }
10388
+ return false;
10389
+ },
10390
+ destroy() {
10391
+ ctx.toolbar?.setZoom(void 0);
10392
+ }
10393
+ };
10394
+ }
10395
+ function parseGdsLayout(bytes, fileName) {
10396
+ const shapes = [];
10397
+ const labels = [];
10398
+ const references = [];
10399
+ const cells = [];
10400
+ const layers = /* @__PURE__ */ new Map();
10401
+ const recordCounts = /* @__PURE__ */ new Map();
10402
+ const warnings = [];
10403
+ let libraryName = "";
10404
+ let version = "";
10405
+ let unit = "";
10406
+ let offset = 0;
10407
+ let current = {};
10408
+ let currentKind = "";
10409
+ while (offset + 4 <= bytes.length) {
10410
+ const length = readUInt16(bytes, offset);
10411
+ const recordType = bytes[offset + 2];
10412
+ const data = bytes.slice(offset + 4, offset + length);
10413
+ const name = gdsRecordNames[recordType] || `0x${recordType.toString(16).padStart(2, "0")}`;
10414
+ recordCounts.set(name, (recordCounts.get(name) || 0) + 1);
10415
+ if (length < 4 || offset + length > bytes.length) {
10416
+ warnings.push(`GDS \u8BB0\u5F55\u5728 ${offset} \u5B57\u8282\u5904\u957F\u5EA6\u5F02\u5E38\uFF0C\u5DF2\u505C\u6B62\u89E3\u6790\u3002`);
10417
+ break;
10418
+ }
10419
+ if (recordType === 0 && data.length >= 2) {
10420
+ version = String(readUInt16(data, 0));
10421
+ } else if (recordType === 2) {
10422
+ libraryName = readGdsString(data);
10423
+ } else if (recordType === 3 && data.length >= 16) {
10424
+ unit = `${formatGdsReal(data, 0)} / ${formatGdsReal(data, 8)}`;
10425
+ } else if (recordType === 6) {
10426
+ cells.push(readGdsString(data));
10427
+ } else if (recordType === 8 || recordType === 9 || recordType === 45) {
10428
+ currentKind = recordType === 9 ? "path" : recordType === 45 ? "box" : "boundary";
10429
+ current = { kind: currentKind, layer: "0", datatype: "0", points: [] };
10430
+ } else if (recordType === 12) {
10431
+ currentKind = "text";
10432
+ current = { layer: "0", text: "", x: 0, y: 0 };
10433
+ } else if (recordType === 10 || recordType === 11) {
10434
+ currentKind = "reference";
10435
+ current = { cell: "", x: 0, y: 0 };
10436
+ } else if (recordType === 13 && data.length >= 2) {
10437
+ current.layer = String(readInt16(data, 0));
10438
+ } else if ((recordType === 14 || recordType === 22) && data.length >= 2) {
10439
+ current.datatype = String(readInt16(data, 0));
10440
+ } else if (recordType === 15 && data.length >= 4) {
10441
+ current.width = Math.abs(readInt32(data, 0));
10442
+ } else if (recordType === 16) {
10443
+ const points = readGdsPoints(data);
10444
+ if (currentKind === "text" && points[0]) {
10445
+ current.x = points[0][0];
10446
+ current.y = points[0][1];
10447
+ } else if (currentKind === "reference" && points[0]) {
10448
+ current.x = points[0][0];
10449
+ current.y = points[0][1];
10450
+ } else {
10451
+ current.points = points;
10452
+ }
10453
+ } else if (recordType === 18) {
10454
+ current.cell = readGdsString(data);
10455
+ } else if (recordType === 25) {
10456
+ current.text = readGdsString(data);
10457
+ } else if (recordType === 17) {
10458
+ if ((currentKind === "boundary" || currentKind === "path" || currentKind === "box") && current.points && current.points.length > 1) {
10459
+ const shape = {
10460
+ kind: current.kind || "boundary",
10461
+ layer: String(current.layer || "0"),
10462
+ datatype: current.datatype,
10463
+ points: current.points,
10464
+ width: current.width
10465
+ };
10466
+ shapes.push(shape);
10467
+ layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
10468
+ } else if (currentKind === "text" && current.text) {
10469
+ const label = {
10470
+ layer: String(current.layer || "0"),
10471
+ text: String(current.text),
10472
+ x: Number(current.x || 0),
10473
+ y: Number(current.y || 0)
10474
+ };
10475
+ labels.push(label);
10476
+ layers.set(label.layer, (layers.get(label.layer) || 0) + 1);
10477
+ } else if (currentKind === "reference" && current.cell) {
10478
+ references.push({ cell: String(current.cell), x: Number(current.x || 0), y: Number(current.y || 0) });
10479
+ }
10480
+ current = {};
10481
+ currentKind = "";
10482
+ }
10483
+ offset += length;
10484
+ }
10485
+ return {
10486
+ format: "GDSII",
10487
+ fileName,
10488
+ libraryName,
10489
+ version: version ? `Stream ${version}` : void 0,
10490
+ unit,
10491
+ cells,
10492
+ shapes,
10493
+ labels,
10494
+ references,
10495
+ layers,
10496
+ metadata: [
10497
+ ["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
10498
+ ["\u8BB0\u5F55", sumCounts(recordCounts)],
10499
+ ["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size]
10500
+ ],
10501
+ notes: [
10502
+ `\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`
10503
+ ],
10504
+ warnings
10505
+ };
10506
+ }
10507
+ function parseOasisLayout(bytes, fileName) {
10508
+ const chunks = extractOasisCblocks(bytes);
10509
+ const expanded = chunks.flatMap((chunk) => [...chunk.bytes]);
10510
+ 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_"));
10511
+ const propertyNames = uniqueHints(chunks.flatMap((chunk) => extractAsciiRuns(chunk.bytes)).filter((item) => item.startsWith("S_")));
10512
+ const recordCounts = scanOasisRecordCounts(bytes);
10513
+ const expandedCounts = scanOasisRecordCounts(new Uint8Array(expanded));
10514
+ const layers = /* @__PURE__ */ new Map();
10515
+ const shapes = [];
10516
+ const labels = [];
10517
+ const references = [];
10518
+ for (const name of cellNames) {
10519
+ references.push({ cell: name, x: 0, y: 0 });
10520
+ }
10521
+ const pseudo = createOasisStructureShapes(cellNames, chunks.length || recordCounts.size || 1);
10522
+ for (const shape of pseudo) {
10523
+ shapes.push(shape);
10524
+ layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
10525
+ }
10526
+ for (let index = 0; index < cellNames.length; index++) {
10527
+ labels.push({ layer: "cell", text: cellNames[index], x: 12, y: -(18 + index * 18) });
10528
+ }
10529
+ if (cellNames.length > 0) {
10530
+ layers.set("cell", (layers.get("cell") || 0) + cellNames.length);
10531
+ }
10532
+ const version = readOasisVersion(bytes);
10533
+ const cblockText = chunks.length ? `${chunks.length} \u4E2A\uFF0C\u5C55\u5F00 ${formatBytes3(chunks.reduce((sum, chunk) => sum + chunk.bytes.byteLength, 0))}` : "\u672A\u53D1\u73B0";
10534
+ const notes = [
10535
+ "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"
10536
+ ];
10537
+ if (propertyNames.length > 0) {
10538
+ notes.push(`\u8BC6\u522B\u5230\u5C5E\u6027\uFF1A${propertyNames.slice(0, 5).join("\u3001")}`);
10539
+ }
10540
+ return {
10541
+ format: "OASIS",
10542
+ fileName,
10543
+ version,
10544
+ cells: cellNames,
10545
+ shapes,
10546
+ labels,
10547
+ references: [],
10548
+ layers,
10549
+ metadata: [
10550
+ ["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
10551
+ ["CBLOCK", cblockText],
10552
+ ["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size + expandedCounts.size],
10553
+ ["\u53EF\u8BFB\u7247\u6BB5", cellNames.length + propertyNames.length]
10554
+ ],
10555
+ notes,
10556
+ 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"] : []
10557
+ };
10558
+ }
10559
+ function computeLayoutBounds(shapes, labels, references) {
10560
+ const xs = [];
10561
+ const ys = [];
10562
+ for (const shape of shapes) {
10563
+ for (const [x, y] of shape.points) {
10564
+ xs.push(x);
10565
+ ys.push(-y);
10566
+ }
10567
+ }
10568
+ for (const label of labels) {
10569
+ xs.push(label.x, label.x + label.text.length * 12);
10570
+ ys.push(-label.y, -label.y - 16);
10571
+ }
10572
+ for (const reference of references) {
10573
+ xs.push(reference.x);
10574
+ ys.push(-reference.y);
10575
+ }
10576
+ const minX = Math.min(...xs, 0);
10577
+ const maxX = Math.max(...xs, 100);
10578
+ const minY = Math.min(...ys, 0);
10579
+ const maxY = Math.max(...ys, 100);
10580
+ const width = Math.max(1, maxX - minX);
10581
+ const height = Math.max(1, maxY - minY);
10582
+ const padding = Math.max(width, height) * 0.06;
10583
+ return {
10584
+ minX: minX - padding,
10585
+ minY: minY - padding,
10586
+ width: width + padding * 2,
10587
+ height: height + padding * 2,
10588
+ stroke: Math.max(width, height) / 900
10589
+ };
10590
+ }
10591
+ function createLayoutLayerControls(svg, layers, counts) {
10592
+ const controls = document.createElement("div");
10593
+ controls.className = "ofv-cad-layers ofv-layout-layers";
10594
+ const title = document.createElement("strong");
10595
+ title.textContent = `\u56FE\u5C42 ${layers.length}`;
10596
+ controls.append(title);
10597
+ for (const layer of layers) {
10598
+ const label = document.createElement("label");
10599
+ const checkbox = document.createElement("input");
10600
+ checkbox.type = "checkbox";
10601
+ checkbox.checked = true;
10602
+ checkbox.addEventListener("change", () => {
10603
+ for (const element of svg.querySelectorAll(`[data-layer="${escapeCssAttribute(layer)}"]`)) {
10604
+ element.style.display = checkbox.checked ? "" : "none";
10605
+ }
10606
+ });
10607
+ const name = document.createElement("span");
10608
+ name.textContent = `${layer} (${counts.get(layer) || 0})`;
10609
+ label.append(checkbox, name);
10610
+ controls.append(label);
10611
+ }
10612
+ return controls;
10613
+ }
10614
+ function createLayoutCellList(cells, references) {
10615
+ const details = document.createElement("details");
10616
+ details.className = "ofv-details ofv-layout-cells";
10617
+ details.open = true;
10618
+ const summary = document.createElement("summary");
10619
+ summary.textContent = `Cell \u7ED3\u6784 ${cells.length}`;
10620
+ const list = document.createElement("ul");
10621
+ const refCounts = countBy(references.map((reference) => reference.cell));
10622
+ for (const cell of cells.slice(0, 120)) {
10623
+ const item = document.createElement("li");
10624
+ const count = refCounts.get(cell) || 0;
10625
+ item.textContent = count > 0 ? `${cell} \xB7 \u5F15\u7528 ${count}` : cell;
10626
+ list.append(item);
10627
+ }
10628
+ details.append(summary, list);
10629
+ return details;
10630
+ }
10631
+ function readUInt16(bytes, offset) {
10632
+ return bytes[offset] << 8 | bytes[offset + 1];
10633
+ }
10634
+ function readInt16(bytes, offset) {
10635
+ const value = readUInt16(bytes, offset);
10636
+ return value & 32768 ? value - 65536 : value;
10637
+ }
10638
+ function readInt32(bytes, offset) {
10639
+ const value = bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
10640
+ return value | 0;
10641
+ }
10642
+ function readGdsString(bytes) {
10643
+ return new TextDecoder("ascii").decode(bytes).replace(/\0+$/g, "").trim();
10644
+ }
10645
+ function readGdsPoints(bytes) {
10646
+ const points = [];
10647
+ for (let offset = 0; offset + 7 < bytes.length; offset += 8) {
10648
+ points.push([readInt32(bytes, offset), readInt32(bytes, offset + 4)]);
10649
+ }
10650
+ return points;
10651
+ }
10652
+ function formatGdsReal(bytes, offset) {
10653
+ const value = readGdsReal(bytes, offset);
10654
+ if (!Number.isFinite(value) || value === 0) {
10655
+ return "0";
10656
+ }
10657
+ if (Math.abs(value) < 1e-3 || Math.abs(value) >= 1e4) {
10658
+ return value.toExponential(4);
10659
+ }
10660
+ return String(Number(value.toPrecision(6)));
10661
+ }
10662
+ function readGdsReal(bytes, offset) {
10663
+ const first = bytes[offset];
10664
+ if (!first) {
10665
+ return 0;
10666
+ }
10667
+ const sign = first & 128 ? -1 : 1;
10668
+ const exponent = (first & 127) - 64;
10669
+ let mantissa = 0;
10670
+ for (let index = 1; index < 8; index++) {
10671
+ mantissa = mantissa * 256 + bytes[offset + index];
10672
+ }
10673
+ return sign * (mantissa / Math.pow(2, 56)) * Math.pow(16, exponent);
10674
+ }
10675
+ function sumCounts(counts) {
10676
+ return [...counts.values()].reduce((sum, count) => sum + count, 0);
10677
+ }
10678
+ function extractOasisCblocks(bytes) {
10679
+ const chunks = [];
10680
+ const seen = /* @__PURE__ */ new Set();
10681
+ const limit = Math.min(bytes.length, 25e4);
10682
+ for (let offset = 0; offset < limit; offset++) {
10683
+ try {
10684
+ const inflated = import_pako3.default.inflateRaw(bytes.slice(offset));
10685
+ if (inflated.byteLength < 4) {
10686
+ continue;
10687
+ }
10688
+ const ascii = extractAsciiRuns(inflated);
10689
+ const hasLayoutSignal = ascii.some((item) => item.startsWith("S_") || /TOP|CELL|DIE|SIZE/i.test(item));
10690
+ const hasRecordSignal = inflated.some((byte) => byte >= 13 && byte <= 34);
10691
+ if (!hasLayoutSignal && !hasRecordSignal) {
10692
+ continue;
10693
+ }
10694
+ const signature = `${inflated.byteLength}:${Array.from(inflated.slice(0, 12)).join(",")}`;
10695
+ if (seen.has(signature)) {
10696
+ continue;
10697
+ }
10698
+ seen.add(signature);
10699
+ chunks.push({ offset, bytes: inflated });
10700
+ if (chunks.length >= 12) {
10701
+ break;
10702
+ }
10703
+ } catch {
10704
+ }
10705
+ }
10706
+ return chunks;
10707
+ }
10708
+ function scanOasisRecordCounts(bytes) {
10709
+ const counts = /* @__PURE__ */ new Map();
10710
+ for (const byte of bytes.slice(0, Math.min(bytes.length, 12e3))) {
10711
+ const name = oasisRecordNames[byte];
10712
+ if (name) {
10713
+ counts.set(name, (counts.get(name) || 0) + 1);
10714
+ }
10715
+ }
10716
+ return counts;
10717
+ }
10718
+ function readOasisVersion(bytes) {
10719
+ const magic = "%SEMI-OASIS\r\n";
10720
+ const header = new TextDecoder("ascii").decode(bytes.slice(0, Math.min(bytes.length, 48)));
10721
+ if (!header.startsWith(magic)) {
10722
+ return void 0;
10723
+ }
10724
+ const start = magic.length;
10725
+ if (bytes[start] !== 1) {
10726
+ return "OASIS";
10727
+ }
10728
+ const length = bytes[start + 1];
10729
+ const version = new TextDecoder("ascii").decode(bytes.slice(start + 2, start + 2 + length));
10730
+ return version ? `OASIS ${version}` : "OASIS";
10731
+ }
10732
+ function createOasisStructureShapes(cellNames, fallbackCount) {
10733
+ const rows = Math.max(1, cellNames.length || fallbackCount);
10734
+ const shapes = [];
10735
+ for (let index = 0; index < rows; index++) {
10736
+ const top = -(index * 18);
10737
+ const height = 12;
10738
+ const width = 88 + Math.min((cellNames[index]?.length || 5) * 4, 90);
10739
+ shapes.push({
10740
+ kind: "box",
10741
+ layer: "cell",
10742
+ points: [
10743
+ [0, top],
10744
+ [width, top],
10745
+ [width, top - height],
10746
+ [0, top - height],
10747
+ [0, top]
10748
+ ]
10749
+ });
10750
+ }
10751
+ return shapes;
10752
+ }
10030
10753
  function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
10031
10754
  const bytes = new Uint8Array(arrayBuffer);
10032
10755
  const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);