@dimipay/bqencode
Version:
Binary Encoding for QR Codes
209 lines (205 loc) • 6.09 kB
JavaScript
// libs/base.ts
class Base {
}
class InvalidEncodingError extends Error {
constructor() {
super("Invalid encoding");
}
}
// libs/utils.ts
function readBigInt56BE(buf, offset = 0) {
if (buf[offset] === undefined || buf[offset + 6] === undefined) {
throw new RangeError("Cannot access beyond buffer length");
}
const first = buf.readUintBE(offset, 3);
const last = buf.readUint32BE(offset + 3);
return BigInt(first) << 32n | BigInt(last);
}
function writeBigInt56BE(value, buf, offset = 0) {
const first = Number(value >> 32n);
buf[offset] = first >> 16;
buf[offset + 1] = first >> 8 & 255;
buf[offset + 2] = first & 255;
const second = Number(value & 0xffffffffn);
buf[offset + 3] = second >> 24;
buf[offset + 4] = second >> 16 & 255;
buf[offset + 5] = second >> 8 & 255;
buf[offset + 6] = second & 255;
}
// libs/base10.ts
class Base10 extends Base {
byteLength = [3, 5, 8, 10, 13, 15];
encode(input) {
let result = "";
const buf = Buffer.isBuffer(input) ? input : Buffer.from(input);
for (let i = 0;i < buf.length; i += 7) {
if (i + 6 < buf.length) {
const val = readBigInt56BE(buf, i);
result += String(val).padStart(17, "0");
} else {
const size = buf.length - i;
const length = this.byteLength[size - 1];
const val = buf.readUintBE(i, size);
result += String(val).padStart(length, "0");
}
}
return result;
}
decode(str) {
if (str.length === 0) {
return Buffer.alloc(0);
}
if (!this.isBase10(str)) {
throw new InvalidEncodingError;
}
const chunk = str.match(/\d{1,17}/g);
const lastSize = str.length % 17 ? this.byteLength.indexOf(str.length % 17) + 1 : 7;
const result = Buffer.alloc(chunk.length * 7 - (7 - lastSize));
for (let i = 0;i < chunk.length; i++) {
if (chunk[i].length === 17) {
writeBigInt56BE(BigInt(chunk[i]), result, i * 7);
} else {
result.writeUintBE(Number(chunk[i]), i * 7, lastSize);
}
}
return result;
}
isBase10(str) {
const r = str.length % 17;
return /^\d*$/.test(str) && (r === 0 || this.byteLength.includes(r));
}
}
var base10 = new Base10;
// libs/base45.ts
class Base45 extends Base {
charset = new Charset("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:");
encode(input) {
let result = "";
const buf = Buffer.isBuffer(input) ? input : Buffer.from(input);
for (let i = 0;i < buf.length; i += 2) {
if (i + 1 < buf.length) {
let val = buf.readUint16BE(i);
for (let j = 0;j < 3; j++) {
result += this.charset.charAt(val % 45);
val = Math.floor(val / 45);
}
} else {
let val = buf.readUint8(i);
for (let j = 0;j < 2; j++) {
result += this.charset.charAt(val % 45);
val = Math.floor(val / 45);
}
}
}
return result;
}
decode(str) {
if (!this.isBase45(str)) {
throw new InvalidEncodingError;
}
if (str.length === 0) {
return Buffer.alloc(0);
}
const input = Array.from(str).map((c) => this.charset.indexOf(c));
const result = [];
for (let i = 0;i < str.length; i += 3) {
if (i + 2 < str.length) {
const val = input[i] + input[i + 1] * 45 + input[i + 2] * 45 ** 2;
result.push(val >> 8, val & 255);
} else {
const val = input[i] + input[i + 1] * 45;
result.push(val);
}
}
return Buffer.from(result);
}
isBase45(str) {
const r = str.length % 3;
return new RegExp(`^[${this.charset.charset}]*\$`).test(str) && (r === 0 || r === 2);
}
}
class Charset {
charset;
charsetObj;
constructor(charset) {
this.charset = charset;
this.charsetObj = Object.fromEntries(Array.from(charset).map((c, i) => [c, i]));
}
charAt(index) {
return this.charset[index];
}
indexOf(char) {
return this.charsetObj[char];
}
parse(str) {
return Array.from(str).map((c) => this.indexOf(c));
}
}
var base45 = new Base45;
// libs/base94.ts
class Base94 extends Base {
byteLength = [2, 3, 4];
encode(input) {
let result = "";
const buf = Buffer.isBuffer(input) ? input : Buffer.from(input);
for (let i = 0;i < buf.length; i += 4) {
if (i + 3 < buf.length) {
let val = buf.readUint32BE(i);
for (let j = 0;j < 5; j++) {
result += String.fromCharCode(val % 94 + 33);
val = Math.floor(val / 94);
}
} else {
const size = buf.length - i;
const length = this.byteLength[size - 1];
let val = buf.readUintBE(i, size);
for (let j = 0;j < length; j++) {
result += String.fromCharCode(val % 94 + 33);
val = Math.floor(val / 94);
}
}
}
return result;
}
decode(str) {
if (str.length === 0) {
return Buffer.alloc(0);
}
if (!this.isBase94(str)) {
throw new InvalidEncodingError;
}
const chunk = str.match(/.{1,5}/g);
const lastSize = str.length % 5 ? this.byteLength.indexOf(str.length % 5) + 1 : 4;
const result = Buffer.alloc(chunk.length * 4 - (4 - lastSize));
for (let i = 0;i < chunk.length; i++) {
const buf = Buffer.from(Array.from(chunk[i]).map((c) => c.charCodeAt(0) - 33));
if (chunk[i].length === 5) {
const val = buf[0] + buf[1] * 94 + buf[2] * 94 ** 2 + buf[3] * 94 ** 3 + buf[4] * 94 ** 4;
result.writeUint32BE(val, i * 4);
} else {
let val;
if (chunk[i].length === 4) {
val = buf[0] + buf[1] * 94 + buf[2] * 94 ** 2 + buf[3] * 94 ** 3;
} else if (chunk[i].length === 3) {
val = buf[0] + buf[1] * 94 + buf[2] * 94 ** 2;
} else {
val = buf[0] + buf[1] * 94;
}
result.writeUintBE(val, i * 4, lastSize);
}
}
return result;
}
isBase94(str) {
const r = str.length % 5;
return /^[\x21-\x7e]*$/.test(str) && (r === 0 || this.byteLength.includes(r));
}
}
var base94 = new Base94;
export {
base94,
base45,
base10,
InvalidEncodingError,
Base
};