base69
Version:
Base69 encoding. Nice!
75 lines (69 loc) • 2.24 kB
JavaScript
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/-*<>|';
function byteToChars(n) {
return `${chars[n % 69]}${chars[Math.floor(n / 69)]}`;
}
function charsToByte(s) {
return (69 * chars.indexOf(s.charAt(1))) + chars.indexOf(s.charAt(0));
}
function encodeArrayWithLength(bytes, startIndex, length, codes) {
const endIndex = startIndex + length;
for (let i = startIndex; i < endIndex; i++) {
const shift = (i % 7) + 1;
let shifted = bytes[i] >> shift;
if (shift > 1) {
const pre = (bytes[i - 1] & ((2 << (shift - 2)) - 1)) << (8 - shift);
shifted = pre | shifted;
}
codes.push(byteToChars(shifted));
if (shift == 7) {
shifted = bytes[i] & 127;
codes.push(byteToChars(shifted));
}
}
}
function decodeChunk(s) {
const paddedBytes = s.endsWith('=');
const decoded = new Uint8Array(8);
for (let i = 0; i < 8; i++) {
decoded[i] = (i === 7 && paddedBytes) ? 0 : charsToByte(s.substring(i * 2, i * 2 + 2));
}
const result = new Uint8Array(7);
for (let i = 0; i < 7; i++) {
let t1 = decoded[i] << (i + 1);
let t2 = decoded[i + 1] >> (7 - i - 1);
result[i] = t1 | t2;
}
return result;
}
export function encode(bytes) {
const codes = [];
const len = bytes.length;
const extraBytes = len % 7;
encodeArrayWithLength(bytes, 0, len - extraBytes, codes);
if (extraBytes) {
const extra = new Uint8Array(7);
extra.fill(0, 0);
extra.set(bytes.slice(len - extraBytes), 0);
encodeArrayWithLength(extra, 0, extra.length, codes);
codes[codes.length - 1] = `${7 - extraBytes}=`;
}
return codes.join('');
}
export function decode(value) {
let extraBytes = 0;
if (value.charAt(value.length - 1) === '=') {
extraBytes = +value.charAt(value.length - 2);
}
const chunkCount = Math.ceil(value.length / 16);
const bytes = new Uint8Array(chunkCount * 7 - extraBytes);
for (let i = 0; i < chunkCount; i++) {
const chunkString = value.substring(i * 16, (i + 1) * 16);
if (extraBytes && (i == chunkCount - 1)) {
bytes.set(decodeChunk(chunkString).subarray(0, 7 - extraBytes), i * 7);
}
else {
bytes.set(decodeChunk(chunkString), i * 7);
}
}
return bytes;
}