@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.js CHANGED
@@ -248,6 +248,9 @@ var extensionMimeMap = {
248
248
  skp: "application/vnd.sketchup.skp",
249
249
  sldprt: "application/sldworks",
250
250
  sldasm: "application/sldworks",
251
+ gds: "application/vnd.gds",
252
+ oas: "application/vnd.oasis.layout",
253
+ oasis: "application/vnd.oasis.layout",
251
254
  ttf: "font/ttf",
252
255
  otf: "font/otf",
253
256
  woff: "font/woff",
@@ -268,7 +271,7 @@ var extensionMimeMap = {
268
271
  };
269
272
  async function normalizeFile(source, fileName, mimeType) {
270
273
  if (typeof source === "string") {
271
- const name2 = fileName || source.split("?")[0]?.split("/").pop() || "remote-file";
274
+ const name2 = fileName || getFileNameFromUrl(source) || "remote-file";
272
275
  const extension2 = getExtension(name2);
273
276
  return {
274
277
  source,
@@ -313,6 +316,17 @@ async function normalizeFile(source, fileName, mimeType) {
313
316
  blob
314
317
  };
315
318
  }
319
+ function getFileNameFromUrl(source) {
320
+ const rawName = source.split(/[?#]/, 1)[0]?.split("/").filter(Boolean).pop() || "";
321
+ if (!rawName) {
322
+ return "";
323
+ }
324
+ try {
325
+ return decodeURIComponent(rawName);
326
+ } catch {
327
+ return rawName;
328
+ }
329
+ }
316
330
  function getExtension(name) {
317
331
  const clean = name.split("?")[0]?.split("#")[0] || "";
318
332
  const index = clean.lastIndexOf(".");
@@ -1752,6 +1766,7 @@ function imagePlugin() {
1752
1766
  let dragStartY = 0;
1753
1767
  let startOffsetX = 0;
1754
1768
  let startOffsetY = 0;
1769
+ let activePointerId = null;
1755
1770
  const zoomLabel = document.createElement("span");
1756
1771
  zoomLabel.className = "ofv-image-zoom";
1757
1772
  const updateTransform = () => {
@@ -1799,29 +1814,61 @@ function imagePlugin() {
1799
1814
  if (event.button !== 0) {
1800
1815
  return;
1801
1816
  }
1817
+ if (activePointerId !== null && activePointerId !== event.pointerId) {
1818
+ finishDrag(activePointerId);
1819
+ }
1802
1820
  dragging = true;
1821
+ activePointerId = event.pointerId;
1803
1822
  dragStartX = event.clientX;
1804
1823
  dragStartY = event.clientY;
1805
1824
  startOffsetX = offsetX;
1806
1825
  startOffsetY = offsetY;
1807
1826
  stage.classList.add("is-dragging");
1808
- stage.setPointerCapture(event.pointerId);
1827
+ try {
1828
+ stage.setPointerCapture(event.pointerId);
1829
+ } catch {
1830
+ }
1809
1831
  };
1810
1832
  const onPointerMove = (event) => {
1811
- if (!dragging) {
1833
+ if (!dragging || event.pointerId !== activePointerId) {
1812
1834
  return;
1813
1835
  }
1814
1836
  offsetX = startOffsetX + event.clientX - dragStartX;
1815
1837
  offsetY = startOffsetY + event.clientY - dragStartY;
1816
1838
  updateTransform();
1817
1839
  };
1818
- const onPointerUp = (event) => {
1840
+ const finishDrag = (pointerId) => {
1841
+ const captureId = pointerId ?? activePointerId;
1819
1842
  dragging = false;
1843
+ activePointerId = null;
1820
1844
  stage.classList.remove("is-dragging");
1821
- if (stage.hasPointerCapture(event.pointerId)) {
1822
- stage.releasePointerCapture(event.pointerId);
1845
+ if (captureId !== null && captureId !== void 0) {
1846
+ try {
1847
+ if (stage.hasPointerCapture(captureId)) {
1848
+ stage.releasePointerCapture(captureId);
1849
+ }
1850
+ } catch {
1851
+ }
1852
+ }
1853
+ };
1854
+ const onPointerUp = (event) => {
1855
+ if (event.pointerId === activePointerId) {
1856
+ finishDrag(event.pointerId);
1857
+ }
1858
+ };
1859
+ const onLostPointerCapture = (event) => {
1860
+ if (event.pointerId === activePointerId) {
1861
+ finishDrag(null);
1862
+ }
1863
+ };
1864
+ const onPointerLeave = (event) => {
1865
+ if (event.pointerId === activePointerId && event.buttons === 0) {
1866
+ finishDrag(event.pointerId);
1823
1867
  }
1824
1868
  };
1869
+ const onWindowBlur = () => {
1870
+ finishDrag();
1871
+ };
1825
1872
  const onWheel = (event) => {
1826
1873
  if (!event.ctrlKey && !event.metaKey) {
1827
1874
  return;
@@ -1833,8 +1880,11 @@ function imagePlugin() {
1833
1880
  stage.addEventListener("pointermove", onPointerMove);
1834
1881
  stage.addEventListener("pointerup", onPointerUp);
1835
1882
  stage.addEventListener("pointercancel", onPointerUp);
1883
+ stage.addEventListener("lostpointercapture", onLostPointerCapture);
1884
+ stage.addEventListener("pointerleave", onPointerLeave);
1836
1885
  stage.addEventListener("wheel", onWheel, { passive: false });
1837
1886
  image.addEventListener("error", showImageFallback);
1887
+ window.addEventListener("blur", onWindowBlur);
1838
1888
  stage.append(image);
1839
1889
  wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
1840
1890
  ctx.viewport.append(wrapper);
@@ -1881,8 +1931,12 @@ function imagePlugin() {
1881
1931
  stage.removeEventListener("pointermove", onPointerMove);
1882
1932
  stage.removeEventListener("pointerup", onPointerUp);
1883
1933
  stage.removeEventListener("pointercancel", onPointerUp);
1934
+ stage.removeEventListener("lostpointercapture", onLostPointerCapture);
1935
+ stage.removeEventListener("pointerleave", onPointerLeave);
1884
1936
  stage.removeEventListener("wheel", onWheel);
1885
1937
  image.removeEventListener("error", showImageFallback);
1938
+ window.removeEventListener("blur", onWindowBlur);
1939
+ finishDrag();
1886
1940
  wrapper.remove();
1887
1941
  if (convertedBlob) {
1888
1942
  URL.revokeObjectURL(url);
@@ -4031,6 +4085,38 @@ async function readText(source) {
4031
4085
  return String(source);
4032
4086
  }
4033
4087
 
4088
+ // src/plugins/encrypted.ts
4089
+ function createEncryptedFallback(file, url, copy = {}) {
4090
+ const fallback = document.createElement("div");
4091
+ fallback.className = "ofv-fallback ofv-encrypted";
4092
+ const title = document.createElement("strong");
4093
+ title.textContent = copy.title || "\u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8";
4094
+ const message = document.createElement("span");
4095
+ 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";
4096
+ const meta = document.createElement("dl");
4097
+ meta.className = "ofv-fallback-meta ofv-encrypted-meta";
4098
+ appendEncryptedMeta(meta, "\u6587\u4EF6", file.name || "\u672A\u547D\u540D\u6587\u4EF6");
4099
+ appendEncryptedMeta(meta, "\u683C\u5F0F", file.extension ? `.${file.extension}` : file.mimeType || "\u672A\u77E5");
4100
+ const download = document.createElement("a");
4101
+ download.href = url;
4102
+ download.download = file.name;
4103
+ download.textContent = copy.action || "\u4E0B\u8F7D\u6587\u4EF6";
4104
+ fallback.append(title, message, meta, download);
4105
+ return fallback;
4106
+ }
4107
+ function isEncryptedError(error) {
4108
+ const message = error instanceof Error ? error.message : String(error || "");
4109
+ const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
4110
+ return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(`${name} ${message}`);
4111
+ }
4112
+ function appendEncryptedMeta(parent, label, value) {
4113
+ const key = document.createElement("dt");
4114
+ key.textContent = label;
4115
+ const content = document.createElement("dd");
4116
+ content.textContent = value;
4117
+ parent.append(key, content);
4118
+ }
4119
+
4034
4120
  // src/plugins/pdf.ts
4035
4121
  function multiplyMatrices(m1, m2) {
4036
4122
  return [
@@ -4072,7 +4158,11 @@ function pdfPlugin(options = {}) {
4072
4158
  const doc = await documentTask.promise.catch((error) => {
4073
4159
  viewer.remove();
4074
4160
  ctx.viewport.classList.add("ofv-center");
4075
- const fallback = createPdfFallback(ctx.file.name, url, normalizePdfError(error));
4161
+ const fallback = isEncryptedError(error) ? createEncryptedFallback(ctx.file, url, {
4162
+ title: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
4163
+ message: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
4164
+ action: "\u4E0B\u8F7D PDF"
4165
+ }) : createPdfFallback(ctx.file.name, url, normalizePdfError(error));
4076
4166
  ctx.viewport.append(fallback);
4077
4167
  return void 0;
4078
4168
  });
@@ -4362,9 +4452,6 @@ function normalizePdfError(error) {
4362
4452
  const message = error instanceof Error ? error.message : String(error || "");
4363
4453
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
4364
4454
  const lower = `${name} ${message}`.toLowerCase();
4365
- if (lower.includes("password")) {
4366
- return "\u8BE5 PDF \u53D7\u5BC6\u7801\u4FDD\u62A4\uFF0C\u5F53\u524D\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u5185\u76F4\u63A5\u9884\u89C8\u3002";
4367
- }
4368
4455
  if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
4369
4456
  return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
4370
4457
  }
@@ -5292,7 +5379,13 @@ async function renderSheet(panel, arrayBuffer, extension) {
5292
5379
  const xlsx = await import("xlsx");
5293
5380
  let workbook;
5294
5381
  try {
5295
- workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), { type: "string", FS: extension === "tsv" ? " " : "," }) : xlsx.read(arrayBuffer, { type: "array" });
5382
+ workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), {
5383
+ type: "string",
5384
+ FS: extension === "tsv" ? " " : ",",
5385
+ cellDates: true,
5386
+ cellNF: true,
5387
+ cellStyles: true
5388
+ }) : xlsx.read(arrayBuffer, { type: "array", cellDates: true, cellNF: true, cellStyles: true });
5296
5389
  } catch (error) {
5297
5390
  if (isLegacyOfficeBinary(extension)) {
5298
5391
  renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
@@ -5320,7 +5413,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5320
5413
  const heading = document.createElement("h3");
5321
5414
  heading.textContent = sheetName;
5322
5415
  const sheet = workbook.Sheets[sheetName];
5323
- const range = xlsx.utils.decode_range(sheet["!ref"] || "A1:A1");
5416
+ const range = trimWorkbookSheetRange(sheet, xlsx.utils.decode_range(sheet["!ref"] || "A1:A1"), xlsx.utils.decode_cell);
5324
5417
  const rowCount = range.e.r - range.s.r + 1;
5325
5418
  const columnCount = range.e.c - range.s.c + 1;
5326
5419
  const formulaRows = collectFormulaRows(sheet, range, xlsx.utils.encode_cell);
@@ -5330,6 +5423,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5330
5423
  const tableWrapper = document.createElement("div");
5331
5424
  tableWrapper.className = "ofv-table-scroll";
5332
5425
  const viewport = createSheetViewport(rowCount, columnCount);
5426
+ const columnSizing = { widths: /* @__PURE__ */ new Map() };
5333
5427
  const windowControls = createSheetWindowControls(viewport, () => renderTableWindow());
5334
5428
  const renderTableWindow = () => {
5335
5429
  tableWrapper.replaceChildren(
@@ -5339,7 +5433,9 @@ async function renderSheet(panel, arrayBuffer, extension) {
5339
5433
  sheetIndex,
5340
5434
  viewport,
5341
5435
  xlsx.utils.encode_cell,
5342
- xlsx.utils.format_cell
5436
+ xlsx.utils.format_cell,
5437
+ columnSizing,
5438
+ renderTableWindow
5343
5439
  )
5344
5440
  );
5345
5441
  windowControls?.update();
@@ -5394,6 +5490,10 @@ async function renderSheet(panel, arrayBuffer, extension) {
5394
5490
  }
5395
5491
  }
5396
5492
  function renderSheetFallback(panel, extension, detail) {
5493
+ if (isEncryptedText(detail)) {
5494
+ renderEncryptedOfficeByFileInfo(panel, `.${extension || "sheet"}`, "Office \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8");
5495
+ return;
5496
+ }
5397
5497
  const section = createSection("\u8868\u683C\u89E3\u6790\u5931\u8D25");
5398
5498
  const title = document.createElement("p");
5399
5499
  title.textContent = `.${extension || "sheet"} \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790\u4E3A\u53EF\u9884\u89C8\u8868\u683C\u3002`;
@@ -5404,6 +5504,17 @@ function renderSheetFallback(panel, extension, detail) {
5404
5504
  section.append(title, meta, support);
5405
5505
  panel.append(section);
5406
5506
  }
5507
+ function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
5508
+ const section = createSection(title);
5509
+ section.classList.add("ofv-encrypted");
5510
+ const message = document.createElement("p");
5511
+ 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`;
5512
+ section.append(message);
5513
+ panel.append(section);
5514
+ }
5515
+ function isEncryptedText(value) {
5516
+ return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(value);
5517
+ }
5407
5518
  function renderFlatOds(panel, xml) {
5408
5519
  const sheets = parseFlatOds(xml);
5409
5520
  renderParsedSheets(panel, sheets, "FODS \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002");
@@ -5434,9 +5545,10 @@ function renderParsedSheets(panel, sheets, emptyMessage) {
5434
5545
  const tableWrapper = document.createElement("div");
5435
5546
  tableWrapper.className = "ofv-table-scroll";
5436
5547
  const viewport = createSheetViewport(rowCount, columnCount);
5548
+ const columnSizing = { widths: /* @__PURE__ */ new Map() };
5437
5549
  const windowControls = createSheetWindowControls(viewport, () => renderTableWindow());
5438
5550
  const renderTableWindow = () => {
5439
- tableWrapper.replaceChildren(createParsedSheetTable(sheet, sheetIndex, viewport));
5551
+ tableWrapper.replaceChildren(createParsedSheetTable(sheet, sheetIndex, viewport, columnSizing, renderTableWindow));
5440
5552
  windowControls?.update();
5441
5553
  };
5442
5554
  content.append(heading, summary);
@@ -5663,6 +5775,45 @@ function chartText(element) {
5663
5775
  function chartStringValues(element) {
5664
5776
  return Array.from(element.querySelectorAll("*")).filter((item) => item.localName === "v" || item.localName === "t").map((item) => item.textContent?.trim() || "").filter(Boolean);
5665
5777
  }
5778
+ function trimWorkbookSheetRange(sheet, range, decodeCell) {
5779
+ let minRow = Number.POSITIVE_INFINITY;
5780
+ let minColumn = Number.POSITIVE_INFINITY;
5781
+ let maxRow = Number.NEGATIVE_INFINITY;
5782
+ let maxColumn = Number.NEGATIVE_INFINITY;
5783
+ const include = (row, column) => {
5784
+ minRow = Math.min(minRow, row);
5785
+ minColumn = Math.min(minColumn, column);
5786
+ maxRow = Math.max(maxRow, row);
5787
+ maxColumn = Math.max(maxColumn, column);
5788
+ };
5789
+ for (const [address, cell] of Object.entries(sheet)) {
5790
+ if (address.startsWith("!")) {
5791
+ continue;
5792
+ }
5793
+ if (!cell || cell.v == null && !cell.f && !cell.w && !cell.h) {
5794
+ continue;
5795
+ }
5796
+ const decoded = decodeCell(address);
5797
+ include(decoded.r, decoded.c);
5798
+ }
5799
+ for (const merge of sheet["!merges"] || []) {
5800
+ include(merge.s.r, merge.s.c);
5801
+ include(merge.e.r, merge.e.c);
5802
+ }
5803
+ if (!Number.isFinite(minRow) || !Number.isFinite(minColumn) || !Number.isFinite(maxRow) || !Number.isFinite(maxColumn)) {
5804
+ return range;
5805
+ }
5806
+ return {
5807
+ s: {
5808
+ r: Math.max(range.s.r, minRow),
5809
+ c: Math.max(range.s.c, minColumn)
5810
+ },
5811
+ e: {
5812
+ r: Math.min(range.e.r, maxRow),
5813
+ c: Math.min(range.e.c, maxColumn)
5814
+ }
5815
+ };
5816
+ }
5666
5817
  function createSheetViewport(rowCount, columnCount) {
5667
5818
  return {
5668
5819
  rowStart: 0,
@@ -5725,42 +5876,245 @@ function createWindowButton(label, onClick) {
5725
5876
  function maxStart(total, size) {
5726
5877
  return Math.max(0, total - size);
5727
5878
  }
5728
- function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell) {
5879
+ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender) {
5729
5880
  const table = document.createElement("table");
5730
5881
  table.id = `ofv-sheet-${sheetIndex + 1}`;
5882
+ table.className = "ofv-workbook-table";
5731
5883
  const rowEnd = Math.min(range.s.r + viewport.rowStart + SHEET_WINDOW_ROWS - 1, range.e.r);
5732
5884
  const columnEnd = Math.min(range.s.c + viewport.columnStart + SHEET_WINDOW_COLUMNS - 1, range.e.c);
5733
- for (let rowIndex = range.s.r + viewport.rowStart; rowIndex <= rowEnd; rowIndex += 1) {
5885
+ const columnStart = range.s.c + viewport.columnStart;
5886
+ const rowStart = range.s.r + viewport.rowStart;
5887
+ const mergePlan = createSheetMergePlan(sheet["!merges"] || [], rowStart, rowEnd, columnStart, columnEnd);
5888
+ const colGroup = document.createElement("colgroup");
5889
+ let tableWidth = 0;
5890
+ for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex += 1) {
5891
+ const col = document.createElement("col");
5892
+ const width = columnSizing.widths.get(columnIndex) ?? getSheetColumnWidth(sheet["!cols"]?.[columnIndex]);
5893
+ col.dataset.columnIndex = String(columnIndex);
5894
+ col.style.width = `${width}px`;
5895
+ tableWidth += width;
5896
+ colGroup.append(col);
5897
+ }
5898
+ table.style.width = `${tableWidth}px`;
5899
+ table.append(colGroup);
5900
+ for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex += 1) {
5734
5901
  const row = document.createElement("tr");
5735
- for (let columnIndex = range.s.c + viewport.columnStart; columnIndex <= columnEnd; columnIndex += 1) {
5736
- const cell = document.createElement(rowIndex === range.s.r ? "th" : "td");
5902
+ const rowHeight = getSheetRowHeight(sheet["!rows"]?.[rowIndex]);
5903
+ if (rowHeight) {
5904
+ row.style.height = `${rowHeight}px`;
5905
+ }
5906
+ for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex += 1) {
5737
5907
  const address = encodeCell({ r: rowIndex, c: columnIndex });
5738
- const sourceCell = sheet[address];
5908
+ const coordinateKey = `${rowIndex}:${columnIndex}`;
5909
+ if (mergePlan.covered.has(coordinateKey)) {
5910
+ continue;
5911
+ }
5912
+ const merge = mergePlan.anchors.get(coordinateKey);
5913
+ const sourceAddress = merge ? encodeCell({ r: merge.sourceRow, c: merge.sourceColumn }) : address;
5914
+ const sourceCell = sheet[sourceAddress];
5915
+ const cell = document.createElement(rowIndex === range.s.r ? "th" : "td");
5739
5916
  cell.dataset.cell = address;
5917
+ if (sourceAddress !== address) {
5918
+ cell.dataset.sourceCell = sourceAddress;
5919
+ }
5920
+ if (merge) {
5921
+ cell.classList.add("ofv-cell-merged");
5922
+ if (merge.rowspan > 1) {
5923
+ cell.rowSpan = merge.rowspan;
5924
+ }
5925
+ if (merge.colspan > 1) {
5926
+ cell.colSpan = merge.colspan;
5927
+ }
5928
+ }
5740
5929
  const text = sourceCell ? formatCell(sourceCell) : "";
5741
5930
  cell.textContent = text;
5742
5931
  if (text) {
5743
5932
  cell.title = text;
5744
5933
  }
5934
+ applyWorkbookCellStyle(cell, sourceCell);
5745
5935
  if (sourceCell?.f) {
5746
5936
  cell.classList.add("ofv-cell-formula");
5747
5937
  cell.title = `=${sourceCell.f}`;
5748
5938
  }
5939
+ if (text.includes("\n")) {
5940
+ cell.classList.add("ofv-cell-multiline");
5941
+ }
5942
+ appendColumnResizeHandle(cell, columnIndex, columnSizing);
5749
5943
  row.append(cell);
5750
5944
  }
5751
5945
  table.append(row);
5752
5946
  }
5753
5947
  return table;
5754
5948
  }
5755
- function createParsedSheetTable(sheet, sheetIndex, viewport) {
5949
+ function appendColumnResizeHandle(cell, columnIndex, columnSizing) {
5950
+ const handle = document.createElement("span");
5951
+ handle.className = "ofv-column-resize-handle";
5952
+ handle.setAttribute("aria-hidden", "true");
5953
+ handle.addEventListener("pointerdown", (event) => {
5954
+ event.preventDefault();
5955
+ event.stopPropagation();
5956
+ const startX = event.clientX;
5957
+ const startWidth = columnSizing.widths.get(columnIndex) ?? cell.getBoundingClientRect().width;
5958
+ handle.setPointerCapture(event.pointerId);
5959
+ const onMove = (moveEvent) => {
5960
+ const nextWidth = Math.max(48, Math.min(720, Math.round(startWidth + moveEvent.clientX - startX)));
5961
+ columnSizing.widths.set(columnIndex, nextWidth);
5962
+ updateRenderedColumnWidth(cell, columnIndex, nextWidth);
5963
+ };
5964
+ const onEnd = () => {
5965
+ handle.removeEventListener("pointermove", onMove);
5966
+ handle.removeEventListener("pointerup", onEnd);
5967
+ handle.removeEventListener("pointercancel", onEnd);
5968
+ };
5969
+ handle.addEventListener("pointermove", onMove);
5970
+ handle.addEventListener("pointerup", onEnd);
5971
+ handle.addEventListener("pointercancel", onEnd);
5972
+ });
5973
+ cell.append(handle);
5974
+ }
5975
+ function updateRenderedColumnWidth(cell, columnIndex, width) {
5976
+ const table = cell.closest("table");
5977
+ if (!table) {
5978
+ return;
5979
+ }
5980
+ const column = Array.from(table.querySelectorAll("col")).find(
5981
+ (col) => col.dataset.columnIndex === String(columnIndex)
5982
+ );
5983
+ if (column) {
5984
+ column.style.width = `${width}px`;
5985
+ }
5986
+ const tableWidth = Array.from(table.querySelectorAll("col")).reduce((sum, col) => {
5987
+ const parsed = Number.parseFloat(col.style.width);
5988
+ return sum + (Number.isFinite(parsed) ? parsed : 0);
5989
+ }, 0);
5990
+ if (tableWidth > 0) {
5991
+ table.style.width = `${Math.round(tableWidth)}px`;
5992
+ }
5993
+ }
5994
+ function createSheetMergePlan(merges, rowStart, rowEnd, columnStart, columnEnd) {
5995
+ const anchors = /* @__PURE__ */ new Map();
5996
+ const covered = /* @__PURE__ */ new Set();
5997
+ const encode = (row, column) => `${row}:${column}`;
5998
+ for (const merge of merges) {
5999
+ if (merge.e.r < rowStart || merge.s.r > rowEnd || merge.e.c < columnStart || merge.s.c > columnEnd) {
6000
+ continue;
6001
+ }
6002
+ const visibleStartRow = Math.max(merge.s.r, rowStart);
6003
+ const visibleEndRow = Math.min(merge.e.r, rowEnd);
6004
+ const visibleStartColumn = Math.max(merge.s.c, columnStart);
6005
+ const visibleEndColumn = Math.min(merge.e.c, columnEnd);
6006
+ const anchor = encode(visibleStartRow, visibleStartColumn);
6007
+ anchors.set(anchor, {
6008
+ rowspan: visibleEndRow - visibleStartRow + 1,
6009
+ colspan: visibleEndColumn - visibleStartColumn + 1,
6010
+ sourceRow: merge.s.r,
6011
+ sourceColumn: merge.s.c
6012
+ });
6013
+ for (let rowIndex = visibleStartRow; rowIndex <= visibleEndRow; rowIndex += 1) {
6014
+ for (let columnIndex = visibleStartColumn; columnIndex <= visibleEndColumn; columnIndex += 1) {
6015
+ const address = encode(rowIndex, columnIndex);
6016
+ if (address !== anchor) {
6017
+ covered.add(address);
6018
+ }
6019
+ }
6020
+ }
6021
+ }
6022
+ return { anchors, covered };
6023
+ }
6024
+ function getSheetColumnWidth(column) {
6025
+ if (column?.hidden) {
6026
+ return 0;
6027
+ }
6028
+ const width = column?.wpx || (column?.wch ? column.wch * 7 + 5 : void 0) || (column?.width ? column.width * 7 : void 0) || 96;
6029
+ return Math.max(28, Math.min(360, Math.round(width)));
6030
+ }
6031
+ function getSheetRowHeight(row) {
6032
+ if (row?.hidden) {
6033
+ return 0;
6034
+ }
6035
+ const height = row?.hpx || (row?.hpt ? row.hpt * 1.333 : void 0);
6036
+ return height ? Math.max(18, Math.min(260, Math.round(height))) : void 0;
6037
+ }
6038
+ function applyWorkbookCellStyle(cell, sourceCell) {
6039
+ const style = sourceCell?.s;
6040
+ if (!style) {
6041
+ return;
6042
+ }
6043
+ const fill = readWorkbookColor(style.fgColor || style.fill?.fgColor);
6044
+ if (fill && style.patternType !== "none") {
6045
+ cell.style.backgroundColor = fill;
6046
+ }
6047
+ const font = style.font;
6048
+ if (font) {
6049
+ if (font.bold) {
6050
+ cell.style.fontWeight = "700";
6051
+ }
6052
+ if (font.italic) {
6053
+ cell.style.fontStyle = "italic";
6054
+ }
6055
+ if (font.sz) {
6056
+ cell.style.fontSize = `${Math.max(9, Math.min(24, Number(font.sz)))}pt`;
6057
+ }
6058
+ const fontColor = readWorkbookColor(font.color);
6059
+ if (fontColor) {
6060
+ cell.style.color = fontColor;
6061
+ }
6062
+ }
6063
+ const alignment = style.alignment;
6064
+ if (alignment) {
6065
+ const horizontal = normalizeSheetHorizontalAlign(alignment.horizontal);
6066
+ if (horizontal) {
6067
+ cell.style.textAlign = horizontal;
6068
+ }
6069
+ const vertical = normalizeSheetVerticalAlign(alignment.vertical);
6070
+ if (vertical) {
6071
+ cell.style.verticalAlign = vertical;
6072
+ }
6073
+ if (alignment.wrapText) {
6074
+ cell.classList.add("ofv-cell-multiline");
6075
+ }
6076
+ }
6077
+ }
6078
+ function readWorkbookColor(color) {
6079
+ if (!color?.rgb) {
6080
+ return void 0;
6081
+ }
6082
+ const rgb = color.rgb.length === 8 ? color.rgb.slice(2) : color.rgb;
6083
+ return /^[\da-f]{6}$/i.test(rgb) ? `#${rgb}` : void 0;
6084
+ }
6085
+ function normalizeSheetHorizontalAlign(value) {
6086
+ if (value === "center" || value === "right" || value === "left" || value === "justify") {
6087
+ return value;
6088
+ }
6089
+ return void 0;
6090
+ }
6091
+ function normalizeSheetVerticalAlign(value) {
6092
+ if (value === "top" || value === "middle" || value === "bottom") {
6093
+ return value;
6094
+ }
6095
+ return void 0;
6096
+ }
6097
+ function createParsedSheetTable(sheet, sheetIndex, viewport, columnSizing, rerender) {
5756
6098
  const table = document.createElement("table");
5757
6099
  table.id = `ofv-sheet-${sheetIndex + 1}`;
5758
6100
  const formulaMap = new Map(sheet.formulas.map((item) => [item.address, item.formula]));
5759
6101
  const rowEnd = Math.min(viewport.rowStart + SHEET_WINDOW_ROWS, sheet.rows.length);
6102
+ const columnEnd = Math.min(viewport.columnStart + SHEET_WINDOW_COLUMNS, viewport.columnCount);
6103
+ const colGroup = document.createElement("colgroup");
6104
+ let tableWidth = 0;
6105
+ for (let columnIndex = viewport.columnStart; columnIndex < columnEnd; columnIndex += 1) {
6106
+ const width = columnSizing.widths.get(columnIndex) ?? 112;
6107
+ const col = document.createElement("col");
6108
+ col.dataset.columnIndex = String(columnIndex);
6109
+ col.style.width = `${width}px`;
6110
+ tableWidth += width;
6111
+ colGroup.append(col);
6112
+ }
6113
+ table.style.width = `${tableWidth}px`;
6114
+ table.append(colGroup);
5760
6115
  for (let rowIndex = viewport.rowStart; rowIndex < rowEnd; rowIndex += 1) {
5761
6116
  const sourceRow = sheet.rows[rowIndex] || [];
5762
6117
  const row = document.createElement("tr");
5763
- const columnEnd = Math.min(viewport.columnStart + SHEET_WINDOW_COLUMNS, viewport.columnCount);
5764
6118
  for (let columnIndex = viewport.columnStart; columnIndex < columnEnd; columnIndex += 1) {
5765
6119
  const value = sourceRow[columnIndex] || "";
5766
6120
  const cell = document.createElement(rowIndex === 0 ? "th" : "td");
@@ -5775,6 +6129,10 @@ function createParsedSheetTable(sheet, sheetIndex, viewport) {
5775
6129
  cell.classList.add("ofv-cell-formula");
5776
6130
  cell.title = formula;
5777
6131
  }
6132
+ if (value.includes("\n")) {
6133
+ cell.classList.add("ofv-cell-multiline");
6134
+ }
6135
+ appendColumnResizeHandle(cell, columnIndex, columnSizing);
5778
6136
  row.append(cell);
5779
6137
  }
5780
6138
  table.append(row);
@@ -6712,7 +7070,7 @@ function ofdPlugin() {
6712
7070
  try {
6713
7071
  zip = await JSZip4.loadAsync(await readArrayBuffer(ctx.file));
6714
7072
  } catch (error) {
6715
- panel.append(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
7073
+ panel.append(createOfdFailure(ctx.file, url, error));
6716
7074
  return {
6717
7075
  destroy() {
6718
7076
  panel.remove();
@@ -6729,7 +7087,7 @@ function ofdPlugin() {
6729
7087
  textFragments.push(...matches);
6730
7088
  }
6731
7089
  } catch (error) {
6732
- panel.append(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
7090
+ panel.append(createOfdFailure(ctx.file, url, error));
6733
7091
  return {
6734
7092
  destroy() {
6735
7093
  panel.remove();
@@ -7354,6 +7712,16 @@ function finiteNumber2(value, fallback) {
7354
7712
  function formatOfdCssNumber(value) {
7355
7713
  return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
7356
7714
  }
7715
+ function createOfdFailure(file, url, error) {
7716
+ if (isEncryptedError(error)) {
7717
+ return createEncryptedFallback(file, url, {
7718
+ title: "OFD \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
7719
+ 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",
7720
+ action: "\u4E0B\u8F7D OFD"
7721
+ });
7722
+ }
7723
+ return createOfdFallback(file.name, url, normalizeOfdError(error));
7724
+ }
7357
7725
  function createOfdFallback(fileName, url, detail) {
7358
7726
  const fallback = document.createElement("div");
7359
7727
  fallback.className = "ofv-fallback";
@@ -7422,7 +7790,9 @@ function archivePlugin() {
7422
7790
  try {
7423
7791
  if (ext === "zip") {
7424
7792
  try {
7425
- const zip = await JSZip5.loadAsync(await readArrayBuffer(ctx.file));
7793
+ const zip = await JSZip5.loadAsync(await readArrayBuffer(ctx.file), {
7794
+ decodeFileName: decodeZipFileName
7795
+ });
7426
7796
  archiveEntries = Object.values(zip.files).map((entry) => ({
7427
7797
  name: entry.name,
7428
7798
  unsafeName: entry.unsafeOriginalName,
@@ -7431,7 +7801,7 @@ function archivePlugin() {
7431
7801
  read: () => entry.async("arraybuffer")
7432
7802
  }));
7433
7803
  } catch (zipErr) {
7434
- if (zipErr.message && (zipErr.message.includes("encrypted") || zipErr.message.includes("password") || zipErr.message.includes("protected"))) {
7804
+ if (isEncryptedError(zipErr)) {
7435
7805
  isEncrypted = true;
7436
7806
  } else {
7437
7807
  throw zipErr;
@@ -7464,17 +7834,11 @@ function archivePlugin() {
7464
7834
  parseError = `\u538B\u7F29\u5305\u89E3\u6790\u5931\u8D25\uFF1A${err.message || err}`;
7465
7835
  }
7466
7836
  if (isEncrypted) {
7467
- const fallback = document.createElement("div");
7468
- fallback.className = "ofv-fallback";
7469
- const title = document.createElement("strong");
7470
- title.textContent = "\u8BE5\u538B\u7F29\u5305\u5DF2\u88AB\u52A0\u5BC6\u4FDD\u62A4";
7471
- const meta = document.createElement("span");
7472
- meta.textContent = "\u4E3A\u4E86\u60A8\u7684\u6570\u636E\u5B89\u5168\uFF0C\u672C\u9884\u89C8\u5668\u4E0D\u652F\u6301\u76F4\u63A5\u5728\u7EBF\u89E3\u5BC6\u3002\u8BF7\u4E0B\u8F7D\u6587\u4EF6\u540E\u5728\u672C\u5730\u8F93\u5165\u5BC6\u7801\u89E3\u538B\u3002";
7473
- const download = document.createElement("a");
7474
- download.href = url;
7475
- download.download = ctx.file.name;
7476
- download.textContent = "\u4E0B\u8F7D\u6587\u4EF6";
7477
- fallback.append(title, meta, download);
7837
+ const fallback = createEncryptedFallback(ctx.file, url, {
7838
+ title: "\u538B\u7F29\u5305\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
7839
+ 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",
7840
+ action: "\u4E0B\u8F7D\u538B\u7F29\u5305"
7841
+ });
7478
7842
  panel.append(fallback);
7479
7843
  ctx.viewport.classList.add("ofv-center");
7480
7844
  return {
@@ -7723,6 +8087,28 @@ async function findSubPreviewPlugin(plugins, file) {
7723
8087
  }
7724
8088
  return fallbackPlugin();
7725
8089
  }
8090
+ function decodeZipFileName(bytes) {
8091
+ const data = Array.isArray(bytes) ? Uint8Array.from(bytes.map((value) => value.charCodeAt(0) & 255)) : bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);
8092
+ const utf8 = decodeZipNameWith(data, "utf-8", true);
8093
+ if (utf8 && !looksMojibake(utf8)) {
8094
+ return utf8;
8095
+ }
8096
+ const gb18030 = decodeZipNameWith(data, "gb18030", false) || decodeZipNameWith(data, "gbk", false);
8097
+ if (gb18030 && !looksMojibake(gb18030)) {
8098
+ return gb18030;
8099
+ }
8100
+ return utf8 || new TextDecoder("latin1").decode(data);
8101
+ }
8102
+ function decodeZipNameWith(bytes, encoding, fatal) {
8103
+ try {
8104
+ return new TextDecoder(encoding, { fatal }).decode(bytes);
8105
+ } catch {
8106
+ return void 0;
8107
+ }
8108
+ }
8109
+ function looksMojibake(value) {
8110
+ return /[\uFFFDÃÂÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß]/.test(value);
8111
+ }
7726
8112
  function createInlineError(titleText, detailText) {
7727
8113
  const fallback = document.createElement("div");
7728
8114
  fallback.className = "ofv-fallback";
@@ -8144,13 +8530,14 @@ function emailPlugin() {
8144
8530
  } else {
8145
8531
  const PostalMime = (await import("postal-mime")).default;
8146
8532
  const parser = new PostalMime();
8147
- let rawText = await readTextFile(ctx.file);
8533
+ let rawSource = await readArrayBuffer(ctx.file);
8148
8534
  if (ext === "mbox") {
8535
+ let rawText = await readTextFile(ctx.file);
8149
8536
  const messages = splitMboxMessages(rawText);
8150
8537
  mboxSummary = messages.map(summarizeMboxMessage);
8151
- rawText = messages[0] || rawText;
8538
+ rawSource = messages[0] || rawText;
8152
8539
  }
8153
- const parsed = await parser.parse(rawText);
8540
+ const parsed = await parser.parse(rawSource);
8154
8541
  const from = parsed.from ? `${parsed.from.name || ""} <${parsed.from.address || ""}>`.trim() : "";
8155
8542
  const to = Array.isArray(parsed.to) ? parsed.to.map((t) => `${t.name || ""} <${t.address || ""}>`.trim()).join("; ") : "";
8156
8543
  const cc = Array.isArray(parsed.cc) ? parsed.cc.map((c) => `${c.name || ""} <${c.address || ""}>`.trim()).join("; ") : "";
@@ -8220,13 +8607,17 @@ function emailPlugin() {
8220
8607
  const sanitizedHtml = sanitizeEmailHtml(html);
8221
8608
  const iframe = document.createElement("iframe");
8222
8609
  iframe.className = "ofv-email-body-iframe";
8223
- iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
8610
+ iframe.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
8224
8611
  iframe.style.cssText = "width: 100%; border: none; background: #fff; min-height: 200px;";
8225
- bodySection.append(iframe);
8226
- iframe.onload = () => {
8612
+ let renderedHtmlBody = false;
8613
+ const renderHtmlBody = () => {
8614
+ if (renderedHtmlBody) {
8615
+ return;
8616
+ }
8227
8617
  try {
8228
8618
  const idoc = iframe.contentDocument || iframe.contentWindow?.document;
8229
8619
  if (idoc) {
8620
+ renderedHtmlBody = true;
8230
8621
  idoc.open();
8231
8622
  idoc.write(`
8232
8623
  <!doctype html>
@@ -8277,6 +8668,9 @@ function emailPlugin() {
8277
8668
  console.error("Failed to write html body to email iframe:", err);
8278
8669
  }
8279
8670
  };
8671
+ iframe.addEventListener("load", renderHtmlBody, { once: true });
8672
+ bodySection.append(iframe);
8673
+ renderHtmlBody();
8280
8674
  } else {
8281
8675
  const pre = document.createElement("pre");
8282
8676
  pre.className = "ofv-text-block";
@@ -9798,112 +10192,726 @@ function decodeDrawioDiagram(value) {
9798
10192
  }
9799
10193
 
9800
10194
  // src/plugins/cad.ts
9801
- var cadExtensions = /* @__PURE__ */ new Set([
9802
- "dxf",
9803
- "dwg",
9804
- "dwf",
9805
- "step",
9806
- "stp",
9807
- "iges",
9808
- "igs",
9809
- "ifc",
9810
- "sat",
9811
- "sab",
9812
- "x_t",
9813
- "x_b",
9814
- "3dm",
9815
- "skp",
9816
- "sldprt",
9817
- "sldasm"
9818
- ]);
9819
- var cadMimeTypes = /* @__PURE__ */ new Set([
9820
- "application/acad",
9821
- "application/dxf",
9822
- "application/x-dxf",
9823
- "image/vnd.dxf",
9824
- "model/vnd.dwf",
9825
- "model/step",
9826
- "application/step",
9827
- "application/iges",
9828
- "application/x-step",
9829
- "application/sat",
9830
- "application/sab",
9831
- "application/x-parasolid",
9832
- "model/vnd.3dm",
9833
- "application/vnd.sketchup.skp",
9834
- "application/sldworks"
9835
- ]);
9836
- var cadMimeFormatMap = {
9837
- "application/acad": "dwg",
9838
- "application/dxf": "dxf",
9839
- "application/x-dxf": "dxf",
9840
- "image/vnd.dxf": "dxf",
9841
- "model/vnd.dwf": "dwf",
9842
- "model/step": "step",
9843
- "application/step": "step",
9844
- "application/iges": "iges",
9845
- "application/x-step": "ifc",
9846
- "application/sat": "sat",
9847
- "application/sab": "sab",
9848
- "application/x-parasolid": "x_t",
9849
- "model/vnd.3dm": "3dm",
9850
- "application/vnd.sketchup.skp": "skp",
9851
- "application/sldworks": "sldprt"
9852
- };
9853
- function cadPlugin() {
9854
- return {
9855
- name: "cad",
9856
- match(file) {
9857
- return cadExtensions.has(file.extension) || cadMimeTypes.has(file.mimeType);
9858
- },
9859
- async render(ctx) {
9860
- const panel = createPanel("ofv-cad");
9861
- ctx.viewport.append(panel);
9862
- const extension = resolveFormat(ctx.file, cadMimeFormatMap);
9863
- if (extension === "step" || extension === "stp") {
9864
- renderStep(panel, await readTextFile(ctx.file), extension);
9865
- return { destroy: () => panel.remove() };
9866
- }
9867
- if (extension === "iges" || extension === "igs") {
9868
- renderIges(panel, await readTextFile(ctx.file), extension);
9869
- return { destroy: () => panel.remove() };
9870
- }
9871
- if (extension === "ifc") {
9872
- renderIfc(panel, await readTextFile(ctx.file));
9873
- return { destroy: () => panel.remove() };
9874
- }
9875
- if (extension === "dwg" || extension === "dwf") {
9876
- renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
9877
- return { destroy: () => panel.remove() };
10195
+ import pako3 from "pako";
10196
+
10197
+ // src/plugins/cad-dwg.ts
10198
+ var libreDwgPromise;
10199
+ var defaultLibreDwgWasmBaseUrl = "/vendor/libredwg-web";
10200
+ var minReadableDrawingHeight = 420;
10201
+ var svgNumberPattern = /-?\d*\.?\d+(?:e[-+]?\d+)?/gi;
10202
+ async function renderLibreDwgPreview(ctx, options = {}) {
10203
+ if (ctx.extension !== "dwg" || options.enabled === false) {
10204
+ return void 0;
10205
+ }
10206
+ const shell = document.createElement("div");
10207
+ shell.className = "ofv-dwg-preview";
10208
+ const status = document.createElement("div");
10209
+ status.className = "ofv-dwg-preview-status";
10210
+ status.textContent = "Loading DWG rendering engine...";
10211
+ shell.append(status);
10212
+ ctx.panel.append(shell);
10213
+ try {
10214
+ const { LibreDwg, Dwg_File_Type } = await loadLibreDwg();
10215
+ const libredwg = await LibreDwg.create(options.wasmBaseUrl || defaultLibreDwgWasmBaseUrl);
10216
+ const data = libredwg.dwg_read_data(ctx.arrayBuffer, Dwg_File_Type.DWG);
10217
+ if (!data) {
10218
+ throw new Error("DWG parser did not return drawing data.");
10219
+ }
10220
+ let svg = "";
10221
+ let stats;
10222
+ let thumbnailUrl;
10223
+ try {
10224
+ thumbnailUrl = createDwgThumbnailUrl(readDwgThumbnail(libredwg, data));
10225
+ try {
10226
+ const result = libredwg.convertEx(data);
10227
+ const database = result.database;
10228
+ stats = createDwgPreviewStats(database, result.stats.unknownEntityCount, Boolean(thumbnailUrl));
10229
+ svg = libredwg.dwg_to_svg(database);
10230
+ } catch (error) {
10231
+ if (thumbnailUrl) {
10232
+ const fallbackThumbnailUrl = thumbnailUrl;
10233
+ status.replaceChildren(createDwgThumbnailFallbackStatus(ctx.fileName, error));
10234
+ shell.append(createDwgThumbnailPreview(fallbackThumbnailUrl, ctx.fileName));
10235
+ return {
10236
+ destroy() {
10237
+ URL.revokeObjectURL(fallbackThumbnailUrl);
10238
+ shell.remove();
10239
+ }
10240
+ };
10241
+ }
10242
+ throw error;
9878
10243
  }
9879
- if (extension !== "dxf") {
9880
- const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
9881
- 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`);
9882
- panel.append(section);
9883
- return { destroy: () => panel.remove() };
10244
+ } finally {
10245
+ libredwg.dwg_free(data);
10246
+ }
10247
+ if (!svg || !/<svg[\s>]/i.test(svg)) {
10248
+ if (thumbnailUrl) {
10249
+ const fallbackThumbnailUrl = thumbnailUrl;
10250
+ status.replaceChildren(createDwgThumbnailFallbackStatus(ctx.fileName, "DWG parser finished but did not produce SVG output."));
10251
+ shell.append(createDwgThumbnailPreview(fallbackThumbnailUrl, ctx.fileName));
10252
+ return {
10253
+ destroy() {
10254
+ URL.revokeObjectURL(fallbackThumbnailUrl);
10255
+ shell.remove();
10256
+ }
10257
+ };
9884
10258
  }
9885
- const dxf = await readTextFile(ctx.file);
9886
- const viewer = renderDxf(panel, dxf, ctx);
10259
+ throw new Error("DWG parser finished but did not produce SVG output.");
10260
+ }
10261
+ const doc = new DOMParser().parseFromString(svg, "image/svg+xml");
10262
+ const svgElement = doc.documentElement;
10263
+ if (!(svgElement instanceof SVGElement) || svgElement.nodeName.toLowerCase() !== "svg" || svgElement.querySelector("parsererror")) {
10264
+ throw new Error("DWG SVG output is invalid.");
10265
+ }
10266
+ svgElement.classList.add("ofv-dwg-preview-svg");
10267
+ svgElement.setAttribute("role", "img");
10268
+ svgElement.setAttribute("aria-label", ctx.fileName);
10269
+ normalizeDwgSvg(svgElement);
10270
+ const reliability = assessDwgSvgReliability(svgElement);
10271
+ status.replaceChildren(createDwgStatusTitle(ctx.fileName, stats, reliability));
10272
+ if (!reliability.isReliable && thumbnailUrl) {
10273
+ shell.append(createDwgThumbnailPreview(thumbnailUrl, ctx.fileName));
9887
10274
  return {
9888
- canCommand(command) {
9889
- return viewer.canCommand(command);
9890
- },
9891
- command(command) {
9892
- return viewer.command(command);
9893
- },
9894
10275
  destroy() {
9895
- panel.remove();
10276
+ URL.revokeObjectURL(thumbnailUrl);
10277
+ shell.remove();
9896
10278
  }
9897
10279
  };
9898
10280
  }
9899
- };
9900
- }
9901
- function renderStep(panel, text, extension) {
9902
- const records = parseStepRecords(text);
9903
- const typeCounts = countBy(records.map((record) => record.type));
9904
- const section = createSection(`${extension.toUpperCase()} \u7ED3\u6784\u9884\u89C8`);
9905
- const note = document.createElement("p");
9906
- 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";
10281
+ const drawing = createDwgDrawingViewport(svgElement);
10282
+ if (thumbnailUrl) {
10283
+ shell.append(createDwgThumbnailPanel(thumbnailUrl));
10284
+ }
10285
+ shell.append(drawing.frame);
10286
+ return {
10287
+ resize() {
10288
+ drawing.update();
10289
+ },
10290
+ canCommand(command) {
10291
+ return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
10292
+ },
10293
+ command(command) {
10294
+ if (command === "zoom-in") {
10295
+ drawing.setZoom(drawing.zoom * 1.18);
10296
+ return true;
10297
+ }
10298
+ if (command === "zoom-out") {
10299
+ drawing.setZoom(drawing.zoom / 1.18);
10300
+ return true;
10301
+ }
10302
+ if (command === "zoom-reset") {
10303
+ drawing.setZoom(1);
10304
+ return true;
10305
+ }
10306
+ return false;
10307
+ },
10308
+ destroy() {
10309
+ if (thumbnailUrl) {
10310
+ URL.revokeObjectURL(thumbnailUrl);
10311
+ }
10312
+ shell.remove();
10313
+ }
10314
+ };
10315
+ } catch (error) {
10316
+ shell.remove();
10317
+ console.warn("DWG LibreDWG preview failed, falling back to metadata preview:", error);
10318
+ return void 0;
10319
+ }
10320
+ }
10321
+ function loadLibreDwg() {
10322
+ libreDwgPromise ||= import("@mlightcad/libredwg-web");
10323
+ return libreDwgPromise;
10324
+ }
10325
+ function createDwgStatusTitle(fileName, stats, reliability) {
10326
+ const wrapper = document.createElement("span");
10327
+ const title = document.createElement("strong");
10328
+ const note = document.createElement("small");
10329
+ title.textContent = reliability.isReliable ? `\u5B9E\u9A8C\u6027 DWG \u6A21\u578B\u7A7A\u95F4\u9884\u89C8 \xB7 ${fileName}` : `DWG \u5185\u7F6E\u9884\u89C8\u56FE \xB7 ${fileName}`;
10330
+ note.textContent = [
10331
+ `${stats.entityCount.toLocaleString()} \u4E2A\u5B9E\u4F53`,
10332
+ `${stats.visibleLayerCount}/${stats.layerCount} \u4E2A\u53EF\u89C1\u56FE\u5C42`,
10333
+ `${stats.layoutCount} \u4E2A\u5E03\u5C40`,
10334
+ stats.paperSpaceEntityCount ? `${stats.paperSpaceEntityCount.toLocaleString()} \u4E2A\u56FE\u7EB8\u7A7A\u95F4\u5B9E\u4F53` : "\u6A21\u578B\u7A7A\u95F4\u7EBF\u7A3F",
10335
+ stats.unknownEntityCount ? `${stats.unknownEntityCount} \u4E2A\u672A\u77E5\u5B9E\u4F53` : "\u5B9E\u4F53\u89E3\u6790\u5B8C\u6574",
10336
+ stats.hasThumbnail ? "\u5305\u542B\u5185\u7F6E\u7F29\u7565\u56FE" : "\u65E0\u5185\u7F6E\u7F29\u7565\u56FE"
10337
+ ].join(" \xB7 ");
10338
+ const warning = document.createElement("small");
10339
+ 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 ?? ""}`;
10340
+ wrapper.append(title, note, warning);
10341
+ return wrapper;
10342
+ }
10343
+ function createDwgThumbnailFallbackStatus(fileName, error) {
10344
+ const wrapper = document.createElement("span");
10345
+ const title = document.createElement("strong");
10346
+ title.textContent = `DWG \u5185\u7F6E\u9884\u89C8\u56FE \xB7 ${fileName}`;
10347
+ const note = document.createElement("small");
10348
+ 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";
10349
+ const detail = document.createElement("small");
10350
+ detail.textContent = error instanceof Error ? error.message : String(error || "\u672A\u77E5\u89E3\u6790\u9519\u8BEF");
10351
+ wrapper.append(title, note, detail);
10352
+ return wrapper;
10353
+ }
10354
+ function createDwgPreviewStats(database, unknownEntityCount, hasThumbnail) {
10355
+ const layers = database.tables.LAYER.entries;
10356
+ return {
10357
+ entityCount: database.entities.length,
10358
+ layerCount: layers.length,
10359
+ layoutCount: database.objects.LAYOUT.length,
10360
+ unknownEntityCount,
10361
+ visibleLayerCount: layers.filter((layer) => !layer.off && !layer.frozen).length,
10362
+ paperSpaceEntityCount: database.entities.filter((entity) => entity.isInPaperSpace).length,
10363
+ hasThumbnail
10364
+ };
10365
+ }
10366
+ function readDwgThumbnail(libredwg, data) {
10367
+ try {
10368
+ const thumbnail = libredwg.dwg_bmp(data);
10369
+ if (!thumbnail?.data?.length) {
10370
+ return void 0;
10371
+ }
10372
+ return thumbnail;
10373
+ } catch {
10374
+ return void 0;
10375
+ }
10376
+ }
10377
+ function createDwgThumbnailUrl(thumbnail) {
10378
+ if (!thumbnail) {
10379
+ return void 0;
10380
+ }
10381
+ if (thumbnail.type === 6) {
10382
+ return URL.createObjectURL(new Blob([toArrayBuffer(thumbnail.data)], { type: "image/png" }));
10383
+ }
10384
+ if (thumbnail.type === 2) {
10385
+ return URL.createObjectURL(new Blob([toArrayBuffer(createBmpFileBytes(thumbnail.data))], { type: "image/bmp" }));
10386
+ }
10387
+ return void 0;
10388
+ }
10389
+ function toArrayBuffer(bytes) {
10390
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
10391
+ }
10392
+ function createBmpFileBytes(dibBytes) {
10393
+ const view3 = new DataView(dibBytes.buffer, dibBytes.byteOffset, dibBytes.byteLength);
10394
+ const headerSize = view3.getUint32(0, true);
10395
+ const bitCount = view3.getUint16(14, true);
10396
+ const paletteBytes = bitCount <= 8 ? 2 ** bitCount * 4 : 0;
10397
+ const pixelOffset = 14 + headerSize + paletteBytes;
10398
+ const bytes = new Uint8Array(14 + dibBytes.byteLength);
10399
+ bytes[0] = 66;
10400
+ bytes[1] = 77;
10401
+ const fileView = new DataView(bytes.buffer);
10402
+ fileView.setUint32(2, bytes.byteLength, true);
10403
+ fileView.setUint32(10, pixelOffset, true);
10404
+ bytes.set(dibBytes, 14);
10405
+ return bytes;
10406
+ }
10407
+ function createDwgThumbnailPanel(thumbnailUrl) {
10408
+ const panel = document.createElement("figure");
10409
+ panel.className = "ofv-dwg-thumbnail";
10410
+ const image = document.createElement("img");
10411
+ image.src = thumbnailUrl;
10412
+ image.alt = "DWG \u6587\u4EF6\u5185\u7F6E\u7F29\u7565\u56FE";
10413
+ const caption = document.createElement("figcaption");
10414
+ caption.textContent = "\u6587\u4EF6\u5185\u7F6E\u7F29\u7565\u56FE";
10415
+ panel.append(image, caption);
10416
+ return panel;
10417
+ }
10418
+ function createDwgThumbnailPreview(thumbnailUrl, fileName) {
10419
+ const figure = document.createElement("figure");
10420
+ figure.className = "ofv-dwg-thumbnail-preview";
10421
+ const image = document.createElement("img");
10422
+ image.src = thumbnailUrl;
10423
+ image.alt = `${fileName} \u5185\u7F6E\u9884\u89C8\u56FE`;
10424
+ const caption = document.createElement("figcaption");
10425
+ 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";
10426
+ figure.append(image, caption);
10427
+ return figure;
10428
+ }
10429
+ function normalizeDwgSvg(svgElement) {
10430
+ svgElement.setAttribute("preserveAspectRatio", "xMidYMid meet");
10431
+ removeInheritedDwgFills(svgElement);
10432
+ focusDwgSvgOnMainDrawing(svgElement);
10433
+ const style = document.createElementNS("http://www.w3.org/2000/svg", "style");
10434
+ style.textContent = `
10435
+ .ofv-dwg-preview-svg { background: #020617; }
10436
+ .ofv-dwg-preview-svg g {
10437
+ fill: none !important;
10438
+ }
10439
+ .ofv-dwg-preview-svg line,
10440
+ .ofv-dwg-preview-svg path,
10441
+ .ofv-dwg-preview-svg polyline,
10442
+ .ofv-dwg-preview-svg polygon,
10443
+ .ofv-dwg-preview-svg circle,
10444
+ .ofv-dwg-preview-svg ellipse,
10445
+ .ofv-dwg-preview-svg rect {
10446
+ fill: none !important;
10447
+ vector-effect: non-scaling-stroke;
10448
+ stroke-width: 0.7px !important;
10449
+ stroke-linecap: round;
10450
+ stroke-linejoin: round;
10451
+ }
10452
+ .ofv-dwg-preview-svg [stroke="rgb(0,255,0)"] {
10453
+ stroke: #34d399 !important;
10454
+ stroke-opacity: 0.58 !important;
10455
+ }
10456
+ .ofv-dwg-preview-svg text {
10457
+ vector-effect: non-scaling-stroke;
10458
+ stroke-width: 0 !important;
10459
+ fill: currentColor !important;
10460
+ }
10461
+ `;
10462
+ svgElement.prepend(style);
10463
+ }
10464
+ function removeInheritedDwgFills(svgElement) {
10465
+ const shapeSelector = "line,path,polyline,polygon,circle,ellipse,rect";
10466
+ for (const group of svgElement.querySelectorAll("g[fill]")) {
10467
+ group.setAttribute("fill", "none");
10468
+ }
10469
+ for (const styledElement of svgElement.querySelectorAll("[style]")) {
10470
+ styledElement.style.fill = "none";
10471
+ }
10472
+ for (const shape of svgElement.querySelectorAll(shapeSelector)) {
10473
+ shape.setAttribute("fill", "none");
10474
+ }
10475
+ }
10476
+ function assessDwgSvgReliability(svgElement) {
10477
+ const bounds = readSvgViewBox(svgElement);
10478
+ if (!bounds) {
10479
+ return { isReliable: true };
10480
+ }
10481
+ const largePathCount = countLargeSvgPaths(svgElement, bounds);
10482
+ const totalPathCount = svgElement.querySelectorAll("path").length;
10483
+ if (largePathCount >= 24 && totalPathCount > 0 && largePathCount / totalPathCount > 0.08) {
10484
+ return {
10485
+ isReliable: false,
10486
+ 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"
10487
+ };
10488
+ }
10489
+ return { isReliable: true };
10490
+ }
10491
+ function countLargeSvgPaths(svgElement, bounds) {
10492
+ let count = 0;
10493
+ const viewportArea = bounds.width * bounds.height;
10494
+ if (!Number.isFinite(viewportArea) || viewportArea <= 0) {
10495
+ return count;
10496
+ }
10497
+ for (const path of svgElement.querySelectorAll("path")) {
10498
+ if (path.closest("defs")) {
10499
+ continue;
10500
+ }
10501
+ const pathBounds = estimatePathBounds(path);
10502
+ if (!pathBounds) {
10503
+ continue;
10504
+ }
10505
+ const pathArea = pathBounds.width * pathBounds.height;
10506
+ const crossesViewport = pathBounds.minX <= bounds.minX + bounds.width * 0.02 || pathBounds.minY <= bounds.minY + bounds.height * 0.02;
10507
+ if (pathArea > viewportArea * 0.28 || crossesViewport && pathArea > viewportArea * 0.12) {
10508
+ count += 1;
10509
+ }
10510
+ }
10511
+ return count;
10512
+ }
10513
+ function estimatePathBounds(path) {
10514
+ const numbers = readNumbers(path.getAttribute("d") ?? "");
10515
+ if (numbers.length < 4) {
10516
+ return void 0;
10517
+ }
10518
+ let minX = Number.POSITIVE_INFINITY;
10519
+ let minY = Number.POSITIVE_INFINITY;
10520
+ let maxX = Number.NEGATIVE_INFINITY;
10521
+ let maxY = Number.NEGATIVE_INFINITY;
10522
+ for (let index = 0; index + 1 < numbers.length; index += 2) {
10523
+ const x = numbers[index];
10524
+ const y = numbers[index + 1];
10525
+ if (!Number.isFinite(x) || !Number.isFinite(y) || Math.abs(x) > 1e9 || Math.abs(y) > 1e9) {
10526
+ continue;
10527
+ }
10528
+ minX = Math.min(minX, x);
10529
+ minY = Math.min(minY, y);
10530
+ maxX = Math.max(maxX, x);
10531
+ maxY = Math.max(maxY, y);
10532
+ }
10533
+ if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(maxX) || !Number.isFinite(maxY)) {
10534
+ return void 0;
10535
+ }
10536
+ return { minX, minY, width: maxX - minX, height: maxY - minY };
10537
+ }
10538
+ function focusDwgSvgOnMainDrawing(svgElement) {
10539
+ const originalBounds = readSvgViewBox(svgElement);
10540
+ const mainBounds = estimateMainDrawingBounds(svgElement);
10541
+ if (!originalBounds || !mainBounds || !shouldUseMainDrawingBounds(originalBounds, mainBounds)) {
10542
+ return;
10543
+ }
10544
+ const paddedBounds = padBounds(mainBounds, 0.05);
10545
+ svgElement.dataset.originalViewBox = formatViewBox(originalBounds);
10546
+ svgElement.dataset.focusViewBox = formatViewBox(paddedBounds);
10547
+ svgElement.setAttribute("viewBox", formatViewBox(paddedBounds));
10548
+ }
10549
+ function shouldUseMainDrawingBounds(original, candidate) {
10550
+ const originalAspectRatio = original.width / original.height;
10551
+ const candidateAspectRatio = candidate.width / candidate.height;
10552
+ const originalArea = original.width * original.height;
10553
+ const candidateArea = candidate.width * candidate.height;
10554
+ if (!Number.isFinite(originalArea) || !Number.isFinite(candidateArea) || candidateArea <= 0) {
10555
+ return false;
10556
+ }
10557
+ return originalAspectRatio > 8 || originalAspectRatio < 0.125 || candidateArea / originalArea < 0.55 || Math.abs(Math.log(originalAspectRatio / candidateAspectRatio)) > Math.log(4);
10558
+ }
10559
+ function estimateMainDrawingBounds(svgElement) {
10560
+ const points = [];
10561
+ const addPoint = (x, y) => {
10562
+ if (Number.isFinite(x) && Number.isFinite(y) && Math.abs(x) < 1e9 && Math.abs(y) < 1e9) {
10563
+ points.push([x, y]);
10564
+ }
10565
+ };
10566
+ for (const element of svgElement.querySelectorAll("line,path,polyline,polygon,circle,ellipse,rect,text,use")) {
10567
+ if (element.closest("defs")) {
10568
+ continue;
10569
+ }
10570
+ collectElementPoints(element, addPoint);
10571
+ }
10572
+ if (points.length < 16) {
10573
+ return void 0;
10574
+ }
10575
+ const xs = points.map(([x]) => x).sort((a, b) => a - b);
10576
+ const ys = points.map(([, y]) => y).sort((a, b) => a - b);
10577
+ const minX = quantile(xs, 5e-3);
10578
+ const maxX = quantile(xs, 0.995);
10579
+ const minY = quantile(ys, 5e-3);
10580
+ const maxY = quantile(ys, 0.995);
10581
+ if (!Number.isFinite(minX) || !Number.isFinite(maxX) || !Number.isFinite(minY) || !Number.isFinite(maxY)) {
10582
+ return void 0;
10583
+ }
10584
+ const width = maxX - minX;
10585
+ const height = maxY - minY;
10586
+ if (width <= 0 || height <= 0) {
10587
+ return void 0;
10588
+ }
10589
+ return { minX, minY, width, height };
10590
+ }
10591
+ function collectElementPoints(element, addPoint) {
10592
+ const tagName = element.tagName.toLowerCase();
10593
+ if (tagName === "line") {
10594
+ addPoint(readNumberAttribute(element, "x1"), readNumberAttribute(element, "y1"));
10595
+ addPoint(readNumberAttribute(element, "x2"), readNumberAttribute(element, "y2"));
10596
+ } else if (tagName === "circle") {
10597
+ const cx = readNumberAttribute(element, "cx");
10598
+ const cy = readNumberAttribute(element, "cy");
10599
+ const r = readNumberAttribute(element, "r");
10600
+ addPoint(cx - r, cy - r);
10601
+ addPoint(cx + r, cy + r);
10602
+ } else if (tagName === "ellipse") {
10603
+ const cx = readNumberAttribute(element, "cx");
10604
+ const cy = readNumberAttribute(element, "cy");
10605
+ const rx = readNumberAttribute(element, "rx");
10606
+ const ry = readNumberAttribute(element, "ry");
10607
+ addPoint(cx - rx, cy - ry);
10608
+ addPoint(cx + rx, cy + ry);
10609
+ } else if (tagName === "rect") {
10610
+ const x = readNumberAttribute(element, "x");
10611
+ const y = readNumberAttribute(element, "y");
10612
+ addPoint(x, y);
10613
+ addPoint(x + readNumberAttribute(element, "width"), y + readNumberAttribute(element, "height"));
10614
+ } else if (tagName === "text") {
10615
+ addPoint(readNumberAttribute(element, "x"), readNumberAttribute(element, "y"));
10616
+ } else if (tagName === "path") {
10617
+ collectNumberPairs(element.getAttribute("d"), addPoint);
10618
+ } else if (tagName === "polyline" || tagName === "polygon") {
10619
+ collectNumberPairs(element.getAttribute("points"), addPoint);
10620
+ }
10621
+ collectTranslatePoints(element.getAttribute("transform"), addPoint);
10622
+ }
10623
+ function collectNumberPairs(value, addPoint) {
10624
+ if (!value) {
10625
+ return;
10626
+ }
10627
+ const numbers = readNumbers(value);
10628
+ for (let index = 0; index + 1 < numbers.length; index += 2) {
10629
+ addPoint(numbers[index], numbers[index + 1]);
10630
+ }
10631
+ }
10632
+ function collectTranslatePoints(value, addPoint) {
10633
+ if (!value) {
10634
+ return;
10635
+ }
10636
+ const translatePattern = /translate\(\s*(-?\d*\.?\d+(?:e[-+]?\d+)?)(?:[\s,]+(-?\d*\.?\d+(?:e[-+]?\d+)?))?/gi;
10637
+ for (const match of value.matchAll(translatePattern)) {
10638
+ addPoint(Number(match[1]), Number(match[2] ?? 0));
10639
+ }
10640
+ }
10641
+ function readNumbers(value) {
10642
+ const matches = value.match(svgNumberPattern);
10643
+ return matches ? matches.map((item) => Number(item)).filter(Number.isFinite) : [];
10644
+ }
10645
+ function readNumberAttribute(element, name) {
10646
+ const value = Number(element.getAttribute(name) ?? 0);
10647
+ return Number.isFinite(value) ? value : 0;
10648
+ }
10649
+ function quantile(values, ratio) {
10650
+ const index = Math.max(0, Math.min(values.length - 1, Math.floor((values.length - 1) * ratio)));
10651
+ return values[index];
10652
+ }
10653
+ function padBounds(bounds, ratio) {
10654
+ const paddingX = bounds.width * ratio;
10655
+ const paddingY = bounds.height * ratio;
10656
+ return {
10657
+ minX: bounds.minX - paddingX,
10658
+ minY: bounds.minY - paddingY,
10659
+ width: bounds.width + paddingX * 2,
10660
+ height: bounds.height + paddingY * 2
10661
+ };
10662
+ }
10663
+ function formatViewBox(bounds) {
10664
+ return [bounds.minX, bounds.minY, bounds.width, bounds.height].map((value) => Number(value.toFixed(4))).join(" ");
10665
+ }
10666
+ function createDwgDrawingViewport(svgElement) {
10667
+ const frame = document.createElement("div");
10668
+ frame.className = "ofv-dwg-preview-frame";
10669
+ const importedSvg = document.importNode(svgElement, true);
10670
+ frame.append(importedSvg);
10671
+ const aspectRatio = readSvgAspectRatio(svgElement);
10672
+ let zoom = 1;
10673
+ const update = () => {
10674
+ const frameWidth = Math.max(frame.clientWidth, 1);
10675
+ const readableWidth = aspectRatio ? minReadableDrawingHeight * aspectRatio : frameWidth;
10676
+ const width = Math.max(frameWidth, readableWidth) * zoom;
10677
+ importedSvg.style.width = `${Math.round(width)}px`;
10678
+ importedSvg.style.minWidth = `${Math.round(width)}px`;
10679
+ };
10680
+ const setZoom = (nextZoom) => {
10681
+ zoom = Math.min(Math.max(nextZoom, 0.2), 8);
10682
+ update();
10683
+ };
10684
+ requestAnimationFrame(update);
10685
+ return {
10686
+ frame,
10687
+ get zoom() {
10688
+ return zoom;
10689
+ },
10690
+ setZoom,
10691
+ update
10692
+ };
10693
+ }
10694
+ function readSvgAspectRatio(svgElement) {
10695
+ const viewBox = readSvgViewBox(svgElement);
10696
+ return viewBox ? viewBox.width / viewBox.height : void 0;
10697
+ }
10698
+ function readSvgViewBox(svgElement) {
10699
+ const viewBox = svgElement.getAttribute("viewBox");
10700
+ if (!viewBox) {
10701
+ return void 0;
10702
+ }
10703
+ const [minX, minY, width, height] = viewBox.trim().split(/[\s,]+/).map((value) => Number(value));
10704
+ if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
10705
+ return void 0;
10706
+ }
10707
+ return { minX, minY, width, height };
10708
+ }
10709
+
10710
+ // src/plugins/cad.ts
10711
+ var cadExtensions = /* @__PURE__ */ new Set([
10712
+ "dxf",
10713
+ "dwg",
10714
+ "dwf",
10715
+ "step",
10716
+ "stp",
10717
+ "iges",
10718
+ "igs",
10719
+ "ifc",
10720
+ "sat",
10721
+ "sab",
10722
+ "x_t",
10723
+ "x_b",
10724
+ "3dm",
10725
+ "skp",
10726
+ "sldprt",
10727
+ "sldasm",
10728
+ "gds",
10729
+ "oas",
10730
+ "oasis"
10731
+ ]);
10732
+ var cadMimeTypes = /* @__PURE__ */ new Set([
10733
+ "application/acad",
10734
+ "application/dxf",
10735
+ "application/x-dxf",
10736
+ "image/vnd.dxf",
10737
+ "model/vnd.dwf",
10738
+ "model/step",
10739
+ "application/step",
10740
+ "application/iges",
10741
+ "application/x-step",
10742
+ "application/sat",
10743
+ "application/sab",
10744
+ "application/x-parasolid",
10745
+ "model/vnd.3dm",
10746
+ "application/vnd.sketchup.skp",
10747
+ "application/sldworks",
10748
+ "application/vnd.gds",
10749
+ "application/x-gdsii",
10750
+ "application/vnd.oasis.layout",
10751
+ "application/x-oasis-layout"
10752
+ ]);
10753
+ var cadMimeFormatMap = {
10754
+ "application/acad": "dwg",
10755
+ "application/dxf": "dxf",
10756
+ "application/x-dxf": "dxf",
10757
+ "image/vnd.dxf": "dxf",
10758
+ "model/vnd.dwf": "dwf",
10759
+ "model/step": "step",
10760
+ "application/step": "step",
10761
+ "application/iges": "iges",
10762
+ "application/x-step": "ifc",
10763
+ "application/sat": "sat",
10764
+ "application/sab": "sab",
10765
+ "application/x-parasolid": "x_t",
10766
+ "model/vnd.3dm": "3dm",
10767
+ "application/vnd.sketchup.skp": "skp",
10768
+ "application/sldworks": "sldprt",
10769
+ "application/vnd.gds": "gds",
10770
+ "application/x-gdsii": "gds",
10771
+ "application/vnd.oasis.layout": "oas",
10772
+ "application/x-oasis-layout": "oas"
10773
+ };
10774
+ function cadPlugin(options = {}) {
10775
+ return {
10776
+ name: "cad",
10777
+ match(file) {
10778
+ return cadExtensions.has(file.extension) || cadMimeTypes.has(file.mimeType);
10779
+ },
10780
+ async render(ctx) {
10781
+ const panel = createPanel("ofv-cad");
10782
+ ctx.viewport.append(panel);
10783
+ const extension = resolveFormat(ctx.file, cadMimeFormatMap);
10784
+ if (extension === "step" || extension === "stp") {
10785
+ renderStep(panel, await readTextFile(ctx.file), extension);
10786
+ return { destroy: () => panel.remove() };
10787
+ }
10788
+ if (extension === "iges" || extension === "igs") {
10789
+ renderIges(panel, await readTextFile(ctx.file), extension);
10790
+ return { destroy: () => panel.remove() };
10791
+ }
10792
+ if (extension === "ifc") {
10793
+ renderIfc(panel, await readTextFile(ctx.file));
10794
+ return { destroy: () => panel.remove() };
10795
+ }
10796
+ if (extension === "dwg" || extension === "dwf") {
10797
+ const arrayBuffer = await readArrayBuffer(ctx.file);
10798
+ const bytes = new Uint8Array(arrayBuffer);
10799
+ const enhancedInstance = await options.binaryRenderer?.({
10800
+ panel,
10801
+ fileName: ctx.file.name,
10802
+ extension,
10803
+ arrayBuffer,
10804
+ bytes,
10805
+ preview: ctx
10806
+ });
10807
+ if (enhancedInstance) {
10808
+ return {
10809
+ resize(size) {
10810
+ enhancedInstance.resize?.(size);
10811
+ },
10812
+ canCommand(command) {
10813
+ return enhancedInstance.canCommand?.(command) ?? false;
10814
+ },
10815
+ command(command) {
10816
+ return enhancedInstance.command?.(command);
10817
+ },
10818
+ destroy() {
10819
+ enhancedInstance.destroy();
10820
+ panel.remove();
10821
+ }
10822
+ };
10823
+ }
10824
+ if (extension === "dwg" && options.libreDwg !== false) {
10825
+ const libreDwgInstance = await renderLibreDwgPreview(
10826
+ {
10827
+ panel,
10828
+ fileName: ctx.file.name,
10829
+ extension,
10830
+ arrayBuffer,
10831
+ bytes,
10832
+ preview: ctx
10833
+ },
10834
+ typeof options.libreDwg === "object" ? options.libreDwg : void 0
10835
+ );
10836
+ if (libreDwgInstance) {
10837
+ return {
10838
+ resize(size) {
10839
+ libreDwgInstance.resize?.(size);
10840
+ },
10841
+ canCommand(command) {
10842
+ return libreDwgInstance.canCommand?.(command) ?? false;
10843
+ },
10844
+ command(command) {
10845
+ return libreDwgInstance.command?.(command);
10846
+ },
10847
+ destroy() {
10848
+ libreDwgInstance.destroy();
10849
+ panel.remove();
10850
+ }
10851
+ };
10852
+ }
10853
+ }
10854
+ renderBinaryCad(panel, bytes, extension, ctx.file.name);
10855
+ return { destroy: () => panel.remove() };
10856
+ }
10857
+ if (extension === "gds") {
10858
+ const viewer2 = renderLayoutPreview(panel, parseGdsLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
10859
+ return {
10860
+ canCommand(command) {
10861
+ return viewer2.canCommand(command);
10862
+ },
10863
+ command(command) {
10864
+ return viewer2.command(command);
10865
+ },
10866
+ destroy() {
10867
+ viewer2.destroy();
10868
+ panel.remove();
10869
+ }
10870
+ };
10871
+ }
10872
+ if (extension === "oas" || extension === "oasis") {
10873
+ const viewer2 = renderLayoutPreview(panel, parseOasisLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
10874
+ return {
10875
+ canCommand(command) {
10876
+ return viewer2.canCommand(command);
10877
+ },
10878
+ command(command) {
10879
+ return viewer2.command(command);
10880
+ },
10881
+ destroy() {
10882
+ viewer2.destroy();
10883
+ panel.remove();
10884
+ }
10885
+ };
10886
+ }
10887
+ if (extension !== "dxf") {
10888
+ const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
10889
+ 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`);
10890
+ panel.append(section);
10891
+ return { destroy: () => panel.remove() };
10892
+ }
10893
+ const dxf = await readTextFile(ctx.file);
10894
+ const viewer = renderDxf(panel, dxf, ctx);
10895
+ return {
10896
+ canCommand(command) {
10897
+ return viewer.canCommand(command);
10898
+ },
10899
+ command(command) {
10900
+ return viewer.command(command);
10901
+ },
10902
+ destroy() {
10903
+ panel.remove();
10904
+ }
10905
+ };
10906
+ }
10907
+ };
10908
+ }
10909
+ function renderStep(panel, text, extension) {
10910
+ const records = parseStepRecords(text);
10911
+ const typeCounts = countBy(records.map((record) => record.type));
10912
+ const section = createSection(`${extension.toUpperCase()} \u7ED3\u6784\u9884\u89C8`);
10913
+ const note = document.createElement("p");
10914
+ 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";
9907
10915
  const meta = document.createElement("div");
9908
10916
  meta.className = "ofv-cad-summary";
9909
10917
  appendMeta5(meta, "\u5B9E\u4F53", records.length);
@@ -9974,11 +10982,564 @@ function renderIfc(panel, text) {
9974
10982
  section.append(table);
9975
10983
  panel.append(section);
9976
10984
  }
9977
- function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
9978
- const bytes = new Uint8Array(arrayBuffer);
10985
+ var layoutPalette = [
10986
+ "#2563eb",
10987
+ "#dc2626",
10988
+ "#059669",
10989
+ "#7c3aed",
10990
+ "#d97706",
10991
+ "#0891b2",
10992
+ "#be123c",
10993
+ "#4f46e5",
10994
+ "#15803d",
10995
+ "#a16207"
10996
+ ];
10997
+ var gdsRecordNames = {
10998
+ 0: "HEADER",
10999
+ 1: "BGNLIB",
11000
+ 2: "LIBNAME",
11001
+ 3: "UNITS",
11002
+ 4: "ENDLIB",
11003
+ 5: "BGNSTR",
11004
+ 6: "STRNAME",
11005
+ 7: "ENDSTR",
11006
+ 8: "BOUNDARY",
11007
+ 9: "PATH",
11008
+ 10: "SREF",
11009
+ 11: "AREF",
11010
+ 12: "TEXT",
11011
+ 13: "LAYER",
11012
+ 14: "DATATYPE",
11013
+ 15: "WIDTH",
11014
+ 16: "XY",
11015
+ 17: "ENDEL",
11016
+ 18: "SNAME",
11017
+ 22: "TEXTTYPE",
11018
+ 25: "STRING",
11019
+ 45: "BOX"
11020
+ };
11021
+ var oasisRecordNames = {
11022
+ 0: "PAD",
11023
+ 1: "START",
11024
+ 2: "END",
11025
+ 3: "CELLNAME",
11026
+ 4: "CELLNAME-REF",
11027
+ 5: "TEXTSTRING",
11028
+ 6: "TEXTSTRING-REF",
11029
+ 7: "PROPNAME",
11030
+ 8: "PROPNAME-REF",
11031
+ 9: "PROPSTRING",
11032
+ 10: "PROPSTRING-REF",
11033
+ 11: "LAYERNAME",
11034
+ 12: "LAYERNAME-REF",
11035
+ 13: "CELL",
11036
+ 14: "XYABSOLUTE",
11037
+ 15: "XYRELATIVE",
11038
+ 16: "PLACEMENT",
11039
+ 17: "PLACEMENT",
11040
+ 18: "TEXT",
11041
+ 19: "RECTANGLE",
11042
+ 20: "POLYGON",
11043
+ 21: "PATH",
11044
+ 22: "TRAPEZOID",
11045
+ 23: "TRAPEZOID",
11046
+ 24: "TRAPEZOID",
11047
+ 25: "CTRAPEZOID",
11048
+ 26: "CIRCLE",
11049
+ 27: "PROPERTY",
11050
+ 28: "PROPERTY",
11051
+ 29: "XNAME",
11052
+ 30: "XNAME-REF",
11053
+ 31: "XELEMENT",
11054
+ 32: "XGEOMETRY",
11055
+ 33: "CBLOCK"
11056
+ };
11057
+ function renderLayoutPreview(panel, data, ctx) {
11058
+ const section = createSection(`${data.format} \u7248\u56FE\u9884\u89C8`);
11059
+ const summary = document.createElement("div");
11060
+ summary.className = "ofv-cad-summary ofv-layout-summary";
11061
+ appendMeta5(summary, "\u6587\u4EF6", data.fileName);
11062
+ appendMeta5(summary, "\u683C\u5F0F", data.format);
11063
+ if (data.libraryName) {
11064
+ appendMeta5(summary, "\u5E93", data.libraryName);
11065
+ }
11066
+ if (data.version) {
11067
+ appendMeta5(summary, "\u7248\u672C", data.version);
11068
+ }
11069
+ if (data.unit) {
11070
+ appendMeta5(summary, "\u5355\u4F4D", data.unit);
11071
+ }
11072
+ appendMeta5(summary, "Cell", data.cells.length);
11073
+ appendMeta5(summary, "\u51E0\u4F55", data.shapes.length);
11074
+ appendMeta5(summary, "\u5F15\u7528", data.references.length);
11075
+ appendMeta5(summary, "\u6587\u5B57", data.labels.length);
11076
+ for (const [label, value] of data.metadata) {
11077
+ appendMeta5(summary, label, value);
11078
+ }
11079
+ section.append(summary);
11080
+ for (const noteText of [...data.notes, ...data.warnings]) {
11081
+ const note = document.createElement("p");
11082
+ note.className = data.warnings.includes(noteText) ? "ofv-layout-warning" : "ofv-layout-note";
11083
+ note.textContent = noteText;
11084
+ section.append(note);
11085
+ }
11086
+ const bounds = computeLayoutBounds(data.shapes, data.labels, data.references);
11087
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
11088
+ svg.setAttribute("class", "ofv-svg-stage ofv-layout-stage");
11089
+ let currentViewBox = { x: bounds.minX, y: bounds.minY, width: bounds.width, height: bounds.height };
11090
+ const initialViewBox = { ...currentViewBox };
11091
+ const applyViewBox = () => {
11092
+ svg.setAttribute("viewBox", `${currentViewBox.x} ${currentViewBox.y} ${currentViewBox.width} ${currentViewBox.height}`);
11093
+ };
11094
+ applyViewBox();
11095
+ if (data.shapes.length === 0) {
11096
+ const empty = document.createElementNS(svg.namespaceURI, "text");
11097
+ empty.setAttribute("x", String(bounds.minX + bounds.width * 0.5));
11098
+ empty.setAttribute("y", String(bounds.minY + bounds.height * 0.5));
11099
+ empty.setAttribute("text-anchor", "middle");
11100
+ empty.setAttribute("font-size", String(Math.max(bounds.width, bounds.height) / 34));
11101
+ empty.setAttribute("fill", "currentColor");
11102
+ empty.textContent = "\u5DF2\u8BC6\u522B\u7248\u56FE\u6587\u4EF6\uFF0C\u5F53\u524D\u6587\u4EF6\u672A\u89E3\u6790\u51FA\u53EF\u7ED8\u5236\u51E0\u4F55";
11103
+ svg.append(empty);
11104
+ }
11105
+ const layerIndex = new Map([...data.layers.keys()].sort((a, b) => a.localeCompare(b)).map((layer, index) => [layer, index]));
11106
+ for (const shape of data.shapes.slice(0, 6e3)) {
11107
+ const color = layoutPalette[(layerIndex.get(shape.layer) || 0) % layoutPalette.length];
11108
+ if (shape.kind === "path") {
11109
+ const polyline = document.createElementNS(svg.namespaceURI, "polyline");
11110
+ polyline.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
11111
+ polyline.setAttribute("fill", "none");
11112
+ polyline.setAttribute("stroke", color);
11113
+ polyline.setAttribute("stroke-width", String(Math.max(bounds.stroke, Math.abs(shape.width || 0))));
11114
+ polyline.setAttribute("stroke-linecap", "round");
11115
+ polyline.setAttribute("stroke-linejoin", "round");
11116
+ applyLayer(polyline, shape.layer);
11117
+ svg.append(polyline);
11118
+ continue;
11119
+ }
11120
+ const polygon = document.createElementNS(svg.namespaceURI, "polygon");
11121
+ polygon.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
11122
+ polygon.setAttribute("fill", color);
11123
+ polygon.setAttribute("fill-opacity", "0.18");
11124
+ polygon.setAttribute("stroke", color);
11125
+ polygon.setAttribute("stroke-width", String(bounds.stroke));
11126
+ polygon.setAttribute("vector-effect", "non-scaling-stroke");
11127
+ applyLayer(polygon, shape.layer);
11128
+ svg.append(polygon);
11129
+ }
11130
+ for (const label of data.labels.slice(0, 400)) {
11131
+ const text = document.createElementNS(svg.namespaceURI, "text");
11132
+ text.setAttribute("x", String(label.x));
11133
+ text.setAttribute("y", String(-label.y));
11134
+ text.setAttribute("font-size", String(Math.max(bounds.stroke * 12, Math.max(bounds.width, bounds.height) / 120)));
11135
+ text.setAttribute("fill", "currentColor");
11136
+ text.textContent = label.text;
11137
+ applyLayer(text, label.layer);
11138
+ svg.append(text);
11139
+ }
11140
+ const layers = [...data.layers.keys()].sort((a, b) => a.localeCompare(b));
11141
+ if (layers.length > 0) {
11142
+ section.append(createLayoutLayerControls(svg, layers, data.layers));
11143
+ }
11144
+ section.append(svg);
11145
+ if (data.cells.length > 0) {
11146
+ section.append(createLayoutCellList(data.cells, data.references));
11147
+ }
11148
+ panel.append(section);
11149
+ const updateToolbarZoom = () => ctx.toolbar?.setZoom(initialViewBox.width / currentViewBox.width);
11150
+ updateToolbarZoom();
11151
+ return {
11152
+ canCommand(command) {
11153
+ return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
11154
+ },
11155
+ command(command) {
11156
+ if (command === "zoom-in" || command === "zoom-out") {
11157
+ const factor = command === "zoom-in" ? 0.82 : 1.18;
11158
+ const centerX = currentViewBox.x + currentViewBox.width / 2;
11159
+ const centerY = currentViewBox.y + currentViewBox.height / 2;
11160
+ currentViewBox.width *= factor;
11161
+ currentViewBox.height *= factor;
11162
+ currentViewBox.x = centerX - currentViewBox.width / 2;
11163
+ currentViewBox.y = centerY - currentViewBox.height / 2;
11164
+ applyViewBox();
11165
+ updateToolbarZoom();
11166
+ return true;
11167
+ }
11168
+ if (command === "zoom-reset") {
11169
+ currentViewBox = { ...initialViewBox };
11170
+ applyViewBox();
11171
+ updateToolbarZoom();
11172
+ return true;
11173
+ }
11174
+ return false;
11175
+ },
11176
+ destroy() {
11177
+ ctx.toolbar?.setZoom(void 0);
11178
+ }
11179
+ };
11180
+ }
11181
+ function parseGdsLayout(bytes, fileName) {
11182
+ const shapes = [];
11183
+ const labels = [];
11184
+ const references = [];
11185
+ const cells = [];
11186
+ const layers = /* @__PURE__ */ new Map();
11187
+ const recordCounts = /* @__PURE__ */ new Map();
11188
+ const warnings = [];
11189
+ let libraryName = "";
11190
+ let version = "";
11191
+ let unit = "";
11192
+ let offset = 0;
11193
+ let current = {};
11194
+ let currentKind = "";
11195
+ while (offset + 4 <= bytes.length) {
11196
+ const length = readUInt16(bytes, offset);
11197
+ const recordType = bytes[offset + 2];
11198
+ const data = bytes.slice(offset + 4, offset + length);
11199
+ const name = gdsRecordNames[recordType] || `0x${recordType.toString(16).padStart(2, "0")}`;
11200
+ recordCounts.set(name, (recordCounts.get(name) || 0) + 1);
11201
+ if (length < 4 || offset + length > bytes.length) {
11202
+ warnings.push(`GDS \u8BB0\u5F55\u5728 ${offset} \u5B57\u8282\u5904\u957F\u5EA6\u5F02\u5E38\uFF0C\u5DF2\u505C\u6B62\u89E3\u6790\u3002`);
11203
+ break;
11204
+ }
11205
+ if (recordType === 0 && data.length >= 2) {
11206
+ version = String(readUInt16(data, 0));
11207
+ } else if (recordType === 2) {
11208
+ libraryName = readGdsString(data);
11209
+ } else if (recordType === 3 && data.length >= 16) {
11210
+ unit = `${formatGdsReal(data, 0)} / ${formatGdsReal(data, 8)}`;
11211
+ } else if (recordType === 6) {
11212
+ cells.push(readGdsString(data));
11213
+ } else if (recordType === 8 || recordType === 9 || recordType === 45) {
11214
+ currentKind = recordType === 9 ? "path" : recordType === 45 ? "box" : "boundary";
11215
+ current = { kind: currentKind, layer: "0", datatype: "0", points: [] };
11216
+ } else if (recordType === 12) {
11217
+ currentKind = "text";
11218
+ current = { layer: "0", text: "", x: 0, y: 0 };
11219
+ } else if (recordType === 10 || recordType === 11) {
11220
+ currentKind = "reference";
11221
+ current = { cell: "", x: 0, y: 0 };
11222
+ } else if (recordType === 13 && data.length >= 2) {
11223
+ current.layer = String(readInt16(data, 0));
11224
+ } else if ((recordType === 14 || recordType === 22) && data.length >= 2) {
11225
+ current.datatype = String(readInt16(data, 0));
11226
+ } else if (recordType === 15 && data.length >= 4) {
11227
+ current.width = Math.abs(readInt32(data, 0));
11228
+ } else if (recordType === 16) {
11229
+ const points = readGdsPoints(data);
11230
+ if (currentKind === "text" && points[0]) {
11231
+ current.x = points[0][0];
11232
+ current.y = points[0][1];
11233
+ } else if (currentKind === "reference" && points[0]) {
11234
+ current.x = points[0][0];
11235
+ current.y = points[0][1];
11236
+ } else {
11237
+ current.points = points;
11238
+ }
11239
+ } else if (recordType === 18) {
11240
+ current.cell = readGdsString(data);
11241
+ } else if (recordType === 25) {
11242
+ current.text = readGdsString(data);
11243
+ } else if (recordType === 17) {
11244
+ if ((currentKind === "boundary" || currentKind === "path" || currentKind === "box") && current.points && current.points.length > 1) {
11245
+ const shape = {
11246
+ kind: current.kind || "boundary",
11247
+ layer: String(current.layer || "0"),
11248
+ datatype: current.datatype,
11249
+ points: current.points,
11250
+ width: current.width
11251
+ };
11252
+ shapes.push(shape);
11253
+ layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
11254
+ } else if (currentKind === "text" && current.text) {
11255
+ const label = {
11256
+ layer: String(current.layer || "0"),
11257
+ text: String(current.text),
11258
+ x: Number(current.x || 0),
11259
+ y: Number(current.y || 0)
11260
+ };
11261
+ labels.push(label);
11262
+ layers.set(label.layer, (layers.get(label.layer) || 0) + 1);
11263
+ } else if (currentKind === "reference" && current.cell) {
11264
+ references.push({ cell: String(current.cell), x: Number(current.x || 0), y: Number(current.y || 0) });
11265
+ }
11266
+ current = {};
11267
+ currentKind = "";
11268
+ }
11269
+ offset += length;
11270
+ }
11271
+ return {
11272
+ format: "GDSII",
11273
+ fileName,
11274
+ libraryName,
11275
+ version: version ? `Stream ${version}` : void 0,
11276
+ unit,
11277
+ cells,
11278
+ shapes,
11279
+ labels,
11280
+ references,
11281
+ layers,
11282
+ metadata: [
11283
+ ["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
11284
+ ["\u8BB0\u5F55", sumCounts(recordCounts)],
11285
+ ["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size]
11286
+ ],
11287
+ notes: [
11288
+ `\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`
11289
+ ],
11290
+ warnings
11291
+ };
11292
+ }
11293
+ function parseOasisLayout(bytes, fileName) {
11294
+ const chunks = extractOasisCblocks(bytes);
11295
+ const expanded = chunks.flatMap((chunk) => [...chunk.bytes]);
11296
+ 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_"));
11297
+ const propertyNames = uniqueHints(chunks.flatMap((chunk) => extractAsciiRuns(chunk.bytes)).filter((item) => item.startsWith("S_")));
11298
+ const recordCounts = scanOasisRecordCounts(bytes);
11299
+ const expandedCounts = scanOasisRecordCounts(new Uint8Array(expanded));
11300
+ const layers = /* @__PURE__ */ new Map();
11301
+ const shapes = [];
11302
+ const labels = [];
11303
+ const references = [];
11304
+ for (const name of cellNames) {
11305
+ references.push({ cell: name, x: 0, y: 0 });
11306
+ }
11307
+ const pseudo = createOasisStructureShapes(cellNames, chunks.length || recordCounts.size || 1);
11308
+ for (const shape of pseudo) {
11309
+ shapes.push(shape);
11310
+ layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
11311
+ }
11312
+ for (let index = 0; index < cellNames.length; index++) {
11313
+ labels.push({ layer: "cell", text: cellNames[index], x: 12, y: -(18 + index * 18) });
11314
+ }
11315
+ if (cellNames.length > 0) {
11316
+ layers.set("cell", (layers.get("cell") || 0) + cellNames.length);
11317
+ }
11318
+ const version = readOasisVersion(bytes);
11319
+ const cblockText = chunks.length ? `${chunks.length} \u4E2A\uFF0C\u5C55\u5F00 ${formatBytes3(chunks.reduce((sum, chunk) => sum + chunk.bytes.byteLength, 0))}` : "\u672A\u53D1\u73B0";
11320
+ const notes = [
11321
+ "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"
11322
+ ];
11323
+ if (propertyNames.length > 0) {
11324
+ notes.push(`\u8BC6\u522B\u5230\u5C5E\u6027\uFF1A${propertyNames.slice(0, 5).join("\u3001")}`);
11325
+ }
11326
+ return {
11327
+ format: "OASIS",
11328
+ fileName,
11329
+ version,
11330
+ cells: cellNames,
11331
+ shapes,
11332
+ labels,
11333
+ references: [],
11334
+ layers,
11335
+ metadata: [
11336
+ ["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
11337
+ ["CBLOCK", cblockText],
11338
+ ["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size + expandedCounts.size],
11339
+ ["\u53EF\u8BFB\u7247\u6BB5", cellNames.length + propertyNames.length]
11340
+ ],
11341
+ notes,
11342
+ 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"] : []
11343
+ };
11344
+ }
11345
+ function computeLayoutBounds(shapes, labels, references) {
11346
+ const xs = [];
11347
+ const ys = [];
11348
+ for (const shape of shapes) {
11349
+ for (const [x, y] of shape.points) {
11350
+ xs.push(x);
11351
+ ys.push(-y);
11352
+ }
11353
+ }
11354
+ for (const label of labels) {
11355
+ xs.push(label.x, label.x + label.text.length * 12);
11356
+ ys.push(-label.y, -label.y - 16);
11357
+ }
11358
+ for (const reference of references) {
11359
+ xs.push(reference.x);
11360
+ ys.push(-reference.y);
11361
+ }
11362
+ const minX = Math.min(...xs, 0);
11363
+ const maxX = Math.max(...xs, 100);
11364
+ const minY = Math.min(...ys, 0);
11365
+ const maxY = Math.max(...ys, 100);
11366
+ const width = Math.max(1, maxX - minX);
11367
+ const height = Math.max(1, maxY - minY);
11368
+ const padding = Math.max(width, height) * 0.06;
11369
+ return {
11370
+ minX: minX - padding,
11371
+ minY: minY - padding,
11372
+ width: width + padding * 2,
11373
+ height: height + padding * 2,
11374
+ stroke: Math.max(width, height) / 900
11375
+ };
11376
+ }
11377
+ function createLayoutLayerControls(svg, layers, counts) {
11378
+ const controls = document.createElement("div");
11379
+ controls.className = "ofv-cad-layers ofv-layout-layers";
11380
+ const title = document.createElement("strong");
11381
+ title.textContent = `\u56FE\u5C42 ${layers.length}`;
11382
+ controls.append(title);
11383
+ for (const layer of layers) {
11384
+ const label = document.createElement("label");
11385
+ const checkbox = document.createElement("input");
11386
+ checkbox.type = "checkbox";
11387
+ checkbox.checked = true;
11388
+ checkbox.addEventListener("change", () => {
11389
+ for (const element of svg.querySelectorAll(`[data-layer="${escapeCssAttribute(layer)}"]`)) {
11390
+ element.style.display = checkbox.checked ? "" : "none";
11391
+ }
11392
+ });
11393
+ const name = document.createElement("span");
11394
+ name.textContent = `${layer} (${counts.get(layer) || 0})`;
11395
+ label.append(checkbox, name);
11396
+ controls.append(label);
11397
+ }
11398
+ return controls;
11399
+ }
11400
+ function createLayoutCellList(cells, references) {
11401
+ const details = document.createElement("details");
11402
+ details.className = "ofv-details ofv-layout-cells";
11403
+ details.open = true;
11404
+ const summary = document.createElement("summary");
11405
+ summary.textContent = `Cell \u7ED3\u6784 ${cells.length}`;
11406
+ const list = document.createElement("ul");
11407
+ const refCounts = countBy(references.map((reference) => reference.cell));
11408
+ for (const cell of cells.slice(0, 120)) {
11409
+ const item = document.createElement("li");
11410
+ const count = refCounts.get(cell) || 0;
11411
+ item.textContent = count > 0 ? `${cell} \xB7 \u5F15\u7528 ${count}` : cell;
11412
+ list.append(item);
11413
+ }
11414
+ details.append(summary, list);
11415
+ return details;
11416
+ }
11417
+ function readUInt16(bytes, offset) {
11418
+ return bytes[offset] << 8 | bytes[offset + 1];
11419
+ }
11420
+ function readInt16(bytes, offset) {
11421
+ const value = readUInt16(bytes, offset);
11422
+ return value & 32768 ? value - 65536 : value;
11423
+ }
11424
+ function readInt32(bytes, offset) {
11425
+ const value = bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
11426
+ return value | 0;
11427
+ }
11428
+ function readGdsString(bytes) {
11429
+ return new TextDecoder("ascii").decode(bytes).replace(/\0+$/g, "").trim();
11430
+ }
11431
+ function readGdsPoints(bytes) {
11432
+ const points = [];
11433
+ for (let offset = 0; offset + 7 < bytes.length; offset += 8) {
11434
+ points.push([readInt32(bytes, offset), readInt32(bytes, offset + 4)]);
11435
+ }
11436
+ return points;
11437
+ }
11438
+ function formatGdsReal(bytes, offset) {
11439
+ const value = readGdsReal(bytes, offset);
11440
+ if (!Number.isFinite(value) || value === 0) {
11441
+ return "0";
11442
+ }
11443
+ if (Math.abs(value) < 1e-3 || Math.abs(value) >= 1e4) {
11444
+ return value.toExponential(4);
11445
+ }
11446
+ return String(Number(value.toPrecision(6)));
11447
+ }
11448
+ function readGdsReal(bytes, offset) {
11449
+ const first = bytes[offset];
11450
+ if (!first) {
11451
+ return 0;
11452
+ }
11453
+ const sign = first & 128 ? -1 : 1;
11454
+ const exponent = (first & 127) - 64;
11455
+ let mantissa = 0;
11456
+ for (let index = 1; index < 8; index++) {
11457
+ mantissa = mantissa * 256 + bytes[offset + index];
11458
+ }
11459
+ return sign * (mantissa / Math.pow(2, 56)) * Math.pow(16, exponent);
11460
+ }
11461
+ function sumCounts(counts) {
11462
+ return [...counts.values()].reduce((sum, count) => sum + count, 0);
11463
+ }
11464
+ function extractOasisCblocks(bytes) {
11465
+ const chunks = [];
11466
+ const seen = /* @__PURE__ */ new Set();
11467
+ const limit = Math.min(bytes.length, 25e4);
11468
+ for (let offset = 0; offset < limit; offset++) {
11469
+ try {
11470
+ const inflated = pako3.inflateRaw(bytes.slice(offset));
11471
+ if (inflated.byteLength < 4) {
11472
+ continue;
11473
+ }
11474
+ const ascii = extractAsciiRuns(inflated);
11475
+ const hasLayoutSignal = ascii.some((item) => item.startsWith("S_") || /TOP|CELL|DIE|SIZE/i.test(item));
11476
+ const hasRecordSignal = inflated.some((byte) => byte >= 13 && byte <= 34);
11477
+ if (!hasLayoutSignal && !hasRecordSignal) {
11478
+ continue;
11479
+ }
11480
+ const signature = `${inflated.byteLength}:${Array.from(inflated.slice(0, 12)).join(",")}`;
11481
+ if (seen.has(signature)) {
11482
+ continue;
11483
+ }
11484
+ seen.add(signature);
11485
+ chunks.push({ offset, bytes: inflated });
11486
+ if (chunks.length >= 12) {
11487
+ break;
11488
+ }
11489
+ } catch {
11490
+ }
11491
+ }
11492
+ return chunks;
11493
+ }
11494
+ function scanOasisRecordCounts(bytes) {
11495
+ const counts = /* @__PURE__ */ new Map();
11496
+ for (const byte of bytes.slice(0, Math.min(bytes.length, 12e3))) {
11497
+ const name = oasisRecordNames[byte];
11498
+ if (name) {
11499
+ counts.set(name, (counts.get(name) || 0) + 1);
11500
+ }
11501
+ }
11502
+ return counts;
11503
+ }
11504
+ function readOasisVersion(bytes) {
11505
+ const magic = "%SEMI-OASIS\r\n";
11506
+ const header = new TextDecoder("ascii").decode(bytes.slice(0, Math.min(bytes.length, 48)));
11507
+ if (!header.startsWith(magic)) {
11508
+ return void 0;
11509
+ }
11510
+ const start = magic.length;
11511
+ if (bytes[start] !== 1) {
11512
+ return "OASIS";
11513
+ }
11514
+ const length = bytes[start + 1];
11515
+ const version = new TextDecoder("ascii").decode(bytes.slice(start + 2, start + 2 + length));
11516
+ return version ? `OASIS ${version}` : "OASIS";
11517
+ }
11518
+ function createOasisStructureShapes(cellNames, fallbackCount) {
11519
+ const rows = Math.max(1, cellNames.length || fallbackCount);
11520
+ const shapes = [];
11521
+ for (let index = 0; index < rows; index++) {
11522
+ const top = -(index * 18);
11523
+ const height = 12;
11524
+ const width = 88 + Math.min((cellNames[index]?.length || 5) * 4, 90);
11525
+ shapes.push({
11526
+ kind: "box",
11527
+ layer: "cell",
11528
+ points: [
11529
+ [0, top],
11530
+ [width, top],
11531
+ [width, top - height],
11532
+ [0, top - height],
11533
+ [0, top]
11534
+ ]
11535
+ });
11536
+ }
11537
+ return shapes;
11538
+ }
11539
+ function renderBinaryCad(panel, bytes, extension, fileName) {
9979
11540
  const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);
9980
11541
  const note = document.createElement("p");
9981
- 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";
11542
+ 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";
9982
11543
  const meta = document.createElement("div");
9983
11544
  meta.className = "ofv-cad-summary";
9984
11545
  appendMeta5(meta, "\u6587\u4EF6", fileName);
@@ -9987,33 +11548,41 @@ function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
9987
11548
  appendMeta5(meta, "\u7B7E\u540D", byteSignature2(bytes));
9988
11549
  appendMeta5(meta, "\u7248\u672C", detectCadVersion(bytes, extension));
9989
11550
  appendMeta5(meta, "\u5BB9\u5668", detectCadContainer(bytes));
9990
- const actions = document.createElement("ol");
11551
+ const actions = document.createElement("div");
9991
11552
  actions.className = "ofv-cad-conversion";
11553
+ const actionTitle = document.createElement("h4");
11554
+ actionTitle.textContent = extension === "dwg" ? "\u63A8\u8350\u589E\u5F3A\u8DEF\u7EBF" : "\u63A8\u8350\u5904\u7406\u8DEF\u7EBF";
11555
+ const actionList = document.createElement("ol");
9992
11556
  const suggestions = extension === "dwg" ? [
9993
- "\u670D\u52A1\u7AEF\u4F7F\u7528 ODA File Converter / Teigha \u5C06 DWG \u8F6C\u4E3A DXF\u3001SVG \u6216 PDF\u3002",
9994
- "\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",
9995
- "\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"
11557
+ "\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",
11558
+ "\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",
11559
+ "\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"
9996
11560
  ] : [
9997
11561
  "\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",
9998
- "\u82E5 DWF \u4E3A\u538B\u7F29\u5BB9\u5668\uFF0C\u53EF\u540E\u7EED\u8BFB\u53D6 manifest/descriptor \u518D\u8FD8\u539F\u9875\u9762\u8D44\u6E90\u3002",
11562
+ "\u82E5 DWF \u4E3A\u538B\u7F29\u5BB9\u5668\uFF0C\u53EF\u901A\u8FC7 binaryRenderer \u8BFB\u53D6 manifest/descriptor \u518D\u8FD8\u539F\u9875\u9762\u8D44\u6E90\u3002",
9999
11563
  "\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"
10000
11564
  ];
10001
11565
  for (const suggestion of suggestions) {
10002
11566
  const item = document.createElement("li");
10003
11567
  item.textContent = suggestion;
10004
- actions.append(item);
11568
+ actionList.append(item);
10005
11569
  }
11570
+ actions.append(actionTitle, actionList);
11571
+ const raw = document.createElement("details");
11572
+ raw.className = "ofv-details ofv-cad-raw-preview";
11573
+ const rawSummary = document.createElement("summary");
11574
+ rawSummary.textContent = "\u539F\u59CB\u5B57\u8282\u9884\u89C8";
10006
11575
  const preview = document.createElement("pre");
10007
11576
  preview.className = "ofv-text-block";
10008
11577
  preview.textContent = hexPreview(bytes);
10009
- section.append(note, meta, createBinaryCadProbe(bytes, extension), actions, preview);
11578
+ raw.append(rawSummary, preview);
11579
+ section.append(note, meta, actions, createBinaryCadProbe(bytes, extension), raw);
10010
11580
  panel.append(section);
10011
11581
  }
10012
11582
  function createBinaryCadProbe(bytes, extension) {
10013
11583
  const probe = probeBinaryCad(bytes);
10014
11584
  const details = document.createElement("details");
10015
11585
  details.className = "ofv-details ofv-cad-binary-probe";
10016
- details.open = true;
10017
11586
  const summary = document.createElement("summary");
10018
11587
  summary.textContent = "\u4E8C\u8FDB\u5236\u7ED3\u6784\u63A2\u6D4B";
10019
11588
  const meta = document.createElement("div");
@@ -11617,6 +13186,15 @@ function assetPlugin() {
11617
13186
  const isExternal = Boolean(ctx.file.url);
11618
13187
  const extension = resolveFormat(ctx.file, assetMimeFormatMap).toLowerCase();
11619
13188
  const bytes = new Uint8Array(await readArrayBuffer(ctx.file).catch(() => new ArrayBuffer(0)));
13189
+ if (isPhotoshopAsset(extension)) {
13190
+ panel.append(await createPhotoshopPreview(bytes));
13191
+ return {
13192
+ destroy() {
13193
+ revokeObjectUrl(url, isExternal);
13194
+ panel.remove();
13195
+ }
13196
+ };
13197
+ }
11620
13198
  const section = createSection(assetTitle(extension));
11621
13199
  const summary = document.createElement("div");
11622
13200
  summary.className = "ofv-asset-summary";
@@ -11638,9 +13216,6 @@ function assetPlugin() {
11638
13216
  if (extension === "wasm") {
11639
13217
  section.append(createWasmPreview(bytes));
11640
13218
  }
11641
- if (extension === "psd" || extension === "psb") {
11642
- section.append(createPhotoshopPreview(bytes));
11643
- }
11644
13219
  if (extension === "sqlite" || extension === "sqlite3" || extension === "db") {
11645
13220
  section.append(createSqlitePreview(bytes));
11646
13221
  }
@@ -11693,7 +13268,7 @@ function assetGuidance(extension) {
11693
13268
  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";
11694
13269
  }
11695
13270
  if (extension === "psd" || extension === "psb") {
11696
- 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";
13271
+ 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";
11697
13272
  }
11698
13273
  if (["ai", "eps", "ps"].includes(extension)) {
11699
13274
  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";
@@ -11715,6 +13290,9 @@ function assetGuidance(extension) {
11715
13290
  function isFontAsset(extension) {
11716
13291
  return ["ttf", "otf", "woff", "woff2", "eot"].includes(extension);
11717
13292
  }
13293
+ function isPhotoshopAsset(extension) {
13294
+ return extension === "psd" || extension === "psb";
13295
+ }
11718
13296
  async function createFontPreview(extension, url, fileName, bytes) {
11719
13297
  const preview = document.createElement("div");
11720
13298
  preview.className = "ofv-font-preview";
@@ -12476,12 +14054,9 @@ function wasmExternalKind(kind) {
12476
14054
  };
12477
14055
  return names[kind] || `unknown ${kind}`;
12478
14056
  }
12479
- function createPhotoshopPreview(bytes) {
14057
+ async function createPhotoshopPreview(bytes) {
12480
14058
  const preview = document.createElement("div");
12481
14059
  preview.className = "ofv-psd-preview";
12482
- const heading = document.createElement("strong");
12483
- heading.textContent = "Photoshop \u7ED3\u6784";
12484
- preview.append(heading);
12485
14060
  const header = parsePhotoshopHeader(bytes);
12486
14061
  if (!header.valid) {
12487
14062
  const error = document.createElement("p");
@@ -12490,16 +14065,66 @@ function createPhotoshopPreview(bytes) {
12490
14065
  preview.append(error);
12491
14066
  return preview;
12492
14067
  }
12493
- const summary = document.createElement("div");
12494
- summary.className = "ofv-psd-summary";
12495
- appendMeta(summary, "\u7248\u672C", header.version === 2 ? "PSB \u5927\u6587\u6863" : "PSD");
12496
- appendMeta(summary, "\u753B\u5E03", `${header.width} x ${header.height}px`);
12497
- appendMeta(summary, "\u901A\u9053", header.channels ?? "\u672A\u77E5");
12498
- appendMeta(summary, "\u4F4D\u6DF1", `${header.depth ?? "\u672A\u77E5"} bit`);
12499
- appendMeta(summary, "\u989C\u8272\u6A21\u5F0F", photoshopColorMode(header.colorMode ?? -1));
12500
- preview.append(summary);
14068
+ preview.append(await createPhotoshopCompositePreview(bytes, header));
12501
14069
  return preview;
12502
14070
  }
14071
+ async function createPhotoshopCompositePreview(bytes, header) {
14072
+ const wrapper = document.createElement("div");
14073
+ wrapper.className = "ofv-psd-composite";
14074
+ if (!canUseBrowserCanvas()) {
14075
+ const status = document.createElement("p");
14076
+ status.className = "ofv-psd-error";
14077
+ status.textContent = "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Canvas\uFF0C\u65E0\u6CD5\u751F\u6210 PSD \u5408\u6210\u56FE\u9884\u89C8\u3002";
14078
+ wrapper.append(status);
14079
+ return wrapper;
14080
+ }
14081
+ try {
14082
+ const { readPsd } = await import("ag-psd");
14083
+ const psd = readPsd(toStandaloneArrayBuffer(bytes), {
14084
+ skipLayerImageData: true,
14085
+ skipThumbnail: true,
14086
+ skipLinkedFilesData: true,
14087
+ useImageData: true,
14088
+ throwForMissingFeatures: false,
14089
+ logMissingFeatures: false
14090
+ });
14091
+ const canvas = psd.canvas || createCanvasFromPsdImageData(psd);
14092
+ if (!canvas) {
14093
+ 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");
14094
+ }
14095
+ canvas.classList.add("ofv-psd-canvas");
14096
+ canvas.setAttribute("role", "img");
14097
+ canvas.setAttribute("aria-label", `Photoshop composite ${header.width} x ${header.height}`);
14098
+ wrapper.append(canvas);
14099
+ } catch (error) {
14100
+ const status = document.createElement("p");
14101
+ status.className = "ofv-psd-error";
14102
+ 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"}`;
14103
+ wrapper.append(status);
14104
+ }
14105
+ return wrapper;
14106
+ }
14107
+ function canUseBrowserCanvas() {
14108
+ return typeof document !== "undefined" && typeof HTMLCanvasElement !== "undefined";
14109
+ }
14110
+ function toStandaloneArrayBuffer(bytes) {
14111
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
14112
+ }
14113
+ function createCanvasFromPsdImageData(psd) {
14114
+ const imageData = psd.imageData;
14115
+ if (!imageData || !imageData.width || !imageData.height || !(imageData.data instanceof Uint8ClampedArray)) {
14116
+ return void 0;
14117
+ }
14118
+ const canvas = document.createElement("canvas");
14119
+ canvas.width = imageData.width;
14120
+ canvas.height = imageData.height;
14121
+ const context = canvas.getContext("2d");
14122
+ if (!context) {
14123
+ return void 0;
14124
+ }
14125
+ context.putImageData(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height), 0, 0);
14126
+ return canvas;
14127
+ }
12503
14128
  function parsePhotoshopHeader(bytes) {
12504
14129
  if (bytes.length < 26) {
12505
14130
  return { valid: false, error: "\u6587\u4EF6\u592A\u77ED\uFF0C\u65E0\u6CD5\u8BFB\u53D6 PSD/PSB \u5934\u4FE1\u606F\u3002" };
@@ -12528,19 +14153,6 @@ function parsePhotoshopHeader(bytes) {
12528
14153
  colorMode: view3.getUint16(24, false)
12529
14154
  };
12530
14155
  }
12531
- function photoshopColorMode(mode) {
12532
- const modes = {
12533
- 0: "Bitmap",
12534
- 1: "Grayscale",
12535
- 2: "Indexed",
12536
- 3: "RGB",
12537
- 4: "CMYK",
12538
- 7: "Multichannel",
12539
- 8: "Duotone",
12540
- 9: "Lab"
12541
- };
12542
- return modes[mode] ? `${modes[mode]} (${mode})` : `\u672A\u77E5 (${mode})`;
12543
- }
12544
14156
  function createSqlitePreview(bytes) {
12545
14157
  const preview = document.createElement("div");
12546
14158
  preview.className = "ofv-sqlite-preview";