--- /dev/null
+// ♓🌟 Piscēs ∷ binary.js
+// ====================================================================
+//
+// Copyright © 2020–2022 Lady [@ Lady’s Computer].
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
+
+import { fill, map, reduce } from "./collection.js";
+import { bind, call } from "./function.js";
+import { ceil, floor } from "./numeric.js";
+import { hasOwnProperty, objectCreate } from "./object.js";
+import {
+ getCodeUnit,
+ rawString,
+ stringFromCodeUnits,
+ stringReplace,
+} from "./string.js";
+
+const Buffer = ArrayBuffer;
+const View = DataView;
+const TypedArray = Object.getPrototypeOf(Uint8Array);
+const { prototype: arrayPrototype } = Array;
+const { prototype: bufferPrototype } = Buffer;
+const { iterator: iteratorSymbol } = Symbol;
+const { prototype: rePrototype } = RegExp;
+const { prototype: typedArrayPrototype } = TypedArray;
+const { prototype: viewPrototype } = View;
+
+const { [iteratorSymbol]: arrayIterator } = arrayPrototype;
+const {
+ next: arrayIteratorNext,
+} = Object.getPrototypeOf([][iteratorSymbol]());
+const getBufferByteLength =
+ Object.getOwnPropertyDescriptor(bufferPrototype, "byteLength").get;
+const getTypedArrayBuffer =
+ Object.getOwnPropertyDescriptor(typedArrayPrototype, "buffer").get;
+const getViewBuffer =
+ Object.getOwnPropertyDescriptor(viewPrototype, "buffer").get;
+const { exec: reExec } = rePrototype;
+const {
+ getUint8: viewGetUint8,
+ setUint8: viewSetUint8,
+ setUint16: viewSetUint16,
+} = viewPrototype;
+
+const bufferFromArgs = ($, $s) =>
+ $ instanceof Buffer
+ ? $
+ : $ instanceof View
+ ? call(getViewBuffer, $, [])
+ : $ instanceof TypedArray
+ ? call(getTypedArrayBuffer, $, [])
+ : ((string) =>
+ call(
+ getViewBuffer,
+ reduce(
+ string,
+ (result, ucsCharacter, index) => (
+ call(viewSetUint16, result, [
+ index * 2,
+ getCodeUnit(ucsCharacter, 0),
+ ]), result
+ ),
+ new View(new Buffer(string.length * 2)),
+ ),
+ [],
+ ))(
+ typeof $ == "string"
+ ? $
+ : hasOwnProperty($, "raw")
+ ? rawString($, ...$s)
+ : `${$}`,
+ );
+
+const binaryCodeUnitIterablePrototype = {
+ [iteratorSymbol]() {
+ return {
+ next: bind(
+ arrayIteratorNext,
+ call(arrayIterator, this, []),
+ [],
+ ),
+ };
+ },
+};
+
+const decodeBase64 = (source, safe = false) => {
+ const u6s = map(
+ source.length % 4 == 0
+ ? stringReplace(source, /={1,2}$/u, "")
+ : source,
+ (ucsCharacter) => {
+ const code = getCodeUnit(ucsCharacter, 0);
+ const result = code >= 0x41 && code <= 0x5A
+ ? code - 65
+ : code >= 0x61 && code <= 0x7A
+ ? code - 71
+ : code >= 0x30 && code <= 0x39
+ ? code + 4
+ : code == (safe ? 0x2D : 0x2B)
+ ? 62
+ : code == (safe ? 0x5F : 0x2F)
+ ? 63
+ : -1;
+ if (result < 0) {
+ throw new RangeError(
+ `Piscēs: Invalid character in Base64: ${character}.`,
+ );
+ } else {
+ return result;
+ }
+ },
+ );
+ const { length } = u6s;
+ const dataView = new View(new Buffer(floor(length * 3 / 4)));
+ for (let index = 0; index < length - 1;) {
+ const dataIndex = ceil(index * 3 / 4);
+ const remainder = index % 3;
+ if (remainder == 0) {
+ call(viewSetUint8, dataView, [
+ dataIndex,
+ (u6s[index] << 2) + (u6s[++index] >> 4),
+ ]);
+ } else if (remainder == 1) {
+ call(viewSetUint8, dataView, [
+ dataIndex,
+ ((u6s[index] & 0xF) << 4) + (u6s[++index] >> 2),
+ ]);
+ } else {
+ call(viewSetUint8, dataView, [
+ dataIndex,
+ ((u6s[index] & 0x3) << 6) + u6s[++index],
+ ]);
+ }
+ }
+ return call(getViewBuffer, dataView, []);
+};
+
+const encodeBase64 = (buffer, safe = false) => {
+ const dataView = new View(buffer);
+ const byteLength = call(getBufferByteLength, buffer, []);
+ const minimumLengthOfResults = ceil(byteLength * 4 / 3);
+ const resultingCodeUnits = fill(
+ objectCreate(
+ binaryCodeUnitIterablePrototype,
+ {
+ length: {
+ value: minimumLengthOfResults +
+ (4 - (minimumLengthOfResults % 4)) % 4,
+ },
+ },
+ ),
+ 0x3D,
+ );
+ for (let index = 0; index < byteLength;) {
+ const codeUnitIndex = ceil(index * 4 / 3);
+ const currentIndex = codeUnitIndex + +(
+ index % 3 == 0 && resultingCodeUnits[codeUnitIndex] != 0x3D
+ );
+ const remainder = currentIndex % 4;
+ const u6 = remainder == 0
+ ? call(viewGetUint8, dataView, [index]) >> 2
+ : remainder == 1
+ ? ((call(viewGetUint8, dataView, [index++]) & 0x3) << 4) +
+ (index < byteLength
+ ? call(viewGetUint8, dataView, [index]) >> 4
+ : 0)
+ : remainder == 2
+ ? ((call(viewGetUint8, dataView, [index++]) & 0xF) << 2) +
+ (index < byteLength
+ ? call(viewGetUint8, dataView, [index]) >> 6
+ : 0)
+ : call(viewGetUint8, dataView, [index++]) & 0x3F;
+ const result = u6 < 26
+ ? u6 + 65
+ : u6 < 52
+ ? u6 + 71
+ : u6 < 62
+ ? u6 - 4
+ : u6 < 63
+ ? (safe ? 0x2D : 0x2B)
+ : u6 < 64
+ ? (safe ? 0x5F : 0x2F)
+ : -1;
+ if (result < 0) {
+ throw new RangeError(`Piscēs: Unexpected Base64 value: ${u6}.`);
+ } else {
+ resultingCodeUnits[currentIndex] = result;
+ }
+ }
+ return stringFromCodeUnits(...resultingCodeUnits);
+};
+
+const sourceFromArgs = ($, $s) =>
+ stringReplace(
+ typeof $ == "string"
+ ? $
+ : hasOwnProperty($, "raw")
+ ? rawString($, ...$s)
+ : `${$}`,
+ /[\t\n\f\r ]+/gu,
+ "",
+ );
+
+/**
+ * Returns an ArrayBuffer generated from the provided Base64.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const base64Binary = ($, ...$s) =>
+ decodeBase64(sourceFromArgs($, $s));
+
+/**
+ * Returns a (big‐endian) base64 string created from a typed array,
+ * buffer, or (16‐bit) string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const base64String = ($, ...$s) =>
+ encodeBase64(bufferFromArgs($, $s));
+
+/**
+ * Returns an ArrayBuffer generated from the provided filename‐safe
+ * Base64.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const filenameSafeBase64Binary = ($, ...$s) =>
+ decodeBase64(sourceFromArgs($, $s), true);
+
+/**
+ * Returns a (big‐endian) filename‐safe base64 string created from a
+ * typed array, buffer, or (16‐bit) string.
+ *
+ * This function can also be used as a tag for a template literal. The
+ * literal will be interpreted akin to `String.raw`.
+ */
+export const filenameSafeBase64String = ($, ...$s) =>
+ encodeBase64(bufferFromArgs($, $s), true);
+
+/**
+ * Returns whether the provided value is a Base64 string.
+ *
+ * Returns false if the provided value is not a string primitive.
+ */
+export const isBase64 = ($) => {
+ if (typeof $ !== "string") {
+ return false;
+ } else {
+ const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+ const trimmed = source.length % 4 == 0
+ ? stringReplace(source, /={1,2}$/u, "")
+ : source;
+ return trimmed.length % 4 != 1 &&
+ call(reExec, /[^0-9A-Za-z+\/]/u, [trimmed]) == null;
+ }
+};
+
+/**
+ * Returns whether the provided value is a filename‐safe base64 string.
+ *
+ * Returns false if the provided value is not a string primitive.
+ */
+export const isFilenameSafeBase64 = ($) => {
+ if (typeof $ !== "string") {
+ return false;
+ } else {
+ const source = stringReplace($, /[\t\n\f\r ]+/gu, "");
+ const trimmed = source.length % 4 == 0
+ ? stringReplace(source, /={1,2}$/u, "")
+ : source;
+ return trimmed.length % 4 != 1 &&
+ call(reExec, /[^0-9A-Za-z_-]/u, [trimmed]) == null;
+ }
+};