altium-toolkit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +67 -0
- package/COMMERCIAL-LICENSE.md +20 -0
- package/CONTRIBUTING.md +19 -0
- package/LICENSE +22 -0
- package/LICENSES/CC-BY-SA-4.0.txt +170 -0
- package/LICENSES/GPL-3.0-or-later.txt +232 -0
- package/NOTICE.md +32 -0
- package/README.md +116 -0
- package/docs/api.md +73 -0
- package/docs/model-format.md +36 -0
- package/docs/testing.md +25 -0
- package/examples/README.md +47 -0
- package/examples/arduino-uno/PcbThreeSceneRenderer.mjs +635 -0
- package/examples/arduino-uno/SvgViewportController.mjs +306 -0
- package/examples/arduino-uno/example.mjs +480 -0
- package/examples/arduino-uno/index.html +163 -0
- package/examples/arduino-uno/styles.css +552 -0
- package/examples/server.mjs +212 -0
- package/package.json +53 -0
- package/spec/library-scope.md +32 -0
- package/src/core/BinaryReader.mjs +127 -0
- package/src/core/altium/AltiumLayoutParser.mjs +485 -0
- package/src/core/altium/AltiumParser.mjs +1007 -0
- package/src/core/altium/AsciiRecordParser.mjs +151 -0
- package/src/core/altium/ParserUtils.mjs +173 -0
- package/src/core/altium/PcbBinaryPrimitiveParser.mjs +424 -0
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +505 -0
- package/src/core/altium/PcbModelParser.mjs +336 -0
- package/src/core/altium/PcbOutlineRasterizer.mjs +852 -0
- package/src/core/altium/PcbOutlineRecovery.mjs +957 -0
- package/src/core/altium/PcbStreamExtractor.mjs +210 -0
- package/src/core/altium/PrintableTextDecoder.mjs +156 -0
- package/src/core/altium/SchematicAnnotationParser.mjs +220 -0
- package/src/core/altium/SchematicBusEntryParser.mjs +48 -0
- package/src/core/altium/SchematicDirectiveParser.mjs +47 -0
- package/src/core/altium/SchematicImageParser.mjs +173 -0
- package/src/core/altium/SchematicJunctionParser.mjs +43 -0
- package/src/core/altium/SchematicMultipartOwnerMatcher.mjs +564 -0
- package/src/core/altium/SchematicNetlistBuilder.mjs +351 -0
- package/src/core/altium/SchematicPinParser.mjs +767 -0
- package/src/core/altium/SchematicPrimitiveParser.mjs +716 -0
- package/src/core/altium/SchematicSheetParser.mjs +241 -0
- package/src/core/altium/SchematicSheetStyleResolver.mjs +46 -0
- package/src/core/altium/SchematicStandaloneCalloutNormalizer.mjs +592 -0
- package/src/core/altium/SchematicTextParser.mjs +708 -0
- package/src/core/altium/SchematicTextPostProcessor.mjs +801 -0
- package/src/core/ole/OleCompoundDocument.mjs +439 -0
- package/src/core/ole/OleConstants.mjs +64 -0
- package/src/core/ole/OleDirectoryEntry.mjs +95 -0
- package/src/index.mjs +7 -0
- package/src/parser.mjs +21 -0
- package/src/renderers.mjs +15 -0
- package/src/scene3d.mjs +9 -0
- package/src/styles/altium-renderers.css +358 -0
- package/src/ui/BomTableRenderer.mjs +46 -0
- package/src/ui/PcbArcUtils.mjs +189 -0
- package/src/ui/PcbEdgeFacingGlyphNormalizer.mjs +808 -0
- package/src/ui/PcbFootprintPrimitiveSelector.mjs +128 -0
- package/src/ui/PcbScene3dBuilder.mjs +742 -0
- package/src/ui/PcbScene3dModelRegistry.mjs +309 -0
- package/src/ui/PcbScene3dPackages.mjs +137 -0
- package/src/ui/PcbScene3dScenePreparator.mjs +36 -0
- package/src/ui/PcbScene3dSummaryRenderer.mjs +65 -0
- package/src/ui/PcbSvgRenderer.mjs +906 -0
- package/src/ui/SchematicColorResolver.mjs +132 -0
- package/src/ui/SchematicContentLayout.mjs +661 -0
- package/src/ui/SchematicDirectiveRenderer.mjs +184 -0
- package/src/ui/SchematicImageRenderer.mjs +135 -0
- package/src/ui/SchematicJunctionRenderer.mjs +381 -0
- package/src/ui/SchematicNoteRenderer.mjs +427 -0
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +173 -0
- package/src/ui/SchematicPinSvgRenderer.mjs +495 -0
- package/src/ui/SchematicPortRenderer.mjs +558 -0
- package/src/ui/SchematicPowerPortRenderer.mjs +574 -0
- package/src/ui/SchematicRegionRenderer.mjs +94 -0
- package/src/ui/SchematicShapeRenderer.mjs +398 -0
- package/src/ui/SchematicSheetChromeRenderer.mjs +1025 -0
- package/src/ui/SchematicSheetSymbolRenderer.mjs +228 -0
- package/src/ui/SchematicSvgRenderer.mjs +756 -0
- package/src/ui/SchematicSvgUtils.mjs +182 -0
- package/src/ui/SchematicTypography.mjs +204 -0
- package/src/workers/altium-parser.worker.mjs +29 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { createReadStream } from 'node:fs'
|
|
6
|
+
import { stat } from 'node:fs/promises'
|
|
7
|
+
import { createServer } from 'node:http'
|
|
8
|
+
import { extname, join, normalize, relative, resolve, sep } from 'node:path'
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
10
|
+
|
|
11
|
+
const DEFAULT_EXAMPLE_PATH = '/examples/arduino-uno/'
|
|
12
|
+
const DEFAULT_HOST = '127.0.0.1'
|
|
13
|
+
const DEFAULT_PORT = 4173
|
|
14
|
+
const CONTENT_TYPES = new Map([
|
|
15
|
+
['.css', 'text/css; charset=utf-8'],
|
|
16
|
+
['.html', 'text/html; charset=utf-8'],
|
|
17
|
+
['.js', 'text/javascript; charset=utf-8'],
|
|
18
|
+
['.json', 'application/json; charset=utf-8'],
|
|
19
|
+
['.mjs', 'text/javascript; charset=utf-8'],
|
|
20
|
+
['.svg', 'image/svg+xml; charset=utf-8'],
|
|
21
|
+
['.txt', 'text/plain; charset=utf-8']
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Serves the browser examples with Node's built-in HTTP server.
|
|
26
|
+
*/
|
|
27
|
+
export class ExampleServer {
|
|
28
|
+
/**
|
|
29
|
+
* Reads default server options from the environment.
|
|
30
|
+
* @param {NodeJS.ProcessEnv} env
|
|
31
|
+
* @returns {{ host: string, port: number, rootDirectory: string }}
|
|
32
|
+
*/
|
|
33
|
+
static defaultOptions(env = process.env) {
|
|
34
|
+
return {
|
|
35
|
+
host: env.HOST || DEFAULT_HOST,
|
|
36
|
+
port: ExampleServer.#parsePort(env.PORT),
|
|
37
|
+
rootDirectory: resolve(
|
|
38
|
+
fileURLToPath(new URL('..', import.meta.url))
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates an HTTP request handler rooted at the repository directory.
|
|
45
|
+
* @param {string} rootDirectory
|
|
46
|
+
* @returns {import('node:http').RequestListener}
|
|
47
|
+
*/
|
|
48
|
+
static createHandler(rootDirectory) {
|
|
49
|
+
const root = resolve(rootDirectory)
|
|
50
|
+
|
|
51
|
+
return async (request, response) => {
|
|
52
|
+
const filePath = await ExampleServer.resolveRequestPath(
|
|
53
|
+
root,
|
|
54
|
+
request.url || '/'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (!filePath) {
|
|
58
|
+
ExampleServer.#writeResponse(response, 403, 'Forbidden')
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const fileStat = await stat(filePath).catch(() => null)
|
|
63
|
+
if (!fileStat?.isFile()) {
|
|
64
|
+
ExampleServer.#writeResponse(response, 404, 'Not found')
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
response.writeHead(200, {
|
|
69
|
+
'content-type': ExampleServer.getContentType(filePath)
|
|
70
|
+
})
|
|
71
|
+
createReadStream(filePath).pipe(response)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolves a request URL to a file inside the served root.
|
|
77
|
+
* @param {string} rootDirectory
|
|
78
|
+
* @param {string} requestUrl
|
|
79
|
+
* @returns {Promise<string | null>}
|
|
80
|
+
*/
|
|
81
|
+
static async resolveRequestPath(rootDirectory, requestUrl) {
|
|
82
|
+
const parsedUrl = new URL(requestUrl, 'http://localhost')
|
|
83
|
+
const pathname =
|
|
84
|
+
parsedUrl.pathname === '/'
|
|
85
|
+
? DEFAULT_EXAMPLE_PATH
|
|
86
|
+
: parsedUrl.pathname
|
|
87
|
+
const requestedPath = ExampleServer.#resolveSafePath(
|
|
88
|
+
rootDirectory,
|
|
89
|
+
pathname
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if (!requestedPath) return null
|
|
93
|
+
|
|
94
|
+
const fileStat = await stat(requestedPath).catch(() => null)
|
|
95
|
+
if (fileStat?.isDirectory()) {
|
|
96
|
+
return join(requestedPath, 'index.html')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return requestedPath
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Returns a content type for a file path.
|
|
104
|
+
* @param {string} filePath
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
static getContentType(filePath) {
|
|
108
|
+
return (
|
|
109
|
+
CONTENT_TYPES.get(extname(filePath).toLowerCase()) ||
|
|
110
|
+
'application/octet-stream'
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Starts the local example server.
|
|
116
|
+
* @param {{ host?: string, port?: number, rootDirectory?: string, logger?: Pick<Console, 'log'> }} options
|
|
117
|
+
* @returns {Promise<{ server: import('node:http').Server, url: string, host: string, port: number, rootDirectory: string }>}
|
|
118
|
+
*/
|
|
119
|
+
static async start(options = {}) {
|
|
120
|
+
const defaults = ExampleServer.defaultOptions()
|
|
121
|
+
const host = options.host || defaults.host
|
|
122
|
+
const port = options.port ?? defaults.port
|
|
123
|
+
const rootDirectory = options.rootDirectory || defaults.rootDirectory
|
|
124
|
+
const logger = options.logger || console
|
|
125
|
+
const server = createServer(ExampleServer.createHandler(rootDirectory))
|
|
126
|
+
|
|
127
|
+
await new Promise((resolveStart, rejectStart) => {
|
|
128
|
+
server.once('error', rejectStart)
|
|
129
|
+
server.listen(port, host, () => {
|
|
130
|
+
server.off('error', rejectStart)
|
|
131
|
+
resolveStart()
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const address = server.address()
|
|
136
|
+
const resolvedPort =
|
|
137
|
+
typeof address === 'object' && address ? address.port : port
|
|
138
|
+
const url = 'http://' + host + ':' + resolvedPort + DEFAULT_EXAMPLE_PATH
|
|
139
|
+
|
|
140
|
+
logger.log('Serving Arduino Uno example at ' + url)
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
server,
|
|
144
|
+
url,
|
|
145
|
+
host,
|
|
146
|
+
port: resolvedPort,
|
|
147
|
+
rootDirectory
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Converts an environment port value into a listen port.
|
|
153
|
+
* @param {string | undefined} value
|
|
154
|
+
* @returns {number}
|
|
155
|
+
*/
|
|
156
|
+
static #parsePort(value) {
|
|
157
|
+
const port = Number(value || DEFAULT_PORT)
|
|
158
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
159
|
+
throw new Error('PORT must be an integer from 0 through 65535.')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return port
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Resolves a request path without allowing directory traversal.
|
|
167
|
+
* @param {string} rootDirectory
|
|
168
|
+
* @param {string} pathname
|
|
169
|
+
* @returns {string | null}
|
|
170
|
+
*/
|
|
171
|
+
static #resolveSafePath(rootDirectory, pathname) {
|
|
172
|
+
const root = resolve(rootDirectory)
|
|
173
|
+
const decodedPath = decodeURIComponent(pathname)
|
|
174
|
+
const normalizedPath = normalize('/' + decodedPath).replace(/^\/+/, '')
|
|
175
|
+
const filePath = resolve(root, normalizedPath)
|
|
176
|
+
const relativePath = relative(root, filePath)
|
|
177
|
+
|
|
178
|
+
if (
|
|
179
|
+
relativePath === '..' ||
|
|
180
|
+
relativePath.startsWith('..' + sep) ||
|
|
181
|
+
relativePath.startsWith('/')
|
|
182
|
+
) {
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return filePath
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Writes a plain-text status response.
|
|
191
|
+
* @param {import('node:http').ServerResponse} response
|
|
192
|
+
* @param {number} status
|
|
193
|
+
* @param {string} body
|
|
194
|
+
* @returns {void}
|
|
195
|
+
*/
|
|
196
|
+
static #writeResponse(response, status, body) {
|
|
197
|
+
response.writeHead(status, {
|
|
198
|
+
'content-type': 'text/plain; charset=utf-8'
|
|
199
|
+
})
|
|
200
|
+
response.end(body)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (
|
|
205
|
+
process.argv[1] &&
|
|
206
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
207
|
+
) {
|
|
208
|
+
ExampleServer.start().catch((error) => {
|
|
209
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
210
|
+
process.exitCode = 1
|
|
211
|
+
})
|
|
212
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "altium-toolkit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Altium document parsing and non-interactive rendering utilities",
|
|
5
|
+
"license": "GPL-3.0-or-later",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.mjs",
|
|
10
|
+
"./parser": "./src/parser.mjs",
|
|
11
|
+
"./renderers": "./src/renderers.mjs",
|
|
12
|
+
"./scene3d": "./src/scene3d.mjs",
|
|
13
|
+
"./workers/altium-parser.worker.mjs": "./src/workers/altium-parser.worker.mjs",
|
|
14
|
+
"./styles/altium-renderers.css": "./src/styles/altium-renderers.css"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"docs",
|
|
19
|
+
"examples",
|
|
20
|
+
"spec",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"LICENSES",
|
|
23
|
+
"COMMERCIAL-LICENSE.md",
|
|
24
|
+
"CONTRIBUTING.md",
|
|
25
|
+
"NOTICE.md",
|
|
26
|
+
"README.md",
|
|
27
|
+
"AGENTS.md"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"start": "node examples/server.mjs",
|
|
31
|
+
"test": "node --test tests/*.test.mjs tests/**/*.test.mjs",
|
|
32
|
+
"format": "prettier --write .",
|
|
33
|
+
"check:format": "prettier --check ."
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"fflate": "^0.8.2",
|
|
37
|
+
"three": "^0.183.2"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"prettier": "^3.4.2"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/SunboX/altium-toolkit.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/SunboX/altium-toolkit/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/SunboX/altium-toolkit#readme"
|
|
53
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 André Fiedler
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# Library Scope
|
|
8
|
+
|
|
9
|
+
Altium Toolkit provides reusable native Altium parsing and non-interactive
|
|
10
|
+
rendering primitives.
|
|
11
|
+
|
|
12
|
+
## In Scope
|
|
13
|
+
|
|
14
|
+
- `.SchDoc` and `.PcbDoc` parsing from `ArrayBuffer`
|
|
15
|
+
- OLE and binary stream helpers needed by parser recovery
|
|
16
|
+
- Schematic SVG rendering
|
|
17
|
+
- PCB SVG rendering
|
|
18
|
+
- BOM HTML rendering
|
|
19
|
+
- PCB 3D scene-description data
|
|
20
|
+
- Static 3D summary HTML
|
|
21
|
+
- Parser worker entrypoint for host applications
|
|
22
|
+
- Optional renderer CSS
|
|
23
|
+
|
|
24
|
+
## Out Of Scope
|
|
25
|
+
|
|
26
|
+
- Application state management
|
|
27
|
+
- File picker, drag/drop, or session orchestration
|
|
28
|
+
- Schematic/PCB pan and zoom event controllers
|
|
29
|
+
- Three.js runtime, OrbitControls, canvas mounting, and picking
|
|
30
|
+
- STEP mesh loading and browser script injection
|
|
31
|
+
- Model ZIP export UI and download orchestration
|
|
32
|
+
- Server, deployment, and app metadata endpoints
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reads little-endian primitive values from an ArrayBuffer with bounds checks.
|
|
7
|
+
*/
|
|
8
|
+
export class BinaryReader {
|
|
9
|
+
#arrayBuffer
|
|
10
|
+
|
|
11
|
+
#byteLength
|
|
12
|
+
|
|
13
|
+
#dataView
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {ArrayBuffer} arrayBuffer
|
|
17
|
+
*/
|
|
18
|
+
constructor(arrayBuffer) {
|
|
19
|
+
this.#arrayBuffer = arrayBuffer
|
|
20
|
+
this.#dataView = new DataView(arrayBuffer)
|
|
21
|
+
this.#byteLength = arrayBuffer.byteLength
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns the underlying byte length.
|
|
26
|
+
* @returns {number}
|
|
27
|
+
*/
|
|
28
|
+
get byteLength() {
|
|
29
|
+
return this.#byteLength
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reads one unsigned byte.
|
|
34
|
+
* @param {number} offset
|
|
35
|
+
* @returns {number}
|
|
36
|
+
*/
|
|
37
|
+
readUint8(offset) {
|
|
38
|
+
this.#assertReadable(offset, 1)
|
|
39
|
+
return this.#dataView.getUint8(offset)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reads one unsigned 16-bit integer.
|
|
44
|
+
* @param {number} offset
|
|
45
|
+
* @returns {number}
|
|
46
|
+
*/
|
|
47
|
+
readUint16(offset) {
|
|
48
|
+
this.#assertReadable(offset, 2)
|
|
49
|
+
return this.#dataView.getUint16(offset, true)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Reads one unsigned 32-bit integer.
|
|
54
|
+
* @param {number} offset
|
|
55
|
+
* @returns {number}
|
|
56
|
+
*/
|
|
57
|
+
readUint32(offset) {
|
|
58
|
+
this.#assertReadable(offset, 4)
|
|
59
|
+
return this.#dataView.getUint32(offset, true)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Reads one signed 32-bit integer.
|
|
64
|
+
* @param {number} offset
|
|
65
|
+
* @returns {number}
|
|
66
|
+
*/
|
|
67
|
+
readInt32(offset) {
|
|
68
|
+
this.#assertReadable(offset, 4)
|
|
69
|
+
return this.#dataView.getInt32(offset, true)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Reads one unsigned 64-bit integer as a JavaScript number.
|
|
74
|
+
* @param {number} offset
|
|
75
|
+
* @returns {number}
|
|
76
|
+
*/
|
|
77
|
+
readUint64(offset) {
|
|
78
|
+
this.#assertReadable(offset, 8)
|
|
79
|
+
|
|
80
|
+
const low = this.#dataView.getUint32(offset, true)
|
|
81
|
+
const high = this.#dataView.getUint32(offset + 4, true)
|
|
82
|
+
const value = high * 0x100000000 + low
|
|
83
|
+
|
|
84
|
+
if (!Number.isSafeInteger(value)) {
|
|
85
|
+
throw new RangeError(
|
|
86
|
+
'BinaryReader cannot represent an unsafe 64-bit integer.'
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return value
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Reads one byte slice.
|
|
95
|
+
* @param {number} offset
|
|
96
|
+
* @param {number} length
|
|
97
|
+
* @returns {Uint8Array}
|
|
98
|
+
*/
|
|
99
|
+
readBytes(offset, length) {
|
|
100
|
+
this.#assertReadable(offset, length)
|
|
101
|
+
return new Uint8Array(this.#arrayBuffer.slice(offset, offset + length))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Ensures one read stays inside the buffer.
|
|
106
|
+
* @param {number} offset
|
|
107
|
+
* @param {number} size
|
|
108
|
+
*/
|
|
109
|
+
#assertReadable(offset, size) {
|
|
110
|
+
const normalizedOffset = Number(offset)
|
|
111
|
+
const normalizedSize = Number(size)
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
!Number.isInteger(normalizedOffset) ||
|
|
115
|
+
normalizedOffset < 0 ||
|
|
116
|
+
normalizedOffset + normalizedSize > this.#byteLength
|
|
117
|
+
) {
|
|
118
|
+
throw new RangeError(
|
|
119
|
+
'BinaryReader read is out of bounds at offset ' +
|
|
120
|
+
normalizedOffset +
|
|
121
|
+
' for ' +
|
|
122
|
+
normalizedSize +
|
|
123
|
+
' byte(s).'
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|