altium-toolkit 1.0.8 → 1.0.10
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.md +18 -6
- package/docs/api.md +78 -16
- package/docs/model-format.md +229 -8
- package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +76 -0
- package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +35 -0
- package/docs/schemas/altium_toolkit/netlist_a1.schema.json +53 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +1826 -110
- package/docs/schemas/altium_toolkit/parser_compatibility_fuzz_a1.schema.json +25 -0
- package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +86 -0
- package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +63 -0
- package/docs/schemas/altium_toolkit/project_document_graph_a1.schema.json +33 -0
- package/docs/schemas/altium_toolkit/schematic_svg_semantics_a1.schema.json +50 -0
- package/docs/schemas/altium_toolkit/svg_model_cross_link_a1.schema.json +39 -0
- package/docs/testing.md +9 -3
- package/package.json +1 -1
- package/spec/library-scope.md +7 -1
- package/src/core/altium/AltiumLayoutParser.mjs +104 -8
- package/src/core/altium/AltiumParser.mjs +196 -45
- package/src/core/altium/CiArtifactBundleBuilder.mjs +202 -0
- package/src/core/altium/DraftsmanDigestParser.mjs +689 -0
- package/src/core/altium/EmbeddedFileInventoryBuilder.mjs +255 -0
- package/src/core/altium/IntLibModelParser.mjs +240 -0
- package/src/core/altium/IntLibStreamExtractor.mjs +366 -0
- package/src/core/altium/LibraryRenderManifestBuilder.mjs +417 -0
- package/src/core/altium/LibrarySearchIndex.mjs +215 -0
- package/src/core/altium/NormalizedModelSchema.mjs +36 -0
- package/src/core/altium/ParserCompatibilityFuzzer.mjs +192 -0
- package/src/core/altium/PcbCustomPadShapeParser.mjs +244 -0
- package/src/core/altium/PcbDefaultsParser.mjs +171 -0
- package/src/core/altium/PcbDimensionParser.mjs +229 -0
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +232 -6
- package/src/core/altium/PcbExtendedPrimitiveInformationParser.mjs +256 -0
- package/src/core/altium/PcbLibModelParser.mjs +235 -14
- package/src/core/altium/PcbLibStreamExtractor.mjs +62 -4
- package/src/core/altium/PcbMaskPasteResolver.mjs +354 -0
- package/src/core/altium/PcbMechanicalLayerPairParser.mjs +204 -0
- package/src/core/altium/PcbModelParser.mjs +495 -32
- package/src/core/altium/PcbOwnershipGraphBuilder.mjs +245 -0
- package/src/core/altium/PcbPadPrimitiveParser.mjs +78 -65
- package/src/core/altium/PcbPadStackParser.mjs +229 -2
- package/src/core/altium/PcbPickPlacePositionResolver.mjs +224 -0
- package/src/core/altium/PcbPrimitiveParameterParser.mjs +3 -2
- package/src/core/altium/PcbRawRecordRegistry.mjs +121 -130
- package/src/core/altium/PcbRegionPrimitiveParser.mjs +76 -3
- package/src/core/altium/PcbRouteAnalysisBuilder.mjs +730 -0
- package/src/core/altium/PcbRuleParser.mjs +354 -33
- package/src/core/altium/PcbSidecarRecordParser.mjs +177 -0
- package/src/core/altium/PcbSpecialStringResolver.mjs +220 -0
- package/src/core/altium/PcbStatisticsBuilder.mjs +541 -0
- package/src/core/altium/PcbStreamExtractor.mjs +111 -4
- package/src/core/altium/PcbTextPrimitiveParser.mjs +60 -0
- package/src/core/altium/PcbUnionParser.mjs +307 -0
- package/src/core/altium/PcbViaStackParser.mjs +98 -10
- package/src/core/altium/PcbViaStructureParser.mjs +335 -0
- package/src/core/altium/PrintableTextDecoder.mjs +53 -3
- package/src/core/altium/PrjPcbModelParser.mjs +281 -7
- package/src/core/altium/ProjectAnnotationParser.mjs +205 -0
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +492 -0
- package/src/core/altium/ProjectDocumentGraphBuilder.mjs +280 -0
- package/src/core/altium/ProjectNetlistExporter.mjs +503 -0
- package/src/core/altium/ProjectOutJobDigestBuilder.mjs +109 -0
- package/src/core/altium/ProjectVariantViewBuilder.mjs +334 -0
- package/src/core/altium/SchematicBindingProvenanceParser.mjs +223 -0
- package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +312 -0
- package/src/core/altium/SchematicComponentTextResolver.mjs +72 -19
- package/src/core/altium/SchematicConnectivityQaBuilder.mjs +271 -0
- package/src/core/altium/SchematicCrossSheetConnectorParser.mjs +140 -0
- package/src/core/altium/SchematicDirectiveParser.mjs +312 -0
- package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +231 -0
- package/src/core/altium/SchematicHarnessParser.mjs +302 -0
- package/src/core/altium/SchematicImageParser.mjs +474 -3
- package/src/core/altium/SchematicImplementationParser.mjs +518 -0
- package/src/core/altium/SchematicNetlistBuilder.mjs +15 -2
- package/src/core/altium/SchematicOwnershipGraphParser.mjs +195 -0
- package/src/core/altium/SchematicPinParser.mjs +84 -1
- package/src/core/altium/SchematicPrimitiveParser.mjs +301 -0
- package/src/core/altium/SchematicProjectParameterResolver.mjs +361 -0
- package/src/core/altium/SchematicQaReportBuilder.mjs +284 -0
- package/src/core/altium/SchematicRecordTypeRegistry.mjs +137 -0
- package/src/core/altium/SchematicRepeatedChannelParser.mjs +229 -0
- package/src/core/altium/SchematicStreamExtractor.mjs +10 -1
- package/src/core/altium/SchematicTemplateParser.mjs +256 -0
- package/src/core/altium/SchematicTextParser.mjs +123 -0
- package/src/core/altium/SvgModelCrossLinkValidator.mjs +402 -0
- package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +136 -96
- package/src/core/circuit-json/CircuitJsonModelAdapterPcbElements.mjs +244 -0
- package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
- package/src/core/ole/OleCompoundDocument.mjs +20 -0
- package/src/parser.mjs +35 -0
- package/src/styles/altium-renderers.css +19 -0
- package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
- package/src/ui/PcbInteractionIndex.mjs +9 -4
- package/src/ui/PcbScene3dBuilder.mjs +137 -3
- package/src/ui/PcbScene3dModelRegistry.mjs +74 -0
- package/src/ui/PcbSvgRenderer.mjs +1252 -34
- package/src/ui/PcbTextPrimitiveRenderer.mjs +193 -7
- package/src/ui/SchematicNoteRenderer.mjs +9 -2
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +206 -0
- package/src/ui/SchematicShapeRenderer.mjs +362 -0
- package/src/ui/SchematicSvgRenderer.mjs +1442 -92
- package/src/ui/SchematicTypography.mjs +48 -5
- package/src/ui/TextGeometrySidecarBuilder.mjs +147 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { unzlibSync } from 'fflate'
|
|
6
|
+
import { OleCompoundDocument } from '../ole/OleCompoundDocument.mjs'
|
|
7
|
+
import { PcbSidecarRecordParser } from './PcbSidecarRecordParser.mjs'
|
|
8
|
+
import { PrintableTextDecoder } from './PrintableTextDecoder.mjs'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extracts read-only metadata and bundled source entries from integrated
|
|
12
|
+
* library compound documents.
|
|
13
|
+
*/
|
|
14
|
+
export class IntLibStreamExtractor {
|
|
15
|
+
/**
|
|
16
|
+
* Extracts integrated-library data directly from one OLE buffer.
|
|
17
|
+
* @param {ArrayBuffer} arrayBuffer
|
|
18
|
+
* @returns {ReturnType<typeof IntLibStreamExtractor.extractFromStreams> | null}
|
|
19
|
+
*/
|
|
20
|
+
static extractFromArrayBuffer(arrayBuffer) {
|
|
21
|
+
const compoundDocument =
|
|
22
|
+
OleCompoundDocument.fromArrayBuffer(arrayBuffer)
|
|
23
|
+
const streams = new Map()
|
|
24
|
+
|
|
25
|
+
for (const name of compoundDocument.listStreams()) {
|
|
26
|
+
streams.set(name, compoundDocument.getStream(name))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return IntLibStreamExtractor.extractFromStreams(streams)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extracts integrated-library metadata and source payloads from streams.
|
|
34
|
+
* @param {Map<string, Uint8Array>} streams
|
|
35
|
+
* @returns {{ version: string, crossReferences: object[], parameters: Record<string, string>, parameterRecords: object[], sources: object[], streamNames: string[], diagnostics: Record<string, number> }}
|
|
36
|
+
*/
|
|
37
|
+
static extractFromStreams(streams) {
|
|
38
|
+
const version = IntLibStreamExtractor.#decodeText(
|
|
39
|
+
streams.get('Version.Txt')
|
|
40
|
+
).trim()
|
|
41
|
+
const crossReferenceParse = IntLibStreamExtractor.#parseCrossReferences(
|
|
42
|
+
streams.get('LibCrossRef.Txt')
|
|
43
|
+
)
|
|
44
|
+
const crossReferences = crossReferenceParse.records
|
|
45
|
+
const parameterRecords = IntLibStreamExtractor.#parseParameterRecords(
|
|
46
|
+
streams.get('Parameters .bin')
|
|
47
|
+
)
|
|
48
|
+
const parameters =
|
|
49
|
+
IntLibStreamExtractor.#buildParameterMap(parameterRecords)
|
|
50
|
+
const sources = IntLibStreamExtractor.#extractSources(streams)
|
|
51
|
+
const streamNames = IntLibStreamExtractor.#collectUsedStreamNames(
|
|
52
|
+
streams,
|
|
53
|
+
sources
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
version,
|
|
58
|
+
crossReferences,
|
|
59
|
+
parameters,
|
|
60
|
+
parameterRecords,
|
|
61
|
+
sources,
|
|
62
|
+
streamNames,
|
|
63
|
+
diagnostics: {
|
|
64
|
+
crossReferenceCount: crossReferences.length,
|
|
65
|
+
parameterCount: Object.keys(parameters).length,
|
|
66
|
+
sourceCount: sources.length,
|
|
67
|
+
issues: crossReferenceParse.issues
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parses LibCrossRef.Txt into field records.
|
|
74
|
+
* @param {Uint8Array | undefined} bytes
|
|
75
|
+
* @returns {{ records: object[], issues: object[] }}
|
|
76
|
+
*/
|
|
77
|
+
static #parseCrossReferences(bytes) {
|
|
78
|
+
const records = []
|
|
79
|
+
const issues = []
|
|
80
|
+
|
|
81
|
+
IntLibStreamExtractor.#decodeText(bytes)
|
|
82
|
+
.split(/\r?\n/u)
|
|
83
|
+
.map((line) => line.trim())
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.forEach((line, index) => {
|
|
86
|
+
const fields = PcbSidecarRecordParser.parseRecordFields(
|
|
87
|
+
new TextEncoder().encode(line)
|
|
88
|
+
)
|
|
89
|
+
const component = fields.COMPONENT || fields.COMPONENTNAME || ''
|
|
90
|
+
const model = fields.MODEL || fields.MODELNAME || ''
|
|
91
|
+
|
|
92
|
+
if (!Object.keys(fields).length || !component || !model) {
|
|
93
|
+
issues.push(
|
|
94
|
+
IntLibStreamExtractor.#issue(
|
|
95
|
+
'intlib.crossref.malformed-row',
|
|
96
|
+
'LibCrossRef.Txt',
|
|
97
|
+
index + 1,
|
|
98
|
+
'Skipped malformed integrated-library cross-reference row.'
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
records.push({
|
|
105
|
+
component,
|
|
106
|
+
model,
|
|
107
|
+
kind: fields.KIND || fields.TYPE || '',
|
|
108
|
+
fields
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
return { records, issues }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Builds a structured parser issue.
|
|
117
|
+
* @param {string} code Stable diagnostic code.
|
|
118
|
+
* @param {string} stream Source stream name.
|
|
119
|
+
* @param {number} line One-based line number.
|
|
120
|
+
* @param {string} message User-facing summary.
|
|
121
|
+
* @returns {object}
|
|
122
|
+
*/
|
|
123
|
+
static #issue(code, stream, line, message) {
|
|
124
|
+
return {
|
|
125
|
+
code,
|
|
126
|
+
severity: 'warning',
|
|
127
|
+
stream,
|
|
128
|
+
line,
|
|
129
|
+
message
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Parses integrated-library parameter records.
|
|
135
|
+
* @param {Uint8Array | undefined} bytes
|
|
136
|
+
* @returns {object[]}
|
|
137
|
+
*/
|
|
138
|
+
static #parseParameterRecords(bytes) {
|
|
139
|
+
const lengthPrefixed =
|
|
140
|
+
PcbSidecarRecordParser.parseLengthPrefixedRecords(
|
|
141
|
+
bytes,
|
|
142
|
+
'Parameters .bin'
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if (lengthPrefixed.length) {
|
|
146
|
+
return lengthPrefixed.map((record) => record.fields)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return IntLibStreamExtractor.#decodeText(bytes)
|
|
150
|
+
.split(/\r?\n/u)
|
|
151
|
+
.map((line) => line.trim())
|
|
152
|
+
.filter(Boolean)
|
|
153
|
+
.map((line) =>
|
|
154
|
+
PcbSidecarRecordParser.parseRecordFields(
|
|
155
|
+
new TextEncoder().encode(line)
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
.filter((fields) => Object.keys(fields).length)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Builds a parameter lookup from parameter records.
|
|
163
|
+
* @param {Record<string, string>[]} records
|
|
164
|
+
* @returns {Record<string, string>}
|
|
165
|
+
*/
|
|
166
|
+
static #buildParameterMap(records) {
|
|
167
|
+
const parameters = {}
|
|
168
|
+
|
|
169
|
+
for (const record of records) {
|
|
170
|
+
const name = record.NAME || record.PARAMETER || ''
|
|
171
|
+
if (!name) {
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
parameters[name] = record.VALUE || ''
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return parameters
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extracts bundled source entries from recognized source-library folders.
|
|
183
|
+
* @param {Map<string, Uint8Array>} streams
|
|
184
|
+
* @returns {object[]}
|
|
185
|
+
*/
|
|
186
|
+
static #extractSources(streams) {
|
|
187
|
+
const sources = []
|
|
188
|
+
|
|
189
|
+
for (const [path, bytes] of streams.entries()) {
|
|
190
|
+
const sourceKind = IntLibStreamExtractor.#sourceKind(path)
|
|
191
|
+
if (!sourceKind) {
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const payload = IntLibStreamExtractor.#decodePayload(bytes)
|
|
196
|
+
sources.push({
|
|
197
|
+
path,
|
|
198
|
+
fileName: IntLibStreamExtractor.#basename(path),
|
|
199
|
+
fileType: IntLibStreamExtractor.#sourceFileType(path),
|
|
200
|
+
libraryKind: sourceKind,
|
|
201
|
+
compression: payload.compression,
|
|
202
|
+
byteLength: payload.bytes.byteLength,
|
|
203
|
+
payloadBase64: IntLibStreamExtractor.#bytesToBase64(
|
|
204
|
+
payload.bytes
|
|
205
|
+
),
|
|
206
|
+
payloadText: IntLibStreamExtractor.#decodePrintablePayload(
|
|
207
|
+
payload.bytes
|
|
208
|
+
)
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return sources.sort((left, right) =>
|
|
213
|
+
left.path.localeCompare(right.path)
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Decodes a possibly wrapped zlib source payload.
|
|
219
|
+
* @param {Uint8Array} bytes
|
|
220
|
+
* @returns {{ bytes: Uint8Array, compression: string }}
|
|
221
|
+
*/
|
|
222
|
+
static #decodePayload(bytes) {
|
|
223
|
+
const normalized = PcbSidecarRecordParser.toUint8Array(bytes)
|
|
224
|
+
|
|
225
|
+
if (normalized[0] === 0x02 && normalized[1] === 0x78) {
|
|
226
|
+
return {
|
|
227
|
+
bytes: Uint8Array.from(unzlibSync(normalized.subarray(1))),
|
|
228
|
+
compression: 'zlib-wrapper'
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (normalized[0] === 0x78) {
|
|
233
|
+
return {
|
|
234
|
+
bytes: Uint8Array.from(unzlibSync(normalized)),
|
|
235
|
+
compression: 'zlib'
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
bytes: normalized,
|
|
241
|
+
compression: 'none'
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Decodes text bytes with toolkit printable-text fallbacks.
|
|
247
|
+
* @param {Uint8Array | undefined} bytes
|
|
248
|
+
* @returns {string}
|
|
249
|
+
*/
|
|
250
|
+
static #decodeText(bytes) {
|
|
251
|
+
if (!bytes) {
|
|
252
|
+
return ''
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return PrintableTextDecoder.decodeBytes(
|
|
256
|
+
PcbSidecarRecordParser.toUint8Array(bytes)
|
|
257
|
+
).replace(/^\uFEFF/u, '')
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Decodes source payload text only when it looks printable.
|
|
262
|
+
* @param {Uint8Array} bytes
|
|
263
|
+
* @returns {string}
|
|
264
|
+
*/
|
|
265
|
+
static #decodePrintablePayload(bytes) {
|
|
266
|
+
const text = IntLibStreamExtractor.#decodeText(bytes)
|
|
267
|
+
.replace(/\u0000/gu, '')
|
|
268
|
+
.trim()
|
|
269
|
+
if (!text || /[\u0001-\u0008\u000b\u000c\u000e-\u001f]/u.test(text)) {
|
|
270
|
+
return ''
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return text
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Resolves the source-library kind for one stream path.
|
|
278
|
+
* @param {string} path
|
|
279
|
+
* @returns {string}
|
|
280
|
+
*/
|
|
281
|
+
static #sourceKind(path) {
|
|
282
|
+
const normalized = String(path || '').replace(/\\/gu, '/')
|
|
283
|
+
|
|
284
|
+
if (/^SchLib\//iu.test(normalized)) {
|
|
285
|
+
return 'schematic-symbols'
|
|
286
|
+
}
|
|
287
|
+
if (/^PCBLib\//iu.test(normalized)) {
|
|
288
|
+
return 'pcb-footprints'
|
|
289
|
+
}
|
|
290
|
+
if (/^PCB3DLib\//iu.test(normalized)) {
|
|
291
|
+
return 'pcb-3d-models'
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return ''
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Resolves the source file type from one stream path.
|
|
299
|
+
* @param {string} path
|
|
300
|
+
* @returns {string}
|
|
301
|
+
*/
|
|
302
|
+
static #sourceFileType(path) {
|
|
303
|
+
const extension = IntLibStreamExtractor.#basename(path).split('.').pop()
|
|
304
|
+
|
|
305
|
+
if (/^SchLib$/iu.test(extension)) return 'SchLib'
|
|
306
|
+
if (/^PcbLib$/iu.test(extension)) return 'PcbLib'
|
|
307
|
+
if (/^PCB3DLib$/iu.test(extension)) return 'PCB3DLib'
|
|
308
|
+
|
|
309
|
+
return extension || 'unknown'
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Returns the final path segment.
|
|
314
|
+
* @param {string} path
|
|
315
|
+
* @returns {string}
|
|
316
|
+
*/
|
|
317
|
+
static #basename(path) {
|
|
318
|
+
return String(path || '')
|
|
319
|
+
.replace(/\\/gu, '/')
|
|
320
|
+
.split('/')
|
|
321
|
+
.pop()
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Collects streams that contributed to extraction.
|
|
326
|
+
* @param {Map<string, Uint8Array>} streams
|
|
327
|
+
* @param {object[]} sources
|
|
328
|
+
* @returns {string[]}
|
|
329
|
+
*/
|
|
330
|
+
static #collectUsedStreamNames(streams, sources) {
|
|
331
|
+
return [
|
|
332
|
+
'Version.Txt',
|
|
333
|
+
'LibCrossRef.Txt',
|
|
334
|
+
'Parameters .bin',
|
|
335
|
+
...sources.map((source) => source.path)
|
|
336
|
+
]
|
|
337
|
+
.filter((streamName) => streams.has(streamName))
|
|
338
|
+
.sort((left, right) => left.localeCompare(right))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Encodes bytes as base64 in browser and Node runtimes.
|
|
343
|
+
* @param {Uint8Array} bytes
|
|
344
|
+
* @returns {string}
|
|
345
|
+
*/
|
|
346
|
+
static #bytesToBase64(bytes) {
|
|
347
|
+
if (typeof btoa === 'function') {
|
|
348
|
+
let binary = ''
|
|
349
|
+
const chunkSize = 0x8000
|
|
350
|
+
|
|
351
|
+
for (
|
|
352
|
+
let offset = 0;
|
|
353
|
+
offset < bytes.byteLength;
|
|
354
|
+
offset += chunkSize
|
|
355
|
+
) {
|
|
356
|
+
binary += String.fromCharCode(
|
|
357
|
+
...bytes.subarray(offset, offset + chunkSize)
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return btoa(binary)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return Buffer.from(bytes).toString('base64')
|
|
365
|
+
}
|
|
366
|
+
}
|