gds-lens 0.1.0
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/CHANGELOG.md +167 -0
- package/LICENCE.md +21 -0
- package/README.md +372 -0
- package/THIRD-PARTY-LICENSES.md +261 -0
- package/dist/esm/gds-lens.js +4731 -0
- package/dist/inline-wasm/gds-lens-engine.js +0 -0
- package/dist/inline-wasm/gds-lens-host.js +82 -0
- package/dist/inline-wasm/gds-lens-worker.js +118 -0
- package/dist/inline-wasm/gds-lens.html +29 -0
- package/dist/inline-wasm/gds-lens.js +4705 -0
- package/dist/web/gds-lens-engine.js +2 -0
- package/dist/web/gds-lens-engine.wasm +0 -0
- package/dist/web/gds-lens-host.js +82 -0
- package/dist/web/gds-lens-worker.js +118 -0
- package/dist/web/gds-lens.html +29 -0
- package/dist/web/gds-lens.js +4705 -0
- package/package.json +113 -0
- package/src/cell-search.js +88 -0
- package/src/coord-parse.js +50 -0
- package/src/engine-source.esm.js +49 -0
- package/src/engine-source.js +20 -0
- package/src/gds-lens.js +175 -0
- package/src/hosts/browser.js +156 -0
- package/src/layout-bytes.js +143 -0
- package/src/load-errors.js +64 -0
- package/src/marker-parsers.js +672 -0
- package/src/mount-target.js +23 -0
- package/src/viewer-shell.html +105 -0
- package/src/viewer.css +469 -0
- package/src/viewer.html +29 -0
- package/src/viewer.js +2688 -0
- package/src/wasm-worker.js +157 -0
- package/types/cell-search.d.ts +33 -0
- package/types/coord-parse.d.ts +11 -0
- package/types/gds-lens.d.ts +135 -0
- package/types/hosts-browser.d.ts +13 -0
- package/types/layout-bytes.d.ts +49 -0
- package/types/load-errors.d.ts +27 -0
- package/types/parsers.d.ts +84 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describeLoadFailure } from "./load-errors.js";
|
|
2
|
+
|
|
3
|
+
// Runs the parse/flatten/triangulate half of loading a layout file (GDSII or
|
|
4
|
+
// OASIS) off the main thread. Either way createGdstkModule is already in
|
|
5
|
+
// scope by the time this file's own code runs, but how it got there depends
|
|
6
|
+
// on the host (see createParseWorker in viewer.js):
|
|
7
|
+
//
|
|
8
|
+
// On an ordinary page the worker is a small blob that importScripts() both
|
|
9
|
+
// gds-lens-engine.js and this file by absolute URL, and the wasm binary is a
|
|
10
|
+
// separate file the module fetches for itself.
|
|
11
|
+
//
|
|
12
|
+
// Some hosts can do neither. A VS Code webview is the worked example: its
|
|
13
|
+
// resource protocol serves `<script src>` tags in the main document fine, but
|
|
14
|
+
// a Worker (even a blob one) can't reach it at all -- importScripts against
|
|
15
|
+
// that URL fails with a NetworkError before it even gets to CSP, and fetch()
|
|
16
|
+
// fails the same way even from the main thread, so the binary can't be
|
|
17
|
+
// fetched either. Such a host uses the inline-wasm build (-sSINGLE_FILE=1,
|
|
18
|
+
// binary embedded in the .js), concatenates its full text with this file's,
|
|
19
|
+
// and returns a Worker built from the result through the ViewerHost's
|
|
20
|
+
// createWorker() hook -- no network involved. See createWorker in
|
|
21
|
+
// hosts/browser.js for the ordinary case this replaces.
|
|
22
|
+
//
|
|
23
|
+
// createGdstkModule() instantiates the *same* wasm module used on the main
|
|
24
|
+
// thread. Its main() calls init_gl(), which fails harmlessly here (no
|
|
25
|
+
// "#glCanvas" -- no DOM at all, same as running under plain Node for
|
|
26
|
+
// headless testing) and returns before touching any DOM/GL state, so this
|
|
27
|
+
// stays a pure computation module in this context: renderer.cpp's
|
|
28
|
+
// parseGdsToLayers() does the parse/flatten/triangulate work and posts
|
|
29
|
+
// 'gdsProgress' messages directly (see report_progress() in renderer.cpp).
|
|
30
|
+
//
|
|
31
|
+
// createGdstkModule() returns a Promise -- a rejection there (or any other
|
|
32
|
+
// async failure below) would otherwise vanish as an unhandled rejection
|
|
33
|
+
// inside this Worker instead of reaching viewer.js's worker.onerror (that
|
|
34
|
+
// only fires for *synchronous* throws), leaving the main thread waiting
|
|
35
|
+
// forever with no error and no progress. Every path below explicitly
|
|
36
|
+
// posts a 'gdsResult' failure instead of letting anything fail silently.
|
|
37
|
+
// Relay console.log/error to the main thread as 'gdsLog' messages -- this
|
|
38
|
+
// worker has no DOM of its own, so viewer.js's on-screen #debugPanel is the
|
|
39
|
+
// only way these are visible without a DevTools window correctly attached to
|
|
40
|
+
// this specific webview (which has proven fiddly to get right). Args are
|
|
41
|
+
// stringified defensively since not everything passed to console.log here
|
|
42
|
+
// (e.g. Error objects, the Module object) is guaranteed structured-cloneable.
|
|
43
|
+
function safeStringify(arg) {
|
|
44
|
+
if (typeof arg === "string") return arg;
|
|
45
|
+
if (arg instanceof Error) return arg.stack || arg.message;
|
|
46
|
+
try {
|
|
47
|
+
return JSON.stringify(arg);
|
|
48
|
+
} catch {
|
|
49
|
+
return String(arg);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const originalLog = console.log.bind(console);
|
|
53
|
+
const originalError = console.error.bind(console);
|
|
54
|
+
console.log = (...args) => {
|
|
55
|
+
originalLog(...args);
|
|
56
|
+
try {
|
|
57
|
+
postMessage({type: "gdsLog", level: "log", text: args.map(safeStringify).join(" ")});
|
|
58
|
+
} catch {
|
|
59
|
+
// ignore -- best-effort relay only
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
console.error = (...args) => {
|
|
63
|
+
originalError(...args);
|
|
64
|
+
try {
|
|
65
|
+
postMessage({type: "gdsLog", level: "error", text: args.map(safeStringify).join(" ")});
|
|
66
|
+
} catch {
|
|
67
|
+
// ignore -- best-effort relay only
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
console.log("[GDS worker] script started executing, typeof createGdstkModule:", typeof createGdstkModule);
|
|
72
|
+
|
|
73
|
+
self.onerror = (msg, url, line, col, err) => {
|
|
74
|
+
console.error("[GDS worker] self.onerror:", msg, "at", url + ":" + line + ":" + col, err && err.stack);
|
|
75
|
+
};
|
|
76
|
+
self.addEventListener("unhandledrejection", (event) => {
|
|
77
|
+
console.error("[GDS worker] unhandled promise rejection inside worker:", event.reason);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Where to fetch gds-lens-engine.wasm from, for the build that keeps it separate.
|
|
81
|
+
// Emscripten would resolve it against this worker's own script URL, which is
|
|
82
|
+
// a blob: with no directory to speak of, so viewer.js passes the real one in.
|
|
83
|
+
// Absent (the inline-wasm build, or a host that assembles the worker itself)
|
|
84
|
+
// there is no binary to locate and the default is left alone.
|
|
85
|
+
function moduleArgs() {
|
|
86
|
+
const base = self.gdsLensScriptBase;
|
|
87
|
+
if (!base) return {};
|
|
88
|
+
return { locateFile: (file) => new URL(file, base).href };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log("[GDS worker] registering onmessage handler");
|
|
92
|
+
self.onmessage = (event) => {
|
|
93
|
+
const message = event.data;
|
|
94
|
+
console.log("[GDS worker] received message, type:", message.type);
|
|
95
|
+
if (message.type !== "parse") return;
|
|
96
|
+
|
|
97
|
+
console.log("[GDS worker] fileData byteLength:", message.fileData && message.fileData.byteLength);
|
|
98
|
+
console.log("[GDS worker] calling createGdstkModule()...");
|
|
99
|
+
createGdstkModule(moduleArgs()).then((Module) => {
|
|
100
|
+
console.log("[GDS worker] createGdstkModule() resolved, Module keys:", Object.keys(Module).filter(k => typeof Module[k] === "function"));
|
|
101
|
+
// Extension-less name on purpose: GDSII vs OASIS is decided by the
|
|
102
|
+
// file's own header inside the wasm (gds_common::detect_format), so
|
|
103
|
+
// nothing here has to know or plumb through which one this is.
|
|
104
|
+
console.log("[GDS worker] writing /input.layout to MEMFS...");
|
|
105
|
+
Module.FS.writeFile("/input.layout", new Uint8Array(message.fileData));
|
|
106
|
+
console.log("[GDS worker] calling Module.parseGdsToLayers('/input.layout')...");
|
|
107
|
+
const result = Module.parseGdsToLayers("/input.layout");
|
|
108
|
+
console.log("[GDS worker] parseGdsToLayers returned, ok:", result.ok, "format:", result.format, "error:", result.error);
|
|
109
|
+
Module.FS.unlink("/input.layout");
|
|
110
|
+
|
|
111
|
+
if (!result.ok) {
|
|
112
|
+
postMessage({type: "gdsResult", ok: false, error: result.error});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log("[GDS worker] layers:", result.layers.length, "instance groups:", result.instanceGroups.length, "labels:", result.totalLabels, "cells:", result.hierarchy.cellCount, "-- posting gdsResult back to main thread");
|
|
117
|
+
const transferList = [];
|
|
118
|
+
for (const layer of result.layers) {
|
|
119
|
+
transferList.push(layer.outlineVertices.buffer, layer.outlineRanges.buffer,
|
|
120
|
+
layer.fillVertices.buffer);
|
|
121
|
+
// Label text (see attach_labels in renderer.cpp). Only the
|
|
122
|
+
// top-level layer entries carry it -- an instanced cell's labels
|
|
123
|
+
// are expanded into world space during the flatten, so the
|
|
124
|
+
// per-group entries below have no text of their own.
|
|
125
|
+
transferList.push(layer.textChars.buffer, layer.textLengths.buffer,
|
|
126
|
+
layer.textOrigins.buffer, layer.textAnchors.buffer);
|
|
127
|
+
}
|
|
128
|
+
for (const group of result.instanceGroups) {
|
|
129
|
+
transferList.push(group.instances.buffer);
|
|
130
|
+
for (const layer of group.layers) {
|
|
131
|
+
transferList.push(layer.outlineVertices.buffer, layer.outlineRanges.buffer,
|
|
132
|
+
layer.fillVertices.buffer);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// hierarchy (see build_hierarchy in renderer.cpp) is plain objects and
|
|
136
|
+
// numbers -- one entry per cell in the file -- except for each row's
|
|
137
|
+
// per-placement transforms, which are Float64Arrays and so worth
|
|
138
|
+
// transferring rather than cloning. They're capped library-wide
|
|
139
|
+
// (kMaxHierarchyPlacements), so this walk is bounded and most rows
|
|
140
|
+
// (single placements) carry none at all.
|
|
141
|
+
for (const cell of result.hierarchy.cells) {
|
|
142
|
+
for (const ref of cell.refs) {
|
|
143
|
+
if (ref.placements) transferList.push(ref.placements.buffer);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
postMessage(
|
|
147
|
+
{type: "gdsResult", ok: true, layers: result.layers, instanceGroups: result.instanceGroups,
|
|
148
|
+
hierarchy: result.hierarchy, bbox: result.bbox},
|
|
149
|
+
transferList
|
|
150
|
+
);
|
|
151
|
+
console.log("[GDS worker] postMessage(gdsResult) call returned");
|
|
152
|
+
}).catch((err) => {
|
|
153
|
+
console.error("[GDS worker] createGdstkModule() chain rejected:", err, err && err.stack);
|
|
154
|
+
postMessage({type: "gdsResult", ok: false, error: describeLoadFailure(err, "Layout worker failed")});
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
console.log("[GDS worker] onmessage handler registered, script finished top-level execution");
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// `gds-lens/cell-search` -- ranking and pathfinding over a design's cell tree.
|
|
2
|
+
//
|
|
3
|
+
// Both work on the hierarchy model the wasm parse hands back.
|
|
4
|
+
|
|
5
|
+
/** One node of the cell tree. */
|
|
6
|
+
export interface CellNode {
|
|
7
|
+
name?: string;
|
|
8
|
+
/** Child placements, by index into the same array. */
|
|
9
|
+
references?: Array<{ cell: number }>;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Indices of the cells whose name contains `query`, best first: exact match,
|
|
15
|
+
* then prefix matches, then substring matches. Case-insensitive. Empty when
|
|
16
|
+
* the query is blank or nothing matches.
|
|
17
|
+
*/
|
|
18
|
+
export function rankCellMatches(cells: CellNode[], query: string): number[];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* One path of cell indices from a top cell down to `target`, root first and
|
|
22
|
+
* target last -- or `null` if no top cell reaches it.
|
|
23
|
+
*
|
|
24
|
+
* Depth-first, so it is *a* path rather than the shortest: a cell placed in
|
|
25
|
+
* twenty places has twenty paths and none is more correct. Terminates on a
|
|
26
|
+
* reference cycle, and stops descending at `maxDepth`.
|
|
27
|
+
*/
|
|
28
|
+
export function cellPathToTarget(
|
|
29
|
+
cells: CellNode[],
|
|
30
|
+
roots: number[],
|
|
31
|
+
target: number,
|
|
32
|
+
maxDepth: number,
|
|
33
|
+
): number[] | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// `gds-lens/coord-parse` -- reading a coordinate a person typed.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A coordinate pair in microns, or `null` if the text is not one.
|
|
5
|
+
*
|
|
6
|
+
* Tolerates the shapes coordinates actually arrive in: bare pairs, wrapped in
|
|
7
|
+
* brackets, comma- or space-separated, and per-number units (`nm`, `um`, `mm`)
|
|
8
|
+
* which are converted. Anything left over after the two numbers rejects the
|
|
9
|
+
* whole string rather than being half-read.
|
|
10
|
+
*/
|
|
11
|
+
export function parseCoordinatePair(text: string): { x: number; y: number } | null;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Public types for the `gds-lens` entry point: the custom element, the host
|
|
2
|
+
// interface an embedder implements, and the surface the viewer hands back.
|
|
3
|
+
//
|
|
4
|
+
// Hand-written rather than emitted, because these describe a contract that is
|
|
5
|
+
// deliberately looser than the implementation: every ViewerHost method is
|
|
6
|
+
// optional, and the viewer hides the control for anything a host leaves out.
|
|
7
|
+
// That optionality is the interesting part, and it is the part a generated
|
|
8
|
+
// declaration would get wrong.
|
|
9
|
+
|
|
10
|
+
/** A file a host picked on the viewer's behalf. */
|
|
11
|
+
export interface PickedFile {
|
|
12
|
+
name: string;
|
|
13
|
+
text: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A saved camera position, persisted by the host between sessions. */
|
|
17
|
+
export interface NamedView {
|
|
18
|
+
name: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The result of a `goToPoint`, reported back to the host. */
|
|
23
|
+
export interface GotoResult {
|
|
24
|
+
ok: boolean;
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* What the viewer can be told to do, handed to the host in `connect`.
|
|
31
|
+
*
|
|
32
|
+
* This is the push direction: the host calls these to drive the viewer,
|
|
33
|
+
* rather than answering questions the viewer asks.
|
|
34
|
+
*/
|
|
35
|
+
export interface ViewerSurface {
|
|
36
|
+
/**
|
|
37
|
+
* The element the viewer is mounted in. Bind anything of your own to this
|
|
38
|
+
* rather than to `window`, so it stays inside the component -- a listener
|
|
39
|
+
* on `window` reaches the whole embedding page.
|
|
40
|
+
*/
|
|
41
|
+
element: HTMLElement;
|
|
42
|
+
load(bytes: Uint8Array | ArrayBuffer, options?: { reload?: boolean }): void;
|
|
43
|
+
showError(message: string): void;
|
|
44
|
+
setLyp(name: string, text: string): void;
|
|
45
|
+
setMarkers(name: string, text: string): void;
|
|
46
|
+
/** Offer a reload, for when the file changed underneath. */
|
|
47
|
+
showStale(text: string): void;
|
|
48
|
+
goToPoint(x: number, y: number): void;
|
|
49
|
+
toggleDebug(): void;
|
|
50
|
+
setNamedViews(views: NamedView[]): void;
|
|
51
|
+
/** Re-ask `isLightTheme()` after a theme change. */
|
|
52
|
+
applyTheme(): void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Everything the viewer needs from whatever is embedding it. Install as
|
|
57
|
+
* `window.gdsLensHost` before the element script runs.
|
|
58
|
+
*
|
|
59
|
+
* Every method is optional: a missing one is not an error, it means the
|
|
60
|
+
* embedder does not offer that service, and the viewer removes the control
|
|
61
|
+
* for it. A read-only embed can implement almost none of this.
|
|
62
|
+
*/
|
|
63
|
+
export interface ViewerHost {
|
|
64
|
+
/** `null` means the user cancelled. */
|
|
65
|
+
pickLyp?(): Promise<PickedFile | null> | PickedFile | null;
|
|
66
|
+
unloadLyp?(): void;
|
|
67
|
+
pickMarkers?(): Promise<PickedFile | null> | PickedFile | null;
|
|
68
|
+
unloadMarkers?(): void;
|
|
69
|
+
/** Called once at mount, for saved camera positions. */
|
|
70
|
+
loadViews?(): Promise<NamedView[]> | NamedView[];
|
|
71
|
+
saveViews?(views: NamedView[]): void;
|
|
72
|
+
/** `existing` is the names already in use; `null` means cancelled. */
|
|
73
|
+
promptViewName?(existing: string[]): Promise<string | null> | string | null;
|
|
74
|
+
requestReload?(): void;
|
|
75
|
+
setAutoReload?(on: boolean): void;
|
|
76
|
+
onGotoResult?(result: GotoResult): void;
|
|
77
|
+
/** Defaults to the OS preference when not implemented. */
|
|
78
|
+
isLightTheme?(): boolean;
|
|
79
|
+
/** Override where the payload's scripts cannot be fetched by URL. */
|
|
80
|
+
createWorker?(): Worker;
|
|
81
|
+
/** Called at mount, handing over the push-direction surface. */
|
|
82
|
+
connect?(viewer: ViewerSurface): void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Accepted by `load`: a URL to fetch, or bytes you already have. */
|
|
86
|
+
export type LayoutSource = string | Uint8Array | ArrayBuffer;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The `<gds-lens>` element. Importing `gds-lens` registers it; the engine,
|
|
90
|
+
* the wasm module and the WebGL context are all deferred until an element
|
|
91
|
+
* actually connects.
|
|
92
|
+
*
|
|
93
|
+
* `display: block` with no intrinsic height, so give it one.
|
|
94
|
+
*/
|
|
95
|
+
export declare class GdsLens extends HTMLElement {
|
|
96
|
+
/**
|
|
97
|
+
* Resolves once the engine has mounted. Every method below awaits this,
|
|
98
|
+
* so it is rarely needed directly. Rejects if the element is not
|
|
99
|
+
* connected.
|
|
100
|
+
*/
|
|
101
|
+
readonly ready: Promise<ViewerSurface>;
|
|
102
|
+
|
|
103
|
+
/** `options.reload` keeps the current camera and layer visibility. */
|
|
104
|
+
load(source: LayoutSource, options?: { reload?: boolean }): Promise<void>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Centres on a coordinate in microns and flashes a crosshair. Resolves
|
|
108
|
+
* `true` if the point is inside the layout.
|
|
109
|
+
*/
|
|
110
|
+
goToPoint(x: number, y: number): Promise<boolean>;
|
|
111
|
+
|
|
112
|
+
/** Applies a `.lyp` layer-properties file. Pass `""` to clear. */
|
|
113
|
+
setLyp(name: string, text: string): Promise<void>;
|
|
114
|
+
|
|
115
|
+
/** Applies a marker database; the format is sniffed from the content. */
|
|
116
|
+
setMarkers(name: string, text: string): Promise<void>;
|
|
117
|
+
|
|
118
|
+
/** Replaces the view with an error message. */
|
|
119
|
+
showError(message: string): Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
declare global {
|
|
123
|
+
interface HTMLElementTagNameMap {
|
|
124
|
+
"gds-lens": GdsLens;
|
|
125
|
+
}
|
|
126
|
+
interface Window {
|
|
127
|
+
/** Install before the element script runs to replace the default host. */
|
|
128
|
+
gdsLensHost?: ViewerHost;
|
|
129
|
+
/**
|
|
130
|
+
* Published by the default browser host (not by the element), so a
|
|
131
|
+
* plain page can drive the viewer from a script tag or the console.
|
|
132
|
+
*/
|
|
133
|
+
gdsLens?: ViewerSurface;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// `gds-lens/hosts/browser` -- the default ViewerHost, for a plain web page.
|
|
2
|
+
|
|
3
|
+
import type { ViewerHost } from "./gds-lens.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A host implemented for an ordinary page: `<input type=file>` for the
|
|
7
|
+
* pickers, `localStorage` for saved views, `prompt()` for a name, and three
|
|
8
|
+
* ways in for a layout -- `?src=`, drag-and-drop on the element, and a direct
|
|
9
|
+
* call through `window.gdsLens`.
|
|
10
|
+
*
|
|
11
|
+
* Installs itself as `window.gdsLensHost` on import if nothing else has.
|
|
12
|
+
*/
|
|
13
|
+
export function createBrowserHost(): ViewerHost;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// `gds-lens/layout-bytes` -- gzip in front of the parser.
|
|
2
|
+
//
|
|
3
|
+
// The result is a discriminated union rather than a throw: "too large for
|
|
4
|
+
// this machine" and "the file is broken" need different things said about
|
|
5
|
+
// them, and the caller is the one with the wording.
|
|
6
|
+
|
|
7
|
+
export interface DecodedLayout {
|
|
8
|
+
ok: true;
|
|
9
|
+
bytes: Uint8Array;
|
|
10
|
+
gzipped: boolean;
|
|
11
|
+
/** gzip's ISIZE trailer, or `null` when the input was too short. */
|
|
12
|
+
storedSize: number | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FailedLayout {
|
|
16
|
+
ok: false;
|
|
17
|
+
/** `"too-large"` is this machine's limit; `"corrupt"` is the file's. */
|
|
18
|
+
reason: "too-large" | "corrupt";
|
|
19
|
+
storedSize: number | null;
|
|
20
|
+
/** The cap that was enforced, so a message can quote it. */
|
|
21
|
+
limit: number;
|
|
22
|
+
detail: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** gzip's magic number (RFC 1952 ID1/ID2). Content, not extension. */
|
|
26
|
+
export function looksGzipped(bytes: Uint8Array | null | undefined): boolean;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The uncompressed size gzip records in its trailer, or `null` if the input is
|
|
30
|
+
* too short to hold one.
|
|
31
|
+
*
|
|
32
|
+
* A hint about the file rather than a fact about it: it is stored modulo 2^32,
|
|
33
|
+
* and for a multi-member file it describes only the last member. Never use it
|
|
34
|
+
* to enforce a limit -- `decodeLayoutBytes` counts actual output instead.
|
|
35
|
+
*/
|
|
36
|
+
export function gzipStoredSize(bytes: Uint8Array | null | undefined): number | null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Uncompressed layout bytes, whatever the input was. Non-gzipped input is
|
|
40
|
+
* passed straight back, untouched and uncopied.
|
|
41
|
+
*
|
|
42
|
+
* `maxBytes` caps what a compressed file is allowed to expand to, enforced
|
|
43
|
+
* against the running output total rather than the trailer's claim, so a
|
|
44
|
+
* decompression bomb stops at the cap. Pass a non-finite value for no cap.
|
|
45
|
+
*/
|
|
46
|
+
export function decodeLayoutBytes(
|
|
47
|
+
bytes: Uint8Array,
|
|
48
|
+
maxBytes?: number,
|
|
49
|
+
): Promise<DecodedLayout | FailedLayout>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// `gds-lens/load-errors` -- turning engine-level failures into text a layout
|
|
2
|
+
// engineer can act on.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A human-readable explanation of a load failure.
|
|
6
|
+
*
|
|
7
|
+
* Running out of room in the 32-bit wasm heap surfaces as one of a handful of
|
|
8
|
+
* unhelpful strings depending on which allocation happened to fail; they all
|
|
9
|
+
* mean the same thing, and all get the same explanation. `prefix` labels
|
|
10
|
+
* anything else with where it came from, and is ignored for out-of-memory,
|
|
11
|
+
* since that is about the layout rather than the component that noticed.
|
|
12
|
+
*/
|
|
13
|
+
export function describeLoadFailure(err: unknown, prefix?: string): string;
|
|
14
|
+
|
|
15
|
+
/** Whether a failure is the wasm heap running out, however it surfaced. */
|
|
16
|
+
export function isOutOfMemory(err: unknown): boolean;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Wording for a failed gzip expansion -- the `{ ok: false }` result from
|
|
20
|
+
* `decodeLayoutBytes`. Returns `""` for a successful one.
|
|
21
|
+
*
|
|
22
|
+
* The two reasons get different prose: `"too-large"` is about this machine's
|
|
23
|
+
* limit, `"corrupt"` about the file being truncated or half-written.
|
|
24
|
+
*/
|
|
25
|
+
export function describeDecodeFailure(
|
|
26
|
+
result: { ok: boolean; reason?: string; storedSize?: number | null; limit?: number; detail?: string },
|
|
27
|
+
): string;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// `gds-lens/parsers` -- DRC/LVS marker databases, normalized.
|
|
2
|
+
//
|
|
3
|
+
// Pure JavaScript: no DOM, no WebAssembly. The XML parser is passed in as a
|
|
4
|
+
// constructor argument rather than imported, so this runs in Node (with
|
|
5
|
+
// @xmldom/xmldom) and in a browser (with the built-in DOMParser) unchanged.
|
|
6
|
+
|
|
7
|
+
/** A `DOMParser` constructor -- the platform's, or @xmldom/xmldom's. */
|
|
8
|
+
export type DOMParserConstructor = new () => {
|
|
9
|
+
parseFromString(text: string, type: string): Document;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export interface MarkerBBox {
|
|
13
|
+
minX: number;
|
|
14
|
+
minY: number;
|
|
15
|
+
maxX: number;
|
|
16
|
+
maxY: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One marker. Coordinates are in µm, y-up world space.
|
|
21
|
+
*
|
|
22
|
+
* Items with no geometry (float or text values) keep the raw value in `note`,
|
|
23
|
+
* have `bbox === null`, and draw nothing.
|
|
24
|
+
*/
|
|
25
|
+
export interface MarkerItem {
|
|
26
|
+
/** Globally unique; equals the index in category-major order. */
|
|
27
|
+
id: number;
|
|
28
|
+
/** Short label for the list row. */
|
|
29
|
+
label: string;
|
|
30
|
+
/** Non-geometry values, multiplicity, cell reference. */
|
|
31
|
+
note: string;
|
|
32
|
+
/** One packed `(x, y, ...)` array per ring. */
|
|
33
|
+
polygons: Float64Array[];
|
|
34
|
+
/** Packed segments: `(x0, y0, x1, y1, ...)`. */
|
|
35
|
+
edges: Float64Array;
|
|
36
|
+
/** `null` when the item has no geometry. */
|
|
37
|
+
bbox: MarkerBBox | null;
|
|
38
|
+
/** True for a `WE<n>` waiver record. */
|
|
39
|
+
waived: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface MarkerCategory {
|
|
43
|
+
/** Full path; `.`-joined for lyrdb nesting. */
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
items: MarkerItem[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The normalized model both parsers emit. */
|
|
50
|
+
export interface MarkerModel {
|
|
51
|
+
/** `""` when the file does not say. */
|
|
52
|
+
topCell: string;
|
|
53
|
+
warnings: string[];
|
|
54
|
+
categories: MarkerCategory[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Geometry repacked per item id, ready to hand to the renderer. */
|
|
58
|
+
export interface FlatMarkerModel {
|
|
59
|
+
categories: MarkerCategory[];
|
|
60
|
+
itemCategory: Int32Array;
|
|
61
|
+
itemBBoxes: Float64Array;
|
|
62
|
+
polyVerts: Float64Array;
|
|
63
|
+
polyVertCounts: Int32Array;
|
|
64
|
+
polyItemIds: Int32Array;
|
|
65
|
+
edgeVerts: Float64Array;
|
|
66
|
+
edgeItemIds: Int32Array;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Decided by content, not by extension. `null` when unrecognized. */
|
|
70
|
+
export function sniffMarkerFormat(text: string): "lyrdb" | "drc" | null;
|
|
71
|
+
|
|
72
|
+
/** Parses a whitespace-separated coordinate list into a packed array. */
|
|
73
|
+
export function parsePointList(text: string): Float64Array;
|
|
74
|
+
|
|
75
|
+
/** Parses a KLayout `.lyrdb` report database. Throws if it is not one. */
|
|
76
|
+
export function parseLyrdb(text: string, DOMParserCtor: DOMParserConstructor): MarkerModel;
|
|
77
|
+
|
|
78
|
+
/** Parses an ASCII DRC results database. */
|
|
79
|
+
export function parseDrcAscii(text: string): MarkerModel;
|
|
80
|
+
|
|
81
|
+
/** Dispatches on `sniffMarkerFormat`. Throws on an unrecognized format. */
|
|
82
|
+
export function parseMarkerFile(text: string, DOMParserCtor: DOMParserConstructor): MarkerModel;
|
|
83
|
+
|
|
84
|
+
export function flattenMarkerModel(model: MarkerModel): FlatMarkerModel;
|