@open-file-viewer/core 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +756 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +756 -33
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/LICENSE +0 -21
package/dist/index.cjs
CHANGED
|
@@ -301,6 +301,9 @@ var extensionMimeMap = {
|
|
|
301
301
|
skp: "application/vnd.sketchup.skp",
|
|
302
302
|
sldprt: "application/sldworks",
|
|
303
303
|
sldasm: "application/sldworks",
|
|
304
|
+
gds: "application/vnd.gds",
|
|
305
|
+
oas: "application/vnd.oasis.layout",
|
|
306
|
+
oasis: "application/vnd.oasis.layout",
|
|
304
307
|
ttf: "font/ttf",
|
|
305
308
|
otf: "font/otf",
|
|
306
309
|
woff: "font/woff",
|
|
@@ -1805,6 +1808,7 @@ function imagePlugin() {
|
|
|
1805
1808
|
let dragStartY = 0;
|
|
1806
1809
|
let startOffsetX = 0;
|
|
1807
1810
|
let startOffsetY = 0;
|
|
1811
|
+
let activePointerId = null;
|
|
1808
1812
|
const zoomLabel = document.createElement("span");
|
|
1809
1813
|
zoomLabel.className = "ofv-image-zoom";
|
|
1810
1814
|
const updateTransform = () => {
|
|
@@ -1852,29 +1856,61 @@ function imagePlugin() {
|
|
|
1852
1856
|
if (event.button !== 0) {
|
|
1853
1857
|
return;
|
|
1854
1858
|
}
|
|
1859
|
+
if (activePointerId !== null && activePointerId !== event.pointerId) {
|
|
1860
|
+
finishDrag(activePointerId);
|
|
1861
|
+
}
|
|
1855
1862
|
dragging = true;
|
|
1863
|
+
activePointerId = event.pointerId;
|
|
1856
1864
|
dragStartX = event.clientX;
|
|
1857
1865
|
dragStartY = event.clientY;
|
|
1858
1866
|
startOffsetX = offsetX;
|
|
1859
1867
|
startOffsetY = offsetY;
|
|
1860
1868
|
stage.classList.add("is-dragging");
|
|
1861
|
-
|
|
1869
|
+
try {
|
|
1870
|
+
stage.setPointerCapture(event.pointerId);
|
|
1871
|
+
} catch {
|
|
1872
|
+
}
|
|
1862
1873
|
};
|
|
1863
1874
|
const onPointerMove = (event) => {
|
|
1864
|
-
if (!dragging) {
|
|
1875
|
+
if (!dragging || event.pointerId !== activePointerId) {
|
|
1865
1876
|
return;
|
|
1866
1877
|
}
|
|
1867
1878
|
offsetX = startOffsetX + event.clientX - dragStartX;
|
|
1868
1879
|
offsetY = startOffsetY + event.clientY - dragStartY;
|
|
1869
1880
|
updateTransform();
|
|
1870
1881
|
};
|
|
1871
|
-
const
|
|
1882
|
+
const finishDrag = (pointerId) => {
|
|
1883
|
+
const captureId = pointerId ?? activePointerId;
|
|
1872
1884
|
dragging = false;
|
|
1885
|
+
activePointerId = null;
|
|
1873
1886
|
stage.classList.remove("is-dragging");
|
|
1874
|
-
if (
|
|
1875
|
-
|
|
1887
|
+
if (captureId !== null && captureId !== void 0) {
|
|
1888
|
+
try {
|
|
1889
|
+
if (stage.hasPointerCapture(captureId)) {
|
|
1890
|
+
stage.releasePointerCapture(captureId);
|
|
1891
|
+
}
|
|
1892
|
+
} catch {
|
|
1893
|
+
}
|
|
1876
1894
|
}
|
|
1877
1895
|
};
|
|
1896
|
+
const onPointerUp = (event) => {
|
|
1897
|
+
if (event.pointerId === activePointerId) {
|
|
1898
|
+
finishDrag(event.pointerId);
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
const onLostPointerCapture = (event) => {
|
|
1902
|
+
if (event.pointerId === activePointerId) {
|
|
1903
|
+
finishDrag(null);
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
const onPointerLeave = (event) => {
|
|
1907
|
+
if (event.pointerId === activePointerId && event.buttons === 0) {
|
|
1908
|
+
finishDrag(event.pointerId);
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
const onWindowBlur = () => {
|
|
1912
|
+
finishDrag();
|
|
1913
|
+
};
|
|
1878
1914
|
const onWheel = (event) => {
|
|
1879
1915
|
if (!event.ctrlKey && !event.metaKey) {
|
|
1880
1916
|
return;
|
|
@@ -1886,8 +1922,11 @@ function imagePlugin() {
|
|
|
1886
1922
|
stage.addEventListener("pointermove", onPointerMove);
|
|
1887
1923
|
stage.addEventListener("pointerup", onPointerUp);
|
|
1888
1924
|
stage.addEventListener("pointercancel", onPointerUp);
|
|
1925
|
+
stage.addEventListener("lostpointercapture", onLostPointerCapture);
|
|
1926
|
+
stage.addEventListener("pointerleave", onPointerLeave);
|
|
1889
1927
|
stage.addEventListener("wheel", onWheel, { passive: false });
|
|
1890
1928
|
image.addEventListener("error", showImageFallback);
|
|
1929
|
+
window.addEventListener("blur", onWindowBlur);
|
|
1891
1930
|
stage.append(image);
|
|
1892
1931
|
wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
|
|
1893
1932
|
ctx.viewport.append(wrapper);
|
|
@@ -1934,8 +1973,12 @@ function imagePlugin() {
|
|
|
1934
1973
|
stage.removeEventListener("pointermove", onPointerMove);
|
|
1935
1974
|
stage.removeEventListener("pointerup", onPointerUp);
|
|
1936
1975
|
stage.removeEventListener("pointercancel", onPointerUp);
|
|
1976
|
+
stage.removeEventListener("lostpointercapture", onLostPointerCapture);
|
|
1977
|
+
stage.removeEventListener("pointerleave", onPointerLeave);
|
|
1937
1978
|
stage.removeEventListener("wheel", onWheel);
|
|
1938
1979
|
image.removeEventListener("error", showImageFallback);
|
|
1980
|
+
window.removeEventListener("blur", onWindowBlur);
|
|
1981
|
+
finishDrag();
|
|
1939
1982
|
wrapper.remove();
|
|
1940
1983
|
if (convertedBlob) {
|
|
1941
1984
|
URL.revokeObjectURL(url);
|
|
@@ -4084,6 +4127,38 @@ async function readText(source) {
|
|
|
4084
4127
|
return String(source);
|
|
4085
4128
|
}
|
|
4086
4129
|
|
|
4130
|
+
// src/plugins/encrypted.ts
|
|
4131
|
+
function createEncryptedFallback(file, url, copy = {}) {
|
|
4132
|
+
const fallback = document.createElement("div");
|
|
4133
|
+
fallback.className = "ofv-fallback ofv-encrypted";
|
|
4134
|
+
const title = document.createElement("strong");
|
|
4135
|
+
title.textContent = copy.title || "\u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8";
|
|
4136
|
+
const message = document.createElement("span");
|
|
4137
|
+
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";
|
|
4138
|
+
const meta = document.createElement("dl");
|
|
4139
|
+
meta.className = "ofv-fallback-meta ofv-encrypted-meta";
|
|
4140
|
+
appendEncryptedMeta(meta, "\u6587\u4EF6", file.name || "\u672A\u547D\u540D\u6587\u4EF6");
|
|
4141
|
+
appendEncryptedMeta(meta, "\u683C\u5F0F", file.extension ? `.${file.extension}` : file.mimeType || "\u672A\u77E5");
|
|
4142
|
+
const download = document.createElement("a");
|
|
4143
|
+
download.href = url;
|
|
4144
|
+
download.download = file.name;
|
|
4145
|
+
download.textContent = copy.action || "\u4E0B\u8F7D\u6587\u4EF6";
|
|
4146
|
+
fallback.append(title, message, meta, download);
|
|
4147
|
+
return fallback;
|
|
4148
|
+
}
|
|
4149
|
+
function isEncryptedError(error) {
|
|
4150
|
+
const message = error instanceof Error ? error.message : String(error || "");
|
|
4151
|
+
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
4152
|
+
return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(`${name} ${message}`);
|
|
4153
|
+
}
|
|
4154
|
+
function appendEncryptedMeta(parent, label, value) {
|
|
4155
|
+
const key = document.createElement("dt");
|
|
4156
|
+
key.textContent = label;
|
|
4157
|
+
const content = document.createElement("dd");
|
|
4158
|
+
content.textContent = value;
|
|
4159
|
+
parent.append(key, content);
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4087
4162
|
// src/plugins/pdf.ts
|
|
4088
4163
|
function multiplyMatrices(m1, m2) {
|
|
4089
4164
|
return [
|
|
@@ -4125,7 +4200,11 @@ function pdfPlugin(options = {}) {
|
|
|
4125
4200
|
const doc = await documentTask.promise.catch((error) => {
|
|
4126
4201
|
viewer.remove();
|
|
4127
4202
|
ctx.viewport.classList.add("ofv-center");
|
|
4128
|
-
const fallback =
|
|
4203
|
+
const fallback = isEncryptedError(error) ? createEncryptedFallback(ctx.file, url, {
|
|
4204
|
+
title: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
4205
|
+
message: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
|
|
4206
|
+
action: "\u4E0B\u8F7D PDF"
|
|
4207
|
+
}) : createPdfFallback(ctx.file.name, url, normalizePdfError(error));
|
|
4129
4208
|
ctx.viewport.append(fallback);
|
|
4130
4209
|
return void 0;
|
|
4131
4210
|
});
|
|
@@ -4415,9 +4494,6 @@ function normalizePdfError(error) {
|
|
|
4415
4494
|
const message = error instanceof Error ? error.message : String(error || "");
|
|
4416
4495
|
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
4417
4496
|
const lower = `${name} ${message}`.toLowerCase();
|
|
4418
|
-
if (lower.includes("password")) {
|
|
4419
|
-
return "\u8BE5 PDF \u53D7\u5BC6\u7801\u4FDD\u62A4\uFF0C\u5F53\u524D\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u5185\u76F4\u63A5\u9884\u89C8\u3002";
|
|
4420
|
-
}
|
|
4421
4497
|
if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
|
|
4422
4498
|
return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
|
|
4423
4499
|
}
|
|
@@ -5447,6 +5523,10 @@ async function renderSheet(panel, arrayBuffer, extension) {
|
|
|
5447
5523
|
}
|
|
5448
5524
|
}
|
|
5449
5525
|
function renderSheetFallback(panel, extension, detail) {
|
|
5526
|
+
if (isEncryptedText(detail)) {
|
|
5527
|
+
renderEncryptedOfficeByFileInfo(panel, `.${extension || "sheet"}`, "Office \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8");
|
|
5528
|
+
return;
|
|
5529
|
+
}
|
|
5450
5530
|
const section = createSection("\u8868\u683C\u89E3\u6790\u5931\u8D25");
|
|
5451
5531
|
const title = document.createElement("p");
|
|
5452
5532
|
title.textContent = `.${extension || "sheet"} \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790\u4E3A\u53EF\u9884\u89C8\u8868\u683C\u3002`;
|
|
@@ -5457,6 +5537,17 @@ function renderSheetFallback(panel, extension, detail) {
|
|
|
5457
5537
|
section.append(title, meta, support);
|
|
5458
5538
|
panel.append(section);
|
|
5459
5539
|
}
|
|
5540
|
+
function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
|
|
5541
|
+
const section = createSection(title);
|
|
5542
|
+
section.classList.add("ofv-encrypted");
|
|
5543
|
+
const message = document.createElement("p");
|
|
5544
|
+
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`;
|
|
5545
|
+
section.append(message);
|
|
5546
|
+
panel.append(section);
|
|
5547
|
+
}
|
|
5548
|
+
function isEncryptedText(value) {
|
|
5549
|
+
return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(value);
|
|
5550
|
+
}
|
|
5460
5551
|
function renderFlatOds(panel, xml) {
|
|
5461
5552
|
const sheets = parseFlatOds(xml);
|
|
5462
5553
|
renderParsedSheets(panel, sheets, "FODS \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002");
|
|
@@ -6765,7 +6856,7 @@ function ofdPlugin() {
|
|
|
6765
6856
|
try {
|
|
6766
6857
|
zip = await import_jszip4.default.loadAsync(await readArrayBuffer(ctx.file));
|
|
6767
6858
|
} catch (error) {
|
|
6768
|
-
panel.append(
|
|
6859
|
+
panel.append(createOfdFailure(ctx.file, url, error));
|
|
6769
6860
|
return {
|
|
6770
6861
|
destroy() {
|
|
6771
6862
|
panel.remove();
|
|
@@ -6782,7 +6873,7 @@ function ofdPlugin() {
|
|
|
6782
6873
|
textFragments.push(...matches);
|
|
6783
6874
|
}
|
|
6784
6875
|
} catch (error) {
|
|
6785
|
-
panel.append(
|
|
6876
|
+
panel.append(createOfdFailure(ctx.file, url, error));
|
|
6786
6877
|
return {
|
|
6787
6878
|
destroy() {
|
|
6788
6879
|
panel.remove();
|
|
@@ -7407,6 +7498,16 @@ function finiteNumber2(value, fallback) {
|
|
|
7407
7498
|
function formatOfdCssNumber(value) {
|
|
7408
7499
|
return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
7409
7500
|
}
|
|
7501
|
+
function createOfdFailure(file, url, error) {
|
|
7502
|
+
if (isEncryptedError(error)) {
|
|
7503
|
+
return createEncryptedFallback(file, url, {
|
|
7504
|
+
title: "OFD \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
7505
|
+
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",
|
|
7506
|
+
action: "\u4E0B\u8F7D OFD"
|
|
7507
|
+
});
|
|
7508
|
+
}
|
|
7509
|
+
return createOfdFallback(file.name, url, normalizeOfdError(error));
|
|
7510
|
+
}
|
|
7410
7511
|
function createOfdFallback(fileName, url, detail) {
|
|
7411
7512
|
const fallback = document.createElement("div");
|
|
7412
7513
|
fallback.className = "ofv-fallback";
|
|
@@ -7475,7 +7576,9 @@ function archivePlugin() {
|
|
|
7475
7576
|
try {
|
|
7476
7577
|
if (ext === "zip") {
|
|
7477
7578
|
try {
|
|
7478
|
-
const zip = await import_jszip5.default.loadAsync(await readArrayBuffer(ctx.file)
|
|
7579
|
+
const zip = await import_jszip5.default.loadAsync(await readArrayBuffer(ctx.file), {
|
|
7580
|
+
decodeFileName: decodeZipFileName
|
|
7581
|
+
});
|
|
7479
7582
|
archiveEntries = Object.values(zip.files).map((entry) => ({
|
|
7480
7583
|
name: entry.name,
|
|
7481
7584
|
unsafeName: entry.unsafeOriginalName,
|
|
@@ -7484,7 +7587,7 @@ function archivePlugin() {
|
|
|
7484
7587
|
read: () => entry.async("arraybuffer")
|
|
7485
7588
|
}));
|
|
7486
7589
|
} catch (zipErr) {
|
|
7487
|
-
if (
|
|
7590
|
+
if (isEncryptedError(zipErr)) {
|
|
7488
7591
|
isEncrypted = true;
|
|
7489
7592
|
} else {
|
|
7490
7593
|
throw zipErr;
|
|
@@ -7517,17 +7620,11 @@ function archivePlugin() {
|
|
|
7517
7620
|
parseError = `\u538B\u7F29\u5305\u89E3\u6790\u5931\u8D25\uFF1A${err.message || err}`;
|
|
7518
7621
|
}
|
|
7519
7622
|
if (isEncrypted) {
|
|
7520
|
-
const fallback =
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
meta.textContent = "\u4E3A\u4E86\u60A8\u7684\u6570\u636E\u5B89\u5168\uFF0C\u672C\u9884\u89C8\u5668\u4E0D\u652F\u6301\u76F4\u63A5\u5728\u7EBF\u89E3\u5BC6\u3002\u8BF7\u4E0B\u8F7D\u6587\u4EF6\u540E\u5728\u672C\u5730\u8F93\u5165\u5BC6\u7801\u89E3\u538B\u3002";
|
|
7526
|
-
const download = document.createElement("a");
|
|
7527
|
-
download.href = url;
|
|
7528
|
-
download.download = ctx.file.name;
|
|
7529
|
-
download.textContent = "\u4E0B\u8F7D\u6587\u4EF6";
|
|
7530
|
-
fallback.append(title, meta, download);
|
|
7623
|
+
const fallback = createEncryptedFallback(ctx.file, url, {
|
|
7624
|
+
title: "\u538B\u7F29\u5305\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
7625
|
+
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",
|
|
7626
|
+
action: "\u4E0B\u8F7D\u538B\u7F29\u5305"
|
|
7627
|
+
});
|
|
7531
7628
|
panel.append(fallback);
|
|
7532
7629
|
ctx.viewport.classList.add("ofv-center");
|
|
7533
7630
|
return {
|
|
@@ -7776,6 +7873,28 @@ async function findSubPreviewPlugin(plugins, file) {
|
|
|
7776
7873
|
}
|
|
7777
7874
|
return fallbackPlugin();
|
|
7778
7875
|
}
|
|
7876
|
+
function decodeZipFileName(bytes) {
|
|
7877
|
+
const data = Array.isArray(bytes) ? Uint8Array.from(bytes.map((value) => value.charCodeAt(0) & 255)) : bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);
|
|
7878
|
+
const utf8 = decodeZipNameWith(data, "utf-8", true);
|
|
7879
|
+
if (utf8 && !looksMojibake(utf8)) {
|
|
7880
|
+
return utf8;
|
|
7881
|
+
}
|
|
7882
|
+
const gb18030 = decodeZipNameWith(data, "gb18030", false) || decodeZipNameWith(data, "gbk", false);
|
|
7883
|
+
if (gb18030 && !looksMojibake(gb18030)) {
|
|
7884
|
+
return gb18030;
|
|
7885
|
+
}
|
|
7886
|
+
return utf8 || new TextDecoder("latin1").decode(data);
|
|
7887
|
+
}
|
|
7888
|
+
function decodeZipNameWith(bytes, encoding, fatal) {
|
|
7889
|
+
try {
|
|
7890
|
+
return new TextDecoder(encoding, { fatal }).decode(bytes);
|
|
7891
|
+
} catch {
|
|
7892
|
+
return void 0;
|
|
7893
|
+
}
|
|
7894
|
+
}
|
|
7895
|
+
function looksMojibake(value) {
|
|
7896
|
+
return /[\uFFFDÃÂÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß]/.test(value);
|
|
7897
|
+
}
|
|
7779
7898
|
function createInlineError(titleText, detailText) {
|
|
7780
7899
|
const fallback = document.createElement("div");
|
|
7781
7900
|
fallback.className = "ofv-fallback";
|
|
@@ -8197,13 +8316,14 @@ function emailPlugin() {
|
|
|
8197
8316
|
} else {
|
|
8198
8317
|
const PostalMime = (await import("postal-mime")).default;
|
|
8199
8318
|
const parser = new PostalMime();
|
|
8200
|
-
let
|
|
8319
|
+
let rawSource = await readArrayBuffer(ctx.file);
|
|
8201
8320
|
if (ext === "mbox") {
|
|
8321
|
+
let rawText = await readTextFile(ctx.file);
|
|
8202
8322
|
const messages = splitMboxMessages(rawText);
|
|
8203
8323
|
mboxSummary = messages.map(summarizeMboxMessage);
|
|
8204
|
-
|
|
8324
|
+
rawSource = messages[0] || rawText;
|
|
8205
8325
|
}
|
|
8206
|
-
const parsed = await parser.parse(
|
|
8326
|
+
const parsed = await parser.parse(rawSource);
|
|
8207
8327
|
const from = parsed.from ? `${parsed.from.name || ""} <${parsed.from.address || ""}>`.trim() : "";
|
|
8208
8328
|
const to = Array.isArray(parsed.to) ? parsed.to.map((t) => `${t.name || ""} <${t.address || ""}>`.trim()).join("; ") : "";
|
|
8209
8329
|
const cc = Array.isArray(parsed.cc) ? parsed.cc.map((c) => `${c.name || ""} <${c.address || ""}>`.trim()).join("; ") : "";
|
|
@@ -8273,13 +8393,17 @@ function emailPlugin() {
|
|
|
8273
8393
|
const sanitizedHtml = sanitizeEmailHtml(html);
|
|
8274
8394
|
const iframe = document.createElement("iframe");
|
|
8275
8395
|
iframe.className = "ofv-email-body-iframe";
|
|
8276
|
-
iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
|
|
8396
|
+
iframe.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
|
|
8277
8397
|
iframe.style.cssText = "width: 100%; border: none; background: #fff; min-height: 200px;";
|
|
8278
|
-
|
|
8279
|
-
|
|
8398
|
+
let renderedHtmlBody = false;
|
|
8399
|
+
const renderHtmlBody = () => {
|
|
8400
|
+
if (renderedHtmlBody) {
|
|
8401
|
+
return;
|
|
8402
|
+
}
|
|
8280
8403
|
try {
|
|
8281
8404
|
const idoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
8282
8405
|
if (idoc) {
|
|
8406
|
+
renderedHtmlBody = true;
|
|
8283
8407
|
idoc.open();
|
|
8284
8408
|
idoc.write(`
|
|
8285
8409
|
<!doctype html>
|
|
@@ -8330,6 +8454,9 @@ function emailPlugin() {
|
|
|
8330
8454
|
console.error("Failed to write html body to email iframe:", err);
|
|
8331
8455
|
}
|
|
8332
8456
|
};
|
|
8457
|
+
iframe.addEventListener("load", renderHtmlBody, { once: true });
|
|
8458
|
+
bodySection.append(iframe);
|
|
8459
|
+
renderHtmlBody();
|
|
8333
8460
|
} else {
|
|
8334
8461
|
const pre = document.createElement("pre");
|
|
8335
8462
|
pre.className = "ofv-text-block";
|
|
@@ -9851,6 +9978,7 @@ function decodeDrawioDiagram(value) {
|
|
|
9851
9978
|
}
|
|
9852
9979
|
|
|
9853
9980
|
// src/plugins/cad.ts
|
|
9981
|
+
var import_pako3 = __toESM(require("pako"), 1);
|
|
9854
9982
|
var cadExtensions = /* @__PURE__ */ new Set([
|
|
9855
9983
|
"dxf",
|
|
9856
9984
|
"dwg",
|
|
@@ -9867,7 +9995,10 @@ var cadExtensions = /* @__PURE__ */ new Set([
|
|
|
9867
9995
|
"3dm",
|
|
9868
9996
|
"skp",
|
|
9869
9997
|
"sldprt",
|
|
9870
|
-
"sldasm"
|
|
9998
|
+
"sldasm",
|
|
9999
|
+
"gds",
|
|
10000
|
+
"oas",
|
|
10001
|
+
"oasis"
|
|
9871
10002
|
]);
|
|
9872
10003
|
var cadMimeTypes = /* @__PURE__ */ new Set([
|
|
9873
10004
|
"application/acad",
|
|
@@ -9884,7 +10015,11 @@ var cadMimeTypes = /* @__PURE__ */ new Set([
|
|
|
9884
10015
|
"application/x-parasolid",
|
|
9885
10016
|
"model/vnd.3dm",
|
|
9886
10017
|
"application/vnd.sketchup.skp",
|
|
9887
|
-
"application/sldworks"
|
|
10018
|
+
"application/sldworks",
|
|
10019
|
+
"application/vnd.gds",
|
|
10020
|
+
"application/x-gdsii",
|
|
10021
|
+
"application/vnd.oasis.layout",
|
|
10022
|
+
"application/x-oasis-layout"
|
|
9888
10023
|
]);
|
|
9889
10024
|
var cadMimeFormatMap = {
|
|
9890
10025
|
"application/acad": "dwg",
|
|
@@ -9901,7 +10036,11 @@ var cadMimeFormatMap = {
|
|
|
9901
10036
|
"application/x-parasolid": "x_t",
|
|
9902
10037
|
"model/vnd.3dm": "3dm",
|
|
9903
10038
|
"application/vnd.sketchup.skp": "skp",
|
|
9904
|
-
"application/sldworks": "sldprt"
|
|
10039
|
+
"application/sldworks": "sldprt",
|
|
10040
|
+
"application/vnd.gds": "gds",
|
|
10041
|
+
"application/x-gdsii": "gds",
|
|
10042
|
+
"application/vnd.oasis.layout": "oas",
|
|
10043
|
+
"application/x-oasis-layout": "oas"
|
|
9905
10044
|
};
|
|
9906
10045
|
function cadPlugin() {
|
|
9907
10046
|
return {
|
|
@@ -9929,6 +10068,36 @@ function cadPlugin() {
|
|
|
9929
10068
|
renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
|
|
9930
10069
|
return { destroy: () => panel.remove() };
|
|
9931
10070
|
}
|
|
10071
|
+
if (extension === "gds") {
|
|
10072
|
+
const viewer2 = renderLayoutPreview(panel, parseGdsLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
|
|
10073
|
+
return {
|
|
10074
|
+
canCommand(command) {
|
|
10075
|
+
return viewer2.canCommand(command);
|
|
10076
|
+
},
|
|
10077
|
+
command(command) {
|
|
10078
|
+
return viewer2.command(command);
|
|
10079
|
+
},
|
|
10080
|
+
destroy() {
|
|
10081
|
+
viewer2.destroy();
|
|
10082
|
+
panel.remove();
|
|
10083
|
+
}
|
|
10084
|
+
};
|
|
10085
|
+
}
|
|
10086
|
+
if (extension === "oas" || extension === "oasis") {
|
|
10087
|
+
const viewer2 = renderLayoutPreview(panel, parseOasisLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
|
|
10088
|
+
return {
|
|
10089
|
+
canCommand(command) {
|
|
10090
|
+
return viewer2.canCommand(command);
|
|
10091
|
+
},
|
|
10092
|
+
command(command) {
|
|
10093
|
+
return viewer2.command(command);
|
|
10094
|
+
},
|
|
10095
|
+
destroy() {
|
|
10096
|
+
viewer2.destroy();
|
|
10097
|
+
panel.remove();
|
|
10098
|
+
}
|
|
10099
|
+
};
|
|
10100
|
+
}
|
|
9932
10101
|
if (extension !== "dxf") {
|
|
9933
10102
|
const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
|
|
9934
10103
|
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`);
|
|
@@ -10027,6 +10196,560 @@ function renderIfc(panel, text) {
|
|
|
10027
10196
|
section.append(table);
|
|
10028
10197
|
panel.append(section);
|
|
10029
10198
|
}
|
|
10199
|
+
var layoutPalette = [
|
|
10200
|
+
"#2563eb",
|
|
10201
|
+
"#dc2626",
|
|
10202
|
+
"#059669",
|
|
10203
|
+
"#7c3aed",
|
|
10204
|
+
"#d97706",
|
|
10205
|
+
"#0891b2",
|
|
10206
|
+
"#be123c",
|
|
10207
|
+
"#4f46e5",
|
|
10208
|
+
"#15803d",
|
|
10209
|
+
"#a16207"
|
|
10210
|
+
];
|
|
10211
|
+
var gdsRecordNames = {
|
|
10212
|
+
0: "HEADER",
|
|
10213
|
+
1: "BGNLIB",
|
|
10214
|
+
2: "LIBNAME",
|
|
10215
|
+
3: "UNITS",
|
|
10216
|
+
4: "ENDLIB",
|
|
10217
|
+
5: "BGNSTR",
|
|
10218
|
+
6: "STRNAME",
|
|
10219
|
+
7: "ENDSTR",
|
|
10220
|
+
8: "BOUNDARY",
|
|
10221
|
+
9: "PATH",
|
|
10222
|
+
10: "SREF",
|
|
10223
|
+
11: "AREF",
|
|
10224
|
+
12: "TEXT",
|
|
10225
|
+
13: "LAYER",
|
|
10226
|
+
14: "DATATYPE",
|
|
10227
|
+
15: "WIDTH",
|
|
10228
|
+
16: "XY",
|
|
10229
|
+
17: "ENDEL",
|
|
10230
|
+
18: "SNAME",
|
|
10231
|
+
22: "TEXTTYPE",
|
|
10232
|
+
25: "STRING",
|
|
10233
|
+
45: "BOX"
|
|
10234
|
+
};
|
|
10235
|
+
var oasisRecordNames = {
|
|
10236
|
+
0: "PAD",
|
|
10237
|
+
1: "START",
|
|
10238
|
+
2: "END",
|
|
10239
|
+
3: "CELLNAME",
|
|
10240
|
+
4: "CELLNAME-REF",
|
|
10241
|
+
5: "TEXTSTRING",
|
|
10242
|
+
6: "TEXTSTRING-REF",
|
|
10243
|
+
7: "PROPNAME",
|
|
10244
|
+
8: "PROPNAME-REF",
|
|
10245
|
+
9: "PROPSTRING",
|
|
10246
|
+
10: "PROPSTRING-REF",
|
|
10247
|
+
11: "LAYERNAME",
|
|
10248
|
+
12: "LAYERNAME-REF",
|
|
10249
|
+
13: "CELL",
|
|
10250
|
+
14: "XYABSOLUTE",
|
|
10251
|
+
15: "XYRELATIVE",
|
|
10252
|
+
16: "PLACEMENT",
|
|
10253
|
+
17: "PLACEMENT",
|
|
10254
|
+
18: "TEXT",
|
|
10255
|
+
19: "RECTANGLE",
|
|
10256
|
+
20: "POLYGON",
|
|
10257
|
+
21: "PATH",
|
|
10258
|
+
22: "TRAPEZOID",
|
|
10259
|
+
23: "TRAPEZOID",
|
|
10260
|
+
24: "TRAPEZOID",
|
|
10261
|
+
25: "CTRAPEZOID",
|
|
10262
|
+
26: "CIRCLE",
|
|
10263
|
+
27: "PROPERTY",
|
|
10264
|
+
28: "PROPERTY",
|
|
10265
|
+
29: "XNAME",
|
|
10266
|
+
30: "XNAME-REF",
|
|
10267
|
+
31: "XELEMENT",
|
|
10268
|
+
32: "XGEOMETRY",
|
|
10269
|
+
33: "CBLOCK"
|
|
10270
|
+
};
|
|
10271
|
+
function renderLayoutPreview(panel, data, ctx) {
|
|
10272
|
+
const section = createSection(`${data.format} \u7248\u56FE\u9884\u89C8`);
|
|
10273
|
+
const summary = document.createElement("div");
|
|
10274
|
+
summary.className = "ofv-cad-summary ofv-layout-summary";
|
|
10275
|
+
appendMeta5(summary, "\u6587\u4EF6", data.fileName);
|
|
10276
|
+
appendMeta5(summary, "\u683C\u5F0F", data.format);
|
|
10277
|
+
if (data.libraryName) {
|
|
10278
|
+
appendMeta5(summary, "\u5E93", data.libraryName);
|
|
10279
|
+
}
|
|
10280
|
+
if (data.version) {
|
|
10281
|
+
appendMeta5(summary, "\u7248\u672C", data.version);
|
|
10282
|
+
}
|
|
10283
|
+
if (data.unit) {
|
|
10284
|
+
appendMeta5(summary, "\u5355\u4F4D", data.unit);
|
|
10285
|
+
}
|
|
10286
|
+
appendMeta5(summary, "Cell", data.cells.length);
|
|
10287
|
+
appendMeta5(summary, "\u51E0\u4F55", data.shapes.length);
|
|
10288
|
+
appendMeta5(summary, "\u5F15\u7528", data.references.length);
|
|
10289
|
+
appendMeta5(summary, "\u6587\u5B57", data.labels.length);
|
|
10290
|
+
for (const [label, value] of data.metadata) {
|
|
10291
|
+
appendMeta5(summary, label, value);
|
|
10292
|
+
}
|
|
10293
|
+
section.append(summary);
|
|
10294
|
+
for (const noteText of [...data.notes, ...data.warnings]) {
|
|
10295
|
+
const note = document.createElement("p");
|
|
10296
|
+
note.className = data.warnings.includes(noteText) ? "ofv-layout-warning" : "ofv-layout-note";
|
|
10297
|
+
note.textContent = noteText;
|
|
10298
|
+
section.append(note);
|
|
10299
|
+
}
|
|
10300
|
+
const bounds = computeLayoutBounds(data.shapes, data.labels, data.references);
|
|
10301
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
10302
|
+
svg.setAttribute("class", "ofv-svg-stage ofv-layout-stage");
|
|
10303
|
+
let currentViewBox = { x: bounds.minX, y: bounds.minY, width: bounds.width, height: bounds.height };
|
|
10304
|
+
const initialViewBox = { ...currentViewBox };
|
|
10305
|
+
const applyViewBox = () => {
|
|
10306
|
+
svg.setAttribute("viewBox", `${currentViewBox.x} ${currentViewBox.y} ${currentViewBox.width} ${currentViewBox.height}`);
|
|
10307
|
+
};
|
|
10308
|
+
applyViewBox();
|
|
10309
|
+
if (data.shapes.length === 0) {
|
|
10310
|
+
const empty = document.createElementNS(svg.namespaceURI, "text");
|
|
10311
|
+
empty.setAttribute("x", String(bounds.minX + bounds.width * 0.5));
|
|
10312
|
+
empty.setAttribute("y", String(bounds.minY + bounds.height * 0.5));
|
|
10313
|
+
empty.setAttribute("text-anchor", "middle");
|
|
10314
|
+
empty.setAttribute("font-size", String(Math.max(bounds.width, bounds.height) / 34));
|
|
10315
|
+
empty.setAttribute("fill", "currentColor");
|
|
10316
|
+
empty.textContent = "\u5DF2\u8BC6\u522B\u7248\u56FE\u6587\u4EF6\uFF0C\u5F53\u524D\u6587\u4EF6\u672A\u89E3\u6790\u51FA\u53EF\u7ED8\u5236\u51E0\u4F55";
|
|
10317
|
+
svg.append(empty);
|
|
10318
|
+
}
|
|
10319
|
+
const layerIndex = new Map([...data.layers.keys()].sort((a, b) => a.localeCompare(b)).map((layer, index) => [layer, index]));
|
|
10320
|
+
for (const shape of data.shapes.slice(0, 6e3)) {
|
|
10321
|
+
const color = layoutPalette[(layerIndex.get(shape.layer) || 0) % layoutPalette.length];
|
|
10322
|
+
if (shape.kind === "path") {
|
|
10323
|
+
const polyline = document.createElementNS(svg.namespaceURI, "polyline");
|
|
10324
|
+
polyline.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
|
|
10325
|
+
polyline.setAttribute("fill", "none");
|
|
10326
|
+
polyline.setAttribute("stroke", color);
|
|
10327
|
+
polyline.setAttribute("stroke-width", String(Math.max(bounds.stroke, Math.abs(shape.width || 0))));
|
|
10328
|
+
polyline.setAttribute("stroke-linecap", "round");
|
|
10329
|
+
polyline.setAttribute("stroke-linejoin", "round");
|
|
10330
|
+
applyLayer(polyline, shape.layer);
|
|
10331
|
+
svg.append(polyline);
|
|
10332
|
+
continue;
|
|
10333
|
+
}
|
|
10334
|
+
const polygon = document.createElementNS(svg.namespaceURI, "polygon");
|
|
10335
|
+
polygon.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
|
|
10336
|
+
polygon.setAttribute("fill", color);
|
|
10337
|
+
polygon.setAttribute("fill-opacity", "0.18");
|
|
10338
|
+
polygon.setAttribute("stroke", color);
|
|
10339
|
+
polygon.setAttribute("stroke-width", String(bounds.stroke));
|
|
10340
|
+
polygon.setAttribute("vector-effect", "non-scaling-stroke");
|
|
10341
|
+
applyLayer(polygon, shape.layer);
|
|
10342
|
+
svg.append(polygon);
|
|
10343
|
+
}
|
|
10344
|
+
for (const label of data.labels.slice(0, 400)) {
|
|
10345
|
+
const text = document.createElementNS(svg.namespaceURI, "text");
|
|
10346
|
+
text.setAttribute("x", String(label.x));
|
|
10347
|
+
text.setAttribute("y", String(-label.y));
|
|
10348
|
+
text.setAttribute("font-size", String(Math.max(bounds.stroke * 12, Math.max(bounds.width, bounds.height) / 120)));
|
|
10349
|
+
text.setAttribute("fill", "currentColor");
|
|
10350
|
+
text.textContent = label.text;
|
|
10351
|
+
applyLayer(text, label.layer);
|
|
10352
|
+
svg.append(text);
|
|
10353
|
+
}
|
|
10354
|
+
const layers = [...data.layers.keys()].sort((a, b) => a.localeCompare(b));
|
|
10355
|
+
if (layers.length > 0) {
|
|
10356
|
+
section.append(createLayoutLayerControls(svg, layers, data.layers));
|
|
10357
|
+
}
|
|
10358
|
+
section.append(svg);
|
|
10359
|
+
if (data.cells.length > 0) {
|
|
10360
|
+
section.append(createLayoutCellList(data.cells, data.references));
|
|
10361
|
+
}
|
|
10362
|
+
panel.append(section);
|
|
10363
|
+
const updateToolbarZoom = () => ctx.toolbar?.setZoom(initialViewBox.width / currentViewBox.width);
|
|
10364
|
+
updateToolbarZoom();
|
|
10365
|
+
return {
|
|
10366
|
+
canCommand(command) {
|
|
10367
|
+
return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
|
|
10368
|
+
},
|
|
10369
|
+
command(command) {
|
|
10370
|
+
if (command === "zoom-in" || command === "zoom-out") {
|
|
10371
|
+
const factor = command === "zoom-in" ? 0.82 : 1.18;
|
|
10372
|
+
const centerX = currentViewBox.x + currentViewBox.width / 2;
|
|
10373
|
+
const centerY = currentViewBox.y + currentViewBox.height / 2;
|
|
10374
|
+
currentViewBox.width *= factor;
|
|
10375
|
+
currentViewBox.height *= factor;
|
|
10376
|
+
currentViewBox.x = centerX - currentViewBox.width / 2;
|
|
10377
|
+
currentViewBox.y = centerY - currentViewBox.height / 2;
|
|
10378
|
+
applyViewBox();
|
|
10379
|
+
updateToolbarZoom();
|
|
10380
|
+
return true;
|
|
10381
|
+
}
|
|
10382
|
+
if (command === "zoom-reset") {
|
|
10383
|
+
currentViewBox = { ...initialViewBox };
|
|
10384
|
+
applyViewBox();
|
|
10385
|
+
updateToolbarZoom();
|
|
10386
|
+
return true;
|
|
10387
|
+
}
|
|
10388
|
+
return false;
|
|
10389
|
+
},
|
|
10390
|
+
destroy() {
|
|
10391
|
+
ctx.toolbar?.setZoom(void 0);
|
|
10392
|
+
}
|
|
10393
|
+
};
|
|
10394
|
+
}
|
|
10395
|
+
function parseGdsLayout(bytes, fileName) {
|
|
10396
|
+
const shapes = [];
|
|
10397
|
+
const labels = [];
|
|
10398
|
+
const references = [];
|
|
10399
|
+
const cells = [];
|
|
10400
|
+
const layers = /* @__PURE__ */ new Map();
|
|
10401
|
+
const recordCounts = /* @__PURE__ */ new Map();
|
|
10402
|
+
const warnings = [];
|
|
10403
|
+
let libraryName = "";
|
|
10404
|
+
let version = "";
|
|
10405
|
+
let unit = "";
|
|
10406
|
+
let offset = 0;
|
|
10407
|
+
let current = {};
|
|
10408
|
+
let currentKind = "";
|
|
10409
|
+
while (offset + 4 <= bytes.length) {
|
|
10410
|
+
const length = readUInt16(bytes, offset);
|
|
10411
|
+
const recordType = bytes[offset + 2];
|
|
10412
|
+
const data = bytes.slice(offset + 4, offset + length);
|
|
10413
|
+
const name = gdsRecordNames[recordType] || `0x${recordType.toString(16).padStart(2, "0")}`;
|
|
10414
|
+
recordCounts.set(name, (recordCounts.get(name) || 0) + 1);
|
|
10415
|
+
if (length < 4 || offset + length > bytes.length) {
|
|
10416
|
+
warnings.push(`GDS \u8BB0\u5F55\u5728 ${offset} \u5B57\u8282\u5904\u957F\u5EA6\u5F02\u5E38\uFF0C\u5DF2\u505C\u6B62\u89E3\u6790\u3002`);
|
|
10417
|
+
break;
|
|
10418
|
+
}
|
|
10419
|
+
if (recordType === 0 && data.length >= 2) {
|
|
10420
|
+
version = String(readUInt16(data, 0));
|
|
10421
|
+
} else if (recordType === 2) {
|
|
10422
|
+
libraryName = readGdsString(data);
|
|
10423
|
+
} else if (recordType === 3 && data.length >= 16) {
|
|
10424
|
+
unit = `${formatGdsReal(data, 0)} / ${formatGdsReal(data, 8)}`;
|
|
10425
|
+
} else if (recordType === 6) {
|
|
10426
|
+
cells.push(readGdsString(data));
|
|
10427
|
+
} else if (recordType === 8 || recordType === 9 || recordType === 45) {
|
|
10428
|
+
currentKind = recordType === 9 ? "path" : recordType === 45 ? "box" : "boundary";
|
|
10429
|
+
current = { kind: currentKind, layer: "0", datatype: "0", points: [] };
|
|
10430
|
+
} else if (recordType === 12) {
|
|
10431
|
+
currentKind = "text";
|
|
10432
|
+
current = { layer: "0", text: "", x: 0, y: 0 };
|
|
10433
|
+
} else if (recordType === 10 || recordType === 11) {
|
|
10434
|
+
currentKind = "reference";
|
|
10435
|
+
current = { cell: "", x: 0, y: 0 };
|
|
10436
|
+
} else if (recordType === 13 && data.length >= 2) {
|
|
10437
|
+
current.layer = String(readInt16(data, 0));
|
|
10438
|
+
} else if ((recordType === 14 || recordType === 22) && data.length >= 2) {
|
|
10439
|
+
current.datatype = String(readInt16(data, 0));
|
|
10440
|
+
} else if (recordType === 15 && data.length >= 4) {
|
|
10441
|
+
current.width = Math.abs(readInt32(data, 0));
|
|
10442
|
+
} else if (recordType === 16) {
|
|
10443
|
+
const points = readGdsPoints(data);
|
|
10444
|
+
if (currentKind === "text" && points[0]) {
|
|
10445
|
+
current.x = points[0][0];
|
|
10446
|
+
current.y = points[0][1];
|
|
10447
|
+
} else if (currentKind === "reference" && points[0]) {
|
|
10448
|
+
current.x = points[0][0];
|
|
10449
|
+
current.y = points[0][1];
|
|
10450
|
+
} else {
|
|
10451
|
+
current.points = points;
|
|
10452
|
+
}
|
|
10453
|
+
} else if (recordType === 18) {
|
|
10454
|
+
current.cell = readGdsString(data);
|
|
10455
|
+
} else if (recordType === 25) {
|
|
10456
|
+
current.text = readGdsString(data);
|
|
10457
|
+
} else if (recordType === 17) {
|
|
10458
|
+
if ((currentKind === "boundary" || currentKind === "path" || currentKind === "box") && current.points && current.points.length > 1) {
|
|
10459
|
+
const shape = {
|
|
10460
|
+
kind: current.kind || "boundary",
|
|
10461
|
+
layer: String(current.layer || "0"),
|
|
10462
|
+
datatype: current.datatype,
|
|
10463
|
+
points: current.points,
|
|
10464
|
+
width: current.width
|
|
10465
|
+
};
|
|
10466
|
+
shapes.push(shape);
|
|
10467
|
+
layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
|
|
10468
|
+
} else if (currentKind === "text" && current.text) {
|
|
10469
|
+
const label = {
|
|
10470
|
+
layer: String(current.layer || "0"),
|
|
10471
|
+
text: String(current.text),
|
|
10472
|
+
x: Number(current.x || 0),
|
|
10473
|
+
y: Number(current.y || 0)
|
|
10474
|
+
};
|
|
10475
|
+
labels.push(label);
|
|
10476
|
+
layers.set(label.layer, (layers.get(label.layer) || 0) + 1);
|
|
10477
|
+
} else if (currentKind === "reference" && current.cell) {
|
|
10478
|
+
references.push({ cell: String(current.cell), x: Number(current.x || 0), y: Number(current.y || 0) });
|
|
10479
|
+
}
|
|
10480
|
+
current = {};
|
|
10481
|
+
currentKind = "";
|
|
10482
|
+
}
|
|
10483
|
+
offset += length;
|
|
10484
|
+
}
|
|
10485
|
+
return {
|
|
10486
|
+
format: "GDSII",
|
|
10487
|
+
fileName,
|
|
10488
|
+
libraryName,
|
|
10489
|
+
version: version ? `Stream ${version}` : void 0,
|
|
10490
|
+
unit,
|
|
10491
|
+
cells,
|
|
10492
|
+
shapes,
|
|
10493
|
+
labels,
|
|
10494
|
+
references,
|
|
10495
|
+
layers,
|
|
10496
|
+
metadata: [
|
|
10497
|
+
["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
|
|
10498
|
+
["\u8BB0\u5F55", sumCounts(recordCounts)],
|
|
10499
|
+
["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size]
|
|
10500
|
+
],
|
|
10501
|
+
notes: [
|
|
10502
|
+
`\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`
|
|
10503
|
+
],
|
|
10504
|
+
warnings
|
|
10505
|
+
};
|
|
10506
|
+
}
|
|
10507
|
+
function parseOasisLayout(bytes, fileName) {
|
|
10508
|
+
const chunks = extractOasisCblocks(bytes);
|
|
10509
|
+
const expanded = chunks.flatMap((chunk) => [...chunk.bytes]);
|
|
10510
|
+
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_"));
|
|
10511
|
+
const propertyNames = uniqueHints(chunks.flatMap((chunk) => extractAsciiRuns(chunk.bytes)).filter((item) => item.startsWith("S_")));
|
|
10512
|
+
const recordCounts = scanOasisRecordCounts(bytes);
|
|
10513
|
+
const expandedCounts = scanOasisRecordCounts(new Uint8Array(expanded));
|
|
10514
|
+
const layers = /* @__PURE__ */ new Map();
|
|
10515
|
+
const shapes = [];
|
|
10516
|
+
const labels = [];
|
|
10517
|
+
const references = [];
|
|
10518
|
+
for (const name of cellNames) {
|
|
10519
|
+
references.push({ cell: name, x: 0, y: 0 });
|
|
10520
|
+
}
|
|
10521
|
+
const pseudo = createOasisStructureShapes(cellNames, chunks.length || recordCounts.size || 1);
|
|
10522
|
+
for (const shape of pseudo) {
|
|
10523
|
+
shapes.push(shape);
|
|
10524
|
+
layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
|
|
10525
|
+
}
|
|
10526
|
+
for (let index = 0; index < cellNames.length; index++) {
|
|
10527
|
+
labels.push({ layer: "cell", text: cellNames[index], x: 12, y: -(18 + index * 18) });
|
|
10528
|
+
}
|
|
10529
|
+
if (cellNames.length > 0) {
|
|
10530
|
+
layers.set("cell", (layers.get("cell") || 0) + cellNames.length);
|
|
10531
|
+
}
|
|
10532
|
+
const version = readOasisVersion(bytes);
|
|
10533
|
+
const cblockText = chunks.length ? `${chunks.length} \u4E2A\uFF0C\u5C55\u5F00 ${formatBytes3(chunks.reduce((sum, chunk) => sum + chunk.bytes.byteLength, 0))}` : "\u672A\u53D1\u73B0";
|
|
10534
|
+
const notes = [
|
|
10535
|
+
"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"
|
|
10536
|
+
];
|
|
10537
|
+
if (propertyNames.length > 0) {
|
|
10538
|
+
notes.push(`\u8BC6\u522B\u5230\u5C5E\u6027\uFF1A${propertyNames.slice(0, 5).join("\u3001")}`);
|
|
10539
|
+
}
|
|
10540
|
+
return {
|
|
10541
|
+
format: "OASIS",
|
|
10542
|
+
fileName,
|
|
10543
|
+
version,
|
|
10544
|
+
cells: cellNames,
|
|
10545
|
+
shapes,
|
|
10546
|
+
labels,
|
|
10547
|
+
references: [],
|
|
10548
|
+
layers,
|
|
10549
|
+
metadata: [
|
|
10550
|
+
["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
|
|
10551
|
+
["CBLOCK", cblockText],
|
|
10552
|
+
["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size + expandedCounts.size],
|
|
10553
|
+
["\u53EF\u8BFB\u7247\u6BB5", cellNames.length + propertyNames.length]
|
|
10554
|
+
],
|
|
10555
|
+
notes,
|
|
10556
|
+
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"] : []
|
|
10557
|
+
};
|
|
10558
|
+
}
|
|
10559
|
+
function computeLayoutBounds(shapes, labels, references) {
|
|
10560
|
+
const xs = [];
|
|
10561
|
+
const ys = [];
|
|
10562
|
+
for (const shape of shapes) {
|
|
10563
|
+
for (const [x, y] of shape.points) {
|
|
10564
|
+
xs.push(x);
|
|
10565
|
+
ys.push(-y);
|
|
10566
|
+
}
|
|
10567
|
+
}
|
|
10568
|
+
for (const label of labels) {
|
|
10569
|
+
xs.push(label.x, label.x + label.text.length * 12);
|
|
10570
|
+
ys.push(-label.y, -label.y - 16);
|
|
10571
|
+
}
|
|
10572
|
+
for (const reference of references) {
|
|
10573
|
+
xs.push(reference.x);
|
|
10574
|
+
ys.push(-reference.y);
|
|
10575
|
+
}
|
|
10576
|
+
const minX = Math.min(...xs, 0);
|
|
10577
|
+
const maxX = Math.max(...xs, 100);
|
|
10578
|
+
const minY = Math.min(...ys, 0);
|
|
10579
|
+
const maxY = Math.max(...ys, 100);
|
|
10580
|
+
const width = Math.max(1, maxX - minX);
|
|
10581
|
+
const height = Math.max(1, maxY - minY);
|
|
10582
|
+
const padding = Math.max(width, height) * 0.06;
|
|
10583
|
+
return {
|
|
10584
|
+
minX: minX - padding,
|
|
10585
|
+
minY: minY - padding,
|
|
10586
|
+
width: width + padding * 2,
|
|
10587
|
+
height: height + padding * 2,
|
|
10588
|
+
stroke: Math.max(width, height) / 900
|
|
10589
|
+
};
|
|
10590
|
+
}
|
|
10591
|
+
function createLayoutLayerControls(svg, layers, counts) {
|
|
10592
|
+
const controls = document.createElement("div");
|
|
10593
|
+
controls.className = "ofv-cad-layers ofv-layout-layers";
|
|
10594
|
+
const title = document.createElement("strong");
|
|
10595
|
+
title.textContent = `\u56FE\u5C42 ${layers.length}`;
|
|
10596
|
+
controls.append(title);
|
|
10597
|
+
for (const layer of layers) {
|
|
10598
|
+
const label = document.createElement("label");
|
|
10599
|
+
const checkbox = document.createElement("input");
|
|
10600
|
+
checkbox.type = "checkbox";
|
|
10601
|
+
checkbox.checked = true;
|
|
10602
|
+
checkbox.addEventListener("change", () => {
|
|
10603
|
+
for (const element of svg.querySelectorAll(`[data-layer="${escapeCssAttribute(layer)}"]`)) {
|
|
10604
|
+
element.style.display = checkbox.checked ? "" : "none";
|
|
10605
|
+
}
|
|
10606
|
+
});
|
|
10607
|
+
const name = document.createElement("span");
|
|
10608
|
+
name.textContent = `${layer} (${counts.get(layer) || 0})`;
|
|
10609
|
+
label.append(checkbox, name);
|
|
10610
|
+
controls.append(label);
|
|
10611
|
+
}
|
|
10612
|
+
return controls;
|
|
10613
|
+
}
|
|
10614
|
+
function createLayoutCellList(cells, references) {
|
|
10615
|
+
const details = document.createElement("details");
|
|
10616
|
+
details.className = "ofv-details ofv-layout-cells";
|
|
10617
|
+
details.open = true;
|
|
10618
|
+
const summary = document.createElement("summary");
|
|
10619
|
+
summary.textContent = `Cell \u7ED3\u6784 ${cells.length}`;
|
|
10620
|
+
const list = document.createElement("ul");
|
|
10621
|
+
const refCounts = countBy(references.map((reference) => reference.cell));
|
|
10622
|
+
for (const cell of cells.slice(0, 120)) {
|
|
10623
|
+
const item = document.createElement("li");
|
|
10624
|
+
const count = refCounts.get(cell) || 0;
|
|
10625
|
+
item.textContent = count > 0 ? `${cell} \xB7 \u5F15\u7528 ${count}` : cell;
|
|
10626
|
+
list.append(item);
|
|
10627
|
+
}
|
|
10628
|
+
details.append(summary, list);
|
|
10629
|
+
return details;
|
|
10630
|
+
}
|
|
10631
|
+
function readUInt16(bytes, offset) {
|
|
10632
|
+
return bytes[offset] << 8 | bytes[offset + 1];
|
|
10633
|
+
}
|
|
10634
|
+
function readInt16(bytes, offset) {
|
|
10635
|
+
const value = readUInt16(bytes, offset);
|
|
10636
|
+
return value & 32768 ? value - 65536 : value;
|
|
10637
|
+
}
|
|
10638
|
+
function readInt32(bytes, offset) {
|
|
10639
|
+
const value = bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
|
|
10640
|
+
return value | 0;
|
|
10641
|
+
}
|
|
10642
|
+
function readGdsString(bytes) {
|
|
10643
|
+
return new TextDecoder("ascii").decode(bytes).replace(/\0+$/g, "").trim();
|
|
10644
|
+
}
|
|
10645
|
+
function readGdsPoints(bytes) {
|
|
10646
|
+
const points = [];
|
|
10647
|
+
for (let offset = 0; offset + 7 < bytes.length; offset += 8) {
|
|
10648
|
+
points.push([readInt32(bytes, offset), readInt32(bytes, offset + 4)]);
|
|
10649
|
+
}
|
|
10650
|
+
return points;
|
|
10651
|
+
}
|
|
10652
|
+
function formatGdsReal(bytes, offset) {
|
|
10653
|
+
const value = readGdsReal(bytes, offset);
|
|
10654
|
+
if (!Number.isFinite(value) || value === 0) {
|
|
10655
|
+
return "0";
|
|
10656
|
+
}
|
|
10657
|
+
if (Math.abs(value) < 1e-3 || Math.abs(value) >= 1e4) {
|
|
10658
|
+
return value.toExponential(4);
|
|
10659
|
+
}
|
|
10660
|
+
return String(Number(value.toPrecision(6)));
|
|
10661
|
+
}
|
|
10662
|
+
function readGdsReal(bytes, offset) {
|
|
10663
|
+
const first = bytes[offset];
|
|
10664
|
+
if (!first) {
|
|
10665
|
+
return 0;
|
|
10666
|
+
}
|
|
10667
|
+
const sign = first & 128 ? -1 : 1;
|
|
10668
|
+
const exponent = (first & 127) - 64;
|
|
10669
|
+
let mantissa = 0;
|
|
10670
|
+
for (let index = 1; index < 8; index++) {
|
|
10671
|
+
mantissa = mantissa * 256 + bytes[offset + index];
|
|
10672
|
+
}
|
|
10673
|
+
return sign * (mantissa / Math.pow(2, 56)) * Math.pow(16, exponent);
|
|
10674
|
+
}
|
|
10675
|
+
function sumCounts(counts) {
|
|
10676
|
+
return [...counts.values()].reduce((sum, count) => sum + count, 0);
|
|
10677
|
+
}
|
|
10678
|
+
function extractOasisCblocks(bytes) {
|
|
10679
|
+
const chunks = [];
|
|
10680
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10681
|
+
const limit = Math.min(bytes.length, 25e4);
|
|
10682
|
+
for (let offset = 0; offset < limit; offset++) {
|
|
10683
|
+
try {
|
|
10684
|
+
const inflated = import_pako3.default.inflateRaw(bytes.slice(offset));
|
|
10685
|
+
if (inflated.byteLength < 4) {
|
|
10686
|
+
continue;
|
|
10687
|
+
}
|
|
10688
|
+
const ascii = extractAsciiRuns(inflated);
|
|
10689
|
+
const hasLayoutSignal = ascii.some((item) => item.startsWith("S_") || /TOP|CELL|DIE|SIZE/i.test(item));
|
|
10690
|
+
const hasRecordSignal = inflated.some((byte) => byte >= 13 && byte <= 34);
|
|
10691
|
+
if (!hasLayoutSignal && !hasRecordSignal) {
|
|
10692
|
+
continue;
|
|
10693
|
+
}
|
|
10694
|
+
const signature = `${inflated.byteLength}:${Array.from(inflated.slice(0, 12)).join(",")}`;
|
|
10695
|
+
if (seen.has(signature)) {
|
|
10696
|
+
continue;
|
|
10697
|
+
}
|
|
10698
|
+
seen.add(signature);
|
|
10699
|
+
chunks.push({ offset, bytes: inflated });
|
|
10700
|
+
if (chunks.length >= 12) {
|
|
10701
|
+
break;
|
|
10702
|
+
}
|
|
10703
|
+
} catch {
|
|
10704
|
+
}
|
|
10705
|
+
}
|
|
10706
|
+
return chunks;
|
|
10707
|
+
}
|
|
10708
|
+
function scanOasisRecordCounts(bytes) {
|
|
10709
|
+
const counts = /* @__PURE__ */ new Map();
|
|
10710
|
+
for (const byte of bytes.slice(0, Math.min(bytes.length, 12e3))) {
|
|
10711
|
+
const name = oasisRecordNames[byte];
|
|
10712
|
+
if (name) {
|
|
10713
|
+
counts.set(name, (counts.get(name) || 0) + 1);
|
|
10714
|
+
}
|
|
10715
|
+
}
|
|
10716
|
+
return counts;
|
|
10717
|
+
}
|
|
10718
|
+
function readOasisVersion(bytes) {
|
|
10719
|
+
const magic = "%SEMI-OASIS\r\n";
|
|
10720
|
+
const header = new TextDecoder("ascii").decode(bytes.slice(0, Math.min(bytes.length, 48)));
|
|
10721
|
+
if (!header.startsWith(magic)) {
|
|
10722
|
+
return void 0;
|
|
10723
|
+
}
|
|
10724
|
+
const start = magic.length;
|
|
10725
|
+
if (bytes[start] !== 1) {
|
|
10726
|
+
return "OASIS";
|
|
10727
|
+
}
|
|
10728
|
+
const length = bytes[start + 1];
|
|
10729
|
+
const version = new TextDecoder("ascii").decode(bytes.slice(start + 2, start + 2 + length));
|
|
10730
|
+
return version ? `OASIS ${version}` : "OASIS";
|
|
10731
|
+
}
|
|
10732
|
+
function createOasisStructureShapes(cellNames, fallbackCount) {
|
|
10733
|
+
const rows = Math.max(1, cellNames.length || fallbackCount);
|
|
10734
|
+
const shapes = [];
|
|
10735
|
+
for (let index = 0; index < rows; index++) {
|
|
10736
|
+
const top = -(index * 18);
|
|
10737
|
+
const height = 12;
|
|
10738
|
+
const width = 88 + Math.min((cellNames[index]?.length || 5) * 4, 90);
|
|
10739
|
+
shapes.push({
|
|
10740
|
+
kind: "box",
|
|
10741
|
+
layer: "cell",
|
|
10742
|
+
points: [
|
|
10743
|
+
[0, top],
|
|
10744
|
+
[width, top],
|
|
10745
|
+
[width, top - height],
|
|
10746
|
+
[0, top - height],
|
|
10747
|
+
[0, top]
|
|
10748
|
+
]
|
|
10749
|
+
});
|
|
10750
|
+
}
|
|
10751
|
+
return shapes;
|
|
10752
|
+
}
|
|
10030
10753
|
function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
|
|
10031
10754
|
const bytes = new Uint8Array(arrayBuffer);
|
|
10032
10755
|
const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);
|