-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathnet.kopischke.bytes.js
108 lines (100 loc) · 3.79 KB
/
net.kopischke.bytes.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: light-gray; icon-glyph: puzzle-piece;
/**
* Byte stream handling helpers for Scriptable.
* Source master repository on {@link https://github.com/kopischke/scriptable|GitHub}).
* @author Martin Kopischke <[email protected]>
* @version 1.1.1
* @license MIT
* @module
*/
/**
* The built in Uint8ClampedArray object.
* @external Uint8ClampedArray
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray|Uint8ClampedArray reference on MDN}
*/
/**
* Decodable sequence of bytes.
*
* @extends external:Uint8ClampedArray
* @example
* const { ByteSequence } = loadModule('net.kopischke.bytes.js')
* const bytes = ByteSequence.fromChars('Sanjûrokunin no jôkyaku')
* let decoded = bytes.toUTF8String() // 'Sanjûrokunin no jôkyaku'
*/
module.exports.ByteSequence = class ByteSequence extends Uint8ClampedArray {
/**
* Creates a new ByteSequence instance.
* @param {...string} bytes - The bytes to store in the sequence, in order.
*/
constructor () {
super(...arguments)
}
/**
* Creates a new ByteSequence from an array-like or iterable object.
* See also {@link Array.from()}.
* @returns {ByteSequence} The new ByteSequence object.
* @param {*} iterable - Any iterable primitive or object.
*/
static from (iterable) { // super.from throws TypeError for thisArg
let ary = Array.from(iterable)
return new ByteSequence(ary)
}
/**
* Creates a new ByteSequence with a variable number of arguments.
* See also {@link Array.of()}.
* @returns {ByteSequence} The new ByteSequence object.
* @param {...*} - Any number of primitives or objects.
*/
static of () { // super.of throws TypeError for thisArg
let ary = Array.of(...arguments)
return new ByteSequence(ary)
}
/**
* Create a new ByteSequence of character codes.
*
* This is a convenience initialiser equivalent to
* ByteSequence.from(str, c => c.charCodeAt(0)).
* @returns {ByteSequence}
* @param {string} str - The string representation of the byte sequence.
*/
static fromChars (str) {
return ByteSequence.from(str, c => c.charCodeAt(0))
}
/**
* Decode the byte sequence as UTF-8.
*
* Adapted from the code in {@link https://weblog.rogueamoeba.com/2017/02/27/javascript-correctly-converting-a-byte-array-to-a-utf-8-string/|“JavaScript: Correctly Converting a Byte Array to a UTF-8 String” by Ed Wynne},
* with less emphasis on bitwise operations and hex values, added errors and more efficient
* generation of the resulting string.
* @returns {string} The decoded string.
* @throws {RangeError} On invalid UTF-8 byte sequences.
* @see {@link https://en.wikipedia.org/wiki/UTF-8|“UTF-8” on Wikipedia}
*/
toUTF8String () {
const startBytes = [240, 224, 192] // multibyte start bytes 1111|1110|110X XXXX
const decoded = []
for (let pos = 0; pos < this.length;) {
let code = this[pos++]
if (code > 127) { // multibyte codepoint (if valid)
let forward = 3 - startBytes.findIndex(v => code >= v)
if (forward > 3 || (pos + forward) > this.length) {
let msg = `Invalid start of UTF-8 multibyte codepoint at position ${pos}: ${code}.`
throw new RangeError(msg)
}
code = code & (63 >> forward)
for (; forward > 0; forward--) {
let byte = this[pos++]
if (byte > 191) { // multibyte followup bytes must be 10XX XXXX
let msg = `Invalid UTF-8 multibyte codepoint part at position ${pos}: ${byte}.`
throw new RangeError(msg)
}
code = (code << 6) | (byte & 63)
}
}
decoded.push(code)
}
return String.fromCharCode(...decoded)
}
}