Hex ↔ Text Converter — TypeScript source
Convert text to hexadecimal and hex back to text, with delimiter options (none, spaces, 0x, backslash-x) and full UTF-8 support. 100% client-side.
This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Pure hex ↔ text conversion - no React, no DOM, deterministic.
// Implements UTF-8 by hand so it runs in plain Node without TextEncoder.
// Never throws; decode of invalid sequences yields U+FFFD.
export type Delimiter = 'none' | 'space' | '0x' | 'backslash-x';
export interface DecodeResult {
ok: boolean;
text: string;
error: string | null;
}
/** UTF-8 encode a JS string into a list of byte values (0..255). */
export function utf8Encode(str: string): number[] {
const bytes: number[] = [];
for (const ch of str) {
const cp = ch.codePointAt(0)!;
if (cp <= 0x7f) {
bytes.push(cp);
} else if (cp <= 0x7ff) {
bytes.push(0xc0 | (cp >> 6), 0x80 | (cp & 0x3f));
} else if (cp <= 0xffff) {
bytes.push(0xe0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
} else {
bytes.push(
0xf0 | (cp >> 18),
0x80 | ((cp >> 12) & 0x3f),
0x80 | ((cp >> 6) & 0x3f),
0x80 | (cp & 0x3f),
);
}
}
return bytes;
}
/** UTF-8 decode bytes to a JS string; invalid sequences → U+FFFD. */
export function utf8Decode(bytes: number[]): string {
let out = '';
let i = 0;
while (i < bytes.length) {
const b = bytes[i++];
let cp: number;
if (b <= 0x7f) {
cp = b;
} else if (b >> 5 === 0b110) {
const b1 = bytes[i++] ?? 0;
cp = ((b & 0x1f) << 6) | (b1 & 0x3f);
} else if (b >> 4 === 0b1110) {
const b1 = bytes[i++] ?? 0;
const b2 = bytes[i++] ?? 0;
cp = ((b & 0x0f) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f);
} else if (b >> 3 === 0b11110) {
const b1 = bytes[i++] ?? 0;
const b2 = bytes[i++] ?? 0;
const b3 = bytes[i++] ?? 0;
cp = ((b & 0x07) << 18) | ((b1 & 0x3f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f);
} else {
cp = 0xfffd;
}
out += String.fromCodePoint(cp);
}
return out;
}
/** Convert text to hex with a delimiter. */
export function textToHex(text: string, delimiter: Delimiter = 'none', uppercase = false): string {
const bytes = utf8Encode(text);
let hexes = bytes.map((b) => b.toString(16).padStart(2, '0'));
if (uppercase) hexes = hexes.map((h) => h.toUpperCase());
switch (delimiter) {
case 'none':
return hexes.join('');
case 'space':
return hexes.join(' ');
case '0x':
return hexes.map((h) => '0x' + h).join(' ');
case 'backslash-x':
return hexes.map((h) => '\\x' + h).join('');
}
}
/** Strip 0x, \x, spaces, commas, colons; lowercase; keep hex only. */
export function sanitizeHex(input: string): string {
return (input || '')
.replace(/0x/gi, '')
.replace(/\\x/gi, '')
.replace(/[\s,:]/g, '')
.toLowerCase();
}
/** Convert hex to text. */
export function hexToText(hex: string, _delimiter: Delimiter = 'none'): DecodeResult {
const cleaned = sanitizeHex(hex);
if (cleaned.length === 0) {
return { ok: true, text: '', error: null };
}
if (!/^[0-9a-f]+$/.test(cleaned)) {
return { ok: false, text: '', error: 'Hex strings may only contain 0-9 and a-f.' };
}
if (cleaned.length % 2 !== 0) {
return { ok: false, text: '', error: 'Hex must have an even number of digits.' };
}
const bytes: number[] = [];
for (let i = 0; i < cleaned.length; i += 2) {
bytes.push(parseInt(cleaned.slice(i, i + 2), 16));
}
return { ok: true, text: utf8Decode(bytes), error: null };
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →