@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.cjs
CHANGED
|
@@ -301,6 +301,9 @@ var extensionMimeMap = {
|
|
|
301
301
|
skp: "application/vnd.sketchup.skp",
|
|
302
302
|
sldprt: "application/sldworks",
|
|
303
303
|
sldasm: "application/sldworks",
|
|
304
|
+
gds: "application/vnd.gds",
|
|
305
|
+
oas: "application/vnd.oasis.layout",
|
|
306
|
+
oasis: "application/vnd.oasis.layout",
|
|
304
307
|
ttf: "font/ttf",
|
|
305
308
|
otf: "font/otf",
|
|
306
309
|
woff: "font/woff",
|
|
@@ -1805,6 +1808,7 @@ function imagePlugin() {
|
|
|
1805
1808
|
let dragStartY = 0;
|
|
1806
1809
|
let startOffsetX = 0;
|
|
1807
1810
|
let startOffsetY = 0;
|
|
1811
|
+
let activePointerId = null;
|
|
1808
1812
|
const zoomLabel = document.createElement("span");
|
|
1809
1813
|
zoomLabel.className = "ofv-image-zoom";
|
|
1810
1814
|
const updateTransform = () => {
|
|
@@ -1852,29 +1856,61 @@ function imagePlugin() {
|
|
|
1852
1856
|
if (event.button !== 0) {
|
|
1853
1857
|
return;
|
|
1854
1858
|
}
|
|
1859
|
+
if (activePointerId !== null && activePointerId !== event.pointerId) {
|
|
1860
|
+
finishDrag(activePointerId);
|
|
1861
|
+
}
|
|
1855
1862
|
dragging = true;
|
|
1863
|
+
activePointerId = event.pointerId;
|
|
1856
1864
|
dragStartX = event.clientX;
|
|
1857
1865
|
dragStartY = event.clientY;
|
|
1858
1866
|
startOffsetX = offsetX;
|
|
1859
1867
|
startOffsetY = offsetY;
|
|
1860
1868
|
stage.classList.add("is-dragging");
|
|
1861
|
-
|
|
1869
|
+
try {
|
|
1870
|
+
stage.setPointerCapture(event.pointerId);
|
|
1871
|
+
} catch {
|
|
1872
|
+
}
|
|
1862
1873
|
};
|
|
1863
1874
|
const onPointerMove = (event) => {
|
|
1864
|
-
if (!dragging) {
|
|
1875
|
+
if (!dragging || event.pointerId !== activePointerId) {
|
|
1865
1876
|
return;
|
|
1866
1877
|
}
|
|
1867
1878
|
offsetX = startOffsetX + event.clientX - dragStartX;
|
|
1868
1879
|
offsetY = startOffsetY + event.clientY - dragStartY;
|
|
1869
1880
|
updateTransform();
|
|
1870
1881
|
};
|
|
1871
|
-
const
|
|
1882
|
+
const finishDrag = (pointerId) => {
|
|
1883
|
+
const captureId = pointerId ?? activePointerId;
|
|
1872
1884
|
dragging = false;
|
|
1885
|
+
activePointerId = null;
|
|
1873
1886
|
stage.classList.remove("is-dragging");
|
|
1874
|
-
if (
|
|
1875
|
-
|
|
1887
|
+
if (captureId !== null && captureId !== void 0) {
|
|
1888
|
+
try {
|
|
1889
|
+
if (stage.hasPointerCapture(captureId)) {
|
|
1890
|
+
stage.releasePointerCapture(captureId);
|
|
1891
|
+
}
|
|
1892
|
+
} catch {
|
|
1893
|
+
}
|
|
1876
1894
|
}
|
|
1877
1895
|
};
|
|
1896
|
+
const onPointerUp = (event) => {
|
|
1897
|
+
if (event.pointerId === activePointerId) {
|
|
1898
|
+
finishDrag(event.pointerId);
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
const onLostPointerCapture = (event) => {
|
|
1902
|
+
if (event.pointerId === activePointerId) {
|
|
1903
|
+
finishDrag(null);
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
const onPointerLeave = (event) => {
|
|
1907
|
+
if (event.pointerId === activePointerId && event.buttons === 0) {
|
|
1908
|
+
finishDrag(event.pointerId);
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
const onWindowBlur = () => {
|
|
1912
|
+
finishDrag();
|
|
1913
|
+
};
|
|
1878
1914
|
const onWheel = (event) => {
|
|
1879
1915
|
if (!event.ctrlKey && !event.metaKey) {
|
|
1880
1916
|
return;
|
|
@@ -1886,8 +1922,11 @@ function imagePlugin() {
|
|
|
1886
1922
|
stage.addEventListener("pointermove", onPointerMove);
|
|
1887
1923
|
stage.addEventListener("pointerup", onPointerUp);
|
|
1888
1924
|
stage.addEventListener("pointercancel", onPointerUp);
|
|
1925
|
+
stage.addEventListener("lostpointercapture", onLostPointerCapture);
|
|
1926
|
+
stage.addEventListener("pointerleave", onPointerLeave);
|
|
1889
1927
|
stage.addEventListener("wheel", onWheel, { passive: false });
|
|
1890
1928
|
image.addEventListener("error", showImageFallback);
|
|
1929
|
+
window.addEventListener("blur", onWindowBlur);
|
|
1891
1930
|
stage.append(image);
|
|
1892
1931
|
wrapper.append(...showInlineControls ? [controls, stage, infoBar] : [stage, infoBar]);
|
|
1893
1932
|
ctx.viewport.append(wrapper);
|
|
@@ -1934,8 +1973,12 @@ function imagePlugin() {
|
|
|
1934
1973
|
stage.removeEventListener("pointermove", onPointerMove);
|
|
1935
1974
|
stage.removeEventListener("pointerup", onPointerUp);
|
|
1936
1975
|
stage.removeEventListener("pointercancel", onPointerUp);
|
|
1976
|
+
stage.removeEventListener("lostpointercapture", onLostPointerCapture);
|
|
1977
|
+
stage.removeEventListener("pointerleave", onPointerLeave);
|
|
1937
1978
|
stage.removeEventListener("wheel", onWheel);
|
|
1938
1979
|
image.removeEventListener("error", showImageFallback);
|
|
1980
|
+
window.removeEventListener("blur", onWindowBlur);
|
|
1981
|
+
finishDrag();
|
|
1939
1982
|
wrapper.remove();
|
|
1940
1983
|
if (convertedBlob) {
|
|
1941
1984
|
URL.revokeObjectURL(url);
|
|
@@ -3328,6 +3371,95 @@ function readAiffExtended(bytes, offset) {
|
|
|
3328
3371
|
return mantissa * Math.pow(2, exponent - 16383 - 63);
|
|
3329
3372
|
}
|
|
3330
3373
|
|
|
3374
|
+
// src/plugins/utils.ts
|
|
3375
|
+
async function readArrayBuffer(file) {
|
|
3376
|
+
if (file.source instanceof ArrayBuffer) {
|
|
3377
|
+
return file.source;
|
|
3378
|
+
}
|
|
3379
|
+
if (file.blob) {
|
|
3380
|
+
return file.blob.arrayBuffer();
|
|
3381
|
+
}
|
|
3382
|
+
if (typeof file.source === "string") {
|
|
3383
|
+
const response = await fetch(file.source);
|
|
3384
|
+
if (!response.ok) {
|
|
3385
|
+
throw new Error(`Failed to fetch file: ${response.status}`);
|
|
3386
|
+
}
|
|
3387
|
+
return response.arrayBuffer();
|
|
3388
|
+
}
|
|
3389
|
+
throw new Error("Unsupported file source.");
|
|
3390
|
+
}
|
|
3391
|
+
async function readTextFile(file) {
|
|
3392
|
+
const decode = (buffer) => decodeTextBuffer(buffer);
|
|
3393
|
+
if (typeof file.source === "string") {
|
|
3394
|
+
const response = await fetch(file.source);
|
|
3395
|
+
if (!response.ok) {
|
|
3396
|
+
throw new Error(`Failed to fetch text file: ${response.status}`);
|
|
3397
|
+
}
|
|
3398
|
+
return decode(await response.arrayBuffer());
|
|
3399
|
+
}
|
|
3400
|
+
if (file.blob) {
|
|
3401
|
+
return decode(await file.blob.arrayBuffer());
|
|
3402
|
+
}
|
|
3403
|
+
if (file.source instanceof ArrayBuffer) {
|
|
3404
|
+
return decode(file.source);
|
|
3405
|
+
}
|
|
3406
|
+
return String(file.source);
|
|
3407
|
+
}
|
|
3408
|
+
function createPanel(className = "") {
|
|
3409
|
+
const panel = document.createElement("div");
|
|
3410
|
+
panel.className = `ofv-panel ${className}`.trim();
|
|
3411
|
+
return panel;
|
|
3412
|
+
}
|
|
3413
|
+
function createSection(title) {
|
|
3414
|
+
const section = document.createElement("section");
|
|
3415
|
+
section.className = "ofv-section";
|
|
3416
|
+
const heading = document.createElement("h3");
|
|
3417
|
+
heading.textContent = title;
|
|
3418
|
+
section.append(heading);
|
|
3419
|
+
return section;
|
|
3420
|
+
}
|
|
3421
|
+
function appendMeta(parent, label, value) {
|
|
3422
|
+
const row = document.createElement("div");
|
|
3423
|
+
row.className = "ofv-meta-row";
|
|
3424
|
+
const key = document.createElement("span");
|
|
3425
|
+
key.textContent = label;
|
|
3426
|
+
const content = document.createElement("strong");
|
|
3427
|
+
content.textContent = String(value);
|
|
3428
|
+
row.append(key, content);
|
|
3429
|
+
parent.append(row);
|
|
3430
|
+
}
|
|
3431
|
+
function decodeTextBuffer(buffer) {
|
|
3432
|
+
return decodeTextBytes(new Uint8Array(buffer));
|
|
3433
|
+
}
|
|
3434
|
+
function decodeTextBytes(bytes) {
|
|
3435
|
+
if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
|
|
3436
|
+
return new TextDecoder("utf-8").decode(bytes.subarray(3));
|
|
3437
|
+
}
|
|
3438
|
+
if (bytes.length >= 2) {
|
|
3439
|
+
if (bytes[0] === 255 && bytes[1] === 254) {
|
|
3440
|
+
return new TextDecoder("utf-16le").decode(bytes.subarray(2));
|
|
3441
|
+
}
|
|
3442
|
+
if (bytes[0] === 254 && bytes[1] === 255) {
|
|
3443
|
+
return new TextDecoder("utf-16be").decode(bytes.subarray(2));
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
try {
|
|
3447
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
3448
|
+
} catch {
|
|
3449
|
+
return decodeWithFallback(bytes, "gb18030") || decodeWithFallback(bytes, "gbk") || new TextDecoder("utf-8").decode(bytes);
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
function decodeWithFallback(bytes, encoding) {
|
|
3453
|
+
try {
|
|
3454
|
+
return new TextDecoder(encoding).decode(bytes);
|
|
3455
|
+
} catch {
|
|
3456
|
+
return void 0;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
function resolveFormat(file, mimeMap) {
|
|
3460
|
+
return file.extension || mimeMap[file.mimeType] || "";
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3331
3463
|
// src/plugins/text.ts
|
|
3332
3464
|
var langMap = {
|
|
3333
3465
|
js: "javascript",
|
|
@@ -3984,17 +4116,49 @@ async function readText(source) {
|
|
|
3984
4116
|
if (!response.ok) {
|
|
3985
4117
|
throw new Error(`Failed to fetch text file: ${response.status}`);
|
|
3986
4118
|
}
|
|
3987
|
-
return response.
|
|
4119
|
+
return decodeTextBuffer(await response.arrayBuffer());
|
|
3988
4120
|
}
|
|
3989
4121
|
if (source instanceof Blob) {
|
|
3990
|
-
return source.
|
|
4122
|
+
return decodeTextBuffer(await source.arrayBuffer());
|
|
3991
4123
|
}
|
|
3992
4124
|
if (source instanceof ArrayBuffer) {
|
|
3993
|
-
return
|
|
4125
|
+
return decodeTextBuffer(source);
|
|
3994
4126
|
}
|
|
3995
4127
|
return String(source);
|
|
3996
4128
|
}
|
|
3997
4129
|
|
|
4130
|
+
// src/plugins/encrypted.ts
|
|
4131
|
+
function createEncryptedFallback(file, url, copy = {}) {
|
|
4132
|
+
const fallback = document.createElement("div");
|
|
4133
|
+
fallback.className = "ofv-fallback ofv-encrypted";
|
|
4134
|
+
const title = document.createElement("strong");
|
|
4135
|
+
title.textContent = copy.title || "\u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8";
|
|
4136
|
+
const message = document.createElement("span");
|
|
4137
|
+
message.textContent = copy.message || "\u8BF7\u4E0B\u8F7D\u540E\u5728\u672C\u5730\u8F93\u5165\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684\u6587\u4EF6\u3002";
|
|
4138
|
+
const meta = document.createElement("dl");
|
|
4139
|
+
meta.className = "ofv-fallback-meta ofv-encrypted-meta";
|
|
4140
|
+
appendEncryptedMeta(meta, "\u6587\u4EF6", file.name || "\u672A\u547D\u540D\u6587\u4EF6");
|
|
4141
|
+
appendEncryptedMeta(meta, "\u683C\u5F0F", file.extension ? `.${file.extension}` : file.mimeType || "\u672A\u77E5");
|
|
4142
|
+
const download = document.createElement("a");
|
|
4143
|
+
download.href = url;
|
|
4144
|
+
download.download = file.name;
|
|
4145
|
+
download.textContent = copy.action || "\u4E0B\u8F7D\u6587\u4EF6";
|
|
4146
|
+
fallback.append(title, message, meta, download);
|
|
4147
|
+
return fallback;
|
|
4148
|
+
}
|
|
4149
|
+
function isEncryptedError(error) {
|
|
4150
|
+
const message = error instanceof Error ? error.message : String(error || "");
|
|
4151
|
+
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
4152
|
+
return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(`${name} ${message}`);
|
|
4153
|
+
}
|
|
4154
|
+
function appendEncryptedMeta(parent, label, value) {
|
|
4155
|
+
const key = document.createElement("dt");
|
|
4156
|
+
key.textContent = label;
|
|
4157
|
+
const content = document.createElement("dd");
|
|
4158
|
+
content.textContent = value;
|
|
4159
|
+
parent.append(key, content);
|
|
4160
|
+
}
|
|
4161
|
+
|
|
3998
4162
|
// src/plugins/pdf.ts
|
|
3999
4163
|
function multiplyMatrices(m1, m2) {
|
|
4000
4164
|
return [
|
|
@@ -4036,7 +4200,11 @@ function pdfPlugin(options = {}) {
|
|
|
4036
4200
|
const doc = await documentTask.promise.catch((error) => {
|
|
4037
4201
|
viewer.remove();
|
|
4038
4202
|
ctx.viewport.classList.add("ofv-center");
|
|
4039
|
-
const fallback =
|
|
4203
|
+
const fallback = isEncryptedError(error) ? createEncryptedFallback(ctx.file, url, {
|
|
4204
|
+
title: "PDF \u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
4205
|
+
message: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 PDF \u6587\u4EF6\u3002",
|
|
4206
|
+
action: "\u4E0B\u8F7D PDF"
|
|
4207
|
+
}) : createPdfFallback(ctx.file.name, url, normalizePdfError(error));
|
|
4040
4208
|
ctx.viewport.append(fallback);
|
|
4041
4209
|
return void 0;
|
|
4042
4210
|
});
|
|
@@ -4326,9 +4494,6 @@ function normalizePdfError(error) {
|
|
|
4326
4494
|
const message = error instanceof Error ? error.message : String(error || "");
|
|
4327
4495
|
const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
|
|
4328
4496
|
const lower = `${name} ${message}`.toLowerCase();
|
|
4329
|
-
if (lower.includes("password")) {
|
|
4330
|
-
return "\u8BE5 PDF \u53D7\u5BC6\u7801\u4FDD\u62A4\uFF0C\u5F53\u524D\u65E0\u6CD5\u5728\u6D4F\u89C8\u5668\u5185\u76F4\u63A5\u9884\u89C8\u3002";
|
|
4331
|
-
}
|
|
4332
4497
|
if (lower.includes("invalid") || lower.includes("missing") || lower.includes("corrupt")) {
|
|
4333
4498
|
return "\u8BE5 PDF \u6587\u4EF6\u53EF\u80FD\u5DF2\u635F\u574F\u6216\u683C\u5F0F\u65E0\u6548\u3002";
|
|
4334
4499
|
}
|
|
@@ -4347,68 +4512,6 @@ function configurePdfWorker(pdf, workerSrc) {
|
|
|
4347
4512
|
// src/plugins/epub.ts
|
|
4348
4513
|
var import_jszip = __toESM(require("jszip"), 1);
|
|
4349
4514
|
var import_dompurify = __toESM(require("dompurify"), 1);
|
|
4350
|
-
|
|
4351
|
-
// src/plugins/utils.ts
|
|
4352
|
-
async function readArrayBuffer(file) {
|
|
4353
|
-
if (file.source instanceof ArrayBuffer) {
|
|
4354
|
-
return file.source;
|
|
4355
|
-
}
|
|
4356
|
-
if (file.blob) {
|
|
4357
|
-
return file.blob.arrayBuffer();
|
|
4358
|
-
}
|
|
4359
|
-
if (typeof file.source === "string") {
|
|
4360
|
-
const response = await fetch(file.source);
|
|
4361
|
-
if (!response.ok) {
|
|
4362
|
-
throw new Error(`Failed to fetch file: ${response.status}`);
|
|
4363
|
-
}
|
|
4364
|
-
return response.arrayBuffer();
|
|
4365
|
-
}
|
|
4366
|
-
throw new Error("Unsupported file source.");
|
|
4367
|
-
}
|
|
4368
|
-
async function readTextFile(file) {
|
|
4369
|
-
if (typeof file.source === "string") {
|
|
4370
|
-
const response = await fetch(file.source);
|
|
4371
|
-
if (!response.ok) {
|
|
4372
|
-
throw new Error(`Failed to fetch text file: ${response.status}`);
|
|
4373
|
-
}
|
|
4374
|
-
return response.text();
|
|
4375
|
-
}
|
|
4376
|
-
if (file.blob) {
|
|
4377
|
-
return file.blob.text();
|
|
4378
|
-
}
|
|
4379
|
-
if (file.source instanceof ArrayBuffer) {
|
|
4380
|
-
return new TextDecoder().decode(file.source);
|
|
4381
|
-
}
|
|
4382
|
-
return String(file.source);
|
|
4383
|
-
}
|
|
4384
|
-
function createPanel(className = "") {
|
|
4385
|
-
const panel = document.createElement("div");
|
|
4386
|
-
panel.className = `ofv-panel ${className}`.trim();
|
|
4387
|
-
return panel;
|
|
4388
|
-
}
|
|
4389
|
-
function createSection(title) {
|
|
4390
|
-
const section = document.createElement("section");
|
|
4391
|
-
section.className = "ofv-section";
|
|
4392
|
-
const heading = document.createElement("h3");
|
|
4393
|
-
heading.textContent = title;
|
|
4394
|
-
section.append(heading);
|
|
4395
|
-
return section;
|
|
4396
|
-
}
|
|
4397
|
-
function appendMeta(parent, label, value) {
|
|
4398
|
-
const row = document.createElement("div");
|
|
4399
|
-
row.className = "ofv-meta-row";
|
|
4400
|
-
const key = document.createElement("span");
|
|
4401
|
-
key.textContent = label;
|
|
4402
|
-
const content = document.createElement("strong");
|
|
4403
|
-
content.textContent = String(value);
|
|
4404
|
-
row.append(key, content);
|
|
4405
|
-
parent.append(row);
|
|
4406
|
-
}
|
|
4407
|
-
function resolveFormat(file, mimeMap) {
|
|
4408
|
-
return file.extension || mimeMap[file.mimeType] || "";
|
|
4409
|
-
}
|
|
4410
|
-
|
|
4411
|
-
// src/plugins/epub.ts
|
|
4412
4515
|
var epubMimeTypes = /* @__PURE__ */ new Set(["application/epub+zip", "application/x-epub+zip"]);
|
|
4413
4516
|
function epubPlugin() {
|
|
4414
4517
|
return {
|
|
@@ -5028,8 +5131,15 @@ function officePlugin() {
|
|
|
5028
5131
|
ctx.viewport.append(panel);
|
|
5029
5132
|
const extension = resolveFormat(ctx.file, officeMimeFormatMap);
|
|
5030
5133
|
const arrayBuffer = await readArrayBuffer(ctx.file);
|
|
5134
|
+
const packageFormat = shouldSniffPackagedOffice(extension) ? await detectPackagedOfficeFormat(arrayBuffer) : void 0;
|
|
5031
5135
|
let disposeDocxFit;
|
|
5032
|
-
if (fileIsDocx(extension)) {
|
|
5136
|
+
if (packageFormat === "docx" && !fileIsDocx(extension)) {
|
|
5137
|
+
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
5138
|
+
} else if (packageFormat === "xlsx" && !sheetExtensions.has(extension)) {
|
|
5139
|
+
await renderSheet(panel, arrayBuffer, "xlsx");
|
|
5140
|
+
} else if (packageFormat === "pptx" && !["pptx", "pptm", "ppsx", "ppsm", "potx", "potm"].includes(extension)) {
|
|
5141
|
+
await renderPptx(panel, arrayBuffer);
|
|
5142
|
+
} else if (fileIsDocx(extension)) {
|
|
5033
5143
|
disposeDocxFit = await renderDocx(panel, arrayBuffer);
|
|
5034
5144
|
} else if (extension === "rtf") {
|
|
5035
5145
|
renderPlainDocument(panel, "RTF \u6587\u6863", rtfToText(await readTextFromBuffer(arrayBuffer)));
|
|
@@ -5065,12 +5175,32 @@ function officePlugin() {
|
|
|
5065
5175
|
function fileIsDocx(extension) {
|
|
5066
5176
|
return extension === "docx" || extension === "docm" || extension === "dotx" || extension === "dotm";
|
|
5067
5177
|
}
|
|
5178
|
+
function shouldSniffPackagedOffice(extension) {
|
|
5179
|
+
return isLegacyOfficeBinary(extension) || packagedOfficeCandidates.has(extension) || extension === "";
|
|
5180
|
+
}
|
|
5181
|
+
async function detectPackagedOfficeFormat(arrayBuffer) {
|
|
5182
|
+
try {
|
|
5183
|
+
const zip = await import_jszip3.default.loadAsync(arrayBuffer);
|
|
5184
|
+
const entries = Object.values(zip.files).filter((entry) => !entry.dir);
|
|
5185
|
+
const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
|
|
5186
|
+
if (hasEntry("word/document.xml")) {
|
|
5187
|
+
return "docx";
|
|
5188
|
+
}
|
|
5189
|
+
if (hasEntry("xl/workbook.xml")) {
|
|
5190
|
+
return "xlsx";
|
|
5191
|
+
}
|
|
5192
|
+
if (hasEntry("ppt/presentation.xml")) {
|
|
5193
|
+
return "pptx";
|
|
5194
|
+
}
|
|
5195
|
+
} catch {
|
|
5196
|
+
return void 0;
|
|
5197
|
+
}
|
|
5198
|
+
return void 0;
|
|
5199
|
+
}
|
|
5068
5200
|
async function renderDocx(panel, arrayBuffer) {
|
|
5069
|
-
const section = createSection("Word \u6587\u6863");
|
|
5070
5201
|
const content = document.createElement("div");
|
|
5071
5202
|
content.className = "ofv-docx-document";
|
|
5072
|
-
|
|
5073
|
-
panel.append(section);
|
|
5203
|
+
panel.append(content);
|
|
5074
5204
|
try {
|
|
5075
5205
|
const docxPreview = await import("docx-preview");
|
|
5076
5206
|
await docxPreview.renderAsync(arrayBuffer, content, content, {
|
|
@@ -5291,7 +5421,7 @@ async function renderSheet(panel, arrayBuffer, extension) {
|
|
|
5291
5421
|
const xlsx = await import("xlsx");
|
|
5292
5422
|
let workbook;
|
|
5293
5423
|
try {
|
|
5294
|
-
workbook = xlsx.read(arrayBuffer, { type: "array" });
|
|
5424
|
+
workbook = extension === "csv" || extension === "tsv" ? xlsx.read(decodeTextBuffer(arrayBuffer), { type: "string", FS: extension === "tsv" ? " " : "," }) : xlsx.read(arrayBuffer, { type: "array" });
|
|
5295
5425
|
} catch (error) {
|
|
5296
5426
|
if (isLegacyOfficeBinary(extension)) {
|
|
5297
5427
|
renderLegacyOfficeBinary(panel, extension, arrayBuffer, `\u8868\u683C\u89E3\u6790\u5931\u8D25\uFF1A${normalizeOfficeError(error)}`);
|
|
@@ -5393,6 +5523,10 @@ async function renderSheet(panel, arrayBuffer, extension) {
|
|
|
5393
5523
|
}
|
|
5394
5524
|
}
|
|
5395
5525
|
function renderSheetFallback(panel, extension, detail) {
|
|
5526
|
+
if (isEncryptedText(detail)) {
|
|
5527
|
+
renderEncryptedOfficeByFileInfo(panel, `.${extension || "sheet"}`, "Office \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8");
|
|
5528
|
+
return;
|
|
5529
|
+
}
|
|
5396
5530
|
const section = createSection("\u8868\u683C\u89E3\u6790\u5931\u8D25");
|
|
5397
5531
|
const title = document.createElement("p");
|
|
5398
5532
|
title.textContent = `.${extension || "sheet"} \u6587\u4EF6\u65E0\u6CD5\u89E3\u6790\u4E3A\u53EF\u9884\u89C8\u8868\u683C\u3002`;
|
|
@@ -5403,6 +5537,17 @@ function renderSheetFallback(panel, extension, detail) {
|
|
|
5403
5537
|
section.append(title, meta, support);
|
|
5404
5538
|
panel.append(section);
|
|
5405
5539
|
}
|
|
5540
|
+
function renderEncryptedOfficeByFileInfo(panel, fileLabel, title) {
|
|
5541
|
+
const section = createSection(title);
|
|
5542
|
+
section.classList.add("ofv-encrypted");
|
|
5543
|
+
const message = document.createElement("p");
|
|
5544
|
+
message.textContent = `${fileLabel} \u53EF\u80FD\u5DF2\u52A0\u5BC6\u6216\u53D7\u4FDD\u62A4\u3002\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528 Office/WPS \u8F93\u5165\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684\u6587\u4EF6\u3002`;
|
|
5545
|
+
section.append(message);
|
|
5546
|
+
panel.append(section);
|
|
5547
|
+
}
|
|
5548
|
+
function isEncryptedText(value) {
|
|
5549
|
+
return /\b(password|encrypted|encrypt|protected|decrypt|permission|加密|密码|受保护)\b/i.test(value);
|
|
5550
|
+
}
|
|
5406
5551
|
function renderFlatOds(panel, xml) {
|
|
5407
5552
|
const sheets = parseFlatOds(xml);
|
|
5408
5553
|
renderParsedSheets(panel, sheets, "FODS \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002");
|
|
@@ -5924,10 +6069,30 @@ async function renderPptx(panel, arrayBuffer) {
|
|
|
5924
6069
|
try {
|
|
5925
6070
|
const { PptxViewer } = await import("@aiden0z/pptx-renderer");
|
|
5926
6071
|
await PptxViewer.open(arrayBuffer, container);
|
|
6072
|
+
normalizePptxLayout(container);
|
|
5927
6073
|
} catch {
|
|
5928
6074
|
container.textContent = "PPTX \u6E32\u67D3\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u635F\u574F\u3002";
|
|
5929
6075
|
}
|
|
5930
6076
|
}
|
|
6077
|
+
function normalizePptxLayout(container) {
|
|
6078
|
+
const slideCanvases = findPptxSlideCanvases(container);
|
|
6079
|
+
for (const slide of slideCanvases) {
|
|
6080
|
+
slide.style.backgroundColor = "#FFFFFF";
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
function findPptxSlideCanvases(container) {
|
|
6084
|
+
const slideWrappers = Array.from(container.querySelectorAll("div[data-slide-index]"));
|
|
6085
|
+
const candidates = slideWrappers.flatMap(
|
|
6086
|
+
(wrapper) => Array.from(wrapper.querySelectorAll("div")).filter(isPptxSlideCanvas)
|
|
6087
|
+
);
|
|
6088
|
+
if (candidates.length > 0) {
|
|
6089
|
+
return Array.from(new Set(candidates));
|
|
6090
|
+
}
|
|
6091
|
+
return Array.from(container.querySelectorAll("div")).filter(isPptxSlideCanvas);
|
|
6092
|
+
}
|
|
6093
|
+
function isPptxSlideCanvas(element) {
|
|
6094
|
+
return element.style.position === "relative" && parseCssPixelValue(element.style.width) > 0 && parseCssPixelValue(element.style.height) > 0;
|
|
6095
|
+
}
|
|
5931
6096
|
async function renderOdp(panel, arrayBuffer) {
|
|
5932
6097
|
const zip = await import_jszip3.default.loadAsync(arrayBuffer);
|
|
5933
6098
|
const content = zip.file("content.xml");
|
|
@@ -5955,34 +6120,28 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
|
5955
6120
|
const hasEntry = (path) => entries.some((entry) => entry.name.toLowerCase() === path.toLowerCase());
|
|
5956
6121
|
const contentXml = zip.file(/(^|\/)content\.xml$/i)[0];
|
|
5957
6122
|
if (hasEntry("word/document.xml")) {
|
|
5958
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Word \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 DOCX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5959
6123
|
await renderDocx(panel, arrayBuffer);
|
|
5960
6124
|
return true;
|
|
5961
6125
|
}
|
|
5962
6126
|
if (hasEntry("xl/workbook.xml")) {
|
|
5963
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Workbook \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 XLSX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5964
6127
|
await renderSheet(panel, arrayBuffer, extension);
|
|
5965
6128
|
return true;
|
|
5966
6129
|
}
|
|
5967
6130
|
if (hasEntry("ppt/presentation.xml")) {
|
|
5968
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OOXML Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 PPTX \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5969
6131
|
await renderPptx(panel, arrayBuffer);
|
|
5970
6132
|
return true;
|
|
5971
6133
|
}
|
|
5972
6134
|
if (contentXml) {
|
|
5973
6135
|
const xml = await contentXml.async("text");
|
|
5974
6136
|
if (/<office:spreadsheet\b|<table:table\b/i.test(xml)) {
|
|
5975
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Spreadsheet \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODS \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5976
6137
|
renderParsedSheets(panel, parseFlatOds(xml), `${extension.toUpperCase()} \u6587\u4EF6\u672A\u89E3\u6790\u5230\u8868\u683C\u3002`);
|
|
5977
6138
|
return true;
|
|
5978
6139
|
}
|
|
5979
6140
|
if (/<office:presentation\b|<draw:page\b/i.test(xml)) {
|
|
5980
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Presentation \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODP \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5981
6141
|
renderOpenDocumentPresentation(panel, `${extension.toUpperCase()} \u6F14\u793A\u6587\u7A3F`, xml, await extractZipImages(zip, /^Pictures\//));
|
|
5982
6142
|
return true;
|
|
5983
6143
|
}
|
|
5984
6144
|
if (/<office:text\b|<text:p\b/i.test(xml)) {
|
|
5985
|
-
renderOfficePackageNotice(panel, extension, "\u68C0\u6D4B\u5230 OpenDocument Text \u5305\u7ED3\u6784\uFF0C\u5DF2\u6309 ODT \u517C\u5BB9\u8DEF\u5F84\u9884\u89C8\u3002");
|
|
5986
6145
|
renderOpenDocumentXml(panel, `${extension.toUpperCase()} \u6587\u6863`, xml);
|
|
5987
6146
|
return true;
|
|
5988
6147
|
}
|
|
@@ -6008,14 +6167,6 @@ async function renderPackagedOfficePreview(panel, arrayBuffer, extension) {
|
|
|
6008
6167
|
}
|
|
6009
6168
|
return false;
|
|
6010
6169
|
}
|
|
6011
|
-
function renderOfficePackageNotice(panel, extension, message) {
|
|
6012
|
-
const section = createSection("\u517C\u5BB9\u5305\u8BC6\u522B");
|
|
6013
|
-
const note = document.createElement("p");
|
|
6014
|
-
note.className = "ofv-office-package-note";
|
|
6015
|
-
note.textContent = `.${extension} ${message}`;
|
|
6016
|
-
section.append(note);
|
|
6017
|
-
panel.append(section);
|
|
6018
|
-
}
|
|
6019
6170
|
function renderOfficePackageStructure(panel, extension, entries, message, metadata) {
|
|
6020
6171
|
const section = createSection("Office \u5305\u7ED3\u6784\u9884\u89C8");
|
|
6021
6172
|
const note = document.createElement("p");
|
|
@@ -6663,7 +6814,7 @@ function rtfToText(rtf) {
|
|
|
6663
6814
|
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();
|
|
6664
6815
|
}
|
|
6665
6816
|
async function readTextFromBuffer(arrayBuffer) {
|
|
6666
|
-
return
|
|
6817
|
+
return decodeTextBuffer(arrayBuffer);
|
|
6667
6818
|
}
|
|
6668
6819
|
function mimeTypeFromPath(path) {
|
|
6669
6820
|
const extension = path.split(".").pop()?.toLowerCase();
|
|
@@ -6705,7 +6856,7 @@ function ofdPlugin() {
|
|
|
6705
6856
|
try {
|
|
6706
6857
|
zip = await import_jszip4.default.loadAsync(await readArrayBuffer(ctx.file));
|
|
6707
6858
|
} catch (error) {
|
|
6708
|
-
panel.append(
|
|
6859
|
+
panel.append(createOfdFailure(ctx.file, url, error));
|
|
6709
6860
|
return {
|
|
6710
6861
|
destroy() {
|
|
6711
6862
|
panel.remove();
|
|
@@ -6722,7 +6873,7 @@ function ofdPlugin() {
|
|
|
6722
6873
|
textFragments.push(...matches);
|
|
6723
6874
|
}
|
|
6724
6875
|
} catch (error) {
|
|
6725
|
-
panel.append(
|
|
6876
|
+
panel.append(createOfdFailure(ctx.file, url, error));
|
|
6726
6877
|
return {
|
|
6727
6878
|
destroy() {
|
|
6728
6879
|
panel.remove();
|
|
@@ -6730,36 +6881,72 @@ function ofdPlugin() {
|
|
|
6730
6881
|
}
|
|
6731
6882
|
};
|
|
6732
6883
|
}
|
|
6733
|
-
const
|
|
6734
|
-
const pages = await readOfdPages(entries,
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6884
|
+
const context = await readOfdContext(entries);
|
|
6885
|
+
const pages = await readOfdPages(entries, context);
|
|
6886
|
+
let zoom = 1;
|
|
6887
|
+
let rotation = 0;
|
|
6888
|
+
const applyZoom = () => {
|
|
6889
|
+
panel.style.setProperty("--ofv-ofd-zoom", formatOfdCssNumber(zoom));
|
|
6890
|
+
ctx.toolbar?.setZoom(zoom);
|
|
6891
|
+
};
|
|
6892
|
+
const applyRotation = () => {
|
|
6893
|
+
const normalizedRotation = (rotation % 360 + 360) % 360;
|
|
6894
|
+
panel.style.setProperty("--ofv-ofd-rotation", `${normalizedRotation}deg`);
|
|
6895
|
+
panel.classList.toggle("is-ofd-rotated-sideways", normalizedRotation === 90 || normalizedRotation === 270);
|
|
6896
|
+
};
|
|
6740
6897
|
if (pages.length > 0) {
|
|
6741
6898
|
const pagesWrap = document.createElement("div");
|
|
6742
6899
|
pagesWrap.className = "ofv-ofd-pages";
|
|
6743
6900
|
for (const page of pages) {
|
|
6744
6901
|
pagesWrap.append(renderOfdPage(page));
|
|
6745
6902
|
}
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
li.textContent = entry.name;
|
|
6757
|
-
ul.append(li);
|
|
6758
|
-
}
|
|
6759
|
-
list.append(ul);
|
|
6760
|
-
panel.append(section, list);
|
|
6903
|
+
panel.append(pagesWrap);
|
|
6904
|
+
applyZoom();
|
|
6905
|
+
applyRotation();
|
|
6906
|
+
}
|
|
6907
|
+
if (pages.length === 0) {
|
|
6908
|
+
const content = document.createElement("pre");
|
|
6909
|
+
content.className = "ofv-text-block";
|
|
6910
|
+
content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
|
|
6911
|
+
panel.append(content);
|
|
6912
|
+
}
|
|
6761
6913
|
return {
|
|
6914
|
+
canCommand(command) {
|
|
6915
|
+
return pages.length > 0 && (command === "zoom-in" || command === "zoom-out" || command === "zoom-reset" || command === "rotate-right" || command === "rotate-left");
|
|
6916
|
+
},
|
|
6917
|
+
command(command) {
|
|
6918
|
+
if (pages.length === 0) {
|
|
6919
|
+
return false;
|
|
6920
|
+
}
|
|
6921
|
+
if (command === "zoom-in") {
|
|
6922
|
+
zoom = Math.min(4, zoom + 0.15);
|
|
6923
|
+
applyZoom();
|
|
6924
|
+
return true;
|
|
6925
|
+
}
|
|
6926
|
+
if (command === "zoom-out") {
|
|
6927
|
+
zoom = Math.max(0.25, zoom - 0.15);
|
|
6928
|
+
applyZoom();
|
|
6929
|
+
return true;
|
|
6930
|
+
}
|
|
6931
|
+
if (command === "zoom-reset") {
|
|
6932
|
+
zoom = 1;
|
|
6933
|
+
applyZoom();
|
|
6934
|
+
return true;
|
|
6935
|
+
}
|
|
6936
|
+
if (command === "rotate-right") {
|
|
6937
|
+
rotation += 90;
|
|
6938
|
+
applyRotation();
|
|
6939
|
+
return true;
|
|
6940
|
+
}
|
|
6941
|
+
if (command === "rotate-left") {
|
|
6942
|
+
rotation -= 90;
|
|
6943
|
+
applyRotation();
|
|
6944
|
+
return true;
|
|
6945
|
+
}
|
|
6946
|
+
return false;
|
|
6947
|
+
},
|
|
6762
6948
|
destroy() {
|
|
6949
|
+
ctx.toolbar?.setZoom(void 0);
|
|
6763
6950
|
panel.remove();
|
|
6764
6951
|
revokeObjectUrl(url, isExternal);
|
|
6765
6952
|
}
|
|
@@ -6767,76 +6954,71 @@ function ofdPlugin() {
|
|
|
6767
6954
|
}
|
|
6768
6955
|
};
|
|
6769
6956
|
}
|
|
6770
|
-
function
|
|
6771
|
-
const summary = document.createElement("div");
|
|
6772
|
-
summary.className = "ofv-ofd-summary";
|
|
6773
|
-
const xmlEntries = entries.filter((entry) => entry.name.endsWith(".xml")).length;
|
|
6774
|
-
const textCount = pages.reduce((count, page) => count + page.texts.length, 0);
|
|
6775
|
-
const pathCount = pages.reduce((count, page) => count + page.paths.length, 0);
|
|
6776
|
-
const lineCount = pages.reduce((count, page) => count + page.lines.length, 0);
|
|
6777
|
-
const imageCount = pages.reduce((count, page) => count + page.images.length, 0);
|
|
6778
|
-
const textLength = pages.reduce((count, page) => count + page.texts.reduce((inner, item) => inner + item.text.length, 0), 0);
|
|
6779
|
-
appendOfdSummary(summary, "\u6587\u4EF6", String(entries.length));
|
|
6780
|
-
appendOfdSummary(summary, "XML", String(xmlEntries));
|
|
6781
|
-
appendOfdSummary(summary, "\u9875\u9762", String(pages.length));
|
|
6782
|
-
appendOfdSummary(summary, "\u6587\u672C", String(textCount));
|
|
6783
|
-
appendOfdSummary(summary, "\u8DEF\u5F84", String(pathCount));
|
|
6784
|
-
appendOfdSummary(summary, "\u7EBF\u6761", String(lineCount));
|
|
6785
|
-
appendOfdSummary(summary, "\u56FE\u7247\u5BF9\u8C61", String(imageCount));
|
|
6786
|
-
appendOfdSummary(summary, "\u56FE\u7247\u8D44\u6E90", String(uniqueOfdImageResources(images)));
|
|
6787
|
-
if (textLength > 0) {
|
|
6788
|
-
appendOfdSummary(summary, "\u6587\u5B57\u957F\u5EA6", String(textLength));
|
|
6789
|
-
}
|
|
6790
|
-
const sizes = formatOfdPageSizes(pages);
|
|
6791
|
-
if (sizes) {
|
|
6792
|
-
appendOfdSummary(summary, "\u9875\u9762\u5C3A\u5BF8", sizes);
|
|
6793
|
-
}
|
|
6794
|
-
return summary;
|
|
6795
|
-
}
|
|
6796
|
-
function appendOfdSummary(parent, label, value) {
|
|
6797
|
-
const item = document.createElement("span");
|
|
6798
|
-
const key = document.createElement("span");
|
|
6799
|
-
key.textContent = label;
|
|
6800
|
-
const content = document.createElement("strong");
|
|
6801
|
-
content.textContent = value;
|
|
6802
|
-
item.append(key, content);
|
|
6803
|
-
parent.append(item);
|
|
6804
|
-
}
|
|
6805
|
-
function uniqueOfdImageResources(images) {
|
|
6806
|
-
return new Set(images.values()).size;
|
|
6807
|
-
}
|
|
6808
|
-
function formatOfdPageSizes(pages) {
|
|
6809
|
-
const counts = /* @__PURE__ */ new Map();
|
|
6810
|
-
for (const page of pages) {
|
|
6811
|
-
const key = `${Math.round(page.width)} x ${Math.round(page.height)}`;
|
|
6812
|
-
counts.set(key, (counts.get(key) || 0) + 1);
|
|
6813
|
-
}
|
|
6814
|
-
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([size, count]) => count > 1 ? `${size} (${count})` : size).join(", ");
|
|
6815
|
-
}
|
|
6816
|
-
async function readOfdPages(entries, images) {
|
|
6957
|
+
async function readOfdPages(entries, context) {
|
|
6817
6958
|
const pages = [];
|
|
6818
6959
|
const pageEntries = entries.filter((entry) => /(^|\/)Pages\/Page_[^/]+\/Content\.xml$/i.test(entry.name) || /(^|\/)Page_[^/]+\/Content\.xml$/i.test(entry.name)).slice(0, 80);
|
|
6819
6960
|
for (const entry of pageEntries) {
|
|
6820
6961
|
const xml = await entry.async("text");
|
|
6821
|
-
const
|
|
6962
|
+
const templates = await readPageTemplates(xml, context, entries);
|
|
6963
|
+
const page = parseOfdPage(entry.name, xml, context.images, context.fonts, templates, context.pageSize);
|
|
6822
6964
|
if (page.texts.length > 0 || page.paths.length > 0 || page.lines.length > 0 || page.images.length > 0) {
|
|
6823
6965
|
pages.push(page);
|
|
6824
6966
|
}
|
|
6825
6967
|
}
|
|
6826
6968
|
return pages;
|
|
6827
6969
|
}
|
|
6828
|
-
function
|
|
6970
|
+
async function readPageTemplates(xml, context, entries) {
|
|
6971
|
+
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
6972
|
+
if (doc.querySelector("parsererror")) {
|
|
6973
|
+
return [];
|
|
6974
|
+
}
|
|
6975
|
+
const templateIds = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Template").map((element) => getOfdAttribute(element, "TemplateID") || getOfdAttribute(element, "ID")).filter((id) => Boolean(id));
|
|
6976
|
+
const templates = [];
|
|
6977
|
+
for (const id of templateIds) {
|
|
6978
|
+
const path = context.templates.get(id);
|
|
6979
|
+
const entry = path ? findOfdEntry(entries, path) : void 0;
|
|
6980
|
+
if (entry) {
|
|
6981
|
+
templates.push(await entry.async("text"));
|
|
6982
|
+
}
|
|
6983
|
+
}
|
|
6984
|
+
return templates;
|
|
6985
|
+
}
|
|
6986
|
+
function parseOfdPage(name, xml, images, fonts, templateXmls = [], defaultPageSize) {
|
|
6829
6987
|
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
6830
6988
|
if (doc.querySelector("parsererror")) {
|
|
6831
6989
|
return { name, width: 210, height: 297, texts: [], paths: [], lines: [], images: [] };
|
|
6832
6990
|
}
|
|
6833
|
-
const pageSize = parseOfdPageSize(doc);
|
|
6834
|
-
const
|
|
6835
|
-
|
|
6836
|
-
|
|
6991
|
+
const pageSize = parseOfdPageSize(doc, defaultPageSize);
|
|
6992
|
+
const templatePages = templateXmls.map((templateXml) => {
|
|
6993
|
+
const templateDoc = new DOMParser().parseFromString(templateXml, "application/xml");
|
|
6994
|
+
return templateDoc.querySelector("parsererror") ? emptyOfdPageContent() : parseOfdPageContent(templateDoc, images, fonts);
|
|
6995
|
+
});
|
|
6996
|
+
const pageContent = parseOfdPageContent(doc, images, fonts);
|
|
6997
|
+
const texts = [...templatePages.flatMap((page) => page.texts), ...pageContent.texts];
|
|
6998
|
+
const paths = [...templatePages.flatMap((page) => page.paths), ...pageContent.paths];
|
|
6999
|
+
const lines = [...templatePages.flatMap((page) => page.lines), ...pageContent.lines];
|
|
7000
|
+
const imageObjects = [...templatePages.flatMap((page) => page.images), ...pageContent.images];
|
|
7001
|
+
if (pageSize.explicit) {
|
|
7002
|
+
return { name, width: pageSize.width, height: pageSize.height, texts, paths, lines, images: imageObjects };
|
|
7003
|
+
}
|
|
7004
|
+
const bounds = createOfdBounds(texts, paths, lines, imageObjects);
|
|
7005
|
+
const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
|
|
7006
|
+
const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
|
|
7007
|
+
return { name, width, height, texts, paths, lines, images: imageObjects };
|
|
7008
|
+
}
|
|
7009
|
+
function parseOfdPageContent(doc, images, fonts) {
|
|
7010
|
+
const textObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TextObject");
|
|
7011
|
+
const texts = textObjects.flatMap((element) => parseOfdTextObject(element, fonts));
|
|
7012
|
+
const paths = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "PathObject").flatMap((element) => parseOfdPathObject(element));
|
|
6837
7013
|
const lines = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "LineObject").flatMap((element) => parseOfdLineObject(element));
|
|
6838
7014
|
const imageObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "ImageObject").flatMap((element) => parseOfdImageObject(element, images));
|
|
6839
|
-
|
|
7015
|
+
return { texts, paths, lines, images: imageObjects };
|
|
7016
|
+
}
|
|
7017
|
+
function emptyOfdPageContent() {
|
|
7018
|
+
return { texts: [], paths: [], lines: [], images: [] };
|
|
7019
|
+
}
|
|
7020
|
+
function createOfdBounds(texts, paths, lines, imageObjects) {
|
|
7021
|
+
return [
|
|
6840
7022
|
...texts.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
|
|
6841
7023
|
...paths.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height })),
|
|
6842
7024
|
...lines.map((item) => ({
|
|
@@ -6847,38 +7029,63 @@ function parseOfdPage(name, xml, images) {
|
|
|
6847
7029
|
})),
|
|
6848
7030
|
...imageObjects.map((item) => ({ x: item.x, y: item.y, width: item.width, height: item.height }))
|
|
6849
7031
|
];
|
|
6850
|
-
const width = Math.max(pageSize.width, ...bounds.map((item) => item.x + item.width + 12));
|
|
6851
|
-
const height = Math.max(pageSize.height, ...bounds.map((item) => item.y + item.height + 12));
|
|
6852
|
-
return { name, width, height, texts, paths, lines, images: imageObjects };
|
|
6853
7032
|
}
|
|
6854
|
-
function parseOfdTextObject(element) {
|
|
7033
|
+
function parseOfdTextObject(element, fonts) {
|
|
6855
7034
|
const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
|
|
6856
7035
|
const size = finiteNumber2(getOfdAttribute(element, "Size"), Math.max(4, boundary.height || 5));
|
|
6857
7036
|
const color = parseOfdColor(element, "#111827");
|
|
6858
7037
|
const weight = finiteNumber2(getOfdAttribute(element, "Weight"), 400) >= 600 ? "700" : "400";
|
|
6859
|
-
const
|
|
7038
|
+
const fontFamily = fontStackForOfdFont(fonts.get(getOfdAttribute(element, "Font") || ""));
|
|
7039
|
+
const objectLetterSpacing = getOfdAttribute(element, "DeltaX") ? 0.5 : void 0;
|
|
6860
7040
|
const textCodes = Array.from(element.getElementsByTagName("*")).filter((child) => child.localName === "TextCode");
|
|
6861
7041
|
if (textCodes.length === 0) {
|
|
6862
7042
|
return [];
|
|
6863
7043
|
}
|
|
6864
|
-
return textCodes.
|
|
7044
|
+
return textCodes.flatMap((code) => {
|
|
6865
7045
|
const x = boundary.x + finiteNumber2(getOfdAttribute(code, "X"), 0);
|
|
6866
7046
|
const y = boundary.y + finiteNumber2(getOfdAttribute(code, "Y"), 0);
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
7047
|
+
const text = code.textContent?.trim() || "";
|
|
7048
|
+
const deltaX = parseOfdDeltaX(getOfdAttribute(code, "DeltaX"));
|
|
7049
|
+
const align = deltaX ? "start" : inferOfdTextAlign(text, boundary);
|
|
7050
|
+
const deltaY = getOfdAttribute(code, "DeltaY");
|
|
7051
|
+
if (deltaY && text.length > 1) {
|
|
7052
|
+
const step = parseOfdDeltaStep(deltaY, size);
|
|
7053
|
+
return Array.from(text).map((char, index) => ({
|
|
7054
|
+
text: char,
|
|
7055
|
+
x,
|
|
7056
|
+
y: y + index * step,
|
|
7057
|
+
width: boundary.width,
|
|
7058
|
+
height: boundary.height,
|
|
7059
|
+
size,
|
|
7060
|
+
color,
|
|
7061
|
+
weight,
|
|
7062
|
+
fontFamily,
|
|
7063
|
+
letterSpacing: objectLetterSpacing,
|
|
7064
|
+
vertical: true,
|
|
7065
|
+
align
|
|
7066
|
+
}));
|
|
7067
|
+
}
|
|
7068
|
+
return [
|
|
7069
|
+
{
|
|
7070
|
+
text,
|
|
7071
|
+
x,
|
|
7072
|
+
y,
|
|
7073
|
+
width: boundary.width,
|
|
7074
|
+
height: boundary.height,
|
|
7075
|
+
size,
|
|
7076
|
+
color,
|
|
7077
|
+
weight,
|
|
7078
|
+
fontFamily,
|
|
7079
|
+
letterSpacing: deltaX ? void 0 : objectLetterSpacing,
|
|
7080
|
+
deltaX,
|
|
7081
|
+
align
|
|
7082
|
+
}
|
|
7083
|
+
];
|
|
6878
7084
|
}).filter((item) => item.text);
|
|
6879
7085
|
}
|
|
6880
7086
|
function parseOfdPathObject(element) {
|
|
6881
7087
|
const boundary = parseBoundary(getOfdAttribute(element, "Boundary"));
|
|
7088
|
+
const ctm = parseOfdCtm(getOfdAttribute(element, "CTM"));
|
|
6882
7089
|
const commands = Array.from(element.getElementsByTagName("*")).filter(
|
|
6883
7090
|
(child) => child.localName === "AbbreviatedData" || child.localName === "PathData"
|
|
6884
7091
|
);
|
|
@@ -6893,9 +7100,10 @@ function parseOfdPathObject(element) {
|
|
|
6893
7100
|
y: boundary.y,
|
|
6894
7101
|
width: boundary.width,
|
|
6895
7102
|
height: boundary.height,
|
|
6896
|
-
stroke: parseOfdColor(element, "#
|
|
7103
|
+
stroke: parseOfdColor(element, "#111827", "StrokeColor"),
|
|
6897
7104
|
fill: parseOfdFill(element),
|
|
6898
|
-
strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
|
|
7105
|
+
strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1),
|
|
7106
|
+
transform: createOfdPathTransform(boundary.x, boundary.y, ctm)
|
|
6899
7107
|
}
|
|
6900
7108
|
];
|
|
6901
7109
|
}
|
|
@@ -6912,7 +7120,7 @@ function parseOfdLineObject(element) {
|
|
|
6912
7120
|
y1: boundary.y + start.y,
|
|
6913
7121
|
x2: boundary.x + end.x,
|
|
6914
7122
|
y2: boundary.y + end.y,
|
|
6915
|
-
stroke: parseOfdColor(element, "#
|
|
7123
|
+
stroke: parseOfdColor(element, "#111827"),
|
|
6916
7124
|
strokeWidth: finiteNumber2(getOfdAttribute(element, "LineWidth"), 1)
|
|
6917
7125
|
}
|
|
6918
7126
|
];
|
|
@@ -6934,6 +7142,8 @@ function parseOfdImageObject(element, images) {
|
|
|
6934
7142
|
function renderOfdPage(page) {
|
|
6935
7143
|
const figure = document.createElement("figure");
|
|
6936
7144
|
figure.className = "ofv-ofd-page";
|
|
7145
|
+
figure.style.setProperty("--ofv-ofd-page-width", `${formatOfdCssNumber(page.width)}mm`);
|
|
7146
|
+
figure.style.setProperty("--ofv-ofd-page-height", `${formatOfdCssNumber(page.height)}mm`);
|
|
6937
7147
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
6938
7148
|
svg.setAttribute("viewBox", `0 0 ${page.width} ${page.height}`);
|
|
6939
7149
|
svg.setAttribute("role", "img");
|
|
@@ -6970,7 +7180,7 @@ function renderOfdPage(page) {
|
|
|
6970
7180
|
for (const item of page.paths) {
|
|
6971
7181
|
const path = document.createElementNS(svg.namespaceURI, "path");
|
|
6972
7182
|
path.setAttribute("d", item.d);
|
|
6973
|
-
path.setAttribute("transform",
|
|
7183
|
+
path.setAttribute("transform", item.transform);
|
|
6974
7184
|
path.setAttribute("fill", item.fill);
|
|
6975
7185
|
path.setAttribute("stroke", item.stroke);
|
|
6976
7186
|
path.setAttribute("stroke-width", String(item.strokeWidth));
|
|
@@ -6989,22 +7199,63 @@ function renderOfdPage(page) {
|
|
|
6989
7199
|
}
|
|
6990
7200
|
for (const item of page.texts) {
|
|
6991
7201
|
const text = document.createElementNS(svg.namespaceURI, "text");
|
|
6992
|
-
text.setAttribute("x", String(item.x));
|
|
6993
|
-
text.setAttribute("y", String(item.y
|
|
7202
|
+
text.setAttribute("x", String(item.align === "end" ? item.x + item.width : item.x));
|
|
7203
|
+
text.setAttribute("y", String(item.y));
|
|
6994
7204
|
text.setAttribute("font-size", String(item.size));
|
|
6995
7205
|
text.setAttribute("fill", item.color);
|
|
6996
7206
|
text.setAttribute("font-weight", item.weight);
|
|
7207
|
+
text.setAttribute("font-family", item.fontFamily);
|
|
6997
7208
|
if (item.letterSpacing !== void 0) {
|
|
6998
7209
|
text.setAttribute("letter-spacing", String(item.letterSpacing));
|
|
6999
7210
|
}
|
|
7000
|
-
|
|
7211
|
+
if (item.deltaX && item.deltaX.length > 0 && item.align !== "end") {
|
|
7212
|
+
const chars = Array.from(item.text);
|
|
7213
|
+
let x = item.x;
|
|
7214
|
+
for (let index = 0; index < chars.length; index += 1) {
|
|
7215
|
+
const span = document.createElementNS(svg.namespaceURI, "tspan");
|
|
7216
|
+
span.setAttribute("x", String(x));
|
|
7217
|
+
span.setAttribute("y", String(item.y));
|
|
7218
|
+
if (index < chars.length - 1) {
|
|
7219
|
+
x += item.deltaX[Math.min(index, item.deltaX.length - 1)] || item.size;
|
|
7220
|
+
}
|
|
7221
|
+
span.textContent = chars[index];
|
|
7222
|
+
text.append(span);
|
|
7223
|
+
}
|
|
7224
|
+
} else {
|
|
7225
|
+
if (item.align === "end") {
|
|
7226
|
+
text.setAttribute("text-anchor", "end");
|
|
7227
|
+
}
|
|
7228
|
+
text.textContent = item.text;
|
|
7229
|
+
}
|
|
7001
7230
|
svg.append(text);
|
|
7002
7231
|
}
|
|
7003
|
-
|
|
7004
|
-
caption.textContent = `${page.name} \xB7 ${page.texts.length} text \xB7 ${page.paths.length} path \xB7 ${page.lines.length} line \xB7 ${page.images.length} image`;
|
|
7005
|
-
figure.append(svg, caption);
|
|
7232
|
+
figure.append(svg);
|
|
7006
7233
|
return figure;
|
|
7007
7234
|
}
|
|
7235
|
+
async function readOfdContext(entries) {
|
|
7236
|
+
const images = await readOfdImages(entries);
|
|
7237
|
+
const fonts = await readOfdFonts(entries);
|
|
7238
|
+
const { templates, pageSize } = await readOfdDocumentInfo(entries);
|
|
7239
|
+
return { images, templates, fonts, pageSize };
|
|
7240
|
+
}
|
|
7241
|
+
async function readOfdFonts(entries) {
|
|
7242
|
+
const fonts = /* @__PURE__ */ new Map();
|
|
7243
|
+
for (const entry of entries.filter((item) => /(?:^|\/)(?:DocumentRes|PublicRes)\.xml$/i.test(item.name))) {
|
|
7244
|
+
const xml = await entry.async("text");
|
|
7245
|
+
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
7246
|
+
if (doc.querySelector("parsererror")) {
|
|
7247
|
+
continue;
|
|
7248
|
+
}
|
|
7249
|
+
for (const font of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "Font")) {
|
|
7250
|
+
const id = getOfdAttribute(font, "ID");
|
|
7251
|
+
const name = getOfdAttribute(font, "FontName") || getOfdAttribute(font, "FamilyName");
|
|
7252
|
+
if (id && name) {
|
|
7253
|
+
fonts.set(id, name.trim());
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
return fonts;
|
|
7258
|
+
}
|
|
7008
7259
|
async function readOfdImages(entries) {
|
|
7009
7260
|
const images = /* @__PURE__ */ new Map();
|
|
7010
7261
|
for (const entry of entries.filter((item) => /\.(?:png|jpe?g|gif|bmp|webp)$/i.test(item.name)).slice(0, 80)) {
|
|
@@ -7019,17 +7270,69 @@ async function readOfdImages(entries) {
|
|
|
7019
7270
|
images.set(entry.name, href);
|
|
7020
7271
|
images.set(entry.name.split("/").pop() || entry.name, href);
|
|
7021
7272
|
}
|
|
7273
|
+
for (const entry of entries.filter((item) => /(?:^|\/)(?:DocumentRes|PublicRes)\.xml$/i.test(item.name))) {
|
|
7274
|
+
const xml = await entry.async("text");
|
|
7275
|
+
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
7276
|
+
if (doc.querySelector("parsererror")) {
|
|
7277
|
+
continue;
|
|
7278
|
+
}
|
|
7279
|
+
const baseLoc = getOfdAttribute(doc.documentElement, "BaseLoc") || "";
|
|
7280
|
+
const resourceDir = joinOfdPath(directoryName2(entry.name), baseLoc);
|
|
7281
|
+
for (const media of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "MultiMedia")) {
|
|
7282
|
+
const id = getOfdAttribute(media, "ID");
|
|
7283
|
+
const mediaFile = findOfdChild(media, "MediaFile")?.textContent?.trim();
|
|
7284
|
+
if (!id || !mediaFile) {
|
|
7285
|
+
continue;
|
|
7286
|
+
}
|
|
7287
|
+
const imageEntry = findOfdEntry(entries, joinOfdPath(resourceDir, mediaFile)) || findOfdEntry(entries, mediaFile);
|
|
7288
|
+
const href = imageEntry ? images.get(imageEntry.name) || images.get(imageEntry.name.split("/").pop() || imageEntry.name) : void 0;
|
|
7289
|
+
if (href) {
|
|
7290
|
+
images.set(id, href);
|
|
7291
|
+
}
|
|
7292
|
+
}
|
|
7293
|
+
}
|
|
7022
7294
|
return images;
|
|
7023
7295
|
}
|
|
7024
|
-
function
|
|
7296
|
+
async function readOfdDocumentInfo(entries) {
|
|
7297
|
+
const templates = /* @__PURE__ */ new Map();
|
|
7298
|
+
let pageSize;
|
|
7299
|
+
for (const entry of entries.filter((item) => /(?:^|\/)Document\.xml$/i.test(item.name))) {
|
|
7300
|
+
const xml = await entry.async("text");
|
|
7301
|
+
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
7302
|
+
if (doc.querySelector("parsererror")) {
|
|
7303
|
+
continue;
|
|
7304
|
+
}
|
|
7305
|
+
const documentDir = directoryName2(entry.name);
|
|
7306
|
+
const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PageArea")?.getElementsByTagName("*");
|
|
7307
|
+
const pageAreaBox = physicalBox ? Array.from(physicalBox).find((element) => element.localName === "PhysicalBox") : void 0;
|
|
7308
|
+
if (pageAreaBox?.textContent) {
|
|
7309
|
+
const box = parseBoundary(pageAreaBox.textContent);
|
|
7310
|
+
if (box.width > 0 && box.height > 0) {
|
|
7311
|
+
pageSize = { width: box.width, height: box.height };
|
|
7312
|
+
}
|
|
7313
|
+
}
|
|
7314
|
+
for (const template of Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TemplatePage")) {
|
|
7315
|
+
const id = getOfdAttribute(template, "ID");
|
|
7316
|
+
const baseLoc = getOfdAttribute(template, "BaseLoc");
|
|
7317
|
+
if (id && baseLoc) {
|
|
7318
|
+
templates.set(id, joinOfdPath(documentDir, baseLoc));
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
7322
|
+
return { templates, pageSize };
|
|
7323
|
+
}
|
|
7324
|
+
function parseOfdPageSize(doc, defaultPageSize) {
|
|
7325
|
+
if (defaultPageSize) {
|
|
7326
|
+
return { ...defaultPageSize, explicit: true };
|
|
7327
|
+
}
|
|
7025
7328
|
const physicalBox = Array.from(doc.getElementsByTagName("*")).find((element) => element.localName === "PhysicalBox");
|
|
7026
7329
|
if (physicalBox?.textContent) {
|
|
7027
7330
|
const box = parseBoundary(physicalBox.textContent);
|
|
7028
7331
|
if (box.width > 0 && box.height > 0) {
|
|
7029
|
-
return { width: box.width, height: box.height };
|
|
7332
|
+
return { width: box.width, height: box.height, explicit: true };
|
|
7030
7333
|
}
|
|
7031
7334
|
}
|
|
7032
|
-
return { width: 210, height: 297 };
|
|
7335
|
+
return { width: 210, height: 297, explicit: false };
|
|
7033
7336
|
}
|
|
7034
7337
|
function parseOfdColor(element, fallback, preferredLocalName = "FillColor") {
|
|
7035
7338
|
const colorElement = findOfdChild(element, preferredLocalName) || findOfdChild(element, "StrokeColor") || findOfdChild(element, "FillColor");
|
|
@@ -7060,6 +7363,101 @@ function parsePoint(value, fallback) {
|
|
|
7060
7363
|
function normalizeOfdPathData(value) {
|
|
7061
7364
|
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();
|
|
7062
7365
|
}
|
|
7366
|
+
function parseOfdCtm(value) {
|
|
7367
|
+
const parts = (value || "").trim().split(/\s+/).map((part) => Number(part));
|
|
7368
|
+
if (parts.length !== 6 || parts.some((part) => !Number.isFinite(part))) {
|
|
7369
|
+
return void 0;
|
|
7370
|
+
}
|
|
7371
|
+
return parts;
|
|
7372
|
+
}
|
|
7373
|
+
function createOfdPathTransform(x, y, ctm) {
|
|
7374
|
+
if (!ctm) {
|
|
7375
|
+
return `translate(${x} ${y})`;
|
|
7376
|
+
}
|
|
7377
|
+
const [a, b, c, d, e, f] = ctm;
|
|
7378
|
+
return `translate(${x} ${y}) matrix(${a} ${b} ${c} ${d} ${e} ${f})`;
|
|
7379
|
+
}
|
|
7380
|
+
function parseOfdDeltaStep(value, fallback) {
|
|
7381
|
+
const numbers = value.match(/-?\d+(?:\.\d+)?/g)?.map((part) => Number(part)).filter((part) => Number.isFinite(part)) || [];
|
|
7382
|
+
return numbers.length > 0 ? numbers[numbers.length - 1] : fallback;
|
|
7383
|
+
}
|
|
7384
|
+
function parseOfdDeltaX(value) {
|
|
7385
|
+
if (!value) {
|
|
7386
|
+
return void 0;
|
|
7387
|
+
}
|
|
7388
|
+
const parts = value.match(/[a-z]+|-?\d+(?:\.\d+)?/gi) || [];
|
|
7389
|
+
const deltas = [];
|
|
7390
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
7391
|
+
const token = parts[index];
|
|
7392
|
+
if (/^g$/i.test(token)) {
|
|
7393
|
+
const count = Number(parts[index + 1]);
|
|
7394
|
+
const step = Number(parts[index + 2]);
|
|
7395
|
+
if (Number.isFinite(count) && Number.isFinite(step)) {
|
|
7396
|
+
deltas.push(...Array.from({ length: Math.max(0, Math.floor(count)) }, () => step));
|
|
7397
|
+
}
|
|
7398
|
+
index += 2;
|
|
7399
|
+
continue;
|
|
7400
|
+
}
|
|
7401
|
+
const numeric = Number(token);
|
|
7402
|
+
if (Number.isFinite(numeric)) {
|
|
7403
|
+
deltas.push(numeric);
|
|
7404
|
+
}
|
|
7405
|
+
}
|
|
7406
|
+
return deltas.length > 0 ? deltas : void 0;
|
|
7407
|
+
}
|
|
7408
|
+
function fontStackForOfdFont(fontName) {
|
|
7409
|
+
const normalized = (fontName || "").trim().toLowerCase();
|
|
7410
|
+
if (normalized.includes("courier")) {
|
|
7411
|
+
return '"Courier New", Courier, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
|
|
7412
|
+
}
|
|
7413
|
+
if (normalized.includes("kaiti") || normalized.includes("kai") || normalized.includes("\u6977")) {
|
|
7414
|
+
return '"STKaiti", "Kaiti SC", "KaiTi", "\u6977\u4F53", serif';
|
|
7415
|
+
}
|
|
7416
|
+
if (normalized.includes("simsun") || normalized.includes("simsong") || normalized.includes("song") || normalized.includes("\u5B8B")) {
|
|
7417
|
+
return '"SimSong", "Songti SC", "STSong", SimSun, "\u5B8B\u4F53", serif';
|
|
7418
|
+
}
|
|
7419
|
+
if (normalized.includes("hei") || normalized.includes("\u9ED1")) {
|
|
7420
|
+
return '"PingFang SC", "Microsoft YaHei", SimHei, sans-serif';
|
|
7421
|
+
}
|
|
7422
|
+
return '"SimSong", "Songti SC", "STSong", SimSun, "Noto Serif CJK SC", serif';
|
|
7423
|
+
}
|
|
7424
|
+
function inferOfdTextAlign(text, boundary) {
|
|
7425
|
+
const normalized = text.trim();
|
|
7426
|
+
if (!/^[¥¥]?\d+(?:\.\d+)?%?$/.test(normalized)) {
|
|
7427
|
+
return "start";
|
|
7428
|
+
}
|
|
7429
|
+
if (boundary.x >= 75 || boundary.width <= 30) {
|
|
7430
|
+
return "end";
|
|
7431
|
+
}
|
|
7432
|
+
return "start";
|
|
7433
|
+
}
|
|
7434
|
+
function findOfdEntry(entries, path) {
|
|
7435
|
+
const normalized = normalizeOfdPath(path);
|
|
7436
|
+
return entries.find((entry) => normalizeOfdPath(entry.name) === normalized || normalizeOfdPath(entry.name).endsWith(`/${normalized}`));
|
|
7437
|
+
}
|
|
7438
|
+
function joinOfdPath(...parts) {
|
|
7439
|
+
const joined = parts.filter(Boolean).join("/");
|
|
7440
|
+
const segments = [];
|
|
7441
|
+
for (const segment of joined.split("/")) {
|
|
7442
|
+
if (!segment || segment === ".") {
|
|
7443
|
+
continue;
|
|
7444
|
+
}
|
|
7445
|
+
if (segment === "..") {
|
|
7446
|
+
segments.pop();
|
|
7447
|
+
continue;
|
|
7448
|
+
}
|
|
7449
|
+
segments.push(segment);
|
|
7450
|
+
}
|
|
7451
|
+
return segments.join("/");
|
|
7452
|
+
}
|
|
7453
|
+
function directoryName2(path) {
|
|
7454
|
+
const normalized = normalizeOfdPath(path);
|
|
7455
|
+
const index = normalized.lastIndexOf("/");
|
|
7456
|
+
return index >= 0 ? normalized.slice(0, index) : "";
|
|
7457
|
+
}
|
|
7458
|
+
function normalizeOfdPath(path) {
|
|
7459
|
+
return path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+/g, "/");
|
|
7460
|
+
}
|
|
7063
7461
|
function mimeTypeFromPath2(path) {
|
|
7064
7462
|
const extension = path.split(".").pop()?.toLowerCase();
|
|
7065
7463
|
const map = {
|
|
@@ -7097,6 +7495,19 @@ function finiteNumber2(value, fallback) {
|
|
|
7097
7495
|
const parsed = value === null ? Number.NaN : Number(value);
|
|
7098
7496
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
7099
7497
|
}
|
|
7498
|
+
function formatOfdCssNumber(value) {
|
|
7499
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
7500
|
+
}
|
|
7501
|
+
function createOfdFailure(file, url, error) {
|
|
7502
|
+
if (isEncryptedError(error)) {
|
|
7503
|
+
return createEncryptedFallback(file, url, {
|
|
7504
|
+
title: "OFD \u6587\u4EF6\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
7505
|
+
message: "\u8BF7\u4E0B\u8F7D\u540E\u4F7F\u7528\u672C\u5730 OFD \u9605\u8BFB\u5668\u8F93\u5165\u5BC6\u7801\u6253\u5F00\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684 OFD \u6587\u4EF6\u3002",
|
|
7506
|
+
action: "\u4E0B\u8F7D OFD"
|
|
7507
|
+
});
|
|
7508
|
+
}
|
|
7509
|
+
return createOfdFallback(file.name, url, normalizeOfdError(error));
|
|
7510
|
+
}
|
|
7100
7511
|
function createOfdFallback(fileName, url, detail) {
|
|
7101
7512
|
const fallback = document.createElement("div");
|
|
7102
7513
|
fallback.className = "ofv-fallback";
|
|
@@ -7165,7 +7576,9 @@ function archivePlugin() {
|
|
|
7165
7576
|
try {
|
|
7166
7577
|
if (ext === "zip") {
|
|
7167
7578
|
try {
|
|
7168
|
-
const zip = await import_jszip5.default.loadAsync(await readArrayBuffer(ctx.file)
|
|
7579
|
+
const zip = await import_jszip5.default.loadAsync(await readArrayBuffer(ctx.file), {
|
|
7580
|
+
decodeFileName: decodeZipFileName
|
|
7581
|
+
});
|
|
7169
7582
|
archiveEntries = Object.values(zip.files).map((entry) => ({
|
|
7170
7583
|
name: entry.name,
|
|
7171
7584
|
unsafeName: entry.unsafeOriginalName,
|
|
@@ -7174,7 +7587,7 @@ function archivePlugin() {
|
|
|
7174
7587
|
read: () => entry.async("arraybuffer")
|
|
7175
7588
|
}));
|
|
7176
7589
|
} catch (zipErr) {
|
|
7177
|
-
if (
|
|
7590
|
+
if (isEncryptedError(zipErr)) {
|
|
7178
7591
|
isEncrypted = true;
|
|
7179
7592
|
} else {
|
|
7180
7593
|
throw zipErr;
|
|
@@ -7207,17 +7620,11 @@ function archivePlugin() {
|
|
|
7207
7620
|
parseError = `\u538B\u7F29\u5305\u89E3\u6790\u5931\u8D25\uFF1A${err.message || err}`;
|
|
7208
7621
|
}
|
|
7209
7622
|
if (isEncrypted) {
|
|
7210
|
-
const fallback =
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
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";
|
|
7216
|
-
const download = document.createElement("a");
|
|
7217
|
-
download.href = url;
|
|
7218
|
-
download.download = ctx.file.name;
|
|
7219
|
-
download.textContent = "\u4E0B\u8F7D\u6587\u4EF6";
|
|
7220
|
-
fallback.append(title, meta, download);
|
|
7623
|
+
const fallback = createEncryptedFallback(ctx.file, url, {
|
|
7624
|
+
title: "\u538B\u7F29\u5305\u5DF2\u52A0\u5BC6\uFF0C\u65E0\u6CD5\u5728\u7EBF\u9884\u89C8",
|
|
7625
|
+
message: "\u8BF7\u4E0B\u8F7D\u540E\u5728\u672C\u5730\u8F93\u5165\u5BC6\u7801\u89E3\u538B\uFF0C\u6216\u4E0A\u4F20\u89E3\u5BC6\u540E\u7684\u538B\u7F29\u5305\u3002",
|
|
7626
|
+
action: "\u4E0B\u8F7D\u538B\u7F29\u5305"
|
|
7627
|
+
});
|
|
7221
7628
|
panel.append(fallback);
|
|
7222
7629
|
ctx.viewport.classList.add("ofv-center");
|
|
7223
7630
|
return {
|
|
@@ -7466,6 +7873,28 @@ async function findSubPreviewPlugin(plugins, file) {
|
|
|
7466
7873
|
}
|
|
7467
7874
|
return fallbackPlugin();
|
|
7468
7875
|
}
|
|
7876
|
+
function decodeZipFileName(bytes) {
|
|
7877
|
+
const data = Array.isArray(bytes) ? Uint8Array.from(bytes.map((value) => value.charCodeAt(0) & 255)) : bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);
|
|
7878
|
+
const utf8 = decodeZipNameWith(data, "utf-8", true);
|
|
7879
|
+
if (utf8 && !looksMojibake(utf8)) {
|
|
7880
|
+
return utf8;
|
|
7881
|
+
}
|
|
7882
|
+
const gb18030 = decodeZipNameWith(data, "gb18030", false) || decodeZipNameWith(data, "gbk", false);
|
|
7883
|
+
if (gb18030 && !looksMojibake(gb18030)) {
|
|
7884
|
+
return gb18030;
|
|
7885
|
+
}
|
|
7886
|
+
return utf8 || new TextDecoder("latin1").decode(data);
|
|
7887
|
+
}
|
|
7888
|
+
function decodeZipNameWith(bytes, encoding, fatal) {
|
|
7889
|
+
try {
|
|
7890
|
+
return new TextDecoder(encoding, { fatal }).decode(bytes);
|
|
7891
|
+
} catch {
|
|
7892
|
+
return void 0;
|
|
7893
|
+
}
|
|
7894
|
+
}
|
|
7895
|
+
function looksMojibake(value) {
|
|
7896
|
+
return /[\uFFFDÃÂÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞß]/.test(value);
|
|
7897
|
+
}
|
|
7469
7898
|
function createInlineError(titleText, detailText) {
|
|
7470
7899
|
const fallback = document.createElement("div");
|
|
7471
7900
|
fallback.className = "ofv-fallback";
|
|
@@ -7887,13 +8316,14 @@ function emailPlugin() {
|
|
|
7887
8316
|
} else {
|
|
7888
8317
|
const PostalMime = (await import("postal-mime")).default;
|
|
7889
8318
|
const parser = new PostalMime();
|
|
7890
|
-
let
|
|
8319
|
+
let rawSource = await readArrayBuffer(ctx.file);
|
|
7891
8320
|
if (ext === "mbox") {
|
|
8321
|
+
let rawText = await readTextFile(ctx.file);
|
|
7892
8322
|
const messages = splitMboxMessages(rawText);
|
|
7893
8323
|
mboxSummary = messages.map(summarizeMboxMessage);
|
|
7894
|
-
|
|
8324
|
+
rawSource = messages[0] || rawText;
|
|
7895
8325
|
}
|
|
7896
|
-
const parsed = await parser.parse(
|
|
8326
|
+
const parsed = await parser.parse(rawSource);
|
|
7897
8327
|
const from = parsed.from ? `${parsed.from.name || ""} <${parsed.from.address || ""}>`.trim() : "";
|
|
7898
8328
|
const to = Array.isArray(parsed.to) ? parsed.to.map((t) => `${t.name || ""} <${t.address || ""}>`.trim()).join("; ") : "";
|
|
7899
8329
|
const cc = Array.isArray(parsed.cc) ? parsed.cc.map((c) => `${c.name || ""} <${c.address || ""}>`.trim()).join("; ") : "";
|
|
@@ -7963,13 +8393,17 @@ function emailPlugin() {
|
|
|
7963
8393
|
const sanitizedHtml = sanitizeEmailHtml(html);
|
|
7964
8394
|
const iframe = document.createElement("iframe");
|
|
7965
8395
|
iframe.className = "ofv-email-body-iframe";
|
|
7966
|
-
iframe.setAttribute("sandbox", "allow-popups allow-popups-to-escape-sandbox");
|
|
8396
|
+
iframe.setAttribute("sandbox", "allow-same-origin allow-popups allow-popups-to-escape-sandbox");
|
|
7967
8397
|
iframe.style.cssText = "width: 100%; border: none; background: #fff; min-height: 200px;";
|
|
7968
|
-
|
|
7969
|
-
|
|
8398
|
+
let renderedHtmlBody = false;
|
|
8399
|
+
const renderHtmlBody = () => {
|
|
8400
|
+
if (renderedHtmlBody) {
|
|
8401
|
+
return;
|
|
8402
|
+
}
|
|
7970
8403
|
try {
|
|
7971
8404
|
const idoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
7972
8405
|
if (idoc) {
|
|
8406
|
+
renderedHtmlBody = true;
|
|
7973
8407
|
idoc.open();
|
|
7974
8408
|
idoc.write(`
|
|
7975
8409
|
<!doctype html>
|
|
@@ -8020,6 +8454,9 @@ function emailPlugin() {
|
|
|
8020
8454
|
console.error("Failed to write html body to email iframe:", err);
|
|
8021
8455
|
}
|
|
8022
8456
|
};
|
|
8457
|
+
iframe.addEventListener("load", renderHtmlBody, { once: true });
|
|
8458
|
+
bodySection.append(iframe);
|
|
8459
|
+
renderHtmlBody();
|
|
8023
8460
|
} else {
|
|
8024
8461
|
const pre = document.createElement("pre");
|
|
8025
8462
|
pre.className = "ofv-text-block";
|
|
@@ -9541,6 +9978,7 @@ function decodeDrawioDiagram(value) {
|
|
|
9541
9978
|
}
|
|
9542
9979
|
|
|
9543
9980
|
// src/plugins/cad.ts
|
|
9981
|
+
var import_pako3 = __toESM(require("pako"), 1);
|
|
9544
9982
|
var cadExtensions = /* @__PURE__ */ new Set([
|
|
9545
9983
|
"dxf",
|
|
9546
9984
|
"dwg",
|
|
@@ -9557,7 +9995,10 @@ var cadExtensions = /* @__PURE__ */ new Set([
|
|
|
9557
9995
|
"3dm",
|
|
9558
9996
|
"skp",
|
|
9559
9997
|
"sldprt",
|
|
9560
|
-
"sldasm"
|
|
9998
|
+
"sldasm",
|
|
9999
|
+
"gds",
|
|
10000
|
+
"oas",
|
|
10001
|
+
"oasis"
|
|
9561
10002
|
]);
|
|
9562
10003
|
var cadMimeTypes = /* @__PURE__ */ new Set([
|
|
9563
10004
|
"application/acad",
|
|
@@ -9574,7 +10015,11 @@ var cadMimeTypes = /* @__PURE__ */ new Set([
|
|
|
9574
10015
|
"application/x-parasolid",
|
|
9575
10016
|
"model/vnd.3dm",
|
|
9576
10017
|
"application/vnd.sketchup.skp",
|
|
9577
|
-
"application/sldworks"
|
|
10018
|
+
"application/sldworks",
|
|
10019
|
+
"application/vnd.gds",
|
|
10020
|
+
"application/x-gdsii",
|
|
10021
|
+
"application/vnd.oasis.layout",
|
|
10022
|
+
"application/x-oasis-layout"
|
|
9578
10023
|
]);
|
|
9579
10024
|
var cadMimeFormatMap = {
|
|
9580
10025
|
"application/acad": "dwg",
|
|
@@ -9591,7 +10036,11 @@ var cadMimeFormatMap = {
|
|
|
9591
10036
|
"application/x-parasolid": "x_t",
|
|
9592
10037
|
"model/vnd.3dm": "3dm",
|
|
9593
10038
|
"application/vnd.sketchup.skp": "skp",
|
|
9594
|
-
"application/sldworks": "sldprt"
|
|
10039
|
+
"application/sldworks": "sldprt",
|
|
10040
|
+
"application/vnd.gds": "gds",
|
|
10041
|
+
"application/x-gdsii": "gds",
|
|
10042
|
+
"application/vnd.oasis.layout": "oas",
|
|
10043
|
+
"application/x-oasis-layout": "oas"
|
|
9595
10044
|
};
|
|
9596
10045
|
function cadPlugin() {
|
|
9597
10046
|
return {
|
|
@@ -9619,6 +10068,36 @@ function cadPlugin() {
|
|
|
9619
10068
|
renderBinaryCad(panel, await readArrayBuffer(ctx.file), extension, ctx.file.name);
|
|
9620
10069
|
return { destroy: () => panel.remove() };
|
|
9621
10070
|
}
|
|
10071
|
+
if (extension === "gds") {
|
|
10072
|
+
const viewer2 = renderLayoutPreview(panel, parseGdsLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
|
|
10073
|
+
return {
|
|
10074
|
+
canCommand(command) {
|
|
10075
|
+
return viewer2.canCommand(command);
|
|
10076
|
+
},
|
|
10077
|
+
command(command) {
|
|
10078
|
+
return viewer2.command(command);
|
|
10079
|
+
},
|
|
10080
|
+
destroy() {
|
|
10081
|
+
viewer2.destroy();
|
|
10082
|
+
panel.remove();
|
|
10083
|
+
}
|
|
10084
|
+
};
|
|
10085
|
+
}
|
|
10086
|
+
if (extension === "oas" || extension === "oasis") {
|
|
10087
|
+
const viewer2 = renderLayoutPreview(panel, parseOasisLayout(new Uint8Array(await readArrayBuffer(ctx.file)), ctx.file.name), ctx);
|
|
10088
|
+
return {
|
|
10089
|
+
canCommand(command) {
|
|
10090
|
+
return viewer2.canCommand(command);
|
|
10091
|
+
},
|
|
10092
|
+
command(command) {
|
|
10093
|
+
return viewer2.command(command);
|
|
10094
|
+
},
|
|
10095
|
+
destroy() {
|
|
10096
|
+
viewer2.destroy();
|
|
10097
|
+
panel.remove();
|
|
10098
|
+
}
|
|
10099
|
+
};
|
|
10100
|
+
}
|
|
9622
10101
|
if (extension !== "dxf") {
|
|
9623
10102
|
const section = createSection("CAD \u57FA\u7840\u9884\u89C8");
|
|
9624
10103
|
section.append(`.${extension || "cad"} \u5DF2\u8BC6\u522B\u4E3A\u56FE\u7EB8/\u5DE5\u7A0B\u683C\u5F0F\uFF0C\u5F53\u524D\u524D\u7AEF\u63D2\u4EF6\u4F18\u5148\u6E32\u67D3 DXF\u3002\u8BE5\u683C\u5F0F\u5EFA\u8BAE\u63A5\u5165\u670D\u52A1\u7AEF\u8F6C\u6362\u6216 WASM \u4E13\u7528\u5F15\u64CE\u3002`);
|
|
@@ -9717,6 +10196,560 @@ function renderIfc(panel, text) {
|
|
|
9717
10196
|
section.append(table);
|
|
9718
10197
|
panel.append(section);
|
|
9719
10198
|
}
|
|
10199
|
+
var layoutPalette = [
|
|
10200
|
+
"#2563eb",
|
|
10201
|
+
"#dc2626",
|
|
10202
|
+
"#059669",
|
|
10203
|
+
"#7c3aed",
|
|
10204
|
+
"#d97706",
|
|
10205
|
+
"#0891b2",
|
|
10206
|
+
"#be123c",
|
|
10207
|
+
"#4f46e5",
|
|
10208
|
+
"#15803d",
|
|
10209
|
+
"#a16207"
|
|
10210
|
+
];
|
|
10211
|
+
var gdsRecordNames = {
|
|
10212
|
+
0: "HEADER",
|
|
10213
|
+
1: "BGNLIB",
|
|
10214
|
+
2: "LIBNAME",
|
|
10215
|
+
3: "UNITS",
|
|
10216
|
+
4: "ENDLIB",
|
|
10217
|
+
5: "BGNSTR",
|
|
10218
|
+
6: "STRNAME",
|
|
10219
|
+
7: "ENDSTR",
|
|
10220
|
+
8: "BOUNDARY",
|
|
10221
|
+
9: "PATH",
|
|
10222
|
+
10: "SREF",
|
|
10223
|
+
11: "AREF",
|
|
10224
|
+
12: "TEXT",
|
|
10225
|
+
13: "LAYER",
|
|
10226
|
+
14: "DATATYPE",
|
|
10227
|
+
15: "WIDTH",
|
|
10228
|
+
16: "XY",
|
|
10229
|
+
17: "ENDEL",
|
|
10230
|
+
18: "SNAME",
|
|
10231
|
+
22: "TEXTTYPE",
|
|
10232
|
+
25: "STRING",
|
|
10233
|
+
45: "BOX"
|
|
10234
|
+
};
|
|
10235
|
+
var oasisRecordNames = {
|
|
10236
|
+
0: "PAD",
|
|
10237
|
+
1: "START",
|
|
10238
|
+
2: "END",
|
|
10239
|
+
3: "CELLNAME",
|
|
10240
|
+
4: "CELLNAME-REF",
|
|
10241
|
+
5: "TEXTSTRING",
|
|
10242
|
+
6: "TEXTSTRING-REF",
|
|
10243
|
+
7: "PROPNAME",
|
|
10244
|
+
8: "PROPNAME-REF",
|
|
10245
|
+
9: "PROPSTRING",
|
|
10246
|
+
10: "PROPSTRING-REF",
|
|
10247
|
+
11: "LAYERNAME",
|
|
10248
|
+
12: "LAYERNAME-REF",
|
|
10249
|
+
13: "CELL",
|
|
10250
|
+
14: "XYABSOLUTE",
|
|
10251
|
+
15: "XYRELATIVE",
|
|
10252
|
+
16: "PLACEMENT",
|
|
10253
|
+
17: "PLACEMENT",
|
|
10254
|
+
18: "TEXT",
|
|
10255
|
+
19: "RECTANGLE",
|
|
10256
|
+
20: "POLYGON",
|
|
10257
|
+
21: "PATH",
|
|
10258
|
+
22: "TRAPEZOID",
|
|
10259
|
+
23: "TRAPEZOID",
|
|
10260
|
+
24: "TRAPEZOID",
|
|
10261
|
+
25: "CTRAPEZOID",
|
|
10262
|
+
26: "CIRCLE",
|
|
10263
|
+
27: "PROPERTY",
|
|
10264
|
+
28: "PROPERTY",
|
|
10265
|
+
29: "XNAME",
|
|
10266
|
+
30: "XNAME-REF",
|
|
10267
|
+
31: "XELEMENT",
|
|
10268
|
+
32: "XGEOMETRY",
|
|
10269
|
+
33: "CBLOCK"
|
|
10270
|
+
};
|
|
10271
|
+
function renderLayoutPreview(panel, data, ctx) {
|
|
10272
|
+
const section = createSection(`${data.format} \u7248\u56FE\u9884\u89C8`);
|
|
10273
|
+
const summary = document.createElement("div");
|
|
10274
|
+
summary.className = "ofv-cad-summary ofv-layout-summary";
|
|
10275
|
+
appendMeta5(summary, "\u6587\u4EF6", data.fileName);
|
|
10276
|
+
appendMeta5(summary, "\u683C\u5F0F", data.format);
|
|
10277
|
+
if (data.libraryName) {
|
|
10278
|
+
appendMeta5(summary, "\u5E93", data.libraryName);
|
|
10279
|
+
}
|
|
10280
|
+
if (data.version) {
|
|
10281
|
+
appendMeta5(summary, "\u7248\u672C", data.version);
|
|
10282
|
+
}
|
|
10283
|
+
if (data.unit) {
|
|
10284
|
+
appendMeta5(summary, "\u5355\u4F4D", data.unit);
|
|
10285
|
+
}
|
|
10286
|
+
appendMeta5(summary, "Cell", data.cells.length);
|
|
10287
|
+
appendMeta5(summary, "\u51E0\u4F55", data.shapes.length);
|
|
10288
|
+
appendMeta5(summary, "\u5F15\u7528", data.references.length);
|
|
10289
|
+
appendMeta5(summary, "\u6587\u5B57", data.labels.length);
|
|
10290
|
+
for (const [label, value] of data.metadata) {
|
|
10291
|
+
appendMeta5(summary, label, value);
|
|
10292
|
+
}
|
|
10293
|
+
section.append(summary);
|
|
10294
|
+
for (const noteText of [...data.notes, ...data.warnings]) {
|
|
10295
|
+
const note = document.createElement("p");
|
|
10296
|
+
note.className = data.warnings.includes(noteText) ? "ofv-layout-warning" : "ofv-layout-note";
|
|
10297
|
+
note.textContent = noteText;
|
|
10298
|
+
section.append(note);
|
|
10299
|
+
}
|
|
10300
|
+
const bounds = computeLayoutBounds(data.shapes, data.labels, data.references);
|
|
10301
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
10302
|
+
svg.setAttribute("class", "ofv-svg-stage ofv-layout-stage");
|
|
10303
|
+
let currentViewBox = { x: bounds.minX, y: bounds.minY, width: bounds.width, height: bounds.height };
|
|
10304
|
+
const initialViewBox = { ...currentViewBox };
|
|
10305
|
+
const applyViewBox = () => {
|
|
10306
|
+
svg.setAttribute("viewBox", `${currentViewBox.x} ${currentViewBox.y} ${currentViewBox.width} ${currentViewBox.height}`);
|
|
10307
|
+
};
|
|
10308
|
+
applyViewBox();
|
|
10309
|
+
if (data.shapes.length === 0) {
|
|
10310
|
+
const empty = document.createElementNS(svg.namespaceURI, "text");
|
|
10311
|
+
empty.setAttribute("x", String(bounds.minX + bounds.width * 0.5));
|
|
10312
|
+
empty.setAttribute("y", String(bounds.minY + bounds.height * 0.5));
|
|
10313
|
+
empty.setAttribute("text-anchor", "middle");
|
|
10314
|
+
empty.setAttribute("font-size", String(Math.max(bounds.width, bounds.height) / 34));
|
|
10315
|
+
empty.setAttribute("fill", "currentColor");
|
|
10316
|
+
empty.textContent = "\u5DF2\u8BC6\u522B\u7248\u56FE\u6587\u4EF6\uFF0C\u5F53\u524D\u6587\u4EF6\u672A\u89E3\u6790\u51FA\u53EF\u7ED8\u5236\u51E0\u4F55";
|
|
10317
|
+
svg.append(empty);
|
|
10318
|
+
}
|
|
10319
|
+
const layerIndex = new Map([...data.layers.keys()].sort((a, b) => a.localeCompare(b)).map((layer, index) => [layer, index]));
|
|
10320
|
+
for (const shape of data.shapes.slice(0, 6e3)) {
|
|
10321
|
+
const color = layoutPalette[(layerIndex.get(shape.layer) || 0) % layoutPalette.length];
|
|
10322
|
+
if (shape.kind === "path") {
|
|
10323
|
+
const polyline = document.createElementNS(svg.namespaceURI, "polyline");
|
|
10324
|
+
polyline.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
|
|
10325
|
+
polyline.setAttribute("fill", "none");
|
|
10326
|
+
polyline.setAttribute("stroke", color);
|
|
10327
|
+
polyline.setAttribute("stroke-width", String(Math.max(bounds.stroke, Math.abs(shape.width || 0))));
|
|
10328
|
+
polyline.setAttribute("stroke-linecap", "round");
|
|
10329
|
+
polyline.setAttribute("stroke-linejoin", "round");
|
|
10330
|
+
applyLayer(polyline, shape.layer);
|
|
10331
|
+
svg.append(polyline);
|
|
10332
|
+
continue;
|
|
10333
|
+
}
|
|
10334
|
+
const polygon = document.createElementNS(svg.namespaceURI, "polygon");
|
|
10335
|
+
polygon.setAttribute("points", shape.points.map(([x, y]) => `${x},${-y}`).join(" "));
|
|
10336
|
+
polygon.setAttribute("fill", color);
|
|
10337
|
+
polygon.setAttribute("fill-opacity", "0.18");
|
|
10338
|
+
polygon.setAttribute("stroke", color);
|
|
10339
|
+
polygon.setAttribute("stroke-width", String(bounds.stroke));
|
|
10340
|
+
polygon.setAttribute("vector-effect", "non-scaling-stroke");
|
|
10341
|
+
applyLayer(polygon, shape.layer);
|
|
10342
|
+
svg.append(polygon);
|
|
10343
|
+
}
|
|
10344
|
+
for (const label of data.labels.slice(0, 400)) {
|
|
10345
|
+
const text = document.createElementNS(svg.namespaceURI, "text");
|
|
10346
|
+
text.setAttribute("x", String(label.x));
|
|
10347
|
+
text.setAttribute("y", String(-label.y));
|
|
10348
|
+
text.setAttribute("font-size", String(Math.max(bounds.stroke * 12, Math.max(bounds.width, bounds.height) / 120)));
|
|
10349
|
+
text.setAttribute("fill", "currentColor");
|
|
10350
|
+
text.textContent = label.text;
|
|
10351
|
+
applyLayer(text, label.layer);
|
|
10352
|
+
svg.append(text);
|
|
10353
|
+
}
|
|
10354
|
+
const layers = [...data.layers.keys()].sort((a, b) => a.localeCompare(b));
|
|
10355
|
+
if (layers.length > 0) {
|
|
10356
|
+
section.append(createLayoutLayerControls(svg, layers, data.layers));
|
|
10357
|
+
}
|
|
10358
|
+
section.append(svg);
|
|
10359
|
+
if (data.cells.length > 0) {
|
|
10360
|
+
section.append(createLayoutCellList(data.cells, data.references));
|
|
10361
|
+
}
|
|
10362
|
+
panel.append(section);
|
|
10363
|
+
const updateToolbarZoom = () => ctx.toolbar?.setZoom(initialViewBox.width / currentViewBox.width);
|
|
10364
|
+
updateToolbarZoom();
|
|
10365
|
+
return {
|
|
10366
|
+
canCommand(command) {
|
|
10367
|
+
return command === "zoom-in" || command === "zoom-out" || command === "zoom-reset";
|
|
10368
|
+
},
|
|
10369
|
+
command(command) {
|
|
10370
|
+
if (command === "zoom-in" || command === "zoom-out") {
|
|
10371
|
+
const factor = command === "zoom-in" ? 0.82 : 1.18;
|
|
10372
|
+
const centerX = currentViewBox.x + currentViewBox.width / 2;
|
|
10373
|
+
const centerY = currentViewBox.y + currentViewBox.height / 2;
|
|
10374
|
+
currentViewBox.width *= factor;
|
|
10375
|
+
currentViewBox.height *= factor;
|
|
10376
|
+
currentViewBox.x = centerX - currentViewBox.width / 2;
|
|
10377
|
+
currentViewBox.y = centerY - currentViewBox.height / 2;
|
|
10378
|
+
applyViewBox();
|
|
10379
|
+
updateToolbarZoom();
|
|
10380
|
+
return true;
|
|
10381
|
+
}
|
|
10382
|
+
if (command === "zoom-reset") {
|
|
10383
|
+
currentViewBox = { ...initialViewBox };
|
|
10384
|
+
applyViewBox();
|
|
10385
|
+
updateToolbarZoom();
|
|
10386
|
+
return true;
|
|
10387
|
+
}
|
|
10388
|
+
return false;
|
|
10389
|
+
},
|
|
10390
|
+
destroy() {
|
|
10391
|
+
ctx.toolbar?.setZoom(void 0);
|
|
10392
|
+
}
|
|
10393
|
+
};
|
|
10394
|
+
}
|
|
10395
|
+
function parseGdsLayout(bytes, fileName) {
|
|
10396
|
+
const shapes = [];
|
|
10397
|
+
const labels = [];
|
|
10398
|
+
const references = [];
|
|
10399
|
+
const cells = [];
|
|
10400
|
+
const layers = /* @__PURE__ */ new Map();
|
|
10401
|
+
const recordCounts = /* @__PURE__ */ new Map();
|
|
10402
|
+
const warnings = [];
|
|
10403
|
+
let libraryName = "";
|
|
10404
|
+
let version = "";
|
|
10405
|
+
let unit = "";
|
|
10406
|
+
let offset = 0;
|
|
10407
|
+
let current = {};
|
|
10408
|
+
let currentKind = "";
|
|
10409
|
+
while (offset + 4 <= bytes.length) {
|
|
10410
|
+
const length = readUInt16(bytes, offset);
|
|
10411
|
+
const recordType = bytes[offset + 2];
|
|
10412
|
+
const data = bytes.slice(offset + 4, offset + length);
|
|
10413
|
+
const name = gdsRecordNames[recordType] || `0x${recordType.toString(16).padStart(2, "0")}`;
|
|
10414
|
+
recordCounts.set(name, (recordCounts.get(name) || 0) + 1);
|
|
10415
|
+
if (length < 4 || offset + length > bytes.length) {
|
|
10416
|
+
warnings.push(`GDS \u8BB0\u5F55\u5728 ${offset} \u5B57\u8282\u5904\u957F\u5EA6\u5F02\u5E38\uFF0C\u5DF2\u505C\u6B62\u89E3\u6790\u3002`);
|
|
10417
|
+
break;
|
|
10418
|
+
}
|
|
10419
|
+
if (recordType === 0 && data.length >= 2) {
|
|
10420
|
+
version = String(readUInt16(data, 0));
|
|
10421
|
+
} else if (recordType === 2) {
|
|
10422
|
+
libraryName = readGdsString(data);
|
|
10423
|
+
} else if (recordType === 3 && data.length >= 16) {
|
|
10424
|
+
unit = `${formatGdsReal(data, 0)} / ${formatGdsReal(data, 8)}`;
|
|
10425
|
+
} else if (recordType === 6) {
|
|
10426
|
+
cells.push(readGdsString(data));
|
|
10427
|
+
} else if (recordType === 8 || recordType === 9 || recordType === 45) {
|
|
10428
|
+
currentKind = recordType === 9 ? "path" : recordType === 45 ? "box" : "boundary";
|
|
10429
|
+
current = { kind: currentKind, layer: "0", datatype: "0", points: [] };
|
|
10430
|
+
} else if (recordType === 12) {
|
|
10431
|
+
currentKind = "text";
|
|
10432
|
+
current = { layer: "0", text: "", x: 0, y: 0 };
|
|
10433
|
+
} else if (recordType === 10 || recordType === 11) {
|
|
10434
|
+
currentKind = "reference";
|
|
10435
|
+
current = { cell: "", x: 0, y: 0 };
|
|
10436
|
+
} else if (recordType === 13 && data.length >= 2) {
|
|
10437
|
+
current.layer = String(readInt16(data, 0));
|
|
10438
|
+
} else if ((recordType === 14 || recordType === 22) && data.length >= 2) {
|
|
10439
|
+
current.datatype = String(readInt16(data, 0));
|
|
10440
|
+
} else if (recordType === 15 && data.length >= 4) {
|
|
10441
|
+
current.width = Math.abs(readInt32(data, 0));
|
|
10442
|
+
} else if (recordType === 16) {
|
|
10443
|
+
const points = readGdsPoints(data);
|
|
10444
|
+
if (currentKind === "text" && points[0]) {
|
|
10445
|
+
current.x = points[0][0];
|
|
10446
|
+
current.y = points[0][1];
|
|
10447
|
+
} else if (currentKind === "reference" && points[0]) {
|
|
10448
|
+
current.x = points[0][0];
|
|
10449
|
+
current.y = points[0][1];
|
|
10450
|
+
} else {
|
|
10451
|
+
current.points = points;
|
|
10452
|
+
}
|
|
10453
|
+
} else if (recordType === 18) {
|
|
10454
|
+
current.cell = readGdsString(data);
|
|
10455
|
+
} else if (recordType === 25) {
|
|
10456
|
+
current.text = readGdsString(data);
|
|
10457
|
+
} else if (recordType === 17) {
|
|
10458
|
+
if ((currentKind === "boundary" || currentKind === "path" || currentKind === "box") && current.points && current.points.length > 1) {
|
|
10459
|
+
const shape = {
|
|
10460
|
+
kind: current.kind || "boundary",
|
|
10461
|
+
layer: String(current.layer || "0"),
|
|
10462
|
+
datatype: current.datatype,
|
|
10463
|
+
points: current.points,
|
|
10464
|
+
width: current.width
|
|
10465
|
+
};
|
|
10466
|
+
shapes.push(shape);
|
|
10467
|
+
layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
|
|
10468
|
+
} else if (currentKind === "text" && current.text) {
|
|
10469
|
+
const label = {
|
|
10470
|
+
layer: String(current.layer || "0"),
|
|
10471
|
+
text: String(current.text),
|
|
10472
|
+
x: Number(current.x || 0),
|
|
10473
|
+
y: Number(current.y || 0)
|
|
10474
|
+
};
|
|
10475
|
+
labels.push(label);
|
|
10476
|
+
layers.set(label.layer, (layers.get(label.layer) || 0) + 1);
|
|
10477
|
+
} else if (currentKind === "reference" && current.cell) {
|
|
10478
|
+
references.push({ cell: String(current.cell), x: Number(current.x || 0), y: Number(current.y || 0) });
|
|
10479
|
+
}
|
|
10480
|
+
current = {};
|
|
10481
|
+
currentKind = "";
|
|
10482
|
+
}
|
|
10483
|
+
offset += length;
|
|
10484
|
+
}
|
|
10485
|
+
return {
|
|
10486
|
+
format: "GDSII",
|
|
10487
|
+
fileName,
|
|
10488
|
+
libraryName,
|
|
10489
|
+
version: version ? `Stream ${version}` : void 0,
|
|
10490
|
+
unit,
|
|
10491
|
+
cells,
|
|
10492
|
+
shapes,
|
|
10493
|
+
labels,
|
|
10494
|
+
references,
|
|
10495
|
+
layers,
|
|
10496
|
+
metadata: [
|
|
10497
|
+
["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
|
|
10498
|
+
["\u8BB0\u5F55", sumCounts(recordCounts)],
|
|
10499
|
+
["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size]
|
|
10500
|
+
],
|
|
10501
|
+
notes: [
|
|
10502
|
+
`\u5DF2\u4ECE GDSII Stream \u4E2D\u89E3\u6790 ${shapes.length} \u4E2A\u51E0\u4F55\u3001${references.length} \u4E2A cell \u5F15\u7528\u548C ${labels.length} \u6BB5\u6587\u5B57\u3002`
|
|
10503
|
+
],
|
|
10504
|
+
warnings
|
|
10505
|
+
};
|
|
10506
|
+
}
|
|
10507
|
+
function parseOasisLayout(bytes, fileName) {
|
|
10508
|
+
const chunks = extractOasisCblocks(bytes);
|
|
10509
|
+
const expanded = chunks.flatMap((chunk) => [...chunk.bytes]);
|
|
10510
|
+
const cellNames = uniqueHints([...extractAsciiRuns(bytes), ...chunks.flatMap((chunk) => extractAsciiRuns(chunk.bytes))]).filter((item) => /^[A-Za-z_][\w$.-]{1,80}$/.test(item)).filter((item) => !item.startsWith("S_"));
|
|
10511
|
+
const propertyNames = uniqueHints(chunks.flatMap((chunk) => extractAsciiRuns(chunk.bytes)).filter((item) => item.startsWith("S_")));
|
|
10512
|
+
const recordCounts = scanOasisRecordCounts(bytes);
|
|
10513
|
+
const expandedCounts = scanOasisRecordCounts(new Uint8Array(expanded));
|
|
10514
|
+
const layers = /* @__PURE__ */ new Map();
|
|
10515
|
+
const shapes = [];
|
|
10516
|
+
const labels = [];
|
|
10517
|
+
const references = [];
|
|
10518
|
+
for (const name of cellNames) {
|
|
10519
|
+
references.push({ cell: name, x: 0, y: 0 });
|
|
10520
|
+
}
|
|
10521
|
+
const pseudo = createOasisStructureShapes(cellNames, chunks.length || recordCounts.size || 1);
|
|
10522
|
+
for (const shape of pseudo) {
|
|
10523
|
+
shapes.push(shape);
|
|
10524
|
+
layers.set(shape.layer, (layers.get(shape.layer) || 0) + 1);
|
|
10525
|
+
}
|
|
10526
|
+
for (let index = 0; index < cellNames.length; index++) {
|
|
10527
|
+
labels.push({ layer: "cell", text: cellNames[index], x: 12, y: -(18 + index * 18) });
|
|
10528
|
+
}
|
|
10529
|
+
if (cellNames.length > 0) {
|
|
10530
|
+
layers.set("cell", (layers.get("cell") || 0) + cellNames.length);
|
|
10531
|
+
}
|
|
10532
|
+
const version = readOasisVersion(bytes);
|
|
10533
|
+
const cblockText = chunks.length ? `${chunks.length} \u4E2A\uFF0C\u5C55\u5F00 ${formatBytes3(chunks.reduce((sum, chunk) => sum + chunk.bytes.byteLength, 0))}` : "\u672A\u53D1\u73B0";
|
|
10534
|
+
const notes = [
|
|
10535
|
+
"OASIS \u662F\u9AD8\u538B\u7F29\u82AF\u7247\u7248\u56FE\u683C\u5F0F\uFF0C\u5F53\u524D\u7248\u672C\u63D0\u4F9B\u6D4F\u89C8\u5668\u7AEF\u8BC6\u522B\u3001CBLOCK \u89E3\u538B\u3001cell/\u5C5E\u6027\u7ED3\u6784\u548C\u8F7B\u91CF\u7ED3\u6784\u793A\u610F\uFF1B\u5B8C\u6574\u51E0\u4F55\u9AD8\u4FDD\u771F\u6E32\u67D3\u5EFA\u8BAE\u540E\u7EED\u63A5\u5165\u4E13\u7528 OASIS \u89E3\u6790\u5668\u3002"
|
|
10536
|
+
];
|
|
10537
|
+
if (propertyNames.length > 0) {
|
|
10538
|
+
notes.push(`\u8BC6\u522B\u5230\u5C5E\u6027\uFF1A${propertyNames.slice(0, 5).join("\u3001")}`);
|
|
10539
|
+
}
|
|
10540
|
+
return {
|
|
10541
|
+
format: "OASIS",
|
|
10542
|
+
fileName,
|
|
10543
|
+
version,
|
|
10544
|
+
cells: cellNames,
|
|
10545
|
+
shapes,
|
|
10546
|
+
labels,
|
|
10547
|
+
references: [],
|
|
10548
|
+
layers,
|
|
10549
|
+
metadata: [
|
|
10550
|
+
["\u5927\u5C0F", formatBytes3(bytes.byteLength)],
|
|
10551
|
+
["CBLOCK", cblockText],
|
|
10552
|
+
["\u8BB0\u5F55\u7C7B\u578B", recordCounts.size + expandedCounts.size],
|
|
10553
|
+
["\u53EF\u8BFB\u7247\u6BB5", cellNames.length + propertyNames.length]
|
|
10554
|
+
],
|
|
10555
|
+
notes,
|
|
10556
|
+
warnings: cellNames.length === 0 ? ["\u5F53\u524D OASIS \u6587\u4EF6\u672A\u63D0\u53D6\u5230 cell \u540D\u79F0\uFF0C\u53EF\u80FD\u4F7F\u7528\u4E86\u66F4\u590D\u6742\u7684\u7D22\u5F15\u6216\u52A0\u5BC6/\u538B\u7F29\u5E03\u5C40\u3002"] : []
|
|
10557
|
+
};
|
|
10558
|
+
}
|
|
10559
|
+
function computeLayoutBounds(shapes, labels, references) {
|
|
10560
|
+
const xs = [];
|
|
10561
|
+
const ys = [];
|
|
10562
|
+
for (const shape of shapes) {
|
|
10563
|
+
for (const [x, y] of shape.points) {
|
|
10564
|
+
xs.push(x);
|
|
10565
|
+
ys.push(-y);
|
|
10566
|
+
}
|
|
10567
|
+
}
|
|
10568
|
+
for (const label of labels) {
|
|
10569
|
+
xs.push(label.x, label.x + label.text.length * 12);
|
|
10570
|
+
ys.push(-label.y, -label.y - 16);
|
|
10571
|
+
}
|
|
10572
|
+
for (const reference of references) {
|
|
10573
|
+
xs.push(reference.x);
|
|
10574
|
+
ys.push(-reference.y);
|
|
10575
|
+
}
|
|
10576
|
+
const minX = Math.min(...xs, 0);
|
|
10577
|
+
const maxX = Math.max(...xs, 100);
|
|
10578
|
+
const minY = Math.min(...ys, 0);
|
|
10579
|
+
const maxY = Math.max(...ys, 100);
|
|
10580
|
+
const width = Math.max(1, maxX - minX);
|
|
10581
|
+
const height = Math.max(1, maxY - minY);
|
|
10582
|
+
const padding = Math.max(width, height) * 0.06;
|
|
10583
|
+
return {
|
|
10584
|
+
minX: minX - padding,
|
|
10585
|
+
minY: minY - padding,
|
|
10586
|
+
width: width + padding * 2,
|
|
10587
|
+
height: height + padding * 2,
|
|
10588
|
+
stroke: Math.max(width, height) / 900
|
|
10589
|
+
};
|
|
10590
|
+
}
|
|
10591
|
+
function createLayoutLayerControls(svg, layers, counts) {
|
|
10592
|
+
const controls = document.createElement("div");
|
|
10593
|
+
controls.className = "ofv-cad-layers ofv-layout-layers";
|
|
10594
|
+
const title = document.createElement("strong");
|
|
10595
|
+
title.textContent = `\u56FE\u5C42 ${layers.length}`;
|
|
10596
|
+
controls.append(title);
|
|
10597
|
+
for (const layer of layers) {
|
|
10598
|
+
const label = document.createElement("label");
|
|
10599
|
+
const checkbox = document.createElement("input");
|
|
10600
|
+
checkbox.type = "checkbox";
|
|
10601
|
+
checkbox.checked = true;
|
|
10602
|
+
checkbox.addEventListener("change", () => {
|
|
10603
|
+
for (const element of svg.querySelectorAll(`[data-layer="${escapeCssAttribute(layer)}"]`)) {
|
|
10604
|
+
element.style.display = checkbox.checked ? "" : "none";
|
|
10605
|
+
}
|
|
10606
|
+
});
|
|
10607
|
+
const name = document.createElement("span");
|
|
10608
|
+
name.textContent = `${layer} (${counts.get(layer) || 0})`;
|
|
10609
|
+
label.append(checkbox, name);
|
|
10610
|
+
controls.append(label);
|
|
10611
|
+
}
|
|
10612
|
+
return controls;
|
|
10613
|
+
}
|
|
10614
|
+
function createLayoutCellList(cells, references) {
|
|
10615
|
+
const details = document.createElement("details");
|
|
10616
|
+
details.className = "ofv-details ofv-layout-cells";
|
|
10617
|
+
details.open = true;
|
|
10618
|
+
const summary = document.createElement("summary");
|
|
10619
|
+
summary.textContent = `Cell \u7ED3\u6784 ${cells.length}`;
|
|
10620
|
+
const list = document.createElement("ul");
|
|
10621
|
+
const refCounts = countBy(references.map((reference) => reference.cell));
|
|
10622
|
+
for (const cell of cells.slice(0, 120)) {
|
|
10623
|
+
const item = document.createElement("li");
|
|
10624
|
+
const count = refCounts.get(cell) || 0;
|
|
10625
|
+
item.textContent = count > 0 ? `${cell} \xB7 \u5F15\u7528 ${count}` : cell;
|
|
10626
|
+
list.append(item);
|
|
10627
|
+
}
|
|
10628
|
+
details.append(summary, list);
|
|
10629
|
+
return details;
|
|
10630
|
+
}
|
|
10631
|
+
function readUInt16(bytes, offset) {
|
|
10632
|
+
return bytes[offset] << 8 | bytes[offset + 1];
|
|
10633
|
+
}
|
|
10634
|
+
function readInt16(bytes, offset) {
|
|
10635
|
+
const value = readUInt16(bytes, offset);
|
|
10636
|
+
return value & 32768 ? value - 65536 : value;
|
|
10637
|
+
}
|
|
10638
|
+
function readInt32(bytes, offset) {
|
|
10639
|
+
const value = bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
|
|
10640
|
+
return value | 0;
|
|
10641
|
+
}
|
|
10642
|
+
function readGdsString(bytes) {
|
|
10643
|
+
return new TextDecoder("ascii").decode(bytes).replace(/\0+$/g, "").trim();
|
|
10644
|
+
}
|
|
10645
|
+
function readGdsPoints(bytes) {
|
|
10646
|
+
const points = [];
|
|
10647
|
+
for (let offset = 0; offset + 7 < bytes.length; offset += 8) {
|
|
10648
|
+
points.push([readInt32(bytes, offset), readInt32(bytes, offset + 4)]);
|
|
10649
|
+
}
|
|
10650
|
+
return points;
|
|
10651
|
+
}
|
|
10652
|
+
function formatGdsReal(bytes, offset) {
|
|
10653
|
+
const value = readGdsReal(bytes, offset);
|
|
10654
|
+
if (!Number.isFinite(value) || value === 0) {
|
|
10655
|
+
return "0";
|
|
10656
|
+
}
|
|
10657
|
+
if (Math.abs(value) < 1e-3 || Math.abs(value) >= 1e4) {
|
|
10658
|
+
return value.toExponential(4);
|
|
10659
|
+
}
|
|
10660
|
+
return String(Number(value.toPrecision(6)));
|
|
10661
|
+
}
|
|
10662
|
+
function readGdsReal(bytes, offset) {
|
|
10663
|
+
const first = bytes[offset];
|
|
10664
|
+
if (!first) {
|
|
10665
|
+
return 0;
|
|
10666
|
+
}
|
|
10667
|
+
const sign = first & 128 ? -1 : 1;
|
|
10668
|
+
const exponent = (first & 127) - 64;
|
|
10669
|
+
let mantissa = 0;
|
|
10670
|
+
for (let index = 1; index < 8; index++) {
|
|
10671
|
+
mantissa = mantissa * 256 + bytes[offset + index];
|
|
10672
|
+
}
|
|
10673
|
+
return sign * (mantissa / Math.pow(2, 56)) * Math.pow(16, exponent);
|
|
10674
|
+
}
|
|
10675
|
+
function sumCounts(counts) {
|
|
10676
|
+
return [...counts.values()].reduce((sum, count) => sum + count, 0);
|
|
10677
|
+
}
|
|
10678
|
+
function extractOasisCblocks(bytes) {
|
|
10679
|
+
const chunks = [];
|
|
10680
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10681
|
+
const limit = Math.min(bytes.length, 25e4);
|
|
10682
|
+
for (let offset = 0; offset < limit; offset++) {
|
|
10683
|
+
try {
|
|
10684
|
+
const inflated = import_pako3.default.inflateRaw(bytes.slice(offset));
|
|
10685
|
+
if (inflated.byteLength < 4) {
|
|
10686
|
+
continue;
|
|
10687
|
+
}
|
|
10688
|
+
const ascii = extractAsciiRuns(inflated);
|
|
10689
|
+
const hasLayoutSignal = ascii.some((item) => item.startsWith("S_") || /TOP|CELL|DIE|SIZE/i.test(item));
|
|
10690
|
+
const hasRecordSignal = inflated.some((byte) => byte >= 13 && byte <= 34);
|
|
10691
|
+
if (!hasLayoutSignal && !hasRecordSignal) {
|
|
10692
|
+
continue;
|
|
10693
|
+
}
|
|
10694
|
+
const signature = `${inflated.byteLength}:${Array.from(inflated.slice(0, 12)).join(",")}`;
|
|
10695
|
+
if (seen.has(signature)) {
|
|
10696
|
+
continue;
|
|
10697
|
+
}
|
|
10698
|
+
seen.add(signature);
|
|
10699
|
+
chunks.push({ offset, bytes: inflated });
|
|
10700
|
+
if (chunks.length >= 12) {
|
|
10701
|
+
break;
|
|
10702
|
+
}
|
|
10703
|
+
} catch {
|
|
10704
|
+
}
|
|
10705
|
+
}
|
|
10706
|
+
return chunks;
|
|
10707
|
+
}
|
|
10708
|
+
function scanOasisRecordCounts(bytes) {
|
|
10709
|
+
const counts = /* @__PURE__ */ new Map();
|
|
10710
|
+
for (const byte of bytes.slice(0, Math.min(bytes.length, 12e3))) {
|
|
10711
|
+
const name = oasisRecordNames[byte];
|
|
10712
|
+
if (name) {
|
|
10713
|
+
counts.set(name, (counts.get(name) || 0) + 1);
|
|
10714
|
+
}
|
|
10715
|
+
}
|
|
10716
|
+
return counts;
|
|
10717
|
+
}
|
|
10718
|
+
function readOasisVersion(bytes) {
|
|
10719
|
+
const magic = "%SEMI-OASIS\r\n";
|
|
10720
|
+
const header = new TextDecoder("ascii").decode(bytes.slice(0, Math.min(bytes.length, 48)));
|
|
10721
|
+
if (!header.startsWith(magic)) {
|
|
10722
|
+
return void 0;
|
|
10723
|
+
}
|
|
10724
|
+
const start = magic.length;
|
|
10725
|
+
if (bytes[start] !== 1) {
|
|
10726
|
+
return "OASIS";
|
|
10727
|
+
}
|
|
10728
|
+
const length = bytes[start + 1];
|
|
10729
|
+
const version = new TextDecoder("ascii").decode(bytes.slice(start + 2, start + 2 + length));
|
|
10730
|
+
return version ? `OASIS ${version}` : "OASIS";
|
|
10731
|
+
}
|
|
10732
|
+
function createOasisStructureShapes(cellNames, fallbackCount) {
|
|
10733
|
+
const rows = Math.max(1, cellNames.length || fallbackCount);
|
|
10734
|
+
const shapes = [];
|
|
10735
|
+
for (let index = 0; index < rows; index++) {
|
|
10736
|
+
const top = -(index * 18);
|
|
10737
|
+
const height = 12;
|
|
10738
|
+
const width = 88 + Math.min((cellNames[index]?.length || 5) * 4, 90);
|
|
10739
|
+
shapes.push({
|
|
10740
|
+
kind: "box",
|
|
10741
|
+
layer: "cell",
|
|
10742
|
+
points: [
|
|
10743
|
+
[0, top],
|
|
10744
|
+
[width, top],
|
|
10745
|
+
[width, top - height],
|
|
10746
|
+
[0, top - height],
|
|
10747
|
+
[0, top]
|
|
10748
|
+
]
|
|
10749
|
+
});
|
|
10750
|
+
}
|
|
10751
|
+
return shapes;
|
|
10752
|
+
}
|
|
9720
10753
|
function renderBinaryCad(panel, arrayBuffer, extension, fileName) {
|
|
9721
10754
|
const bytes = new Uint8Array(arrayBuffer);
|
|
9722
10755
|
const section = createSection(`${extension.toUpperCase()} \u6587\u4EF6\u9884\u89C8`);
|