Gaming profiles and Instagram bios with bold or cursive text are everywhere now. It's easy to assume these use custom fonts, but the method is simpler than you'd think — it's all Unicode characters, not fancy typography.
I recently released PrettyTxt, a free tool for creating this "fancy" text, and wanted to share how it actually works under the hood.
The Unicode trick nobody talks about
Unicode offers a dedicated block of Mathematical Alphanumeric Symbols (U+1D400 – U+1D7FF) with distinctive visual styles. These code points — originally designed for mathematical notation — form complete alphabets in bold, italic, script, fraktur, double-struck, monospace, and several other variations.
The key takeaway: these characters look like styled Latin letters, but they're actually distinct code points. Writing "Hello" in bold Unicode doesn't apply formatting — it produces 𝐇𝐞𝐥𝐥𝐨, which consists of five individual characters.
Standard Unicode support ensures they work across platforms — Instagram, Discord, Twitter, WhatsApp, PUBG, Free Fire, and most other text input fields.
The code: character mapping with offsets
The process isn't as complicated as it seems. Each mathematical alphabet operates under a predictable structure — the letters A through Z and a through z are arranged in sequence, with a consistent offset from their ASCII code points.
Here's the core function:
function makeAlphaMap(
upperStart: number,
lowerStart: number,
exceptions?: Record<string, string>,
): Record<string, string> {
const map: Record<string, string> = {};
for (let i = 0; i < 26; i++) {
map[String.fromCharCode(65 + i)] = String.fromCodePoint(upperStart + i);
map[String.fromCharCode(97 + i)] = String.fromCodePoint(lowerStart + i);
}
if (exceptions) Object.assign(map, exceptions);
return map;
}
With this single function, you can generate complete character maps for every mathematical style:
const maps = {
boldSerif: makeAlphaMap(0x1D400, 0x1D41A),
italicSerif: makeAlphaMap(0x1D434, 0x1D44E, { h: "\u{1D489}" }),
script: makeAlphaMap(0x1D49C, 0x1D4B6, { B: "\u212C", E: "\u2130" }),
fraktur: makeAlphaMap(0x1D504, 0x1D51E, { C: "\u212D", H: "\u210C" }),
doubleStruck: makeAlphaMap(0x1D538, 0x1D552, { C: "\u2102", N: "\u2115" }),
boldSans: makeAlphaMap(0x1D5D4, 0x1D5EE),
monospace: makeAlphaMap(0x1D670, 0x1D68A),
};
That exceptions parameter is important. Unicode didn't perfectly align every letter — some characters were assigned to different blocks before the Mathematical Alphanumeric Symbols block was even defined. The script capital B (ℬ) lives at U+212C, not where you'd expect. The italic lowercase h (ℎ) is at U+1D489, deviating from its expected position. Without these exception maps, you'd end up with incorrect or missing characters.
Beyond math symbols: manual maps
Everything isn't purely algorithmic, though. Small caps, superscripts, and some decorative styles require separate character tables, as their Unicode code points are scattered across multiple blocks:
const smallCapsMap: Record<string, string> = {
a: "\u1D00", // LATIN LETTER SMALL CAPITAL A
b: "\u0299", // LATIN LETTER SMALL CAPITAL B
c: "\u1D04", // LATIN LETTER SMALL CAPITAL C
// ... each letter at a different, unpredictable offset
q: "Q", // no small cap exists — fallback to regular Q
x: "x", // no small cap exists — fallback to regular x
};
A few characters lack small caps equivalents in Unicode — Q and X, for instance — so we fall back to the standard letter. Handling these gaps smoothly is a common challenge when building Unicode text generators.
The transform function
Converting text is just a map lookup:
export function transformText(
text: string,
charMap: Record<string, string>,
): string {
return [...text].map(c => charMap[c] ?? c).join('');
}
The spread operator [...text] handles multi-byte characters correctly, which matters for inputs that might already include emoji. Characters absent from the map remain untouched — numbers, spaces, and punctuation pass through unchanged.
Gaming decorations
For gaming, basic styled text isn't sufficient. Players want names like ꧁༺ DarkKnight ༻꧂ or ★彡 ProGamer 彡★. These use decorative Unicode symbols from Tibetan, Javanese, Dingbats, and other blocks.
We define decoration templates as prefix-suffix pairs:
const decorations = [
{ name: 'Royal', prefix: '꧁༺ ', suffix: ' ༻꧂' },
{ name: 'Thunder', prefix: '☬ ', suffix: ' ☬' },
{ name: 'Crown', prefix: '👑 ', suffix: ' 👑' },
{ name: 'Stars', prefix: '★彡 ', suffix: ' 彡★' },
];
First apply a Unicode style transform, then wrap with decorations. That yields 15 base styles × 10 decorations = 150 combinations from a compact codebase.
The stack: SvelteKit + Cloudflare Pages
SvelteKit 2 with Svelte 5 felt like the right choice for the frontend. Svelte 5's runes ($state, $derived) make reactive text transformations intuitive — typing updates all 45+ results instantly via derived state, no manual subscriptions needed.
The text generation itself has zero runtime overhead. All character maps are plain JavaScript objects — no heavy library, no canvas rendering, no font downloads. Everything happens through string replacement.
SSR works out of the box with SvelteKit, which matters for SEO on a content-heavy tool site. Deployment is on Cloudflare Pages free tier. The entire site stays under 100KB in transfer size. Edge rendering, global distribution, zero cold starts.
A basic store-based i18n system handles five languages (English, Hindi, Portuguese, Turkish, Indonesian) using URL prefix routing (/en/tool-slug, /hi/tool-slug). No bulky i18n library — just a dictionary object per language.
Three takeaways
Unicode goes deeper than most developers expect. The Mathematical Alphanumeric Symbols block alone contains 996 characters across 13 styles. Combine these with characters from other blocks, and you can build 45+ unique "fonts" without CSS or images.
Exception handling matters more than the algorithm. The makeAlphaMap function is trivial. Crafting reliable exception maps for each style — that's where the real work is. Every style introduces 2-7 characters that break the expected pattern.
Simple tools draw real traffic. People actively search for "Instagram fonts" or "PUBG name generator" wanting instant results. The keyword volumes are solid (22K/month for "tattoo font generator", 12K/month for "PUBG name generator") and competition is surprisingly thin.
If you want to try it: prettytxt.com — 45+ Unicode styles, gaming decorations, name generators. Free, no signup.
The approach described here works for any language built on the Latin alphabet. Non-Latin scripts need different Unicode blocks, but the same mapping principle applies.
Top comments (0)