@open-file-viewer/core 0.1.4 → 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 +1257 -224
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1257 -224
- package/dist/index.js.map +1 -1
- package/dist/style.css +45 -33
- package/package.json +2 -2
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);
|
|
@@ -3275,6 +3318,95 @@ function readAiffExtended(bytes, offset) {
|
|
|
3275
3318
|
return mantissa * Math.pow(2, exponent - 16383 - 63);
|
|
3276
3319
|
}
|
|
3277
3320
|
|
|
3321
|
+
// src/plugins/utils.ts
|
|
3322
|
+
async function readArrayBuffer(file) {
|
|
3323
|
+
if (file.source instanceof ArrayBuffer) {
|
|
3324
|
+
return file.source;
|
|
3325
|
+
}
|
|
3326
|
+
if (file.blob) {
|
|
3327
|
+
return file.blob.arrayBuffer();
|
|
3328
|
+
}
|
|
3329
|
+
if (typeof file.source === "string") {
|
|
3330
|
+
const response = await fetch(file.source);
|
|
3331
|
+
if (!response.ok) {
|
|
3332
|
+
throw new Error(`Failed to fetch file: ${response.status}`);
|
|
3333
|
+
}
|
|
3334
|
+
return response.arrayBuffer();
|
|
3335
|
+
}
|
|
3336
|
+
throw new Error("Unsupported file source.");
|
|
3337
|
+
}
|
|
3338
|
+
async function readTextFile(file) {
|
|
3339
|
+
const decode = (buffer) => decodeTextBuffer(buffer);
|
|
3340
|
+
if (typeof file.source === "string") {
|
|
3341
|
+
const response = await fetch(file.source);
|
|
3342
|
+
if (!response.ok) {
|
|
3343
|
+
throw new Error(`Failed to fetch text file: ${response.status}`);
|
|
3344
|
+
}
|
|
3345
|
+
return decode(await response.arrayBuffer());
|
|
3346
|
+
}
|
|
3347
|
+
if (file.blob) {
|
|
3348
|
+
return decode(await file.blob.arrayBuffer());
|
|
3349
|
+
}
|
|
3350
|
+
if (file.source instanceof ArrayBuffer) {
|
|
3351
|
+
return decode(file.source);
|
|
3352
|
+
}
|
|
3353
|
+
return String(file.source);
|
|
3354
|
+
}
|
|
3355
|
+
function createPanel(className = "") {
|
|
3356
|
+
const panel = document.createElement("div");
|
|
3357
|
+
panel.className = `ofv-panel ${className}`.trim();
|
|
3358
|
+
return panel;
|
|
3359
|
+
}
|
|
3360
|
+
function createSection(title) {
|
|
3361
|
+
const section = document.createElement("section");
|
|
3362
|
+
section.className = "ofv-section";
|
|
3363
|
+
const heading = document.createElement("h3");
|
|
3364
|
+
heading.textContent = title;
|
|
3365
|
+
section.append(heading);
|
|
3366
|
+
return section;
|
|
3367
|
+
}
|
|
3368
|
+
function appendMeta(parent, label, value) {
|
|
3369
|
+
const row = document.createElement("div");
|
|
3370
|
+
row.className = "ofv-meta-row";
|
|
3371
|
+
const key = document.createElement("span");
|
|
3372
|
+
key.textContent = label;
|
|
3373
|
+
const content = document.createElement("strong");
|
|
3374
|
+
content.textContent = String(value);
|
|
3375
|
+
row.append(key, content);
|
|
3376
|
+
parent.append(row);
|
|
3377
|
+
}
|
|
3378
|
+
function decodeTextBuffer(buffer) {
|
|
3379
|
+
return decodeTextBytes(new Uint8Array(buffer));
|
|
3380
|
+
}
|
|
3381
|
+
function decodeTextBytes(bytes) {
|
|
3382
|
+
if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
|
|
3383
|
+
return new TextDecoder("utf-8").decode(bytes.subarray(3));
|
|
3384
|
+
}
|
|
3385
|
+
if (bytes.length >= 2) {
|
|
3386
|
+
if (bytes[0] === 255 && bytes[1] === 254) {
|
|
3387
|
+
return new TextDecoder("utf-16le").decode(bytes.subarray(2));
|
|
3388
|
+
}
|
|
3389
|
+
if (bytes[0] === 254 && bytes[1] === 255) {
|
|
3390
|
+
return new TextDecoder("utf-16be").decode(bytes.subarray(2));
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
try {
|
|
3394
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
3395
|
+
} catch {
|
|
3396
|
+
return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
function decodeWithFallback(bytes, encoding) {
|
|
3400
|
+
try {
|
|
3401
|
+
return new TextDecoder(encoding).decode(bytes);
|
|
3402
|
+
} catch {
|
|
3403
|
+
return void 0;
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
function resolveFormat(file, mimeMap) {
|
|
3407
|
+
return file.extension || mimeMap[file.mimeType] || "";
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3278
3410
|
// src/plugins/text.ts
|
|
3279
3411
|
var langMap = {
|
|
3280
3412
|
js: "javascript",
|
|
@@ -3931,17 +4063,49 @@ async function readText(source) {
|
|
|
3931
4063
|
if (!response.ok) {
|
|
3932
4064
|
throw new Error(`Failed to fetch text file: ${response.status}`);
|
|
3933
4065
|
}
|
|
3934
|
-
return response.
|
|
4066
|
+
return decodeTextBuffer(await response.arrayBuffer());
|
|
3935
4067
|
}
|
|
3936
4068
|
if (source instanceof Blob) {
|
|
3937
|
-
return source.
|
|
4069
|
+
return decodeTextBuffer(await source.arrayBuffer());
|
|
3938
4070
|
}
|
|
3939
4071
|
if (source instanceof ArrayBuffer) {
|
|
3940
|
-
return
|
|
4072
|
+
return decodeTextBuffer(source);
|
|
3941
4073
|
}
|
|
3942
4074
|
return String(source);
|
|
3943
4075
|
}
|
|
3944
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
|
+
|
|
3945
4109
|
// src/plugins/pdf.ts
|
|
3946
4110
|
function multiplyMatrices(m1, m2) {
|
|
3947
4111
|
return [
|
|
@@ -3983,7 +4147,11 @@ function pdfPlugin(options = {}) {
|
|
|
3983
4147
|
const doc = await documentTask.promise.catch((error) => {
|
|
3984
4148
|
viewer.remove();
|
|
3985
4149
|
ctx.viewport.classList.add("ofv-center");
|
|
3986
|
-
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));
|
|
3987
4155
|
ctx.viewport.append(fallback);
|
|
3988
4156
|
return void 0;
|
|
3989
4157
|
});
|
|
@@ -4273,9 +4441,6 @@ function normalizePdfError(error) {
|
|
|
4273
4441
|
const message = error instanceof Error ? error.message : String(error || "");
|
|
4274
4442
|
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
4275
4443
|
const lower = `${name} ${message}`.toLowerCase();
|
|
4276
|
-
if (lower.includes("password")) {
|
|
4277
|
-
return "\u8BE5 PDF \u53D7\u5BC6\u7801\u4FDD\u62A4\uFF0C\u5F53\u524D\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u5185\u76F4\u63A5\u9884\u89C8\u3002";
|
|
4278
|
-
}
|
|
4279
4444
|
if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
|
|
4280
4445
|
return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
|
|
4281
4446
|
}
|
|
@@ -4294,68 +4459,6 @@ function configurePdfWorker(pdf, workerSrc) {
|
|
|
4294
4459
|
// src/plugins/epub.ts
|
|
4295
4460
|
import JSZip from "jszip";
|
|
4296
4461
|
import DOMPurify from "dompurify";
|
|
4297
|
-
|
|
4298
|
-
// src/plugins/utils.ts
|
|
4299
|
-
async function readArrayBuffer(file) {
|
|
4300
|
-
if (file.source instanceof ArrayBuffer) {
|
|
4301
|
-
return file.source;
|
|
4302
|
-
}
|
|
4303
|
-
if (file.blob) {
|
|
4304
|
-
return file.blob.arrayBuffer();
|
|
4305
|
-
}
|
|
4306
|
-
if (typeof file.source === "string") {
|
|
4307
|
-
const response = await fetch(file.source);
|
|
4308
|
-
if (!response.ok) {
|
|
4309
|
-
throw new Error(`Failed to fetch file: ${response.status}`);
|
|
4310
|
-
}
|
|
4311
|
-
return response.arrayBuffer();
|
|
4312
|
-
}
|
|
4313
|
-
throw new Error("Unsupported file source.");
|
|
4314
|
-
}
|
|
4315
|
-
async function readTextFile(file) {
|
|
4316
|
-
if (typeof file.source === "string") {
|
|
4317
|
-
const response = await fetch(file.source);
|
|
4318
|
-
if (!response.ok) {
|
|
4319
|
-
throw new Error(`Failed to fetch text file: ${response.status}`);
|
|
4320
|
-
}
|
|
4321
|
-
return response.text();
|
|
4322
|
-
}
|
|
4323
|
-
if (file.blob) {
|
|
4324
|
-
return file.blob.text();
|
|
4325
|
-
}
|
|
4326
|
-
if (file.source instanceof ArrayBuffer) {
|
|
4327
|
-
return new TextDecoder().decode(file.source);
|
|
4328
|
-
}
|
|
4329
|
-
return String(file.source);
|
|
4330
|
-
}
|
|
4331
|
-
function createPanel(className = "") {
|
|
4332
|
-
const panel = document.createElement("div");
|
|
4333
|
-
panel.className = `ofv-panel ${className}`.trim();
|
|
4334
|
-
return panel;
|
|
4335
|
-
}
|
|
4336
|
-
function createSection(title) {
|
|
4337
|
-
const section = document.createElement("section");
|
|
4338
|
-
section.className = "ofv-section";
|
|
4339
|
-
const heading = document.createElement("h3");
|
|
4340
|
-
heading.textContent = title;
|
|
4341
|
-
section.append(heading);
|
|
4342
|
-
return section;
|
|
4343
|
-
}
|
|
4344
|
-
function appendMeta(parent, label, value) {
|
|
4345
|
-
const row = document.createElement("div");
|
|
4346
|
-
row.className = "ofv-meta-row";
|
|
4347
|
-
const key = document.createElement("span");
|
|
4348
|
-
key.textContent = label;
|
|
4349
|
-
const content = document.createElement("strong");
|
|
4350
|
-
content.textContent = String(value);
|
|
4351
|
-
row.append(key, content);
|
|
4352
|
-
parent.append(row);
|
|
4353
|
-
}
|
|
4354
|
-
function resolveFormat(file, mimeMap) {
|
|
4355
|
-
return file.extension || mimeMap[file.mimeType] || "";
|
|
4356
|
-
}
|
|
4357
|
-
|
|
4358
|
-
// src/plugins/epub.ts
|
|
4359
4462
|
var epubMimeTypes = /* @__PURE__ */ new Set(["application/epub+zip", "application/x-epub+zip"]);
|
|
4360
4463
|
function epubPlugin() {
|
|
4361
4464
|
return {
|
|
@@ -4975,8 +5078,15 @@ function officePlugin() {
|
|
|
4975
5078
|
ctx.viewport.append(panel);
|
|
4976
5079
|
const extension = resolveFormat(ctx.file, officeMimeFormatMap);
|
|
4977
5080
|
const arrayBuffer = await readArrayBuffer(ctx.file);
|
|
5081
|
+
const packageFormat = shouldSniffPackagedOffice(extension) ? await detectPackagedOfficeFormat(arrayBuffer) : void 0;
|
|
4978
5082
|
let disposeDocxFit;
|
|
4979
|
-
if (fileIsDocx(extension)) {
|
|
5083
|
+
if (packageFormat === "docx" && !fileIsDocx(extension)) {
|
|
5084
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
5085
|
+
} else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
|
|
5086
|
+
await renderSheet(panel, arrayBuffer, "xlsx");
|
|
5087
|
+
} else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
|
|
5088
|
+
await renderPptx(panel, arrayBuffer);
|
|
5089
|
+
} else if (fileIsDocx(extension)) {
|
|
4980
5090
|
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
4981
5091
|
} else if (extension === "rtf") {
|
|
4982
5092
|
renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
|
|
@@ -5012,12 +5122,32 @@ function officePlugin() {
|
|
|
5012
5122
|
function fileIsDocx(extension) {
|
|
5013
5123
|
return extension === "docx" || extension === "docm" || extension === "dotx" || extension === "dotm";
|
|
5014
5124
|
}
|
|
5125
|
+
function shouldSniffPackagedOffice(extension) {
|
|
5126
|
+
return isLegacyOfficeBinary(extension) || packagedOfficeCandidates.has(extension) || extension === "";
|
|
5127
|
+
}
|
|
5128
|
+
async function detectPackagedOfficeFormat(arrayBuffer) {
|
|
5129
|
+
try {
|
|
5130
|
+
const zip = await JSZip3.loadAsync(arrayBuffer);
|
|
5131
|
+
const entries = Object.values(zip.files).filter((entry) => !entry.dir);
|
|
5132
|
+
const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
|
|
5133
|
+
if (hasEntry("word/document.xml")) {
|
|
5134
|
+
return "docx";
|
|
5135
|
+
}
|
|
5136
|
+
if (hasEntry("xl/workbook.xml")) {
|
|
5137
|
+
return "xlsx";
|
|
5138
|
+
}
|
|
5139
|
+
if (hasEntry("ppt/presentation.xml")) {
|
|
5140
|
+
return "pptx";
|
|
5141
|
+
}
|
|
5142
|
+
} catch {
|
|
5143
|
+
return void 0;
|
|
5144
|
+
}
|
|
5145
|
+
return void 0;
|
|
5146
|
+
}
|
|
5015
5147
|
async function renderDocx(panel, arrayBuffer) {
|
|
5016
|
-
const section = createSection("Word \u6587\u6863");
|
|
5017
5148
|
const content = document.createElement("div");
|
|
5018
5149
|
content.className = "ofv-docx-document";
|
|
5019
|
-
|
|
5020
|
-
panel.append(section);
|
|
5150
|
+
panel.append(content);
|
|
5021
5151
|
try {
|
|
5022
5152
|
const docxPreview = await import("docx-preview");
|
|
5023
5153
|
await docxPreview.renderAsync(arrayBuffer, content, content, {
|
|
@@ -5238,7 +5368,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
|
|
|
5238
5368
|
const xlsx = await import("xlsx");
|
|
5239
5369
|
let workbook;
|
|
5240
5370
|
try {
|
|
5241
|
-
workbook = xlsx.read(arrayBuffer, { type: "array" });
|
|
5371
|
+
workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), { type: "string", FS: extension === "tsv" ? " " : "," }) : xlsx.read(arrayBuffer, { type: "array" });
|
|
5242
5372
|
} catch (error) {
|
|
5243
5373
|
if (isLegacyOfficeBinary(extension)) {
|
|
5244
5374
|
renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
|
|
@@ -5340,6 +5470,10 @@ async function renderSheet(panel, arrayBuffer, extension) {
|
|
|
5340
5470
|
}
|
|
5341
5471
|
}
|
|
5342
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
|
+
}
|
|
5343
5477
|
const section = createSection("\u8868\u683C\u89E3\u6790\u5931\u8D25");
|
|
5344
5478
|
const title = document.createElement("p");
|
|
5345
5479
|
title.textContent = `.${extension || "sheet"} \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790\u4E3A\u53EF\u9884\u89C8\u8868\u683C\u3002`;
|
|
@@ -5350,6 +5484,17 @@ function renderSheetFallback(panel, extension, detail) {
|
|
|
5350
5484
|
section.append(title, meta, support);
|
|
5351
5485
|
panel.append(section);
|
|
5352
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
|
+
}
|
|
5353
5498
|
function renderFlatOds(panel, xml) {
|
|
5354
5499
|
const sheets = parseFlatOds(xml);
|
|
5355
5500
|
renderParsedSheets(panel, sheets, "FODS \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002");
|
|
@@ -5871,10 +6016,30 @@ async function renderPptx(panel, arrayBuffer) {
|
|
|
5871
6016
|
try {
|
|
5872
6017
|
const { PptxViewer } = await import("@aiden0z/pptx-renderer");
|
|
5873
6018
|
await PptxViewer.open(arrayBuffer, container);
|
|
6019
|
+
normalizePptxLayout(container);
|
|
5874
6020
|
} catch {
|
|
5875
6021
|
container.textContent = "PPTX \u6E32\u67D3\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u635F\u574F\u3002";
|
|
5876
6022
|
}
|
|
5877
6023
|
}
|
|
6024
|
+
function normalizePptxLayout(container) {
|
|
6025
|
+
const slideCanvases = findPptxSlideCanvases(container);
|
|
6026
|
+
for (const slide of slideCanvases) {
|
|
6027
|
+
slide.style.backgroundColor = "#FFFFFF";
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
function findPptxSlideCanvases(container) {
|
|
6031
|
+
const slideWrappers = Array.from(container.querySelectorAll("div[data-slide-index]"));
|
|
6032
|
+
const candidates = slideWrappers.flatMap(
|
|
6033
|
+
(wrapper) => Array.from(wrapper.querySelectorAll("div")).filter(isPptxSlideCanvas)
|
|
6034
|
+
);
|
|
6035
|
+
if (candidates.length > 0) {
|
|
6036
|
+
return Array.from(new Set(candidates));
|
|
6037
|
+
}
|
|
6038
|
+
return Array.from(container.querySelectorAll("div")).filter(isPptxSlideCanvas);
|
|
6039
|
+
}
|
|
6040
|
+
function isPptxSlideCanvas(element) {
|
|
6041
|
+
return element.style.position === "relative" && parseCssPixelValue(element.style.width) > 0 && parseCssPixelValue(element.style.height) > 0;
|
|
6042
|
+
}
|
|
5878
6043
|
async function renderOdp(panel, arrayBuffer) {
|
|
5879
6044
|
const zip = await JSZip3.loadAsync(arrayBuffer);
|
|
5880
6045
|
const content = zip.file("content.xml");
|
|
@@ -5902,34 +6067,28 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
|
5902
6067
|
const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
|
|
5903
6068
|
const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
|
|
5904
6069
|
if (hasEntry("word/document.xml")) {
|
|
5905
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Word \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 DOCX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5906
6070
|
await renderDocx(panel, arrayBuffer);
|
|
5907
6071
|
return true;
|
|
5908
6072
|
}
|
|
5909
6073
|
if (hasEntry("xl/workbook.xml")) {
|
|
5910
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Workbook \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 XLSX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5911
6074
|
await renderSheet(panel, arrayBuffer, extension);
|
|
5912
6075
|
return true;
|
|
5913
6076
|
}
|
|
5914
6077
|
if (hasEntry("ppt/presentation.xml")) {
|
|
5915
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 PPTX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5916
6078
|
await renderPptx(panel, arrayBuffer);
|
|
5917
6079
|
return true;
|
|
5918
6080
|
}
|
|
5919
6081
|
if (contentXml) {
|
|
5920
6082
|
const xml = await contentXml.async("text");
|
|
5921
6083
|
if (/<office:spreadsheet\b|<table:table\b/i.test(xml)) {
|
|
5922
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Spreadsheet \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODS \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5923
6084
|
renderParsedSheets(panel, parseFlatOds(xml), `${extension.toUpperCase()} \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002`);
|
|
5924
6085
|
return true;
|
|
5925
6086
|
}
|
|
5926
6087
|
if (/<office:presentation\b|<draw:page\b/i.test(xml)) {
|
|
5927
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODP \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5928
6088
|
renderOpenDocumentPresentation(panel, `${extension.toUpperCase()} \u6F14\u793A\u6587\u7A3F`, xml, await extractZipImages(zip, /^Pictures\//));
|
|
5929
6089
|
return true;
|
|
5930
6090
|
}
|
|
5931
6091
|
if (/<office:text\b|<text:p\b/i.test(xml)) {
|
|
5932
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Text \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODT \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5933
6092
|
renderOpenDocumentXml(panel, `${extension.toUpperCase()} \u6587\u6863`, xml);
|
|
5934
6093
|
return true;
|
|
5935
6094
|
}
|
|
@@ -5955,14 +6114,6 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
|
5955
6114
|
}
|
|
5956
6115
|
return false;
|
|
5957
6116
|
}
|
|
5958
|
-
function renderOfficePackageNotice(panel, extension, message) {
|
|
5959
|
-
const section = createSection("\u517C\u5BB9\u5305\u8BC6\u522B");
|
|
5960
|
-
const note = document.createElement("p");
|
|
5961
|
-
note.className = "ofv-office-package-note";
|
|
5962
|
-
note.textContent = `.${extension} ${message}`;
|
|
5963
|
-
section.append(note);
|
|
5964
|
-
panel.append(section);
|
|
5965
|
-
}
|
|
5966
6117
|
function renderOfficePackageStructure(panel, extension, entries, message, metadata) {
|
|
5967
6118
|
const section = createSection("Office \u5305\u7ED3\u6784\u9884\u89C8");
|
|
5968
6119
|
const note = document.createElement("p");
|
|
@@ -6610,7 +6761,7 @@ function rtfToText(rtf) {
|
|
|
6610
6761
|
return rtf.replace(/\\'[0-9a-fA-F]{2}/g, "").replace(/\\par[d]?/g, "\n").replace(/\\tab/g, " ").replace(/\\[a-zA-Z]+\d* ?/g, "").replace(/[{}]/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
6611
6762
|
}
|
|
6612
6763
|
async function readTextFromBuffer(arrayBuffer) {
|
|
6613
|
-
return
|
|
6764
|
+
return decodeTextBuffer(arrayBuffer);
|
|
6614
6765
|
}
|
|
6615
6766
|
function mimeTypeFromPath(path) {
|
|
6616
6767
|
const extension = path.split(".").pop()?.toLowerCase();
|
|
@@ -6652,7 +6803,7 @@ function ofdPlugin() {
|
|
|
6652
6803
|
try {
|
|
6653
6804
|
zip = await JSZip4.loadAsync(await readArrayBuffer(ctx.file));
|
|
6654
6805
|
} catch (error) {
|
|
6655
|
-
panel.append(
|
|
6806
|
+
panel.append(createOfdFailure(ctx.file, url, error));
|
|
6656
6807
|
return {
|
|
6657
6808
|
destroy() {
|
|
6658
6809
|
panel.remove();
|
|
@@ -6669,7 +6820,7 @@ function ofdPlugin() {
|
|
|
6669
6820
|
textFragments.push(...matches);
|
|
6670
6821
|
}
|
|
6671
6822
|
} catch (error) {
|
|
6672
|
-
panel.append(
|
|
6823
|
+
panel.append(createOfdFailure(ctx.file, url, error));
|
|
6673
6824
|
return {
|
|
6674
6825
|
destroy() {
|
|
6675
6826
|
panel.remove();
|
|
@@ -6677,36 +6828,72 @@ function ofdPlugin() {
|
|
|
6677
6828
|
}
|
|
6678
6829
|
};
|
|
6679
6830
|
}
|
|
6680
|
-
const
|
|
6681
|
-
const pages = await readOfdPages(entries,
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6831
|
+
const context = await readOfdContext(entries);
|
|
6832
|
+
const pages = await readOfdPages(entries, context);
|
|
6833
|
+
let zoom = 1;
|
|
6834
|
+
let rotation = 0;
|
|
6835
|
+
const applyZoom = () => {
|
|
6836
|
+
panel.style.setProperty("--ofv-ofd-zoom", formatOfdCssNumber(zoom));
|
|
6837
|
+
ctx.toolbar?.setZoom(zoom);
|
|
6838
|
+
};
|
|
6839
|
+
const applyRotation = () => {
|
|
6840
|
+
const normalizedRotation = (rotation % 360 + 360) % 360;
|
|
6841
|
+
panel.style.setProperty("--ofv-ofd-rotation", `${normalizedRotation}deg`);
|
|
6842
|
+
panel.classList.toggle("is-ofd-rotated-sideways", normalizedRotation === 90 || normalizedRotation === 270);
|
|
6843
|
+
};
|
|
6687
6844
|
if (pages.length > 0) {
|
|
6688
6845
|
const pagesWrap = document.createElement("div");
|
|
6689
6846
|
pagesWrap.className = "ofv-ofd-pages";
|
|
6690
6847
|
for (const page of pages) {
|
|
6691
6848
|
pagesWrap.append(renderOfdPage(page));
|
|
6692
6849
|
}
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
li.textContent = entry.name;
|
|
6704
|
-
ul.append(li);
|
|
6705
|
-
}
|
|
6706
|
-
list.append(ul);
|
|
6707
|
-
panel.append(section, list);
|
|
6850
|
+
panel.append(pagesWrap);
|
|
6851
|
+
applyZoom();
|
|
6852
|
+
applyRotation();
|
|
6853
|
+
}
|
|
6854
|
+
if (pages.length === 0) {
|
|
6855
|
+
const content = document.createElement("pre");
|
|
6856
|
+
content.className = "ofv-text-block";
|
|
6857
|
+
content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
|
|
6858
|
+
panel.append(content);
|
|
6859
|
+
}
|
|
6708
6860
|
return {
|
|
6861
|
+
canCommand(command) {
|
|
6862
|
+
return pages.length > 0 && (command === "zoom-in" || command === "zoom-out" || command === "zoom-reset" || command === "rotate-right" || command === "rotate-left");
|
|
6863
|
+
},
|
|
6864
|
+
command(command) {
|
|
6865
|
+
if (pages.length === 0) {
|
|
6866
|
+
return false;
|
|
6867
|
+
}
|
|
6868
|
+
if (command === "zoom-in") {
|
|
6869
|
+
zoom = Math.min(4, zoom + 0.15);
|
|
6870
|
+
applyZoom();
|
|
6871
|
+
return true;
|
|
6872
|
+
}
|
|
6873
|
+
if (command === "zoom-out") {
|
|
6874
|
+
zoom = Math.max(0.25, zoom - 0.15);
|
|
6875
|
+
applyZoom();
|
|
6876
|
+
return true;
|
|
6877
|
+
}
|
|
6878
|
+
if (command === "zoom-reset") {
|
|
6879
|
+
zoom = 1;
|
|
6880
|
+
applyZoom();
|
|
6881
|
+
return true;
|
|
6882
|
+
}
|
|
6883
|
+
if (command === "rotate-right") {
|
|
6884
|
+
rotation += 90;
|
|
6885
|
+
applyRotation();
|
|
6886
|
+
return true;
|
|
6887
|
+
}
|
|
6888
|
+
if (command === "rotate-left") {
|
|
6889
|
+
rotation -= 90;
|
|
6890
|
+
applyRotation();
|
|
6891
|
+
return true;
|
|
6892
|
+
}
|
|
6893
|
+
return false;
|
|
6894
|
+
},
|
|
6709
6895
|
destroy() {
|
|
6896
|
+
ctx.toolbar?.setZoom(void 0);
|
|
6710
6897
|
panel.remove();
|
|
6711
6898
|
revokeObjectUrl(url, isExternal);
|
|
6712
6899
|
}
|
|
@@ -6714,76 +6901,71 @@ function ofdPlugin() {
|
|
|
6714
6901
|
}
|
|
6715
6902
|
};
|
|
6716
6903
|
}
|
|
6717
|
-
function
|
|
6718
|
-
const summary = document.createElement("div");
|
|
6719
|
-
summary.className = "ofv-ofd-summary";
|
|
6720
|
-
const xmlEntries = entries.filter((entry) => entry.name.endsWith(".xml")).length;
|
|
6721
|
-
const textCount = pages.reduce((count, page) => count + page.texts.length, 0);
|
|
6722
|
-
const pathCount = pages.reduce((count, page) => count + page.paths.length, 0);
|
|
6723
|
-
const lineCount = pages.reduce((count, page) => count + page.lines.length, 0);
|
|
6724
|
-
const imageCount = pages.reduce((count, page) => count + page.images.length, 0);
|
|
6725
|
-
const textLength = pages.reduce((count, page) => count + page.texts.reduce((inner, item) => inner + item.text.length, 0), 0);
|
|
6726
|
-
appendOfdSummary(summary, "\u6587\u4EF6", String(entries.length));
|
|
6727
|
-
appendOfdSummary(summary, "XML", String(xmlEntries));
|
|
6728
|
-
appendOfdSummary(summary, "\u9875\u9762", String(pages.length));
|
|
6729
|
-
appendOfdSummary(summary, "\u6587\u672C", String(textCount));
|
|
6730
|
-
appendOfdSummary(summary, "\u8DEF\u5F84", String(pathCount));
|
|
6731
|
-
appendOfdSummary(summary, "\u7EBF\u6761", String(lineCount));
|
|
6732
|
-
appendOfdSummary(summary, "\u56FE\u7247\u5BF9\u8C61", String(imageCount));
|
|
6733
|
-
appendOfdSummary(summary, "\u56FE\u7247\u8D44\u6E90", String(uniqueOfdImageResources(images)));
|
|
6734
|
-
if (textLength > 0) {
|
|
6735
|
-
appendOfdSummary(summary, "\u6587\u5B57\u957F\u5EA6", String(textLength));
|
|
6736
|
-
}
|
|
6737
|
-
const sizes = formatOfdPageSizes(pages);
|
|
6738
|
-
if (sizes) {
|
|
6739
|
-
appendOfdSummary(summary, "\u9875\u9762\u5C3A\u5BF8", sizes);
|
|
6740
|
-
}
|
|
6741
|
-
return summary;
|
|
6742
|
-
}
|
|
6743
|
-
function appendOfdSummary(parent, label, value) {
|
|
6744
|
-
const item = document.createElement("span");
|
|
6745
|
-
const key = document.createElement("span");
|
|
6746
|
-
key.textContent = label;
|
|
6747
|
-
const content = document.createElement("strong");
|
|
6748
|
-
content.textContent = value;
|
|
6749
|
-
item.append(key, content);
|
|
6750
|
-
parent.append(item);
|
|
6751
|
-
}
|
|
6752
|
-
function uniqueOfdImageResources(images) {
|
|
6753
|
-
return new Set(images.values()).size;
|
|
6754
|
-
}
|
|
6755
|
-
function formatOfdPageSizes(pages) {
|
|
6756
|
-
const counts = /* @__PURE__ */ new Map();
|
|
6757
|
-
for (const page of pages) {
|
|
6758
|
-
const key = `${Math.round(page.width)} x ${Math.round(page.height)}`;
|
|
6759
|
-
counts.set(key, (counts.get(key) || 0) + 1);
|
|
6760
|
-
}
|
|
6761
|
-
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([size, count]) => count > 1 ? `${size} (${count})` : size).join(", ");
|
|
6762
|
-
}
|
|
6763
|
-
async function readOfdPages(entries, images) {
|
|
6904
|
+
async function readOfdPages(entries, context) {
|
|
6764
6905
|
const pages = [];
|
|
6765
6906
|
const pageEntries = entries.filter((entry) => /(^|\/)Pages\/Page_[^/]+\/Content\.xml$/i.test(entry.name) || /(^|\/)Page_[^/]+\/Content\.xml$/i.test(entry.name)).slice(0, 80);
|
|
6766
6907
|
for (const entry of pageEntries) {
|
|
6767
6908
|
const xml = await entry.async("text");
|
|
6768
|
-
const
|
|
6909
|
+
const templates = await readPageTemplates(xml, context, entries);
|
|
6910
|
+
const page = parseOfdPage(entry.name, xml, context.images, context.fonts, templates, context.pageSize);
|
|
6769
6911
|
if (page.texts.length > 0 || page.paths.length > 0 || page.lines.length > 0 || page.images.length > 0) {
|
|
6770
6912
|
pages.push(page);
|
|
6771
6913
|
}
|
|
6772
6914
|
}
|
|
6773
6915
|
return pages;
|
|
6774
6916
|
}
|
|
6775
|
-
function
|
|
6917
|
+
async function readPageTemplates(xml, context, entries) {
|
|
6918
|
+
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
6919
|
+
if (doc.querySelector("parsererror")) {
|
|
6920
|
+
return [];
|
|
6921
|
+
}
|
|
6922
|
+
const templateIds = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Template").map((element) => getOfdAttribute(element, "TemplateID") || getOfdAttribute(element, "ID")).filter((id) => Boolean(id));
|
|
6923
|
+
const templates = [];
|
|
6924
|
+
for (const id of templateIds) {
|
|
6925
|
+
const path = context.templates.get(id);
|
|
6926
|
+
const entry = path ? findOfdEntry(entries, path) : void 0;
|
|
6927
|
+
if (entry) {
|
|
6928
|
+
templates.push(await entry.async("text"));
|
|
6929
|
+
}
|
|
6930
|
+
}
|
|
6931
|
+
return templates;
|
|
6932
|
+
}
|
|
6933
|
+
function parseOfdPage(name, xml, images, fonts, templateXmls = [], defaultPageSize) {
|
|
6776
6934
|
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
6777
6935
|
if (doc.querySelector("parsererror")) {
|
|
6778
6936
|
return { name, width: 210, height: 297, texts: [], paths: [], lines: [], images: [] };
|
|
6779
6937
|
}
|
|
6780
|
-
const pageSize = parseOfdPageSize(doc);
|
|
6781
|
-
const
|
|
6782
|
-
|
|
6783
|
-
|
|
6938
|
+
const pageSize = parseOfdPageSize(doc, defaultPageSize);
|
|
6939
|
+
const templatePages = templateXmls.map((templateXml) => {
|
|
6940
|
+
const templateDoc = new DOMParser().parseFromString(templateXml, "application/xml");
|
|
6941
|
+
return templateDoc.querySelector("parsererror") ? emptyOfdPageContent() : parseOfdPageContent(templateDoc, images, fonts);
|
|
6942
|
+
});
|
|
6943
|
+
const pageContent = parseOfdPageContent(doc, images, fonts);
|
|
6944
|
+
const texts = [...templatePages.flatMap((page) => page.texts), ...pageContent.texts];
|
|
6945
|
+
const paths = [...templatePages.flatMap((page) => page.paths), ...pageContent.paths];
|
|
6946
|
+
const lines = [...templatePages.flatMap((page) => page.lines), ...pageContent.lines];
|
|
6947
|
+
const imageObjects = [...templatePages.flatMap((page) => page.images), ...pageContent.images];
|
|
6948
|
+
if (pageSize.explicit) {
|
|
6949
|
+
return { name, width: pageSize.width, height: pageSize.height, texts, paths, lines, images: imageObjects };
|
|
6950
|
+
}
|
|
6951
|
+
const bounds = createOfdBounds(texts, paths, lines, imageObjects);
|
|
6952
|
+
const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
|
|
6953
|
+
const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
|
|
6954
|
+
return { name, width, height, texts, paths, lines, images: imageObjects };
|
|
6955
|
+
}
|
|
6956
|
+
function parseOfdPageContent(doc, images, fonts) {
|
|
6957
|
+
const textObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TextObject");
|
|
6958
|
+
const texts = textObjects.flatMap((element) => parseOfdTextObject(element, fonts));
|
|
6959
|
+
const paths = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "PathObject").flatMap((element) => parseOfdPathObject(element));
|
|
6784
6960
|
const lines = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "LineObject").flatMap((element) => parseOfdLineObject(element));
|
|
6785
6961
|
const imageObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ImageObject").flatMap((element) => parseOfdImageObject(element, images));
|
|
6786
|
-
|
|
6962
|
+
return { texts, paths, lines, images: imageObjects };
|
|
6963
|
+
}
|
|
6964
|
+
function emptyOfdPageContent() {
|
|
6965
|
+
return { texts: [], paths: [], lines: [], images: [] };
|
|
6966
|
+
}
|
|
6967
|
+
function createOfdBounds(texts, paths, lines, imageObjects) {
|
|
6968
|
+
return [
|
|
6787
6969
|
...texts.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
|
|
6788
6970
|
...paths.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
|
|
6789
6971
|
...lines.map((item) => ({
|
|
@@ -6794,38 +6976,63 @@ function parseOfdPage(name, xml, images) {
|
|
|
6794
6976
|
})),
|
|
6795
6977
|
...imageObjects.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height }))
|
|
6796
6978
|
];
|
|
6797
|
-
const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
|
|
6798
|
-
const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
|
|
6799
|
-
return { name, width, height, texts, paths, lines, images: imageObjects };
|
|
6800
6979
|
}
|
|
6801
|
-
function parseOfdTextObject(element) {
|
|
6980
|
+
function parseOfdTextObject(element, fonts) {
|
|
6802
6981
|
const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
|
|
6803
6982
|
const size = finiteNumber2(getOfdAttribute(element, "Size"), Math.max(4, boundary.height || 5));
|
|
6804
6983
|
const color = parseOfdColor(element, "#111827");
|
|
6805
6984
|
const weight = finiteNumber2(getOfdAttribute(element, "Weight"), 400) >= 600 ? "700" : "400";
|
|
6806
|
-
const
|
|
6985
|
+
const fontFamily = fontStackForOfdFont(fonts.get(getOfdAttribute(element, "Font") || ""));
|
|
6986
|
+
const objectLetterSpacing = getOfdAttribute(element, "DeltaX") ? 0.5 : void 0;
|
|
6807
6987
|
const textCodes = Array.from(element.getElementsByTagName("*")).filter((child) => child.localName === "TextCode");
|
|
6808
6988
|
if (textCodes.length === 0) {
|
|
6809
6989
|
return [];
|
|
6810
6990
|
}
|
|
6811
|
-
return textCodes.
|
|
6991
|
+
return textCodes.flatMap((code) => {
|
|
6812
6992
|
const x = boundary.x + finiteNumber2(getOfdAttribute(code, "X"), 0);
|
|
6813
6993
|
const y = boundary.y + finiteNumber2(getOfdAttribute(code, "Y"), 0);
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6994
|
+
const text = code.textContent?.trim() || "";
|
|
6995
|
+
const deltaX = parseOfdDeltaX(getOfdAttribute(code, "DeltaX"));
|
|
6996
|
+
const align = deltaX ? "start" : inferOfdTextAlign(text, boundary);
|
|
6997
|
+
const deltaY = getOfdAttribute(code, "DeltaY");
|
|
6998
|
+
if (deltaY && text.length > 1) {
|
|
6999
|
+
const step = parseOfdDeltaStep(deltaY, size);
|
|
7000
|
+
return Array.from(text).map((char, index) => ({
|
|
7001
|
+
text: char,
|
|
7002
|
+
x,
|
|
7003
|
+
y: y + index * step,
|
|
7004
|
+
width: boundary.width,
|
|
7005
|
+
height: boundary.height,
|
|
7006
|
+
size,
|
|
7007
|
+
color,
|
|
7008
|
+
weight,
|
|
7009
|
+
fontFamily,
|
|
7010
|
+
letterSpacing: objectLetterSpacing,
|
|
7011
|
+
vertical: true,
|
|
7012
|
+
align
|
|
7013
|
+
}));
|
|
7014
|
+
}
|
|
7015
|
+
return [
|
|
7016
|
+
{
|
|
7017
|
+
text,
|
|
7018
|
+
x,
|
|
7019
|
+
y,
|
|
7020
|
+
width: boundary.width,
|
|
7021
|
+
height: boundary.height,
|
|
7022
|
+
size,
|
|
7023
|
+
color,
|
|
7024
|
+
weight,
|
|
7025
|
+
fontFamily,
|
|
7026
|
+
letterSpacing: deltaX ? void 0 : objectLetterSpacing,
|
|
7027
|
+
deltaX,
|
|
7028
|
+
align
|
|
7029
|
+
}
|
|
7030
|
+
];
|
|
6825
7031
|
}).filter((item) => item.text);
|
|
6826
7032
|
}
|
|
6827
7033
|
function parseOfdPathObject(element) {
|
|
6828
7034
|
const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
|
|
7035
|
+
const ctm = parseOfdCtm(getOfdAttribute(element, "CTM"));
|
|
6829
7036
|
const commands = Array.from(element.getElementsByTagName("*")).filter(
|
|
6830
7037
|
(child) => child.localName === "AbbreviatedData" || child.localName === "PathData"
|
|
6831
7038
|
);
|
|
@@ -6840,9 +7047,10 @@ function parseOfdPathObject(element) {
|
|
|
6840
7047
|
y: boundary.y,
|
|
6841
7048
|
width: boundary.width,
|
|
6842
7049
|
height: boundary.height,
|
|
6843
|
-
stroke: parseOfdColor(element, "#
|
|
7050
|
+
stroke: parseOfdColor(element, "#111827", "StrokeColor"),
|
|
6844
7051
|
fill: parseOfdFill(element),
|
|
6845
|
-
strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
|
|
7052
|
+
strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1),
|
|
7053
|
+
transform: createOfdPathTransform(boundary.x, boundary.y, ctm)
|
|
6846
7054
|
}
|
|
6847
7055
|
];
|
|
6848
7056
|
}
|
|
@@ -6859,7 +7067,7 @@ function parseOfdLineObject(element) {
|
|
|
6859
7067
|
y1: boundary.y + start.y,
|
|
6860
7068
|
x2: boundary.x + end.x,
|
|
6861
7069
|
y2: boundary.y + end.y,
|
|
6862
|
-
stroke: parseOfdColor(element, "#
|
|
7070
|
+
stroke: parseOfdColor(element, "#111827"),
|
|
6863
7071
|
strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
|
|
6864
7072
|
}
|
|
6865
7073
|
];
|
|
@@ -6881,6 +7089,8 @@ function parseOfdImageObject(element, images) {
|
|
|
6881
7089
|
function renderOfdPage(page) {
|
|
6882
7090
|
const figure = document.createElement("figure");
|
|
6883
7091
|
figure.className = "ofv-ofd-page";
|
|
7092
|
+
figure.style.setProperty("--ofv-ofd-page-width", `${formatOfdCssNumber(page.width)}mm`);
|
|
7093
|
+
figure.style.setProperty("--ofv-ofd-page-height", `${formatOfdCssNumber(page.height)}mm`);
|
|
6884
7094
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
6885
7095
|
svg.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`);
|
|
6886
7096
|
svg.setAttribute("role", "img");
|
|
@@ -6917,7 +7127,7 @@ function renderOfdPage(page) {
|
|
|
6917
7127
|
for (const item of page.paths) {
|
|
6918
7128
|
const path = document.createElementNS(svg.namespaceURI, "path");
|
|
6919
7129
|
path.setAttribute("d", item.d);
|
|
6920
|
-
path.setAttribute("transform",
|
|
7130
|
+
path.setAttribute("transform", item.transform);
|
|
6921
7131
|
path.setAttribute("fill", item.fill);
|
|
6922
7132
|
path.setAttribute("stroke", item.stroke);
|
|
6923
7133
|
path.setAttribute("stroke-width", String(item.strokeWidth));
|
|
@@ -6936,22 +7146,63 @@ function renderOfdPage(page) {
|
|
|
6936
7146
|
}
|
|
6937
7147
|
for (const item of page.texts) {
|
|
6938
7148
|
const text = document.createElementNS(svg.namespaceURI, "text");
|
|
6939
|
-
text.setAttribute("x", String(item.x));
|
|
6940
|
-
text.setAttribute("y", String(item.y
|
|
7149
|
+
text.setAttribute("x", String(item.align === "end" ? item.x + item.width : item.x));
|
|
7150
|
+
text.setAttribute("y", String(item.y));
|
|
6941
7151
|
text.setAttribute("font-size", String(item.size));
|
|
6942
7152
|
text.setAttribute("fill", item.color);
|
|
6943
7153
|
text.setAttribute("font-weight", item.weight);
|
|
7154
|
+
text.setAttribute("font-family", item.fontFamily);
|
|
6944
7155
|
if (item.letterSpacing !== void 0) {
|
|
6945
7156
|
text.setAttribute("letter-spacing", String(item.letterSpacing));
|
|
6946
7157
|
}
|
|
6947
|
-
|
|
7158
|
+
if (item.deltaX && item.deltaX.length > 0 && item.align !== "end") {
|
|
7159
|
+
const chars = Array.from(item.text);
|
|
7160
|
+
let x = item.x;
|
|
7161
|
+
for (let index = 0; index < chars.length; index += 1) {
|
|
7162
|
+
const span = document.createElementNS(svg.namespaceURI, "tspan");
|
|
7163
|
+
span.setAttribute("x", String(x));
|
|
7164
|
+
span.setAttribute("y", String(item.y));
|
|
7165
|
+
if (index < chars.length - 1) {
|
|
7166
|
+
x += item.deltaX[Math.min(index, item.deltaX.length - 1)] || item.size;
|
|
7167
|
+
}
|
|
7168
|
+
span.textContent = chars[index];
|
|
7169
|
+
text.append(span);
|
|
7170
|
+
}
|
|
7171
|
+
} else {
|
|
7172
|
+
if (item.align === "end") {
|
|
7173
|
+
text.setAttribute("text-anchor", "end");
|
|
7174
|
+
}
|
|
7175
|
+
text.textContent = item.text;
|
|
7176
|
+
}
|
|
6948
7177
|
svg.append(text);
|
|
6949
7178
|
}
|
|
6950
|
-
|
|
6951
|
-
caption.textContent = `${page.name} \xB7 ${page.texts.length} text \xB7 ${page.paths.length} path \xB7 ${page.lines.length} line \xB7 ${page.images.length} image`;
|
|
6952
|
-
figure.append(svg, caption);
|
|
7179
|
+
figure.append(svg);
|
|
6953
7180
|
return figure;
|
|
6954
7181
|
}
|
|
7182
|
+
async function readOfdContext(entries) {
|
|
7183
|
+
const images = await readOfdImages(entries);
|
|
7184
|
+
const fonts = await readOfdFonts(entries);
|
|
7185
|
+
const { templates, pageSize } = await readOfdDocumentInfo(entries);
|
|
7186
|
+
return { images, templates, fonts, pageSize };
|
|
7187
|
+
}
|
|
7188
|
+
async function readOfdFonts(entries) {
|
|
7189
|
+
const fonts = /* @__PURE__ */ new Map();
|
|
7190
|
+
for (const entry of entries.filter((item) => /(?:^|\/)(?:DocumentRes|PublicRes)\.xml$/i.test(item.name))) {
|
|
7191
|
+
const xml = await entry.async("text");
|
|
7192
|
+
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
7193
|
+
if (doc.querySelector("parsererror")) {
|
|
7194
|
+
continue;
|
|
7195
|
+
}
|
|
7196
|
+
for (const font of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Font")) {
|
|
7197
|
+
const id = getOfdAttribute(font, "ID");
|
|
7198
|
+
const name = getOfdAttribute(font, "FontName") || getOfdAttribute(font, "FamilyName");
|
|
7199
|
+
if (id && name) {
|
|
7200
|
+
fonts.set(id, name.trim());
|
|
7201
|
+
}
|
|
7202
|
+
}
|
|
7203
|
+
}
|
|
7204
|
+
return fonts;
|
|
7205
|
+
}
|
|
6955
7206
|
async function readOfdImages(entries) {
|
|
6956
7207
|
const images = /* @__PURE__ */ new Map();
|
|
6957
7208
|
for (const entry of entries.filter((item) => /\.(?:png|jpe?g|gif|bmp|webp)$/i.test(item.name)).slice(0, 80)) {
|
|
@@ -6966,17 +7217,69 @@ async function readOfdImages(entries) {
|
|
|
6966
7217
|
images.set(entry.name, href);
|
|
6967
7218
|
images.set(entry.name.split("/").pop() || entry.name, href);
|
|
6968
7219
|
}
|
|
7220
|
+
for (const entry of entries.filter((item) => /(?:^|\/)(?:DocumentRes|PublicRes)\.xml$/i.test(item.name))) {
|
|
7221
|
+
const xml = await entry.async("text");
|
|
7222
|
+
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
7223
|
+
if (doc.querySelector("parsererror")) {
|
|
7224
|
+
continue;
|
|
7225
|
+
}
|
|
7226
|
+
const baseLoc = getOfdAttribute(doc.documentElement, "BaseLoc") || "";
|
|
7227
|
+
const resourceDir = joinOfdPath(directoryName2(entry.name), baseLoc);
|
|
7228
|
+
for (const media of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "MultiMedia")) {
|
|
7229
|
+
const id = getOfdAttribute(media, "ID");
|
|
7230
|
+
const mediaFile = findOfdChild(media, "MediaFile")?.textContent?.trim();
|
|
7231
|
+
if (!id || !mediaFile) {
|
|
7232
|
+
continue;
|
|
7233
|
+
}
|
|
7234
|
+
const imageEntry = findOfdEntry(entries, joinOfdPath(resourceDir, mediaFile)) || findOfdEntry(entries, mediaFile);
|
|
7235
|
+
const href = imageEntry ? images.get(imageEntry.name) || images.get(imageEntry.name.split("/").pop() || imageEntry.name) : void 0;
|
|
7236
|
+
if (href) {
|
|
7237
|
+
images.set(id, href);
|
|
7238
|
+
}
|
|
7239
|
+
}
|
|
7240
|
+
}
|
|
6969
7241
|
return images;
|
|
6970
7242
|
}
|
|
6971
|
-
function
|
|
7243
|
+
async function readOfdDocumentInfo(entries) {
|
|
7244
|
+
const templates = /* @__PURE__ */ new Map();
|
|
7245
|
+
let pageSize;
|
|
7246
|
+
for (const entry of entries.filter((item) => /(?:^|\/)Document\.xml$/i.test(item.name))) {
|
|
7247
|
+
const xml = await entry.async("text");
|
|
7248
|
+
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
7249
|
+
if (doc.querySelector("parsererror")) {
|
|
7250
|
+
continue;
|
|
7251
|
+
}
|
|
7252
|
+
const documentDir = directoryName2(entry.name);
|
|
7253
|
+
const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PageArea")?.getElementsByTagName("*");
|
|
7254
|
+
const pageAreaBox = physicalBox ? Array.from(physicalBox).find((element) => element.localName === "PhysicalBox") : void 0;
|
|
7255
|
+
if (pageAreaBox?.textContent) {
|
|
7256
|
+
const box = parseBoundary(pageAreaBox.textContent);
|
|
7257
|
+
if (box.width > 0 && box.height > 0) {
|
|
7258
|
+
pageSize = { width: box.width, height: box.height };
|
|
7259
|
+
}
|
|
7260
|
+
}
|
|
7261
|
+
for (const template of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TemplatePage")) {
|
|
7262
|
+
const id = getOfdAttribute(template, "ID");
|
|
7263
|
+
const baseLoc = getOfdAttribute(template, "BaseLoc");
|
|
7264
|
+
if (id && baseLoc) {
|
|
7265
|
+
templates.set(id, joinOfdPath(documentDir, baseLoc));
|
|
7266
|
+
}
|
|
7267
|
+
}
|
|
7268
|
+
}
|
|
7269
|
+
return { templates, pageSize };
|
|
7270
|
+
}
|
|
7271
|
+
function parseOfdPageSize(doc, defaultPageSize) {
|
|
7272
|
+
if (defaultPageSize) {
|
|
7273
|
+
return { ...defaultPageSize, explicit: true };
|
|
7274
|
+
}
|
|
6972
7275
|
const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PhysicalBox");
|
|
6973
7276
|
if (physicalBox?.textContent) {
|
|
6974
7277
|
const box = parseBoundary(physicalBox.textContent);
|
|
6975
7278
|
if (box.width > 0 && box.height > 0) {
|
|
6976
|
-
return { width: box.width, height: box.height };
|
|
7279
|
+
return { width: box.width, height: box.height, explicit: true };
|
|
6977
7280
|
}
|
|
6978
7281
|
}
|
|
6979
|
-
return { width: 210, height: 297 };
|
|
7282
|
+
return { width: 210, height: 297, explicit: false };
|
|
6980
7283
|
}
|
|
6981
7284
|
function parseOfdColor(element, fallback, preferredLocalName = "FillColor") {
|
|
6982
7285
|
const colorElement = findOfdChild(element, preferredLocalName) || findOfdChild(element, "StrokeColor") || findOfdChild(element, "FillColor");
|
|
@@ -7007,6 +7310,101 @@ function parsePoint(value, fallback) {
|
|
|
7007
7310
|
function normalizeOfdPathData(value) {
|
|
7008
7311
|
return value.replace(/\bM\s+/gi, "M ").replace(/\bL\s+/gi, "L ").replace(/\bC\s+/gi, "C ").replace(/\bQ\s+/gi, "Q ").replace(/\bA\s+/gi, "A ").replace(/\bB\s+/gi, "C ").replace(/\bZ\b/gi, "Z").replace(/\s+/g, " ").trim();
|
|
7009
7312
|
}
|
|
7313
|
+
function parseOfdCtm(value) {
|
|
7314
|
+
const parts = (value || "").trim().split(/\s+/).map((part) => Number(part));
|
|
7315
|
+
if (parts.length !== 6 || parts.some((part) => !Number.isFinite(part))) {
|
|
7316
|
+
return void 0;
|
|
7317
|
+
}
|
|
7318
|
+
return parts;
|
|
7319
|
+
}
|
|
7320
|
+
function createOfdPathTransform(x, y, ctm) {
|
|
7321
|
+
if (!ctm) {
|
|
7322
|
+
return `translate(${x} ${y})`;
|
|
7323
|
+
}
|
|
7324
|
+
const [a, b, c, d, e, f] = ctm;
|
|
7325
|
+
return `translate(${x} ${y}) matrix(${a} ${b} ${c} ${d} ${e} ${f})`;
|
|
7326
|
+
}
|
|
7327
|
+
function parseOfdDeltaStep(value, fallback) {
|
|
7328
|
+
const numbers = value.match(/-?\d+(?:\.\d+)?/g)?.map((part) => Number(part)).filter((part) => Number.isFinite(part)) || [];
|
|
7329
|
+
return numbers.length > 0 ? numbers[numbers.length - 1] : fallback;
|
|
7330
|
+
}
|
|
7331
|
+
function parseOfdDeltaX(value) {
|
|
7332
|
+
if (!value) {
|
|
7333
|
+
return void 0;
|
|
7334
|
+
}
|
|
7335
|
+
const parts = value.match(/[a-z]+|-?\d+(?:\.\d+)?/gi) || [];
|
|
7336
|
+
const deltas = [];
|
|
7337
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
7338
|
+
const token = parts[index];
|
|
7339
|
+
if (/^g$/i.test(token)) {
|
|
7340
|
+
const count = Number(parts[index + 1]);
|
|
7341
|
+
const step = Number(parts[index + 2]);
|
|
7342
|
+
if (Number.isFinite(count) && Number.isFinite(step)) {
|
|
7343
|
+
deltas.push(...Array.from({ length: Math.max(0, Math.floor(count)) }, () => step));
|
|
7344
|
+
}
|
|
7345
|
+
index += 2;
|
|
7346
|
+
continue;
|
|
7347
|
+
}
|
|
7348
|
+
const numeric = Number(token);
|
|
7349
|
+
if (Number.isFinite(numeric)) {
|
|
7350
|
+
deltas.push(numeric);
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
return deltas.length > 0 ? deltas : void 0;
|
|
7354
|
+
}
|
|
7355
|
+
function fontStackForOfdFont(fontName) {
|
|
7356
|
+
const normalized = (fontName || "").trim().toLowerCase();
|
|
7357
|
+
if (normalized.includes("courier")) {
|
|
7358
|
+
return '"Courier New", Courier, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
|
|
7359
|
+
}
|
|
7360
|
+
if (normalized.includes("kaiti") || normalized.includes("kai") || normalized.includes("\u6977")) {
|
|
7361
|
+
return '"STKaiti", "Kaiti SC", "KaiTi", "\u6977\u4F53", serif';
|
|
7362
|
+
}
|
|
7363
|
+
if (normalized.includes("simsun") || normalized.includes("simsong") || normalized.includes("song") || normalized.includes("\u5B8B")) {
|
|
7364
|
+
return '"SimSong", "Songti SC", "STSong", SimSun, "\u5B8B\u4F53", serif';
|
|
7365
|
+
}
|
|
7366
|
+
if (normalized.includes("hei") || normalized.includes("\u9ED1")) {
|
|
7367
|
+
return '"PingFang SC", "Microsoft YaHei", SimHei, sans-serif';
|
|
7368
|
+
}
|
|
7369
|
+
return '"SimSong", "Songti SC", "STSong", SimSun, "Noto Serif CJK SC", serif';
|
|
7370
|
+
}
|
|
7371
|
+
function inferOfdTextAlign(text, boundary) {
|
|
7372
|
+
const normalized = text.trim();
|
|
7373
|
+
if (!/^[¥¥]?\d+(?:\.\d+)?%?$/.test(normalized)) {
|
|
7374
|
+
return "start";
|
|
7375
|
+
}
|
|
7376
|
+
if (boundary.x >= 75 || boundary.width <= 30) {
|
|
7377
|
+
return "end";
|
|
7378
|
+
}
|
|
7379
|
+
return "start";
|
|
7380
|
+
}
|
|
7381
|
+
function findOfdEntry(entries, path) {
|
|
7382
|
+
const normalized = normalizeOfdPath(path);
|
|
7383
|
+
return entries.find((entry) => normalizeOfdPath(entry.name) === normalized || normalizeOfdPath(entry.name).endsWith(`/${normalized}`));
|
|
7384
|
+
}
|
|
7385
|
+
function joinOfdPath(...parts) {
|
|
7386
|
+
const joined = parts.filter(Boolean).join("/");
|
|
7387
|
+
const segments = [];
|
|
7388
|
+
for (const segment of joined.split("/")) {
|
|
7389
|
+
if (!segment || segment === ".") {
|
|
7390
|
+
continue;
|
|
7391
|
+
}
|
|
7392
|
+
if (segment === "..") {
|
|
7393
|
+
segments.pop();
|
|
7394
|
+
continue;
|
|
7395
|
+
}
|
|
7396
|
+
segments.push(segment);
|
|
7397
|
+
}
|
|
7398
|
+
return segments.join("/");
|
|
7399
|
+
}
|
|
7400
|
+
function directoryName2(path) {
|
|
7401
|
+
const normalized = normalizeOfdPath(path);
|
|
7402
|
+
const index = normalized.lastIndexOf("/");
|
|
7403
|
+
return index >= 0 ? normalized.slice(0, index) : "";
|
|
7404
|
+
}
|
|
7405
|
+
function normalizeOfdPath(path) {
|
|
7406
|
+
return path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+/g, "/");
|
|
7407
|
+
}
|
|
7010
7408
|
function mimeTypeFromPath2(path) {
|
|
7011
7409
|
const extension = path.split(".").pop()?.toLowerCase();
|
|
7012
7410
|
const map = {
|
|
@@ -7044,6 +7442,19 @@ function finiteNumber2(value, fallback) {
|
|
|
7044
7442
|
const parsed = value === null ? Number.NaN : Number(value);
|
|
7045
7443
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
7046
7444
|
}
|
|
7445
|
+
function formatOfdCssNumber(value) {
|
|
7446
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
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
|
+
}
|
|
7047
7458
|
function createOfdFallback(fileName, url, detail) {
|
|
7048
7459
|
const fallback = document.createElement("div");
|
|
7049
7460
|
fallback.className = "ofv-fallback";
|
|
@@ -7112,7 +7523,9 @@ function archivePlugin() {
|
|
|
7112
7523
|
try {
|
|
7113
7524
|
if (ext === "zip") {
|
|
7114
7525
|
try {
|
|
7115
|
-
const zip = await JSZip5.loadAsync(await readArrayBuffer(ctx.file)
|
|
7526
|
+
const zip = await JSZip5.loadAsync(await readArrayBuffer(ctx.file), {
|
|
7527
|
+
decodeFileName: decodeZipFileName
|
|
7528
|
+
});
|
|
7116
7529
|
archiveEntries = Object.values(zip.files).map((entry) => ({
|
|
7117
7530
|
name: entry.name,
|
|
7118
7531
|
unsafeName: entry.unsafeOriginalName,
|
|
@@ -7121,7 +7534,7 @@ function archivePlugin() {
|
|
|
7121
7534
|
read: () => entry.async("arraybuffer")
|
|
7122
7535
|
}));
|
|
7123
7536
|
} catch (zipErr) {
|
|
7124
|
-
if (
|
|
7537
|
+
if (isEncryptedError(zipErr)) {
|
|
7125
7538
|
isEncrypted = true;
|
|
7126
7539
|
} else {
|
|
7127
7540
|
throw zipErr;
|
|
@@ -7154,17 +7567,11 @@ function archivePlugin() {
|
|
|
7154
7567
|
parseError = `\u538B\u7F29\u5305\u89E3\u6790\u5931\u8D25\uFF1A${err.message || err}`;
|
|
7155
7568
|
}
|
|
7156
7569
|
if (isEncrypted) {
|
|
7157
|
-
const fallback =
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
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";
|
|
7163
|
-
const download = document.createElement("a");
|
|
7164
|
-
download.href = url;
|
|
7165
|
-
download.download = ctx.file.name;
|
|
7166
|
-
download.textContent = "\u4E0B\u8F7D\u6587\u4EF6";
|
|
7167
|
-
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
|
+
});
|
|
7168
7575
|
panel.append(fallback);
|
|
7169
7576
|
ctx.viewport.classList.add("ofv-center");
|
|
7170
7577
|
return {
|
|
@@ -7413,6 +7820,28 @@ async function findSubPreviewPlugin(plugins, file) {
|
|
|
7413
7820
|
}
|
|
7414
7821
|
return fallbackPlugin();
|
|
7415
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
|
+
}
|
|
7416
7845
|
function createInlineError(titleText, detailText) {
|
|
7417
7846
|
const fallback = document.createElement("div");
|
|
7418
7847
|
fallback.className = "ofv-fallback";
|
|
@@ -7834,13 +8263,14 @@ function emailPlugin() {
|
|
|
7834
8263
|
} else {
|
|
7835
8264
|
const PostalMime = (await import("postal-mime")).default;
|
|
7836
8265
|
const parser = new PostalMime();
|
|
7837
|
-
let
|
|
8266
|
+
let rawSource = await readArrayBuffer(ctx.file);
|
|
7838
8267
|
if (ext === "mbox") {
|
|
8268
|
+
let rawText = await readTextFile(ctx.file);
|
|
7839
8269
|
const messages = splitMboxMessages(rawText);
|
|
7840
8270
|
mboxSummary = messages.map(summarizeMboxMessage);
|
|
7841
|
-
|
|
8271
|
+
rawSource = messages[0] || rawText;
|
|
7842
8272
|
}
|
|
7843
|
-
const parsed = await parser.parse(
|
|
8273
|
+
const parsed = await parser.parse(rawSource);
|
|
7844
8274
|
const from = parsed.from ? `${parsed.from.name || ""} <${parsed.from.address || ""}>`.trim() : "";
|
|
7845
8275
|
const to = Array.isArray(parsed.to) ? parsed.to.map((t) => `${t.name || ""} <${t.address || ""}>`.trim()).join("; ") : "";
|
|
7846
8276
|
const cc = Array.isArray(parsed.cc) ? parsed.cc.map((c) => `${c.name || ""} <${c.address || ""}>`.trim()).join("; ") : "";
|
|
@@ -7910,13 +8340,17 @@ function emailPlugin() {
|
|
|
7910
8340
|
const sanitizedHtml = sanitizeEmailHtml(html);
|
|
7911
8341
|
const iframe = document.createElement("iframe");
|
|
7912
8342
|
iframe.className = "ofv-email-body-iframe";
|
|
7913
|
-
iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
|
|
8343
|
+
iframe.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
|
|
7914
8344
|
iframe.style.cssText = "width: 100%; border: none; background: #fff; min-height: 200px;";
|
|
7915
|
-
|
|
7916
|
-
|
|
8345
|
+
let renderedHtmlBody = false;
|
|
8346
|
+
const renderHtmlBody = () => {
|
|
8347
|
+
if (renderedHtmlBody) {
|
|
8348
|
+
return;
|
|
8349
|
+
}
|
|
7917
8350
|
try {
|
|
7918
8351
|
const idoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
7919
8352
|
if (idoc) {
|
|
8353
|
+
renderedHtmlBody = true;
|
|
7920
8354
|
idoc.open();
|
|
7921
8355
|
idoc.write(`
|
|
7922
8356
|
<!doctype html>
|
|
@@ -7967,6 +8401,9 @@ function emailPlugin() {
|
|
|
7967
8401
|
console.error("Failed to write html body to email iframe:", err);
|
|
7968
8402
|
}
|
|
7969
8403
|
};
|
|
8404
|
+
iframe.addEventListener("load", renderHtmlBody, { once: true });
|
|
8405
|
+
bodySection.append(iframe);
|
|
8406
|
+
renderHtmlBody();
|
|
7970
8407
|
} else {
|
|
7971
8408
|
const pre = document.createElement("pre");
|
|
7972
8409
|
pre.className = "ofv-text-block";
|
|
@@ -9488,6 +9925,7 @@ function decodeDrawioDiagram(value) {
|
|
|
9488
9925
|
}
|
|
9489
9926
|
|
|
9490
9927
|
// src/plugins/cad.ts
|
|
9928
|
+
import pako3 from "pako";
|
|
9491
9929
|
var cadExtensions = /* @__PURE__ */ new Set([
|
|
9492
9930
|
"dxf",
|
|
9493
9931
|
"dwg",
|
|
@@ -9504,7 +9942,10 @@ var cadExtensions = /* @__PURE__ */ new Set([
|
|
|
9504
9942
|
"3dm",
|
|
9505
9943
|
"skp",
|
|
9506
9944
|
"sldprt",
|
|
9507
|
-
"sldasm"
|
|
9945
|
+
"sldasm",
|
|
9946
|
+
"gds",
|
|
9947
|
+
"oas",
|
|
9948
|
+
"oasis"
|
|
9508
9949
|
]);
|
|
9509
9950
|
var cadMimeTypes = /* @__PURE__ */ new Set([
|
|
9510
9951
|
"application/acad",
|
|
@@ -9521,7 +9962,11 @@ var cadMimeTypes = /* @__PURE__ */ new Set([
|
|
|
9521
9962
|
"application/x-parasolid",
|
|
9522
9963
|
"model/vnd.3dm",
|
|
9523
9964
|
"application/vnd.sketchup.skp",
|
|
9524
|
-
"application/sldworks"
|
|
9965
|
+
"application/sldworks",
|
|
9966
|
+
"application/vnd.gds",
|
|
9967
|
+
"application/x-gdsii",
|
|
9968
|
+
"application/vnd.oasis.layout",
|
|
9969
|
+
"application/x-oasis-layout"
|
|
9525
9970
|
]);
|
|
9526
9971
|
var cadMimeFormatMap = {
|
|
9527
9972
|
"application/acad": "dwg",
|
|
@@ -9538,7 +9983,11 @@ var cadMimeFormatMap = {
|
|
|
9538
9983
|
"application/x-parasolid": "x_t",
|
|
9539
9984
|
"model/vnd.3dm": "3dm",
|
|
9540
9985
|
"application/vnd.sketchup.skp": "skp",
|
|
9541
|
-
"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"
|
|
9542
9991
|
};
|
|
9543
9992
|
function cadPlugin() {
|
|
9544
9993
|
return {
|
|
@@ -9566,6 +10015,36 @@ function cadPlugin() {
|
|
|
9566
10015
|
renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
|
|
9567
10016
|
return { destroy: () => panel.remove() };
|
|
9568
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
|
+
}
|
|
9569
10048
|
if (extension !== "dxf") {
|
|
9570
10049
|
const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
|
|
9571
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`);
|
|
@@ -9664,6 +10143,560 @@ function renderIfc(panel, text) {
|
|
|
9664
10143
|
section.append(table);
|
|
9665
10144
|
panel.append(section);
|
|
9666
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
|
+
}
|
|
9667
10700
|
function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
|
|
9668
10701
|
const bytes = new Uint8Array(arrayBuffer);
|
|
9669
10702
|
const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);
|