Packages

Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.

Current section

Files

Jump to
raxol lib raxol terminal ansi character_translations.ex
Raw

lib/raxol/terminal/ansi/character_translations.ex

defmodule Raxol.Terminal.ANSI.CharacterTranslations do
@moduledoc """
Provides character translation tables for different character sets.
Maps characters between different character sets according to ANSI standards.
"""
require Raxol.Core.Runtime.Log
# US ASCII character set (G0)
@us_ascii_map %{
# Control characters (0x00-0x1F) are not translated
# Printable ASCII (0x20-0x7E) are not translated
# DEL (0x7F) is not translated
}
# UK character set (G1)
@uk_map %{
# # -> £
0x23 => 0xA3,
# @ -> £
0x40 => 0xA3,
# [ -> £
0x5B => 0xA3,
# \ -> £
0x5C => 0xA3,
# ] -> £
0x5D => 0xA3,
# { -> £
0x7B => 0xA3,
# | -> £
0x7C => 0xA3,
# } -> £
0x7D => 0xA3,
# ~ -> £
0x7E => 0xA3
}
# French character set (G1)
@french_map %{
# # -> £
0x23 => 0xA3,
# @ -> à
0x40 => 0xE0,
# [ -> °
0x5B => 0xB0,
# \ -> ç
0x5C => 0xE7,
# ] -> §
0x5D => 0xA7,
# { -> é
0x7B => 0xE9,
# | -> ù
0x7C => 0xF9,
# } -> è
0x7D => 0xE8,
# ~ -> ù
0x7E => 0xF9
}
# German character set (G1)
@german_map %{
# # -> £
0x23 => 0xA3,
# @ -> §
0x40 => 0xA7,
# [ -> Ä
0x5B => 0xC4,
# \ -> Ö
0x5C => 0xD6,
# ] -> Ü
0x5D => 0xDC,
# { -> ä
0x7B => 0xE4,
# | -> ö
0x7C => 0xF6,
# } -> ü
0x7D => 0xFC,
# ~ -> ß
0x7E => 0xDF
}
# Latin-1 character set (ISO-8859-1)
@latin1_map %{
# Add Latin-1 specific mappings
# Euro sign
0x80 => 0x20AC,
# Control character
0x81 => 0x0081,
# Single low-9 quotation mark
0x82 => 0x201A,
# Latin small f with hook
0x83 => 0x0192,
# Double low-9 quotation mark
0x84 => 0x201E,
# Horizontal ellipsis
0x85 => 0x2026,
# Dagger
0x86 => 0x2020,
# Double dagger
0x87 => 0x2021,
# Modifier letter circumflex accent
0x88 => 0x02C6,
# Per mille sign
0x89 => 0x2030,
# Latin capital letter S with caron
0x8A => 0x0160,
# Single left-pointing angle quotation mark
0x8B => 0x2039,
# Latin capital ligature OE
0x8C => 0x0152,
# Control character
0x8D => 0x008D,
# Latin capital letter Z with caron
0x8E => 0x017D,
# Control character
0x8F => 0x008F,
# Control character
0x90 => 0x0090,
# Left single quotation mark
0x91 => 0x2018,
# Right single quotation mark
0x92 => 0x2019,
# Left double quotation mark
0x93 => 0x201C,
# Right double quotation mark
0x94 => 0x201D,
# Bullet
0x95 => 0x2022,
# En dash
0x96 => 0x2013,
# Em dash
0x97 => 0x2014,
# Small tilde
0x98 => 0x02DC,
# Trade mark sign
0x99 => 0x2122,
# Latin small letter s with caron
0x9A => 0x0161,
# Single right-pointing angle quotation mark
0x9B => 0x203A,
# Latin small ligature oe
0x9C => 0x0153,
# Control character
0x9D => 0x009D,
# Latin small letter z with caron
0x9E => 0x017E,
# Latin capital letter Y with diaeresis
0x9F => 0x0178
}
# DEC Special Graphics Character Set
@dec_special_graphics_map %{
# _ -> Non-breaking space
0x5F => 0xA0,
# ` -> Diamond (◆)
0x60 => 0x25C6,
# a -> Checkerboard (▒)
0x61 => 0x2592,
# b -> HT symbol (HT)
0x62 => 0x2409,
# c -> FF symbol (FF)
0x63 => 0x240C,
# d -> CR symbol (CR)
0x64 => 0x240D,
# e -> LF symbol (LF)
0x65 => 0x240A,
# f -> Degree sign (°)
0x66 => 0x00B0,
# g -> Plus/minus sign (±)
0x67 => 0x00B1,
# h -> NL symbol (NL)
0x68 => 0x2424,
# i -> VT symbol (VT)
0x69 => 0x240B,
# j -> Lower right corner (┘)
0x6A => 0x2518,
# k -> Upper right corner (┐)
0x6B => 0x2510,
# l -> Upper left corner (┌)
0x6C => 0x250C,
# m -> Lower left corner (└)
0x6D => 0x2514,
# n -> Crossing lines (+) (┼)
0x6E => 0x253C,
# o -> Scan line 1 (⎺)
0x6F => 0x23BA,
# p -> Scan line 3 (⎻)
0x70 => 0x23BB,
# q -> Scan line 5 / Horizontal line (─)
0x71 => 0x2500,
# r -> Scan line 7 (⎼)
0x72 => 0x23BC,
# s -> Scan line 9 (⎽)
0x73 => 0x23BD,
# t -> Tee pointing right (├)
0x74 => 0x251C,
# u -> Tee pointing left (┤)
0x75 => 0x2524,
# v -> Tee pointing up (┴)
0x76 => 0x2534,
# w -> Tee pointing down (┬)
0x77 => 0x252C,
# x -> Vertical line (│)
0x78 => 0x2502,
# y -> Less than or equal to (≤)
0x79 => 0x2264,
# z -> Greater than or equal to (≥)
0x7A => 0x2265,
# { -> Pi (π)
0x7B => 0x03C0,
# | -> Not equal to (≠)
0x7C => 0x2260,
# } -> Pound sign (£)
0x7D => 0x00A3,
# ~ -> Centered dot (·)
0x7E => 0x00B7
}
# DEC Technical Character Set
@dec_technical_map %{
# Add DEC Technical character set mappings
# Degree sign
0x60 => 0x00B0,
# Plus-minus sign
0x61 => 0x00B1,
# Superscript two
0x62 => 0x00B2,
# Superscript three
0x63 => 0x00B3,
# Acute accent
0x64 => 0x00B4,
# Micro sign
0x65 => 0x00B5,
# Pilcrow sign
0x66 => 0x00B6,
# Middle dot
0x67 => 0x00B7,
# Cedilla
0x68 => 0x00B8,
# Superscript one
0x69 => 0x00B9,
# Masculine ordinal indicator
0x6A => 0x00BA,
# Right-pointing double angle quotation mark
0x6B => 0x00BB,
# Vulgar fraction one quarter
0x6C => 0x00BC,
# Vulgar fraction one half
0x6D => 0x00BD,
# Vulgar fraction three quarters
0x6E => 0x00BE,
# Inverted question mark
0x6F => 0x00BF,
# Latin capital letter A with grave
0x70 => 0x00C0,
# Latin capital letter A with acute
0x71 => 0x00C1,
# Latin capital letter A with circumflex
0x72 => 0x00C2,
# Latin capital letter A with tilde
0x73 => 0x00C3,
# Latin capital letter A with diaeresis
0x74 => 0x00C4,
# Latin capital letter A with ring above
0x75 => 0x00C5,
# Latin capital letter AE
0x76 => 0x00C6,
# Latin capital letter C with cedilla
0x77 => 0x00C7,
# Latin capital letter E with grave
0x78 => 0x00C8,
# Latin capital letter E with acute
0x79 => 0x00C9,
# Latin capital letter E with circumflex
0x7A => 0x00CA,
# Latin capital letter E with diaeresis
0x7B => 0x00CB,
# Latin capital letter I with grave
0x7C => 0x00CC,
# Latin capital letter I with acute
0x7D => 0x00CD,
# Latin capital letter I with circumflex
0x7E => 0x00CE
}
# DEC Supplementary Character Set
@dec_supplementary_map %{
# Add DEC Supplementary character set mappings
# Non-breaking space
0x60 => 0x00A0,
# Inverted exclamation mark
0x61 => 0x00A1,
# Cent sign
0x62 => 0x00A2,
# Pound sign
0x63 => 0x00A3,
# Currency sign
0x64 => 0x00A4,
# Yen sign
0x65 => 0x00A5,
# Broken bar
0x66 => 0x00A6,
# Section sign
0x67 => 0x00A7,
# Diaeresis
0x68 => 0x00A8,
# Copyright sign
0x69 => 0x00A9,
# Feminine ordinal indicator
0x6A => 0x00AA,
# Left-pointing double angle quotation mark
0x6B => 0x00AB,
# Not sign
0x6C => 0x00AC,
# Soft hyphen
0x6D => 0x00AD,
# Registered sign
0x6E => 0x00AE,
# Macron
0x6F => 0x00AF,
# Degree sign
0x70 => 0x00B0,
# Plus-minus sign
0x71 => 0x00B1,
# Superscript two
0x72 => 0x00B2,
# Superscript three
0x73 => 0x00B3,
# Acute accent
0x74 => 0x00B4,
# Micro sign
0x75 => 0x00B5,
# Pilcrow sign
0x76 => 0x00B6,
# Middle dot
0x77 => 0x00B7,
# Cedilla
0x78 => 0x00B8,
# Superscript one
0x79 => 0x00B9,
# Masculine ordinal indicator
0x7A => 0x00BA,
# Right-pointing double angle quotation mark
0x7B => 0x00BB,
# Vulgar fraction one quarter
0x7C => 0x00BC,
# Vulgar fraction one half
0x7D => 0x00BD,
# Vulgar fraction three quarters
0x7E => 0x00BE
}
# DEC Supplementary Graphics Character Set
@dec_supplementary_graphics_map %{
# Add DEC Supplementary Graphics character set mappings
# Black square
0x60 => 0x25A0,
# White square
0x61 => 0x25A1,
# White square with rounded corners
0x62 => 0x25A2,
# White square containing black small square
0x63 => 0x25A3,
# Square with horizontal fill
0x64 => 0x25A4,
# Square with vertical fill
0x65 => 0x25A5,
# Square with orthogonal crosshatch fill
0x66 => 0x25A6,
# Square with upper left to lower right fill
0x67 => 0x25A7,
# Square with upper right to lower left fill
0x68 => 0x25A8,
# Square with diagonal crosshatch fill
0x69 => 0x25A9,
# Black small square
0x6A => 0x25AA,
# White small square
0x6B => 0x25AB,
# Black rectangle
0x6C => 0x25AC,
# White rectangle
0x6D => 0x25AD,
# Black vertical rectangle
0x6E => 0x25AE,
# White vertical rectangle
0x6F => 0x25AF,
# Black parallelogram
0x70 => 0x25B0,
# White parallelogram
0x71 => 0x25B1,
# Black up-pointing triangle
0x72 => 0x25B2,
# White up-pointing triangle
0x73 => 0x25B3,
# Black up-pointing small triangle
0x74 => 0x25B4,
# White up-pointing small triangle
0x75 => 0x25B5,
# Black right-pointing triangle
0x76 => 0x25B6,
# White right-pointing triangle
0x77 => 0x25B7,
# Black right-pointing small triangle
0x78 => 0x25B8,
# White right-pointing small triangle
0x79 => 0x25B9,
# Black right-pointing pointer
0x7A => 0x25BA,
# White right-pointing pointer
0x7B => 0x25BB,
# Black down-pointing triangle
0x7C => 0x25BC,
# White down-pointing triangle
0x7D => 0x25BD,
# Black down-pointing small triangle
0x7E => 0x25BE
}
# Map of character set names to their translation tables
@charset_tables %{
us_ascii: @us_ascii_map,
uk: @uk_map,
french: @french_map,
german: @german_map,
latin1: @latin1_map,
dec_special_graphics: @dec_special_graphics_map,
dec_technical: @dec_technical_map,
dec_supplementary: @dec_supplementary_map,
dec_supplementary_graphics: @dec_supplementary_graphics_map
}
@doc """
Returns the US ASCII character set translation table.
## Returns
* Map containing US ASCII character translations
## Examples
iex> Raxol.Terminal.ANSI.CharacterTranslations.us_ascii()
%{...}
"""
def us_ascii, do: @us_ascii_map
@doc """
Returns the UK character set translation table.
## Returns
* Map containing UK character translations
## Examples
iex> Raxol.Terminal.ANSI.CharacterTranslations.uk()
%{...}
"""
def uk, do: @uk_map
@doc """
Returns the French character set translation table.
## Returns
* Map containing French character translations
## Examples
iex> Raxol.Terminal.ANSI.CharacterTranslations.french()
%{...}
"""
def french, do: @french_map
@doc """
Returns the German character set translation table.
## Returns
* Map containing German character translations
## Examples
iex> Raxol.Terminal.ANSI.CharacterTranslations.german()
%{...}
"""
def german, do: @german_map
@doc """
Returns the Latin-1 character set translation table.
## Returns
* Map containing Latin-1 character translations
## Examples
iex> Raxol.Terminal.ANSI.CharacterTranslations.latin1()
%{...}
"""
def latin1, do: @latin1_map
@doc """
Returns the DEC Special Graphics character set translation table.
## Returns
* Map containing DEC Special Graphics character translations
## Examples
iex> Raxol.Terminal.ANSI.CharacterTranslations.dec_special()
%{...}
"""
def dec_special, do: @dec_special_graphics_map
@doc """
Returns the DEC Supplementary character set translation table.
## Returns
* Map containing DEC Supplementary character translations
## Examples
iex> Raxol.Terminal.ANSI.CharacterTranslations.dec_supplementary()
%{...}
"""
def dec_supplementary, do: @dec_supplementary_map
@doc """
Returns the DEC Technical character set translation table.
## Returns
* Map containing DEC Technical character translations
## Examples
iex> Raxol.Terminal.ANSI.CharacterTranslations.dec_technical()
%{...}
"""
def dec_technical, do: @dec_technical_map
@doc """
Returns the DEC Supplementary Graphics character set translation table.
## Returns
* Map containing DEC Supplementary Graphics character translations
## Examples
iex> Raxol.Terminal.ANSI.CharacterTranslations.dec_supplementary_graphics()
%{...}
"""
def dec_supplementary_graphics, do: @dec_supplementary_graphics_map
@doc """
Translates a character from the source character set to the target character set.
## Parameters
* `char_codepoint` - The Unicode codepoint of the character to translate
* `charset` - The target character set to translate to (e.g., `:us_ascii`, `:uk`, `:french`)
## Returns
* Binary containing the translated character in UTF-8 encoding
* The original character if no translation exists
## Examples
iex> Raxol.Terminal.ANSI.CharacterTranslations.translate_char(?é, :us_ascii)
"e"
iex> Raxol.Terminal.ANSI.CharacterTranslations.translate_char(?a, :us_ascii)
"a"
"""
@spec translate_char(char_codepoint :: integer(), charset :: atom()) ::
binary()
def translate_char(char, charset) when is_integer(char) do
map = Map.get(@charset_tables, charset, %{})
codepoint = Map.get(map, char, char)
case Raxol.Core.ErrorHandling.safe_call(fn -> <<codepoint::utf8>> end) do
{:ok, result} ->
result
{:error, _reason} ->
# Fallback for invalid codepoints
Raxol.Core.Runtime.Log.warning_with_context(
"Invalid codepoint #{codepoint} in charset #{charset}",
%{}
)
<<char>>
end
end
@doc """
Translates a string from the source character set to the target character set.
Handles invalid bytes gracefully by passing them through as-is.
"""
@spec translate_string(string :: String.t(), charset :: atom()) :: String.t()
def translate_string(string, charset) when is_binary(string) do
charlist = get_charlist(string)
translated = translate_charlist(charlist, charset)
IO.iodata_to_binary(translated)
end
defp get_charlist(string) do
case :unicode.characters_to_list(string, :utf8) do
{:ok, charlist, _} -> charlist
{:error, _, _} -> :binary.bin_to_list(string)
{:incomplete, _, _} -> :binary.bin_to_list(string)
charlist when is_list(charlist) -> charlist
end
end
defp translate_charlist(charlist, charset) do
Enum.map(charlist, fn
[0] -> translate_char(0, charset)
int when is_integer(int) -> translate_char(int, charset)
_ -> ""
end)
end
@doc """
Map of Unicode codepoints to their ANSI terminal equivalents.
"""
def unicode_to_ansi do
%{
# NO-BREAK SPACE
0x00A0 => 32,
# EN DASH
0x2013 => 45,
# EM DASH
0x2014 => 45,
# LEFT SINGLE QUOTATION MARK
0x2018 => 39,
# RIGHT SINGLE QUOTATION MARK
0x2019 => 39,
# LEFT DOUBLE QUOTATION MARK
0x201C => 34,
# RIGHT DOUBLE QUOTATION MARK
0x201D => 34,
# HORIZONTAL ELLIPSIS
0x2026 => 46,
# LINE SEPARATOR
0x2028 => 10,
# PARAGRAPH SEPARATOR
0x2029 => 10,
# NARROW NO-BREAK SPACE
0x202F => 32,
# MEDIUM MATHEMATICAL SPACE
0x205F => 32,
# IDEOGRAPHIC SPACE
0x3000 => 32
}
end
end