added alphabet tool, updated packages, a few transform fixes

This commit is contained in:
ph1r3754r73r
2026-06-13 10:47:52 -07:00
parent 730ce238a8
commit 31308cd598
17 changed files with 1620 additions and 274 deletions
+134 -94
View File
@@ -1,97 +1,137 @@
// base122 encoding (more efficient than Base64)
// Base122 encoding (Kevin Albs) — UTF-8 binary-to-text, ~14% smaller than Base64
import BaseTransformer from '../BaseTransformer.js';
export default new BaseTransformer({
name: 'Base122',
priority: 250,
category: 'encoding',
func: function(text) {
// Base122 uses UTF-8 bytes and encodes them more efficiently
// It uses 7-bit ASCII (0-127) plus some safe 2-byte UTF-8 sequences
const bytes = new TextEncoder().encode(text);
let result = '';
let i = 0;
while (i < bytes.length) {
const byte = bytes[i];
if (byte < 128) {
// Single byte ASCII
result += String.fromCharCode(byte);
i++;
} else if (i + 1 < bytes.length) {
// Try to encode as 2-byte sequence
const b1 = byte;
const b2 = bytes[i + 1];
// Check if it's a valid 2-byte UTF-8 sequence
if ((b1 & 0xE0) === 0xC0 && (b2 & 0xC0) === 0x80) {
result += String.fromCharCode(b1, b2);
i += 2;
} else {
// Fallback: encode as escaped sequence
result += String.fromCharCode(0xC2, 0x80 + (byte - 128));
i++;
}
} else {
// Last byte, encode as escaped
result += String.fromCharCode(0xC2, 0x80 + (byte - 128));
i++;
}
}
return result;
},
reverse: function(text) {
const bytes = [];
let i = 0;
while (i < text.length) {
const code = text.charCodeAt(i);
if (code < 128) {
bytes.push(code);
i++;
} else if (i + 1 < text.length) {
// Check for 2-byte sequence
const b1 = code;
const b2 = text.charCodeAt(i + 1);
if ((b1 & 0xE0) === 0xC0 && (b2 & 0xC0) === 0x80) {
// Extract original byte from escaped sequence
if (b1 === 0xC2 && b2 >= 0x80 && b2 < 0xC0) {
bytes.push(b2 - 0x80);
} else {
bytes.push(b1, b2);
}
i += 2;
} else {
bytes.push(code);
i++;
}
} else {
bytes.push(code);
i++;
}
}
try {
return new TextDecoder().decode(new Uint8Array(bytes));
} catch (e) {
return '';
}
},
preview: function(text) {
if (!text) return '[base122]';
const result = this.func(text.slice(0, 10));
return result.substring(0, 15) + '...';
},
detector: function(text) {
// Base122 produces text that's mostly ASCII with some UTF-8 sequences
// Hard to detect reliably, but check for mix of ASCII and UTF-8
const hasAscii = /[\x00-\x7F]/.test(text);
const hasUtf8 = /[\xC0-\xFF]/.test(text);
return hasAscii && text.length >= 8;
}
});
export default (function() {
const kIllegals = [
0, 10, 13, 34, 38, 92
];
const kShortened = 0b111;
function encodeBase122Bytes(bytes) {
let curIndex = 0;
let curBit = 0;
const outData = [];
function get7() {
if (curIndex >= bytes.length) {
return false;
}
const firstByte = bytes[curIndex];
let firstPart = ((0b11111110 >>> curBit) & firstByte) << curBit;
firstPart >>= 1;
curBit += 7;
if (curBit < 8) {
return firstPart;
}
curBit -= 8;
curIndex++;
if (curIndex >= bytes.length) {
return firstPart;
}
const secondByte = bytes[curIndex];
let secondPart = ((0xFF00 >>> curBit) & secondByte) & 0xFF;
secondPart >>= 8 - curBit;
return firstPart | secondPart;
}
while (true) {
const bits = get7();
if (bits === false) {
break;
}
const illegalIndex = kIllegals.indexOf(bits);
if (illegalIndex !== -1) {
let nextBits = get7();
let b1 = 0b11000010;
let b2 = 0b10000000;
if (nextBits === false) {
b1 |= (kShortened & 0b111) << 2;
nextBits = bits;
} else {
b1 |= (illegalIndex & 0b111) << 2;
}
const firstBit = (nextBits & 0b01000000) > 0 ? 1 : 0;
b1 |= firstBit;
b2 |= nextBits & 0b00111111;
outData.push(b1, b2);
} else {
outData.push(bits);
}
}
return new TextDecoder('utf-8').decode(new Uint8Array(outData));
}
function decodeBase122String(strData) {
const decoded = [];
let curByte = 0;
let bitOfByte = 0;
function push7(byte) {
byte <<= 1;
curByte |= (byte >>> bitOfByte);
bitOfByte += 7;
if (bitOfByte >= 8) {
decoded.push(curByte);
bitOfByte -= 8;
curByte = (byte << (7 - bitOfByte)) & 255;
}
}
for (let i = 0; i < strData.length; i++) {
const c = strData.charCodeAt(i);
if (c > 127) {
const illegalIndex = (c >>> 8) & 7;
if (illegalIndex !== kShortened) {
push7(kIllegals[illegalIndex]);
}
push7(c & 127);
} else {
push7(c);
}
}
return new Uint8Array(decoded);
}
return new BaseTransformer({
name: 'Base122',
priority: 250,
category: 'encoding',
func: function(text) {
const bytes = new TextEncoder().encode(text);
return encodeBase122Bytes(bytes);
},
reverse: function(text) {
try {
const bytes = decodeBase122String(text);
return new TextDecoder().decode(bytes);
} catch (e) {
return '';
}
},
preview: function(text) {
if (!text) {
return '[base122]';
}
const result = this.func(text.slice(0, 10));
return result.substring(0, 15) + (result.length > 15 ? '...' : '');
},
detector: function(text) {
if (!text || text.length < 4) {
return false;
}
return /[\u0080-\uFFFF]/.test(text) || (text.length >= 8 && text !== text.trim());
}
});
})();
+194 -148
View File
@@ -1,151 +1,197 @@
// baudot code / ITA2 encoding (teletype code)
// Baudot / ITA2 telegraph code — 5-bit letters/figures with shift codes
import BaseTransformer from '../BaseTransformer.js';
export default new BaseTransformer({
name: 'Baudot Code (ITA2)',
priority: 250,
category: 'encoding',
// Baudot/ITA2 5-bit code (letters and figures shift)
letters: {
0b00000: ' ', // NULL/blank
0b00010: 'E',
0b00011: '\n', // Line feed
0b00100: 'A',
0b00101: ' ',
0b00110: 'S',
0b00111: 'I',
0b01000: 'U',
0b01001: '\r', // Carriage return
0b01010: 'D',
0b01011: 'R',
0b01100: 'J',
0b01101: 'N',
0b01110: 'F',
0b01111: 'C',
0b10000: 'K',
0b10001: 'T',
0b10010: 'Z',
0b10011: 'L',
0b10100: 'W',
0b10101: 'H',
0b10110: 'Y',
0b10111: 'P',
0b11000: 'Q',
0b11001: 'O',
0b11010: 'B',
0b11011: 'G',
0b11100: 'Figures', // Shift to figures
0b11101: 'M',
0b11110: 'X',
0b11111: 'V',
},
figures: {
0b00000: ' ',
0b00010: '3',
0b00011: '\n',
0b00100: '-',
0b00101: ' ',
0b00110: '\'',
0b00111: '8',
0b01000: '7',
0b01001: '\r',
0b01010: '\u0005', // ENQ
0b01011: '4',
0b01100: '\'', // Bell
0b01101: ',',
0b01110: '!',
0b01111: ':',
0b10000: '(',
0b10001: '5',
0b10010: '+',
0b10011: ')',
0b10100: '2',
0b10101: '$',
0b10110: '6',
0b10111: '0',
0b11000: '1',
0b11001: '9',
0b11010: '?',
0b11011: '&',
0b11100: 'Letters', // Shift to letters
0b11101: '.',
0b11110: '/',
0b11111: '=',
},
func: function(text) {
// Create reverse maps
const lettersToCode = {};
const figuresToCode = {};
for (const [code, char] of Object.entries(this.letters)) {
if (char !== 'Figures' && char !== 'Letters') {
lettersToCode[char] = parseInt(code);
}
}
for (const [code, char] of Object.entries(this.figures)) {
if (char !== 'Figures' && char !== 'Letters') {
figuresToCode[char] = parseInt(code);
}
}
let result = '';
let inFigures = false;
for (const char of text.toUpperCase()) {
// Check if we need to shift
const isFigure = /[0-9\-'():!$?&.\/+=]/.test(char);
if (isFigure && !inFigures) {
result += String.fromCharCode(0b11100); // Figures shift
inFigures = true;
} else if (!isFigure && inFigures) {
result += String.fromCharCode(0b11111); // Letters shift (approximate)
inFigures = false;
}
// Encode character
const code = inFigures ? figuresToCode[char] : lettersToCode[char];
if (code !== undefined) {
result += String.fromCharCode(code);
} else {
result += char; // Keep unmapped
}
}
return result;
},
reverse: function(text) {
let result = '';
let inFigures = false;
for (let i = 0; i < text.length; i++) {
const code = text.charCodeAt(i) & 0x1F; // 5 bits
if (code === 0b11100) {
inFigures = true;
continue;
} else if (code === 0b11111) {
inFigures = false;
continue;
}
const map = inFigures ? this.figures : this.letters;
const char = map[code];
if (char && char !== 'Figures' && char !== 'Letters') {
result += char;
}
}
return result;
},
preview: function(text) {
if (!text) return '[baudot]';
return this.func(text.slice(0, 5));
},
detector: function(text) {
// Baudot uses 5-bit codes (0-31)
// Check for characters in the 5-bit range
const has5Bit = /[\x00-\x1F]/.test(text);
return has5Bit && text.length >= 5;
}
});
export default (function() {
const FIGS = 0b11011;
const LTRS = 0b11111;
const LETTERS = {
'A': 0b00100,
'B': 0b11010,
'C': 0b01111,
'D': 0b01010,
'E': 0b00010,
'F': 0b01110,
'G': 0b00001,
'H': 0b10101,
'I': 0b00111,
'J': 0b01100,
'K': 0b10000,
'L': 0b10011,
'M': 0b11101,
'N': 0b01101,
'O': 0b11001,
'P': 0b10111,
'Q': 0b11000,
'R': 0b01011,
'S': 0b00110,
'T': 0b10001,
'U': 0b01000,
'V': 0b11100,
'W': 0b10100,
'X': 0b11110,
'Y': 0b10110,
'Z': 0b10010
};
const FIGURES = {
'-': 0b00100,
'?': 0b11010,
':': 0b01111,
'$': 0b01010,
'3': 0b00010,
'!': 0b01110,
'&': 0b11011,
'8': 0b00111,
'7': 0b01000,
'4': 0b01011,
',': 0b01101,
'(': 0b10000,
')': 0b10011,
'.': 0b11101,
'0': 0b10111,
'1': 0b11000,
'9': 0b11001,
'5': 0b10001,
'+': 0b10010,
'2': 0b10100,
'6': 0b10110,
'/': 0b11110
};
const LETTER_BY_CODE = {};
const FIGURE_BY_CODE = {};
for (const [char, code] of Object.entries(LETTERS)) {
LETTER_BY_CODE[code] = char;
}
for (const [char, code] of Object.entries(FIGURES)) {
if (char !== '&') {
FIGURE_BY_CODE[code] = char;
}
}
FIGURE_BY_CODE[FIGS] = '&';
function isFigureChar(char) {
return Object.prototype.hasOwnProperty.call(FIGURES, char);
}
function isLetterChar(char) {
return Object.prototype.hasOwnProperty.call(LETTERS, char);
}
function codesToDisplay(codes) {
return codes.map(code => code.toString(2).padStart(5, '0')).join(' ');
}
function parseDisplayCodes(text) {
return text.trim().split(/\s+/).map(token => {
if (!/^[01]{5}$/.test(token)) {
return null;
}
return parseInt(token, 2);
}).filter(code => code !== null);
}
return new BaseTransformer({
name: 'Baudot Code (ITA2)',
priority: 250,
category: 'encoding',
func: function(text) {
const upper = text.toUpperCase();
const codes = [];
let inFigures = false;
for (const char of upper) {
if (char === '\n') {
codes.push(0b00011);
continue;
}
if (char === '\r') {
codes.push(0b01001);
continue;
}
if (char === ' ') {
codes.push(0b00101);
continue;
}
const wantsFigure = isFigureChar(char);
const wantsLetter = isLetterChar(char);
if (wantsFigure && !inFigures) {
codes.push(FIGS);
inFigures = true;
} else if (wantsLetter && inFigures && char !== '&') {
codes.push(LTRS);
inFigures = false;
}
if (char === '&' && !inFigures) {
codes.push(FIGS);
inFigures = true;
}
const map = inFigures ? FIGURES : LETTERS;
if (Object.prototype.hasOwnProperty.call(map, char)) {
codes.push(map[char]);
}
}
return codesToDisplay(codes);
},
reverse: function(text) {
const codes = parseDisplayCodes(text);
if (codes.length === 0) {
return '';
}
let result = '';
let inFigures = false;
for (const code of codes) {
if (code === FIGS && !inFigures) {
inFigures = true;
continue;
}
if (code === LTRS && inFigures) {
inFigures = false;
continue;
}
if (code === 0b00011) {
result += '\n';
continue;
}
if (code === 0b01001) {
result += '\r';
continue;
}
if (code === 0b00101) {
result += ' ';
continue;
}
const map = inFigures ? FIGURE_BY_CODE : LETTER_BY_CODE;
const char = map[code];
if (char) {
result += char;
}
}
return result;
},
preview: function(text) {
if (!text) {
return '[baudot]';
}
return this.func(text.slice(0, 5));
},
detector: function(text) {
const tokens = text.trim().split(/\s+/);
if (tokens.length < 4) {
return false;
}
return tokens.every(token => /^[01]{5}$/.test(token));
}
});
})();