Char variants
A char variant is a transformation that is applied to every character of a text at the moment it is written on a surface. It allows you to display a text in a different style (bold, italic, script, circled, superscript, etc.) without having to change the text itself. The styled forms are regular Unicode characters (most of them from the Mathematical Alphanumeric Symbols, Enclosed Alphanumerics and Phonetic Extensions blocks), so they work in any terminal whose font contains those glyphs.
Char variants are described by the CharVariant enum:
#![allow(unused)]
fn main() {
pub enum CharVariant {
None, // default - no conversion
Uppercase,
Lowercase,
MathBold,
MathItalic,
MathBoldItalic,
Script,
ScriptBold,
Fraktur,
FrakturBold,
DoubleStruck,
SansSerif,
SansSerifBold,
SansSerifItalic,
SansSerifBoldItalic,
Monospace,
Circled,
CircledNegative,
Squared,
SquaredNegative,
Superscript,
Subscript,
SmallCaps,
Custom(fn(char) -> char),
}
}
Usage
With write_text(...)
The most common way to use a char variant is through the TextFormatBuilder (see Text). The char_variant(...) method sets the variant that will be applied to every character written by write_text(...):
#![allow(unused)]
fn main() {
use appcui::prelude::*;
let mut surface = Surface::new(40, 5);
let format = TextFormatBuilder::new()
.position(1, 1)
.attribute(CharAttribute::with_color(Color::Yellow, Color::DarkBlue))
.align(TextAlignment::Left)
.char_variant(CharVariant::MathBold)
.build();
surface.write_text("Hello World 2026", &format);
// the surface now contains: ๐๐๐ฅ๐ฅ๐จ ๐๐จ๐ซ๐ฅ๐ ๐๐๐๐
}
The conversion is done one character at a time and always produces exactly one character, so:
- the number of characters (and therefore alignment, wrapping and hotkey positions) is the same as for the original text;
- the hotkey character (if any) is converted as well, but it keeps the hotkey attribute;
- the original string is never modified - only what is drawn on the surface changes.
The default value is CharVariant::None, meaning that the text is written as it is.
With map(...)
A CharVariant can also be used directly, to convert a single character through the map(...) method:
#![allow(unused)]
fn main() {
pub fn map(&self, ch: char) -> char
}
This is useful if you want to build a converted string yourself (for example to use it with write_string(...) or to store it):
#![allow(unused)]
fn main() {
use appcui::prelude::*;
let v = CharVariant::Circled;
assert_eq!(v.map('A'), 'โถ');
assert_eq!(v.map('7'), 'โฆ');
assert_eq!(v.map('!'), '!'); // characters without a variant are left unchanged
let text: String = "Hello".chars().map(|c| CharVariant::DoubleStruck.map(c)).collect();
assert_eq!(text, "โ๐๐๐๐ ");
}
Custom variants
If none of the predefined variants fits your needs, CharVariant::Custom accepts any function with the signature fn(char) -> char:
#![allow(unused)]
fn main() {
use appcui::prelude::*;
fn leet(c: char) -> char {
match c {
'o' | 'O' => '0',
'l' | 'L' => '1',
'e' | 'E' => '3',
_ => c,
}
}
let format = TextFormatBuilder::new()
.position(0, 0)
.char_variant(CharVariant::Custom(leet))
.build();
surface.write_text("Hello World", &format); // writes: H3110 W0r1d
}
Translation rules
The following table describes how each variant translates characters. Any character that is not listed in the Translation column (punctuation, spaces, accented letters, other Unicode characters, etc.) is left unchanged.
| Variant | Translation | Notes |
|---|---|---|
None | - | No conversion (default). |
Uppercase | any letter โ its upper case form | Uses Rustโs Unicode case mapping (works for non-ASCII letters as well, e.g. ฤ โ ฤ). Only the first character of the mapping is kept. |
Lowercase | any letter โ its lower case form | Same as Uppercase, but for lower case. |
MathBold | A-Z โ ๐-๐, a-z โ ๐-๐ณ, 0-9 โ ๐-๐ | |
MathItalic | A-Z โ ๐ด-๐, a-z โ ๐-๐ง | h โ โ (Planck constant), because the regular italic h slot is not assigned in Unicode. Digits are not converted. |
MathBoldItalic | A-Z โ ๐จ-๐, a-z โ ๐-๐, 0-9 โ ๐-๐ | There are no bold-italic digits, so digits use the bold form. |
Script | A-Z โ ๐-๐ต, a-z โ ๐ถ-๐ | B E F H I L M R โ โฌ โฐ โฑ โ โ โ โณ โ and e g o โ โฏ โ โด (taken from the Letterlike Symbols block). Digits are not converted. |
ScriptBold | A-Z โ ๐-๐ฉ, a-z โ ๐ช-๐, 0-9 โ ๐-๐ | Digits use the bold form. |
Fraktur | A-Z โ ๐-โจ, a-z โ ๐-๐ท | C H I R Z โ โญ โ โ โ โจ (taken from the Letterlike Symbols block). Digits are not converted. |
FrakturBold | A-Z โ ๐ฌ-๐
, a-z โ ๐-๐, 0-9 โ ๐-๐ | Digits use the bold form. |
DoubleStruck | A-Z โ ๐ธ-โค, a-z โ ๐-๐ซ, 0-9 โ ๐-๐ก | C H N P Q R Z โ โ โ โ โ โ โ โค (taken from the Letterlike Symbols block). |
SansSerif | A-Z โ ๐ -๐น, a-z โ ๐บ-๐, 0-9 โ ๐ข-๐ซ | |
SansSerifBold | A-Z โ ๐-๐ญ, a-z โ ๐ฎ-๐, 0-9 โ ๐ฌ-๐ต | |
SansSerifItalic | A-Z โ ๐-๐ก, a-z โ ๐ข-๐ป, 0-9 โ ๐ข-๐ซ | There are no sans-serif italic digits, so digits use the sans-serif form. |
SansSerifBoldItalic | A-Z โ ๐ผ-๐, a-z โ ๐-๐ฏ, 0-9 โ ๐ฌ-๐ต | Digits use the sans-serif bold form. |
Monospace | A-Z โ ๐ฐ-๐, a-z โ ๐-๐ฃ, 0-9 โ ๐ถ-๐ฟ | |
Circled | A-Z โ โถ-โ, a-z โ โ-โฉ, 1-9 โ โ -โจ, 0 โ โช | |
CircledNegative | A-Z โ ๐
-๐
ฉ, a-z โ ๐
-๐
ฉ, 1-9 โ โถ-โพ, 0 โ โฟ | Only upper case letters exist, so lower case letters are converted to their upper case form first. |
Squared | A-Z โ ๐ฐ-๐
, a-z โ ๐ฐ-๐
| Only upper case letters exist (lower case is converted to upper case first). Digits are not converted. |
SquaredNegative | A-Z โ ๐
ฐ-๐, a-z โ ๐
ฐ-๐ | Only upper case letters exist (lower case is converted to upper case first). Digits are not converted. Some fonts render ๐ ฐ ๐ ฑ ๐ พ ๐ ฟ as emoji (two cells wide). |
Superscript | A-Z โ แดฌ แดฎ แถ แดฐ แดฑ แถ แดณ แดด แดต แดถ แดท แดธ แดน แดบ แดผ แดพ Q แดฟ หข แต แต โฑฝ แต หฃ สธ แถปa-z โ แต แต แถ แต แต แถ แต สฐ โฑ สฒ แต หก แต โฟ แต แต q สณ หข แต แต แต สท หฃ สธ แถป0-9 โ โฐ-โน, + - = ( ) โ โบ โป โผ โฝ โพ | C F S X Y Z have no superscript capital, so the lower case superscript form is used. Q and q have no superscript form and are left unchanged. |
Subscript | a e h i j k l m n o p r s t u v x (and their upper case forms) โ โ โ โ แตข โฑผ โ โ โ โ โ โ แตฃ โ โ แตค แตฅ โ0-9 โ โ-โ, + - = ( ) โ โ โ โ โ โ | Unicode has no upper case subscript letters and only some lower case ones, so upper case is mapped to the lower case subscript and b c d f g q w y z are left unchanged. |
SmallCaps | a-z (and their upper case forms) โ แด ส แด แด
แด ๊ฐ ษข ส ษช แด แด ส แด ษด แด แด ๊ฏ ส ๊ฑ แด แด แด แดก x ส แดข | There is no small capital x, so x/X are left unchanged. Currently T (upper case) is not converted - only t is. |
Custom(f) | every character c โ f(c) | The function is called for every character that is written. |
Examples
The following table shows how the text Hello World 2026 is displayed with each variant:
| Variant | Result |
|---|---|
None | Hello World 2026 |
Uppercase | HELLO WORLD 2026 |
Lowercase | hello world 2026 |
MathBold | ๐๐๐ฅ๐ฅ๐จ ๐๐จ๐ซ๐ฅ๐ ๐๐๐๐ |
MathItalic | ๐ป๐๐๐๐ ๐๐๐๐๐ 2026 |
MathBoldItalic | ๐ฏ๐๐๐๐ ๐พ๐๐๐๐ ๐๐๐๐ |
Script | โโฏ๐๐โด ๐ฒโด๐๐๐น 2026 |
ScriptBold | ๐๐ฎ๐ต๐ต๐ธ ๐ฆ๐ธ๐ป๐ต๐ญ ๐๐๐๐ |
Fraktur | โ๐ข๐ฉ๐ฉ๐ฌ ๐๐ฌ๐ฏ๐ฉ๐ก 2026 |
FrakturBold | ๐ณ๐๐๐๐ ๐๐๐๐๐ ๐๐๐๐ |
DoubleStruck | โ๐๐๐๐ ๐๐ ๐ฃ๐๐ ๐๐๐๐ |
SansSerif | ๐ง๐พ๐ ๐ ๐ ๐ถ๐๐๐ ๐ฝ ๐ค๐ข๐ค๐จ |
SansSerifBold | ๐๐ฒ๐น๐น๐ผ ๐ช๐ผ๐ฟ๐น๐ฑ ๐ฎ๐ฌ๐ฎ๐ฒ |
SansSerifItalic | ๐๐ฆ๐ญ๐ญ๐ฐ ๐๐ฐ๐ณ๐ญ๐ฅ ๐ค๐ข๐ค๐จ |
SansSerifBoldItalic | ๐๐๐ก๐ก๐ค ๐๐ค๐ง๐ก๐ ๐ฎ๐ฌ๐ฎ๐ฒ |
Monospace | ๐ท๐๐๐๐ ๐๐๐๐๐ ๐ธ๐ถ๐ธ๐ผ |
Circled | โฝโโโโ โโโกโโ โกโชโกโฅ |
CircledNegative | ๐ ๐ ๐ ๐ ๐ ๐ ฆ๐ ๐ ก๐ ๐ โทโฟโทโป |
Squared | ๐ท๐ด๐ป๐ป๐พ ๐ ๐พ๐ ๐ป๐ณ 2026 |
SquaredNegative | ๐ ท๐ ด๐ ป๐ ป๐ พ ๐๐ พ๐๐ ป๐ ณ 2026 |
Superscript | แดดแตหกหกแต แตแตสณหกแต ยฒโฐยฒโถ |
Subscript | โโโโโ Wโแตฃโd โโโโ |
SmallCaps | สแดสสแด แดกแดสสแด 2026 |
Writing the same text with multiple variants
The following example writes Hello World 2026 on several lines of a surface, each one using a different variant:
#![allow(unused)]
fn main() {
use appcui::prelude::*;
let variants = [
("None", CharVariant::None),
("MathBold", CharVariant::MathBold),
("Script", CharVariant::Script),
("Fraktur", CharVariant::Fraktur),
("DoubleStruck", CharVariant::DoubleStruck),
("Circled", CharVariant::Circled),
("Superscript", CharVariant::Superscript),
("SmallCaps", CharVariant::SmallCaps),
];
let mut surface = Surface::new(40, variants.len() as u32);
for (index, (name, variant)) in variants.iter().enumerate() {
// the name of the variant (written as it is)
surface.write_string(0, index as i32, name, CharAttribute::with_fore_color(Color::Gray), false);
// the text, converted with the current variant
let format = TextFormatBuilder::new()
.position(16, index as i32)
.attribute(CharAttribute::with_color(Color::Yellow, Color::DarkBlue))
.char_variant(*variant)
.build();
surface.write_text("Hello World 2026", &format);
}
}
The result will look like this:
None Hello World 2026
MathBold ๐๐๐ฅ๐ฅ๐จ ๐๐จ๐ซ๐ฅ๐ ๐๐๐๐
Script โโฏ๐๐โด ๐ฒโด๐๐๐น 2026
Fraktur โ๐ข๐ฉ๐ฉ๐ฌ ๐๐ฌ๐ฏ๐ฉ๐ก 2026
DoubleStruck โ๐๐๐๐ ๐๐ ๐ฃ๐๐ ๐๐๐๐
Circled โฝโโโโ โโโกโโ โกโชโกโฅ
Superscript แดดแตหกหกแต แตแตสณหกแต ยฒโฐยฒโถ
SmallCaps สแดสสแด แดกแดสสแด
2026
Remarks
- Char variants are purely visual: the converted characters are different Unicode code points, so text written this way should not be used for copy/paste, search or accessibility purposes.
- Font support differs between terminals. The Mathematical Alphanumeric Symbols (
MathBold,Script,Fraktur,DoubleStruck,SansSerif*,Monospace) are widely supported, while some of the Enclosed Alphanumeric Supplement characters (CircledNegative,Squared,SquaredNegative) may be missing or rendered as wide emoji. - Because the translation is done per character, variants that do not have a form for a specific character (for example
Subscriptforb, orSquaredfor digits) simply leave that character as it is.