altium-toolkit 1.1.22 → 1.1.23
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 +34 -5
- package/docs/api.md +134 -23
- package/docs/model-format.md +174 -20
- package/docs/schemas/altium_toolkit/embedded_assets_a1.schema.json +56 -0
- package/docs/schemas/altium_toolkit/fixture_coverage_matrix_a1.schema.json +89 -0
- package/docs/schemas/altium_toolkit/geometry_bounds_a1.schema.json +86 -0
- package/docs/schemas/altium_toolkit/library_catalog_a1.schema.json +65 -0
- package/docs/schemas/altium_toolkit/library_diff_a1.schema.json +54 -0
- package/docs/schemas/altium_toolkit/library_inspection_a1.schema.json +94 -0
- package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +4 -0
- package/docs/schemas/altium_toolkit/native_stream_inventory_a1.schema.json +66 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +511 -1
- package/docs/schemas/altium_toolkit/parameter_record_inventory_a1.schema.json +84 -0
- package/docs/schemas/altium_toolkit/parser_diagnostics_a1.schema.json +63 -0
- package/docs/schemas/altium_toolkit/parser_value_verification_a1.schema.json +74 -0
- package/docs/schemas/altium_toolkit/pcb_class_report_a1.schema.json +79 -0
- package/docs/schemas/altium_toolkit/pcb_inspection_a1.schema.json +65 -0
- package/docs/schemas/altium_toolkit/pcb_net_membership_a1.schema.json +98 -0
- package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +3 -0
- package/docs/schemas/altium_toolkit/project_hierarchy_a1.schema.json +79 -0
- package/docs/schemas/altium_toolkit/unsupported_features_a1.schema.json +212 -0
- package/docs/testing.md +2 -0
- package/examples/README.md +21 -0
- package/examples/cli-utils.mjs +148 -0
- package/examples/corpus-smoke.mjs +523 -0
- package/examples/extract-bom.mjs +47 -0
- package/examples/generate-pnp.mjs +59 -0
- package/examples/inspect-board.mjs +70 -0
- package/examples/inspect-schematic.mjs +406 -0
- package/examples/library-catalog.mjs +115 -0
- package/examples/net-report.mjs +61 -0
- package/examples/validate-library.mjs +59 -0
- package/package.json +1 -1
- package/src/core/BinaryReader.mjs +213 -2
- package/src/core/altium/AltiumParser.mjs +352 -14
- package/src/core/altium/AltiumUnits.mjs +205 -0
- package/src/core/altium/AsciiRecordParser.mjs +9 -0
- package/src/core/altium/EmbeddedAssetReportBuilder.mjs +383 -0
- package/src/core/altium/FixtureCoverageMatrixBuilder.mjs +304 -0
- package/src/core/altium/GeometryBoundsReportBuilder.mjs +935 -0
- package/src/core/altium/LibraryCatalogArtifactBuilder.mjs +296 -0
- package/src/core/altium/LibraryDiffReportBuilder.mjs +260 -0
- package/src/core/altium/LibraryInspectionReportBuilder.mjs +156 -0
- package/src/core/altium/LibraryQaReportBuilder.mjs +374 -1
- package/src/core/altium/NativeStreamInventoryBuilder.mjs +177 -0
- package/src/core/altium/NormalizedModelSchema.mjs +3 -31
- package/src/core/altium/ParameterCollection.mjs +431 -0
- package/src/core/altium/ParameterRecordInventoryBuilder.mjs +274 -0
- package/src/core/altium/ParserCompatibilityFuzzer.mjs +106 -2
- package/src/core/altium/ParserDiagnosticNormalizer.mjs +213 -0
- package/src/core/altium/ParserErrors.mjs +90 -0
- package/src/core/altium/ParserFieldCoverageReportBuilder.mjs +656 -0
- package/src/core/altium/ParserUtils.mjs +24 -0
- package/src/core/altium/ParserValueVerificationReportBuilder.mjs +323 -0
- package/src/core/altium/PcbClassReportBuilder.mjs +366 -0
- package/src/core/altium/PcbInspectionReportBuilder.mjs +313 -0
- package/src/core/altium/PcbLayerGroups.mjs +308 -0
- package/src/core/altium/PcbLayerStackCustomDataParser.mjs +183 -0
- package/src/core/altium/PcbLayerStackInterchangeParser.mjs +473 -4
- package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +83 -15
- package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +74 -4
- package/src/core/altium/PcbLibModelParser.mjs +20 -4
- package/src/core/altium/PcbLibStreamExtractor.mjs +49 -6
- package/src/core/altium/PcbModelParser.mjs +223 -4
- package/src/core/altium/PcbNetMembershipReportBuilder.mjs +270 -0
- package/src/core/altium/PcbStreamExtractor.mjs +130 -6
- package/src/core/altium/PcbTrackPrimitiveParser.mjs +66 -2
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +15 -0
- package/src/core/altium/ProjectHierarchyReportBuilder.mjs +660 -0
- package/src/core/altium/ProjectNetlistExporter.mjs +2 -0
- package/src/core/altium/RawDataPreservationReportBuilder.mjs +348 -0
- package/src/core/altium/SchLibModelParser.mjs +840 -0
- package/src/core/altium/SchLibStreamExtractor.mjs +586 -0
- package/src/core/altium/SchematicBusEntryParser.mjs +3 -2
- package/src/core/altium/SchematicCodeSymbolParser.mjs +663 -0
- package/src/core/altium/SchematicConnectivityQaBuilder.mjs +177 -2
- package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +10 -1
- package/src/core/altium/SchematicFieldCoverageReportBuilder.mjs +549 -0
- package/src/core/altium/SchematicHarnessParser.mjs +9 -3
- package/src/core/altium/SchematicHyperlinkParser.mjs +122 -0
- package/src/core/altium/SchematicNetlistBuilder.mjs +271 -8
- package/src/core/altium/SchematicOwnershipGraphParser.mjs +102 -3
- package/src/core/altium/SchematicPinParser.mjs +12 -45
- package/src/core/altium/SchematicPrimitiveParser.mjs +9 -14
- package/src/core/altium/SchematicQaReportBuilder.mjs +2 -0
- package/src/core/altium/SchematicRecordStreamParser.mjs +183 -0
- package/src/core/altium/SchematicRecordTypeRegistry.mjs +6 -1
- package/src/core/altium/SchematicSheetParser.mjs +8 -2
- package/src/core/altium/SchematicStreamExtractor.mjs +64 -25
- package/src/core/altium/SchematicTextOrientationResolver.mjs +76 -0
- package/src/core/altium/SchematicTextParser.mjs +28 -12
- package/src/core/altium/SchematicTextRunParser.mjs +81 -0
- package/src/core/altium/SchematicThumbnailParser.mjs +425 -0
- package/src/core/altium/UnsupportedFeatureReportBuilder.mjs +380 -0
- package/src/parser.mjs +35 -1
- package/src/renderers.mjs +1 -0
- package/src/ui/SchematicShapeRenderer.mjs +49 -6
- package/src/ui/SchematicSvgRenderer.mjs +37 -8
- package/src/ui/SchematicTypography.mjs +4 -3
|
@@ -0,0 +1,425 @@
|
|
|
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
|
+
|
|
8
|
+
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extracts schematic preview thumbnail payloads from OLE-backed schematics.
|
|
12
|
+
*/
|
|
13
|
+
export class SchematicThumbnailParser {
|
|
14
|
+
/**
|
|
15
|
+
* Parses thumbnail metadata from a schematic document buffer.
|
|
16
|
+
* @param {ArrayBuffer} arrayBuffer Source file bytes.
|
|
17
|
+
* @returns {{ thumbnails: object[], diagnostics: object[] }}
|
|
18
|
+
*/
|
|
19
|
+
static parse(arrayBuffer) {
|
|
20
|
+
let oleDocument
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
oleDocument = OleCompoundDocument.fromArrayBuffer(arrayBuffer)
|
|
24
|
+
} catch {
|
|
25
|
+
return { thumbnails: [], diagnostics: [] }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return SchematicThumbnailParser.parseOleDocument(oleDocument)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parses thumbnail metadata from an OLE compound document.
|
|
33
|
+
* @param {OleCompoundDocument} oleDocument Parsed OLE document.
|
|
34
|
+
* @returns {{ thumbnails: object[], diagnostics: object[] }}
|
|
35
|
+
*/
|
|
36
|
+
static parseOleDocument(oleDocument) {
|
|
37
|
+
const diagnostics = []
|
|
38
|
+
const thumbnails = []
|
|
39
|
+
|
|
40
|
+
for (const streamName of oleDocument.listStreams()) {
|
|
41
|
+
if (!SchematicThumbnailParser.#isPreviewStream(streamName)) {
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const thumbnail = SchematicThumbnailParser.#parsePreviewStream(
|
|
46
|
+
streamName,
|
|
47
|
+
oleDocument.getStream(streamName),
|
|
48
|
+
diagnostics
|
|
49
|
+
)
|
|
50
|
+
if (thumbnail) {
|
|
51
|
+
thumbnails.push(thumbnail)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { thumbnails, diagnostics }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns true when one stream name can contain schematic preview data.
|
|
60
|
+
* @param {string} streamName OLE stream path.
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
static #isPreviewStream(streamName) {
|
|
64
|
+
return (
|
|
65
|
+
String(streamName || '')
|
|
66
|
+
.split('/')
|
|
67
|
+
.at(-1)
|
|
68
|
+
?.toLowerCase() === 'preview'
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parses one preview stream.
|
|
74
|
+
* @param {string} sourceStream OLE stream path.
|
|
75
|
+
* @param {Uint8Array} bytes Stream bytes.
|
|
76
|
+
* @param {object[]} diagnostics Diagnostic sink.
|
|
77
|
+
* @returns {object | null}
|
|
78
|
+
*/
|
|
79
|
+
static #parsePreviewStream(sourceStream, bytes, diagnostics) {
|
|
80
|
+
const fields = SchematicThumbnailParser.#parsePreviewFields(bytes)
|
|
81
|
+
const width = SchematicThumbnailParser.#positiveInteger(
|
|
82
|
+
fields.LargeImageWidth
|
|
83
|
+
)
|
|
84
|
+
const height = SchematicThumbnailParser.#positiveInteger(
|
|
85
|
+
fields.LargeImageHeight
|
|
86
|
+
)
|
|
87
|
+
const compressedBytes = SchematicThumbnailParser.#hexToBytes(
|
|
88
|
+
fields.LargeImage
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if (!width || !height || !compressedBytes) {
|
|
92
|
+
diagnostics.push(
|
|
93
|
+
SchematicThumbnailParser.#diagnostic(
|
|
94
|
+
sourceStream,
|
|
95
|
+
'schematic.thumbnail.preview-metadata',
|
|
96
|
+
'Schematic preview metadata was incomplete.'
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let pixelBytes
|
|
103
|
+
try {
|
|
104
|
+
pixelBytes = unzlibSync(compressedBytes)
|
|
105
|
+
} catch {
|
|
106
|
+
diagnostics.push(
|
|
107
|
+
SchematicThumbnailParser.#diagnostic(
|
|
108
|
+
sourceStream,
|
|
109
|
+
'schematic.thumbnail.preview-zlib',
|
|
110
|
+
'Schematic preview thumbnail payload could not be decompressed.'
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (pixelBytes.length < width * height * 4) {
|
|
117
|
+
diagnostics.push(
|
|
118
|
+
SchematicThumbnailParser.#diagnostic(
|
|
119
|
+
sourceStream,
|
|
120
|
+
'schematic.thumbnail.preview-size',
|
|
121
|
+
'Schematic preview thumbnail payload was shorter than its declared dimensions.'
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
kind: 'large-preview',
|
|
129
|
+
width,
|
|
130
|
+
height,
|
|
131
|
+
mimeType: 'image/png',
|
|
132
|
+
dataBase64: SchematicThumbnailParser.#encodeBase64(
|
|
133
|
+
SchematicThumbnailParser.#encodePreviewPng(
|
|
134
|
+
width,
|
|
135
|
+
height,
|
|
136
|
+
pixelBytes
|
|
137
|
+
)
|
|
138
|
+
),
|
|
139
|
+
sourceStream,
|
|
140
|
+
pixelFormat: 'bgra32'
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Parses key-value fields from the `[Preview]` section.
|
|
146
|
+
* @param {Uint8Array} bytes Stream bytes.
|
|
147
|
+
* @returns {Record<string, string>}
|
|
148
|
+
*/
|
|
149
|
+
static #parsePreviewFields(bytes) {
|
|
150
|
+
const text = new TextDecoder('windows-1252').decode(bytes)
|
|
151
|
+
const fields = {}
|
|
152
|
+
let inPreviewSection = false
|
|
153
|
+
|
|
154
|
+
for (const rawLine of text.split(/\r?\n/u)) {
|
|
155
|
+
const line = rawLine.trim()
|
|
156
|
+
if (!line || line.startsWith(';') || line.startsWith('#')) {
|
|
157
|
+
continue
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const sectionMatch = line.match(/^\[([^\]]+)\]$/u)
|
|
161
|
+
if (sectionMatch) {
|
|
162
|
+
inPreviewSection =
|
|
163
|
+
sectionMatch[1].trim().toLowerCase() === 'preview'
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!inPreviewSection) {
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const separatorIndex = line.indexOf('=')
|
|
172
|
+
if (separatorIndex <= 0) {
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fields[line.slice(0, separatorIndex).trim()] = line
|
|
177
|
+
.slice(separatorIndex + 1)
|
|
178
|
+
.trim()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return fields
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Parses a positive integer field.
|
|
186
|
+
* @param {string | undefined} value Raw value.
|
|
187
|
+
* @returns {number | null}
|
|
188
|
+
*/
|
|
189
|
+
static #positiveInteger(value) {
|
|
190
|
+
const parsed = Number.parseInt(String(value || ''), 10)
|
|
191
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Decodes a hexadecimal byte string.
|
|
196
|
+
* @param {string | undefined} value Hexadecimal payload.
|
|
197
|
+
* @returns {Uint8Array | null}
|
|
198
|
+
*/
|
|
199
|
+
static #hexToBytes(value) {
|
|
200
|
+
const hex = String(value || '').replace(/\s+/gu, '')
|
|
201
|
+
if (!hex || hex.length % 2 || /[^0-9a-f]/iu.test(hex)) {
|
|
202
|
+
return null
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const bytes = new Uint8Array(hex.length / 2)
|
|
206
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
207
|
+
bytes[index] = Number.parseInt(
|
|
208
|
+
hex.slice(index * 2, index * 2 + 2),
|
|
209
|
+
16
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return bytes
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Encodes BGRX/BGRA preview pixels into a PNG payload.
|
|
218
|
+
* @param {number} width Pixel width.
|
|
219
|
+
* @param {number} height Pixel height.
|
|
220
|
+
* @param {Uint8Array} pixels Source pixels.
|
|
221
|
+
* @returns {Uint8Array}
|
|
222
|
+
*/
|
|
223
|
+
static #encodePreviewPng(width, height, pixels) {
|
|
224
|
+
const rowStride = width * 4
|
|
225
|
+
const raw = new Uint8Array((rowStride + 1) * height)
|
|
226
|
+
|
|
227
|
+
for (let y = 0; y < height; y += 1) {
|
|
228
|
+
const sourceY = height - y - 1
|
|
229
|
+
const rowOffset = y * (rowStride + 1)
|
|
230
|
+
raw[rowOffset] = 0
|
|
231
|
+
|
|
232
|
+
for (let x = 0; x < width; x += 1) {
|
|
233
|
+
const sourceOffset = (sourceY * width + x) * 4
|
|
234
|
+
const targetOffset = rowOffset + 1 + x * 4
|
|
235
|
+
raw[targetOffset] = pixels[sourceOffset + 2]
|
|
236
|
+
raw[targetOffset + 1] = pixels[sourceOffset + 1]
|
|
237
|
+
raw[targetOffset + 2] = pixels[sourceOffset]
|
|
238
|
+
raw[targetOffset + 3] = pixels[sourceOffset + 3] || 255
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return SchematicThumbnailParser.#concatByteArrays([
|
|
243
|
+
Uint8Array.from(PNG_SIGNATURE),
|
|
244
|
+
SchematicThumbnailParser.#pngChunk(
|
|
245
|
+
'IHDR',
|
|
246
|
+
SchematicThumbnailParser.#pngHeader(width, height)
|
|
247
|
+
),
|
|
248
|
+
SchematicThumbnailParser.#pngChunk(
|
|
249
|
+
'IDAT',
|
|
250
|
+
SchematicThumbnailParser.#zlibStore(raw)
|
|
251
|
+
),
|
|
252
|
+
SchematicThumbnailParser.#pngChunk('IEND', new Uint8Array())
|
|
253
|
+
])
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Builds PNG IHDR chunk data.
|
|
258
|
+
* @param {number} width Pixel width.
|
|
259
|
+
* @param {number} height Pixel height.
|
|
260
|
+
* @returns {Uint8Array}
|
|
261
|
+
*/
|
|
262
|
+
static #pngHeader(width, height) {
|
|
263
|
+
const header = new Uint8Array(13)
|
|
264
|
+
const view = new DataView(header.buffer)
|
|
265
|
+
|
|
266
|
+
view.setUint32(0, width, false)
|
|
267
|
+
view.setUint32(4, height, false)
|
|
268
|
+
header[8] = 8
|
|
269
|
+
header[9] = 6
|
|
270
|
+
|
|
271
|
+
return header
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Builds one PNG chunk.
|
|
276
|
+
* @param {string} type Four-byte chunk type.
|
|
277
|
+
* @param {Uint8Array} data Chunk data.
|
|
278
|
+
* @returns {Uint8Array}
|
|
279
|
+
*/
|
|
280
|
+
static #pngChunk(type, data) {
|
|
281
|
+
const typeBytes = new TextEncoder().encode(type)
|
|
282
|
+
const chunk = new Uint8Array(12 + data.length)
|
|
283
|
+
const view = new DataView(chunk.buffer)
|
|
284
|
+
|
|
285
|
+
view.setUint32(0, data.length, false)
|
|
286
|
+
chunk.set(typeBytes, 4)
|
|
287
|
+
chunk.set(data, 8)
|
|
288
|
+
view.setUint32(
|
|
289
|
+
8 + data.length,
|
|
290
|
+
SchematicThumbnailParser.#crc32(
|
|
291
|
+
SchematicThumbnailParser.#concatByteArrays([typeBytes, data])
|
|
292
|
+
),
|
|
293
|
+
false
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
return chunk
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Encodes raw scanlines as a stored zlib stream.
|
|
301
|
+
* @param {Uint8Array} bytes Raw scanline bytes.
|
|
302
|
+
* @returns {Uint8Array}
|
|
303
|
+
*/
|
|
304
|
+
static #zlibStore(bytes) {
|
|
305
|
+
const blockCount = Math.max(1, Math.ceil(bytes.length / 65535))
|
|
306
|
+
const output = new Uint8Array(2 + blockCount * 5 + bytes.length + 4)
|
|
307
|
+
const view = new DataView(output.buffer)
|
|
308
|
+
let outputOffset = 0
|
|
309
|
+
let inputOffset = 0
|
|
310
|
+
|
|
311
|
+
output[outputOffset] = 0x78
|
|
312
|
+
output[outputOffset + 1] = 0x01
|
|
313
|
+
outputOffset += 2
|
|
314
|
+
|
|
315
|
+
for (let blockIndex = 0; blockIndex < blockCount; blockIndex += 1) {
|
|
316
|
+
const remaining = bytes.length - inputOffset
|
|
317
|
+
const blockLength = Math.min(remaining, 65535)
|
|
318
|
+
output[outputOffset] = blockIndex === blockCount - 1 ? 0x01 : 0x00
|
|
319
|
+
view.setUint16(outputOffset + 1, blockLength, true)
|
|
320
|
+
view.setUint16(outputOffset + 3, 0xffff ^ blockLength, true)
|
|
321
|
+
outputOffset += 5
|
|
322
|
+
output.set(
|
|
323
|
+
bytes.slice(inputOffset, inputOffset + blockLength),
|
|
324
|
+
outputOffset
|
|
325
|
+
)
|
|
326
|
+
outputOffset += blockLength
|
|
327
|
+
inputOffset += blockLength
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
view.setUint32(
|
|
331
|
+
outputOffset,
|
|
332
|
+
SchematicThumbnailParser.#adler32(bytes),
|
|
333
|
+
false
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return output
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Computes an Adler-32 checksum.
|
|
341
|
+
* @param {Uint8Array} bytes Bytes to checksum.
|
|
342
|
+
* @returns {number}
|
|
343
|
+
*/
|
|
344
|
+
static #adler32(bytes) {
|
|
345
|
+
let a = 1
|
|
346
|
+
let b = 0
|
|
347
|
+
|
|
348
|
+
for (const byte of bytes) {
|
|
349
|
+
a = (a + byte) % 65521
|
|
350
|
+
b = (b + a) % 65521
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return ((b << 16) | a) >>> 0
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Computes a PNG-compatible CRC-32 checksum.
|
|
358
|
+
* @param {Uint8Array} bytes Bytes to checksum.
|
|
359
|
+
* @returns {number}
|
|
360
|
+
*/
|
|
361
|
+
static #crc32(bytes) {
|
|
362
|
+
let crc = 0xffffffff
|
|
363
|
+
|
|
364
|
+
for (const byte of bytes) {
|
|
365
|
+
crc ^= byte
|
|
366
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
367
|
+
crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return (crc ^ 0xffffffff) >>> 0
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Concatenates byte arrays.
|
|
376
|
+
* @param {Uint8Array[]} chunks Byte arrays.
|
|
377
|
+
* @returns {Uint8Array}
|
|
378
|
+
*/
|
|
379
|
+
static #concatByteArrays(chunks) {
|
|
380
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
|
|
381
|
+
const output = new Uint8Array(totalLength)
|
|
382
|
+
let offset = 0
|
|
383
|
+
|
|
384
|
+
for (const chunk of chunks) {
|
|
385
|
+
output.set(chunk, offset)
|
|
386
|
+
offset += chunk.length
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return output
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Encodes one byte array as base64 in browser and Node runtimes.
|
|
394
|
+
* @param {Uint8Array} bytes Source bytes.
|
|
395
|
+
* @returns {string}
|
|
396
|
+
*/
|
|
397
|
+
static #encodeBase64(bytes) {
|
|
398
|
+
if (typeof Buffer !== 'undefined') {
|
|
399
|
+
return Buffer.from(bytes).toString('base64')
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let binary = ''
|
|
403
|
+
for (const byte of bytes) {
|
|
404
|
+
binary += String.fromCharCode(byte)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return btoa(binary)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Builds one parser diagnostic.
|
|
412
|
+
* @param {string} sourceStream Source stream name.
|
|
413
|
+
* @param {string} code Stable diagnostic code.
|
|
414
|
+
* @param {string} message User-facing diagnostic message.
|
|
415
|
+
* @returns {{ code: string, severity: string, sourceStream: string, message: string }}
|
|
416
|
+
*/
|
|
417
|
+
static #diagnostic(sourceStream, code, message) {
|
|
418
|
+
return {
|
|
419
|
+
code,
|
|
420
|
+
severity: 'warning',
|
|
421
|
+
sourceStream,
|
|
422
|
+
message
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|