1 // ♓🌟 Piscēs ∷ binary.js
2 // ====================================================================
4 // Copyright © 2020–2022 Lady [@ Lady’s Computer].
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
10 import { fill
, map
, reduce
} from "./collection.js";
11 import { bind
, call
} from "./function.js";
12 import { ceil
, floor
} from "./numeric.js";
13 import { hasOwnProperty
, objectCreate
} from "./object.js";
21 const Buffer
= ArrayBuffer
;
22 const View
= DataView
;
23 const TypedArray
= Object
.getPrototypeOf(Uint8Array
);
24 const { prototype: arrayPrototype
} = Array
;
25 const { prototype: bufferPrototype
} = Buffer
;
26 const { iterator
: iteratorSymbol
} = Symbol
;
27 const { prototype: rePrototype
} = RegExp
;
28 const { prototype: typedArrayPrototype
} = TypedArray
;
29 const { prototype: viewPrototype
} = View
;
31 const { [iteratorSymbol
]: arrayIterator
} = arrayPrototype
;
33 next
: arrayIteratorNext
,
34 } = Object
.getPrototypeOf([][iteratorSymbol
]());
35 const getBufferByteLength
=
36 Object
.getOwnPropertyDescriptor(bufferPrototype
, "byteLength").get;
37 const getTypedArrayBuffer
=
38 Object
.getOwnPropertyDescriptor(typedArrayPrototype
, "buffer").get;
40 Object
.getOwnPropertyDescriptor(viewPrototype
, "buffer").get;
41 const { exec
: reExec
} = rePrototype
;
43 getUint8
: viewGetUint8
,
44 setUint8
: viewSetUint8
,
45 setUint16
: viewSetUint16
,
48 const bufferFromArgs
= ($, $s
) =>
52 ? call(getViewBuffer
, $, [])
53 : $ instanceof TypedArray
54 ? call(getTypedArrayBuffer
, $, [])
60 (result
, ucsCharacter
, index
) => (
61 call(viewSetUint16
, result
, [
63 getCodeUnit(ucsCharacter
, 0),
66 new View(new Buffer(string
.length
* 2)),
72 : hasOwnProperty($, "raw")
77 const binaryCodeUnitIterablePrototype
= {
82 call(arrayIterator
, this, []),
89 const decodeBase64
= (source
, safe
= false) => {
91 source
.length
% 4 == 0
92 ? stringReplace(source
, /={1,2}$/u, "")
95 const code
= getCodeUnit(ucsCharacter
, 0);
96 const result
= code
>= 0x41 && code
<= 0x5A
98 : code
>= 0x61 && code
<= 0x7A
100 : code
>= 0x30 && code
<= 0x39
102 : code
== (safe
? 0x2D : 0x2B)
104 : code
== (safe
? 0x5F : 0x2F)
108 throw new RangeError(
109 `Piscēs: Invalid character in Base64: ${character}.`,
116 const { length
} = u6s
;
117 const dataView
= new View(new Buffer(floor(length
* 3 / 4)));
118 for (let index
= 0; index
< length
- 1;) {
119 const dataIndex
= ceil(index
* 3 / 4);
120 const remainder
= index
% 3;
121 if (remainder
== 0) {
122 call(viewSetUint8
, dataView
, [
124 (u6s
[index
] << 2) + (u6s
[++index
] >> 4),
126 } else if (remainder
== 1) {
127 call(viewSetUint8
, dataView
, [
129 ((u6s
[index
] & 0xF) << 4) + (u6s
[++index
] >> 2),
132 call(viewSetUint8
, dataView
, [
134 ((u6s
[index
] & 0x3) << 6) + u6s
[++index
],
138 return call(getViewBuffer
, dataView
, []);
141 const encodeBase64
= (buffer
, safe
= false) => {
142 const dataView
= new View(buffer
);
143 const byteLength
= call(getBufferByteLength
, buffer
, []);
144 const minimumLengthOfResults
= ceil(byteLength
* 4 / 3);
145 const resultingCodeUnits
= fill(
147 binaryCodeUnitIterablePrototype
,
150 value
: minimumLengthOfResults
+
151 (4 - (minimumLengthOfResults
% 4)) % 4,
157 for (let index
= 0; index
< byteLength
;) {
158 const codeUnitIndex
= ceil(index
* 4 / 3);
159 const currentIndex
= codeUnitIndex
+ +(
160 index
% 3 == 0 && resultingCodeUnits
[codeUnitIndex
] != 0x3D
162 const remainder
= currentIndex
% 4;
163 const u6
= remainder
== 0
164 ? call(viewGetUint8
, dataView
, [index
]) >> 2
166 ? ((call(viewGetUint8
, dataView
, [index
++]) & 0x3) << 4) +
168 ? call(viewGetUint8
, dataView
, [index
]) >> 4
171 ? ((call(viewGetUint8
, dataView
, [index
++]) & 0xF) << 2) +
173 ? call(viewGetUint8
, dataView
, [index
]) >> 6
175 : call(viewGetUint8
, dataView
, [index
++]) & 0x3F;
176 const result
= u6
< 26
183 ? (safe
? 0x2D : 0x2B)
185 ? (safe
? 0x5F : 0x2F)
188 throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
190 resultingCodeUnits
[currentIndex
] = result
;
193 return stringFromCodeUnits(...resultingCodeUnits
);
196 const sourceFromArgs
= ($, $s
) =>
200 : hasOwnProperty($, "raw")
201 ? rawString($, ...$s
)
208 * Returns an ArrayBuffer generated from the provided Base64.
210 * This function can also be used as a tag for a template literal. The
211 * literal will be interpreted akin to `String.raw`.
213 export const base64Binary
= ($, ...$s
) =>
214 decodeBase64(sourceFromArgs($, $s
));
217 * Returns a (big‐endian) base64 string created from a typed array,
218 * buffer, or (16‐bit) string.
220 * This function can also be used as a tag for a template literal. The
221 * literal will be interpreted akin to `String.raw`.
223 export const base64String
= ($, ...$s
) =>
224 encodeBase64(bufferFromArgs($, $s
));
227 * Returns an ArrayBuffer generated from the provided filename‐safe
230 * This function can also be used as a tag for a template literal. The
231 * literal will be interpreted akin to `String.raw`.
233 export const filenameSafeBase64Binary
= ($, ...$s
) =>
234 decodeBase64(sourceFromArgs($, $s
), true);
237 * Returns a (big‐endian) filename‐safe base64 string created from a
238 * typed array, buffer, or (16‐bit) string.
240 * This function can also be used as a tag for a template literal. The
241 * literal will be interpreted akin to `String.raw`.
243 export const filenameSafeBase64String
= ($, ...$s
) =>
244 encodeBase64(bufferFromArgs($, $s
), true);
247 * Returns whether the provided value is a Base64 string.
249 * Returns false if the provided value is not a string primitive.
251 export const isBase64
= ($) => {
252 if (typeof $ !== "string") {
255 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
256 const trimmed
= source
.length
% 4 == 0
257 ? stringReplace(source
, /={1,2}$/u, "")
259 return trimmed
.length
% 4 != 1 &&
260 call(reExec
, /[^0-9A-Za-z+\/]/u, [trimmed
]) == null;
265 * Returns whether the provided value is a filename‐safe base64 string.
267 * Returns false if the provided value is not a string primitive.
269 export const isFilenameSafeBase64
= ($) => {
270 if (typeof $ !== "string") {
273 const source
= stringReplace($, /[\t\n\f\r ]+/gu, "");
274 const trimmed
= source
.length
% 4 == 0
275 ? stringReplace(source
, /={1,2}$/u, "")
277 return trimmed
.length
% 4 != 1 &&
278 call(reExec
, /[^0-9A-Za-z_-]/u, [trimmed
]) == null;