@file-viewer/renderer-eda 2.0.11

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.
@@ -0,0 +1,80 @@
1
+ import { type EdaLayoutPreview } from '@file-viewer/eda-layout';
2
+ export type { EdaLayoutElement, EdaLayoutPreview, } from '@file-viewer/eda-layout';
3
+ export type EdaFileType = 'olb' | 'dra' | 'gds' | 'oas' | 'oasis';
4
+ export type EdaParserMode = 'cfb' | 'binary';
5
+ export type EdaStreamKind = 'text' | 'binary' | 'storage';
6
+ export type EdaDomainRole = 'root' | 'library' | 'symbol' | 'footprint' | 'padstack' | 'drawing' | 'metadata' | 'property' | 'geometry' | 'net' | 'unknown';
7
+ export type EdaDiagnosticLevel = 'info' | 'warning';
8
+ export interface EdaProperty {
9
+ key: string;
10
+ value: string;
11
+ source: string;
12
+ }
13
+ export interface EdaStreamView {
14
+ path: string;
15
+ name: string;
16
+ size: number;
17
+ kind: EdaStreamKind;
18
+ role: EdaDomainRole;
19
+ sample?: string;
20
+ hex?: string;
21
+ strings: string[];
22
+ properties: EdaProperty[];
23
+ }
24
+ export interface EdaTreeNode {
25
+ id: string;
26
+ path: string;
27
+ name: string;
28
+ kind: EdaStreamKind;
29
+ role: EdaDomainRole;
30
+ size: number;
31
+ children: EdaTreeNode[];
32
+ }
33
+ export interface EdaEntity {
34
+ id: string;
35
+ name: string;
36
+ role: EdaDomainRole;
37
+ path: string;
38
+ streamCount: number;
39
+ byteLength: number;
40
+ properties: EdaProperty[];
41
+ pins: string[];
42
+ layers: string[];
43
+ keywords: string[];
44
+ description?: string;
45
+ footprint?: string;
46
+ }
47
+ export interface EdaStats {
48
+ textStreams: number;
49
+ binaryStreams: number;
50
+ storageEntries: number;
51
+ propertyCount: number;
52
+ stringCount: number;
53
+ symbolCount: number;
54
+ footprintCount: number;
55
+ padstackCount: number;
56
+ confidence: 'high' | 'medium' | 'low';
57
+ }
58
+ export interface EdaDiagnostic {
59
+ level: EdaDiagnosticLevel;
60
+ code: string;
61
+ message: string;
62
+ }
63
+ export interface EdaParseResult {
64
+ type: EdaFileType;
65
+ parser: EdaParserMode;
66
+ title: string;
67
+ byteLength: number;
68
+ streamCount: number;
69
+ totalStreamBytes: number;
70
+ streams: EdaStreamView[];
71
+ tree: EdaTreeNode[];
72
+ entities: EdaEntity[];
73
+ metadata: EdaProperty[];
74
+ strings: string[];
75
+ warnings: string[];
76
+ diagnostics: EdaDiagnostic[];
77
+ stats: EdaStats;
78
+ layout?: EdaLayoutPreview;
79
+ }
80
+ export declare const parseEdaFile: (buffer: ArrayBuffer, type?: string) => Promise<EdaParseResult>;
@@ -0,0 +1,489 @@
1
+ import { inspectOasisLayout, parseGdsLayout, parseOasisTextLayout, } from '@file-viewer/eda-layout';
2
+ import { cleanupOrcadText as cleanupText, collectOrcadStrings as collectStrings, createOrcadHexPreview as hexPreview, decodeOrcadSample as decodeSample, isOrcadCompoundFile, looksLikeOrcadText as looksLikeText, } from '@file-viewer/eda-orcad';
3
+ const MAX_STREAMS = 200;
4
+ const MAX_STREAM_STRINGS = 24;
5
+ const MAX_PROPERTIES = 420;
6
+ const toBytes = (buffer) => new Uint8Array(buffer);
7
+ const normalizeBytes = (value) => {
8
+ return value instanceof Uint8Array ? value : new Uint8Array(value);
9
+ };
10
+ const normalizeSearchText = (value) => {
11
+ return cleanupText(value).toLowerCase();
12
+ };
13
+ const lastPathPart = (path) => {
14
+ const parts = path.split('/').filter(Boolean);
15
+ return parts[parts.length - 1] || path || '/';
16
+ };
17
+ const stripExtension = (value) => {
18
+ return value.replace(/\.[a-z0-9]+$/i, '');
19
+ };
20
+ const uniquePush = (target, value, max = Number.POSITIVE_INFINITY) => {
21
+ const cleaned = cleanupText(value);
22
+ if (!cleaned || target.includes(cleaned) || target.length >= max) {
23
+ return;
24
+ }
25
+ target.push(cleaned);
26
+ };
27
+ const PROPERTY_RE = /^\s*([A-Za-z][A-Za-z0-9_. /#-]{1,56})\s*[:=]\s*(.{1,240})\s*$/;
28
+ const INLINE_PROPERTY_RE = /\b([A-Za-z][A-Za-z0-9_. /#-]{1,56})\s*=\s*([^;\n\r|]{1,240})/g;
29
+ const normalizePropertyKey = (key) => {
30
+ return cleanupText(key).replace(/\s+/g, ' ');
31
+ };
32
+ const addProperty = (target, seen, key, value, source) => {
33
+ if (target.length >= MAX_PROPERTIES) {
34
+ return;
35
+ }
36
+ const normalizedKey = normalizePropertyKey(key);
37
+ const normalizedValue = cleanupText(value);
38
+ if (!normalizedKey || !normalizedValue) {
39
+ return;
40
+ }
41
+ const identity = `${source}\u0000${normalizedKey.toLowerCase()}\u0000${normalizedValue}`;
42
+ if (seen.has(identity)) {
43
+ return;
44
+ }
45
+ seen.add(identity);
46
+ target.push({ key: normalizedKey, value: normalizedValue, source });
47
+ };
48
+ const extractProperties = (text, strings, source) => {
49
+ const properties = [];
50
+ const seen = new Set();
51
+ const chunks = [text, ...strings].filter(Boolean);
52
+ chunks.forEach(chunk => {
53
+ cleanupText(chunk)
54
+ .split(/\n|[|;]/)
55
+ .forEach(line => {
56
+ const match = line.match(PROPERTY_RE);
57
+ if (match) {
58
+ addProperty(properties, seen, match[1], match[2], source);
59
+ }
60
+ });
61
+ for (const match of chunk.matchAll(INLINE_PROPERTY_RE)) {
62
+ addProperty(properties, seen, match[1], match[2], source);
63
+ }
64
+ });
65
+ return properties;
66
+ };
67
+ const hasProperty = (properties, keys) => {
68
+ const normalized = keys.map(key => key.toLowerCase());
69
+ return properties.some(property => normalized.includes(property.key.toLowerCase()));
70
+ };
71
+ const getPropertyValue = (properties, keys) => {
72
+ const normalized = keys.map(key => key.toLowerCase());
73
+ return properties.find(property => normalized.includes(property.key.toLowerCase()))?.value;
74
+ };
75
+ const roleFromText = (type, path, name, sample, strings, properties, kind) => {
76
+ const haystack = normalizeSearchText(`${path}\n${name}\n${sample}\n${strings.join('\n')}`);
77
+ if (haystack === '/' || path === '/') {
78
+ return 'root';
79
+ }
80
+ if (kind === 'storage' && /(^|\/)(library|libraries)(\/|$)/i.test(path)) {
81
+ return 'library';
82
+ }
83
+ if (/(header|version|source|author|metadata|property|properties)/.test(haystack)) {
84
+ return hasProperty(properties, ['Name', 'Pins', 'Footprint', 'Padstack']) ? 'property' : 'metadata';
85
+ }
86
+ if (type === 'olb') {
87
+ if (/(^|\/)(symbols?|parts?)(\/|$)/.test(haystack) || hasProperty(properties, ['Pins', 'Footprint', 'PCB Footprint', 'Part Number'])) {
88
+ return 'symbol';
89
+ }
90
+ if (/(^|\/)(library|capture|orcad)(\/|$)/.test(haystack)) {
91
+ return 'library';
92
+ }
93
+ }
94
+ if (type === 'dra') {
95
+ if (/(padstack|pad stack|thermal|antipad|drill)/.test(haystack) || hasProperty(properties, ['Padstack', 'Drill'])) {
96
+ return 'padstack';
97
+ }
98
+ if (/(footprint|package|psm|bsm|fsm|ssm|symbol)/.test(haystack)) {
99
+ return 'footprint';
100
+ }
101
+ if (/(route|net|via|ratsnest)/.test(haystack)) {
102
+ return 'net';
103
+ }
104
+ if (/(line |arc |circle|shape|outline|silk|place_bound|assembly|soldermask|pastemask)/.test(haystack)) {
105
+ return 'geometry';
106
+ }
107
+ if (/(drawing|units|layers?|constraint|allegro)/.test(haystack) || hasProperty(properties, ['Units', 'Layers'])) {
108
+ return 'drawing';
109
+ }
110
+ }
111
+ if (type === 'gds' || type === 'oas' || type === 'oasis') {
112
+ if (/(cell|structure|strname|sref|aref|boundary|path|polygon|layer|datatype|text|xy|gds|oas|oasis|layout)/.test(haystack)) {
113
+ return 'geometry';
114
+ }
115
+ if (/(library|libname|units|precision|technology|property|properties)/.test(haystack)) {
116
+ return 'metadata';
117
+ }
118
+ }
119
+ if (properties.length) {
120
+ return 'property';
121
+ }
122
+ return kind === 'storage' ? 'library' : 'unknown';
123
+ };
124
+ const buildStreamView = (type, path, name, size, kind, bytes) => {
125
+ if (kind === 'storage' || !bytes) {
126
+ const role = roleFromText(type, path, name, '', [], [], kind);
127
+ return { path, name, size, kind, role, strings: [], properties: [] };
128
+ }
129
+ const sample = looksLikeText(bytes) ? decodeSample(bytes) : '';
130
+ const strings = collectStrings([bytes], MAX_STREAM_STRINGS);
131
+ const properties = extractProperties(sample, strings, path);
132
+ const role = roleFromText(type, path, name, sample, strings, properties, sample ? 'text' : 'binary');
133
+ return {
134
+ path,
135
+ name,
136
+ size,
137
+ kind: sample ? 'text' : 'binary',
138
+ role,
139
+ sample,
140
+ hex: sample ? undefined : hexPreview(bytes),
141
+ strings,
142
+ properties
143
+ };
144
+ };
145
+ const sortTree = (node) => {
146
+ node.children.sort((left, right) => {
147
+ if (left.children.length !== right.children.length) {
148
+ return right.children.length - left.children.length;
149
+ }
150
+ return left.name.localeCompare(right.name);
151
+ });
152
+ node.children.forEach(sortTree);
153
+ };
154
+ const buildTree = (streams, type) => {
155
+ const root = {
156
+ id: `${type}:root`,
157
+ path: '/',
158
+ name: type.toUpperCase(),
159
+ kind: 'storage',
160
+ role: 'root',
161
+ size: 0,
162
+ children: []
163
+ };
164
+ const nodes = new Map([['/', root]]);
165
+ streams.forEach(stream => {
166
+ const parts = stream.path.split('/').filter(Boolean);
167
+ let parent = root;
168
+ let currentPath = '';
169
+ parts.forEach((part, index) => {
170
+ currentPath += `/${part}`;
171
+ const isLeaf = index === parts.length - 1;
172
+ let node = nodes.get(currentPath);
173
+ if (!node) {
174
+ node = {
175
+ id: `${type}:${currentPath}`,
176
+ path: currentPath,
177
+ name: part,
178
+ kind: isLeaf ? stream.kind : 'storage',
179
+ role: isLeaf ? stream.role : roleFromText(type, currentPath, part, '', [], [], 'storage'),
180
+ size: isLeaf ? stream.size : 0,
181
+ children: []
182
+ };
183
+ nodes.set(currentPath, node);
184
+ parent.children.push(node);
185
+ }
186
+ if (isLeaf) {
187
+ node.kind = stream.kind;
188
+ node.role = stream.role;
189
+ node.size = stream.size;
190
+ }
191
+ parent = node;
192
+ });
193
+ });
194
+ sortTree(root);
195
+ return root.children;
196
+ };
197
+ const splitListValue = (value) => {
198
+ if (!value) {
199
+ return [];
200
+ }
201
+ const result = [];
202
+ value.split(/[,/;| ]+/).forEach(item => {
203
+ if (/^[A-Za-z0-9_.+-]+$/.test(item)) {
204
+ uniquePush(result, item, 64);
205
+ }
206
+ });
207
+ return result;
208
+ };
209
+ const entityRoleForStream = (stream, type) => {
210
+ if (type === 'olb') {
211
+ return stream.role === 'symbol' && stream.kind !== 'storage' ? 'symbol' : null;
212
+ }
213
+ if (type === 'gds' || type === 'oas' || type === 'oasis') {
214
+ return stream.role === 'geometry' || stream.role === 'metadata' ? 'drawing' : null;
215
+ }
216
+ if (stream.role === 'padstack') {
217
+ return 'padstack';
218
+ }
219
+ if (stream.role === 'footprint' || (stream.role === 'geometry' && /\/footprint\//i.test(stream.path))) {
220
+ return 'footprint';
221
+ }
222
+ if (stream.role === 'drawing') {
223
+ return 'drawing';
224
+ }
225
+ return null;
226
+ };
227
+ const streamEntityPath = (stream, role) => {
228
+ if (role === 'footprint') {
229
+ const match = stream.path.match(/^(.+?\/Footprint)(?:\/|$)/i);
230
+ return match?.[1] || stream.path;
231
+ }
232
+ if (role === 'drawing') {
233
+ const match = stream.path.match(/^(.+?\/Drawing)(?:\/|$)/i);
234
+ return match?.[1] || stream.path;
235
+ }
236
+ return stream.path;
237
+ };
238
+ const inferEntityName = (stream, role, entityPath) => {
239
+ const propertyName = getPropertyValue(stream.properties, [
240
+ 'Name',
241
+ 'Part',
242
+ 'Part Name',
243
+ 'Symbol',
244
+ 'Device',
245
+ 'Footprint',
246
+ 'PCB Footprint',
247
+ 'Package',
248
+ 'Padstack',
249
+ 'Pad Stack',
250
+ 'Drawing'
251
+ ]);
252
+ if (propertyName) {
253
+ return propertyName;
254
+ }
255
+ const pathName = stripExtension(lastPathPart(entityPath));
256
+ if (pathName && pathName !== '/') {
257
+ return pathName;
258
+ }
259
+ return role.toUpperCase();
260
+ };
261
+ const roleKeywords = (stream, role) => {
262
+ const keywords = [];
263
+ const text = `${stream.path}\n${stream.sample || ''}\n${stream.strings.join('\n')}`.toLowerCase();
264
+ const domainWords = role === 'symbol'
265
+ ? ['pins', 'footprint', 'pspice', 'part', 'symbol']
266
+ : ['units', 'layers', 'padstack', 'drill', 'outline', 'route', 'constraint', 'shape', 'place_bound'];
267
+ domainWords.forEach(word => {
268
+ if (text.includes(word)) {
269
+ uniquePush(keywords, word, 12);
270
+ }
271
+ });
272
+ return keywords;
273
+ };
274
+ const appendUniqueProperties = (target, properties) => {
275
+ const seen = new Set(target.map(property => `${property.key.toLowerCase()}\u0000${property.value}`));
276
+ properties.forEach(property => {
277
+ const identity = `${property.key.toLowerCase()}\u0000${property.value}`;
278
+ if (seen.has(identity)) {
279
+ return;
280
+ }
281
+ seen.add(identity);
282
+ target.push(property);
283
+ });
284
+ };
285
+ const collectEntities = (streams, type) => {
286
+ const entities = new Map();
287
+ streams.forEach(stream => {
288
+ const role = entityRoleForStream(stream, type);
289
+ if (!role) {
290
+ return;
291
+ }
292
+ const entityPath = streamEntityPath(stream, role);
293
+ const key = `${role}:${entityPath.toLowerCase()}`;
294
+ const existing = entities.get(key);
295
+ const entity = existing || {
296
+ id: key,
297
+ name: inferEntityName(stream, role, entityPath),
298
+ role,
299
+ path: entityPath,
300
+ streamCount: 0,
301
+ byteLength: 0,
302
+ properties: [],
303
+ pins: [],
304
+ layers: [],
305
+ keywords: []
306
+ };
307
+ entity.streamCount += 1;
308
+ entity.byteLength += stream.size;
309
+ appendUniqueProperties(entity.properties, stream.properties);
310
+ splitListValue(getPropertyValue(stream.properties, ['Pins', 'Pin', 'Pin Numbers'])).forEach(pin => uniquePush(entity.pins, pin, 96));
311
+ splitListValue(getPropertyValue(stream.properties, ['Layers', 'Layer'])).forEach(layer => uniquePush(entity.layers, layer, 64));
312
+ roleKeywords(stream, role).forEach(keyword => uniquePush(entity.keywords, keyword, 16));
313
+ entity.description ||= getPropertyValue(entity.properties, ['Description', 'Desc']);
314
+ entity.footprint ||= getPropertyValue(entity.properties, ['Footprint', 'PCB Footprint', 'Package']);
315
+ entities.set(key, entity);
316
+ });
317
+ return Array.from(entities.values()).sort((left, right) => {
318
+ const roleOrder = { symbol: 0, footprint: 1, padstack: 2, drawing: 3 };
319
+ const order = (roleOrder[left.role] ?? 9) - (roleOrder[right.role] ?? 9);
320
+ return order || left.name.localeCompare(right.name);
321
+ });
322
+ };
323
+ const collectMetadata = (streams) => {
324
+ const metadata = [];
325
+ streams.forEach(stream => {
326
+ if (stream.role === 'metadata' || stream.role === 'library' || stream.role === 'drawing') {
327
+ appendUniqueProperties(metadata, stream.properties);
328
+ }
329
+ });
330
+ return metadata.slice(0, 80);
331
+ };
332
+ const buildStats = (streams, entities, strings, parser, layout) => {
333
+ const stats = {
334
+ textStreams: streams.filter(stream => stream.kind === 'text').length,
335
+ binaryStreams: streams.filter(stream => stream.kind === 'binary').length,
336
+ storageEntries: streams.filter(stream => stream.kind === 'storage').length,
337
+ propertyCount: streams.reduce((sum, stream) => sum + stream.properties.length, 0),
338
+ stringCount: strings.length,
339
+ symbolCount: entities.filter(entity => entity.role === 'symbol').length,
340
+ footprintCount: entities.filter(entity => entity.role === 'footprint').length,
341
+ padstackCount: entities.filter(entity => entity.role === 'padstack').length,
342
+ confidence: 'low'
343
+ };
344
+ if (layout?.elements.length) {
345
+ stats.confidence = 'high';
346
+ }
347
+ else if (parser === 'cfb' && entities.length && stats.propertyCount) {
348
+ stats.confidence = 'high';
349
+ }
350
+ else if (parser === 'cfb' || strings.length || stats.propertyCount) {
351
+ stats.confidence = 'medium';
352
+ }
353
+ return stats;
354
+ };
355
+ const buildDiagnostics = (type, parser, streams, entities, strings, warnings, layout) => {
356
+ const diagnostics = warnings.map((message, index) => ({
357
+ level: 'warning',
358
+ code: `warning-${index + 1}`,
359
+ message
360
+ }));
361
+ diagnostics.push({
362
+ level: 'info',
363
+ code: 'parser',
364
+ message: layout
365
+ ? layout.format === 'oasis'
366
+ ? '已识别为 OASIS 文本结构夹具,并在浏览器端解析几何预览和结构索引;真实 SEMI 二进制 OASIS 仍走安全索引与后续独立内核路线。'
367
+ : '已识别为标准 GDSII 二进制版图记录,并在浏览器端解析几何预览和结构索引。'
368
+ : parser === 'cfb'
369
+ ? '已识别为 Microsoft Compound File / OLE2 复合文档容器,并在浏览器端解析目录与流。'
370
+ : '未识别为 CFB 容器,已使用二进制字符串索引模式展示可读信息。'
371
+ });
372
+ diagnostics.push({
373
+ level: 'info',
374
+ code: 'coverage',
375
+ message: `已索引 ${streams.length} 个条目、${strings.length} 个可读字符串、${entities.length} 个 EDA 结构候选。`
376
+ });
377
+ if (layout) {
378
+ diagnostics.push({
379
+ level: 'info',
380
+ code: `${layout.format}-layout`,
381
+ message: `已解析 ${layout.format === 'oasis' ? 'OASIS 结构夹具' : 'GDSII 版图'}: ${layout.structureCount} 个 structure、${layout.elements.length} 个几何/引用/文本元素,可在版图预览面板中拖动查看。`
382
+ });
383
+ }
384
+ const needsSymbol = type === 'olb' && !entities.some(entity => entity.role === 'symbol');
385
+ const needsFootprint = type === 'dra' && !entities.some(entity => entity.role === 'footprint' || entity.role === 'padstack');
386
+ const needsLayout = (type === 'gds' || type === 'oas' || type === 'oasis') && !layout && !entities.some(entity => entity.role === 'drawing');
387
+ if (needsSymbol || needsFootprint || needsLayout) {
388
+ diagnostics.push({
389
+ level: 'warning',
390
+ code: 'domain-candidates',
391
+ message: type === 'olb'
392
+ ? '未发现明确的元件符号候选,文件可能使用了私有二进制编码或需要专业工具导出 ASCII/XML 后再检查。'
393
+ : type === 'dra'
394
+ ? '未发现明确的封装、图形或 padstack 候选,文件可能使用了私有二进制数据库编码。'
395
+ : type === 'gds'
396
+ ? '未发现明确的 GDSII 版图结构候选。文件可能不是标准 GDSII 二进制或使用了专有封装。'
397
+ : '未发现明确的 OASIS 版图结构候选。OASIS 完整几何浏览通常需要专业版图库或独立 WASM/TS 内核,当前前端包会安全展示头部、字符串、属性和二进制结构线索。'
398
+ });
399
+ }
400
+ return diagnostics;
401
+ };
402
+ const assembleResult = (buffer, type, parser, streamCount, streams, strings, warnings, layout) => {
403
+ const totalStreamBytes = streams.reduce((sum, stream) => sum + stream.size, 0);
404
+ const entities = collectEntities(streams, type);
405
+ const metadata = collectMetadata(streams);
406
+ const diagnostics = buildDiagnostics(type, parser, streams, entities, strings, warnings, layout);
407
+ return {
408
+ type,
409
+ parser,
410
+ title: type === 'olb'
411
+ ? (parser === 'cfb' ? 'OrCAD Capture Symbol Library' : 'OLB Binary Library')
412
+ : type === 'dra'
413
+ ? (parser === 'cfb' ? 'OrCAD / Allegro Drawing Library' : 'DRA Binary Drawing')
414
+ : `${type.toUpperCase()} Layout Structure`,
415
+ byteLength: buffer.byteLength,
416
+ streamCount,
417
+ totalStreamBytes,
418
+ streams,
419
+ tree: buildTree(streams, type),
420
+ entities,
421
+ metadata,
422
+ strings,
423
+ warnings,
424
+ diagnostics,
425
+ stats: buildStats(streams, entities, strings, parser, layout),
426
+ layout
427
+ };
428
+ };
429
+ const parseCfbContainer = async (buffer, type) => {
430
+ const CFB = await import('cfb');
431
+ const container = CFB.parse(toBytes(buffer), { type: 'array' });
432
+ const streamEntries = container.FileIndex
433
+ .map((entry, index) => ({ entry, path: container.FullPaths[index] || entry.name }))
434
+ .filter(item => item.entry.type !== 5 && item.path !== '/' && item.entry.name)
435
+ .slice(0, MAX_STREAMS);
436
+ const byteChunks = [];
437
+ const streams = streamEntries.map(({ entry, path }) => {
438
+ if (entry.type === 1) {
439
+ return buildStreamView(type, path, entry.name, entry.size || 0, 'storage');
440
+ }
441
+ const content = normalizeBytes(entry.content || []);
442
+ byteChunks.push(content);
443
+ return buildStreamView(type, path, entry.name, entry.size || content.byteLength || 0, 'binary', content);
444
+ });
445
+ const warnings = streamEntries.length >= MAX_STREAMS
446
+ ? [`仅展示前 ${MAX_STREAMS} 个 CFB 项,完整文件仍可下载后在专业 EDA 工具中打开。`]
447
+ : [];
448
+ return assembleResult(buffer, type, 'cfb', container.FileIndex.length, streams, collectStrings(byteChunks), warnings);
449
+ };
450
+ const parseBinaryFallback = (buffer, type) => {
451
+ const bytes = toBytes(buffer);
452
+ const stream = buildStreamView(type, `${type}.${type}`, `${type}.${type}`, buffer.byteLength, 'binary', bytes);
453
+ const layout = type === 'gds'
454
+ ? parseGdsLayout(bytes)
455
+ : type === 'oas' || type === 'oasis'
456
+ ? parseOasisTextLayout(bytes)
457
+ : undefined;
458
+ const oasis = type === 'oas' || type === 'oasis' ? inspectOasisLayout(bytes) : undefined;
459
+ const warnings = layout
460
+ ? layout.warnings
461
+ : oasis
462
+ ? oasis.warnings
463
+ : ['该文件不是标准 CFB 容器,已退化为安全的二进制字符串索引预览。'];
464
+ return assembleResult(buffer, type, 'binary', 1, [stream], collectStrings([bytes]), warnings, layout);
465
+ };
466
+ export const parseEdaFile = async (buffer, type = 'olb') => {
467
+ const normalizedType = type === 'dra'
468
+ ? 'dra'
469
+ : type === 'gds' || type === 'oas' || type === 'oasis'
470
+ ? type
471
+ : 'olb';
472
+ const bytes = toBytes(buffer);
473
+ if (!isOrcadCompoundFile(bytes)) {
474
+ return parseBinaryFallback(buffer, normalizedType);
475
+ }
476
+ try {
477
+ return await parseCfbContainer(buffer, normalizedType);
478
+ }
479
+ catch (error) {
480
+ const fallback = parseBinaryFallback(buffer, normalizedType);
481
+ fallback.warnings.unshift(error instanceof Error ? error.message : String(error));
482
+ fallback.diagnostics.unshift({
483
+ level: 'warning',
484
+ code: 'cfb-parse-failed',
485
+ message: error instanceof Error ? error.message : String(error)
486
+ });
487
+ return fallback;
488
+ }
489
+ };
@@ -0,0 +1,6 @@
1
+ import { type FileRenderHandler, type FileViewerRenderedInstance, type FileViewerRendererPlugin, type RendererDefinition } from '@file-viewer/core';
2
+ export declare const edaRendererDefinition: RendererDefinition;
3
+ export declare const renderFileViewerEda: FileRenderHandler<FileViewerRenderedInstance, HTMLDivElement>;
4
+ export declare const edaRenderer: FileViewerRendererPlugin<FileRenderHandler<FileViewerRenderedInstance, HTMLDivElement>>;
5
+ export { parseEdaFile, type EdaDomainRole, type EdaEntity, type EdaFileType, type EdaLayoutElement, type EdaLayoutPreview, type EdaParseResult, type EdaStreamKind, type EdaStreamView, type EdaTreeNode, } from './edaParser.js';
6
+ export default edaRenderer;
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import { DEFAULT_RENDERER_DEFINITIONS, } from '@file-viewer/core';
2
+ const edaDefinition = DEFAULT_RENDERER_DEFINITIONS.find(definition => definition.id === 'eda');
3
+ if (!edaDefinition) {
4
+ throw new Error('@file-viewer/renderer-eda could not locate the shared EDA renderer definition.');
5
+ }
6
+ export const edaRendererDefinition = edaDefinition;
7
+ export const renderFileViewerEda = (buffer, target, type, context) => import('./eda.js').then(({ default: renderEda }) => renderEda(buffer, target, type, context));
8
+ export const edaRenderer = {
9
+ id: 'file-viewer-renderer-eda',
10
+ label: 'Flyfish File Viewer EDA renderer',
11
+ definitions: [edaRendererDefinition],
12
+ handlers: [{
13
+ rendererId: edaRendererDefinition.id,
14
+ handler: renderFileViewerEda,
15
+ }],
16
+ };
17
+ export { parseEdaFile, } from './edaParser.js';
18
+ export default edaRenderer;
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@file-viewer/renderer-eda",
3
+ "version": "2.0.11",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Standalone EDA renderer plugin for Flyfish File Viewer with OLB, DRA, GDSII, and OASIS structure preview.",
7
+ "keywords": [
8
+ "file-viewer",
9
+ "renderer",
10
+ "eda",
11
+ "gds",
12
+ "gdsii",
13
+ "oasis",
14
+ "olb",
15
+ "dra",
16
+ "document-preview",
17
+ "document-viewer",
18
+ "file-preview",
19
+ "self-hosted"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "registry": "https://registry.npmjs.org/"
24
+ },
25
+ "author": {
26
+ "name": "Wangyu",
27
+ "email": "wybaby168@gmail.com"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/flyfish-dev/file-viewer-renderer-eda.git",
32
+ "directory": "packages/renderers/eda"
33
+ },
34
+ "homepage": "https://doc.file-viewer.app/guide/format-fidelity",
35
+ "bugs": {
36
+ "url": "https://github.com/flyfish-dev/file-viewer-renderer-eda/issues"
37
+ },
38
+ "funding": {
39
+ "type": "individual",
40
+ "url": "https://dev.flyfish.group/shop"
41
+ },
42
+ "main": "./dist/index.js",
43
+ "module": "./dist/index.js",
44
+ "types": "./dist/index.d.ts",
45
+ "exports": {
46
+ ".": {
47
+ "types": "./dist/index.d.ts",
48
+ "import": "./dist/index.js",
49
+ "default": "./dist/index.js"
50
+ },
51
+ "./package.json": "./package.json"
52
+ },
53
+ "files": [
54
+ "dist",
55
+ "README.md",
56
+ "README.en.md",
57
+ "LICENSE"
58
+ ],
59
+ "dependencies": {
60
+ "@file-viewer/core": "^2.0.11",
61
+ "@file-viewer/eda-layout": "^2.0.11",
62
+ "@file-viewer/eda-orcad": "^2.0.11",
63
+ "cfb": "^1.2.2"
64
+ },
65
+ "devDependencies": {
66
+ "typescript": "^6.0.3"
67
+ },
68
+ "license": "Apache-2.0",
69
+ "scripts": {
70
+ "build": "tsc -b tsconfig.json",
71
+ "type-check": "tsc -b tsconfig.json"
72
+ }
73
+ }