@open-file-viewer/core 0.1.5 → 0.1.7

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",
@@ -321,7 +324,7 @@ var extensionMimeMap = {
321
324
  };
322
325
  async function normalizeFile(source, fileName, mimeType) {
323
326
  if (typeof source === "string") {
324
- const name2 = fileName || source.split("?")[0]?.split("/").pop() || "remote-file";
327
+ const name2 = fileName || getFileNameFromUrl(source) || "remote-file";
325
328
  const extension2 = getExtension(name2);
326
329
  return {
327
330
  source,
@@ -366,6 +369,17 @@ async function normalizeFile(source, fileName, mimeType) {
366
369
  blob
367
370
  };
368
371
  }
372
+ function getFileNameFromUrl(source) {
373
+ const rawName = source.split(/[?#]/, 1)[0]?.split("/").filter(Boolean).pop() || "";
374
+ if (!rawName) {
375
+ return "";
376
+ }
377
+ try {
378
+ return decodeURIComponent(rawName);
379
+ } catch {
380
+ return rawName;
381
+ }
382
+ }
369
383
  function getExtension(name) {
370
384
  const clean = name.split("?")[0]?.split("#")[0] || "";
371
385
  const index = clean.lastIndexOf(".");
@@ -1805,6 +1819,7 @@ function imagePlugin() {
1805
1819
  let dragStartY = 0;
1806
1820
  let startOffsetX = 0;
1807
1821
  let startOffsetY = 0;
1822
+ let activePointerId = null;
1808
1823
  const zoomLabel = document.createElement("span");
1809
1824
  zoomLabel.className = "ofv-image-zoom";
1810
1825
  const updateTransform = () => {
@@ -1852,29 +1867,61 @@ function imagePlugin() {
1852
1867
  if (event.button !== 0) {
1853
1868
  return;
1854
1869
  }
1870
+ if (activePointerId !== null && activePointerId !== event.pointerId) {
1871
+ finishDrag(activePointerId);
1872
+ }
1855
1873
  dragging = true;
1874
+ activePointerId = event.pointerId;
1856
1875
  dragStartX = event.clientX;
1857
1876
  dragStartY = event.clientY;
1858
1877
  startOffsetX = offsetX;
1859
1878
  startOffsetY = offsetY;
1860
1879
  stage.classList.add("is-dragging");
1861
- stage.setPointerCapture(event.pointerId);
1880
+ try {
1881
+ stage.setPointerCapture(event.pointerId);
1882
+ } catch {
1883
+ }
1862
1884
  };
1863
1885
  const onPointerMove = (event) => {
1864
- if (!dragging) {
1886
+ if (!dragging || event.pointerId !== activePointerId) {
1865
1887
  return;
1866
1888
  }
1867
1889
  offsetX = startOffsetX + event.clientX - dragStartX;
1868
1890
  offsetY = startOffsetY + event.clientY - dragStartY;
1869
1891
  updateTransform();
1870
1892
  };
1871
- const onPointerUp = (event) => {
1893
+ const finishDrag = (pointerId) => {
1894
+ const captureId = pointerId ?? activePointerId;
1872
1895
  dragging = false;
1896
+ activePointerId = null;
1873
1897
  stage.classList.remove("is-dragging");
1874
- if (stage.hasPointerCapture(event.pointerId)) {
1875
- stage.releasePointerCapture(event.pointerId);
1898
+ if (captureId !== null && captureId !== void 0) {
1899
+ try {
1900
+ if (stage.hasPointerCapture(captureId)) {
1901
+ stage.releasePointerCapture(captureId);
1902
+ }
1903
+ } catch {
1904
+ }
1905
+ }
1906
+ };
1907
+ const onPointerUp = (event) => {
1908
+ if (event.pointerId === activePointerId) {
1909
+ finishDrag(event.pointerId);
1910
+ }
1911
+ };
1912
+ const onLostPointerCapture = (event) => {
1913
+ if (event.pointerId === activePointerId) {
1914
+ finishDrag(null);
1915
+ }
1916
+ };
1917
+ const onPointerLeave = (event) => {
1918
+ if (event.pointerId === activePointerId && event.buttons === 0) {
1919
+ finishDrag(event.pointerId);
1876
1920
  }
1877
1921
  };
1922
+ const onWindowBlur = () => {
1923
+ finishDrag();
1924
+ };
1878
1925
  const onWheel = (event) => {
1879
1926
  if (!event.ctrlKey && !event.metaKey) {
1880
1927
  return;
@@ -1886,8 +1933,11 @@ function imagePlugin() {
1886
1933
  stage.addEventListener("pointermove", onPointerMove);
1887
1934
  stage.addEventListener("pointerup", onPointerUp);
1888
1935
  stage.addEventListener("pointercancel", onPointerUp);
1936
+ stage.addEventListener("lostpointercapture", onLostPointerCapture);
1937
+ stage.addEventListener("pointerleave", onPointerLeave);
1889
1938
  stage.addEventListener("wheel", onWheel, { passive: false });
1890
1939
  image.addEventListener("error", showImageFallback);
1940
+ window.addEventListener("blur", onWindowBlur);
1891
1941
  stage.append(image);
1892
1942
  wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
1893
1943
  ctx.viewport.append(wrapper);
@@ -1934,8 +1984,12 @@ function imagePlugin() {
1934
1984
  stage.removeEventListener("pointermove", onPointerMove);
1935
1985
  stage.removeEventListener("pointerup", onPointerUp);
1936
1986
  stage.removeEventListener("pointercancel", onPointerUp);
1987
+ stage.removeEventListener("lostpointercapture", onLostPointerCapture);
1988
+ stage.removeEventListener("pointerleave", onPointerLeave);
1937
1989
  stage.removeEventListener("wheel", onWheel);
1938
1990
  image.removeEventListener("error", showImageFallback);
1991
+ window.removeEventListener("blur", onWindowBlur);
1992
+ finishDrag();
1939
1993
  wrapper.remove();
1940
1994
  if (convertedBlob) {
1941
1995
  URL.revokeObjectURL(url);
@@ -4084,6 +4138,38 @@ async function readText(source) {
4084
4138
  return String(source);
4085
4139
  }
4086
4140
 
4141
+ // src/plugins/encrypted.ts
4142
+ function createEncryptedFallback(file, url, copy = {}) {
4143
+ const fallback = document.createElement("div");
4144
+ fallback.className = "ofv-fallback ofv-encrypted";
4145
+ const title = document.createElement("strong");
4146
+ title.textContent = copy.title || "\u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8";
4147
+ const message = document.createElement("span");
4148
+ 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";
4149
+ const meta = document.createElement("dl");
4150
+ meta.className = "ofv-fallback-meta ofv-encrypted-meta";
4151
+ appendEncryptedMeta(meta, "\u6587\u4EF6", file.name || "\u672A\u547D\u540D\u6587\u4EF6");
4152
+ appendEncryptedMeta(meta, "\u683C\u5F0F", file.extension ? `.${file.extension}` : file.mimeType || "\u672A\u77E5");
4153
+ const download = document.createElement("a");
4154
+ download.href = url;
4155
+ download.download = file.name;
4156
+ download.textContent = copy.action || "\u4E0B\u8F7D\u6587\u4EF6";
4157
+ fallback.append(title, message, meta, download);
4158
+ return fallback;
4159
+ }
4160
+ function isEncryptedError(error) {
4161
+ const message = error instanceof Error ? error.message : String(error || "");
4162
+ const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
4163
+ return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(`${name} ${message}`);
4164
+ }
4165
+ function appendEncryptedMeta(parent, label, value) {
4166
+ const key = document.createElement("dt");
4167
+ key.textContent = label;
4168
+ const content = document.createElement("dd");
4169
+ content.textContent = value;
4170
+ parent.append(key, content);
4171
+ }
4172
+
4087
4173
  // src/plugins/pdf.ts
4088
4174
  function multiplyMatrices(m1, m2) {
4089
4175
  return [
@@ -4125,7 +4211,11 @@ function pdfPlugin(options = {}) {
4125
4211
  const doc = await documentTask.promise.catch((error) => {
4126
4212
  viewer.remove();
4127
4213
  ctx.viewport.classList.add("ofv-center");
4128
- const fallback = createPdfFallback(ctx.file.name, url, normalizePdfError(error));
4214
+ const fallback = isEncryptedError(error) ? createEncryptedFallback(ctx.file, url, {
4215
+ title: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4216
+ message: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
4217
+ action: "\u4E0B\u8F7D PDF"
4218
+ }) : createPdfFallback(ctx.file.name, url, normalizePdfError(error));
4129
4219
  ctx.viewport.append(fallback);
4130
4220
  return void 0;
4131
4221
  });
@@ -4415,9 +4505,6 @@ function normalizePdfError(error) {
4415
4505
  const message = error instanceof Error ? error.message : String(error || "");
4416
4506
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
4417
4507
  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
4508
  if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
4422
4509
  return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
4423
4510
  }
@@ -5345,7 +5432,13 @@ async function renderSheet(panel, arrayBuffer, extension) {
5345
5432
  const xlsx = await import("xlsx");
5346
5433
  let workbook;
5347
5434
  try {
5348
- workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), { type: "string", FS: extension === "tsv" ? " " : "," }) : xlsx.read(arrayBuffer, { type: "array" });
5435
+ workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), {
5436
+ type: "string",
5437
+ FS: extension === "tsv" ? " " : ",",
5438
+ cellDates: true,
5439
+ cellNF: true,
5440
+ cellStyles: true
5441
+ }) : xlsx.read(arrayBuffer, { type: "array", cellDates: true, cellNF: true, cellStyles: true });
5349
5442
  } catch (error) {
5350
5443
  if (isLegacyOfficeBinary(extension)) {
5351
5444
  renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
@@ -5373,7 +5466,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5373
5466
  const heading = document.createElement("h3");
5374
5467
  heading.textContent = sheetName;
5375
5468
  const sheet = workbook.Sheets[sheetName];
5376
- const range = xlsx.utils.decode_range(sheet["!ref"] || "A1:A1");
5469
+ const range = trimWorkbookSheetRange(sheet, xlsx.utils.decode_range(sheet["!ref"] || "A1:A1"), xlsx.utils.decode_cell);
5377
5470
  const rowCount = range.e.r - range.s.r + 1;
5378
5471
  const columnCount = range.e.c - range.s.c + 1;
5379
5472
  const formulaRows = collectFormulaRows(sheet, range, xlsx.utils.encode_cell);
@@ -5383,6 +5476,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5383
5476
  const tableWrapper = document.createElement("div");
5384
5477
  tableWrapper.className = "ofv-table-scroll";
5385
5478
  const viewport = createSheetViewport(rowCount, columnCount);
5479
+ const columnSizing = { widths: /* @__PURE__ */ new Map() };
5386
5480
  const windowControls = createSheetWindowControls(viewport, () => renderTableWindow());
5387
5481
  const renderTableWindow = () => {
5388
5482
  tableWrapper.replaceChildren(
@@ -5392,7 +5486,9 @@ async function renderSheet(panel, arrayBuffer, extension) {
5392
5486
  sheetIndex,
5393
5487
  viewport,
5394
5488
  xlsx.utils.encode_cell,
5395
- xlsx.utils.format_cell
5489
+ xlsx.utils.format_cell,
5490
+ columnSizing,
5491
+ renderTableWindow
5396
5492
  )
5397
5493
  );
5398
5494
  windowControls?.update();
@@ -5447,6 +5543,10 @@ async function renderSheet(panel, arrayBuffer, extension) {
5447
5543
  }
5448
5544
  }
5449
5545
  function renderSheetFallback(panel, extension, detail) {
5546
+ if (isEncryptedText(detail)) {
5547
+ renderEncryptedOfficeByFileInfo(panel, `.${extension || "sheet"}`, "Office \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8");
5548
+ return;
5549
+ }
5450
5550
  const section = createSection("\u8868\u683C\u89E3\u6790\u5931\u8D25");
5451
5551
  const title = document.createElement("p");
5452
5552
  title.textContent = `.${extension || "sheet"} \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790\u4E3A\u53EF\u9884\u89C8\u8868\u683C\u3002`;
@@ -5457,6 +5557,17 @@ function renderSheetFallback(panel, extension, detail) {
5457
5557
  section.append(title, meta, support);
5458
5558
  panel.append(section);
5459
5559
  }
5560
+ function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
5561
+ const section = createSection(title);
5562
+ section.classList.add("ofv-encrypted");
5563
+ const message = document.createElement("p");
5564
+ 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`;
5565
+ section.append(message);
5566
+ panel.append(section);
5567
+ }
5568
+ function isEncryptedText(value) {
5569
+ return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(value);
5570
+ }
5460
5571
  function renderFlatOds(panel, xml) {
5461
5572
  const sheets = parseFlatOds(xml);
5462
5573
  renderParsedSheets(panel, sheets, "FODS \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002");
@@ -5487,9 +5598,10 @@ function renderParsedSheets(panel, sheets, emptyMessage) {
5487
5598
  const tableWrapper = document.createElement("div");
5488
5599
  tableWrapper.className = "ofv-table-scroll";
5489
5600
  const viewport = createSheetViewport(rowCount, columnCount);
5601
+ const columnSizing = { widths: /* @__PURE__ */ new Map() };
5490
5602
  const windowControls = createSheetWindowControls(viewport, () => renderTableWindow());
5491
5603
  const renderTableWindow = () => {
5492
- tableWrapper.replaceChildren(createParsedSheetTable(sheet, sheetIndex, viewport));
5604
+ tableWrapper.replaceChildren(createParsedSheetTable(sheet, sheetIndex, viewport, columnSizing, renderTableWindow));
5493
5605
  windowControls?.update();
5494
5606
  };
5495
5607
  content.append(heading, summary);
@@ -5716,6 +5828,45 @@ function chartText(element) {
5716
5828
  function chartStringValues(element) {
5717
5829
  return Array.from(element.querySelectorAll("*")).filter((item) => item.localName === "v" || item.localName === "t").map((item) => item.textContent?.trim() || "").filter(Boolean);
5718
5830
  }
5831
+ function trimWorkbookSheetRange(sheet, range, decodeCell) {
5832
+ let minRow = Number.POSITIVE_INFINITY;
5833
+ let minColumn = Number.POSITIVE_INFINITY;
5834
+ let maxRow = Number.NEGATIVE_INFINITY;
5835
+ let maxColumn = Number.NEGATIVE_INFINITY;
5836
+ const include = (row, column) => {
5837
+ minRow = Math.min(minRow, row);
5838
+ minColumn = Math.min(minColumn, column);
5839
+ maxRow = Math.max(maxRow, row);
5840
+ maxColumn = Math.max(maxColumn, column);
5841
+ };
5842
+ for (const [address, cell] of Object.entries(sheet)) {
5843
+ if (address.startsWith("!")) {
5844
+ continue;
5845
+ }
5846
+ if (!cell || cell.v == null && !cell.f && !cell.w && !cell.h) {
5847
+ continue;
5848
+ }
5849
+ const decoded = decodeCell(address);
5850
+ include(decoded.r, decoded.c);
5851
+ }
5852
+ for (const merge of sheet["!merges"] || []) {
5853
+ include(merge.s.r, merge.s.c);
5854
+ include(merge.e.r, merge.e.c);
5855
+ }
5856
+ if (!Number.isFinite(minRow) || !Number.isFinite(minColumn) || !Number.isFinite(maxRow) || !Number.isFinite(maxColumn)) {
5857
+ return range;
5858
+ }
5859
+ return {
5860
+ s: {
5861
+ r: Math.max(range.s.r, minRow),
5862
+ c: Math.max(range.s.c, minColumn)
5863
+ },
5864
+ e: {
5865
+ r: Math.min(range.e.r, maxRow),
5866
+ c: Math.min(range.e.c, maxColumn)
5867
+ }
5868
+ };
5869
+ }
5719
5870
  function createSheetViewport(rowCount, columnCount) {
5720
5871
  return {
5721
5872
  rowStart: 0,
@@ -5778,42 +5929,245 @@ function createWindowButton(label, onClick) {
5778
5929
  function maxStart(total, size) {
5779
5930
  return Math.max(0, total - size);
5780
5931
  }
5781
- function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell) {
5932
+ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender) {
5782
5933
  const table = document.createElement("table");
5783
5934
  table.id = `ofv-sheet-${sheetIndex + 1}`;
5935
+ table.className = "ofv-workbook-table";
5784
5936
  const rowEnd = Math.min(range.s.r + viewport.rowStart + SHEET_WINDOW_ROWS - 1, range.e.r);
5785
5937
  const columnEnd = Math.min(range.s.c + viewport.columnStart + SHEET_WINDOW_COLUMNS - 1, range.e.c);
5786
- for (let rowIndex = range.s.r + viewport.rowStart; rowIndex <= rowEnd; rowIndex += 1) {
5938
+ const columnStart = range.s.c + viewport.columnStart;
5939
+ const rowStart = range.s.r + viewport.rowStart;
5940
+ const mergePlan = createSheetMergePlan(sheet["!merges"] || [], rowStart, rowEnd, columnStart, columnEnd);
5941
+ const colGroup = document.createElement("colgroup");
5942
+ let tableWidth = 0;
5943
+ for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex += 1) {
5944
+ const col = document.createElement("col");
5945
+ const width = columnSizing.widths.get(columnIndex) ?? getSheetColumnWidth(sheet["!cols"]?.[columnIndex]);
5946
+ col.dataset.columnIndex = String(columnIndex);
5947
+ col.style.width = `${width}px`;
5948
+ tableWidth += width;
5949
+ colGroup.append(col);
5950
+ }
5951
+ table.style.width = `${tableWidth}px`;
5952
+ table.append(colGroup);
5953
+ for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex += 1) {
5787
5954
  const row = document.createElement("tr");
5788
- for (let columnIndex = range.s.c + viewport.columnStart; columnIndex <= columnEnd; columnIndex += 1) {
5789
- const cell = document.createElement(rowIndex === range.s.r ? "th" : "td");
5955
+ const rowHeight = getSheetRowHeight(sheet["!rows"]?.[rowIndex]);
5956
+ if (rowHeight) {
5957
+ row.style.height = `${rowHeight}px`;
5958
+ }
5959
+ for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex += 1) {
5790
5960
  const address = encodeCell({ r: rowIndex, c: columnIndex });
5791
- const sourceCell = sheet[address];
5961
+ const coordinateKey = `${rowIndex}:${columnIndex}`;
5962
+ if (mergePlan.covered.has(coordinateKey)) {
5963
+ continue;
5964
+ }
5965
+ const merge = mergePlan.anchors.get(coordinateKey);
5966
+ const sourceAddress = merge ? encodeCell({ r: merge.sourceRow, c: merge.sourceColumn }) : address;
5967
+ const sourceCell = sheet[sourceAddress];
5968
+ const cell = document.createElement(rowIndex === range.s.r ? "th" : "td");
5792
5969
  cell.dataset.cell = address;
5970
+ if (sourceAddress !== address) {
5971
+ cell.dataset.sourceCell = sourceAddress;
5972
+ }
5973
+ if (merge) {
5974
+ cell.classList.add("ofv-cell-merged");
5975
+ if (merge.rowspan > 1) {
5976
+ cell.rowSpan = merge.rowspan;
5977
+ }
5978
+ if (merge.colspan > 1) {
5979
+ cell.colSpan = merge.colspan;
5980
+ }
5981
+ }
5793
5982
  const text = sourceCell ? formatCell(sourceCell) : "";
5794
5983
  cell.textContent = text;
5795
5984
  if (text) {
5796
5985
  cell.title = text;
5797
5986
  }
5987
+ applyWorkbookCellStyle(cell, sourceCell);
5798
5988
  if (sourceCell?.f) {
5799
5989
  cell.classList.add("ofv-cell-formula");
5800
5990
  cell.title = `=${sourceCell.f}`;
5801
5991
  }
5992
+ if (text.includes("\n")) {
5993
+ cell.classList.add("ofv-cell-multiline");
5994
+ }
5995
+ appendColumnResizeHandle(cell, columnIndex, columnSizing);
5802
5996
  row.append(cell);
5803
5997
  }
5804
5998
  table.append(row);
5805
5999
  }
5806
6000
  return table;
5807
6001
  }
5808
- function createParsedSheetTable(sheet, sheetIndex, viewport) {
6002
+ function appendColumnResizeHandle(cell, columnIndex, columnSizing) {
6003
+ const handle = document.createElement("span");
6004
+ handle.className = "ofv-column-resize-handle";
6005
+ handle.setAttribute("aria-hidden", "true");
6006
+ handle.addEventListener("pointerdown", (event) => {
6007
+ event.preventDefault();
6008
+ event.stopPropagation();
6009
+ const startX = event.clientX;
6010
+ const startWidth = columnSizing.widths.get(columnIndex) ?? cell.getBoundingClientRect().width;
6011
+ handle.setPointerCapture(event.pointerId);
6012
+ const onMove = (moveEvent) => {
6013
+ const nextWidth = Math.max(48, Math.min(720, Math.round(startWidth + moveEvent.clientX - startX)));
6014
+ columnSizing.widths.set(columnIndex, nextWidth);
6015
+ updateRenderedColumnWidth(cell, columnIndex, nextWidth);
6016
+ };
6017
+ const onEnd = () => {
6018
+ handle.removeEventListener("pointermove", onMove);
6019
+ handle.removeEventListener("pointerup", onEnd);
6020
+ handle.removeEventListener("pointercancel", onEnd);
6021
+ };
6022
+ handle.addEventListener("pointermove", onMove);
6023
+ handle.addEventListener("pointerup", onEnd);
6024
+ handle.addEventListener("pointercancel", onEnd);
6025
+ });
6026
+ cell.append(handle);
6027
+ }
6028
+ function updateRenderedColumnWidth(cell, columnIndex, width) {
6029
+ const table = cell.closest("table");
6030
+ if (!table) {
6031
+ return;
6032
+ }
6033
+ const column = Array.from(table.querySelectorAll("col")).find(
6034
+ (col) => col.dataset.columnIndex === String(columnIndex)
6035
+ );
6036
+ if (column) {
6037
+ column.style.width = `${width}px`;
6038
+ }
6039
+ const tableWidth = Array.from(table.querySelectorAll("col")).reduce((sum, col) => {
6040
+ const parsed = Number.parseFloat(col.style.width);
6041
+ return sum + (Number.isFinite(parsed) ? parsed : 0);
6042
+ }, 0);
6043
+ if (tableWidth > 0) {
6044
+ table.style.width = `${Math.round(tableWidth)}px`;
6045
+ }
6046
+ }
6047
+ function createSheetMergePlan(merges, rowStart, rowEnd, columnStart, columnEnd) {
6048
+ const anchors = /* @__PURE__ */ new Map();
6049
+ const covered = /* @__PURE__ */ new Set();
6050
+ const encode = (row, column) => `${row}:${column}`;
6051
+ for (const merge of merges) {
6052
+ if (merge.e.r < rowStart || merge.s.r > rowEnd || merge.e.c < columnStart || merge.s.c > columnEnd) {
6053
+ continue;
6054
+ }
6055
+ const visibleStartRow = Math.max(merge.s.r, rowStart);
6056
+ const visibleEndRow = Math.min(merge.e.r, rowEnd);
6057
+ const visibleStartColumn = Math.max(merge.s.c, columnStart);
6058
+ const visibleEndColumn = Math.min(merge.e.c, columnEnd);
6059
+ const anchor = encode(visibleStartRow, visibleStartColumn);
6060
+ anchors.set(anchor, {
6061
+ rowspan: visibleEndRow - visibleStartRow + 1,
6062
+ colspan: visibleEndColumn - visibleStartColumn + 1,
6063
+ sourceRow: merge.s.r,
6064
+ sourceColumn: merge.s.c
6065
+ });
6066
+ for (let rowIndex = visibleStartRow; rowIndex <= visibleEndRow; rowIndex += 1) {
6067
+ for (let columnIndex = visibleStartColumn; columnIndex <= visibleEndColumn; columnIndex += 1) {
6068
+ const address = encode(rowIndex, columnIndex);
6069
+ if (address !== anchor) {
6070
+ covered.add(address);
6071
+ }
6072
+ }
6073
+ }
6074
+ }
6075
+ return { anchors, covered };
6076
+ }
6077
+ function getSheetColumnWidth(column) {
6078
+ if (column?.hidden) {
6079
+ return 0;
6080
+ }
6081
+ const width = column?.wpx || (column?.wch ? column.wch * 7 + 5 : void 0) || (column?.width ? column.width * 7 : void 0) || 96;
6082
+ return Math.max(28, Math.min(360, Math.round(width)));
6083
+ }
6084
+ function getSheetRowHeight(row) {
6085
+ if (row?.hidden) {
6086
+ return 0;
6087
+ }
6088
+ const height = row?.hpx || (row?.hpt ? row.hpt * 1.333 : void 0);
6089
+ return height ? Math.max(18, Math.min(260, Math.round(height))) : void 0;
6090
+ }
6091
+ function applyWorkbookCellStyle(cell, sourceCell) {
6092
+ const style = sourceCell?.s;
6093
+ if (!style) {
6094
+ return;
6095
+ }
6096
+ const fill = readWorkbookColor(style.fgColor || style.fill?.fgColor);
6097
+ if (fill && style.patternType !== "none") {
6098
+ cell.style.backgroundColor = fill;
6099
+ }
6100
+ const font = style.font;
6101
+ if (font) {
6102
+ if (font.bold) {
6103
+ cell.style.fontWeight = "700";
6104
+ }
6105
+ if (font.italic) {
6106
+ cell.style.fontStyle = "italic";
6107
+ }
6108
+ if (font.sz) {
6109
+ cell.style.fontSize = `${Math.max(9, Math.min(24, Number(font.sz)))}pt`;
6110
+ }
6111
+ const fontColor = readWorkbookColor(font.color);
6112
+ if (fontColor) {
6113
+ cell.style.color = fontColor;
6114
+ }
6115
+ }
6116
+ const alignment = style.alignment;
6117
+ if (alignment) {
6118
+ const horizontal = normalizeSheetHorizontalAlign(alignment.horizontal);
6119
+ if (horizontal) {
6120
+ cell.style.textAlign = horizontal;
6121
+ }
6122
+ const vertical = normalizeSheetVerticalAlign(alignment.vertical);
6123
+ if (vertical) {
6124
+ cell.style.verticalAlign = vertical;
6125
+ }
6126
+ if (alignment.wrapText) {
6127
+ cell.classList.add("ofv-cell-multiline");
6128
+ }
6129
+ }
6130
+ }
6131
+ function readWorkbookColor(color) {
6132
+ if (!color?.rgb) {
6133
+ return void 0;
6134
+ }
6135
+ const rgb = color.rgb.length === 8 ? color.rgb.slice(2) : color.rgb;
6136
+ return /^[\da-f]{6}$/i.test(rgb) ? `#${rgb}` : void 0;
6137
+ }
6138
+ function normalizeSheetHorizontalAlign(value) {
6139
+ if (value === "center" || value === "right" || value === "left" || value === "justify") {
6140
+ return value;
6141
+ }
6142
+ return void 0;
6143
+ }
6144
+ function normalizeSheetVerticalAlign(value) {
6145
+ if (value === "top" || value === "middle" || value === "bottom") {
6146
+ return value;
6147
+ }
6148
+ return void 0;
6149
+ }
6150
+ function createParsedSheetTable(sheet, sheetIndex, viewport, columnSizing, rerender) {
5809
6151
  const table = document.createElement("table");
5810
6152
  table.id = `ofv-sheet-${sheetIndex + 1}`;
5811
6153
  const formulaMap = new Map(sheet.formulas.map((item) => [item.address, item.formula]));
5812
6154
  const rowEnd = Math.min(viewport.rowStart + SHEET_WINDOW_ROWS, sheet.rows.length);
6155
+ const columnEnd = Math.min(viewport.columnStart + SHEET_WINDOW_COLUMNS, viewport.columnCount);
6156
+ const colGroup = document.createElement("colgroup");
6157
+ let tableWidth = 0;
6158
+ for (let columnIndex = viewport.columnStart; columnIndex < columnEnd; columnIndex += 1) {
6159
+ const width = columnSizing.widths.get(columnIndex) ?? 112;
6160
+ const col = document.createElement("col");
6161
+ col.dataset.columnIndex = String(columnIndex);
6162
+ col.style.width = `${width}px`;
6163
+ tableWidth += width;
6164
+ colGroup.append(col);
6165
+ }
6166
+ table.style.width = `${tableWidth}px`;
6167
+ table.append(colGroup);
5813
6168
  for (let rowIndex = viewport.rowStart; rowIndex < rowEnd; rowIndex += 1) {
5814
6169
  const sourceRow = sheet.rows[rowIndex] || [];
5815
6170
  const row = document.createElement("tr");
5816
- const columnEnd = Math.min(viewport.columnStart + SHEET_WINDOW_COLUMNS, viewport.columnCount);
5817
6171
  for (let columnIndex = viewport.columnStart; columnIndex < columnEnd; columnIndex += 1) {
5818
6172
  const value = sourceRow[columnIndex] || "";
5819
6173
  const cell = document.createElement(rowIndex === 0 ? "th" : "td");
@@ -5828,6 +6182,10 @@ function createParsedSheetTable(sheet, sheetIndex, viewport) {
5828
6182
  cell.classList.add("ofv-cell-formula");
5829
6183
  cell.title = formula;
5830
6184
  }
6185
+ if (value.includes("\n")) {
6186
+ cell.classList.add("ofv-cell-multiline");
6187
+ }
6188
+ appendColumnResizeHandle(cell, columnIndex, columnSizing);
5831
6189
  row.append(cell);
5832
6190
  }
5833
6191
  table.append(row);
@@ -6765,7 +7123,7 @@ function ofdPlugin() {
6765
7123
  try {
6766
7124
  zip = await import_jszip4.default.loadAsync(await readArrayBuffer(ctx.file));
6767
7125
  } catch (error) {
6768
- panel.append(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
7126
+ panel.append(createOfdFailure(ctx.file, url, error));
6769
7127
  return {
6770
7128
  destroy() {
6771
7129
  panel.remove();
@@ -6782,7 +7140,7 @@ function ofdPlugin() {
6782
7140
  textFragments.push(...matches);
6783
7141
  }
6784
7142
  } catch (error) {
6785
- panel.append(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
7143
+ panel.append(createOfdFailure(ctx.file, url, error));
6786
7144
  return {
6787
7145
  destroy() {
6788
7146
  panel.remove();
@@ -7407,6 +7765,16 @@ function finiteNumber2(value, fallback) {
7407
7765
  function formatOfdCssNumber(value) {
7408
7766
  return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7409
7767
  }
7768
+ function createOfdFailure(file, url, error) {
7769
+ if (isEncryptedError(error)) {
7770
+ return createEncryptedFallback(file, url, {
7771
+ title: "OFD \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
7772
+ 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",
7773
+ action: "\u4E0B\u8F7D OFD"
7774
+ });
7775
+ }
7776
+ return createOfdFallback(file.name, url, normalizeOfdError(error));
7777
+ }
7410
7778
  function createOfdFallback(fileName, url, detail) {
7411
7779
  const fallback = document.createElement("div");
7412
7780
  fallback.className = "ofv-fallback";
@@ -7475,7 +7843,9 @@ function archivePlugin() {
7475
7843
  try {
7476
7844
  if (ext === "zip") {
7477
7845
  try {
7478
- const zip = await import_jszip5.default.loadAsync(await readArrayBuffer(ctx.file));
7846
+ const zip = await import_jszip5.default.loadAsync(await readArrayBuffer(ctx.file), {
7847
+ decodeFileName: decodeZipFileName
7848
+ });
7479
7849
  archiveEntries = Object.values(zip.files).map((entry) => ({
7480
7850
  name: entry.name,
7481
7851
  unsafeName: entry.unsafeOriginalName,
@@ -7484,7 +7854,7 @@ function archivePlugin() {
7484
7854
  read: () => entry.async("arraybuffer")
7485
7855
  }));
7486
7856
  } catch (zipErr) {
7487
- if (zipErr.message && (zipErr.message.includes("encrypted") || zipErr.message.includes("password") || zipErr.message.includes("protected"))) {
7857
+ if (isEncryptedError(zipErr)) {
7488
7858
  isEncrypted = true;
7489
7859
  } else {
7490
7860
  throw zipErr;
@@ -7517,17 +7887,11 @@ function archivePlugin() {
7517
7887
  parseError = `\u538B\u7F29\u5305\u89E3\u6790\u5931\u8D25\uFF1A${err.message || err}`;
7518
7888
  }
7519
7889
  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);
7890
+ const fallback = createEncryptedFallback(ctx.file, url, {
7891
+ title: "\u538B\u7F29\u5305\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
7892
+ 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",
7893
+ action: "\u4E0B\u8F7D\u538B\u7F29\u5305"
7894
+ });
7531
7895
  panel.append(fallback);
7532
7896
  ctx.viewport.classList.add("ofv-center");
7533
7897
  return {
@@ -7776,6 +8140,28 @@ async function findSubPreviewPlugin(plugins, file) {
7776
8140
  }
7777
8141
  return fallbackPlugin();
7778
8142
  }
8143
+ function decodeZipFileName(bytes) {
8144
+ const data = Array.isArray(bytes) ? Uint8Array.from(bytes.map((value) => value.charCodeAt(0) & 255)) : bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);
8145
+ const utf8 = decodeZipNameWith(data, "utf-8", true);
8146
+ if (utf8 && !looksMojibake(utf8)) {
8147
+ return utf8;
8148
+ }
8149
+ const gb18030 = decodeZipNameWith(data, "gb18030", false) || decodeZipNameWith(data, "gbk", false);
8150
+ if (gb18030 && !looksMojibake(gb18030)) {
8151
+ return gb18030;
8152
+ }
8153
+ return utf8 || new TextDecoder("latin1").decode(data);
8154
+ }
8155
+ function decodeZipNameWith(bytes, encoding, fatal) {
8156
+ try {
8157
+ return new TextDecoder(encoding, { fatal }).decode(bytes);
8158
+ } catch {
8159
+ return void 0;
8160
+ }
8161
+ }
8162
+ function looksMojibake(value) {
8163
+ return /[\uFFFDÃÂÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß]/.test(value);
8164
+ }
7779
8165
  function createInlineError(titleText, detailText) {
7780
8166
  const fallback = document.createElement("div");
7781
8167
  fallback.className = "ofv-fallback";
@@ -8197,13 +8583,14 @@ function emailPlugin() {
8197
8583
  } else {
8198
8584
  const PostalMime = (await import("postal-mime")).default;
8199
8585
  const parser = new PostalMime();
8200
- let rawText = await readTextFile(ctx.file);
8586
+ let rawSource = await readArrayBuffer(ctx.file);
8201
8587
  if (ext === "mbox") {
8588
+ let rawText = await readTextFile(ctx.file);
8202
8589
  const messages = splitMboxMessages(rawText);
8203
8590
  mboxSummary = messages.map(summarizeMboxMessage);
8204
- rawText = messages[0] || rawText;
8591
+ rawSource = messages[0] || rawText;
8205
8592
  }
8206
- const parsed = await parser.parse(rawText);
8593
+ const parsed = await parser.parse(rawSource);
8207
8594
  const from = parsed.from ? `${parsed.from.name || ""} <${parsed.from.address || ""}>`.trim() : "";
8208
8595
  const to = Array.isArray(parsed.to) ? parsed.to.map((t) => `${t.name || ""} <${t.address || ""}>`.trim()).join("; ") : "";
8209
8596
  const cc = Array.isArray(parsed.cc) ? parsed.cc.map((c) => `${c.name || ""} <${c.address || ""}>`.trim()).join("; ") : "";
@@ -8273,13 +8660,17 @@ function emailPlugin() {
8273
8660
  const sanitizedHtml = sanitizeEmailHtml(html);
8274
8661
  const iframe = document.createElement("iframe");
8275
8662
  iframe.className = "ofv-email-body-iframe";
8276
- iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
8663
+ iframe.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
8277
8664
  iframe.style.cssText = "width: 100%; border: none; background: #fff; min-height: 200px;";
8278
- bodySection.append(iframe);
8279
- iframe.onload = () => {
8665
+ let renderedHtmlBody = false;
8666
+ const renderHtmlBody = () => {
8667
+ if (renderedHtmlBody) {
8668
+ return;
8669
+ }
8280
8670
  try {
8281
8671
  const idoc = iframe.contentDocument || iframe.contentWindow?.document;
8282
8672
  if (idoc) {
8673
+ renderedHtmlBody = true;
8283
8674
  idoc.open();
8284
8675
  idoc.write(`
8285
8676
  <!doctype html>
@@ -8330,6 +8721,9 @@ function emailPlugin() {
8330
8721
  console.error("Failed to write html body to email iframe:", err);
8331
8722
  }
8332
8723
  };
8724
+ iframe.addEventListener("load", renderHtmlBody, { once: true });
8725
+ bodySection.append(iframe);
8726
+ renderHtmlBody();
8333
8727
  } else {
8334
8728
  const pre = document.createElement("pre");
8335
8729
  pre.className = "ofv-text-block";
@@ -9851,112 +10245,726 @@ function decodeDrawioDiagram(value) {
9851
10245
  }
9852
10246
 
9853
10247
  // src/plugins/cad.ts
9854
- var cadExtensions = /* @__PURE__ */ new Set([
9855
- "dxf",
9856
- "dwg",
9857
- "dwf",
9858
- "step",
9859
- "stp",
9860
- "iges",
9861
- "igs",
9862
- "ifc",
9863
- "sat",
9864
- "sab",
9865
- "x_t",
9866
- "x_b",
9867
- "3dm",
9868
- "skp",
9869
- "sldprt",
9870
- "sldasm"
9871
- ]);
9872
- var cadMimeTypes = /* @__PURE__ */ new Set([
9873
- "application/acad",
9874
- "application/dxf",
9875
- "application/x-dxf",
9876
- "image/vnd.dxf",
9877
- "model/vnd.dwf",
9878
- "model/step",
9879
- "application/step",
9880
- "application/iges",
9881
- "application/x-step",
9882
- "application/sat",
9883
- "application/sab",
9884
- "application/x-parasolid",
9885
- "model/vnd.3dm",
9886
- "application/vnd.sketchup.skp",
9887
- "application/sldworks"
9888
- ]);
9889
- var cadMimeFormatMap = {
9890
- "application/acad": "dwg",
9891
- "application/dxf": "dxf",
9892
- "application/x-dxf": "dxf",
9893
- "image/vnd.dxf": "dxf",
9894
- "model/vnd.dwf": "dwf",
9895
- "model/step": "step",
9896
- "application/step": "step",
9897
- "application/iges": "iges",
9898
- "application/x-step": "ifc",
9899
- "application/sat": "sat",
9900
- "application/sab": "sab",
9901
- "application/x-parasolid": "x_t",
9902
- "model/vnd.3dm": "3dm",
9903
- "application/vnd.sketchup.skp": "skp",
9904
- "application/sldworks": "sldprt"
9905
- };
9906
- function cadPlugin() {
9907
- return {
9908
- name: "cad",
9909
- match(file) {
9910
- return cadExtensions.has(file.extension) || cadMimeTypes.has(file.mimeType);
9911
- },
9912
- async render(ctx) {
9913
- const panel = createPanel("ofv-cad");
9914
- ctx.viewport.append(panel);
9915
- const extension = resolveFormat(ctx.file, cadMimeFormatMap);
9916
- if (extension === "step" || extension === "stp") {
9917
- renderStep(panel, await readTextFile(ctx.file), extension);
9918
- return { destroy: () => panel.remove() };
9919
- }
9920
- if (extension === "iges" || extension === "igs") {
9921
- renderIges(panel, await readTextFile(ctx.file), extension);
9922
- return { destroy: () => panel.remove() };
9923
- }
9924
- if (extension === "ifc") {
9925
- renderIfc(panel, await readTextFile(ctx.file));
9926
- return { destroy: () => panel.remove() };
9927
- }
9928
- if (extension === "dwg" || extension === "dwf") {
9929
- renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
9930
- return { destroy: () => panel.remove() };
10248
+ var import_pako3 = __toESM(require("pako"), 1);
10249
+
10250
+ // src/plugins/cad-dwg.ts
10251
+ var libreDwgPromise;
10252
+ var defaultLibreDwgWasmBaseUrl = "/vendor/libredwg-web";
10253
+ var minReadableDrawingHeight = 420;
10254
+ var svgNumberPattern = /-?\d*\.?\d+(?:e[-+]?\d+)?/gi;
10255
+ async function renderLibreDwgPreview(ctx, options = {}) {
10256
+ if (ctx.extension !== "dwg" || options.enabled === false) {
10257
+ return void 0;
10258
+ }
10259
+ const shell = document.createElement("div");
10260
+ shell.className = "ofv-dwg-preview";
10261
+ const status = document.createElement("div");
10262
+ status.className = "ofv-dwg-preview-status";
10263
+ status.textContent = "Loading DWG rendering engine...";
10264
+ shell.append(status);
10265
+ ctx.panel.append(shell);
10266
+ try {
10267
+ const { LibreDwg, Dwg_File_Type } = await loadLibreDwg();
10268
+ const libredwg = await LibreDwg.create(options.wasmBaseUrl || defaultLibreDwgWasmBaseUrl);
10269
+ const data = libredwg.dwg_read_data(ctx.arrayBuffer, Dwg_File_Type.DWG);
10270
+ if (!data) {
10271
+ throw new Error("DWG parser did not return drawing data.");
10272
+ }
10273
+ let svg = "";
10274
+ let stats;
10275
+ let thumbnailUrl;
10276
+ try {
10277
+ thumbnailUrl = createDwgThumbnailUrl(readDwgThumbnail(libredwg, data));
10278
+ try {
10279
+ const result = libredwg.convertEx(data);
10280
+ const database = result.database;
10281
+ stats = createDwgPreviewStats(database, result.stats.unknownEntityCount, Boolean(thumbnailUrl));
10282
+ svg = libredwg.dwg_to_svg(database);
10283
+ } catch (error) {
10284
+ if (thumbnailUrl) {
10285
+ const fallbackThumbnailUrl = thumbnailUrl;
10286
+ status.replaceChildren(createDwgThumbnailFallbackStatus(ctx.fileName, error));
10287
+ shell.append(createDwgThumbnailPreview(fallbackThumbnailUrl, ctx.fileName));
10288
+ return {
10289
+ destroy() {
10290
+ URL.revokeObjectURL(fallbackThumbnailUrl);
10291
+ shell.remove();
10292
+ }
10293
+ };
10294
+ }
10295
+ throw error;
9931
10296
  }
9932
- if (extension !== "dxf") {
9933
- const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
9934
- 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`);
9935
- panel.append(section);
9936
- return { destroy: () => panel.remove() };
10297
+ } finally {
10298
+ libredwg.dwg_free(data);
10299
+ }
10300
+ if (!svg || !/<svg[\s>]/i.test(svg)) {
10301
+ if (thumbnailUrl) {
10302
+ const fallbackThumbnailUrl = thumbnailUrl;
10303
+ status.replaceChildren(createDwgThumbnailFallbackStatus(ctx.fileName, "DWG parser finished but did not produce SVG output."));
10304
+ shell.append(createDwgThumbnailPreview(fallbackThumbnailUrl, ctx.fileName));
10305
+ return {
10306
+ destroy() {
10307
+ URL.revokeObjectURL(fallbackThumbnailUrl);
10308
+ shell.remove();
10309
+ }
10310
+ };
9937
10311
  }
9938
- const dxf = await readTextFile(ctx.file);
9939
- const viewer = renderDxf(panel, dxf, ctx);
10312
+ throw new Error("DWG parser finished but did not produce SVG output.");
10313
+ }
10314
+ const doc = new DOMParser().parseFromString(svg, "image/svg+xml");
10315
+ const svgElement = doc.documentElement;
10316
+ if (!(svgElement instanceof SVGElement) || svgElement.nodeName.toLowerCase() !== "svg" || svgElement.querySelector("parsererror")) {
10317
+ throw new Error("DWG SVG output is invalid.");
10318
+ }
10319
+ svgElement.classList.add("ofv-dwg-preview-svg");
10320
+ svgElement.setAttribute("role", "img");
10321
+ svgElement.setAttribute("aria-label", ctx.fileName);
10322
+ normalizeDwgSvg(svgElement);
10323
+ const reliability = assessDwgSvgReliability(svgElement);
10324
+ status.replaceChildren(createDwgStatusTitle(ctx.fileName, stats, reliability));
10325
+ if (!reliability.isReliable && thumbnailUrl) {
10326
+ shell.append(createDwgThumbnailPreview(thumbnailUrl, ctx.fileName));
9940
10327
  return {
9941
- canCommand(command) {
9942
- return viewer.canCommand(command);
9943
- },
9944
- command(command) {
9945
- return viewer.command(command);
9946
- },
9947
10328
  destroy() {
9948
- panel.remove();
10329
+ URL.revokeObjectURL(thumbnailUrl);
10330
+ shell.remove();
9949
10331
  }
9950
10332
  };
9951
10333
  }
9952
- };
9953
- }
9954
- function renderStep(panel, text, extension) {
9955
- const records = parseStepRecords(text);
9956
- const typeCounts = countBy(records.map((record) => record.type));
9957
- const section = createSection(`${extension.toUpperCase()} \u7ED3\u6784\u9884\u89C8`);
9958
- const note = document.createElement("p");
9959
- note.textContent = "\u5F53\u524D\u7248\u672C\u63D0\u53D6 STEP \u6587\u672C\u5B9E\u4F53\u3001\u7C7B\u578B\u7EDF\u8BA1\u548C\u5173\u952E\u51E0\u4F55\u53C2\u6570\u3002\u7CBE\u786E B-Rep/\u66F2\u9762\u6E32\u67D3\u5EFA\u8BAE\u540E\u7EED\u63A5\u5165 CAD \u5185\u6838\u6216\u670D\u52A1\u7AEF\u8F6C\u6362\u3002";
10334
+ const drawing = createDwgDrawingViewport(svgElement);
10335
+ if (thumbnailUrl) {
10336
+ shell.append(createDwgThumbnailPanel(thumbnailUrl));
10337
+ }
10338
+ shell.append(drawing.frame);
10339
+ return {
10340
+ resize() {
10341
+ drawing.update();
10342
+ },
10343
+ canCommand(command) {
10344
+ return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
10345
+ },
10346
+ command(command) {
10347
+ if (command === "zoom-in") {
10348
+ drawing.setZoom(drawing.zoom * 1.18);
10349
+ return true;
10350
+ }
10351
+ if (command === "zoom-out") {
10352
+ drawing.setZoom(drawing.zoom / 1.18);
10353
+ return true;
10354
+ }
10355
+ if (command === "zoom-reset") {
10356
+ drawing.setZoom(1);
10357
+ return true;
10358
+ }
10359
+ return false;
10360
+ },
10361
+ destroy() {
10362
+ if (thumbnailUrl) {
10363
+ URL.revokeObjectURL(thumbnailUrl);
10364
+ }
10365
+ shell.remove();
10366
+ }
10367
+ };
10368
+ } catch (error) {
10369
+ shell.remove();
10370
+ console.warn("DWG LibreDWG preview failed, falling back to metadata preview:", error);
10371
+ return void 0;
10372
+ }
10373
+ }
10374
+ function loadLibreDwg() {
10375
+ libreDwgPromise ||= import("@mlightcad/libredwg-web");
10376
+ return libreDwgPromise;
10377
+ }
10378
+ function createDwgStatusTitle(fileName, stats, reliability) {
10379
+ const wrapper = document.createElement("span");
10380
+ const title = document.createElement("strong");
10381
+ const note = document.createElement("small");
10382
+ title.textContent = reliability.isReliable ? `\u5B9E\u9A8C\u6027 DWG \u6A21\u578B\u7A7A\u95F4\u9884\u89C8 \xB7 ${fileName}` : `DWG \u5185\u7F6E\u9884\u89C8\u56FE \xB7 ${fileName}`;
10383
+ note.textContent = [
10384
+ `${stats.entityCount.toLocaleString()} \u4E2A\u5B9E\u4F53`,
10385
+ `${stats.visibleLayerCount}/${stats.layerCount} \u4E2A\u53EF\u89C1\u56FE\u5C42`,
10386
+ `${stats.layoutCount} \u4E2A\u5E03\u5C40`,
10387
+ stats.paperSpaceEntityCount ? `${stats.paperSpaceEntityCount.toLocaleString()} \u4E2A\u56FE\u7EB8\u7A7A\u95F4\u5B9E\u4F53` : "\u6A21\u578B\u7A7A\u95F4\u7EBF\u7A3F",
10388
+ stats.unknownEntityCount ? `${stats.unknownEntityCount} \u4E2A\u672A\u77E5\u5B9E\u4F53` : "\u5B9E\u4F53\u89E3\u6790\u5B8C\u6574",
10389
+ stats.hasThumbnail ? "\u5305\u542B\u5185\u7F6E\u7F29\u7565\u56FE" : "\u65E0\u5185\u7F6E\u7F29\u7565\u56FE"
10390
+ ].join(" \xB7 ");
10391
+ const warning = document.createElement("small");
10392
+ warning.textContent = reliability.isReliable ? "\u5F53\u524D\u4E3A LibreDWG WASM \u7EBF\u7A3F\u9884\u89C8\uFF0C\u590D\u6742\u5E03\u5C40/\u6253\u5370\u7A7A\u95F4/\u5B57\u4F53/\u586B\u5145\u4E0E\u4E13\u4E1A CAD \u4ECD\u53EF\u80FD\u5B58\u5728\u5DEE\u5F02\u3002" : `LibreDWG \u7EBF\u7A3F\u68C0\u6D4B\u5230\u5F02\u5E38\u56FE\u5143\uFF0C\u5DF2\u4F18\u5148\u663E\u793A\u6587\u4EF6\u5185\u7F6E\u9884\u89C8\u56FE\u3002${reliability.reason ?? ""}`;
10393
+ wrapper.append(title, note, warning);
10394
+ return wrapper;
10395
+ }
10396
+ function createDwgThumbnailFallbackStatus(fileName, error) {
10397
+ const wrapper = document.createElement("span");
10398
+ const title = document.createElement("strong");
10399
+ title.textContent = `DWG \u5185\u7F6E\u9884\u89C8\u56FE \xB7 ${fileName}`;
10400
+ const note = document.createElement("small");
10401
+ note.textContent = "LibreDWG \u5DF2\u8BFB\u53D6\u5230\u6587\u4EF6\u5185\u7F6E\u7F29\u7565\u56FE\uFF0C\u4F46\u7EBF\u7A3F\u9884\u89C8\u672A\u80FD\u751F\u6210\uFF0C\u5DF2\u81EA\u52A8\u5207\u6362\u4E3A\u7F29\u7565\u56FE\u515C\u5E95\u3002";
10402
+ const detail = document.createElement("small");
10403
+ detail.textContent = error instanceof Error ? error.message : String(error || "\u672A\u77E5\u89E3\u6790\u9519\u8BEF");
10404
+ wrapper.append(title, note, detail);
10405
+ return wrapper;
10406
+ }
10407
+ function createDwgPreviewStats(database, unknownEntityCount, hasThumbnail) {
10408
+ const layers = database.tables.LAYER.entries;
10409
+ return {
10410
+ entityCount: database.entities.length,
10411
+ layerCount: layers.length,
10412
+ layoutCount: database.objects.LAYOUT.length,
10413
+ unknownEntityCount,
10414
+ visibleLayerCount: layers.filter((layer) => !layer.off && !layer.frozen).length,
10415
+ paperSpaceEntityCount: database.entities.filter((entity) => entity.isInPaperSpace).length,
10416
+ hasThumbnail
10417
+ };
10418
+ }
10419
+ function readDwgThumbnail(libredwg, data) {
10420
+ try {
10421
+ const thumbnail = libredwg.dwg_bmp(data);
10422
+ if (!thumbnail?.data?.length) {
10423
+ return void 0;
10424
+ }
10425
+ return thumbnail;
10426
+ } catch {
10427
+ return void 0;
10428
+ }
10429
+ }
10430
+ function createDwgThumbnailUrl(thumbnail) {
10431
+ if (!thumbnail) {
10432
+ return void 0;
10433
+ }
10434
+ if (thumbnail.type === 6) {
10435
+ return URL.createObjectURL(new Blob([toArrayBuffer(thumbnail.data)], { type: "image/png" }));
10436
+ }
10437
+ if (thumbnail.type === 2) {
10438
+ return URL.createObjectURL(new Blob([toArrayBuffer(createBmpFileBytes(thumbnail.data))], { type: "image/bmp" }));
10439
+ }
10440
+ return void 0;
10441
+ }
10442
+ function toArrayBuffer(bytes) {
10443
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
10444
+ }
10445
+ function createBmpFileBytes(dibBytes) {
10446
+ const view3 = new DataView(dibBytes.buffer, dibBytes.byteOffset, dibBytes.byteLength);
10447
+ const headerSize = view3.getUint32(0, true);
10448
+ const bitCount = view3.getUint16(14, true);
10449
+ const paletteBytes = bitCount <= 8 ? 2 ** bitCount * 4 : 0;
10450
+ const pixelOffset = 14 + headerSize + paletteBytes;
10451
+ const bytes = new Uint8Array(14 + dibBytes.byteLength);
10452
+ bytes[0] = 66;
10453
+ bytes[1] = 77;
10454
+ const fileView = new DataView(bytes.buffer);
10455
+ fileView.setUint32(2, bytes.byteLength, true);
10456
+ fileView.setUint32(10, pixelOffset, true);
10457
+ bytes.set(dibBytes, 14);
10458
+ return bytes;
10459
+ }
10460
+ function createDwgThumbnailPanel(thumbnailUrl) {
10461
+ const panel = document.createElement("figure");
10462
+ panel.className = "ofv-dwg-thumbnail";
10463
+ const image = document.createElement("img");
10464
+ image.src = thumbnailUrl;
10465
+ image.alt = "DWG \u6587\u4EF6\u5185\u7F6E\u7F29\u7565\u56FE";
10466
+ const caption = document.createElement("figcaption");
10467
+ caption.textContent = "\u6587\u4EF6\u5185\u7F6E\u7F29\u7565\u56FE";
10468
+ panel.append(image, caption);
10469
+ return panel;
10470
+ }
10471
+ function createDwgThumbnailPreview(thumbnailUrl, fileName) {
10472
+ const figure = document.createElement("figure");
10473
+ figure.className = "ofv-dwg-thumbnail-preview";
10474
+ const image = document.createElement("img");
10475
+ image.src = thumbnailUrl;
10476
+ image.alt = `${fileName} \u5185\u7F6E\u9884\u89C8\u56FE`;
10477
+ const caption = document.createElement("figcaption");
10478
+ caption.textContent = "DWG \u6587\u4EF6\u5185\u7F6E\u9884\u89C8\u56FE\u3002\u82E5\u9700\u8981\u63A5\u8FD1 CAD \u5E03\u5C40/\u6253\u5370\u7A7A\u95F4\u7684\u9AD8\u4FDD\u771F\u6548\u679C\uFF0C\u8BF7\u4F7F\u7528\u540C\u540D PNG\u3001SVG\u3001PDF \u5BFC\u51FA\u56FE\uFF0C\u6216\u901A\u8FC7 binaryRenderer \u63A5\u5165\u4E13\u4E1A CAD \u6E32\u67D3/\u8F6C\u6362\u670D\u52A1\u3002";
10479
+ figure.append(image, caption);
10480
+ return figure;
10481
+ }
10482
+ function normalizeDwgSvg(svgElement) {
10483
+ svgElement.setAttribute("preserveAspectRatio", "xMidYMid meet");
10484
+ removeInheritedDwgFills(svgElement);
10485
+ focusDwgSvgOnMainDrawing(svgElement);
10486
+ const style = document.createElementNS("http://www.w3.org/2000/svg", "style");
10487
+ style.textContent = `
10488
+ .ofv-dwg-preview-svg { background: #020617; }
10489
+ .ofv-dwg-preview-svg g {
10490
+ fill: none !important;
10491
+ }
10492
+ .ofv-dwg-preview-svg line,
10493
+ .ofv-dwg-preview-svg path,
10494
+ .ofv-dwg-preview-svg polyline,
10495
+ .ofv-dwg-preview-svg polygon,
10496
+ .ofv-dwg-preview-svg circle,
10497
+ .ofv-dwg-preview-svg ellipse,
10498
+ .ofv-dwg-preview-svg rect {
10499
+ fill: none !important;
10500
+ vector-effect: non-scaling-stroke;
10501
+ stroke-width: 0.7px !important;
10502
+ stroke-linecap: round;
10503
+ stroke-linejoin: round;
10504
+ }
10505
+ .ofv-dwg-preview-svg [stroke="rgb(0,255,0)"] {
10506
+ stroke: #34d399 !important;
10507
+ stroke-opacity: 0.58 !important;
10508
+ }
10509
+ .ofv-dwg-preview-svg text {
10510
+ vector-effect: non-scaling-stroke;
10511
+ stroke-width: 0 !important;
10512
+ fill: currentColor !important;
10513
+ }
10514
+ `;
10515
+ svgElement.prepend(style);
10516
+ }
10517
+ function removeInheritedDwgFills(svgElement) {
10518
+ const shapeSelector = "line,path,polyline,polygon,circle,ellipse,rect";
10519
+ for (const group of svgElement.querySelectorAll("g[fill]")) {
10520
+ group.setAttribute("fill", "none");
10521
+ }
10522
+ for (const styledElement of svgElement.querySelectorAll("[style]")) {
10523
+ styledElement.style.fill = "none";
10524
+ }
10525
+ for (const shape of svgElement.querySelectorAll(shapeSelector)) {
10526
+ shape.setAttribute("fill", "none");
10527
+ }
10528
+ }
10529
+ function assessDwgSvgReliability(svgElement) {
10530
+ const bounds = readSvgViewBox(svgElement);
10531
+ if (!bounds) {
10532
+ return { isReliable: true };
10533
+ }
10534
+ const largePathCount = countLargeSvgPaths(svgElement, bounds);
10535
+ const totalPathCount = svgElement.querySelectorAll("path").length;
10536
+ if (largePathCount >= 24 && totalPathCount > 0 && largePathCount / totalPathCount > 0.08) {
10537
+ return {
10538
+ isReliable: false,
10539
+ reason: "\u8BE5\u6587\u4EF6\u5305\u542B\u5927\u91CF\u8D85\u51FA\u4E3B\u4F53\u89C6\u53E3\u7684\u5927\u8DEF\u5F84/\u5757\u53C2\u7167\uFF0C\u7EBF\u7A3F\u6A21\u5F0F\u4F1A\u660E\u663E\u504F\u79BB CAD \u5E03\u5C40\u3002"
10540
+ };
10541
+ }
10542
+ return { isReliable: true };
10543
+ }
10544
+ function countLargeSvgPaths(svgElement, bounds) {
10545
+ let count = 0;
10546
+ const viewportArea = bounds.width * bounds.height;
10547
+ if (!Number.isFinite(viewportArea) || viewportArea <= 0) {
10548
+ return count;
10549
+ }
10550
+ for (const path of svgElement.querySelectorAll("path")) {
10551
+ if (path.closest("defs")) {
10552
+ continue;
10553
+ }
10554
+ const pathBounds = estimatePathBounds(path);
10555
+ if (!pathBounds) {
10556
+ continue;
10557
+ }
10558
+ const pathArea = pathBounds.width * pathBounds.height;
10559
+ const crossesViewport = pathBounds.minX <= bounds.minX + bounds.width * 0.02 || pathBounds.minY <= bounds.minY + bounds.height * 0.02;
10560
+ if (pathArea > viewportArea * 0.28 || crossesViewport && pathArea > viewportArea * 0.12) {
10561
+ count += 1;
10562
+ }
10563
+ }
10564
+ return count;
10565
+ }
10566
+ function estimatePathBounds(path) {
10567
+ const numbers = readNumbers(path.getAttribute("d") ?? "");
10568
+ if (numbers.length < 4) {
10569
+ return void 0;
10570
+ }
10571
+ let minX = Number.POSITIVE_INFINITY;
10572
+ let minY = Number.POSITIVE_INFINITY;
10573
+ let maxX = Number.NEGATIVE_INFINITY;
10574
+ let maxY = Number.NEGATIVE_INFINITY;
10575
+ for (let index = 0; index + 1 < numbers.length; index += 2) {
10576
+ const x = numbers[index];
10577
+ const y = numbers[index + 1];
10578
+ if (!Number.isFinite(x) || !Number.isFinite(y) || Math.abs(x) > 1e9 || Math.abs(y) > 1e9) {
10579
+ continue;
10580
+ }
10581
+ minX = Math.min(minX, x);
10582
+ minY = Math.min(minY, y);
10583
+ maxX = Math.max(maxX, x);
10584
+ maxY = Math.max(maxY, y);
10585
+ }
10586
+ if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(maxX) || !Number.isFinite(maxY)) {
10587
+ return void 0;
10588
+ }
10589
+ return { minX, minY, width: maxX - minX, height: maxY - minY };
10590
+ }
10591
+ function focusDwgSvgOnMainDrawing(svgElement) {
10592
+ const originalBounds = readSvgViewBox(svgElement);
10593
+ const mainBounds = estimateMainDrawingBounds(svgElement);
10594
+ if (!originalBounds || !mainBounds || !shouldUseMainDrawingBounds(originalBounds, mainBounds)) {
10595
+ return;
10596
+ }
10597
+ const paddedBounds = padBounds(mainBounds, 0.05);
10598
+ svgElement.dataset.originalViewBox = formatViewBox(originalBounds);
10599
+ svgElement.dataset.focusViewBox = formatViewBox(paddedBounds);
10600
+ svgElement.setAttribute("viewBox", formatViewBox(paddedBounds));
10601
+ }
10602
+ function shouldUseMainDrawingBounds(original, candidate) {
10603
+ const originalAspectRatio = original.width / original.height;
10604
+ const candidateAspectRatio = candidate.width / candidate.height;
10605
+ const originalArea = original.width * original.height;
10606
+ const candidateArea = candidate.width * candidate.height;
10607
+ if (!Number.isFinite(originalArea) || !Number.isFinite(candidateArea) || candidateArea <= 0) {
10608
+ return false;
10609
+ }
10610
+ return originalAspectRatio > 8 || originalAspectRatio < 0.125 || candidateArea / originalArea < 0.55 || Math.abs(Math.log(originalAspectRatio / candidateAspectRatio)) > Math.log(4);
10611
+ }
10612
+ function estimateMainDrawingBounds(svgElement) {
10613
+ const points = [];
10614
+ const addPoint = (x, y) => {
10615
+ if (Number.isFinite(x) && Number.isFinite(y) && Math.abs(x) < 1e9 && Math.abs(y) < 1e9) {
10616
+ points.push([x, y]);
10617
+ }
10618
+ };
10619
+ for (const element of svgElement.querySelectorAll("line,path,polyline,polygon,circle,ellipse,rect,text,use")) {
10620
+ if (element.closest("defs")) {
10621
+ continue;
10622
+ }
10623
+ collectElementPoints(element, addPoint);
10624
+ }
10625
+ if (points.length < 16) {
10626
+ return void 0;
10627
+ }
10628
+ const xs = points.map(([x]) => x).sort((a, b) => a - b);
10629
+ const ys = points.map(([, y]) => y).sort((a, b) => a - b);
10630
+ const minX = quantile(xs, 5e-3);
10631
+ const maxX = quantile(xs, 0.995);
10632
+ const minY = quantile(ys, 5e-3);
10633
+ const maxY = quantile(ys, 0.995);
10634
+ if (!Number.isFinite(minX) || !Number.isFinite(maxX) || !Number.isFinite(minY) || !Number.isFinite(maxY)) {
10635
+ return void 0;
10636
+ }
10637
+ const width = maxX - minX;
10638
+ const height = maxY - minY;
10639
+ if (width <= 0 || height <= 0) {
10640
+ return void 0;
10641
+ }
10642
+ return { minX, minY, width, height };
10643
+ }
10644
+ function collectElementPoints(element, addPoint) {
10645
+ const tagName = element.tagName.toLowerCase();
10646
+ if (tagName === "line") {
10647
+ addPoint(readNumberAttribute(element, "x1"), readNumberAttribute(element, "y1"));
10648
+ addPoint(readNumberAttribute(element, "x2"), readNumberAttribute(element, "y2"));
10649
+ } else if (tagName === "circle") {
10650
+ const cx = readNumberAttribute(element, "cx");
10651
+ const cy = readNumberAttribute(element, "cy");
10652
+ const r = readNumberAttribute(element, "r");
10653
+ addPoint(cx - r, cy - r);
10654
+ addPoint(cx + r, cy + r);
10655
+ } else if (tagName === "ellipse") {
10656
+ const cx = readNumberAttribute(element, "cx");
10657
+ const cy = readNumberAttribute(element, "cy");
10658
+ const rx = readNumberAttribute(element, "rx");
10659
+ const ry = readNumberAttribute(element, "ry");
10660
+ addPoint(cx - rx, cy - ry);
10661
+ addPoint(cx + rx, cy + ry);
10662
+ } else if (tagName === "rect") {
10663
+ const x = readNumberAttribute(element, "x");
10664
+ const y = readNumberAttribute(element, "y");
10665
+ addPoint(x, y);
10666
+ addPoint(x + readNumberAttribute(element, "width"), y + readNumberAttribute(element, "height"));
10667
+ } else if (tagName === "text") {
10668
+ addPoint(readNumberAttribute(element, "x"), readNumberAttribute(element, "y"));
10669
+ } else if (tagName === "path") {
10670
+ collectNumberPairs(element.getAttribute("d"), addPoint);
10671
+ } else if (tagName === "polyline" || tagName === "polygon") {
10672
+ collectNumberPairs(element.getAttribute("points"), addPoint);
10673
+ }
10674
+ collectTranslatePoints(element.getAttribute("transform"), addPoint);
10675
+ }
10676
+ function collectNumberPairs(value, addPoint) {
10677
+ if (!value) {
10678
+ return;
10679
+ }
10680
+ const numbers = readNumbers(value);
10681
+ for (let index = 0; index + 1 < numbers.length; index += 2) {
10682
+ addPoint(numbers[index], numbers[index + 1]);
10683
+ }
10684
+ }
10685
+ function collectTranslatePoints(value, addPoint) {
10686
+ if (!value) {
10687
+ return;
10688
+ }
10689
+ const translatePattern = /translate\(\s*(-?\d*\.?\d+(?:e[-+]?\d+)?)(?:[\s,]+(-?\d*\.?\d+(?:e[-+]?\d+)?))?/gi;
10690
+ for (const match of value.matchAll(translatePattern)) {
10691
+ addPoint(Number(match[1]), Number(match[2] ?? 0));
10692
+ }
10693
+ }
10694
+ function readNumbers(value) {
10695
+ const matches = value.match(svgNumberPattern);
10696
+ return matches ? matches.map((item) => Number(item)).filter(Number.isFinite) : [];
10697
+ }
10698
+ function readNumberAttribute(element, name) {
10699
+ const value = Number(element.getAttribute(name) ?? 0);
10700
+ return Number.isFinite(value) ? value : 0;
10701
+ }
10702
+ function quantile(values, ratio) {
10703
+ const index = Math.max(0, Math.min(values.length - 1, Math.floor((values.length - 1) * ratio)));
10704
+ return values[index];
10705
+ }
10706
+ function padBounds(bounds, ratio) {
10707
+ const paddingX = bounds.width * ratio;
10708
+ const paddingY = bounds.height * ratio;
10709
+ return {
10710
+ minX: bounds.minX - paddingX,
10711
+ minY: bounds.minY - paddingY,
10712
+ width: bounds.width + paddingX * 2,
10713
+ height: bounds.height + paddingY * 2
10714
+ };
10715
+ }
10716
+ function formatViewBox(bounds) {
10717
+ return [bounds.minX, bounds.minY, bounds.width, bounds.height].map((value) => Number(value.toFixed(4))).join(" ");
10718
+ }
10719
+ function createDwgDrawingViewport(svgElement) {
10720
+ const frame = document.createElement("div");
10721
+ frame.className = "ofv-dwg-preview-frame";
10722
+ const importedSvg = document.importNode(svgElement, true);
10723
+ frame.append(importedSvg);
10724
+ const aspectRatio = readSvgAspectRatio(svgElement);
10725
+ let zoom = 1;
10726
+ const update = () => {
10727
+ const frameWidth = Math.max(frame.clientWidth, 1);
10728
+ const readableWidth = aspectRatio ? minReadableDrawingHeight * aspectRatio : frameWidth;
10729
+ const width = Math.max(frameWidth, readableWidth) * zoom;
10730
+ importedSvg.style.width = `${Math.round(width)}px`;
10731
+ importedSvg.style.minWidth = `${Math.round(width)}px`;
10732
+ };
10733
+ const setZoom = (nextZoom) => {
10734
+ zoom = Math.min(Math.max(nextZoom, 0.2), 8);
10735
+ update();
10736
+ };
10737
+ requestAnimationFrame(update);
10738
+ return {
10739
+ frame,
10740
+ get zoom() {
10741
+ return zoom;
10742
+ },
10743
+ setZoom,
10744
+ update
10745
+ };
10746
+ }
10747
+ function readSvgAspectRatio(svgElement) {
10748
+ const viewBox = readSvgViewBox(svgElement);
10749
+ return viewBox ? viewBox.width / viewBox.height : void 0;
10750
+ }
10751
+ function readSvgViewBox(svgElement) {
10752
+ const viewBox = svgElement.getAttribute("viewBox");
10753
+ if (!viewBox) {
10754
+ return void 0;
10755
+ }
10756
+ const [minX, minY, width, height] = viewBox.trim().split(/[\s,]+/).map((value) => Number(value));
10757
+ if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
10758
+ return void 0;
10759
+ }
10760
+ return { minX, minY, width, height };
10761
+ }
10762
+
10763
+ // src/plugins/cad.ts
10764
+ var cadExtensions = /* @__PURE__ */ new Set([
10765
+ "dxf",
10766
+ "dwg",
10767
+ "dwf",
10768
+ "step",
10769
+ "stp",
10770
+ "iges",
10771
+ "igs",
10772
+ "ifc",
10773
+ "sat",
10774
+ "sab",
10775
+ "x_t",
10776
+ "x_b",
10777
+ "3dm",
10778
+ "skp",
10779
+ "sldprt",
10780
+ "sldasm",
10781
+ "gds",
10782
+ "oas",
10783
+ "oasis"
10784
+ ]);
10785
+ var cadMimeTypes = /* @__PURE__ */ new Set([
10786
+ "application/acad",
10787
+ "application/dxf",
10788
+ "application/x-dxf",
10789
+ "image/vnd.dxf",
10790
+ "model/vnd.dwf",
10791
+ "model/step",
10792
+ "application/step",
10793
+ "application/iges",
10794
+ "application/x-step",
10795
+ "application/sat",
10796
+ "application/sab",
10797
+ "application/x-parasolid",
10798
+ "model/vnd.3dm",
10799
+ "application/vnd.sketchup.skp",
10800
+ "application/sldworks",
10801
+ "application/vnd.gds",
10802
+ "application/x-gdsii",
10803
+ "application/vnd.oasis.layout",
10804
+ "application/x-oasis-layout"
10805
+ ]);
10806
+ var cadMimeFormatMap = {
10807
+ "application/acad": "dwg",
10808
+ "application/dxf": "dxf",
10809
+ "application/x-dxf": "dxf",
10810
+ "image/vnd.dxf": "dxf",
10811
+ "model/vnd.dwf": "dwf",
10812
+ "model/step": "step",
10813
+ "application/step": "step",
10814
+ "application/iges": "iges",
10815
+ "application/x-step": "ifc",
10816
+ "application/sat": "sat",
10817
+ "application/sab": "sab",
10818
+ "application/x-parasolid": "x_t",
10819
+ "model/vnd.3dm": "3dm",
10820
+ "application/vnd.sketchup.skp": "skp",
10821
+ "application/sldworks": "sldprt",
10822
+ "application/vnd.gds": "gds",
10823
+ "application/x-gdsii": "gds",
10824
+ "application/vnd.oasis.layout": "oas",
10825
+ "application/x-oasis-layout": "oas"
10826
+ };
10827
+ function cadPlugin(options = {}) {
10828
+ return {
10829
+ name: "cad",
10830
+ match(file) {
10831
+ return cadExtensions.has(file.extension) || cadMimeTypes.has(file.mimeType);
10832
+ },
10833
+ async render(ctx) {
10834
+ const panel = createPanel("ofv-cad");
10835
+ ctx.viewport.append(panel);
10836
+ const extension = resolveFormat(ctx.file, cadMimeFormatMap);
10837
+ if (extension === "step" || extension === "stp") {
10838
+ renderStep(panel, await readTextFile(ctx.file), extension);
10839
+ return { destroy: () => panel.remove() };
10840
+ }
10841
+ if (extension === "iges" || extension === "igs") {
10842
+ renderIges(panel, await readTextFile(ctx.file), extension);
10843
+ return { destroy: () => panel.remove() };
10844
+ }
10845
+ if (extension === "ifc") {
10846
+ renderIfc(panel, await readTextFile(ctx.file));
10847
+ return { destroy: () => panel.remove() };
10848
+ }
10849
+ if (extension === "dwg" || extension === "dwf") {
10850
+ const arrayBuffer = await readArrayBuffer(ctx.file);
10851
+ const bytes = new Uint8Array(arrayBuffer);
10852
+ const enhancedInstance = await options.binaryRenderer?.({
10853
+ panel,
10854
+ fileName: ctx.file.name,
10855
+ extension,
10856
+ arrayBuffer,
10857
+ bytes,
10858
+ preview: ctx
10859
+ });
10860
+ if (enhancedInstance) {
10861
+ return {
10862
+ resize(size) {
10863
+ enhancedInstance.resize?.(size);
10864
+ },
10865
+ canCommand(command) {
10866
+ return enhancedInstance.canCommand?.(command) ?? false;
10867
+ },
10868
+ command(command) {
10869
+ return enhancedInstance.command?.(command);
10870
+ },
10871
+ destroy() {
10872
+ enhancedInstance.destroy();
10873
+ panel.remove();
10874
+ }
10875
+ };
10876
+ }
10877
+ if (extension === "dwg" && options.libreDwg !== false) {
10878
+ const libreDwgInstance = await renderLibreDwgPreview(
10879
+ {
10880
+ panel,
10881
+ fileName: ctx.file.name,
10882
+ extension,
10883
+ arrayBuffer,
10884
+ bytes,
10885
+ preview: ctx
10886
+ },
10887
+ typeof options.libreDwg === "object" ? options.libreDwg : void 0
10888
+ );
10889
+ if (libreDwgInstance) {
10890
+ return {
10891
+ resize(size) {
10892
+ libreDwgInstance.resize?.(size);
10893
+ },
10894
+ canCommand(command) {
10895
+ return libreDwgInstance.canCommand?.(command) ?? false;
10896
+ },
10897
+ command(command) {
10898
+ return libreDwgInstance.command?.(command);
10899
+ },
10900
+ destroy() {
10901
+ libreDwgInstance.destroy();
10902
+ panel.remove();
10903
+ }
10904
+ };
10905
+ }
10906
+ }
10907
+ renderBinaryCad(panel, bytes, extension, ctx.file.name);
10908
+ return { destroy: () => panel.remove() };
10909
+ }
10910
+ if (extension === "gds") {
10911
+ const viewer2 = renderLayoutPreview(panel, parseGdsLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
10912
+ return {
10913
+ canCommand(command) {
10914
+ return viewer2.canCommand(command);
10915
+ },
10916
+ command(command) {
10917
+ return viewer2.command(command);
10918
+ },
10919
+ destroy() {
10920
+ viewer2.destroy();
10921
+ panel.remove();
10922
+ }
10923
+ };
10924
+ }
10925
+ if (extension === "oas" || extension === "oasis") {
10926
+ const viewer2 = renderLayoutPreview(panel, parseOasisLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
10927
+ return {
10928
+ canCommand(command) {
10929
+ return viewer2.canCommand(command);
10930
+ },
10931
+ command(command) {
10932
+ return viewer2.command(command);
10933
+ },
10934
+ destroy() {
10935
+ viewer2.destroy();
10936
+ panel.remove();
10937
+ }
10938
+ };
10939
+ }
10940
+ if (extension !== "dxf") {
10941
+ const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
10942
+ 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`);
10943
+ panel.append(section);
10944
+ return { destroy: () => panel.remove() };
10945
+ }
10946
+ const dxf = await readTextFile(ctx.file);
10947
+ const viewer = renderDxf(panel, dxf, ctx);
10948
+ return {
10949
+ canCommand(command) {
10950
+ return viewer.canCommand(command);
10951
+ },
10952
+ command(command) {
10953
+ return viewer.command(command);
10954
+ },
10955
+ destroy() {
10956
+ panel.remove();
10957
+ }
10958
+ };
10959
+ }
10960
+ };
10961
+ }
10962
+ function renderStep(panel, text, extension) {
10963
+ const records = parseStepRecords(text);
10964
+ const typeCounts = countBy(records.map((record) => record.type));
10965
+ const section = createSection(`${extension.toUpperCase()} \u7ED3\u6784\u9884\u89C8`);
10966
+ const note = document.createElement("p");
10967
+ note.textContent = "\u5F53\u524D\u7248\u672C\u63D0\u53D6 STEP \u6587\u672C\u5B9E\u4F53\u3001\u7C7B\u578B\u7EDF\u8BA1\u548C\u5173\u952E\u51E0\u4F55\u53C2\u6570\u3002\u7CBE\u786E B-Rep/\u66F2\u9762\u6E32\u67D3\u5EFA\u8BAE\u540E\u7EED\u63A5\u5165 CAD \u5185\u6838\u6216\u670D\u52A1\u7AEF\u8F6C\u6362\u3002";
9960
10968
  const meta = document.createElement("div");
9961
10969
  meta.className = "ofv-cad-summary";
9962
10970
  appendMeta5(meta, "\u5B9E\u4F53", records.length);
@@ -10027,11 +11035,564 @@ function renderIfc(panel, text) {
10027
11035
  section.append(table);
10028
11036
  panel.append(section);
10029
11037
  }
10030
- function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
10031
- const bytes = new Uint8Array(arrayBuffer);
11038
+ var layoutPalette = [
11039
+ "#2563eb",
11040
+ "#dc2626",
11041
+ "#059669",
11042
+ "#7c3aed",
11043
+ "#d97706",
11044
+ "#0891b2",
11045
+ "#be123c",
11046
+ "#4f46e5",
11047
+ "#15803d",
11048
+ "#a16207"
11049
+ ];
11050
+ var gdsRecordNames = {
11051
+ 0: "HEADER",
11052
+ 1: "BGNLIB",
11053
+ 2: "LIBNAME",
11054
+ 3: "UNITS",
11055
+ 4: "ENDLIB",
11056
+ 5: "BGNSTR",
11057
+ 6: "STRNAME",
11058
+ 7: "ENDSTR",
11059
+ 8: "BOUNDARY",
11060
+ 9: "PATH",
11061
+ 10: "SREF",
11062
+ 11: "AREF",
11063
+ 12: "TEXT",
11064
+ 13: "LAYER",
11065
+ 14: "DATATYPE",
11066
+ 15: "WIDTH",
11067
+ 16: "XY",
11068
+ 17: "ENDEL",
11069
+ 18: "SNAME",
11070
+ 22: "TEXTTYPE",
11071
+ 25: "STRING",
11072
+ 45: "BOX"
11073
+ };
11074
+ var oasisRecordNames = {
11075
+ 0: "PAD",
11076
+ 1: "START",
11077
+ 2: "END",
11078
+ 3: "CELLNAME",
11079
+ 4: "CELLNAME-REF",
11080
+ 5: "TEXTSTRING",
11081
+ 6: "TEXTSTRING-REF",
11082
+ 7: "PROPNAME",
11083
+ 8: "PROPNAME-REF",
11084
+ 9: "PROPSTRING",
11085
+ 10: "PROPSTRING-REF",
11086
+ 11: "LAYERNAME",
11087
+ 12: "LAYERNAME-REF",
11088
+ 13: "CELL",
11089
+ 14: "XYABSOLUTE",
11090
+ 15: "XYRELATIVE",
11091
+ 16: "PLACEMENT",
11092
+ 17: "PLACEMENT",
11093
+ 18: "TEXT",
11094
+ 19: "RECTANGLE",
11095
+ 20: "POLYGON",
11096
+ 21: "PATH",
11097
+ 22: "TRAPEZOID",
11098
+ 23: "TRAPEZOID",
11099
+ 24: "TRAPEZOID",
11100
+ 25: "CTRAPEZOID",
11101
+ 26: "CIRCLE",
11102
+ 27: "PROPERTY",
11103
+ 28: "PROPERTY",
11104
+ 29: "XNAME",
11105
+ 30: "XNAME-REF",
11106
+ 31: "XELEMENT",
11107
+ 32: "XGEOMETRY",
11108
+ 33: "CBLOCK"
11109
+ };
11110
+ function renderLayoutPreview(panel, data, ctx) {
11111
+ const section = createSection(`${data.format} \u7248\u56FE\u9884\u89C8`);
11112
+ const summary = document.createElement("div");
11113
+ summary.className = "ofv-cad-summary ofv-layout-summary";
11114
+ appendMeta5(summary, "\u6587\u4EF6", data.fileName);
11115
+ appendMeta5(summary, "\u683C\u5F0F", data.format);
11116
+ if (data.libraryName) {
11117
+ appendMeta5(summary, "\u5E93", data.libraryName);
11118
+ }
11119
+ if (data.version) {
11120
+ appendMeta5(summary, "\u7248\u672C", data.version);
11121
+ }
11122
+ if (data.unit) {
11123
+ appendMeta5(summary, "\u5355\u4F4D", data.unit);
11124
+ }
11125
+ appendMeta5(summary, "Cell", data.cells.length);
11126
+ appendMeta5(summary, "\u51E0\u4F55", data.shapes.length);
11127
+ appendMeta5(summary, "\u5F15\u7528", data.references.length);
11128
+ appendMeta5(summary, "\u6587\u5B57", data.labels.length);
11129
+ for (const [label, value] of data.metadata) {
11130
+ appendMeta5(summary, label, value);
11131
+ }
11132
+ section.append(summary);
11133
+ for (const noteText of [...data.notes, ...data.warnings]) {
11134
+ const note = document.createElement("p");
11135
+ note.className = data.warnings.includes(noteText) ? "ofv-layout-warning" : "ofv-layout-note";
11136
+ note.textContent = noteText;
11137
+ section.append(note);
11138
+ }
11139
+ const bounds = computeLayoutBounds(data.shapes, data.labels, data.references);
11140
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
11141
+ svg.setAttribute("class", "ofv-svg-stage ofv-layout-stage");
11142
+ let currentViewBox = { x: bounds.minX, y: bounds.minY, width: bounds.width, height: bounds.height };
11143
+ const initialViewBox = { ...currentViewBox };
11144
+ const applyViewBox = () => {
11145
+ svg.setAttribute("viewBox", `${currentViewBox.x} ${currentViewBox.y} ${currentViewBox.width} ${currentViewBox.height}`);
11146
+ };
11147
+ applyViewBox();
11148
+ if (data.shapes.length === 0) {
11149
+ const empty = document.createElementNS(svg.namespaceURI, "text");
11150
+ empty.setAttribute("x", String(bounds.minX + bounds.width * 0.5));
11151
+ empty.setAttribute("y", String(bounds.minY + bounds.height * 0.5));
11152
+ empty.setAttribute("text-anchor", "middle");
11153
+ empty.setAttribute("font-size", String(Math.max(bounds.width, bounds.height) / 34));
11154
+ empty.setAttribute("fill", "currentColor");
11155
+ empty.textContent = "\u5DF2\u8BC6\u522B\u7248\u56FE\u6587\u4EF6\uFF0C\u5F53\u524D\u6587\u4EF6\u672A\u89E3\u6790\u51FA\u53EF\u7ED8\u5236\u51E0\u4F55";
11156
+ svg.append(empty);
11157
+ }
11158
+ const layerIndex = new Map([...data.layers.keys()].sort((a, b) => a.localeCompare(b)).map((layer, index) => [layer, index]));
11159
+ for (const shape of data.shapes.slice(0, 6e3)) {
11160
+ const color = layoutPalette[(layerIndex.get(shape.layer) || 0) % layoutPalette.length];
11161
+ if (shape.kind === "path") {
11162
+ const polyline = document.createElementNS(svg.namespaceURI, "polyline");
11163
+ polyline.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
11164
+ polyline.setAttribute("fill", "none");
11165
+ polyline.setAttribute("stroke", color);
11166
+ polyline.setAttribute("stroke-width", String(Math.max(bounds.stroke, Math.abs(shape.width || 0))));
11167
+ polyline.setAttribute("stroke-linecap", "round");
11168
+ polyline.setAttribute("stroke-linejoin", "round");
11169
+ applyLayer(polyline, shape.layer);
11170
+ svg.append(polyline);
11171
+ continue;
11172
+ }
11173
+ const polygon = document.createElementNS(svg.namespaceURI, "polygon");
11174
+ polygon.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
11175
+ polygon.setAttribute("fill", color);
11176
+ polygon.setAttribute("fill-opacity", "0.18");
11177
+ polygon.setAttribute("stroke", color);
11178
+ polygon.setAttribute("stroke-width", String(bounds.stroke));
11179
+ polygon.setAttribute("vector-effect", "non-scaling-stroke");
11180
+ applyLayer(polygon, shape.layer);
11181
+ svg.append(polygon);
11182
+ }
11183
+ for (const label of data.labels.slice(0, 400)) {
11184
+ const text = document.createElementNS(svg.namespaceURI, "text");
11185
+ text.setAttribute("x", String(label.x));
11186
+ text.setAttribute("y", String(-label.y));
11187
+ text.setAttribute("font-size", String(Math.max(bounds.stroke * 12, Math.max(bounds.width, bounds.height) / 120)));
11188
+ text.setAttribute("fill", "currentColor");
11189
+ text.textContent = label.text;
11190
+ applyLayer(text, label.layer);
11191
+ svg.append(text);
11192
+ }
11193
+ const layers = [...data.layers.keys()].sort((a, b) => a.localeCompare(b));
11194
+ if (layers.length > 0) {
11195
+ section.append(createLayoutLayerControls(svg, layers, data.layers));
11196
+ }
11197
+ section.append(svg);
11198
+ if (data.cells.length > 0) {
11199
+ section.append(createLayoutCellList(data.cells, data.references));
11200
+ }
11201
+ panel.append(section);
11202
+ const updateToolbarZoom = () => ctx.toolbar?.setZoom(initialViewBox.width / currentViewBox.width);
11203
+ updateToolbarZoom();
11204
+ return {
11205
+ canCommand(command) {
11206
+ return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
11207
+ },
11208
+ command(command) {
11209
+ if (command === "zoom-in" || command === "zoom-out") {
11210
+ const factor = command === "zoom-in" ? 0.82 : 1.18;
11211
+ const centerX = currentViewBox.x + currentViewBox.width / 2;
11212
+ const centerY = currentViewBox.y + currentViewBox.height / 2;
11213
+ currentViewBox.width *= factor;
11214
+ currentViewBox.height *= factor;
11215
+ currentViewBox.x = centerX - currentViewBox.width / 2;
11216
+ currentViewBox.y = centerY - currentViewBox.height / 2;
11217
+ applyViewBox();
11218
+ updateToolbarZoom();
11219
+ return true;
11220
+ }
11221
+ if (command === "zoom-reset") {
11222
+ currentViewBox = { ...initialViewBox };
11223
+ applyViewBox();
11224
+ updateToolbarZoom();
11225
+ return true;
11226
+ }
11227
+ return false;
11228
+ },
11229
+ destroy() {
11230
+ ctx.toolbar?.setZoom(void 0);
11231
+ }
11232
+ };
11233
+ }
11234
+ function parseGdsLayout(bytes, fileName) {
11235
+ const shapes = [];
11236
+ const labels = [];
11237
+ const references = [];
11238
+ const cells = [];
11239
+ const layers = /* @__PURE__ */ new Map();
11240
+ const recordCounts = /* @__PURE__ */ new Map();
11241
+ const warnings = [];
11242
+ let libraryName = "";
11243
+ let version = "";
11244
+ let unit = "";
11245
+ let offset = 0;
11246
+ let current = {};
11247
+ let currentKind = "";
11248
+ while (offset + 4 <= bytes.length) {
11249
+ const length = readUInt16(bytes, offset);
11250
+ const recordType = bytes[offset + 2];
11251
+ const data = bytes.slice(offset + 4, offset + length);
11252
+ const name = gdsRecordNames[recordType] || `0x${recordType.toString(16).padStart(2, "0")}`;
11253
+ recordCounts.set(name, (recordCounts.get(name) || 0) + 1);
11254
+ if (length < 4 || offset + length > bytes.length) {
11255
+ warnings.push(`GDS \u8BB0\u5F55\u5728 ${offset} \u5B57\u8282\u5904\u957F\u5EA6\u5F02\u5E38\uFF0C\u5DF2\u505C\u6B62\u89E3\u6790\u3002`);
11256
+ break;
11257
+ }
11258
+ if (recordType === 0 && data.length >= 2) {
11259
+ version = String(readUInt16(data, 0));
11260
+ } else if (recordType === 2) {
11261
+ libraryName = readGdsString(data);
11262
+ } else if (recordType === 3 && data.length >= 16) {
11263
+ unit = `${formatGdsReal(data, 0)} / ${formatGdsReal(data, 8)}`;
11264
+ } else if (recordType === 6) {
11265
+ cells.push(readGdsString(data));
11266
+ } else if (recordType === 8 || recordType === 9 || recordType === 45) {
11267
+ currentKind = recordType === 9 ? "path" : recordType === 45 ? "box" : "boundary";
11268
+ current = { kind: currentKind, layer: "0", datatype: "0", points: [] };
11269
+ } else if (recordType === 12) {
11270
+ currentKind = "text";
11271
+ current = { layer: "0", text: "", x: 0, y: 0 };
11272
+ } else if (recordType === 10 || recordType === 11) {
11273
+ currentKind = "reference";
11274
+ current = { cell: "", x: 0, y: 0 };
11275
+ } else if (recordType === 13 && data.length >= 2) {
11276
+ current.layer = String(readInt16(data, 0));
11277
+ } else if ((recordType === 14 || recordType === 22) && data.length >= 2) {
11278
+ current.datatype = String(readInt16(data, 0));
11279
+ } else if (recordType === 15 && data.length >= 4) {
11280
+ current.width = Math.abs(readInt32(data, 0));
11281
+ } else if (recordType === 16) {
11282
+ const points = readGdsPoints(data);
11283
+ if (currentKind === "text" && points[0]) {
11284
+ current.x = points[0][0];
11285
+ current.y = points[0][1];
11286
+ } else if (currentKind === "reference" && points[0]) {
11287
+ current.x = points[0][0];
11288
+ current.y = points[0][1];
11289
+ } else {
11290
+ current.points = points;
11291
+ }
11292
+ } else if (recordType === 18) {
11293
+ current.cell = readGdsString(data);
11294
+ } else if (recordType === 25) {
11295
+ current.text = readGdsString(data);
11296
+ } else if (recordType === 17) {
11297
+ if ((currentKind === "boundary" || currentKind === "path" || currentKind === "box") && current.points && current.points.length > 1) {
11298
+ const shape = {
11299
+ kind: current.kind || "boundary",
11300
+ layer: String(current.layer || "0"),
11301
+ datatype: current.datatype,
11302
+ points: current.points,
11303
+ width: current.width
11304
+ };
11305
+ shapes.push(shape);
11306
+ layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
11307
+ } else if (currentKind === "text" && current.text) {
11308
+ const label = {
11309
+ layer: String(current.layer || "0"),
11310
+ text: String(current.text),
11311
+ x: Number(current.x || 0),
11312
+ y: Number(current.y || 0)
11313
+ };
11314
+ labels.push(label);
11315
+ layers.set(label.layer, (layers.get(label.layer) || 0) + 1);
11316
+ } else if (currentKind === "reference" && current.cell) {
11317
+ references.push({ cell: String(current.cell), x: Number(current.x || 0), y: Number(current.y || 0) });
11318
+ }
11319
+ current = {};
11320
+ currentKind = "";
11321
+ }
11322
+ offset += length;
11323
+ }
11324
+ return {
11325
+ format: "GDSII",
11326
+ fileName,
11327
+ libraryName,
11328
+ version: version ? `Stream ${version}` : void 0,
11329
+ unit,
11330
+ cells,
11331
+ shapes,
11332
+ labels,
11333
+ references,
11334
+ layers,
11335
+ metadata: [
11336
+ ["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
11337
+ ["\u8BB0\u5F55", sumCounts(recordCounts)],
11338
+ ["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size]
11339
+ ],
11340
+ notes: [
11341
+ `\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`
11342
+ ],
11343
+ warnings
11344
+ };
11345
+ }
11346
+ function parseOasisLayout(bytes, fileName) {
11347
+ const chunks = extractOasisCblocks(bytes);
11348
+ const expanded = chunks.flatMap((chunk) => [...chunk.bytes]);
11349
+ 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_"));
11350
+ const propertyNames = uniqueHints(chunks.flatMap((chunk) => extractAsciiRuns(chunk.bytes)).filter((item) => item.startsWith("S_")));
11351
+ const recordCounts = scanOasisRecordCounts(bytes);
11352
+ const expandedCounts = scanOasisRecordCounts(new Uint8Array(expanded));
11353
+ const layers = /* @__PURE__ */ new Map();
11354
+ const shapes = [];
11355
+ const labels = [];
11356
+ const references = [];
11357
+ for (const name of cellNames) {
11358
+ references.push({ cell: name, x: 0, y: 0 });
11359
+ }
11360
+ const pseudo = createOasisStructureShapes(cellNames, chunks.length || recordCounts.size || 1);
11361
+ for (const shape of pseudo) {
11362
+ shapes.push(shape);
11363
+ layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
11364
+ }
11365
+ for (let index = 0; index < cellNames.length; index++) {
11366
+ labels.push({ layer: "cell", text: cellNames[index], x: 12, y: -(18 + index * 18) });
11367
+ }
11368
+ if (cellNames.length > 0) {
11369
+ layers.set("cell", (layers.get("cell") || 0) + cellNames.length);
11370
+ }
11371
+ const version = readOasisVersion(bytes);
11372
+ const cblockText = chunks.length ? `${chunks.length} \u4E2A\uFF0C\u5C55\u5F00 ${formatBytes3(chunks.reduce((sum, chunk) => sum + chunk.bytes.byteLength, 0))}` : "\u672A\u53D1\u73B0";
11373
+ const notes = [
11374
+ "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"
11375
+ ];
11376
+ if (propertyNames.length > 0) {
11377
+ notes.push(`\u8BC6\u522B\u5230\u5C5E\u6027\uFF1A${propertyNames.slice(0, 5).join("\u3001")}`);
11378
+ }
11379
+ return {
11380
+ format: "OASIS",
11381
+ fileName,
11382
+ version,
11383
+ cells: cellNames,
11384
+ shapes,
11385
+ labels,
11386
+ references: [],
11387
+ layers,
11388
+ metadata: [
11389
+ ["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
11390
+ ["CBLOCK", cblockText],
11391
+ ["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size + expandedCounts.size],
11392
+ ["\u53EF\u8BFB\u7247\u6BB5", cellNames.length + propertyNames.length]
11393
+ ],
11394
+ notes,
11395
+ 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"] : []
11396
+ };
11397
+ }
11398
+ function computeLayoutBounds(shapes, labels, references) {
11399
+ const xs = [];
11400
+ const ys = [];
11401
+ for (const shape of shapes) {
11402
+ for (const [x, y] of shape.points) {
11403
+ xs.push(x);
11404
+ ys.push(-y);
11405
+ }
11406
+ }
11407
+ for (const label of labels) {
11408
+ xs.push(label.x, label.x + label.text.length * 12);
11409
+ ys.push(-label.y, -label.y - 16);
11410
+ }
11411
+ for (const reference of references) {
11412
+ xs.push(reference.x);
11413
+ ys.push(-reference.y);
11414
+ }
11415
+ const minX = Math.min(...xs, 0);
11416
+ const maxX = Math.max(...xs, 100);
11417
+ const minY = Math.min(...ys, 0);
11418
+ const maxY = Math.max(...ys, 100);
11419
+ const width = Math.max(1, maxX - minX);
11420
+ const height = Math.max(1, maxY - minY);
11421
+ const padding = Math.max(width, height) * 0.06;
11422
+ return {
11423
+ minX: minX - padding,
11424
+ minY: minY - padding,
11425
+ width: width + padding * 2,
11426
+ height: height + padding * 2,
11427
+ stroke: Math.max(width, height) / 900
11428
+ };
11429
+ }
11430
+ function createLayoutLayerControls(svg, layers, counts) {
11431
+ const controls = document.createElement("div");
11432
+ controls.className = "ofv-cad-layers ofv-layout-layers";
11433
+ const title = document.createElement("strong");
11434
+ title.textContent = `\u56FE\u5C42 ${layers.length}`;
11435
+ controls.append(title);
11436
+ for (const layer of layers) {
11437
+ const label = document.createElement("label");
11438
+ const checkbox = document.createElement("input");
11439
+ checkbox.type = "checkbox";
11440
+ checkbox.checked = true;
11441
+ checkbox.addEventListener("change", () => {
11442
+ for (const element of svg.querySelectorAll(`[data-layer="${escapeCssAttribute(layer)}"]`)) {
11443
+ element.style.display = checkbox.checked ? "" : "none";
11444
+ }
11445
+ });
11446
+ const name = document.createElement("span");
11447
+ name.textContent = `${layer} (${counts.get(layer) || 0})`;
11448
+ label.append(checkbox, name);
11449
+ controls.append(label);
11450
+ }
11451
+ return controls;
11452
+ }
11453
+ function createLayoutCellList(cells, references) {
11454
+ const details = document.createElement("details");
11455
+ details.className = "ofv-details ofv-layout-cells";
11456
+ details.open = true;
11457
+ const summary = document.createElement("summary");
11458
+ summary.textContent = `Cell \u7ED3\u6784 ${cells.length}`;
11459
+ const list = document.createElement("ul");
11460
+ const refCounts = countBy(references.map((reference) => reference.cell));
11461
+ for (const cell of cells.slice(0, 120)) {
11462
+ const item = document.createElement("li");
11463
+ const count = refCounts.get(cell) || 0;
11464
+ item.textContent = count > 0 ? `${cell} \xB7 \u5F15\u7528 ${count}` : cell;
11465
+ list.append(item);
11466
+ }
11467
+ details.append(summary, list);
11468
+ return details;
11469
+ }
11470
+ function readUInt16(bytes, offset) {
11471
+ return bytes[offset] << 8 | bytes[offset + 1];
11472
+ }
11473
+ function readInt16(bytes, offset) {
11474
+ const value = readUInt16(bytes, offset);
11475
+ return value & 32768 ? value - 65536 : value;
11476
+ }
11477
+ function readInt32(bytes, offset) {
11478
+ const value = bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
11479
+ return value | 0;
11480
+ }
11481
+ function readGdsString(bytes) {
11482
+ return new TextDecoder("ascii").decode(bytes).replace(/\0+$/g, "").trim();
11483
+ }
11484
+ function readGdsPoints(bytes) {
11485
+ const points = [];
11486
+ for (let offset = 0; offset + 7 < bytes.length; offset += 8) {
11487
+ points.push([readInt32(bytes, offset), readInt32(bytes, offset + 4)]);
11488
+ }
11489
+ return points;
11490
+ }
11491
+ function formatGdsReal(bytes, offset) {
11492
+ const value = readGdsReal(bytes, offset);
11493
+ if (!Number.isFinite(value) || value === 0) {
11494
+ return "0";
11495
+ }
11496
+ if (Math.abs(value) < 1e-3 || Math.abs(value) >= 1e4) {
11497
+ return value.toExponential(4);
11498
+ }
11499
+ return String(Number(value.toPrecision(6)));
11500
+ }
11501
+ function readGdsReal(bytes, offset) {
11502
+ const first = bytes[offset];
11503
+ if (!first) {
11504
+ return 0;
11505
+ }
11506
+ const sign = first & 128 ? -1 : 1;
11507
+ const exponent = (first & 127) - 64;
11508
+ let mantissa = 0;
11509
+ for (let index = 1; index < 8; index++) {
11510
+ mantissa = mantissa * 256 + bytes[offset + index];
11511
+ }
11512
+ return sign * (mantissa / Math.pow(2, 56)) * Math.pow(16, exponent);
11513
+ }
11514
+ function sumCounts(counts) {
11515
+ return [...counts.values()].reduce((sum, count) => sum + count, 0);
11516
+ }
11517
+ function extractOasisCblocks(bytes) {
11518
+ const chunks = [];
11519
+ const seen = /* @__PURE__ */ new Set();
11520
+ const limit = Math.min(bytes.length, 25e4);
11521
+ for (let offset = 0; offset < limit; offset++) {
11522
+ try {
11523
+ const inflated = import_pako3.default.inflateRaw(bytes.slice(offset));
11524
+ if (inflated.byteLength < 4) {
11525
+ continue;
11526
+ }
11527
+ const ascii = extractAsciiRuns(inflated);
11528
+ const hasLayoutSignal = ascii.some((item) => item.startsWith("S_") || /TOP|CELL|DIE|SIZE/i.test(item));
11529
+ const hasRecordSignal = inflated.some((byte) => byte >= 13 && byte <= 34);
11530
+ if (!hasLayoutSignal && !hasRecordSignal) {
11531
+ continue;
11532
+ }
11533
+ const signature = `${inflated.byteLength}:${Array.from(inflated.slice(0, 12)).join(",")}`;
11534
+ if (seen.has(signature)) {
11535
+ continue;
11536
+ }
11537
+ seen.add(signature);
11538
+ chunks.push({ offset, bytes: inflated });
11539
+ if (chunks.length >= 12) {
11540
+ break;
11541
+ }
11542
+ } catch {
11543
+ }
11544
+ }
11545
+ return chunks;
11546
+ }
11547
+ function scanOasisRecordCounts(bytes) {
11548
+ const counts = /* @__PURE__ */ new Map();
11549
+ for (const byte of bytes.slice(0, Math.min(bytes.length, 12e3))) {
11550
+ const name = oasisRecordNames[byte];
11551
+ if (name) {
11552
+ counts.set(name, (counts.get(name) || 0) + 1);
11553
+ }
11554
+ }
11555
+ return counts;
11556
+ }
11557
+ function readOasisVersion(bytes) {
11558
+ const magic = "%SEMI-OASIS\r\n";
11559
+ const header = new TextDecoder("ascii").decode(bytes.slice(0, Math.min(bytes.length, 48)));
11560
+ if (!header.startsWith(magic)) {
11561
+ return void 0;
11562
+ }
11563
+ const start = magic.length;
11564
+ if (bytes[start] !== 1) {
11565
+ return "OASIS";
11566
+ }
11567
+ const length = bytes[start + 1];
11568
+ const version = new TextDecoder("ascii").decode(bytes.slice(start + 2, start + 2 + length));
11569
+ return version ? `OASIS ${version}` : "OASIS";
11570
+ }
11571
+ function createOasisStructureShapes(cellNames, fallbackCount) {
11572
+ const rows = Math.max(1, cellNames.length || fallbackCount);
11573
+ const shapes = [];
11574
+ for (let index = 0; index < rows; index++) {
11575
+ const top = -(index * 18);
11576
+ const height = 12;
11577
+ const width = 88 + Math.min((cellNames[index]?.length || 5) * 4, 90);
11578
+ shapes.push({
11579
+ kind: "box",
11580
+ layer: "cell",
11581
+ points: [
11582
+ [0, top],
11583
+ [width, top],
11584
+ [width, top - height],
11585
+ [0, top - height],
11586
+ [0, top]
11587
+ ]
11588
+ });
11589
+ }
11590
+ return shapes;
11591
+ }
11592
+ function renderBinaryCad(panel, bytes, extension, fileName) {
10032
11593
  const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);
10033
11594
  const note = document.createElement("p");
10034
- note.textContent = extension === "dwg" ? "DWG \u662F AutoCAD \u4E13\u6709\u4E8C\u8FDB\u5236\u683C\u5F0F\uFF0C\u5F53\u524D\u524D\u7AEF\u63D2\u4EF6\u63D0\u4F9B\u6587\u4EF6\u8BC6\u522B\u3001\u7248\u672C\u63D0\u793A\u548C\u8F6C\u6362\u5EFA\u8BAE\uFF1B\u51E0\u4F55\u6E32\u67D3\u5EFA\u8BAE\u63A5\u5165 ODA/LibreDWG/\u670D\u52A1\u7AEF\u8F6C\u6362\u3002" : "DWF \u662F\u53D1\u5E03\u7528\u56FE\u7EB8\u683C\u5F0F\uFF0C\u5F53\u524D\u524D\u7AEF\u63D2\u4EF6\u63D0\u4F9B\u5BB9\u5668\u8BC6\u522B\u548C\u8F6C\u6362\u5EFA\u8BAE\uFF1B\u9AD8\u4FDD\u771F\u9875\u9762\u6E32\u67D3\u5EFA\u8BAE\u63A5\u5165\u4E13\u7528\u89E3\u6790\u5668\u6216\u670D\u52A1\u7AEF\u8F6C\u6362\u3002";
11595
+ note.textContent = extension === "dwg" ? "\u5DF2\u8BC6\u522B DWG \u4E13\u6709\u4E8C\u8FDB\u5236\u56FE\u7EB8\u3002\u6838\u5FC3\u63D2\u4EF6\u9ED8\u8BA4\u63D0\u4F9B\u7248\u672C\u3001\u5BB9\u5668\u3001\u7ED3\u6784\u7EBF\u7D22\u548C\u63A5\u5165\u5EFA\u8BAE\uFF1B\u771F\u5B9E\u51E0\u4F55\u6E32\u67D3\u53EF\u901A\u8FC7 cadPlugin({ binaryRenderer }) \u63A5\u5165\u53EF\u9009\u524D\u7AEF\u5F15\u64CE\u6216\u8F6C\u6362\u670D\u52A1\u3002" : "\u5DF2\u8BC6\u522B DWF \u53D1\u5E03\u56FE\u7EB8\u3002\u6838\u5FC3\u63D2\u4EF6\u9ED8\u8BA4\u63D0\u4F9B\u5BB9\u5668\u7EBF\u7D22\u548C\u63A5\u5165\u5EFA\u8BAE\uFF1B\u9AD8\u4FDD\u771F\u9875\u9762\u6E32\u67D3\u53EF\u901A\u8FC7 cadPlugin({ binaryRenderer }) \u63A5\u5165\u4E13\u7528\u89E3\u6790\u5668\u6216\u8F6C\u6362\u670D\u52A1\u3002";
10035
11596
  const meta = document.createElement("div");
10036
11597
  meta.className = "ofv-cad-summary";
10037
11598
  appendMeta5(meta, "\u6587\u4EF6", fileName);
@@ -10040,33 +11601,41 @@ function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
10040
11601
  appendMeta5(meta, "\u7B7E\u540D", byteSignature2(bytes));
10041
11602
  appendMeta5(meta, "\u7248\u672C", detectCadVersion(bytes, extension));
10042
11603
  appendMeta5(meta, "\u5BB9\u5668", detectCadContainer(bytes));
10043
- const actions = document.createElement("ol");
11604
+ const actions = document.createElement("div");
10044
11605
  actions.className = "ofv-cad-conversion";
11606
+ const actionTitle = document.createElement("h4");
11607
+ actionTitle.textContent = extension === "dwg" ? "\u63A8\u8350\u589E\u5F3A\u8DEF\u7EBF" : "\u63A8\u8350\u5904\u7406\u8DEF\u7EBF";
11608
+ const actionList = document.createElement("ol");
10045
11609
  const suggestions = extension === "dwg" ? [
10046
- "\u670D\u52A1\u7AEF\u4F7F\u7528 ODA File Converter / Teigha \u5C06 DWG \u8F6C\u4E3A DXF\u3001SVG \u6216 PDF\u3002",
10047
- "\u6D4F\u89C8\u5668\u7AEF\u53EF\u540E\u7EED\u63A5\u5165 LibreDWG WASM\uFF0C\u4F46\u9700\u5904\u7406\u5B57\u4F53\u3001\u5757\u53C2\u7167\u3001\u5916\u90E8\u53C2\u7167\u548C\u8BB8\u53EF\u8BC1\u3002",
10048
- "\u82E5\u4E1A\u52A1\u53EA\u9700\u9884\u89C8\uFF0C\u5EFA\u8BAE\u8F6C\u6362\u4E3A PDF/SVG \u540E\u8D70\u73B0\u6709 PDF \u6216\u56FE\u50CF\u9884\u89C8\u94FE\u8DEF\u3002"
11610
+ "\u4EA7\u54C1\u9ED8\u8BA4\u94FE\u8DEF\uFF1A\u670D\u52A1\u7AEF\u5C06 DWG \u8F6C\u4E3A PDF/SVG/DXF\uFF0C\u518D\u590D\u7528\u73B0\u6709 PDF\u3001\u56FE\u50CF\u6216 DXF \u9884\u89C8\u3002",
11611
+ "\u7EAF\u524D\u7AEF\u589E\u5F3A\uFF1A\u901A\u8FC7 binaryRenderer \u63A5\u5165 mlightcad / LibreDWG WASM \u4E00\u7C7B\u5F15\u64CE\uFF0C\u6309\u9700\u52A0\u8F7D worker \u548C\u5B57\u4F53\u8D44\u6E90\u3002",
11612
+ "\u5546\u7528\u9AD8\u4FDD\u771F\uFF1A\u63A5\u5165 ODA Drawings SDK / Web SDK\uFF0C\u9002\u5408\u590D\u6742\u56FE\u5C42\u3001\u5B57\u4F53\u3001\u5916\u90E8\u53C2\u7167\u548C\u5927\u56FE\u7EB8\u3002"
10049
11613
  ] : [
10050
11614
  "\u4F18\u5148\u5728\u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/SVG\uFF0C\u4FDD\u7559\u56FE\u5C42\u3001\u9875\u9762\u548C\u6807\u6CE8\u4FE1\u606F\u3002",
10051
- "\u82E5 DWF \u4E3A\u538B\u7F29\u5BB9\u5668\uFF0C\u53EF\u540E\u7EED\u8BFB\u53D6 manifest/descriptor \u518D\u8FD8\u539F\u9875\u9762\u8D44\u6E90\u3002",
11615
+ "\u82E5 DWF \u4E3A\u538B\u7F29\u5BB9\u5668\uFF0C\u53EF\u901A\u8FC7 binaryRenderer \u8BFB\u53D6 manifest/descriptor \u518D\u8FD8\u539F\u9875\u9762\u8D44\u6E90\u3002",
10052
11616
  "\u82E5\u4E1A\u52A1\u53EA\u9700\u4E0B\u8F7D/\u5F52\u6863\uFF0C\u4FDD\u7559\u5F53\u524D\u6587\u4EF6\u5143\u4FE1\u606F\u548C\u8F6C\u6362\u63D0\u793A\u5373\u53EF\u3002"
10053
11617
  ];
10054
11618
  for (const suggestion of suggestions) {
10055
11619
  const item = document.createElement("li");
10056
11620
  item.textContent = suggestion;
10057
- actions.append(item);
11621
+ actionList.append(item);
10058
11622
  }
11623
+ actions.append(actionTitle, actionList);
11624
+ const raw = document.createElement("details");
11625
+ raw.className = "ofv-details ofv-cad-raw-preview";
11626
+ const rawSummary = document.createElement("summary");
11627
+ rawSummary.textContent = "\u539F\u59CB\u5B57\u8282\u9884\u89C8";
10059
11628
  const preview = document.createElement("pre");
10060
11629
  preview.className = "ofv-text-block";
10061
11630
  preview.textContent = hexPreview(bytes);
10062
- section.append(note, meta, createBinaryCadProbe(bytes, extension), actions, preview);
11631
+ raw.append(rawSummary, preview);
11632
+ section.append(note, meta, actions, createBinaryCadProbe(bytes, extension), raw);
10063
11633
  panel.append(section);
10064
11634
  }
10065
11635
  function createBinaryCadProbe(bytes, extension) {
10066
11636
  const probe = probeBinaryCad(bytes);
10067
11637
  const details = document.createElement("details");
10068
11638
  details.className = "ofv-details ofv-cad-binary-probe";
10069
- details.open = true;
10070
11639
  const summary = document.createElement("summary");
10071
11640
  summary.textContent = "\u4E8C\u8FDB\u5236\u7ED3\u6784\u63A2\u6D4B";
10072
11641
  const meta = document.createElement("div");
@@ -11670,6 +13239,15 @@ function assetPlugin() {
11670
13239
  const isExternal = Boolean(ctx.file.url);
11671
13240
  const extension = resolveFormat(ctx.file, assetMimeFormatMap).toLowerCase();
11672
13241
  const bytes = new Uint8Array(await readArrayBuffer(ctx.file).catch(() => new ArrayBuffer(0)));
13242
+ if (isPhotoshopAsset(extension)) {
13243
+ panel.append(await createPhotoshopPreview(bytes));
13244
+ return {
13245
+ destroy() {
13246
+ revokeObjectUrl(url, isExternal);
13247
+ panel.remove();
13248
+ }
13249
+ };
13250
+ }
11673
13251
  const section = createSection(assetTitle(extension));
11674
13252
  const summary = document.createElement("div");
11675
13253
  summary.className = "ofv-asset-summary";
@@ -11691,9 +13269,6 @@ function assetPlugin() {
11691
13269
  if (extension === "wasm") {
11692
13270
  section.append(createWasmPreview(bytes));
11693
13271
  }
11694
- if (extension === "psd" || extension === "psb") {
11695
- section.append(createPhotoshopPreview(bytes));
11696
- }
11697
13272
  if (extension === "sqlite" || extension === "sqlite3" || extension === "db") {
11698
13273
  section.append(createSqlitePreview(bytes));
11699
13274
  }
@@ -11746,7 +13321,7 @@ function assetGuidance(extension) {
11746
13321
  return "\u5B57\u4F53\u6587\u4EF6\u5DF2\u8BC6\u522B\uFF0C\u5F53\u524D\u4F1A\u4F7F\u7528 FontFace \u5C55\u793A\u5B57\u5F62\u6837\u5F20\uFF0C\u5E76\u89E3\u6790 sfnt/WOFF \u8868\u76EE\u5F55\u548C name \u5143\u4FE1\u606F\u3002";
11747
13322
  }
11748
13323
  if (extension === "psd" || extension === "psb") {
11749
- return "PSD/PSB \u5DF2\u8BC6\u522B\uFF0C\u5F53\u524D\u4F1A\u89E3\u6790\u57FA\u7840\u753B\u5E03\u3001\u901A\u9053\u3001\u4F4D\u6DF1\u548C\u989C\u8272\u6A21\u5F0F\uFF1B\u56FE\u5C42\u7F29\u7565\u56FE\u53EF\u540E\u7EED\u63A5\u5165 psd.js/WASM \u6216\u670D\u52A1\u7AEF\u8F6C\u6362\u3002";
13324
+ return "PSD/PSB \u5DF2\u8BC6\u522B\uFF0C\u5F53\u524D\u4F1A\u4F18\u5148\u89E3\u6790 Photoshop \u5408\u6210\u56FE\u9884\u89C8\uFF0C\u5E76\u4FDD\u7559\u753B\u5E03\u3001\u901A\u9053\u3001\u4F4D\u6DF1\u548C\u989C\u8272\u6A21\u5F0F\u7B49\u7ED3\u6784\u4FE1\u606F\u3002";
11750
13325
  }
11751
13326
  if (["ai", "eps", "ps"].includes(extension)) {
11752
13327
  return "PostScript/Illustrator \u6587\u4EF6\u5DF2\u8BC6\u522B\uFF0C\u5F53\u524D\u4F1A\u89E3\u6790\u6587\u6863\u5934\u3001BoundingBox \u548C\u5E38\u89C1 DSC \u5143\u4FE1\u606F\uFF1B\u9AD8\u4FDD\u771F\u56FE\u5F62\u53EF\u540E\u7EED\u63A5\u5165 Ghostscript/Illustrator \u8F6C\u6362\u3002";
@@ -11768,6 +13343,9 @@ function assetGuidance(extension) {
11768
13343
  function isFontAsset(extension) {
11769
13344
  return ["ttf", "otf", "woff", "woff2", "eot"].includes(extension);
11770
13345
  }
13346
+ function isPhotoshopAsset(extension) {
13347
+ return extension === "psd" || extension === "psb";
13348
+ }
11771
13349
  async function createFontPreview(extension, url, fileName, bytes) {
11772
13350
  const preview = document.createElement("div");
11773
13351
  preview.className = "ofv-font-preview";
@@ -12529,12 +14107,9 @@ function wasmExternalKind(kind) {
12529
14107
  };
12530
14108
  return names[kind] || `unknown ${kind}`;
12531
14109
  }
12532
- function createPhotoshopPreview(bytes) {
14110
+ async function createPhotoshopPreview(bytes) {
12533
14111
  const preview = document.createElement("div");
12534
14112
  preview.className = "ofv-psd-preview";
12535
- const heading = document.createElement("strong");
12536
- heading.textContent = "Photoshop \u7ED3\u6784";
12537
- preview.append(heading);
12538
14113
  const header = parsePhotoshopHeader(bytes);
12539
14114
  if (!header.valid) {
12540
14115
  const error = document.createElement("p");
@@ -12543,16 +14118,66 @@ function createPhotoshopPreview(bytes) {
12543
14118
  preview.append(error);
12544
14119
  return preview;
12545
14120
  }
12546
- const summary = document.createElement("div");
12547
- summary.className = "ofv-psd-summary";
12548
- appendMeta(summary, "\u7248\u672C", header.version === 2 ? "PSB \u5927\u6587\u6863" : "PSD");
12549
- appendMeta(summary, "\u753B\u5E03", `${header.width} x ${header.height}px`);
12550
- appendMeta(summary, "\u901A\u9053", header.channels ?? "\u672A\u77E5");
12551
- appendMeta(summary, "\u4F4D\u6DF1", `${header.depth ?? "\u672A\u77E5"} bit`);
12552
- appendMeta(summary, "\u989C\u8272\u6A21\u5F0F", photoshopColorMode(header.colorMode ?? -1));
12553
- preview.append(summary);
14121
+ preview.append(await createPhotoshopCompositePreview(bytes, header));
12554
14122
  return preview;
12555
14123
  }
14124
+ async function createPhotoshopCompositePreview(bytes, header) {
14125
+ const wrapper = document.createElement("div");
14126
+ wrapper.className = "ofv-psd-composite";
14127
+ if (!canUseBrowserCanvas()) {
14128
+ const status = document.createElement("p");
14129
+ status.className = "ofv-psd-error";
14130
+ status.textContent = "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Canvas\uFF0C\u65E0\u6CD5\u751F\u6210 PSD \u5408\u6210\u56FE\u9884\u89C8\u3002";
14131
+ wrapper.append(status);
14132
+ return wrapper;
14133
+ }
14134
+ try {
14135
+ const { readPsd } = await import("ag-psd");
14136
+ const psd = readPsd(toStandaloneArrayBuffer(bytes), {
14137
+ skipLayerImageData: true,
14138
+ skipThumbnail: true,
14139
+ skipLinkedFilesData: true,
14140
+ useImageData: true,
14141
+ throwForMissingFeatures: false,
14142
+ logMissingFeatures: false
14143
+ });
14144
+ const canvas = psd.canvas || createCanvasFromPsdImageData(psd);
14145
+ if (!canvas) {
14146
+ throw new Error("PSD \u6587\u4EF6\u6CA1\u6709\u53EF\u8BFB\u53D6\u7684\u5408\u6210\u56FE\u50CF\u6570\u636E\u3002\u8BF7\u5728 Photoshop \u4E2D\u5F00\u542F\u201C\u6700\u5927\u517C\u5BB9\u6027\u201D\u4FDD\u5B58\uFF0C\u6216\u63A5\u5165\u5916\u90E8\u8F6C\u6362\u670D\u52A1\u3002");
14147
+ }
14148
+ canvas.classList.add("ofv-psd-canvas");
14149
+ canvas.setAttribute("role", "img");
14150
+ canvas.setAttribute("aria-label", `Photoshop composite ${header.width} x ${header.height}`);
14151
+ wrapper.append(canvas);
14152
+ } catch (error) {
14153
+ const status = document.createElement("p");
14154
+ status.className = "ofv-psd-error";
14155
+ status.textContent = `PSD \u5408\u6210\u56FE\u89E3\u6790\u5931\u8D25\uFF1A${error instanceof Error ? error.message : "\u5F53\u524D\u6587\u4EF6\u7279\u6027\u6682\u4E0D\u652F\u6301\u3002"}`;
14156
+ wrapper.append(status);
14157
+ }
14158
+ return wrapper;
14159
+ }
14160
+ function canUseBrowserCanvas() {
14161
+ return typeof document !== "undefined" && typeof HTMLCanvasElement !== "undefined";
14162
+ }
14163
+ function toStandaloneArrayBuffer(bytes) {
14164
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
14165
+ }
14166
+ function createCanvasFromPsdImageData(psd) {
14167
+ const imageData = psd.imageData;
14168
+ if (!imageData || !imageData.width || !imageData.height || !(imageData.data instanceof Uint8ClampedArray)) {
14169
+ return void 0;
14170
+ }
14171
+ const canvas = document.createElement("canvas");
14172
+ canvas.width = imageData.width;
14173
+ canvas.height = imageData.height;
14174
+ const context = canvas.getContext("2d");
14175
+ if (!context) {
14176
+ return void 0;
14177
+ }
14178
+ context.putImageData(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height), 0, 0);
14179
+ return canvas;
14180
+ }
12556
14181
  function parsePhotoshopHeader(bytes) {
12557
14182
  if (bytes.length < 26) {
12558
14183
  return { valid: false, error: "\u6587\u4EF6\u592A\u77ED\uFF0C\u65E0\u6CD5\u8BFB\u53D6 PSD/PSB \u5934\u4FE1\u606F\u3002" };
@@ -12581,19 +14206,6 @@ function parsePhotoshopHeader(bytes) {
12581
14206
  colorMode: view3.getUint16(24, false)
12582
14207
  };
12583
14208
  }
12584
- function photoshopColorMode(mode) {
12585
- const modes = {
12586
- 0: "Bitmap",
12587
- 1: "Grayscale",
12588
- 2: "Indexed",
12589
- 3: "RGB",
12590
- 4: "CMYK",
12591
- 7: "Multichannel",
12592
- 8: "Duotone",
12593
- 9: "Lab"
12594
- };
12595
- return modes[mode] ? `${modes[mode]} (${mode})` : `\u672A\u77E5 (${mode})`;
12596
- }
12597
14209
  function createSqlitePreview(bytes) {
12598
14210
  const preview = document.createElement("div");
12599
14211
  preview.className = "ofv-sqlite-preview";