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