@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.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",
|
|
@@ -1752,6 +1755,7 @@ function imagePlugin() {
|
|
|
1752
1755
|
let dragStartY = 0;
|
|
1753
1756
|
let startOffsetX = 0;
|
|
1754
1757
|
let startOffsetY = 0;
|
|
1758
|
+
let activePointerId = null;
|
|
1755
1759
|
const zoomLabel = document.createElement("span");
|
|
1756
1760
|
zoomLabel.className = "ofv-image-zoom";
|
|
1757
1761
|
const updateTransform = () => {
|
|
@@ -1799,29 +1803,61 @@ function imagePlugin() {
|
|
|
1799
1803
|
if (event.button !== 0) {
|
|
1800
1804
|
return;
|
|
1801
1805
|
}
|
|
1806
|
+
if (activePointerId !== null && activePointerId !== event.pointerId) {
|
|
1807
|
+
finishDrag(activePointerId);
|
|
1808
|
+
}
|
|
1802
1809
|
dragging = true;
|
|
1810
|
+
activePointerId = event.pointerId;
|
|
1803
1811
|
dragStartX = event.clientX;
|
|
1804
1812
|
dragStartY = event.clientY;
|
|
1805
1813
|
startOffsetX = offsetX;
|
|
1806
1814
|
startOffsetY = offsetY;
|
|
1807
1815
|
stage.classList.add("is-dragging");
|
|
1808
|
-
|
|
1816
|
+
try {
|
|
1817
|
+
stage.setPointerCapture(event.pointerId);
|
|
1818
|
+
} catch {
|
|
1819
|
+
}
|
|
1809
1820
|
};
|
|
1810
1821
|
const onPointerMove = (event) => {
|
|
1811
|
-
if (!dragging) {
|
|
1822
|
+
if (!dragging || event.pointerId !== activePointerId) {
|
|
1812
1823
|
return;
|
|
1813
1824
|
}
|
|
1814
1825
|
offsetX = startOffsetX + event.clientX - dragStartX;
|
|
1815
1826
|
offsetY = startOffsetY + event.clientY - dragStartY;
|
|
1816
1827
|
updateTransform();
|
|
1817
1828
|
};
|
|
1818
|
-
const
|
|
1829
|
+
const finishDrag = (pointerId) => {
|
|
1830
|
+
const captureId = pointerId ?? activePointerId;
|
|
1819
1831
|
dragging = false;
|
|
1832
|
+
activePointerId = null;
|
|
1820
1833
|
stage.classList.remove("is-dragging");
|
|
1821
|
-
if (
|
|
1822
|
-
|
|
1834
|
+
if (captureId !== null && captureId !== void 0) {
|
|
1835
|
+
try {
|
|
1836
|
+
if (stage.hasPointerCapture(captureId)) {
|
|
1837
|
+
stage.releasePointerCapture(captureId);
|
|
1838
|
+
}
|
|
1839
|
+
} catch {
|
|
1840
|
+
}
|
|
1823
1841
|
}
|
|
1824
1842
|
};
|
|
1843
|
+
const onPointerUp = (event) => {
|
|
1844
|
+
if (event.pointerId === activePointerId) {
|
|
1845
|
+
finishDrag(event.pointerId);
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
const onLostPointerCapture = (event) => {
|
|
1849
|
+
if (event.pointerId === activePointerId) {
|
|
1850
|
+
finishDrag(null);
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
const onPointerLeave = (event) => {
|
|
1854
|
+
if (event.pointerId === activePointerId && event.buttons === 0) {
|
|
1855
|
+
finishDrag(event.pointerId);
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
const onWindowBlur = () => {
|
|
1859
|
+
finishDrag();
|
|
1860
|
+
};
|
|
1825
1861
|
const onWheel = (event) => {
|
|
1826
1862
|
if (!event.ctrlKey && !event.metaKey) {
|
|
1827
1863
|
return;
|
|
@@ -1833,8 +1869,11 @@ function imagePlugin() {
|
|
|
1833
1869
|
stage.addEventListener("pointermove", onPointerMove);
|
|
1834
1870
|
stage.addEventListener("pointerup", onPointerUp);
|
|
1835
1871
|
stage.addEventListener("pointercancel", onPointerUp);
|
|
1872
|
+
stage.addEventListener("lostpointercapture", onLostPointerCapture);
|
|
1873
|
+
stage.addEventListener("pointerleave", onPointerLeave);
|
|
1836
1874
|
stage.addEventListener("wheel", onWheel, { passive: false });
|
|
1837
1875
|
image.addEventListener("error", showImageFallback);
|
|
1876
|
+
window.addEventListener("blur", onWindowBlur);
|
|
1838
1877
|
stage.append(image);
|
|
1839
1878
|
wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
|
|
1840
1879
|
ctx.viewport.append(wrapper);
|
|
@@ -1881,8 +1920,12 @@ function imagePlugin() {
|
|
|
1881
1920
|
stage.removeEventListener("pointermove", onPointerMove);
|
|
1882
1921
|
stage.removeEventListener("pointerup", onPointerUp);
|
|
1883
1922
|
stage.removeEventListener("pointercancel", onPointerUp);
|
|
1923
|
+
stage.removeEventListener("lostpointercapture", onLostPointerCapture);
|
|
1924
|
+
stage.removeEventListener("pointerleave", onPointerLeave);
|
|
1884
1925
|
stage.removeEventListener("wheel", onWheel);
|
|
1885
1926
|
image.removeEventListener("error", showImageFallback);
|
|
1927
|
+
window.removeEventListener("blur", onWindowBlur);
|
|
1928
|
+
finishDrag();
|
|
1886
1929
|
wrapper.remove();
|
|
1887
1930
|
if (convertedBlob) {
|
|
1888
1931
|
URL.revokeObjectURL(url);
|
|
@@ -4031,6 +4074,38 @@ async function readText(source) {
|
|
|
4031
4074
|
return String(source);
|
|
4032
4075
|
}
|
|
4033
4076
|
|
|
4077
|
+
// src/plugins/encrypted.ts
|
|
4078
|
+
function createEncryptedFallback(file, url, copy = {}) {
|
|
4079
|
+
const fallback = document.createElement("div");
|
|
4080
|
+
fallback.className = "ofv-fallback ofv-encrypted";
|
|
4081
|
+
const title = document.createElement("strong");
|
|
4082
|
+
title.textContent = copy.title || "\u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8";
|
|
4083
|
+
const message = document.createElement("span");
|
|
4084
|
+
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";
|
|
4085
|
+
const meta = document.createElement("dl");
|
|
4086
|
+
meta.className = "ofv-fallback-meta ofv-encrypted-meta";
|
|
4087
|
+
appendEncryptedMeta(meta, "\u6587\u4EF6", file.name || "\u672A\u547D\u540D\u6587\u4EF6");
|
|
4088
|
+
appendEncryptedMeta(meta, "\u683C\u5F0F", file.extension ? `.${file.extension}` : file.mimeType || "\u672A\u77E5");
|
|
4089
|
+
const download = document.createElement("a");
|
|
4090
|
+
download.href = url;
|
|
4091
|
+
download.download = file.name;
|
|
4092
|
+
download.textContent = copy.action || "\u4E0B\u8F7D\u6587\u4EF6";
|
|
4093
|
+
fallback.append(title, message, meta, download);
|
|
4094
|
+
return fallback;
|
|
4095
|
+
}
|
|
4096
|
+
function isEncryptedError(error) {
|
|
4097
|
+
const message = error instanceof Error ? error.message : String(error || "");
|
|
4098
|
+
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
4099
|
+
return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(`${name} ${message}`);
|
|
4100
|
+
}
|
|
4101
|
+
function appendEncryptedMeta(parent, label, value) {
|
|
4102
|
+
const key = document.createElement("dt");
|
|
4103
|
+
key.textContent = label;
|
|
4104
|
+
const content = document.createElement("dd");
|
|
4105
|
+
content.textContent = value;
|
|
4106
|
+
parent.append(key, content);
|
|
4107
|
+
}
|
|
4108
|
+
|
|
4034
4109
|
// src/plugins/pdf.ts
|
|
4035
4110
|
function multiplyMatrices(m1, m2) {
|
|
4036
4111
|
return [
|
|
@@ -4072,7 +4147,11 @@ function pdfPlugin(options = {}) {
|
|
|
4072
4147
|
const doc = await documentTask.promise.catch((error) => {
|
|
4073
4148
|
viewer.remove();
|
|
4074
4149
|
ctx.viewport.classList.add("ofv-center");
|
|
4075
|
-
const fallback =
|
|
4150
|
+
const fallback = isEncryptedError(error) ? createEncryptedFallback(ctx.file, url, {
|
|
4151
|
+
title: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
4152
|
+
message: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
|
|
4153
|
+
action: "\u4E0B\u8F7D PDF"
|
|
4154
|
+
}) : createPdfFallback(ctx.file.name, url, normalizePdfError(error));
|
|
4076
4155
|
ctx.viewport.append(fallback);
|
|
4077
4156
|
return void 0;
|
|
4078
4157
|
});
|
|
@@ -4362,9 +4441,6 @@ function normalizePdfError(error) {
|
|
|
4362
4441
|
const message = error instanceof Error ? error.message : String(error || "");
|
|
4363
4442
|
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
4364
4443
|
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
4444
|
if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
|
|
4369
4445
|
return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
|
|
4370
4446
|
}
|
|
@@ -5394,6 +5470,10 @@ async function renderSheet(panel, arrayBuffer, extension) {
|
|
|
5394
5470
|
}
|
|
5395
5471
|
}
|
|
5396
5472
|
function renderSheetFallback(panel, extension, detail) {
|
|
5473
|
+
if (isEncryptedText(detail)) {
|
|
5474
|
+
renderEncryptedOfficeByFileInfo(panel, `.${extension || "sheet"}`, "Office \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8");
|
|
5475
|
+
return;
|
|
5476
|
+
}
|
|
5397
5477
|
const section = createSection("\u8868\u683C\u89E3\u6790\u5931\u8D25");
|
|
5398
5478
|
const title = document.createElement("p");
|
|
5399
5479
|
title.textContent = `.${extension || "sheet"} \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790\u4E3A\u53EF\u9884\u89C8\u8868\u683C\u3002`;
|
|
@@ -5404,6 +5484,17 @@ function renderSheetFallback(panel, extension, detail) {
|
|
|
5404
5484
|
section.append(title, meta, support);
|
|
5405
5485
|
panel.append(section);
|
|
5406
5486
|
}
|
|
5487
|
+
function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
|
|
5488
|
+
const section = createSection(title);
|
|
5489
|
+
section.classList.add("ofv-encrypted");
|
|
5490
|
+
const message = document.createElement("p");
|
|
5491
|
+
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`;
|
|
5492
|
+
section.append(message);
|
|
5493
|
+
panel.append(section);
|
|
5494
|
+
}
|
|
5495
|
+
function isEncryptedText(value) {
|
|
5496
|
+
return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(value);
|
|
5497
|
+
}
|
|
5407
5498
|
function renderFlatOds(panel, xml) {
|
|
5408
5499
|
const sheets = parseFlatOds(xml);
|
|
5409
5500
|
renderParsedSheets(panel, sheets, "FODS \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002");
|
|
@@ -6712,7 +6803,7 @@ function ofdPlugin() {
|
|
|
6712
6803
|
try {
|
|
6713
6804
|
zip = await JSZip4.loadAsync(await readArrayBuffer(ctx.file));
|
|
6714
6805
|
} catch (error) {
|
|
6715
|
-
panel.append(
|
|
6806
|
+
panel.append(createOfdFailure(ctx.file, url, error));
|
|
6716
6807
|
return {
|
|
6717
6808
|
destroy() {
|
|
6718
6809
|
panel.remove();
|
|
@@ -6729,7 +6820,7 @@ function ofdPlugin() {
|
|
|
6729
6820
|
textFragments.push(...matches);
|
|
6730
6821
|
}
|
|
6731
6822
|
} catch (error) {
|
|
6732
|
-
panel.append(
|
|
6823
|
+
panel.append(createOfdFailure(ctx.file, url, error));
|
|
6733
6824
|
return {
|
|
6734
6825
|
destroy() {
|
|
6735
6826
|
panel.remove();
|
|
@@ -7354,6 +7445,16 @@ function finiteNumber2(value, fallback) {
|
|
|
7354
7445
|
function formatOfdCssNumber(value) {
|
|
7355
7446
|
return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
7356
7447
|
}
|
|
7448
|
+
function createOfdFailure(file, url, error) {
|
|
7449
|
+
if (isEncryptedError(error)) {
|
|
7450
|
+
return createEncryptedFallback(file, url, {
|
|
7451
|
+
title: "OFD \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
7452
|
+
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",
|
|
7453
|
+
action: "\u4E0B\u8F7D OFD"
|
|
7454
|
+
});
|
|
7455
|
+
}
|
|
7456
|
+
return createOfdFallback(file.name, url, normalizeOfdError(error));
|
|
7457
|
+
}
|
|
7357
7458
|
function createOfdFallback(fileName, url, detail) {
|
|
7358
7459
|
const fallback = document.createElement("div");
|
|
7359
7460
|
fallback.className = "ofv-fallback";
|
|
@@ -7422,7 +7523,9 @@ function archivePlugin() {
|
|
|
7422
7523
|
try {
|
|
7423
7524
|
if (ext === "zip") {
|
|
7424
7525
|
try {
|
|
7425
|
-
const zip = await JSZip5.loadAsync(await readArrayBuffer(ctx.file)
|
|
7526
|
+
const zip = await JSZip5.loadAsync(await readArrayBuffer(ctx.file), {
|
|
7527
|
+
decodeFileName: decodeZipFileName
|
|
7528
|
+
});
|
|
7426
7529
|
archiveEntries = Object.values(zip.files).map((entry) => ({
|
|
7427
7530
|
name: entry.name,
|
|
7428
7531
|
unsafeName: entry.unsafeOriginalName,
|
|
@@ -7431,7 +7534,7 @@ function archivePlugin() {
|
|
|
7431
7534
|
read: () => entry.async("arraybuffer")
|
|
7432
7535
|
}));
|
|
7433
7536
|
} catch (zipErr) {
|
|
7434
|
-
if (
|
|
7537
|
+
if (isEncryptedError(zipErr)) {
|
|
7435
7538
|
isEncrypted = true;
|
|
7436
7539
|
} else {
|
|
7437
7540
|
throw zipErr;
|
|
@@ -7464,17 +7567,11 @@ function archivePlugin() {
|
|
|
7464
7567
|
parseError = `\u538B\u7F29\u5305\u89E3\u6790\u5931\u8D25\uFF1A${err.message || err}`;
|
|
7465
7568
|
}
|
|
7466
7569
|
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);
|
|
7570
|
+
const fallback = createEncryptedFallback(ctx.file, url, {
|
|
7571
|
+
title: "\u538B\u7F29\u5305\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
7572
|
+
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",
|
|
7573
|
+
action: "\u4E0B\u8F7D\u538B\u7F29\u5305"
|
|
7574
|
+
});
|
|
7478
7575
|
panel.append(fallback);
|
|
7479
7576
|
ctx.viewport.classList.add("ofv-center");
|
|
7480
7577
|
return {
|
|
@@ -7723,6 +7820,28 @@ async function findSubPreviewPlugin(plugins, file) {
|
|
|
7723
7820
|
}
|
|
7724
7821
|
return fallbackPlugin();
|
|
7725
7822
|
}
|
|
7823
|
+
function decodeZipFileName(bytes) {
|
|
7824
|
+
const data = Array.isArray(bytes) ? Uint8Array.from(bytes.map((value) => value.charCodeAt(0) & 255)) : bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);
|
|
7825
|
+
const utf8 = decodeZipNameWith(data, "utf-8", true);
|
|
7826
|
+
if (utf8 && !looksMojibake(utf8)) {
|
|
7827
|
+
return utf8;
|
|
7828
|
+
}
|
|
7829
|
+
const gb18030 = decodeZipNameWith(data, "gb18030", false) || decodeZipNameWith(data, "gbk", false);
|
|
7830
|
+
if (gb18030 && !looksMojibake(gb18030)) {
|
|
7831
|
+
return gb18030;
|
|
7832
|
+
}
|
|
7833
|
+
return utf8 || new TextDecoder("latin1").decode(data);
|
|
7834
|
+
}
|
|
7835
|
+
function decodeZipNameWith(bytes, encoding, fatal) {
|
|
7836
|
+
try {
|
|
7837
|
+
return new TextDecoder(encoding, { fatal }).decode(bytes);
|
|
7838
|
+
} catch {
|
|
7839
|
+
return void 0;
|
|
7840
|
+
}
|
|
7841
|
+
}
|
|
7842
|
+
function looksMojibake(value) {
|
|
7843
|
+
return /[\uFFFDÃÂÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß]/.test(value);
|
|
7844
|
+
}
|
|
7726
7845
|
function createInlineError(titleText, detailText) {
|
|
7727
7846
|
const fallback = document.createElement("div");
|
|
7728
7847
|
fallback.className = "ofv-fallback";
|
|
@@ -8144,13 +8263,14 @@ function emailPlugin() {
|
|
|
8144
8263
|
} else {
|
|
8145
8264
|
const PostalMime = (await import("postal-mime")).default;
|
|
8146
8265
|
const parser = new PostalMime();
|
|
8147
|
-
let
|
|
8266
|
+
let rawSource = await readArrayBuffer(ctx.file);
|
|
8148
8267
|
if (ext === "mbox") {
|
|
8268
|
+
let rawText = await readTextFile(ctx.file);
|
|
8149
8269
|
const messages = splitMboxMessages(rawText);
|
|
8150
8270
|
mboxSummary = messages.map(summarizeMboxMessage);
|
|
8151
|
-
|
|
8271
|
+
rawSource = messages[0] || rawText;
|
|
8152
8272
|
}
|
|
8153
|
-
const parsed = await parser.parse(
|
|
8273
|
+
const parsed = await parser.parse(rawSource);
|
|
8154
8274
|
const from = parsed.from ? `${parsed.from.name || ""} <${parsed.from.address || ""}>`.trim() : "";
|
|
8155
8275
|
const to = Array.isArray(parsed.to) ? parsed.to.map((t) => `${t.name || ""} <${t.address || ""}>`.trim()).join("; ") : "";
|
|
8156
8276
|
const cc = Array.isArray(parsed.cc) ? parsed.cc.map((c) => `${c.name || ""} <${c.address || ""}>`.trim()).join("; ") : "";
|
|
@@ -8220,13 +8340,17 @@ function emailPlugin() {
|
|
|
8220
8340
|
const sanitizedHtml = sanitizeEmailHtml(html);
|
|
8221
8341
|
const iframe = document.createElement("iframe");
|
|
8222
8342
|
iframe.className = "ofv-email-body-iframe";
|
|
8223
|
-
iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
|
|
8343
|
+
iframe.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
|
|
8224
8344
|
iframe.style.cssText = "width: 100%; border: none; background: #fff; min-height: 200px;";
|
|
8225
|
-
|
|
8226
|
-
|
|
8345
|
+
let renderedHtmlBody = false;
|
|
8346
|
+
const renderHtmlBody = () => {
|
|
8347
|
+
if (renderedHtmlBody) {
|
|
8348
|
+
return;
|
|
8349
|
+
}
|
|
8227
8350
|
try {
|
|
8228
8351
|
const idoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
8229
8352
|
if (idoc) {
|
|
8353
|
+
renderedHtmlBody = true;
|
|
8230
8354
|
idoc.open();
|
|
8231
8355
|
idoc.write(`
|
|
8232
8356
|
<!doctype html>
|
|
@@ -8277,6 +8401,9 @@ function emailPlugin() {
|
|
|
8277
8401
|
console.error("Failed to write html body to email iframe:", err);
|
|
8278
8402
|
}
|
|
8279
8403
|
};
|
|
8404
|
+
iframe.addEventListener("load", renderHtmlBody, { once: true });
|
|
8405
|
+
bodySection.append(iframe);
|
|
8406
|
+
renderHtmlBody();
|
|
8280
8407
|
} else {
|
|
8281
8408
|
const pre = document.createElement("pre");
|
|
8282
8409
|
pre.className = "ofv-text-block";
|
|
@@ -9798,6 +9925,7 @@ function decodeDrawioDiagram(value) {
|
|
|
9798
9925
|
}
|
|
9799
9926
|
|
|
9800
9927
|
// src/plugins/cad.ts
|
|
9928
|
+
import pako3 from "pako";
|
|
9801
9929
|
var cadExtensions = /* @__PURE__ */ new Set([
|
|
9802
9930
|
"dxf",
|
|
9803
9931
|
"dwg",
|
|
@@ -9814,7 +9942,10 @@ var cadExtensions = /* @__PURE__ */ new Set([
|
|
|
9814
9942
|
"3dm",
|
|
9815
9943
|
"skp",
|
|
9816
9944
|
"sldprt",
|
|
9817
|
-
"sldasm"
|
|
9945
|
+
"sldasm",
|
|
9946
|
+
"gds",
|
|
9947
|
+
"oas",
|
|
9948
|
+
"oasis"
|
|
9818
9949
|
]);
|
|
9819
9950
|
var cadMimeTypes = /* @__PURE__ */ new Set([
|
|
9820
9951
|
"application/acad",
|
|
@@ -9831,7 +9962,11 @@ var cadMimeTypes = /* @__PURE__ */ new Set([
|
|
|
9831
9962
|
"application/x-parasolid",
|
|
9832
9963
|
"model/vnd.3dm",
|
|
9833
9964
|
"application/vnd.sketchup.skp",
|
|
9834
|
-
"application/sldworks"
|
|
9965
|
+
"application/sldworks",
|
|
9966
|
+
"application/vnd.gds",
|
|
9967
|
+
"application/x-gdsii",
|
|
9968
|
+
"application/vnd.oasis.layout",
|
|
9969
|
+
"application/x-oasis-layout"
|
|
9835
9970
|
]);
|
|
9836
9971
|
var cadMimeFormatMap = {
|
|
9837
9972
|
"application/acad": "dwg",
|
|
@@ -9848,7 +9983,11 @@ var cadMimeFormatMap = {
|
|
|
9848
9983
|
"application/x-parasolid": "x_t",
|
|
9849
9984
|
"model/vnd.3dm": "3dm",
|
|
9850
9985
|
"application/vnd.sketchup.skp": "skp",
|
|
9851
|
-
"application/sldworks": "sldprt"
|
|
9986
|
+
"application/sldworks": "sldprt",
|
|
9987
|
+
"application/vnd.gds": "gds",
|
|
9988
|
+
"application/x-gdsii": "gds",
|
|
9989
|
+
"application/vnd.oasis.layout": "oas",
|
|
9990
|
+
"application/x-oasis-layout": "oas"
|
|
9852
9991
|
};
|
|
9853
9992
|
function cadPlugin() {
|
|
9854
9993
|
return {
|
|
@@ -9876,6 +10015,36 @@ function cadPlugin() {
|
|
|
9876
10015
|
renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
|
|
9877
10016
|
return { destroy: () => panel.remove() };
|
|
9878
10017
|
}
|
|
10018
|
+
if (extension === "gds") {
|
|
10019
|
+
const viewer2 = renderLayoutPreview(panel, parseGdsLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
|
|
10020
|
+
return {
|
|
10021
|
+
canCommand(command) {
|
|
10022
|
+
return viewer2.canCommand(command);
|
|
10023
|
+
},
|
|
10024
|
+
command(command) {
|
|
10025
|
+
return viewer2.command(command);
|
|
10026
|
+
},
|
|
10027
|
+
destroy() {
|
|
10028
|
+
viewer2.destroy();
|
|
10029
|
+
panel.remove();
|
|
10030
|
+
}
|
|
10031
|
+
};
|
|
10032
|
+
}
|
|
10033
|
+
if (extension === "oas" || extension === "oasis") {
|
|
10034
|
+
const viewer2 = renderLayoutPreview(panel, parseOasisLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
|
|
10035
|
+
return {
|
|
10036
|
+
canCommand(command) {
|
|
10037
|
+
return viewer2.canCommand(command);
|
|
10038
|
+
},
|
|
10039
|
+
command(command) {
|
|
10040
|
+
return viewer2.command(command);
|
|
10041
|
+
},
|
|
10042
|
+
destroy() {
|
|
10043
|
+
viewer2.destroy();
|
|
10044
|
+
panel.remove();
|
|
10045
|
+
}
|
|
10046
|
+
};
|
|
10047
|
+
}
|
|
9879
10048
|
if (extension !== "dxf") {
|
|
9880
10049
|
const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
|
|
9881
10050
|
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`);
|
|
@@ -9974,6 +10143,560 @@ function renderIfc(panel, text) {
|
|
|
9974
10143
|
section.append(table);
|
|
9975
10144
|
panel.append(section);
|
|
9976
10145
|
}
|
|
10146
|
+
var layoutPalette = [
|
|
10147
|
+
"#2563eb",
|
|
10148
|
+
"#dc2626",
|
|
10149
|
+
"#059669",
|
|
10150
|
+
"#7c3aed",
|
|
10151
|
+
"#d97706",
|
|
10152
|
+
"#0891b2",
|
|
10153
|
+
"#be123c",
|
|
10154
|
+
"#4f46e5",
|
|
10155
|
+
"#15803d",
|
|
10156
|
+
"#a16207"
|
|
10157
|
+
];
|
|
10158
|
+
var gdsRecordNames = {
|
|
10159
|
+
0: "HEADER",
|
|
10160
|
+
1: "BGNLIB",
|
|
10161
|
+
2: "LIBNAME",
|
|
10162
|
+
3: "UNITS",
|
|
10163
|
+
4: "ENDLIB",
|
|
10164
|
+
5: "BGNSTR",
|
|
10165
|
+
6: "STRNAME",
|
|
10166
|
+
7: "ENDSTR",
|
|
10167
|
+
8: "BOUNDARY",
|
|
10168
|
+
9: "PATH",
|
|
10169
|
+
10: "SREF",
|
|
10170
|
+
11: "AREF",
|
|
10171
|
+
12: "TEXT",
|
|
10172
|
+
13: "LAYER",
|
|
10173
|
+
14: "DATATYPE",
|
|
10174
|
+
15: "WIDTH",
|
|
10175
|
+
16: "XY",
|
|
10176
|
+
17: "ENDEL",
|
|
10177
|
+
18: "SNAME",
|
|
10178
|
+
22: "TEXTTYPE",
|
|
10179
|
+
25: "STRING",
|
|
10180
|
+
45: "BOX"
|
|
10181
|
+
};
|
|
10182
|
+
var oasisRecordNames = {
|
|
10183
|
+
0: "PAD",
|
|
10184
|
+
1: "START",
|
|
10185
|
+
2: "END",
|
|
10186
|
+
3: "CELLNAME",
|
|
10187
|
+
4: "CELLNAME-REF",
|
|
10188
|
+
5: "TEXTSTRING",
|
|
10189
|
+
6: "TEXTSTRING-REF",
|
|
10190
|
+
7: "PROPNAME",
|
|
10191
|
+
8: "PROPNAME-REF",
|
|
10192
|
+
9: "PROPSTRING",
|
|
10193
|
+
10: "PROPSTRING-REF",
|
|
10194
|
+
11: "LAYERNAME",
|
|
10195
|
+
12: "LAYERNAME-REF",
|
|
10196
|
+
13: "CELL",
|
|
10197
|
+
14: "XYABSOLUTE",
|
|
10198
|
+
15: "XYRELATIVE",
|
|
10199
|
+
16: "PLACEMENT",
|
|
10200
|
+
17: "PLACEMENT",
|
|
10201
|
+
18: "TEXT",
|
|
10202
|
+
19: "RECTANGLE",
|
|
10203
|
+
20: "POLYGON",
|
|
10204
|
+
21: "PATH",
|
|
10205
|
+
22: "TRAPEZOID",
|
|
10206
|
+
23: "TRAPEZOID",
|
|
10207
|
+
24: "TRAPEZOID",
|
|
10208
|
+
25: "CTRAPEZOID",
|
|
10209
|
+
26: "CIRCLE",
|
|
10210
|
+
27: "PROPERTY",
|
|
10211
|
+
28: "PROPERTY",
|
|
10212
|
+
29: "XNAME",
|
|
10213
|
+
30: "XNAME-REF",
|
|
10214
|
+
31: "XELEMENT",
|
|
10215
|
+
32: "XGEOMETRY",
|
|
10216
|
+
33: "CBLOCK"
|
|
10217
|
+
};
|
|
10218
|
+
function renderLayoutPreview(panel, data, ctx) {
|
|
10219
|
+
const section = createSection(`${data.format} \u7248\u56FE\u9884\u89C8`);
|
|
10220
|
+
const summary = document.createElement("div");
|
|
10221
|
+
summary.className = "ofv-cad-summary ofv-layout-summary";
|
|
10222
|
+
appendMeta5(summary, "\u6587\u4EF6", data.fileName);
|
|
10223
|
+
appendMeta5(summary, "\u683C\u5F0F", data.format);
|
|
10224
|
+
if (data.libraryName) {
|
|
10225
|
+
appendMeta5(summary, "\u5E93", data.libraryName);
|
|
10226
|
+
}
|
|
10227
|
+
if (data.version) {
|
|
10228
|
+
appendMeta5(summary, "\u7248\u672C", data.version);
|
|
10229
|
+
}
|
|
10230
|
+
if (data.unit) {
|
|
10231
|
+
appendMeta5(summary, "\u5355\u4F4D", data.unit);
|
|
10232
|
+
}
|
|
10233
|
+
appendMeta5(summary, "Cell", data.cells.length);
|
|
10234
|
+
appendMeta5(summary, "\u51E0\u4F55", data.shapes.length);
|
|
10235
|
+
appendMeta5(summary, "\u5F15\u7528", data.references.length);
|
|
10236
|
+
appendMeta5(summary, "\u6587\u5B57", data.labels.length);
|
|
10237
|
+
for (const [label, value] of data.metadata) {
|
|
10238
|
+
appendMeta5(summary, label, value);
|
|
10239
|
+
}
|
|
10240
|
+
section.append(summary);
|
|
10241
|
+
for (const noteText of [...data.notes, ...data.warnings]) {
|
|
10242
|
+
const note = document.createElement("p");
|
|
10243
|
+
note.className = data.warnings.includes(noteText) ? "ofv-layout-warning" : "ofv-layout-note";
|
|
10244
|
+
note.textContent = noteText;
|
|
10245
|
+
section.append(note);
|
|
10246
|
+
}
|
|
10247
|
+
const bounds = computeLayoutBounds(data.shapes, data.labels, data.references);
|
|
10248
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
10249
|
+
svg.setAttribute("class", "ofv-svg-stage ofv-layout-stage");
|
|
10250
|
+
let currentViewBox = { x: bounds.minX, y: bounds.minY, width: bounds.width, height: bounds.height };
|
|
10251
|
+
const initialViewBox = { ...currentViewBox };
|
|
10252
|
+
const applyViewBox = () => {
|
|
10253
|
+
svg.setAttribute("viewBox", `${currentViewBox.x} ${currentViewBox.y} ${currentViewBox.width} ${currentViewBox.height}`);
|
|
10254
|
+
};
|
|
10255
|
+
applyViewBox();
|
|
10256
|
+
if (data.shapes.length === 0) {
|
|
10257
|
+
const empty = document.createElementNS(svg.namespaceURI, "text");
|
|
10258
|
+
empty.setAttribute("x", String(bounds.minX + bounds.width * 0.5));
|
|
10259
|
+
empty.setAttribute("y", String(bounds.minY + bounds.height * 0.5));
|
|
10260
|
+
empty.setAttribute("text-anchor", "middle");
|
|
10261
|
+
empty.setAttribute("font-size", String(Math.max(bounds.width, bounds.height) / 34));
|
|
10262
|
+
empty.setAttribute("fill", "currentColor");
|
|
10263
|
+
empty.textContent = "\u5DF2\u8BC6\u522B\u7248\u56FE\u6587\u4EF6\uFF0C\u5F53\u524D\u6587\u4EF6\u672A\u89E3\u6790\u51FA\u53EF\u7ED8\u5236\u51E0\u4F55";
|
|
10264
|
+
svg.append(empty);
|
|
10265
|
+
}
|
|
10266
|
+
const layerIndex = new Map([...data.layers.keys()].sort((a, b) => a.localeCompare(b)).map((layer, index) => [layer, index]));
|
|
10267
|
+
for (const shape of data.shapes.slice(0, 6e3)) {
|
|
10268
|
+
const color = layoutPalette[(layerIndex.get(shape.layer) || 0) % layoutPalette.length];
|
|
10269
|
+
if (shape.kind === "path") {
|
|
10270
|
+
const polyline = document.createElementNS(svg.namespaceURI, "polyline");
|
|
10271
|
+
polyline.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
|
|
10272
|
+
polyline.setAttribute("fill", "none");
|
|
10273
|
+
polyline.setAttribute("stroke", color);
|
|
10274
|
+
polyline.setAttribute("stroke-width", String(Math.max(bounds.stroke, Math.abs(shape.width || 0))));
|
|
10275
|
+
polyline.setAttribute("stroke-linecap", "round");
|
|
10276
|
+
polyline.setAttribute("stroke-linejoin", "round");
|
|
10277
|
+
applyLayer(polyline, shape.layer);
|
|
10278
|
+
svg.append(polyline);
|
|
10279
|
+
continue;
|
|
10280
|
+
}
|
|
10281
|
+
const polygon = document.createElementNS(svg.namespaceURI, "polygon");
|
|
10282
|
+
polygon.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
|
|
10283
|
+
polygon.setAttribute("fill", color);
|
|
10284
|
+
polygon.setAttribute("fill-opacity", "0.18");
|
|
10285
|
+
polygon.setAttribute("stroke", color);
|
|
10286
|
+
polygon.setAttribute("stroke-width", String(bounds.stroke));
|
|
10287
|
+
polygon.setAttribute("vector-effect", "non-scaling-stroke");
|
|
10288
|
+
applyLayer(polygon, shape.layer);
|
|
10289
|
+
svg.append(polygon);
|
|
10290
|
+
}
|
|
10291
|
+
for (const label of data.labels.slice(0, 400)) {
|
|
10292
|
+
const text = document.createElementNS(svg.namespaceURI, "text");
|
|
10293
|
+
text.setAttribute("x", String(label.x));
|
|
10294
|
+
text.setAttribute("y", String(-label.y));
|
|
10295
|
+
text.setAttribute("font-size", String(Math.max(bounds.stroke * 12, Math.max(bounds.width, bounds.height) / 120)));
|
|
10296
|
+
text.setAttribute("fill", "currentColor");
|
|
10297
|
+
text.textContent = label.text;
|
|
10298
|
+
applyLayer(text, label.layer);
|
|
10299
|
+
svg.append(text);
|
|
10300
|
+
}
|
|
10301
|
+
const layers = [...data.layers.keys()].sort((a, b) => a.localeCompare(b));
|
|
10302
|
+
if (layers.length > 0) {
|
|
10303
|
+
section.append(createLayoutLayerControls(svg, layers, data.layers));
|
|
10304
|
+
}
|
|
10305
|
+
section.append(svg);
|
|
10306
|
+
if (data.cells.length > 0) {
|
|
10307
|
+
section.append(createLayoutCellList(data.cells, data.references));
|
|
10308
|
+
}
|
|
10309
|
+
panel.append(section);
|
|
10310
|
+
const updateToolbarZoom = () => ctx.toolbar?.setZoom(initialViewBox.width / currentViewBox.width);
|
|
10311
|
+
updateToolbarZoom();
|
|
10312
|
+
return {
|
|
10313
|
+
canCommand(command) {
|
|
10314
|
+
return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
|
|
10315
|
+
},
|
|
10316
|
+
command(command) {
|
|
10317
|
+
if (command === "zoom-in" || command === "zoom-out") {
|
|
10318
|
+
const factor = command === "zoom-in" ? 0.82 : 1.18;
|
|
10319
|
+
const centerX = currentViewBox.x + currentViewBox.width / 2;
|
|
10320
|
+
const centerY = currentViewBox.y + currentViewBox.height / 2;
|
|
10321
|
+
currentViewBox.width *= factor;
|
|
10322
|
+
currentViewBox.height *= factor;
|
|
10323
|
+
currentViewBox.x = centerX - currentViewBox.width / 2;
|
|
10324
|
+
currentViewBox.y = centerY - currentViewBox.height / 2;
|
|
10325
|
+
applyViewBox();
|
|
10326
|
+
updateToolbarZoom();
|
|
10327
|
+
return true;
|
|
10328
|
+
}
|
|
10329
|
+
if (command === "zoom-reset") {
|
|
10330
|
+
currentViewBox = { ...initialViewBox };
|
|
10331
|
+
applyViewBox();
|
|
10332
|
+
updateToolbarZoom();
|
|
10333
|
+
return true;
|
|
10334
|
+
}
|
|
10335
|
+
return false;
|
|
10336
|
+
},
|
|
10337
|
+
destroy() {
|
|
10338
|
+
ctx.toolbar?.setZoom(void 0);
|
|
10339
|
+
}
|
|
10340
|
+
};
|
|
10341
|
+
}
|
|
10342
|
+
function parseGdsLayout(bytes, fileName) {
|
|
10343
|
+
const shapes = [];
|
|
10344
|
+
const labels = [];
|
|
10345
|
+
const references = [];
|
|
10346
|
+
const cells = [];
|
|
10347
|
+
const layers = /* @__PURE__ */ new Map();
|
|
10348
|
+
const recordCounts = /* @__PURE__ */ new Map();
|
|
10349
|
+
const warnings = [];
|
|
10350
|
+
let libraryName = "";
|
|
10351
|
+
let version = "";
|
|
10352
|
+
let unit = "";
|
|
10353
|
+
let offset = 0;
|
|
10354
|
+
let current = {};
|
|
10355
|
+
let currentKind = "";
|
|
10356
|
+
while (offset + 4 <= bytes.length) {
|
|
10357
|
+
const length = readUInt16(bytes, offset);
|
|
10358
|
+
const recordType = bytes[offset + 2];
|
|
10359
|
+
const data = bytes.slice(offset + 4, offset + length);
|
|
10360
|
+
const name = gdsRecordNames[recordType] || `0x${recordType.toString(16).padStart(2, "0")}`;
|
|
10361
|
+
recordCounts.set(name, (recordCounts.get(name) || 0) + 1);
|
|
10362
|
+
if (length < 4 || offset + length > bytes.length) {
|
|
10363
|
+
warnings.push(`GDS \u8BB0\u5F55\u5728 ${offset} \u5B57\u8282\u5904\u957F\u5EA6\u5F02\u5E38\uFF0C\u5DF2\u505C\u6B62\u89E3\u6790\u3002`);
|
|
10364
|
+
break;
|
|
10365
|
+
}
|
|
10366
|
+
if (recordType === 0 && data.length >= 2) {
|
|
10367
|
+
version = String(readUInt16(data, 0));
|
|
10368
|
+
} else if (recordType === 2) {
|
|
10369
|
+
libraryName = readGdsString(data);
|
|
10370
|
+
} else if (recordType === 3 && data.length >= 16) {
|
|
10371
|
+
unit = `${formatGdsReal(data, 0)} / ${formatGdsReal(data, 8)}`;
|
|
10372
|
+
} else if (recordType === 6) {
|
|
10373
|
+
cells.push(readGdsString(data));
|
|
10374
|
+
} else if (recordType === 8 || recordType === 9 || recordType === 45) {
|
|
10375
|
+
currentKind = recordType === 9 ? "path" : recordType === 45 ? "box" : "boundary";
|
|
10376
|
+
current = { kind: currentKind, layer: "0", datatype: "0", points: [] };
|
|
10377
|
+
} else if (recordType === 12) {
|
|
10378
|
+
currentKind = "text";
|
|
10379
|
+
current = { layer: "0", text: "", x: 0, y: 0 };
|
|
10380
|
+
} else if (recordType === 10 || recordType === 11) {
|
|
10381
|
+
currentKind = "reference";
|
|
10382
|
+
current = { cell: "", x: 0, y: 0 };
|
|
10383
|
+
} else if (recordType === 13 && data.length >= 2) {
|
|
10384
|
+
current.layer = String(readInt16(data, 0));
|
|
10385
|
+
} else if ((recordType === 14 || recordType === 22) && data.length >= 2) {
|
|
10386
|
+
current.datatype = String(readInt16(data, 0));
|
|
10387
|
+
} else if (recordType === 15 && data.length >= 4) {
|
|
10388
|
+
current.width = Math.abs(readInt32(data, 0));
|
|
10389
|
+
} else if (recordType === 16) {
|
|
10390
|
+
const points = readGdsPoints(data);
|
|
10391
|
+
if (currentKind === "text" && points[0]) {
|
|
10392
|
+
current.x = points[0][0];
|
|
10393
|
+
current.y = points[0][1];
|
|
10394
|
+
} else if (currentKind === "reference" && points[0]) {
|
|
10395
|
+
current.x = points[0][0];
|
|
10396
|
+
current.y = points[0][1];
|
|
10397
|
+
} else {
|
|
10398
|
+
current.points = points;
|
|
10399
|
+
}
|
|
10400
|
+
} else if (recordType === 18) {
|
|
10401
|
+
current.cell = readGdsString(data);
|
|
10402
|
+
} else if (recordType === 25) {
|
|
10403
|
+
current.text = readGdsString(data);
|
|
10404
|
+
} else if (recordType === 17) {
|
|
10405
|
+
if ((currentKind === "boundary" || currentKind === "path" || currentKind === "box") && current.points && current.points.length > 1) {
|
|
10406
|
+
const shape = {
|
|
10407
|
+
kind: current.kind || "boundary",
|
|
10408
|
+
layer: String(current.layer || "0"),
|
|
10409
|
+
datatype: current.datatype,
|
|
10410
|
+
points: current.points,
|
|
10411
|
+
width: current.width
|
|
10412
|
+
};
|
|
10413
|
+
shapes.push(shape);
|
|
10414
|
+
layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
|
|
10415
|
+
} else if (currentKind === "text" && current.text) {
|
|
10416
|
+
const label = {
|
|
10417
|
+
layer: String(current.layer || "0"),
|
|
10418
|
+
text: String(current.text),
|
|
10419
|
+
x: Number(current.x || 0),
|
|
10420
|
+
y: Number(current.y || 0)
|
|
10421
|
+
};
|
|
10422
|
+
labels.push(label);
|
|
10423
|
+
layers.set(label.layer, (layers.get(label.layer) || 0) + 1);
|
|
10424
|
+
} else if (currentKind === "reference" && current.cell) {
|
|
10425
|
+
references.push({ cell: String(current.cell), x: Number(current.x || 0), y: Number(current.y || 0) });
|
|
10426
|
+
}
|
|
10427
|
+
current = {};
|
|
10428
|
+
currentKind = "";
|
|
10429
|
+
}
|
|
10430
|
+
offset += length;
|
|
10431
|
+
}
|
|
10432
|
+
return {
|
|
10433
|
+
format: "GDSII",
|
|
10434
|
+
fileName,
|
|
10435
|
+
libraryName,
|
|
10436
|
+
version: version ? `Stream ${version}` : void 0,
|
|
10437
|
+
unit,
|
|
10438
|
+
cells,
|
|
10439
|
+
shapes,
|
|
10440
|
+
labels,
|
|
10441
|
+
references,
|
|
10442
|
+
layers,
|
|
10443
|
+
metadata: [
|
|
10444
|
+
["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
|
|
10445
|
+
["\u8BB0\u5F55", sumCounts(recordCounts)],
|
|
10446
|
+
["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size]
|
|
10447
|
+
],
|
|
10448
|
+
notes: [
|
|
10449
|
+
`\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`
|
|
10450
|
+
],
|
|
10451
|
+
warnings
|
|
10452
|
+
};
|
|
10453
|
+
}
|
|
10454
|
+
function parseOasisLayout(bytes, fileName) {
|
|
10455
|
+
const chunks = extractOasisCblocks(bytes);
|
|
10456
|
+
const expanded = chunks.flatMap((chunk) => [...chunk.bytes]);
|
|
10457
|
+
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_"));
|
|
10458
|
+
const propertyNames = uniqueHints(chunks.flatMap((chunk) => extractAsciiRuns(chunk.bytes)).filter((item) => item.startsWith("S_")));
|
|
10459
|
+
const recordCounts = scanOasisRecordCounts(bytes);
|
|
10460
|
+
const expandedCounts = scanOasisRecordCounts(new Uint8Array(expanded));
|
|
10461
|
+
const layers = /* @__PURE__ */ new Map();
|
|
10462
|
+
const shapes = [];
|
|
10463
|
+
const labels = [];
|
|
10464
|
+
const references = [];
|
|
10465
|
+
for (const name of cellNames) {
|
|
10466
|
+
references.push({ cell: name, x: 0, y: 0 });
|
|
10467
|
+
}
|
|
10468
|
+
const pseudo = createOasisStructureShapes(cellNames, chunks.length || recordCounts.size || 1);
|
|
10469
|
+
for (const shape of pseudo) {
|
|
10470
|
+
shapes.push(shape);
|
|
10471
|
+
layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
|
|
10472
|
+
}
|
|
10473
|
+
for (let index = 0; index < cellNames.length; index++) {
|
|
10474
|
+
labels.push({ layer: "cell", text: cellNames[index], x: 12, y: -(18 + index * 18) });
|
|
10475
|
+
}
|
|
10476
|
+
if (cellNames.length > 0) {
|
|
10477
|
+
layers.set("cell", (layers.get("cell") || 0) + cellNames.length);
|
|
10478
|
+
}
|
|
10479
|
+
const version = readOasisVersion(bytes);
|
|
10480
|
+
const cblockText = chunks.length ? `${chunks.length} \u4E2A\uFF0C\u5C55\u5F00 ${formatBytes3(chunks.reduce((sum, chunk) => sum + chunk.bytes.byteLength, 0))}` : "\u672A\u53D1\u73B0";
|
|
10481
|
+
const notes = [
|
|
10482
|
+
"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"
|
|
10483
|
+
];
|
|
10484
|
+
if (propertyNames.length > 0) {
|
|
10485
|
+
notes.push(`\u8BC6\u522B\u5230\u5C5E\u6027\uFF1A${propertyNames.slice(0, 5).join("\u3001")}`);
|
|
10486
|
+
}
|
|
10487
|
+
return {
|
|
10488
|
+
format: "OASIS",
|
|
10489
|
+
fileName,
|
|
10490
|
+
version,
|
|
10491
|
+
cells: cellNames,
|
|
10492
|
+
shapes,
|
|
10493
|
+
labels,
|
|
10494
|
+
references: [],
|
|
10495
|
+
layers,
|
|
10496
|
+
metadata: [
|
|
10497
|
+
["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
|
|
10498
|
+
["CBLOCK", cblockText],
|
|
10499
|
+
["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size + expandedCounts.size],
|
|
10500
|
+
["\u53EF\u8BFB\u7247\u6BB5", cellNames.length + propertyNames.length]
|
|
10501
|
+
],
|
|
10502
|
+
notes,
|
|
10503
|
+
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"] : []
|
|
10504
|
+
};
|
|
10505
|
+
}
|
|
10506
|
+
function computeLayoutBounds(shapes, labels, references) {
|
|
10507
|
+
const xs = [];
|
|
10508
|
+
const ys = [];
|
|
10509
|
+
for (const shape of shapes) {
|
|
10510
|
+
for (const [x, y] of shape.points) {
|
|
10511
|
+
xs.push(x);
|
|
10512
|
+
ys.push(-y);
|
|
10513
|
+
}
|
|
10514
|
+
}
|
|
10515
|
+
for (const label of labels) {
|
|
10516
|
+
xs.push(label.x, label.x + label.text.length * 12);
|
|
10517
|
+
ys.push(-label.y, -label.y - 16);
|
|
10518
|
+
}
|
|
10519
|
+
for (const reference of references) {
|
|
10520
|
+
xs.push(reference.x);
|
|
10521
|
+
ys.push(-reference.y);
|
|
10522
|
+
}
|
|
10523
|
+
const minX = Math.min(...xs, 0);
|
|
10524
|
+
const maxX = Math.max(...xs, 100);
|
|
10525
|
+
const minY = Math.min(...ys, 0);
|
|
10526
|
+
const maxY = Math.max(...ys, 100);
|
|
10527
|
+
const width = Math.max(1, maxX - minX);
|
|
10528
|
+
const height = Math.max(1, maxY - minY);
|
|
10529
|
+
const padding = Math.max(width, height) * 0.06;
|
|
10530
|
+
return {
|
|
10531
|
+
minX: minX - padding,
|
|
10532
|
+
minY: minY - padding,
|
|
10533
|
+
width: width + padding * 2,
|
|
10534
|
+
height: height + padding * 2,
|
|
10535
|
+
stroke: Math.max(width, height) / 900
|
|
10536
|
+
};
|
|
10537
|
+
}
|
|
10538
|
+
function createLayoutLayerControls(svg, layers, counts) {
|
|
10539
|
+
const controls = document.createElement("div");
|
|
10540
|
+
controls.className = "ofv-cad-layers ofv-layout-layers";
|
|
10541
|
+
const title = document.createElement("strong");
|
|
10542
|
+
title.textContent = `\u56FE\u5C42 ${layers.length}`;
|
|
10543
|
+
controls.append(title);
|
|
10544
|
+
for (const layer of layers) {
|
|
10545
|
+
const label = document.createElement("label");
|
|
10546
|
+
const checkbox = document.createElement("input");
|
|
10547
|
+
checkbox.type = "checkbox";
|
|
10548
|
+
checkbox.checked = true;
|
|
10549
|
+
checkbox.addEventListener("change", () => {
|
|
10550
|
+
for (const element of svg.querySelectorAll(`[data-layer="${escapeCssAttribute(layer)}"]`)) {
|
|
10551
|
+
element.style.display = checkbox.checked ? "" : "none";
|
|
10552
|
+
}
|
|
10553
|
+
});
|
|
10554
|
+
const name = document.createElement("span");
|
|
10555
|
+
name.textContent = `${layer} (${counts.get(layer) || 0})`;
|
|
10556
|
+
label.append(checkbox, name);
|
|
10557
|
+
controls.append(label);
|
|
10558
|
+
}
|
|
10559
|
+
return controls;
|
|
10560
|
+
}
|
|
10561
|
+
function createLayoutCellList(cells, references) {
|
|
10562
|
+
const details = document.createElement("details");
|
|
10563
|
+
details.className = "ofv-details ofv-layout-cells";
|
|
10564
|
+
details.open = true;
|
|
10565
|
+
const summary = document.createElement("summary");
|
|
10566
|
+
summary.textContent = `Cell \u7ED3\u6784 ${cells.length}`;
|
|
10567
|
+
const list = document.createElement("ul");
|
|
10568
|
+
const refCounts = countBy(references.map((reference) => reference.cell));
|
|
10569
|
+
for (const cell of cells.slice(0, 120)) {
|
|
10570
|
+
const item = document.createElement("li");
|
|
10571
|
+
const count = refCounts.get(cell) || 0;
|
|
10572
|
+
item.textContent = count > 0 ? `${cell} \xB7 \u5F15\u7528 ${count}` : cell;
|
|
10573
|
+
list.append(item);
|
|
10574
|
+
}
|
|
10575
|
+
details.append(summary, list);
|
|
10576
|
+
return details;
|
|
10577
|
+
}
|
|
10578
|
+
function readUInt16(bytes, offset) {
|
|
10579
|
+
return bytes[offset] << 8 | bytes[offset + 1];
|
|
10580
|
+
}
|
|
10581
|
+
function readInt16(bytes, offset) {
|
|
10582
|
+
const value = readUInt16(bytes, offset);
|
|
10583
|
+
return value & 32768 ? value - 65536 : value;
|
|
10584
|
+
}
|
|
10585
|
+
function readInt32(bytes, offset) {
|
|
10586
|
+
const value = bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
|
|
10587
|
+
return value | 0;
|
|
10588
|
+
}
|
|
10589
|
+
function readGdsString(bytes) {
|
|
10590
|
+
return new TextDecoder("ascii").decode(bytes).replace(/\0+$/g, "").trim();
|
|
10591
|
+
}
|
|
10592
|
+
function readGdsPoints(bytes) {
|
|
10593
|
+
const points = [];
|
|
10594
|
+
for (let offset = 0; offset + 7 < bytes.length; offset += 8) {
|
|
10595
|
+
points.push([readInt32(bytes, offset), readInt32(bytes, offset + 4)]);
|
|
10596
|
+
}
|
|
10597
|
+
return points;
|
|
10598
|
+
}
|
|
10599
|
+
function formatGdsReal(bytes, offset) {
|
|
10600
|
+
const value = readGdsReal(bytes, offset);
|
|
10601
|
+
if (!Number.isFinite(value) || value === 0) {
|
|
10602
|
+
return "0";
|
|
10603
|
+
}
|
|
10604
|
+
if (Math.abs(value) < 1e-3 || Math.abs(value) >= 1e4) {
|
|
10605
|
+
return value.toExponential(4);
|
|
10606
|
+
}
|
|
10607
|
+
return String(Number(value.toPrecision(6)));
|
|
10608
|
+
}
|
|
10609
|
+
function readGdsReal(bytes, offset) {
|
|
10610
|
+
const first = bytes[offset];
|
|
10611
|
+
if (!first) {
|
|
10612
|
+
return 0;
|
|
10613
|
+
}
|
|
10614
|
+
const sign = first & 128 ? -1 : 1;
|
|
10615
|
+
const exponent = (first & 127) - 64;
|
|
10616
|
+
let mantissa = 0;
|
|
10617
|
+
for (let index = 1; index < 8; index++) {
|
|
10618
|
+
mantissa = mantissa * 256 + bytes[offset + index];
|
|
10619
|
+
}
|
|
10620
|
+
return sign * (mantissa / Math.pow(2, 56)) * Math.pow(16, exponent);
|
|
10621
|
+
}
|
|
10622
|
+
function sumCounts(counts) {
|
|
10623
|
+
return [...counts.values()].reduce((sum, count) => sum + count, 0);
|
|
10624
|
+
}
|
|
10625
|
+
function extractOasisCblocks(bytes) {
|
|
10626
|
+
const chunks = [];
|
|
10627
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10628
|
+
const limit = Math.min(bytes.length, 25e4);
|
|
10629
|
+
for (let offset = 0; offset < limit; offset++) {
|
|
10630
|
+
try {
|
|
10631
|
+
const inflated = pako3.inflateRaw(bytes.slice(offset));
|
|
10632
|
+
if (inflated.byteLength < 4) {
|
|
10633
|
+
continue;
|
|
10634
|
+
}
|
|
10635
|
+
const ascii = extractAsciiRuns(inflated);
|
|
10636
|
+
const hasLayoutSignal = ascii.some((item) => item.startsWith("S_") || /TOP|CELL|DIE|SIZE/i.test(item));
|
|
10637
|
+
const hasRecordSignal = inflated.some((byte) => byte >= 13 && byte <= 34);
|
|
10638
|
+
if (!hasLayoutSignal && !hasRecordSignal) {
|
|
10639
|
+
continue;
|
|
10640
|
+
}
|
|
10641
|
+
const signature = `${inflated.byteLength}:${Array.from(inflated.slice(0, 12)).join(",")}`;
|
|
10642
|
+
if (seen.has(signature)) {
|
|
10643
|
+
continue;
|
|
10644
|
+
}
|
|
10645
|
+
seen.add(signature);
|
|
10646
|
+
chunks.push({ offset, bytes: inflated });
|
|
10647
|
+
if (chunks.length >= 12) {
|
|
10648
|
+
break;
|
|
10649
|
+
}
|
|
10650
|
+
} catch {
|
|
10651
|
+
}
|
|
10652
|
+
}
|
|
10653
|
+
return chunks;
|
|
10654
|
+
}
|
|
10655
|
+
function scanOasisRecordCounts(bytes) {
|
|
10656
|
+
const counts = /* @__PURE__ */ new Map();
|
|
10657
|
+
for (const byte of bytes.slice(0, Math.min(bytes.length, 12e3))) {
|
|
10658
|
+
const name = oasisRecordNames[byte];
|
|
10659
|
+
if (name) {
|
|
10660
|
+
counts.set(name, (counts.get(name) || 0) + 1);
|
|
10661
|
+
}
|
|
10662
|
+
}
|
|
10663
|
+
return counts;
|
|
10664
|
+
}
|
|
10665
|
+
function readOasisVersion(bytes) {
|
|
10666
|
+
const magic = "%SEMI-OASIS\r\n";
|
|
10667
|
+
const header = new TextDecoder("ascii").decode(bytes.slice(0, Math.min(bytes.length, 48)));
|
|
10668
|
+
if (!header.startsWith(magic)) {
|
|
10669
|
+
return void 0;
|
|
10670
|
+
}
|
|
10671
|
+
const start = magic.length;
|
|
10672
|
+
if (bytes[start] !== 1) {
|
|
10673
|
+
return "OASIS";
|
|
10674
|
+
}
|
|
10675
|
+
const length = bytes[start + 1];
|
|
10676
|
+
const version = new TextDecoder("ascii").decode(bytes.slice(start + 2, start + 2 + length));
|
|
10677
|
+
return version ? `OASIS ${version}` : "OASIS";
|
|
10678
|
+
}
|
|
10679
|
+
function createOasisStructureShapes(cellNames, fallbackCount) {
|
|
10680
|
+
const rows = Math.max(1, cellNames.length || fallbackCount);
|
|
10681
|
+
const shapes = [];
|
|
10682
|
+
for (let index = 0; index < rows; index++) {
|
|
10683
|
+
const top = -(index * 18);
|
|
10684
|
+
const height = 12;
|
|
10685
|
+
const width = 88 + Math.min((cellNames[index]?.length || 5) * 4, 90);
|
|
10686
|
+
shapes.push({
|
|
10687
|
+
kind: "box",
|
|
10688
|
+
layer: "cell",
|
|
10689
|
+
points: [
|
|
10690
|
+
[0, top],
|
|
10691
|
+
[width, top],
|
|
10692
|
+
[width, top - height],
|
|
10693
|
+
[0, top - height],
|
|
10694
|
+
[0, top]
|
|
10695
|
+
]
|
|
10696
|
+
});
|
|
10697
|
+
}
|
|
10698
|
+
return shapes;
|
|
10699
|
+
}
|
|
9977
10700
|
function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
|
|
9978
10701
|
const bytes = new Uint8Array(arrayBuffer);
|
|
9979
10702
|
const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);
|