@open-file-viewer/core 0.1.6 → 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
@@ -271,7 +271,7 @@ var extensionMimeMap = {
271
271
  };
272
272
  async function normalizeFile(source, fileName, mimeType) {
273
273
  if (typeof source === "string") {
274
- const name2 = fileName || source.split("?")[0]?.split("/").pop() || "remote-file";
274
+ const name2 = fileName || getFileNameFromUrl(source) || "remote-file";
275
275
  const extension2 = getExtension(name2);
276
276
  return {
277
277
  source,
@@ -316,6 +316,17 @@ async function normalizeFile(source, fileName, mimeType) {
316
316
  blob
317
317
  };
318
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
+ }
319
330
  function getExtension(name) {
320
331
  const clean = name.split("?")[0]?.split("#")[0] || "";
321
332
  const index = clean.lastIndexOf(".");
@@ -5368,7 +5379,13 @@ async function renderSheet(panel, arrayBuffer, extension) {
5368
5379
  const xlsx = await import("xlsx");
5369
5380
  let workbook;
5370
5381
  try {
5371
- 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 });
5372
5389
  } catch (error) {
5373
5390
  if (isLegacyOfficeBinary(extension)) {
5374
5391
  renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
@@ -5396,7 +5413,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5396
5413
  const heading = document.createElement("h3");
5397
5414
  heading.textContent = sheetName;
5398
5415
  const sheet = workbook.Sheets[sheetName];
5399
- 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);
5400
5417
  const rowCount = range.e.r - range.s.r + 1;
5401
5418
  const columnCount = range.e.c - range.s.c + 1;
5402
5419
  const formulaRows = collectFormulaRows(sheet, range, xlsx.utils.encode_cell);
@@ -5406,6 +5423,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5406
5423
  const tableWrapper = document.createElement("div");
5407
5424
  tableWrapper.className = "ofv-table-scroll";
5408
5425
  const viewport = createSheetViewport(rowCount, columnCount);
5426
+ const columnSizing = { widths: /* @__PURE__ */ new Map() };
5409
5427
  const windowControls = createSheetWindowControls(viewport, () => renderTableWindow());
5410
5428
  const renderTableWindow = () => {
5411
5429
  tableWrapper.replaceChildren(
@@ -5415,7 +5433,9 @@ async function renderSheet(panel, arrayBuffer, extension) {
5415
5433
  sheetIndex,
5416
5434
  viewport,
5417
5435
  xlsx.utils.encode_cell,
5418
- xlsx.utils.format_cell
5436
+ xlsx.utils.format_cell,
5437
+ columnSizing,
5438
+ renderTableWindow
5419
5439
  )
5420
5440
  );
5421
5441
  windowControls?.update();
@@ -5525,9 +5545,10 @@ function renderParsedSheets(panel, sheets, emptyMessage) {
5525
5545
  const tableWrapper = document.createElement("div");
5526
5546
  tableWrapper.className = "ofv-table-scroll";
5527
5547
  const viewport = createSheetViewport(rowCount, columnCount);
5548
+ const columnSizing = { widths: /* @__PURE__ */ new Map() };
5528
5549
  const windowControls = createSheetWindowControls(viewport, () => renderTableWindow());
5529
5550
  const renderTableWindow = () => {
5530
- tableWrapper.replaceChildren(createParsedSheetTable(sheet, sheetIndex, viewport));
5551
+ tableWrapper.replaceChildren(createParsedSheetTable(sheet, sheetIndex, viewport, columnSizing, renderTableWindow));
5531
5552
  windowControls?.update();
5532
5553
  };
5533
5554
  content.append(heading, summary);
@@ -5754,6 +5775,45 @@ function chartText(element) {
5754
5775
  function chartStringValues(element) {
5755
5776
  return Array.from(element.querySelectorAll("*")).filter((item) => item.localName === "v" || item.localName === "t").map((item) => item.textContent?.trim() || "").filter(Boolean);
5756
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
+ }
5757
5817
  function createSheetViewport(rowCount, columnCount) {
5758
5818
  return {
5759
5819
  rowStart: 0,
@@ -5816,42 +5876,245 @@ function createWindowButton(label, onClick) {
5816
5876
  function maxStart(total, size) {
5817
5877
  return Math.max(0, total - size);
5818
5878
  }
5819
- function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell) {
5879
+ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender) {
5820
5880
  const table = document.createElement("table");
5821
5881
  table.id = `ofv-sheet-${sheetIndex + 1}`;
5882
+ table.className = "ofv-workbook-table";
5822
5883
  const rowEnd = Math.min(range.s.r + viewport.rowStart + SHEET_WINDOW_ROWS - 1, range.e.r);
5823
5884
  const columnEnd = Math.min(range.s.c + viewport.columnStart + SHEET_WINDOW_COLUMNS - 1, range.e.c);
5824
- 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) {
5825
5901
  const row = document.createElement("tr");
5826
- for (let columnIndex = range.s.c + viewport.columnStart; columnIndex <= columnEnd; columnIndex += 1) {
5827
- 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) {
5828
5907
  const address = encodeCell({ r: rowIndex, c: columnIndex });
5829
- 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");
5830
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
+ }
5831
5929
  const text = sourceCell ? formatCell(sourceCell) : "";
5832
5930
  cell.textContent = text;
5833
5931
  if (text) {
5834
5932
  cell.title = text;
5835
5933
  }
5934
+ applyWorkbookCellStyle(cell, sourceCell);
5836
5935
  if (sourceCell?.f) {
5837
5936
  cell.classList.add("ofv-cell-formula");
5838
5937
  cell.title = `=${sourceCell.f}`;
5839
5938
  }
5939
+ if (text.includes("\n")) {
5940
+ cell.classList.add("ofv-cell-multiline");
5941
+ }
5942
+ appendColumnResizeHandle(cell, columnIndex, columnSizing);
5840
5943
  row.append(cell);
5841
5944
  }
5842
5945
  table.append(row);
5843
5946
  }
5844
5947
  return table;
5845
5948
  }
5846
- 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) {
5847
6098
  const table = document.createElement("table");
5848
6099
  table.id = `ofv-sheet-${sheetIndex + 1}`;
5849
6100
  const formulaMap = new Map(sheet.formulas.map((item) => [item.address, item.formula]));
5850
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);
5851
6115
  for (let rowIndex = viewport.rowStart; rowIndex < rowEnd; rowIndex += 1) {
5852
6116
  const sourceRow = sheet.rows[rowIndex] || [];
5853
6117
  const row = document.createElement("tr");
5854
- const columnEnd = Math.min(viewport.columnStart + SHEET_WINDOW_COLUMNS, viewport.columnCount);
5855
6118
  for (let columnIndex = viewport.columnStart; columnIndex < columnEnd; columnIndex += 1) {
5856
6119
  const value = sourceRow[columnIndex] || "";
5857
6120
  const cell = document.createElement(rowIndex === 0 ? "th" : "td");
@@ -5866,6 +6129,10 @@ function createParsedSheetTable(sheet, sheetIndex, viewport) {
5866
6129
  cell.classList.add("ofv-cell-formula");
5867
6130
  cell.title = formula;
5868
6131
  }
6132
+ if (value.includes("\n")) {
6133
+ cell.classList.add("ofv-cell-multiline");
6134
+ }
6135
+ appendColumnResizeHandle(cell, columnIndex, columnSizing);
5869
6136
  row.append(cell);
5870
6137
  }
5871
6138
  table.append(row);
@@ -9926,6 +10193,521 @@ function decodeDrawioDiagram(value) {
9926
10193
 
9927
10194
  // src/plugins/cad.ts
9928
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;
10243
+ }
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
+ };
10258
+ }
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));
10274
+ return {
10275
+ destroy() {
10276
+ URL.revokeObjectURL(thumbnailUrl);
10277
+ shell.remove();
10278
+ }
10279
+ };
10280
+ }
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
9929
10711
  var cadExtensions = /* @__PURE__ */ new Set([
9930
10712
  "dxf",
9931
10713
  "dwg",
@@ -9989,7 +10771,7 @@ var cadMimeFormatMap = {
9989
10771
  "application/vnd.oasis.layout": "oas",
9990
10772
  "application/x-oasis-layout": "oas"
9991
10773
  };
9992
- function cadPlugin() {
10774
+ function cadPlugin(options = {}) {
9993
10775
  return {
9994
10776
  name: "cad",
9995
10777
  match(file) {
@@ -10012,7 +10794,64 @@ function cadPlugin() {
10012
10794
  return { destroy: () => panel.remove() };
10013
10795
  }
10014
10796
  if (extension === "dwg" || extension === "dwf") {
10015
- renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
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);
10016
10855
  return { destroy: () => panel.remove() };
10017
10856
  }
10018
10857
  if (extension === "gds") {
@@ -10697,11 +11536,10 @@ function createOasisStructureShapes(cellNames, fallbackCount) {
10697
11536
  }
10698
11537
  return shapes;
10699
11538
  }
10700
- function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
10701
- const bytes = new Uint8Array(arrayBuffer);
11539
+ function renderBinaryCad(panel, bytes, extension, fileName) {
10702
11540
  const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);
10703
11541
  const note = document.createElement("p");
10704
- 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";
10705
11543
  const meta = document.createElement("div");
10706
11544
  meta.className = "ofv-cad-summary";
10707
11545
  appendMeta5(meta, "\u6587\u4EF6", fileName);
@@ -10710,33 +11548,41 @@ function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
10710
11548
  appendMeta5(meta, "\u7B7E\u540D", byteSignature2(bytes));
10711
11549
  appendMeta5(meta, "\u7248\u672C", detectCadVersion(bytes, extension));
10712
11550
  appendMeta5(meta, "\u5BB9\u5668", detectCadContainer(bytes));
10713
- const actions = document.createElement("ol");
11551
+ const actions = document.createElement("div");
10714
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");
10715
11556
  const suggestions = extension === "dwg" ? [
10716
- "\u670D\u52A1\u7AEF\u4F7F\u7528 ODA File Converter / Teigha \u5C06 DWG \u8F6C\u4E3A DXF\u3001SVG \u6216 PDF\u3002",
10717
- "\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",
10718
- "\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"
10719
11560
  ] : [
10720
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",
10721
- "\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",
10722
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"
10723
11564
  ];
10724
11565
  for (const suggestion of suggestions) {
10725
11566
  const item = document.createElement("li");
10726
11567
  item.textContent = suggestion;
10727
- actions.append(item);
11568
+ actionList.append(item);
10728
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";
10729
11575
  const preview = document.createElement("pre");
10730
11576
  preview.className = "ofv-text-block";
10731
11577
  preview.textContent = hexPreview(bytes);
10732
- section.append(note, meta, createBinaryCadProbe(bytes, extension), actions, preview);
11578
+ raw.append(rawSummary, preview);
11579
+ section.append(note, meta, actions, createBinaryCadProbe(bytes, extension), raw);
10733
11580
  panel.append(section);
10734
11581
  }
10735
11582
  function createBinaryCadProbe(bytes, extension) {
10736
11583
  const probe = probeBinaryCad(bytes);
10737
11584
  const details = document.createElement("details");
10738
11585
  details.className = "ofv-details ofv-cad-binary-probe";
10739
- details.open = true;
10740
11586
  const summary = document.createElement("summary");
10741
11587
  summary.textContent = "\u4E8C\u8FDB\u5236\u7ED3\u6784\u63A2\u6D4B";
10742
11588
  const meta = document.createElement("div");
@@ -12340,6 +13186,15 @@ function assetPlugin() {
12340
13186
  const isExternal = Boolean(ctx.file.url);
12341
13187
  const extension = resolveFormat(ctx.file, assetMimeFormatMap).toLowerCase();
12342
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
+ }
12343
13198
  const section = createSection(assetTitle(extension));
12344
13199
  const summary = document.createElement("div");
12345
13200
  summary.className = "ofv-asset-summary";
@@ -12361,9 +13216,6 @@ function assetPlugin() {
12361
13216
  if (extension === "wasm") {
12362
13217
  section.append(createWasmPreview(bytes));
12363
13218
  }
12364
- if (extension === "psd" || extension === "psb") {
12365
- section.append(createPhotoshopPreview(bytes));
12366
- }
12367
13219
  if (extension === "sqlite" || extension === "sqlite3" || extension === "db") {
12368
13220
  section.append(createSqlitePreview(bytes));
12369
13221
  }
@@ -12416,7 +13268,7 @@ function assetGuidance(extension) {
12416
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";
12417
13269
  }
12418
13270
  if (extension === "psd" || extension === "psb") {
12419
- 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";
12420
13272
  }
12421
13273
  if (["ai", "eps", "ps"].includes(extension)) {
12422
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";
@@ -12438,6 +13290,9 @@ function assetGuidance(extension) {
12438
13290
  function isFontAsset(extension) {
12439
13291
  return ["ttf", "otf", "woff", "woff2", "eot"].includes(extension);
12440
13292
  }
13293
+ function isPhotoshopAsset(extension) {
13294
+ return extension === "psd" || extension === "psb";
13295
+ }
12441
13296
  async function createFontPreview(extension, url, fileName, bytes) {
12442
13297
  const preview = document.createElement("div");
12443
13298
  preview.className = "ofv-font-preview";
@@ -13199,12 +14054,9 @@ function wasmExternalKind(kind) {
13199
14054
  };
13200
14055
  return names[kind] || `unknown ${kind}`;
13201
14056
  }
13202
- function createPhotoshopPreview(bytes) {
14057
+ async function createPhotoshopPreview(bytes) {
13203
14058
  const preview = document.createElement("div");
13204
14059
  preview.className = "ofv-psd-preview";
13205
- const heading = document.createElement("strong");
13206
- heading.textContent = "Photoshop \u7ED3\u6784";
13207
- preview.append(heading);
13208
14060
  const header = parsePhotoshopHeader(bytes);
13209
14061
  if (!header.valid) {
13210
14062
  const error = document.createElement("p");
@@ -13213,16 +14065,66 @@ function createPhotoshopPreview(bytes) {
13213
14065
  preview.append(error);
13214
14066
  return preview;
13215
14067
  }
13216
- const summary = document.createElement("div");
13217
- summary.className = "ofv-psd-summary";
13218
- appendMeta(summary, "\u7248\u672C", header.version === 2 ? "PSB \u5927\u6587\u6863" : "PSD");
13219
- appendMeta(summary, "\u753B\u5E03", `${header.width} x ${header.height}px`);
13220
- appendMeta(summary, "\u901A\u9053", header.channels ?? "\u672A\u77E5");
13221
- appendMeta(summary, "\u4F4D\u6DF1", `${header.depth ?? "\u672A\u77E5"} bit`);
13222
- appendMeta(summary, "\u989C\u8272\u6A21\u5F0F", photoshopColorMode(header.colorMode ?? -1));
13223
- preview.append(summary);
14068
+ preview.append(await createPhotoshopCompositePreview(bytes, header));
13224
14069
  return preview;
13225
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
+ }
13226
14128
  function parsePhotoshopHeader(bytes) {
13227
14129
  if (bytes.length < 26) {
13228
14130
  return { valid: false, error: "\u6587\u4EF6\u592A\u77ED\uFF0C\u65E0\u6CD5\u8BFB\u53D6 PSD/PSB \u5934\u4FE1\u606F\u3002" };
@@ -13251,19 +14153,6 @@ function parsePhotoshopHeader(bytes) {
13251
14153
  colorMode: view3.getUint16(24, false)
13252
14154
  };
13253
14155
  }
13254
- function photoshopColorMode(mode) {
13255
- const modes = {
13256
- 0: "Bitmap",
13257
- 1: "Grayscale",
13258
- 2: "Indexed",
13259
- 3: "RGB",
13260
- 4: "CMYK",
13261
- 7: "Multichannel",
13262
- 8: "Duotone",
13263
- 9: "Lab"
13264
- };
13265
- return modes[mode] ? `${modes[mode]} (${mode})` : `\u672A\u77E5 (${mode})`;
13266
- }
13267
14156
  function createSqlitePreview(bytes) {
13268
14157
  const preview = document.createElement("div");
13269
14158
  preview.className = "ofv-sqlite-preview";