Skip to content

Instantly share code, notes, and snippets.

@nberlette
Last active April 17, 2025 23:25
Show Gist options
  • Save nberlette/8770a3a422d31e726aa7d0ffde50e50d to your computer and use it in GitHub Desktop.
Save nberlette/8770a3a422d31e726aa7d0ffde50e50d to your computer and use it in GitHub Desktop.
TypeScript Block Letter Renderer (ASCII or Unicode)
/**
* This module provides a block letter rendering utility for text,
* capable of rendering arbitrary strings into multiple styles of
* block letters using either ASCII characters or Unicode blocks.
*
* It's perfect for creating stylized text in console applications!
*
*
* @license MIT (https://nick.mit-license.org)
* @author Nicholas Berlette <https://github.com/nberlette>
* @module block-letters
*/
/**
* Represents the available font styles for use with {@linkcode renderText}.
*
* @category Types
*/
export type Style = "solid" | "ascii" | "compact" | "tiny";
/**
* A mapping from characters to their unicode block letter
* representation for the "solid" style (5 rows tall).
*
* @internal
*/
const solidFont = {
A: [" █ ", "█ █", "███", "█ █", "█ █"],
B: ["██ ", "█ █", "██ ", "█ █", "██ "],
C: [" ██", "█ ", "█ ", "█ ", " ██"],
D: ["██ ", "█ █", "█ █", "█ █", "██ "],
E: ["███", "█ ", "██ ", "█ ", "███"],
F: ["███", "█ ", "██ ", "█ ", "█ "],
G: [" ██", "█ ", "█ █", "█ █", " ██"],
H: ["█ █", "█ █", "███", "█ █", "█ █"],
I: ["███", " █ ", " █ ", " █ ", "███"],
J: [" ██", " █", " █", "█ █", " █ "],
K: ["█ █", "█ █", "██ ", "█ █", "█ █"],
L: ["█ ", "█ ", "█ ", "█ ", "███"],
M: ["█ █", "███", "█ █", "█ █", "█ █"],
N: ["█ █", "███", "███", "█ █", "█ █"],
O: [" █ ", "█ █", "█ █", "█ █", " █ "],
P: ["██ ", "█ █", "██ ", "█ ", "█ "],
Q: [" █ ", "█ █", "█ █", " ██", " █"],
R: ["██ ", "█ █", "██ ", "█ █", "█ █"],
S: [" ██", "█ ", " █ ", " █", "██ "],
T: ["███", " █ ", " █ ", " █ ", " █ "],
U: ["█ █", "█ █", "█ █", "█ █", "███"],
V: ["█ █", "█ █", "█ █", "█ █", " █ "],
W: ["█ █", "█ █", "█ █", "███", "█ █"],
X: ["█ █", "█ █", " █ ", "█ █", "█ █"],
Y: ["█ █", "█ █", " █ ", " █ ", " █ "],
Z: ["███", " █", " █ ", "█ ", "███"],
" ": [" ", " ", " ", " ", " "],
};
/**
* Translate the solidFont █ characters into ascii '*' for the "ascii" style.
*/
const asciiFont: Record<string, string[]> = {};
for (const [ch, rows] of Object.entries(solidFont)) {
asciiFont[ch] = rows.map((row) => row.replace(//g, "*"));
}
/**
* Collapse a base font into fewer rows by OR-ing groups of rows.
* @param base - the original font mapping
* @param groups - array of groups of row indices to merge
* @param fillChar - the character to treat as "filled" (defaults to '█')
*/
function collapseFont(
base: Record<string, string[]>,
groups: number[][],
fillChar: string = "█",
): Record<string, string[]> {
const out: Record<string, string[]> = {};
for (const [ch, rows] of Object.entries(base)) {
out[ch] = groups.map((group) => {
const width = rows[0].length;
let line = "";
for (let col = 0; col < width; col++) {
const filled = group.some((r) => rows[r][col] === fillChar);
line += filled ? fillChar : " ";
}
return line;
});
}
return out;
}
// Create two more compact versions by collapsing the solidFont:
// "compact" (3 rows): merge rows [0,1] → row0, [2] → row1, [3,4] → row2
// "tiny" (2 rows): merge rows [0,1,2] → row0, [3,4] → row1
const compactFont = collapseFont(solidFont, [
[0, 1],
[2],
[3, 4],
]);
const tinyFont = collapseFont(solidFont, [
[0, 1, 2],
[3, 4],
]);
/**
* Render text into styled unicode block letters.
*
* @example
* ```ts
* import { renderText } from "./block_letters.ts";
*
* console.log(renderText("RATATUI", "solid"));
* console.log(renderText("RATATUI", "ascii"));
* console.log(renderText("RATATUI", "compact"));
* console.log(renderText("RATATUI", "tiny"));
* ```
*
* @param input - The text to render. Only A–Z and space are supported.
* @param style - The block letter style to use. One of "solid" (5 rows),
* "ascii" (5 rows, '*' instead of '█'),
* "compact" (3 rows),
* or "tiny" (2 rows). Defaults to "solid".
* @returns The rendered string with newline-separated rows.
*/
export function renderText(
input: string,
style: Style = "solid",
): string {
let font: Record<string, string[]>;
switch (style) {
case "ascii":
font = asciiFont;
break;
case "compact":
font = compactFont;
break;
case "tiny":
font = tinyFont;
break;
case "solid":
default:
font = solidFont;
break;
}
const rowsCount = font["A"].length;
const rows: string[] = Array(rowsCount).fill("");
const text = input.toUpperCase();
for (const ch of text) {
const charRows = font[ch] ?? font[" "];
for (let i = 0; i < rowsCount; i++) {
rows[i] += charRows[i] + " ";
}
}
return rows.map((line) => line.trimEnd()).join("\n");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment