@file-viewer/core 2.0.11 → 2.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/README.en.md +2 -2
- package/README.md +2 -2
- package/dist/config/options.d.ts +1 -1
- package/dist/config/options.js +1 -1
- package/dist/contracts/types.d.ts +71 -1
- package/dist/headless.d.ts +2 -2
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.js +105 -48
- package/dist/platform/assets.d.ts +3 -1
- package/dist/platform/assets.js +18 -2
- package/dist/registry/capabilities.d.ts +2 -2
- package/dist/registry/capabilities.js +2 -1
- package/dist/registry/formats.d.ts +20 -7
- package/dist/registry/formats.js +14 -5
- package/dist/registry/registry.d.ts +8 -1
- package/dist/registry/registry.js +29 -0
- package/dist/renderers/image.js +1 -10
- package/dist/renderers/index.d.ts +320 -2
- package/dist/renderers/index.js +27 -157
- package/dist/viewer/createViewer.js +86 -3
- package/package.json +17 -44
- package/dist/renderers/archive.d.ts +0 -2
- package/dist/renderers/archive.js +0 -547
- package/dist/renderers/archiveCache.d.ts +0 -10
- package/dist/renderers/archiveCache.js +0 -96
- package/dist/renderers/archiveFallback.d.ts +0 -7
- package/dist/renderers/archiveFallback.js +0 -166
- package/dist/renderers/archiveShared.d.ts +0 -23
- package/dist/renderers/archiveShared.js +0 -71
- package/dist/renderers/audio.d.ts +0 -8
- package/dist/renderers/audio.js +0 -219
- package/dist/renderers/cad.d.ts +0 -2
- package/dist/renderers/cad.js +0 -446
- package/dist/renderers/code.d.ts +0 -11
- package/dist/renderers/code.js +0 -233
- package/dist/renderers/data.d.ts +0 -7
- package/dist/renderers/data.js +0 -370
- package/dist/renderers/drawing.d.ts +0 -10
- package/dist/renderers/drawing.js +0 -882
- package/dist/renderers/eda.d.ts +0 -2
- package/dist/renderers/eda.js +0 -434
- package/dist/renderers/edaParser.d.ts +0 -77
- package/dist/renderers/edaParser.js +0 -569
- package/dist/renderers/email.d.ts +0 -2
- package/dist/renderers/email.js +0 -463
- package/dist/renderers/epub.d.ts +0 -2
- package/dist/renderers/epub.js +0 -331
- package/dist/renderers/geo.d.ts +0 -2
- package/dist/renderers/geo.js +0 -284
- package/dist/renderers/markdown.d.ts +0 -2
- package/dist/renderers/markdown.js +0 -83
- package/dist/renderers/model.d.ts +0 -2
- package/dist/renderers/model.js +0 -567
- package/dist/renderers/ofd.d.ts +0 -2
- package/dist/renderers/ofd.js +0 -256
- package/dist/renderers/openDocument.d.ts +0 -2
- package/dist/renderers/openDocument.js +0 -122
- package/dist/renderers/pdf.d.ts +0 -3
- package/dist/renderers/pdf.js +0 -1001
- package/dist/renderers/pdfStyles.d.ts +0 -1
- package/dist/renderers/pdfStyles.js +0 -1
- package/dist/renderers/pptx.d.ts +0 -2
- package/dist/renderers/pptx.js +0 -217
- package/dist/renderers/spreadsheet/state.d.ts +0 -80
- package/dist/renderers/spreadsheet/state.js +0 -96
- package/dist/renderers/spreadsheet/view.d.ts +0 -25
- package/dist/renderers/spreadsheet/view.js +0 -833
- package/dist/renderers/spreadsheet/worker/index.d.ts +0 -2
- package/dist/renderers/spreadsheet/worker/index.js +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.d.ts +0 -73
- package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.js +0 -623
- package/dist/renderers/spreadsheet/worker/sheetjs/color.d.ts +0 -2
- package/dist/renderers/spreadsheet/worker/sheetjs/color.js +0 -73
- package/dist/renderers/spreadsheet/worker/sheetjs/index.d.ts +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/index.js +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/parser.d.ts +0 -18
- package/dist/renderers/spreadsheet/worker/sheetjs/parser.js +0 -106
- package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.d.ts +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.js +0 -11
- package/dist/renderers/spreadsheet/worker/type.d.ts +0 -57
- package/dist/renderers/spreadsheet/worker/type.js +0 -1
- package/dist/renderers/spreadsheet.d.ts +0 -3
- package/dist/renderers/spreadsheet.js +0 -929
- package/dist/renderers/typst.d.ts +0 -8
- package/dist/renderers/typst.js +0 -547
- package/dist/renderers/umd/parser.d.ts +0 -30
- package/dist/renderers/umd/parser.js +0 -408
- package/dist/renderers/umd.d.ts +0 -2
- package/dist/renderers/umd.js +0 -297
- package/dist/renderers/video.d.ts +0 -8
- package/dist/renderers/video.js +0 -108
- package/dist/renderers/wordDoc.d.ts +0 -5
- package/dist/renderers/wordDoc.js +0 -284
- package/dist/renderers/wordDocx.d.ts +0 -5
- package/dist/renderers/wordDocx.js +0 -985
- package/dist/renderers/wordDocx.worker.d.ts +0 -1
- package/dist/renderers/wordDocx.worker.js +0 -96
- package/vendor/ofd/dltech/jbig2/arithmetic_decoder.js +0 -183
- package/vendor/ofd/dltech/jbig2/ccitt.js +0 -1070
- package/vendor/ofd/dltech/jbig2/compatibility.js +0 -12
- package/vendor/ofd/dltech/jbig2/core_utils.js +0 -180
- package/vendor/ofd/dltech/jbig2/is_node.js +0 -27
- package/vendor/ofd/dltech/jbig2/jbig2.js +0 -2589
- package/vendor/ofd/dltech/jbig2/jbig2_stream.js +0 -81
- package/vendor/ofd/dltech/jbig2/primitives.js +0 -387
- package/vendor/ofd/dltech/jbig2/stream.js +0 -1348
- package/vendor/ofd/dltech/jbig2/util.js +0 -972
- package/vendor/ofd/dltech/ofd/ofd.d.ts +0 -11
- package/vendor/ofd/dltech/ofd/ofd.js +0 -100
- package/vendor/ofd/dltech/ofd/ofd_parser.js +0 -395
- package/vendor/ofd/dltech/ofd/ofd_render.js +0 -473
- package/vendor/ofd/dltech/ofd/ofd_util.js +0 -350
- package/vendor/ofd/dltech/ofd/pipeline.js +0 -26
|
@@ -1,569 +0,0 @@
|
|
|
1
|
-
const CFB_MAGIC = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1];
|
|
2
|
-
const MAX_STREAMS = 200;
|
|
3
|
-
const MAX_SAMPLE_BYTES = 4096;
|
|
4
|
-
const MAX_HEX_BYTES = 192;
|
|
5
|
-
const MAX_STRINGS = 180;
|
|
6
|
-
const MAX_STREAM_STRINGS = 24;
|
|
7
|
-
const MAX_PROPERTIES = 420;
|
|
8
|
-
const toBytes = (buffer) => new Uint8Array(buffer);
|
|
9
|
-
const isCfbFile = (bytes) => {
|
|
10
|
-
return CFB_MAGIC.every((value, index) => bytes[index] === value);
|
|
11
|
-
};
|
|
12
|
-
const normalizeBytes = (value) => {
|
|
13
|
-
return value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
14
|
-
};
|
|
15
|
-
const cleanupText = (text) => {
|
|
16
|
-
return text
|
|
17
|
-
.replace(/\u0000/g, '')
|
|
18
|
-
.replace(/[^\S\r\n]+/g, ' ')
|
|
19
|
-
.replace(/\r\n/g, '\n')
|
|
20
|
-
.trim();
|
|
21
|
-
};
|
|
22
|
-
const normalizeSearchText = (value) => {
|
|
23
|
-
return cleanupText(value).toLowerCase();
|
|
24
|
-
};
|
|
25
|
-
const lastPathPart = (path) => {
|
|
26
|
-
const parts = path.split('/').filter(Boolean);
|
|
27
|
-
return parts[parts.length - 1] || path || '/';
|
|
28
|
-
};
|
|
29
|
-
const stripExtension = (value) => {
|
|
30
|
-
return value.replace(/\.[a-z0-9]+$/i, '');
|
|
31
|
-
};
|
|
32
|
-
const uniquePush = (target, value, max = Number.POSITIVE_INFINITY) => {
|
|
33
|
-
const cleaned = cleanupText(value);
|
|
34
|
-
if (!cleaned || target.includes(cleaned) || target.length >= max) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
target.push(cleaned);
|
|
38
|
-
};
|
|
39
|
-
const looksLikeText = (bytes) => {
|
|
40
|
-
if (!bytes.length) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
const sample = bytes.slice(0, Math.min(bytes.length, MAX_SAMPLE_BYTES));
|
|
44
|
-
let printable = 0;
|
|
45
|
-
let zeroBytes = 0;
|
|
46
|
-
for (const byte of sample) {
|
|
47
|
-
if (byte === 0) {
|
|
48
|
-
zeroBytes += 1;
|
|
49
|
-
}
|
|
50
|
-
if (byte === 9 || byte === 10 || byte === 13 || (byte >= 32 && byte <= 126) || byte >= 0x80) {
|
|
51
|
-
printable += 1;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return printable / sample.length > 0.82 || zeroBytes / sample.length > 0.25;
|
|
55
|
-
};
|
|
56
|
-
const decodeSample = (bytes) => {
|
|
57
|
-
const sample = bytes.slice(0, Math.min(bytes.length, MAX_SAMPLE_BYTES));
|
|
58
|
-
if (!sample.length) {
|
|
59
|
-
return '';
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
let zeroOdd = 0;
|
|
63
|
-
let zeroEven = 0;
|
|
64
|
-
for (let index = 0; index < sample.length; index += 1) {
|
|
65
|
-
if (sample[index] !== 0) {
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (index % 2 === 0) {
|
|
69
|
-
zeroEven += 1;
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
zeroOdd += 1;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const decoder = zeroOdd > sample.length / 5 && zeroOdd > zeroEven * 2
|
|
76
|
-
? new TextDecoder('utf-16le', { fatal: false })
|
|
77
|
-
: new TextDecoder('utf-8', { fatal: false });
|
|
78
|
-
return cleanupText(decoder.decode(sample));
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return '';
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
const hexPreview = (bytes) => {
|
|
85
|
-
const sample = bytes.slice(0, Math.min(bytes.length, MAX_HEX_BYTES));
|
|
86
|
-
const lines = [];
|
|
87
|
-
for (let offset = 0; offset < sample.length; offset += 16) {
|
|
88
|
-
const row = sample.slice(offset, offset + 16);
|
|
89
|
-
const hex = Array.from(row).map(byte => byte.toString(16).padStart(2, '0')).join(' ');
|
|
90
|
-
const ascii = Array.from(row)
|
|
91
|
-
.map(byte => byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '.')
|
|
92
|
-
.join('');
|
|
93
|
-
lines.push(`${offset.toString(16).padStart(8, '0')} ${hex.padEnd(47)} ${ascii}`);
|
|
94
|
-
}
|
|
95
|
-
return lines.join('\n');
|
|
96
|
-
};
|
|
97
|
-
const extractAsciiStrings = (bytes) => {
|
|
98
|
-
const result = [];
|
|
99
|
-
let current = '';
|
|
100
|
-
for (const byte of bytes) {
|
|
101
|
-
if (byte >= 32 && byte <= 126) {
|
|
102
|
-
current += String.fromCharCode(byte);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (current.length >= 4) {
|
|
106
|
-
result.push(current);
|
|
107
|
-
}
|
|
108
|
-
current = '';
|
|
109
|
-
}
|
|
110
|
-
if (current.length >= 4) {
|
|
111
|
-
result.push(current);
|
|
112
|
-
}
|
|
113
|
-
return result;
|
|
114
|
-
};
|
|
115
|
-
const extractUtf16Strings = (bytes) => {
|
|
116
|
-
const result = [];
|
|
117
|
-
let current = '';
|
|
118
|
-
for (let index = 0; index + 1 < bytes.length; index += 2) {
|
|
119
|
-
const low = bytes[index];
|
|
120
|
-
const high = bytes[index + 1];
|
|
121
|
-
if (high === 0 && low >= 32 && low <= 126) {
|
|
122
|
-
current += String.fromCharCode(low);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
if (current.length >= 4) {
|
|
126
|
-
result.push(current);
|
|
127
|
-
}
|
|
128
|
-
current = '';
|
|
129
|
-
}
|
|
130
|
-
if (current.length >= 4) {
|
|
131
|
-
result.push(current);
|
|
132
|
-
}
|
|
133
|
-
return result;
|
|
134
|
-
};
|
|
135
|
-
const collectStrings = (chunks, maxStrings = MAX_STRINGS) => {
|
|
136
|
-
const seen = new Set();
|
|
137
|
-
const result = [];
|
|
138
|
-
chunks.forEach(chunk => {
|
|
139
|
-
const candidates = [...extractAsciiStrings(chunk), ...extractUtf16Strings(chunk)];
|
|
140
|
-
candidates.forEach(item => {
|
|
141
|
-
const cleaned = cleanupText(item);
|
|
142
|
-
if (!cleaned || cleaned.length < 4 || seen.has(cleaned) || result.length >= maxStrings) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
seen.add(cleaned);
|
|
146
|
-
result.push(cleaned);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
return result;
|
|
150
|
-
};
|
|
151
|
-
const PROPERTY_RE = /^\s*([A-Za-z][A-Za-z0-9_. /#-]{1,56})\s*[:=]\s*(.{1,240})\s*$/;
|
|
152
|
-
const INLINE_PROPERTY_RE = /\b([A-Za-z][A-Za-z0-9_. /#-]{1,56})\s*=\s*([^;\n\r|]{1,240})/g;
|
|
153
|
-
const normalizePropertyKey = (key) => {
|
|
154
|
-
return cleanupText(key).replace(/\s+/g, ' ');
|
|
155
|
-
};
|
|
156
|
-
const addProperty = (target, seen, key, value, source) => {
|
|
157
|
-
if (target.length >= MAX_PROPERTIES) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
const normalizedKey = normalizePropertyKey(key);
|
|
161
|
-
const normalizedValue = cleanupText(value);
|
|
162
|
-
if (!normalizedKey || !normalizedValue) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const identity = `${source}\u0000${normalizedKey.toLowerCase()}\u0000${normalizedValue}`;
|
|
166
|
-
if (seen.has(identity)) {
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
seen.add(identity);
|
|
170
|
-
target.push({ key: normalizedKey, value: normalizedValue, source });
|
|
171
|
-
};
|
|
172
|
-
const extractProperties = (text, strings, source) => {
|
|
173
|
-
const properties = [];
|
|
174
|
-
const seen = new Set();
|
|
175
|
-
const chunks = [text, ...strings].filter(Boolean);
|
|
176
|
-
chunks.forEach(chunk => {
|
|
177
|
-
cleanupText(chunk)
|
|
178
|
-
.split(/\n|[|;]/)
|
|
179
|
-
.forEach(line => {
|
|
180
|
-
const match = line.match(PROPERTY_RE);
|
|
181
|
-
if (match) {
|
|
182
|
-
addProperty(properties, seen, match[1], match[2], source);
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
for (const match of chunk.matchAll(INLINE_PROPERTY_RE)) {
|
|
186
|
-
addProperty(properties, seen, match[1], match[2], source);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
return properties;
|
|
190
|
-
};
|
|
191
|
-
const hasProperty = (properties, keys) => {
|
|
192
|
-
const normalized = keys.map(key => key.toLowerCase());
|
|
193
|
-
return properties.some(property => normalized.includes(property.key.toLowerCase()));
|
|
194
|
-
};
|
|
195
|
-
const getPropertyValue = (properties, keys) => {
|
|
196
|
-
var _a;
|
|
197
|
-
const normalized = keys.map(key => key.toLowerCase());
|
|
198
|
-
return (_a = properties.find(property => normalized.includes(property.key.toLowerCase()))) === null || _a === void 0 ? void 0 : _a.value;
|
|
199
|
-
};
|
|
200
|
-
const roleFromText = (type, path, name, sample, strings, properties, kind) => {
|
|
201
|
-
const haystack = normalizeSearchText(`${path}\n${name}\n${sample}\n${strings.join('\n')}`);
|
|
202
|
-
if (haystack === '/' || path === '/') {
|
|
203
|
-
return 'root';
|
|
204
|
-
}
|
|
205
|
-
if (kind === 'storage' && /(^|\/)(library|libraries)(\/|$)/i.test(path)) {
|
|
206
|
-
return 'library';
|
|
207
|
-
}
|
|
208
|
-
if (/(header|version|source|author|metadata|property|properties)/.test(haystack)) {
|
|
209
|
-
return hasProperty(properties, ['Name', 'Pins', 'Footprint', 'Padstack']) ? 'property' : 'metadata';
|
|
210
|
-
}
|
|
211
|
-
if (type === 'olb') {
|
|
212
|
-
if (/(^|\/)(symbols?|parts?)(\/|$)/.test(haystack) || hasProperty(properties, ['Pins', 'Footprint', 'PCB Footprint', 'Part Number'])) {
|
|
213
|
-
return 'symbol';
|
|
214
|
-
}
|
|
215
|
-
if (/(^|\/)(library|capture|orcad)(\/|$)/.test(haystack)) {
|
|
216
|
-
return 'library';
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (type === 'dra') {
|
|
220
|
-
if (/(padstack|pad stack|thermal|antipad|drill)/.test(haystack) || hasProperty(properties, ['Padstack', 'Drill'])) {
|
|
221
|
-
return 'padstack';
|
|
222
|
-
}
|
|
223
|
-
if (/(footprint|package|psm|bsm|fsm|ssm|symbol)/.test(haystack)) {
|
|
224
|
-
return 'footprint';
|
|
225
|
-
}
|
|
226
|
-
if (/(route|net|via|ratsnest)/.test(haystack)) {
|
|
227
|
-
return 'net';
|
|
228
|
-
}
|
|
229
|
-
if (/(line |arc |circle|shape|outline|silk|place_bound|assembly|soldermask|pastemask)/.test(haystack)) {
|
|
230
|
-
return 'geometry';
|
|
231
|
-
}
|
|
232
|
-
if (/(drawing|units|layers?|constraint|allegro)/.test(haystack) || hasProperty(properties, ['Units', 'Layers'])) {
|
|
233
|
-
return 'drawing';
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
if (properties.length) {
|
|
237
|
-
return 'property';
|
|
238
|
-
}
|
|
239
|
-
return kind === 'storage' ? 'library' : 'unknown';
|
|
240
|
-
};
|
|
241
|
-
const buildStreamView = (type, path, name, size, kind, bytes) => {
|
|
242
|
-
if (kind === 'storage' || !bytes) {
|
|
243
|
-
const role = roleFromText(type, path, name, '', [], [], kind);
|
|
244
|
-
return { path, name, size, kind, role, strings: [], properties: [] };
|
|
245
|
-
}
|
|
246
|
-
const sample = looksLikeText(bytes) ? decodeSample(bytes) : '';
|
|
247
|
-
const strings = collectStrings([bytes], MAX_STREAM_STRINGS);
|
|
248
|
-
const properties = extractProperties(sample, strings, path);
|
|
249
|
-
const role = roleFromText(type, path, name, sample, strings, properties, sample ? 'text' : 'binary');
|
|
250
|
-
return {
|
|
251
|
-
path,
|
|
252
|
-
name,
|
|
253
|
-
size,
|
|
254
|
-
kind: sample ? 'text' : 'binary',
|
|
255
|
-
role,
|
|
256
|
-
sample,
|
|
257
|
-
hex: sample ? undefined : hexPreview(bytes),
|
|
258
|
-
strings,
|
|
259
|
-
properties
|
|
260
|
-
};
|
|
261
|
-
};
|
|
262
|
-
const sortTree = (node) => {
|
|
263
|
-
node.children.sort((left, right) => {
|
|
264
|
-
if (left.children.length !== right.children.length) {
|
|
265
|
-
return right.children.length - left.children.length;
|
|
266
|
-
}
|
|
267
|
-
return left.name.localeCompare(right.name);
|
|
268
|
-
});
|
|
269
|
-
node.children.forEach(sortTree);
|
|
270
|
-
};
|
|
271
|
-
const buildTree = (streams, type) => {
|
|
272
|
-
const root = {
|
|
273
|
-
id: `${type}:root`,
|
|
274
|
-
path: '/',
|
|
275
|
-
name: type.toUpperCase(),
|
|
276
|
-
kind: 'storage',
|
|
277
|
-
role: 'root',
|
|
278
|
-
size: 0,
|
|
279
|
-
children: []
|
|
280
|
-
};
|
|
281
|
-
const nodes = new Map([['/', root]]);
|
|
282
|
-
streams.forEach(stream => {
|
|
283
|
-
const parts = stream.path.split('/').filter(Boolean);
|
|
284
|
-
let parent = root;
|
|
285
|
-
let currentPath = '';
|
|
286
|
-
parts.forEach((part, index) => {
|
|
287
|
-
currentPath += `/${part}`;
|
|
288
|
-
const isLeaf = index === parts.length - 1;
|
|
289
|
-
let node = nodes.get(currentPath);
|
|
290
|
-
if (!node) {
|
|
291
|
-
node = {
|
|
292
|
-
id: `${type}:${currentPath}`,
|
|
293
|
-
path: currentPath,
|
|
294
|
-
name: part,
|
|
295
|
-
kind: isLeaf ? stream.kind : 'storage',
|
|
296
|
-
role: isLeaf ? stream.role : roleFromText(type, currentPath, part, '', [], [], 'storage'),
|
|
297
|
-
size: isLeaf ? stream.size : 0,
|
|
298
|
-
children: []
|
|
299
|
-
};
|
|
300
|
-
nodes.set(currentPath, node);
|
|
301
|
-
parent.children.push(node);
|
|
302
|
-
}
|
|
303
|
-
if (isLeaf) {
|
|
304
|
-
node.kind = stream.kind;
|
|
305
|
-
node.role = stream.role;
|
|
306
|
-
node.size = stream.size;
|
|
307
|
-
}
|
|
308
|
-
parent = node;
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
sortTree(root);
|
|
312
|
-
return root.children;
|
|
313
|
-
};
|
|
314
|
-
const splitListValue = (value) => {
|
|
315
|
-
if (!value) {
|
|
316
|
-
return [];
|
|
317
|
-
}
|
|
318
|
-
const result = [];
|
|
319
|
-
value.split(/[,/;| ]+/).forEach(item => {
|
|
320
|
-
if (/^[A-Za-z0-9_.+-]+$/.test(item)) {
|
|
321
|
-
uniquePush(result, item, 64);
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
return result;
|
|
325
|
-
};
|
|
326
|
-
const entityRoleForStream = (stream, type) => {
|
|
327
|
-
if (type === 'olb') {
|
|
328
|
-
return stream.role === 'symbol' && stream.kind !== 'storage' ? 'symbol' : null;
|
|
329
|
-
}
|
|
330
|
-
if (stream.role === 'padstack') {
|
|
331
|
-
return 'padstack';
|
|
332
|
-
}
|
|
333
|
-
if (stream.role === 'footprint' || (stream.role === 'geometry' && /\/footprint\//i.test(stream.path))) {
|
|
334
|
-
return 'footprint';
|
|
335
|
-
}
|
|
336
|
-
if (stream.role === 'drawing') {
|
|
337
|
-
return 'drawing';
|
|
338
|
-
}
|
|
339
|
-
return null;
|
|
340
|
-
};
|
|
341
|
-
const streamEntityPath = (stream, role) => {
|
|
342
|
-
if (role === 'footprint') {
|
|
343
|
-
const match = stream.path.match(/^(.+?\/Footprint)(?:\/|$)/i);
|
|
344
|
-
return (match === null || match === void 0 ? void 0 : match[1]) || stream.path;
|
|
345
|
-
}
|
|
346
|
-
if (role === 'drawing') {
|
|
347
|
-
const match = stream.path.match(/^(.+?\/Drawing)(?:\/|$)/i);
|
|
348
|
-
return (match === null || match === void 0 ? void 0 : match[1]) || stream.path;
|
|
349
|
-
}
|
|
350
|
-
return stream.path;
|
|
351
|
-
};
|
|
352
|
-
const inferEntityName = (stream, role, entityPath) => {
|
|
353
|
-
const propertyName = getPropertyValue(stream.properties, [
|
|
354
|
-
'Name',
|
|
355
|
-
'Part',
|
|
356
|
-
'Part Name',
|
|
357
|
-
'Symbol',
|
|
358
|
-
'Device',
|
|
359
|
-
'Footprint',
|
|
360
|
-
'PCB Footprint',
|
|
361
|
-
'Package',
|
|
362
|
-
'Padstack',
|
|
363
|
-
'Pad Stack',
|
|
364
|
-
'Drawing'
|
|
365
|
-
]);
|
|
366
|
-
if (propertyName) {
|
|
367
|
-
return propertyName;
|
|
368
|
-
}
|
|
369
|
-
const pathName = stripExtension(lastPathPart(entityPath));
|
|
370
|
-
if (pathName && pathName !== '/') {
|
|
371
|
-
return pathName;
|
|
372
|
-
}
|
|
373
|
-
return role.toUpperCase();
|
|
374
|
-
};
|
|
375
|
-
const roleKeywords = (stream, role) => {
|
|
376
|
-
const keywords = [];
|
|
377
|
-
const text = `${stream.path}\n${stream.sample || ''}\n${stream.strings.join('\n')}`.toLowerCase();
|
|
378
|
-
const domainWords = role === 'symbol'
|
|
379
|
-
? ['pins', 'footprint', 'pspice', 'part', 'symbol']
|
|
380
|
-
: ['units', 'layers', 'padstack', 'drill', 'outline', 'route', 'constraint', 'shape', 'place_bound'];
|
|
381
|
-
domainWords.forEach(word => {
|
|
382
|
-
if (text.includes(word)) {
|
|
383
|
-
uniquePush(keywords, word, 12);
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
return keywords;
|
|
387
|
-
};
|
|
388
|
-
const appendUniqueProperties = (target, properties) => {
|
|
389
|
-
const seen = new Set(target.map(property => `${property.key.toLowerCase()}\u0000${property.value}`));
|
|
390
|
-
properties.forEach(property => {
|
|
391
|
-
const identity = `${property.key.toLowerCase()}\u0000${property.value}`;
|
|
392
|
-
if (seen.has(identity)) {
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
seen.add(identity);
|
|
396
|
-
target.push(property);
|
|
397
|
-
});
|
|
398
|
-
};
|
|
399
|
-
const collectEntities = (streams, type) => {
|
|
400
|
-
const entities = new Map();
|
|
401
|
-
streams.forEach(stream => {
|
|
402
|
-
const role = entityRoleForStream(stream, type);
|
|
403
|
-
if (!role) {
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
const entityPath = streamEntityPath(stream, role);
|
|
407
|
-
const key = `${role}:${entityPath.toLowerCase()}`;
|
|
408
|
-
const existing = entities.get(key);
|
|
409
|
-
const entity = existing || {
|
|
410
|
-
id: key,
|
|
411
|
-
name: inferEntityName(stream, role, entityPath),
|
|
412
|
-
role,
|
|
413
|
-
path: entityPath,
|
|
414
|
-
streamCount: 0,
|
|
415
|
-
byteLength: 0,
|
|
416
|
-
properties: [],
|
|
417
|
-
pins: [],
|
|
418
|
-
layers: [],
|
|
419
|
-
keywords: []
|
|
420
|
-
};
|
|
421
|
-
entity.streamCount += 1;
|
|
422
|
-
entity.byteLength += stream.size;
|
|
423
|
-
appendUniqueProperties(entity.properties, stream.properties);
|
|
424
|
-
splitListValue(getPropertyValue(stream.properties, ['Pins', 'Pin', 'Pin Numbers'])).forEach(pin => uniquePush(entity.pins, pin, 96));
|
|
425
|
-
splitListValue(getPropertyValue(stream.properties, ['Layers', 'Layer'])).forEach(layer => uniquePush(entity.layers, layer, 64));
|
|
426
|
-
roleKeywords(stream, role).forEach(keyword => uniquePush(entity.keywords, keyword, 16));
|
|
427
|
-
entity.description || (entity.description = getPropertyValue(entity.properties, ['Description', 'Desc']));
|
|
428
|
-
entity.footprint || (entity.footprint = getPropertyValue(entity.properties, ['Footprint', 'PCB Footprint', 'Package']));
|
|
429
|
-
entities.set(key, entity);
|
|
430
|
-
});
|
|
431
|
-
return Array.from(entities.values()).sort((left, right) => {
|
|
432
|
-
var _a, _b;
|
|
433
|
-
const roleOrder = { symbol: 0, footprint: 1, padstack: 2, drawing: 3 };
|
|
434
|
-
const order = ((_a = roleOrder[left.role]) !== null && _a !== void 0 ? _a : 9) - ((_b = roleOrder[right.role]) !== null && _b !== void 0 ? _b : 9);
|
|
435
|
-
return order || left.name.localeCompare(right.name);
|
|
436
|
-
});
|
|
437
|
-
};
|
|
438
|
-
const collectMetadata = (streams) => {
|
|
439
|
-
const metadata = [];
|
|
440
|
-
streams.forEach(stream => {
|
|
441
|
-
if (stream.role === 'metadata' || stream.role === 'library' || stream.role === 'drawing') {
|
|
442
|
-
appendUniqueProperties(metadata, stream.properties);
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
return metadata.slice(0, 80);
|
|
446
|
-
};
|
|
447
|
-
const buildStats = (streams, entities, strings, parser) => {
|
|
448
|
-
const stats = {
|
|
449
|
-
textStreams: streams.filter(stream => stream.kind === 'text').length,
|
|
450
|
-
binaryStreams: streams.filter(stream => stream.kind === 'binary').length,
|
|
451
|
-
storageEntries: streams.filter(stream => stream.kind === 'storage').length,
|
|
452
|
-
propertyCount: streams.reduce((sum, stream) => sum + stream.properties.length, 0),
|
|
453
|
-
stringCount: strings.length,
|
|
454
|
-
symbolCount: entities.filter(entity => entity.role === 'symbol').length,
|
|
455
|
-
footprintCount: entities.filter(entity => entity.role === 'footprint').length,
|
|
456
|
-
padstackCount: entities.filter(entity => entity.role === 'padstack').length,
|
|
457
|
-
confidence: 'low'
|
|
458
|
-
};
|
|
459
|
-
if (parser === 'cfb' && entities.length && stats.propertyCount) {
|
|
460
|
-
stats.confidence = 'high';
|
|
461
|
-
}
|
|
462
|
-
else if (parser === 'cfb' || strings.length || stats.propertyCount) {
|
|
463
|
-
stats.confidence = 'medium';
|
|
464
|
-
}
|
|
465
|
-
return stats;
|
|
466
|
-
};
|
|
467
|
-
const buildDiagnostics = (type, parser, streams, entities, strings, warnings) => {
|
|
468
|
-
const diagnostics = warnings.map((message, index) => ({
|
|
469
|
-
level: 'warning',
|
|
470
|
-
code: `warning-${index + 1}`,
|
|
471
|
-
message
|
|
472
|
-
}));
|
|
473
|
-
diagnostics.push({
|
|
474
|
-
level: 'info',
|
|
475
|
-
code: 'parser',
|
|
476
|
-
message: parser === 'cfb'
|
|
477
|
-
? '已识别为 Microsoft Compound File / OLE2 复合文档容器,并在浏览器端解析目录与流。'
|
|
478
|
-
: '未识别为 CFB 容器,已使用二进制字符串索引模式展示可读信息。'
|
|
479
|
-
});
|
|
480
|
-
diagnostics.push({
|
|
481
|
-
level: 'info',
|
|
482
|
-
code: 'coverage',
|
|
483
|
-
message: `已索引 ${streams.length} 个条目、${strings.length} 个可读字符串、${entities.length} 个 EDA 结构候选。`
|
|
484
|
-
});
|
|
485
|
-
const needsSymbol = type === 'olb' && !entities.some(entity => entity.role === 'symbol');
|
|
486
|
-
const needsFootprint = type === 'dra' && !entities.some(entity => entity.role === 'footprint' || entity.role === 'padstack');
|
|
487
|
-
if (needsSymbol || needsFootprint) {
|
|
488
|
-
diagnostics.push({
|
|
489
|
-
level: 'warning',
|
|
490
|
-
code: 'domain-candidates',
|
|
491
|
-
message: type === 'olb'
|
|
492
|
-
? '未发现明确的元件符号候选,文件可能使用了私有二进制编码或需要专业工具导出 ASCII/XML 后再检查。'
|
|
493
|
-
: '未发现明确的封装、图形或 padstack 候选,文件可能使用了私有二进制数据库编码。'
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
return diagnostics;
|
|
497
|
-
};
|
|
498
|
-
const assembleResult = (buffer, type, parser, streamCount, streams, strings, warnings) => {
|
|
499
|
-
const totalStreamBytes = streams.reduce((sum, stream) => sum + stream.size, 0);
|
|
500
|
-
const entities = collectEntities(streams, type);
|
|
501
|
-
const metadata = collectMetadata(streams);
|
|
502
|
-
const diagnostics = buildDiagnostics(type, parser, streams, entities, strings, warnings);
|
|
503
|
-
return {
|
|
504
|
-
type,
|
|
505
|
-
parser,
|
|
506
|
-
title: type === 'olb'
|
|
507
|
-
? (parser === 'cfb' ? 'OrCAD Capture Symbol Library' : 'OLB Binary Library')
|
|
508
|
-
: (parser === 'cfb' ? 'OrCAD / Allegro Drawing Library' : 'DRA Binary Drawing'),
|
|
509
|
-
byteLength: buffer.byteLength,
|
|
510
|
-
streamCount,
|
|
511
|
-
totalStreamBytes,
|
|
512
|
-
streams,
|
|
513
|
-
tree: buildTree(streams, type),
|
|
514
|
-
entities,
|
|
515
|
-
metadata,
|
|
516
|
-
strings,
|
|
517
|
-
warnings,
|
|
518
|
-
diagnostics,
|
|
519
|
-
stats: buildStats(streams, entities, strings, parser)
|
|
520
|
-
};
|
|
521
|
-
};
|
|
522
|
-
const parseCfbContainer = async (buffer, type) => {
|
|
523
|
-
const CFB = await import('cfb');
|
|
524
|
-
const container = CFB.parse(toBytes(buffer), { type: 'array' });
|
|
525
|
-
const streamEntries = container.FileIndex
|
|
526
|
-
.map((entry, index) => ({ entry, path: container.FullPaths[index] || entry.name }))
|
|
527
|
-
.filter(item => item.entry.type !== 5 && item.path !== '/' && item.entry.name)
|
|
528
|
-
.slice(0, MAX_STREAMS);
|
|
529
|
-
const byteChunks = [];
|
|
530
|
-
const streams = streamEntries.map(({ entry, path }) => {
|
|
531
|
-
if (entry.type === 1) {
|
|
532
|
-
return buildStreamView(type, path, entry.name, entry.size || 0, 'storage');
|
|
533
|
-
}
|
|
534
|
-
const content = normalizeBytes(entry.content || []);
|
|
535
|
-
byteChunks.push(content);
|
|
536
|
-
return buildStreamView(type, path, entry.name, entry.size || content.byteLength || 0, 'binary', content);
|
|
537
|
-
});
|
|
538
|
-
const warnings = streamEntries.length >= MAX_STREAMS
|
|
539
|
-
? [`仅展示前 ${MAX_STREAMS} 个 CFB 项,完整文件仍可下载后在专业 EDA 工具中打开。`]
|
|
540
|
-
: [];
|
|
541
|
-
return assembleResult(buffer, type, 'cfb', container.FileIndex.length, streams, collectStrings(byteChunks), warnings);
|
|
542
|
-
};
|
|
543
|
-
const parseBinaryFallback = (buffer, type) => {
|
|
544
|
-
const bytes = toBytes(buffer);
|
|
545
|
-
const stream = buildStreamView(type, `${type}.${type}`, `${type}.${type}`, buffer.byteLength, 'binary', bytes);
|
|
546
|
-
return assembleResult(buffer, type, 'binary', 1, [stream], collectStrings([bytes]), [
|
|
547
|
-
'该文件不是标准 CFB 容器,已退化为安全的二进制字符串索引预览。'
|
|
548
|
-
]);
|
|
549
|
-
};
|
|
550
|
-
export const parseEdaFile = async (buffer, type = 'olb') => {
|
|
551
|
-
const normalizedType = type === 'dra' ? 'dra' : 'olb';
|
|
552
|
-
const bytes = toBytes(buffer);
|
|
553
|
-
if (!isCfbFile(bytes)) {
|
|
554
|
-
return parseBinaryFallback(buffer, normalizedType);
|
|
555
|
-
}
|
|
556
|
-
try {
|
|
557
|
-
return await parseCfbContainer(buffer, normalizedType);
|
|
558
|
-
}
|
|
559
|
-
catch (error) {
|
|
560
|
-
const fallback = parseBinaryFallback(buffer, normalizedType);
|
|
561
|
-
fallback.warnings.unshift(error instanceof Error ? error.message : String(error));
|
|
562
|
-
fallback.diagnostics.unshift({
|
|
563
|
-
level: 'warning',
|
|
564
|
-
code: 'cfb-parse-failed',
|
|
565
|
-
message: error instanceof Error ? error.message : String(error)
|
|
566
|
-
});
|
|
567
|
-
return fallback;
|
|
568
|
-
}
|
|
569
|
-
};
|