@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 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
- stage.setPointerCapture(event.pointerId);
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 onPointerUp = (event) => {
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 (stage.hasPointerCapture(event.pointerId)) {
1875
- stage.releasePointerCapture(event.pointerId);
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.text();
4119
+ return decodeTextBuffer(await response.arrayBuffer());
3988
4120
  }
3989
4121
  if (source instanceof Blob) {
3990
- return source.text();
4122
+ return decodeTextBuffer(await source.arrayBuffer());
3991
4123
  }
3992
4124
  if (source instanceof ArrayBuffer) {
3993
- return new TextDecoder().decode(source);
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 = createPdfFallback(ctx.file.name, url, normalizePdfError(error));
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
- section.append(content);
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 new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
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(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
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(createOfdFallback(ctx.file.name, url, normalizeOfdError(error)));
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 images = await readOfdImages(entries);
6734
- const pages = await readOfdPages(entries, images);
6735
- const section = createSection("OFD \u57FA\u7840\u9884\u89C8");
6736
- const note = document.createElement("p");
6737
- note.textContent = pages.length > 0 ? "\u5F53\u524D\u7248\u672C\u63D0\u53D6 OFD \u9875\u9762\u6587\u672C\u3001\u8DEF\u5F84\u3001\u76F4\u7EBF\u548C\u56FE\u7247\u5BF9\u8C61\uFF0C\u5E76\u6309 Boundary \u5750\u6807\u751F\u6210\u8F7B\u91CF SVG \u7248\u5F0F\u9884\u89C8\u3002\u590D\u6742\u5B57\u4F53\u3001\u7B7E\u7AE0\u548C\u989C\u8272\u7A7A\u95F4\u53EF\u540E\u7EED\u63A5\u5165\u4E13\u7528 OFD \u6E32\u67D3\u5668\u3002" : "\u5F53\u524D\u7248\u672C\u63D0\u53D6 OFD \u5305\u5185 XML \u6587\u672C\u548C\u6587\u4EF6\u7ED3\u6784\u3002\u7248\u5F0F\u7EA7\u6E32\u67D3\u53EF\u5728\u540E\u7EED\u63A5\u5165\u4E13\u7528 OFD \u6E32\u67D3\u5668\u3002";
6738
- section.append(note);
6739
- section.append(createOfdSummary(entries, pages, images));
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
- section.append(pagesWrap);
6747
- }
6748
- const content = document.createElement("pre");
6749
- content.className = "ofv-text-block";
6750
- content.textContent = textFragments.slice(0, 300).join("\n") || "\u672A\u63D0\u53D6\u5230\u53EF\u8BFB\u6587\u672C\u3002";
6751
- section.append(content);
6752
- const list = createSection(`\u6587\u4EF6\u7ED3\u6784 ${entries.length}`);
6753
- const ul = document.createElement("ul");
6754
- for (const entry of entries.slice(0, 200)) {
6755
- const li = document.createElement("li");
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 createOfdSummary(entries, pages, images) {
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 page = parseOfdPage(entry.name, xml, images);
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 parseOfdPage(name, xml, images) {
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 textObjects = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "TextObject");
6835
- const texts = textObjects.flatMap((element) => parseOfdTextObject(element));
6836
- const paths = Array.from(doc.getElementsByTagName("*")).filter((element) => element.localName === "PathObject").flatMap((element) => parseOfdPathObject(element));
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
- const bounds = [
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 letterSpacing = getOfdAttribute(element, "DeltaX") ? 0.5 : void 0;
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.map((code) => {
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
- return {
6868
- text: code.textContent?.trim() || "",
6869
- x,
6870
- y,
6871
- width: boundary.width,
6872
- height: boundary.height,
6873
- size,
6874
- color,
6875
- weight,
6876
- letterSpacing
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, "#334155", "StrokeColor"),
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, "#334155"),
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", `translate(${item.x} ${item.y})`);
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 + item.size));
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
- text.textContent = item.text;
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
- const caption = document.createElement("figcaption");
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 parseOfdPageSize(doc) {
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 (zipErr.message && (zipErr.message.includes("encrypted") || zipErr.message.includes("password") || zipErr.message.includes("protected"))) {
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 = document.createElement("div");
7211
- fallback.className = "ofv-fallback";
7212
- const title = document.createElement("strong");
7213
- title.textContent = "\u8BE5\u538B\u7F29\u5305\u5DF2\u88AB\u52A0\u5BC6\u4FDD\u62A4";
7214
- const meta = document.createElement("span");
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 rawText = await readTextFile(ctx.file);
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
- rawText = messages[0] || rawText;
8324
+ rawSource = messages[0] || rawText;
7895
8325
  }
7896
- const parsed = await parser.parse(rawText);
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
- bodySection.append(iframe);
7969
- iframe.onload = () => {
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`);