@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.cjs CHANGED
@@ -324,7 +324,7 @@ var extensionMimeMap = {
324
324
  };
325
325
  async function normalizeFile(source, fileName, mimeType) {
326
326
  if (typeof source === "string") {
327
- const name2 = fileName || source.split("?")[0]?.split("/").pop() || "remote-file";
327
+ const name2 = fileName || getFileNameFromUrl(source) || "remote-file";
328
328
  const extension2 = getExtension(name2);
329
329
  return {
330
330
  source,
@@ -369,6 +369,17 @@ async function normalizeFile(source, fileName, mimeType) {
369
369
  blob
370
370
  };
371
371
  }
372
+ function getFileNameFromUrl(source) {
373
+ const rawName = source.split(/[?#]/, 1)[0]?.split("/").filter(Boolean).pop() || "";
374
+ if (!rawName) {
375
+ return "";
376
+ }
377
+ try {
378
+ return decodeURIComponent(rawName);
379
+ } catch {
380
+ return rawName;
381
+ }
382
+ }
372
383
  function getExtension(name) {
373
384
  const clean = name.split("?")[0]?.split("#")[0] || "";
374
385
  const index = clean.lastIndexOf(".");
@@ -5421,7 +5432,13 @@ async function renderSheet(panel, arrayBuffer, extension) {
5421
5432
  const xlsx = await import("xlsx");
5422
5433
  let workbook;
5423
5434
  try {
5424
- workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), { type: "string", FS: extension === "tsv" ? " " : "," }) : xlsx.read(arrayBuffer, { type: "array" });
5435
+ workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), {
5436
+ type: "string",
5437
+ FS: extension === "tsv" ? " " : ",",
5438
+ cellDates: true,
5439
+ cellNF: true,
5440
+ cellStyles: true
5441
+ }) : xlsx.read(arrayBuffer, { type: "array", cellDates: true, cellNF: true, cellStyles: true });
5425
5442
  } catch (error) {
5426
5443
  if (isLegacyOfficeBinary(extension)) {
5427
5444
  renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
@@ -5449,7 +5466,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5449
5466
  const heading = document.createElement("h3");
5450
5467
  heading.textContent = sheetName;
5451
5468
  const sheet = workbook.Sheets[sheetName];
5452
- const range = xlsx.utils.decode_range(sheet["!ref"] || "A1:A1");
5469
+ const range = trimWorkbookSheetRange(sheet, xlsx.utils.decode_range(sheet["!ref"] || "A1:A1"), xlsx.utils.decode_cell);
5453
5470
  const rowCount = range.e.r - range.s.r + 1;
5454
5471
  const columnCount = range.e.c - range.s.c + 1;
5455
5472
  const formulaRows = collectFormulaRows(sheet, range, xlsx.utils.encode_cell);
@@ -5459,6 +5476,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
5459
5476
  const tableWrapper = document.createElement("div");
5460
5477
  tableWrapper.className = "ofv-table-scroll";
5461
5478
  const viewport = createSheetViewport(rowCount, columnCount);
5479
+ const columnSizing = { widths: /* @__PURE__ */ new Map() };
5462
5480
  const windowControls = createSheetWindowControls(viewport, () => renderTableWindow());
5463
5481
  const renderTableWindow = () => {
5464
5482
  tableWrapper.replaceChildren(
@@ -5468,7 +5486,9 @@ async function renderSheet(panel, arrayBuffer, extension) {
5468
5486
  sheetIndex,
5469
5487
  viewport,
5470
5488
  xlsx.utils.encode_cell,
5471
- xlsx.utils.format_cell
5489
+ xlsx.utils.format_cell,
5490
+ columnSizing,
5491
+ renderTableWindow
5472
5492
  )
5473
5493
  );
5474
5494
  windowControls?.update();
@@ -5578,9 +5598,10 @@ function renderParsedSheets(panel, sheets, emptyMessage) {
5578
5598
  const tableWrapper = document.createElement("div");
5579
5599
  tableWrapper.className = "ofv-table-scroll";
5580
5600
  const viewport = createSheetViewport(rowCount, columnCount);
5601
+ const columnSizing = { widths: /* @__PURE__ */ new Map() };
5581
5602
  const windowControls = createSheetWindowControls(viewport, () => renderTableWindow());
5582
5603
  const renderTableWindow = () => {
5583
- tableWrapper.replaceChildren(createParsedSheetTable(sheet, sheetIndex, viewport));
5604
+ tableWrapper.replaceChildren(createParsedSheetTable(sheet, sheetIndex, viewport, columnSizing, renderTableWindow));
5584
5605
  windowControls?.update();
5585
5606
  };
5586
5607
  content.append(heading, summary);
@@ -5807,6 +5828,45 @@ function chartText(element) {
5807
5828
  function chartStringValues(element) {
5808
5829
  return Array.from(element.querySelectorAll("*")).filter((item) => item.localName === "v" || item.localName === "t").map((item) => item.textContent?.trim() || "").filter(Boolean);
5809
5830
  }
5831
+ function trimWorkbookSheetRange(sheet, range, decodeCell) {
5832
+ let minRow = Number.POSITIVE_INFINITY;
5833
+ let minColumn = Number.POSITIVE_INFINITY;
5834
+ let maxRow = Number.NEGATIVE_INFINITY;
5835
+ let maxColumn = Number.NEGATIVE_INFINITY;
5836
+ const include = (row, column) => {
5837
+ minRow = Math.min(minRow, row);
5838
+ minColumn = Math.min(minColumn, column);
5839
+ maxRow = Math.max(maxRow, row);
5840
+ maxColumn = Math.max(maxColumn, column);
5841
+ };
5842
+ for (const [address, cell] of Object.entries(sheet)) {
5843
+ if (address.startsWith("!")) {
5844
+ continue;
5845
+ }
5846
+ if (!cell || cell.v == null && !cell.f && !cell.w && !cell.h) {
5847
+ continue;
5848
+ }
5849
+ const decoded = decodeCell(address);
5850
+ include(decoded.r, decoded.c);
5851
+ }
5852
+ for (const merge of sheet["!merges"] || []) {
5853
+ include(merge.s.r, merge.s.c);
5854
+ include(merge.e.r, merge.e.c);
5855
+ }
5856
+ if (!Number.isFinite(minRow) || !Number.isFinite(minColumn) || !Number.isFinite(maxRow) || !Number.isFinite(maxColumn)) {
5857
+ return range;
5858
+ }
5859
+ return {
5860
+ s: {
5861
+ r: Math.max(range.s.r, minRow),
5862
+ c: Math.max(range.s.c, minColumn)
5863
+ },
5864
+ e: {
5865
+ r: Math.min(range.e.r, maxRow),
5866
+ c: Math.min(range.e.c, maxColumn)
5867
+ }
5868
+ };
5869
+ }
5810
5870
  function createSheetViewport(rowCount, columnCount) {
5811
5871
  return {
5812
5872
  rowStart: 0,
@@ -5869,42 +5929,245 @@ function createWindowButton(label, onClick) {
5869
5929
  function maxStart(total, size) {
5870
5930
  return Math.max(0, total - size);
5871
5931
  }
5872
- function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell) {
5932
+ function createWorkbookSheetTable(sheet, range, sheetIndex, viewport, encodeCell, formatCell, columnSizing, rerender) {
5873
5933
  const table = document.createElement("table");
5874
5934
  table.id = `ofv-sheet-${sheetIndex + 1}`;
5935
+ table.className = "ofv-workbook-table";
5875
5936
  const rowEnd = Math.min(range.s.r + viewport.rowStart + SHEET_WINDOW_ROWS - 1, range.e.r);
5876
5937
  const columnEnd = Math.min(range.s.c + viewport.columnStart + SHEET_WINDOW_COLUMNS - 1, range.e.c);
5877
- for (let rowIndex = range.s.r + viewport.rowStart; rowIndex <= rowEnd; rowIndex += 1) {
5938
+ const columnStart = range.s.c + viewport.columnStart;
5939
+ const rowStart = range.s.r + viewport.rowStart;
5940
+ const mergePlan = createSheetMergePlan(sheet["!merges"] || [], rowStart, rowEnd, columnStart, columnEnd);
5941
+ const colGroup = document.createElement("colgroup");
5942
+ let tableWidth = 0;
5943
+ for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex += 1) {
5944
+ const col = document.createElement("col");
5945
+ const width = columnSizing.widths.get(columnIndex) ?? getSheetColumnWidth(sheet["!cols"]?.[columnIndex]);
5946
+ col.dataset.columnIndex = String(columnIndex);
5947
+ col.style.width = `${width}px`;
5948
+ tableWidth += width;
5949
+ colGroup.append(col);
5950
+ }
5951
+ table.style.width = `${tableWidth}px`;
5952
+ table.append(colGroup);
5953
+ for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex += 1) {
5878
5954
  const row = document.createElement("tr");
5879
- for (let columnIndex = range.s.c + viewport.columnStart; columnIndex <= columnEnd; columnIndex += 1) {
5880
- const cell = document.createElement(rowIndex === range.s.r ? "th" : "td");
5955
+ const rowHeight = getSheetRowHeight(sheet["!rows"]?.[rowIndex]);
5956
+ if (rowHeight) {
5957
+ row.style.height = `${rowHeight}px`;
5958
+ }
5959
+ for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex += 1) {
5881
5960
  const address = encodeCell({ r: rowIndex, c: columnIndex });
5882
- const sourceCell = sheet[address];
5961
+ const coordinateKey = `${rowIndex}:${columnIndex}`;
5962
+ if (mergePlan.covered.has(coordinateKey)) {
5963
+ continue;
5964
+ }
5965
+ const merge = mergePlan.anchors.get(coordinateKey);
5966
+ const sourceAddress = merge ? encodeCell({ r: merge.sourceRow, c: merge.sourceColumn }) : address;
5967
+ const sourceCell = sheet[sourceAddress];
5968
+ const cell = document.createElement(rowIndex === range.s.r ? "th" : "td");
5883
5969
  cell.dataset.cell = address;
5970
+ if (sourceAddress !== address) {
5971
+ cell.dataset.sourceCell = sourceAddress;
5972
+ }
5973
+ if (merge) {
5974
+ cell.classList.add("ofv-cell-merged");
5975
+ if (merge.rowspan > 1) {
5976
+ cell.rowSpan = merge.rowspan;
5977
+ }
5978
+ if (merge.colspan > 1) {
5979
+ cell.colSpan = merge.colspan;
5980
+ }
5981
+ }
5884
5982
  const text = sourceCell ? formatCell(sourceCell) : "";
5885
5983
  cell.textContent = text;
5886
5984
  if (text) {
5887
5985
  cell.title = text;
5888
5986
  }
5987
+ applyWorkbookCellStyle(cell, sourceCell);
5889
5988
  if (sourceCell?.f) {
5890
5989
  cell.classList.add("ofv-cell-formula");
5891
5990
  cell.title = `=${sourceCell.f}`;
5892
5991
  }
5992
+ if (text.includes("\n")) {
5993
+ cell.classList.add("ofv-cell-multiline");
5994
+ }
5995
+ appendColumnResizeHandle(cell, columnIndex, columnSizing);
5893
5996
  row.append(cell);
5894
5997
  }
5895
5998
  table.append(row);
5896
5999
  }
5897
6000
  return table;
5898
6001
  }
5899
- function createParsedSheetTable(sheet, sheetIndex, viewport) {
6002
+ function appendColumnResizeHandle(cell, columnIndex, columnSizing) {
6003
+ const handle = document.createElement("span");
6004
+ handle.className = "ofv-column-resize-handle";
6005
+ handle.setAttribute("aria-hidden", "true");
6006
+ handle.addEventListener("pointerdown", (event) => {
6007
+ event.preventDefault();
6008
+ event.stopPropagation();
6009
+ const startX = event.clientX;
6010
+ const startWidth = columnSizing.widths.get(columnIndex) ?? cell.getBoundingClientRect().width;
6011
+ handle.setPointerCapture(event.pointerId);
6012
+ const onMove = (moveEvent) => {
6013
+ const nextWidth = Math.max(48, Math.min(720, Math.round(startWidth + moveEvent.clientX - startX)));
6014
+ columnSizing.widths.set(columnIndex, nextWidth);
6015
+ updateRenderedColumnWidth(cell, columnIndex, nextWidth);
6016
+ };
6017
+ const onEnd = () => {
6018
+ handle.removeEventListener("pointermove", onMove);
6019
+ handle.removeEventListener("pointerup", onEnd);
6020
+ handle.removeEventListener("pointercancel", onEnd);
6021
+ };
6022
+ handle.addEventListener("pointermove", onMove);
6023
+ handle.addEventListener("pointerup", onEnd);
6024
+ handle.addEventListener("pointercancel", onEnd);
6025
+ });
6026
+ cell.append(handle);
6027
+ }
6028
+ function updateRenderedColumnWidth(cell, columnIndex, width) {
6029
+ const table = cell.closest("table");
6030
+ if (!table) {
6031
+ return;
6032
+ }
6033
+ const column = Array.from(table.querySelectorAll("col")).find(
6034
+ (col) => col.dataset.columnIndex === String(columnIndex)
6035
+ );
6036
+ if (column) {
6037
+ column.style.width = `${width}px`;
6038
+ }
6039
+ const tableWidth = Array.from(table.querySelectorAll("col")).reduce((sum, col) => {
6040
+ const parsed = Number.parseFloat(col.style.width);
6041
+ return sum + (Number.isFinite(parsed) ? parsed : 0);
6042
+ }, 0);
6043
+ if (tableWidth > 0) {
6044
+ table.style.width = `${Math.round(tableWidth)}px`;
6045
+ }
6046
+ }
6047
+ function createSheetMergePlan(merges, rowStart, rowEnd, columnStart, columnEnd) {
6048
+ const anchors = /* @__PURE__ */ new Map();
6049
+ const covered = /* @__PURE__ */ new Set();
6050
+ const encode = (row, column) => `${row}:${column}`;
6051
+ for (const merge of merges) {
6052
+ if (merge.e.r < rowStart || merge.s.r > rowEnd || merge.e.c < columnStart || merge.s.c > columnEnd) {
6053
+ continue;
6054
+ }
6055
+ const visibleStartRow = Math.max(merge.s.r, rowStart);
6056
+ const visibleEndRow = Math.min(merge.e.r, rowEnd);
6057
+ const visibleStartColumn = Math.max(merge.s.c, columnStart);
6058
+ const visibleEndColumn = Math.min(merge.e.c, columnEnd);
6059
+ const anchor = encode(visibleStartRow, visibleStartColumn);
6060
+ anchors.set(anchor, {
6061
+ rowspan: visibleEndRow - visibleStartRow + 1,
6062
+ colspan: visibleEndColumn - visibleStartColumn + 1,
6063
+ sourceRow: merge.s.r,
6064
+ sourceColumn: merge.s.c
6065
+ });
6066
+ for (let rowIndex = visibleStartRow; rowIndex <= visibleEndRow; rowIndex += 1) {
6067
+ for (let columnIndex = visibleStartColumn; columnIndex <= visibleEndColumn; columnIndex += 1) {
6068
+ const address = encode(rowIndex, columnIndex);
6069
+ if (address !== anchor) {
6070
+ covered.add(address);
6071
+ }
6072
+ }
6073
+ }
6074
+ }
6075
+ return { anchors, covered };
6076
+ }
6077
+ function getSheetColumnWidth(column) {
6078
+ if (column?.hidden) {
6079
+ return 0;
6080
+ }
6081
+ const width = column?.wpx || (column?.wch ? column.wch * 7 + 5 : void 0) || (column?.width ? column.width * 7 : void 0) || 96;
6082
+ return Math.max(28, Math.min(360, Math.round(width)));
6083
+ }
6084
+ function getSheetRowHeight(row) {
6085
+ if (row?.hidden) {
6086
+ return 0;
6087
+ }
6088
+ const height = row?.hpx || (row?.hpt ? row.hpt * 1.333 : void 0);
6089
+ return height ? Math.max(18, Math.min(260, Math.round(height))) : void 0;
6090
+ }
6091
+ function applyWorkbookCellStyle(cell, sourceCell) {
6092
+ const style = sourceCell?.s;
6093
+ if (!style) {
6094
+ return;
6095
+ }
6096
+ const fill = readWorkbookColor(style.fgColor || style.fill?.fgColor);
6097
+ if (fill && style.patternType !== "none") {
6098
+ cell.style.backgroundColor = fill;
6099
+ }
6100
+ const font = style.font;
6101
+ if (font) {
6102
+ if (font.bold) {
6103
+ cell.style.fontWeight = "700";
6104
+ }
6105
+ if (font.italic) {
6106
+ cell.style.fontStyle = "italic";
6107
+ }
6108
+ if (font.sz) {
6109
+ cell.style.fontSize = `${Math.max(9, Math.min(24, Number(font.sz)))}pt`;
6110
+ }
6111
+ const fontColor = readWorkbookColor(font.color);
6112
+ if (fontColor) {
6113
+ cell.style.color = fontColor;
6114
+ }
6115
+ }
6116
+ const alignment = style.alignment;
6117
+ if (alignment) {
6118
+ const horizontal = normalizeSheetHorizontalAlign(alignment.horizontal);
6119
+ if (horizontal) {
6120
+ cell.style.textAlign = horizontal;
6121
+ }
6122
+ const vertical = normalizeSheetVerticalAlign(alignment.vertical);
6123
+ if (vertical) {
6124
+ cell.style.verticalAlign = vertical;
6125
+ }
6126
+ if (alignment.wrapText) {
6127
+ cell.classList.add("ofv-cell-multiline");
6128
+ }
6129
+ }
6130
+ }
6131
+ function readWorkbookColor(color) {
6132
+ if (!color?.rgb) {
6133
+ return void 0;
6134
+ }
6135
+ const rgb = color.rgb.length === 8 ? color.rgb.slice(2) : color.rgb;
6136
+ return /^[\da-f]{6}$/i.test(rgb) ? `#${rgb}` : void 0;
6137
+ }
6138
+ function normalizeSheetHorizontalAlign(value) {
6139
+ if (value === "center" || value === "right" || value === "left" || value === "justify") {
6140
+ return value;
6141
+ }
6142
+ return void 0;
6143
+ }
6144
+ function normalizeSheetVerticalAlign(value) {
6145
+ if (value === "top" || value === "middle" || value === "bottom") {
6146
+ return value;
6147
+ }
6148
+ return void 0;
6149
+ }
6150
+ function createParsedSheetTable(sheet, sheetIndex, viewport, columnSizing, rerender) {
5900
6151
  const table = document.createElement("table");
5901
6152
  table.id = `ofv-sheet-${sheetIndex + 1}`;
5902
6153
  const formulaMap = new Map(sheet.formulas.map((item) => [item.address, item.formula]));
5903
6154
  const rowEnd = Math.min(viewport.rowStart + SHEET_WINDOW_ROWS, sheet.rows.length);
6155
+ const columnEnd = Math.min(viewport.columnStart + SHEET_WINDOW_COLUMNS, viewport.columnCount);
6156
+ const colGroup = document.createElement("colgroup");
6157
+ let tableWidth = 0;
6158
+ for (let columnIndex = viewport.columnStart; columnIndex < columnEnd; columnIndex += 1) {
6159
+ const width = columnSizing.widths.get(columnIndex) ?? 112;
6160
+ const col = document.createElement("col");
6161
+ col.dataset.columnIndex = String(columnIndex);
6162
+ col.style.width = `${width}px`;
6163
+ tableWidth += width;
6164
+ colGroup.append(col);
6165
+ }
6166
+ table.style.width = `${tableWidth}px`;
6167
+ table.append(colGroup);
5904
6168
  for (let rowIndex = viewport.rowStart; rowIndex < rowEnd; rowIndex += 1) {
5905
6169
  const sourceRow = sheet.rows[rowIndex] || [];
5906
6170
  const row = document.createElement("tr");
5907
- const columnEnd = Math.min(viewport.columnStart + SHEET_WINDOW_COLUMNS, viewport.columnCount);
5908
6171
  for (let columnIndex = viewport.columnStart; columnIndex < columnEnd; columnIndex += 1) {
5909
6172
  const value = sourceRow[columnIndex] || "";
5910
6173
  const cell = document.createElement(rowIndex === 0 ? "th" : "td");
@@ -5919,6 +6182,10 @@ function createParsedSheetTable(sheet, sheetIndex, viewport) {
5919
6182
  cell.classList.add("ofv-cell-formula");
5920
6183
  cell.title = formula;
5921
6184
  }
6185
+ if (value.includes("\n")) {
6186
+ cell.classList.add("ofv-cell-multiline");
6187
+ }
6188
+ appendColumnResizeHandle(cell, columnIndex, columnSizing);
5922
6189
  row.append(cell);
5923
6190
  }
5924
6191
  table.append(row);
@@ -9979,6 +10246,521 @@ function decodeDrawioDiagram(value) {
9979
10246
 
9980
10247
  // src/plugins/cad.ts
9981
10248
  var import_pako3 = __toESM(require("pako"), 1);
10249
+
10250
+ // src/plugins/cad-dwg.ts
10251
+ var libreDwgPromise;
10252
+ var defaultLibreDwgWasmBaseUrl = "/vendor/libredwg-web";
10253
+ var minReadableDrawingHeight = 420;
10254
+ var svgNumberPattern = /-?\d*\.?\d+(?:e[-+]?\d+)?/gi;
10255
+ async function renderLibreDwgPreview(ctx, options = {}) {
10256
+ if (ctx.extension !== "dwg" || options.enabled === false) {
10257
+ return void 0;
10258
+ }
10259
+ const shell = document.createElement("div");
10260
+ shell.className = "ofv-dwg-preview";
10261
+ const status = document.createElement("div");
10262
+ status.className = "ofv-dwg-preview-status";
10263
+ status.textContent = "Loading DWG rendering engine...";
10264
+ shell.append(status);
10265
+ ctx.panel.append(shell);
10266
+ try {
10267
+ const { LibreDwg, Dwg_File_Type } = await loadLibreDwg();
10268
+ const libredwg = await LibreDwg.create(options.wasmBaseUrl || defaultLibreDwgWasmBaseUrl);
10269
+ const data = libredwg.dwg_read_data(ctx.arrayBuffer, Dwg_File_Type.DWG);
10270
+ if (!data) {
10271
+ throw new Error("DWG parser did not return drawing data.");
10272
+ }
10273
+ let svg = "";
10274
+ let stats;
10275
+ let thumbnailUrl;
10276
+ try {
10277
+ thumbnailUrl = createDwgThumbnailUrl(readDwgThumbnail(libredwg, data));
10278
+ try {
10279
+ const result = libredwg.convertEx(data);
10280
+ const database = result.database;
10281
+ stats = createDwgPreviewStats(database, result.stats.unknownEntityCount, Boolean(thumbnailUrl));
10282
+ svg = libredwg.dwg_to_svg(database);
10283
+ } catch (error) {
10284
+ if (thumbnailUrl) {
10285
+ const fallbackThumbnailUrl = thumbnailUrl;
10286
+ status.replaceChildren(createDwgThumbnailFallbackStatus(ctx.fileName, error));
10287
+ shell.append(createDwgThumbnailPreview(fallbackThumbnailUrl, ctx.fileName));
10288
+ return {
10289
+ destroy() {
10290
+ URL.revokeObjectURL(fallbackThumbnailUrl);
10291
+ shell.remove();
10292
+ }
10293
+ };
10294
+ }
10295
+ throw error;
10296
+ }
10297
+ } finally {
10298
+ libredwg.dwg_free(data);
10299
+ }
10300
+ if (!svg || !/<svg[\s>]/i.test(svg)) {
10301
+ if (thumbnailUrl) {
10302
+ const fallbackThumbnailUrl = thumbnailUrl;
10303
+ status.replaceChildren(createDwgThumbnailFallbackStatus(ctx.fileName, "DWG parser finished but did not produce SVG output."));
10304
+ shell.append(createDwgThumbnailPreview(fallbackThumbnailUrl, ctx.fileName));
10305
+ return {
10306
+ destroy() {
10307
+ URL.revokeObjectURL(fallbackThumbnailUrl);
10308
+ shell.remove();
10309
+ }
10310
+ };
10311
+ }
10312
+ throw new Error("DWG parser finished but did not produce SVG output.");
10313
+ }
10314
+ const doc = new DOMParser().parseFromString(svg, "image/svg+xml");
10315
+ const svgElement = doc.documentElement;
10316
+ if (!(svgElement instanceof SVGElement) || svgElement.nodeName.toLowerCase() !== "svg" || svgElement.querySelector("parsererror")) {
10317
+ throw new Error("DWG SVG output is invalid.");
10318
+ }
10319
+ svgElement.classList.add("ofv-dwg-preview-svg");
10320
+ svgElement.setAttribute("role", "img");
10321
+ svgElement.setAttribute("aria-label", ctx.fileName);
10322
+ normalizeDwgSvg(svgElement);
10323
+ const reliability = assessDwgSvgReliability(svgElement);
10324
+ status.replaceChildren(createDwgStatusTitle(ctx.fileName, stats, reliability));
10325
+ if (!reliability.isReliable && thumbnailUrl) {
10326
+ shell.append(createDwgThumbnailPreview(thumbnailUrl, ctx.fileName));
10327
+ return {
10328
+ destroy() {
10329
+ URL.revokeObjectURL(thumbnailUrl);
10330
+ shell.remove();
10331
+ }
10332
+ };
10333
+ }
10334
+ const drawing = createDwgDrawingViewport(svgElement);
10335
+ if (thumbnailUrl) {
10336
+ shell.append(createDwgThumbnailPanel(thumbnailUrl));
10337
+ }
10338
+ shell.append(drawing.frame);
10339
+ return {
10340
+ resize() {
10341
+ drawing.update();
10342
+ },
10343
+ canCommand(command) {
10344
+ return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
10345
+ },
10346
+ command(command) {
10347
+ if (command === "zoom-in") {
10348
+ drawing.setZoom(drawing.zoom * 1.18);
10349
+ return true;
10350
+ }
10351
+ if (command === "zoom-out") {
10352
+ drawing.setZoom(drawing.zoom / 1.18);
10353
+ return true;
10354
+ }
10355
+ if (command === "zoom-reset") {
10356
+ drawing.setZoom(1);
10357
+ return true;
10358
+ }
10359
+ return false;
10360
+ },
10361
+ destroy() {
10362
+ if (thumbnailUrl) {
10363
+ URL.revokeObjectURL(thumbnailUrl);
10364
+ }
10365
+ shell.remove();
10366
+ }
10367
+ };
10368
+ } catch (error) {
10369
+ shell.remove();
10370
+ console.warn("DWG LibreDWG preview failed, falling back to metadata preview:", error);
10371
+ return void 0;
10372
+ }
10373
+ }
10374
+ function loadLibreDwg() {
10375
+ libreDwgPromise ||= import("@mlightcad/libredwg-web");
10376
+ return libreDwgPromise;
10377
+ }
10378
+ function createDwgStatusTitle(fileName, stats, reliability) {
10379
+ const wrapper = document.createElement("span");
10380
+ const title = document.createElement("strong");
10381
+ const note = document.createElement("small");
10382
+ title.textContent = reliability.isReliable ? `\u5B9E\u9A8C\u6027 DWG \u6A21\u578B\u7A7A\u95F4\u9884\u89C8 \xB7 ${fileName}` : `DWG \u5185\u7F6E\u9884\u89C8\u56FE \xB7 ${fileName}`;
10383
+ note.textContent = [
10384
+ `${stats.entityCount.toLocaleString()} \u4E2A\u5B9E\u4F53`,
10385
+ `${stats.visibleLayerCount}/${stats.layerCount} \u4E2A\u53EF\u89C1\u56FE\u5C42`,
10386
+ `${stats.layoutCount} \u4E2A\u5E03\u5C40`,
10387
+ stats.paperSpaceEntityCount ? `${stats.paperSpaceEntityCount.toLocaleString()} \u4E2A\u56FE\u7EB8\u7A7A\u95F4\u5B9E\u4F53` : "\u6A21\u578B\u7A7A\u95F4\u7EBF\u7A3F",
10388
+ stats.unknownEntityCount ? `${stats.unknownEntityCount} \u4E2A\u672A\u77E5\u5B9E\u4F53` : "\u5B9E\u4F53\u89E3\u6790\u5B8C\u6574",
10389
+ stats.hasThumbnail ? "\u5305\u542B\u5185\u7F6E\u7F29\u7565\u56FE" : "\u65E0\u5185\u7F6E\u7F29\u7565\u56FE"
10390
+ ].join(" \xB7 ");
10391
+ const warning = document.createElement("small");
10392
+ warning.textContent = reliability.isReliable ? "\u5F53\u524D\u4E3A LibreDWG WASM \u7EBF\u7A3F\u9884\u89C8\uFF0C\u590D\u6742\u5E03\u5C40/\u6253\u5370\u7A7A\u95F4/\u5B57\u4F53/\u586B\u5145\u4E0E\u4E13\u4E1A CAD \u4ECD\u53EF\u80FD\u5B58\u5728\u5DEE\u5F02\u3002" : `LibreDWG \u7EBF\u7A3F\u68C0\u6D4B\u5230\u5F02\u5E38\u56FE\u5143\uFF0C\u5DF2\u4F18\u5148\u663E\u793A\u6587\u4EF6\u5185\u7F6E\u9884\u89C8\u56FE\u3002${reliability.reason ?? ""}`;
10393
+ wrapper.append(title, note, warning);
10394
+ return wrapper;
10395
+ }
10396
+ function createDwgThumbnailFallbackStatus(fileName, error) {
10397
+ const wrapper = document.createElement("span");
10398
+ const title = document.createElement("strong");
10399
+ title.textContent = `DWG \u5185\u7F6E\u9884\u89C8\u56FE \xB7 ${fileName}`;
10400
+ const note = document.createElement("small");
10401
+ note.textContent = "LibreDWG \u5DF2\u8BFB\u53D6\u5230\u6587\u4EF6\u5185\u7F6E\u7F29\u7565\u56FE\uFF0C\u4F46\u7EBF\u7A3F\u9884\u89C8\u672A\u80FD\u751F\u6210\uFF0C\u5DF2\u81EA\u52A8\u5207\u6362\u4E3A\u7F29\u7565\u56FE\u515C\u5E95\u3002";
10402
+ const detail = document.createElement("small");
10403
+ detail.textContent = error instanceof Error ? error.message : String(error || "\u672A\u77E5\u89E3\u6790\u9519\u8BEF");
10404
+ wrapper.append(title, note, detail);
10405
+ return wrapper;
10406
+ }
10407
+ function createDwgPreviewStats(database, unknownEntityCount, hasThumbnail) {
10408
+ const layers = database.tables.LAYER.entries;
10409
+ return {
10410
+ entityCount: database.entities.length,
10411
+ layerCount: layers.length,
10412
+ layoutCount: database.objects.LAYOUT.length,
10413
+ unknownEntityCount,
10414
+ visibleLayerCount: layers.filter((layer) => !layer.off && !layer.frozen).length,
10415
+ paperSpaceEntityCount: database.entities.filter((entity) => entity.isInPaperSpace).length,
10416
+ hasThumbnail
10417
+ };
10418
+ }
10419
+ function readDwgThumbnail(libredwg, data) {
10420
+ try {
10421
+ const thumbnail = libredwg.dwg_bmp(data);
10422
+ if (!thumbnail?.data?.length) {
10423
+ return void 0;
10424
+ }
10425
+ return thumbnail;
10426
+ } catch {
10427
+ return void 0;
10428
+ }
10429
+ }
10430
+ function createDwgThumbnailUrl(thumbnail) {
10431
+ if (!thumbnail) {
10432
+ return void 0;
10433
+ }
10434
+ if (thumbnail.type === 6) {
10435
+ return URL.createObjectURL(new Blob([toArrayBuffer(thumbnail.data)], { type: "image/png" }));
10436
+ }
10437
+ if (thumbnail.type === 2) {
10438
+ return URL.createObjectURL(new Blob([toArrayBuffer(createBmpFileBytes(thumbnail.data))], { type: "image/bmp" }));
10439
+ }
10440
+ return void 0;
10441
+ }
10442
+ function toArrayBuffer(bytes) {
10443
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
10444
+ }
10445
+ function createBmpFileBytes(dibBytes) {
10446
+ const view3 = new DataView(dibBytes.buffer, dibBytes.byteOffset, dibBytes.byteLength);
10447
+ const headerSize = view3.getUint32(0, true);
10448
+ const bitCount = view3.getUint16(14, true);
10449
+ const paletteBytes = bitCount <= 8 ? 2 ** bitCount * 4 : 0;
10450
+ const pixelOffset = 14 + headerSize + paletteBytes;
10451
+ const bytes = new Uint8Array(14 + dibBytes.byteLength);
10452
+ bytes[0] = 66;
10453
+ bytes[1] = 77;
10454
+ const fileView = new DataView(bytes.buffer);
10455
+ fileView.setUint32(2, bytes.byteLength, true);
10456
+ fileView.setUint32(10, pixelOffset, true);
10457
+ bytes.set(dibBytes, 14);
10458
+ return bytes;
10459
+ }
10460
+ function createDwgThumbnailPanel(thumbnailUrl) {
10461
+ const panel = document.createElement("figure");
10462
+ panel.className = "ofv-dwg-thumbnail";
10463
+ const image = document.createElement("img");
10464
+ image.src = thumbnailUrl;
10465
+ image.alt = "DWG \u6587\u4EF6\u5185\u7F6E\u7F29\u7565\u56FE";
10466
+ const caption = document.createElement("figcaption");
10467
+ caption.textContent = "\u6587\u4EF6\u5185\u7F6E\u7F29\u7565\u56FE";
10468
+ panel.append(image, caption);
10469
+ return panel;
10470
+ }
10471
+ function createDwgThumbnailPreview(thumbnailUrl, fileName) {
10472
+ const figure = document.createElement("figure");
10473
+ figure.className = "ofv-dwg-thumbnail-preview";
10474
+ const image = document.createElement("img");
10475
+ image.src = thumbnailUrl;
10476
+ image.alt = `${fileName} \u5185\u7F6E\u9884\u89C8\u56FE`;
10477
+ const caption = document.createElement("figcaption");
10478
+ caption.textContent = "DWG \u6587\u4EF6\u5185\u7F6E\u9884\u89C8\u56FE\u3002\u82E5\u9700\u8981\u63A5\u8FD1 CAD \u5E03\u5C40/\u6253\u5370\u7A7A\u95F4\u7684\u9AD8\u4FDD\u771F\u6548\u679C\uFF0C\u8BF7\u4F7F\u7528\u540C\u540D PNG\u3001SVG\u3001PDF \u5BFC\u51FA\u56FE\uFF0C\u6216\u901A\u8FC7 binaryRenderer \u63A5\u5165\u4E13\u4E1A CAD \u6E32\u67D3/\u8F6C\u6362\u670D\u52A1\u3002";
10479
+ figure.append(image, caption);
10480
+ return figure;
10481
+ }
10482
+ function normalizeDwgSvg(svgElement) {
10483
+ svgElement.setAttribute("preserveAspectRatio", "xMidYMid meet");
10484
+ removeInheritedDwgFills(svgElement);
10485
+ focusDwgSvgOnMainDrawing(svgElement);
10486
+ const style = document.createElementNS("http://www.w3.org/2000/svg", "style");
10487
+ style.textContent = `
10488
+ .ofv-dwg-preview-svg { background: #020617; }
10489
+ .ofv-dwg-preview-svg g {
10490
+ fill: none !important;
10491
+ }
10492
+ .ofv-dwg-preview-svg line,
10493
+ .ofv-dwg-preview-svg path,
10494
+ .ofv-dwg-preview-svg polyline,
10495
+ .ofv-dwg-preview-svg polygon,
10496
+ .ofv-dwg-preview-svg circle,
10497
+ .ofv-dwg-preview-svg ellipse,
10498
+ .ofv-dwg-preview-svg rect {
10499
+ fill: none !important;
10500
+ vector-effect: non-scaling-stroke;
10501
+ stroke-width: 0.7px !important;
10502
+ stroke-linecap: round;
10503
+ stroke-linejoin: round;
10504
+ }
10505
+ .ofv-dwg-preview-svg [stroke="rgb(0,255,0)"] {
10506
+ stroke: #34d399 !important;
10507
+ stroke-opacity: 0.58 !important;
10508
+ }
10509
+ .ofv-dwg-preview-svg text {
10510
+ vector-effect: non-scaling-stroke;
10511
+ stroke-width: 0 !important;
10512
+ fill: currentColor !important;
10513
+ }
10514
+ `;
10515
+ svgElement.prepend(style);
10516
+ }
10517
+ function removeInheritedDwgFills(svgElement) {
10518
+ const shapeSelector = "line,path,polyline,polygon,circle,ellipse,rect";
10519
+ for (const group of svgElement.querySelectorAll("g[fill]")) {
10520
+ group.setAttribute("fill", "none");
10521
+ }
10522
+ for (const styledElement of svgElement.querySelectorAll("[style]")) {
10523
+ styledElement.style.fill = "none";
10524
+ }
10525
+ for (const shape of svgElement.querySelectorAll(shapeSelector)) {
10526
+ shape.setAttribute("fill", "none");
10527
+ }
10528
+ }
10529
+ function assessDwgSvgReliability(svgElement) {
10530
+ const bounds = readSvgViewBox(svgElement);
10531
+ if (!bounds) {
10532
+ return { isReliable: true };
10533
+ }
10534
+ const largePathCount = countLargeSvgPaths(svgElement, bounds);
10535
+ const totalPathCount = svgElement.querySelectorAll("path").length;
10536
+ if (largePathCount >= 24 && totalPathCount > 0 && largePathCount / totalPathCount > 0.08) {
10537
+ return {
10538
+ isReliable: false,
10539
+ reason: "\u8BE5\u6587\u4EF6\u5305\u542B\u5927\u91CF\u8D85\u51FA\u4E3B\u4F53\u89C6\u53E3\u7684\u5927\u8DEF\u5F84/\u5757\u53C2\u7167\uFF0C\u7EBF\u7A3F\u6A21\u5F0F\u4F1A\u660E\u663E\u504F\u79BB CAD \u5E03\u5C40\u3002"
10540
+ };
10541
+ }
10542
+ return { isReliable: true };
10543
+ }
10544
+ function countLargeSvgPaths(svgElement, bounds) {
10545
+ let count = 0;
10546
+ const viewportArea = bounds.width * bounds.height;
10547
+ if (!Number.isFinite(viewportArea) || viewportArea <= 0) {
10548
+ return count;
10549
+ }
10550
+ for (const path of svgElement.querySelectorAll("path")) {
10551
+ if (path.closest("defs")) {
10552
+ continue;
10553
+ }
10554
+ const pathBounds = estimatePathBounds(path);
10555
+ if (!pathBounds) {
10556
+ continue;
10557
+ }
10558
+ const pathArea = pathBounds.width * pathBounds.height;
10559
+ const crossesViewport = pathBounds.minX <= bounds.minX + bounds.width * 0.02 || pathBounds.minY <= bounds.minY + bounds.height * 0.02;
10560
+ if (pathArea > viewportArea * 0.28 || crossesViewport && pathArea > viewportArea * 0.12) {
10561
+ count += 1;
10562
+ }
10563
+ }
10564
+ return count;
10565
+ }
10566
+ function estimatePathBounds(path) {
10567
+ const numbers = readNumbers(path.getAttribute("d") ?? "");
10568
+ if (numbers.length < 4) {
10569
+ return void 0;
10570
+ }
10571
+ let minX = Number.POSITIVE_INFINITY;
10572
+ let minY = Number.POSITIVE_INFINITY;
10573
+ let maxX = Number.NEGATIVE_INFINITY;
10574
+ let maxY = Number.NEGATIVE_INFINITY;
10575
+ for (let index = 0; index + 1 < numbers.length; index += 2) {
10576
+ const x = numbers[index];
10577
+ const y = numbers[index + 1];
10578
+ if (!Number.isFinite(x) || !Number.isFinite(y) || Math.abs(x) > 1e9 || Math.abs(y) > 1e9) {
10579
+ continue;
10580
+ }
10581
+ minX = Math.min(minX, x);
10582
+ minY = Math.min(minY, y);
10583
+ maxX = Math.max(maxX, x);
10584
+ maxY = Math.max(maxY, y);
10585
+ }
10586
+ if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(maxX) || !Number.isFinite(maxY)) {
10587
+ return void 0;
10588
+ }
10589
+ return { minX, minY, width: maxX - minX, height: maxY - minY };
10590
+ }
10591
+ function focusDwgSvgOnMainDrawing(svgElement) {
10592
+ const originalBounds = readSvgViewBox(svgElement);
10593
+ const mainBounds = estimateMainDrawingBounds(svgElement);
10594
+ if (!originalBounds || !mainBounds || !shouldUseMainDrawingBounds(originalBounds, mainBounds)) {
10595
+ return;
10596
+ }
10597
+ const paddedBounds = padBounds(mainBounds, 0.05);
10598
+ svgElement.dataset.originalViewBox = formatViewBox(originalBounds);
10599
+ svgElement.dataset.focusViewBox = formatViewBox(paddedBounds);
10600
+ svgElement.setAttribute("viewBox", formatViewBox(paddedBounds));
10601
+ }
10602
+ function shouldUseMainDrawingBounds(original, candidate) {
10603
+ const originalAspectRatio = original.width / original.height;
10604
+ const candidateAspectRatio = candidate.width / candidate.height;
10605
+ const originalArea = original.width * original.height;
10606
+ const candidateArea = candidate.width * candidate.height;
10607
+ if (!Number.isFinite(originalArea) || !Number.isFinite(candidateArea) || candidateArea <= 0) {
10608
+ return false;
10609
+ }
10610
+ return originalAspectRatio > 8 || originalAspectRatio < 0.125 || candidateArea / originalArea < 0.55 || Math.abs(Math.log(originalAspectRatio / candidateAspectRatio)) > Math.log(4);
10611
+ }
10612
+ function estimateMainDrawingBounds(svgElement) {
10613
+ const points = [];
10614
+ const addPoint = (x, y) => {
10615
+ if (Number.isFinite(x) && Number.isFinite(y) && Math.abs(x) < 1e9 && Math.abs(y) < 1e9) {
10616
+ points.push([x, y]);
10617
+ }
10618
+ };
10619
+ for (const element of svgElement.querySelectorAll("line,path,polyline,polygon,circle,ellipse,rect,text,use")) {
10620
+ if (element.closest("defs")) {
10621
+ continue;
10622
+ }
10623
+ collectElementPoints(element, addPoint);
10624
+ }
10625
+ if (points.length < 16) {
10626
+ return void 0;
10627
+ }
10628
+ const xs = points.map(([x]) => x).sort((a, b) => a - b);
10629
+ const ys = points.map(([, y]) => y).sort((a, b) => a - b);
10630
+ const minX = quantile(xs, 5e-3);
10631
+ const maxX = quantile(xs, 0.995);
10632
+ const minY = quantile(ys, 5e-3);
10633
+ const maxY = quantile(ys, 0.995);
10634
+ if (!Number.isFinite(minX) || !Number.isFinite(maxX) || !Number.isFinite(minY) || !Number.isFinite(maxY)) {
10635
+ return void 0;
10636
+ }
10637
+ const width = maxX - minX;
10638
+ const height = maxY - minY;
10639
+ if (width <= 0 || height <= 0) {
10640
+ return void 0;
10641
+ }
10642
+ return { minX, minY, width, height };
10643
+ }
10644
+ function collectElementPoints(element, addPoint) {
10645
+ const tagName = element.tagName.toLowerCase();
10646
+ if (tagName === "line") {
10647
+ addPoint(readNumberAttribute(element, "x1"), readNumberAttribute(element, "y1"));
10648
+ addPoint(readNumberAttribute(element, "x2"), readNumberAttribute(element, "y2"));
10649
+ } else if (tagName === "circle") {
10650
+ const cx = readNumberAttribute(element, "cx");
10651
+ const cy = readNumberAttribute(element, "cy");
10652
+ const r = readNumberAttribute(element, "r");
10653
+ addPoint(cx - r, cy - r);
10654
+ addPoint(cx + r, cy + r);
10655
+ } else if (tagName === "ellipse") {
10656
+ const cx = readNumberAttribute(element, "cx");
10657
+ const cy = readNumberAttribute(element, "cy");
10658
+ const rx = readNumberAttribute(element, "rx");
10659
+ const ry = readNumberAttribute(element, "ry");
10660
+ addPoint(cx - rx, cy - ry);
10661
+ addPoint(cx + rx, cy + ry);
10662
+ } else if (tagName === "rect") {
10663
+ const x = readNumberAttribute(element, "x");
10664
+ const y = readNumberAttribute(element, "y");
10665
+ addPoint(x, y);
10666
+ addPoint(x + readNumberAttribute(element, "width"), y + readNumberAttribute(element, "height"));
10667
+ } else if (tagName === "text") {
10668
+ addPoint(readNumberAttribute(element, "x"), readNumberAttribute(element, "y"));
10669
+ } else if (tagName === "path") {
10670
+ collectNumberPairs(element.getAttribute("d"), addPoint);
10671
+ } else if (tagName === "polyline" || tagName === "polygon") {
10672
+ collectNumberPairs(element.getAttribute("points"), addPoint);
10673
+ }
10674
+ collectTranslatePoints(element.getAttribute("transform"), addPoint);
10675
+ }
10676
+ function collectNumberPairs(value, addPoint) {
10677
+ if (!value) {
10678
+ return;
10679
+ }
10680
+ const numbers = readNumbers(value);
10681
+ for (let index = 0; index + 1 < numbers.length; index += 2) {
10682
+ addPoint(numbers[index], numbers[index + 1]);
10683
+ }
10684
+ }
10685
+ function collectTranslatePoints(value, addPoint) {
10686
+ if (!value) {
10687
+ return;
10688
+ }
10689
+ const translatePattern = /translate\(\s*(-?\d*\.?\d+(?:e[-+]?\d+)?)(?:[\s,]+(-?\d*\.?\d+(?:e[-+]?\d+)?))?/gi;
10690
+ for (const match of value.matchAll(translatePattern)) {
10691
+ addPoint(Number(match[1]), Number(match[2] ?? 0));
10692
+ }
10693
+ }
10694
+ function readNumbers(value) {
10695
+ const matches = value.match(svgNumberPattern);
10696
+ return matches ? matches.map((item) => Number(item)).filter(Number.isFinite) : [];
10697
+ }
10698
+ function readNumberAttribute(element, name) {
10699
+ const value = Number(element.getAttribute(name) ?? 0);
10700
+ return Number.isFinite(value) ? value : 0;
10701
+ }
10702
+ function quantile(values, ratio) {
10703
+ const index = Math.max(0, Math.min(values.length - 1, Math.floor((values.length - 1) * ratio)));
10704
+ return values[index];
10705
+ }
10706
+ function padBounds(bounds, ratio) {
10707
+ const paddingX = bounds.width * ratio;
10708
+ const paddingY = bounds.height * ratio;
10709
+ return {
10710
+ minX: bounds.minX - paddingX,
10711
+ minY: bounds.minY - paddingY,
10712
+ width: bounds.width + paddingX * 2,
10713
+ height: bounds.height + paddingY * 2
10714
+ };
10715
+ }
10716
+ function formatViewBox(bounds) {
10717
+ return [bounds.minX, bounds.minY, bounds.width, bounds.height].map((value) => Number(value.toFixed(4))).join(" ");
10718
+ }
10719
+ function createDwgDrawingViewport(svgElement) {
10720
+ const frame = document.createElement("div");
10721
+ frame.className = "ofv-dwg-preview-frame";
10722
+ const importedSvg = document.importNode(svgElement, true);
10723
+ frame.append(importedSvg);
10724
+ const aspectRatio = readSvgAspectRatio(svgElement);
10725
+ let zoom = 1;
10726
+ const update = () => {
10727
+ const frameWidth = Math.max(frame.clientWidth, 1);
10728
+ const readableWidth = aspectRatio ? minReadableDrawingHeight * aspectRatio : frameWidth;
10729
+ const width = Math.max(frameWidth, readableWidth) * zoom;
10730
+ importedSvg.style.width = `${Math.round(width)}px`;
10731
+ importedSvg.style.minWidth = `${Math.round(width)}px`;
10732
+ };
10733
+ const setZoom = (nextZoom) => {
10734
+ zoom = Math.min(Math.max(nextZoom, 0.2), 8);
10735
+ update();
10736
+ };
10737
+ requestAnimationFrame(update);
10738
+ return {
10739
+ frame,
10740
+ get zoom() {
10741
+ return zoom;
10742
+ },
10743
+ setZoom,
10744
+ update
10745
+ };
10746
+ }
10747
+ function readSvgAspectRatio(svgElement) {
10748
+ const viewBox = readSvgViewBox(svgElement);
10749
+ return viewBox ? viewBox.width / viewBox.height : void 0;
10750
+ }
10751
+ function readSvgViewBox(svgElement) {
10752
+ const viewBox = svgElement.getAttribute("viewBox");
10753
+ if (!viewBox) {
10754
+ return void 0;
10755
+ }
10756
+ const [minX, minY, width, height] = viewBox.trim().split(/[\s,]+/).map((value) => Number(value));
10757
+ if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
10758
+ return void 0;
10759
+ }
10760
+ return { minX, minY, width, height };
10761
+ }
10762
+
10763
+ // src/plugins/cad.ts
9982
10764
  var cadExtensions = /* @__PURE__ */ new Set([
9983
10765
  "dxf",
9984
10766
  "dwg",
@@ -10042,7 +10824,7 @@ var cadMimeFormatMap = {
10042
10824
  "application/vnd.oasis.layout": "oas",
10043
10825
  "application/x-oasis-layout": "oas"
10044
10826
  };
10045
- function cadPlugin() {
10827
+ function cadPlugin(options = {}) {
10046
10828
  return {
10047
10829
  name: "cad",
10048
10830
  match(file) {
@@ -10065,7 +10847,64 @@ function cadPlugin() {
10065
10847
  return { destroy: () => panel.remove() };
10066
10848
  }
10067
10849
  if (extension === "dwg" || extension === "dwf") {
10068
- renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
10850
+ const arrayBuffer = await readArrayBuffer(ctx.file);
10851
+ const bytes = new Uint8Array(arrayBuffer);
10852
+ const enhancedInstance = await options.binaryRenderer?.({
10853
+ panel,
10854
+ fileName: ctx.file.name,
10855
+ extension,
10856
+ arrayBuffer,
10857
+ bytes,
10858
+ preview: ctx
10859
+ });
10860
+ if (enhancedInstance) {
10861
+ return {
10862
+ resize(size) {
10863
+ enhancedInstance.resize?.(size);
10864
+ },
10865
+ canCommand(command) {
10866
+ return enhancedInstance.canCommand?.(command) ?? false;
10867
+ },
10868
+ command(command) {
10869
+ return enhancedInstance.command?.(command);
10870
+ },
10871
+ destroy() {
10872
+ enhancedInstance.destroy();
10873
+ panel.remove();
10874
+ }
10875
+ };
10876
+ }
10877
+ if (extension === "dwg" && options.libreDwg !== false) {
10878
+ const libreDwgInstance = await renderLibreDwgPreview(
10879
+ {
10880
+ panel,
10881
+ fileName: ctx.file.name,
10882
+ extension,
10883
+ arrayBuffer,
10884
+ bytes,
10885
+ preview: ctx
10886
+ },
10887
+ typeof options.libreDwg === "object" ? options.libreDwg : void 0
10888
+ );
10889
+ if (libreDwgInstance) {
10890
+ return {
10891
+ resize(size) {
10892
+ libreDwgInstance.resize?.(size);
10893
+ },
10894
+ canCommand(command) {
10895
+ return libreDwgInstance.canCommand?.(command) ?? false;
10896
+ },
10897
+ command(command) {
10898
+ return libreDwgInstance.command?.(command);
10899
+ },
10900
+ destroy() {
10901
+ libreDwgInstance.destroy();
10902
+ panel.remove();
10903
+ }
10904
+ };
10905
+ }
10906
+ }
10907
+ renderBinaryCad(panel, bytes, extension, ctx.file.name);
10069
10908
  return { destroy: () => panel.remove() };
10070
10909
  }
10071
10910
  if (extension === "gds") {
@@ -10750,11 +11589,10 @@ function createOasisStructureShapes(cellNames, fallbackCount) {
10750
11589
  }
10751
11590
  return shapes;
10752
11591
  }
10753
- function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
10754
- const bytes = new Uint8Array(arrayBuffer);
11592
+ function renderBinaryCad(panel, bytes, extension, fileName) {
10755
11593
  const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);
10756
11594
  const note = document.createElement("p");
10757
- note.textContent = extension === "dwg" ? "DWG \u662F AutoCAD \u4E13\u6709\u4E8C\u8FDB\u5236\u683C\u5F0F\uFF0C\u5F53\u524D\u524D\u7AEF\u63D2\u4EF6\u63D0\u4F9B\u6587\u4EF6\u8BC6\u522B\u3001\u7248\u672C\u63D0\u793A\u548C\u8F6C\u6362\u5EFA\u8BAE\uFF1B\u51E0\u4F55\u6E32\u67D3\u5EFA\u8BAE\u63A5\u5165 ODA/LibreDWG/\u670D\u52A1\u7AEF\u8F6C\u6362\u3002" : "DWF \u662F\u53D1\u5E03\u7528\u56FE\u7EB8\u683C\u5F0F\uFF0C\u5F53\u524D\u524D\u7AEF\u63D2\u4EF6\u63D0\u4F9B\u5BB9\u5668\u8BC6\u522B\u548C\u8F6C\u6362\u5EFA\u8BAE\uFF1B\u9AD8\u4FDD\u771F\u9875\u9762\u6E32\u67D3\u5EFA\u8BAE\u63A5\u5165\u4E13\u7528\u89E3\u6790\u5668\u6216\u670D\u52A1\u7AEF\u8F6C\u6362\u3002";
11595
+ note.textContent = extension === "dwg" ? "\u5DF2\u8BC6\u522B DWG \u4E13\u6709\u4E8C\u8FDB\u5236\u56FE\u7EB8\u3002\u6838\u5FC3\u63D2\u4EF6\u9ED8\u8BA4\u63D0\u4F9B\u7248\u672C\u3001\u5BB9\u5668\u3001\u7ED3\u6784\u7EBF\u7D22\u548C\u63A5\u5165\u5EFA\u8BAE\uFF1B\u771F\u5B9E\u51E0\u4F55\u6E32\u67D3\u53EF\u901A\u8FC7 cadPlugin({ binaryRenderer }) \u63A5\u5165\u53EF\u9009\u524D\u7AEF\u5F15\u64CE\u6216\u8F6C\u6362\u670D\u52A1\u3002" : "\u5DF2\u8BC6\u522B DWF \u53D1\u5E03\u56FE\u7EB8\u3002\u6838\u5FC3\u63D2\u4EF6\u9ED8\u8BA4\u63D0\u4F9B\u5BB9\u5668\u7EBF\u7D22\u548C\u63A5\u5165\u5EFA\u8BAE\uFF1B\u9AD8\u4FDD\u771F\u9875\u9762\u6E32\u67D3\u53EF\u901A\u8FC7 cadPlugin({ binaryRenderer }) \u63A5\u5165\u4E13\u7528\u89E3\u6790\u5668\u6216\u8F6C\u6362\u670D\u52A1\u3002";
10758
11596
  const meta = document.createElement("div");
10759
11597
  meta.className = "ofv-cad-summary";
10760
11598
  appendMeta5(meta, "\u6587\u4EF6", fileName);
@@ -10763,33 +11601,41 @@ function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
10763
11601
  appendMeta5(meta, "\u7B7E\u540D", byteSignature2(bytes));
10764
11602
  appendMeta5(meta, "\u7248\u672C", detectCadVersion(bytes, extension));
10765
11603
  appendMeta5(meta, "\u5BB9\u5668", detectCadContainer(bytes));
10766
- const actions = document.createElement("ol");
11604
+ const actions = document.createElement("div");
10767
11605
  actions.className = "ofv-cad-conversion";
11606
+ const actionTitle = document.createElement("h4");
11607
+ actionTitle.textContent = extension === "dwg" ? "\u63A8\u8350\u589E\u5F3A\u8DEF\u7EBF" : "\u63A8\u8350\u5904\u7406\u8DEF\u7EBF";
11608
+ const actionList = document.createElement("ol");
10768
11609
  const suggestions = extension === "dwg" ? [
10769
- "\u670D\u52A1\u7AEF\u4F7F\u7528 ODA File Converter / Teigha \u5C06 DWG \u8F6C\u4E3A DXF\u3001SVG \u6216 PDF\u3002",
10770
- "\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",
10771
- "\u82E5\u4E1A\u52A1\u53EA\u9700\u9884\u89C8\uFF0C\u5EFA\u8BAE\u8F6C\u6362\u4E3A PDF/SVG \u540E\u8D70\u73B0\u6709 PDF \u6216\u56FE\u50CF\u9884\u89C8\u94FE\u8DEF\u3002"
11610
+ "\u4EA7\u54C1\u9ED8\u8BA4\u94FE\u8DEF\uFF1A\u670D\u52A1\u7AEF\u5C06 DWG \u8F6C\u4E3A PDF/SVG/DXF\uFF0C\u518D\u590D\u7528\u73B0\u6709 PDF\u3001\u56FE\u50CF\u6216 DXF \u9884\u89C8\u3002",
11611
+ "\u7EAF\u524D\u7AEF\u589E\u5F3A\uFF1A\u901A\u8FC7 binaryRenderer \u63A5\u5165 mlightcad / LibreDWG WASM \u4E00\u7C7B\u5F15\u64CE\uFF0C\u6309\u9700\u52A0\u8F7D worker \u548C\u5B57\u4F53\u8D44\u6E90\u3002",
11612
+ "\u5546\u7528\u9AD8\u4FDD\u771F\uFF1A\u63A5\u5165 ODA Drawings SDK / Web SDK\uFF0C\u9002\u5408\u590D\u6742\u56FE\u5C42\u3001\u5B57\u4F53\u3001\u5916\u90E8\u53C2\u7167\u548C\u5927\u56FE\u7EB8\u3002"
10772
11613
  ] : [
10773
11614
  "\u4F18\u5148\u5728\u670D\u52A1\u7AEF\u8F6C\u6362\u4E3A PDF/SVG\uFF0C\u4FDD\u7559\u56FE\u5C42\u3001\u9875\u9762\u548C\u6807\u6CE8\u4FE1\u606F\u3002",
10774
- "\u82E5 DWF \u4E3A\u538B\u7F29\u5BB9\u5668\uFF0C\u53EF\u540E\u7EED\u8BFB\u53D6 manifest/descriptor \u518D\u8FD8\u539F\u9875\u9762\u8D44\u6E90\u3002",
11615
+ "\u82E5 DWF \u4E3A\u538B\u7F29\u5BB9\u5668\uFF0C\u53EF\u901A\u8FC7 binaryRenderer \u8BFB\u53D6 manifest/descriptor \u518D\u8FD8\u539F\u9875\u9762\u8D44\u6E90\u3002",
10775
11616
  "\u82E5\u4E1A\u52A1\u53EA\u9700\u4E0B\u8F7D/\u5F52\u6863\uFF0C\u4FDD\u7559\u5F53\u524D\u6587\u4EF6\u5143\u4FE1\u606F\u548C\u8F6C\u6362\u63D0\u793A\u5373\u53EF\u3002"
10776
11617
  ];
10777
11618
  for (const suggestion of suggestions) {
10778
11619
  const item = document.createElement("li");
10779
11620
  item.textContent = suggestion;
10780
- actions.append(item);
11621
+ actionList.append(item);
10781
11622
  }
11623
+ actions.append(actionTitle, actionList);
11624
+ const raw = document.createElement("details");
11625
+ raw.className = "ofv-details ofv-cad-raw-preview";
11626
+ const rawSummary = document.createElement("summary");
11627
+ rawSummary.textContent = "\u539F\u59CB\u5B57\u8282\u9884\u89C8";
10782
11628
  const preview = document.createElement("pre");
10783
11629
  preview.className = "ofv-text-block";
10784
11630
  preview.textContent = hexPreview(bytes);
10785
- section.append(note, meta, createBinaryCadProbe(bytes, extension), actions, preview);
11631
+ raw.append(rawSummary, preview);
11632
+ section.append(note, meta, actions, createBinaryCadProbe(bytes, extension), raw);
10786
11633
  panel.append(section);
10787
11634
  }
10788
11635
  function createBinaryCadProbe(bytes, extension) {
10789
11636
  const probe = probeBinaryCad(bytes);
10790
11637
  const details = document.createElement("details");
10791
11638
  details.className = "ofv-details ofv-cad-binary-probe";
10792
- details.open = true;
10793
11639
  const summary = document.createElement("summary");
10794
11640
  summary.textContent = "\u4E8C\u8FDB\u5236\u7ED3\u6784\u63A2\u6D4B";
10795
11641
  const meta = document.createElement("div");
@@ -12393,6 +13239,15 @@ function assetPlugin() {
12393
13239
  const isExternal = Boolean(ctx.file.url);
12394
13240
  const extension = resolveFormat(ctx.file, assetMimeFormatMap).toLowerCase();
12395
13241
  const bytes = new Uint8Array(await readArrayBuffer(ctx.file).catch(() => new ArrayBuffer(0)));
13242
+ if (isPhotoshopAsset(extension)) {
13243
+ panel.append(await createPhotoshopPreview(bytes));
13244
+ return {
13245
+ destroy() {
13246
+ revokeObjectUrl(url, isExternal);
13247
+ panel.remove();
13248
+ }
13249
+ };
13250
+ }
12396
13251
  const section = createSection(assetTitle(extension));
12397
13252
  const summary = document.createElement("div");
12398
13253
  summary.className = "ofv-asset-summary";
@@ -12414,9 +13269,6 @@ function assetPlugin() {
12414
13269
  if (extension === "wasm") {
12415
13270
  section.append(createWasmPreview(bytes));
12416
13271
  }
12417
- if (extension === "psd" || extension === "psb") {
12418
- section.append(createPhotoshopPreview(bytes));
12419
- }
12420
13272
  if (extension === "sqlite" || extension === "sqlite3" || extension === "db") {
12421
13273
  section.append(createSqlitePreview(bytes));
12422
13274
  }
@@ -12469,7 +13321,7 @@ function assetGuidance(extension) {
12469
13321
  return "\u5B57\u4F53\u6587\u4EF6\u5DF2\u8BC6\u522B\uFF0C\u5F53\u524D\u4F1A\u4F7F\u7528 FontFace \u5C55\u793A\u5B57\u5F62\u6837\u5F20\uFF0C\u5E76\u89E3\u6790 sfnt/WOFF \u8868\u76EE\u5F55\u548C name \u5143\u4FE1\u606F\u3002";
12470
13322
  }
12471
13323
  if (extension === "psd" || extension === "psb") {
12472
- return "PSD/PSB \u5DF2\u8BC6\u522B\uFF0C\u5F53\u524D\u4F1A\u89E3\u6790\u57FA\u7840\u753B\u5E03\u3001\u901A\u9053\u3001\u4F4D\u6DF1\u548C\u989C\u8272\u6A21\u5F0F\uFF1B\u56FE\u5C42\u7F29\u7565\u56FE\u53EF\u540E\u7EED\u63A5\u5165 psd.js/WASM \u6216\u670D\u52A1\u7AEF\u8F6C\u6362\u3002";
13324
+ return "PSD/PSB \u5DF2\u8BC6\u522B\uFF0C\u5F53\u524D\u4F1A\u4F18\u5148\u89E3\u6790 Photoshop \u5408\u6210\u56FE\u9884\u89C8\uFF0C\u5E76\u4FDD\u7559\u753B\u5E03\u3001\u901A\u9053\u3001\u4F4D\u6DF1\u548C\u989C\u8272\u6A21\u5F0F\u7B49\u7ED3\u6784\u4FE1\u606F\u3002";
12473
13325
  }
12474
13326
  if (["ai", "eps", "ps"].includes(extension)) {
12475
13327
  return "PostScript/Illustrator \u6587\u4EF6\u5DF2\u8BC6\u522B\uFF0C\u5F53\u524D\u4F1A\u89E3\u6790\u6587\u6863\u5934\u3001BoundingBox \u548C\u5E38\u89C1 DSC \u5143\u4FE1\u606F\uFF1B\u9AD8\u4FDD\u771F\u56FE\u5F62\u53EF\u540E\u7EED\u63A5\u5165 Ghostscript/Illustrator \u8F6C\u6362\u3002";
@@ -12491,6 +13343,9 @@ function assetGuidance(extension) {
12491
13343
  function isFontAsset(extension) {
12492
13344
  return ["ttf", "otf", "woff", "woff2", "eot"].includes(extension);
12493
13345
  }
13346
+ function isPhotoshopAsset(extension) {
13347
+ return extension === "psd" || extension === "psb";
13348
+ }
12494
13349
  async function createFontPreview(extension, url, fileName, bytes) {
12495
13350
  const preview = document.createElement("div");
12496
13351
  preview.className = "ofv-font-preview";
@@ -13252,12 +14107,9 @@ function wasmExternalKind(kind) {
13252
14107
  };
13253
14108
  return names[kind] || `unknown ${kind}`;
13254
14109
  }
13255
- function createPhotoshopPreview(bytes) {
14110
+ async function createPhotoshopPreview(bytes) {
13256
14111
  const preview = document.createElement("div");
13257
14112
  preview.className = "ofv-psd-preview";
13258
- const heading = document.createElement("strong");
13259
- heading.textContent = "Photoshop \u7ED3\u6784";
13260
- preview.append(heading);
13261
14113
  const header = parsePhotoshopHeader(bytes);
13262
14114
  if (!header.valid) {
13263
14115
  const error = document.createElement("p");
@@ -13266,16 +14118,66 @@ function createPhotoshopPreview(bytes) {
13266
14118
  preview.append(error);
13267
14119
  return preview;
13268
14120
  }
13269
- const summary = document.createElement("div");
13270
- summary.className = "ofv-psd-summary";
13271
- appendMeta(summary, "\u7248\u672C", header.version === 2 ? "PSB \u5927\u6587\u6863" : "PSD");
13272
- appendMeta(summary, "\u753B\u5E03", `${header.width} x ${header.height}px`);
13273
- appendMeta(summary, "\u901A\u9053", header.channels ?? "\u672A\u77E5");
13274
- appendMeta(summary, "\u4F4D\u6DF1", `${header.depth ?? "\u672A\u77E5"} bit`);
13275
- appendMeta(summary, "\u989C\u8272\u6A21\u5F0F", photoshopColorMode(header.colorMode ?? -1));
13276
- preview.append(summary);
14121
+ preview.append(await createPhotoshopCompositePreview(bytes, header));
13277
14122
  return preview;
13278
14123
  }
14124
+ async function createPhotoshopCompositePreview(bytes, header) {
14125
+ const wrapper = document.createElement("div");
14126
+ wrapper.className = "ofv-psd-composite";
14127
+ if (!canUseBrowserCanvas()) {
14128
+ const status = document.createElement("p");
14129
+ status.className = "ofv-psd-error";
14130
+ status.textContent = "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 Canvas\uFF0C\u65E0\u6CD5\u751F\u6210 PSD \u5408\u6210\u56FE\u9884\u89C8\u3002";
14131
+ wrapper.append(status);
14132
+ return wrapper;
14133
+ }
14134
+ try {
14135
+ const { readPsd } = await import("ag-psd");
14136
+ const psd = readPsd(toStandaloneArrayBuffer(bytes), {
14137
+ skipLayerImageData: true,
14138
+ skipThumbnail: true,
14139
+ skipLinkedFilesData: true,
14140
+ useImageData: true,
14141
+ throwForMissingFeatures: false,
14142
+ logMissingFeatures: false
14143
+ });
14144
+ const canvas = psd.canvas || createCanvasFromPsdImageData(psd);
14145
+ if (!canvas) {
14146
+ throw new Error("PSD \u6587\u4EF6\u6CA1\u6709\u53EF\u8BFB\u53D6\u7684\u5408\u6210\u56FE\u50CF\u6570\u636E\u3002\u8BF7\u5728 Photoshop \u4E2D\u5F00\u542F\u201C\u6700\u5927\u517C\u5BB9\u6027\u201D\u4FDD\u5B58\uFF0C\u6216\u63A5\u5165\u5916\u90E8\u8F6C\u6362\u670D\u52A1\u3002");
14147
+ }
14148
+ canvas.classList.add("ofv-psd-canvas");
14149
+ canvas.setAttribute("role", "img");
14150
+ canvas.setAttribute("aria-label", `Photoshop composite ${header.width} x ${header.height}`);
14151
+ wrapper.append(canvas);
14152
+ } catch (error) {
14153
+ const status = document.createElement("p");
14154
+ status.className = "ofv-psd-error";
14155
+ status.textContent = `PSD \u5408\u6210\u56FE\u89E3\u6790\u5931\u8D25\uFF1A${error instanceof Error ? error.message : "\u5F53\u524D\u6587\u4EF6\u7279\u6027\u6682\u4E0D\u652F\u6301\u3002"}`;
14156
+ wrapper.append(status);
14157
+ }
14158
+ return wrapper;
14159
+ }
14160
+ function canUseBrowserCanvas() {
14161
+ return typeof document !== "undefined" && typeof HTMLCanvasElement !== "undefined";
14162
+ }
14163
+ function toStandaloneArrayBuffer(bytes) {
14164
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
14165
+ }
14166
+ function createCanvasFromPsdImageData(psd) {
14167
+ const imageData = psd.imageData;
14168
+ if (!imageData || !imageData.width || !imageData.height || !(imageData.data instanceof Uint8ClampedArray)) {
14169
+ return void 0;
14170
+ }
14171
+ const canvas = document.createElement("canvas");
14172
+ canvas.width = imageData.width;
14173
+ canvas.height = imageData.height;
14174
+ const context = canvas.getContext("2d");
14175
+ if (!context) {
14176
+ return void 0;
14177
+ }
14178
+ context.putImageData(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height), 0, 0);
14179
+ return canvas;
14180
+ }
13279
14181
  function parsePhotoshopHeader(bytes) {
13280
14182
  if (bytes.length < 26) {
13281
14183
  return { valid: false, error: "\u6587\u4EF6\u592A\u77ED\uFF0C\u65E0\u6CD5\u8BFB\u53D6 PSD/PSB \u5934\u4FE1\u606F\u3002" };
@@ -13304,19 +14206,6 @@ function parsePhotoshopHeader(bytes) {
13304
14206
  colorMode: view3.getUint16(24, false)
13305
14207
  };
13306
14208
  }
13307
- function photoshopColorMode(mode) {
13308
- const modes = {
13309
- 0: "Bitmap",
13310
- 1: "Grayscale",
13311
- 2: "Indexed",
13312
- 3: "RGB",
13313
- 4: "CMYK",
13314
- 7: "Multichannel",
13315
- 8: "Duotone",
13316
- 9: "Lab"
13317
- };
13318
- return modes[mode] ? `${modes[mode]} (${mode})` : `\u672A\u77E5 (${mode})`;
13319
- }
13320
14209
  function createSqlitePreview(bytes) {
13321
14210
  const preview = document.createElement("div");
13322
14211
  preview.className = "ofv-sqlite-preview";