#! /usr/bin/python3
from __future__ import print_function

__copyright__ = "(C) 2021-2022 Guido U. Draheim, licensed under the APLv2"
__version__ = "1.1.1017"

from typing import List, Dict, Generator, Tuple
from io import StringIO
import sys
import logging

logg = logging.getLogger("UNICODER")

if True:
    norm_base_A = ord('A')
    norm_base_Z = ord('Z')
    norm_base_a = ord('a')
    norm_base_z = ord('z')
    norm_base_0 = ord('0')
    norm_base_9 = ord('9')
    norm_base_sz = 0xDF
    ital_base_A = 0x1D434
    ital_base_Z = 0x1D44D
    ital_base_a = 0x1D44E
    ital_base_z = 0x1D467
    ital_sans_A = 0x1D608
    ital_sans_Z = 0x1D621
    ital_sans_a = 0x1D622
    ital_sans_z = 0x1D63B
    bold_base_A = 0x1D400
    bold_base_Z = 0x1D419
    bold_base_a = 0x1D41A
    bold_base_z = 0x1D433
    bold_base_0 = 0x1D7CE
    bold_base_9 = 0x1D7D7
    bold_ital_base_A = 0x1D468
    bold_ital_base_Z = 0x1D481
    bold_ital_base_a = 0x1D482
    bold_ital_base_z = 0x1D49B
    #
    norm_sans_A = 0x1D5A0
    norm_sans_Z = 0x1D5B9
    norm_sans_a = 0x1D5BA
    norm_sans_z = 0x1D5D3
    norm_sans_0 = 0x1D7E2
    norm_sans_9 = 0x1D7EB
    bold_sans_A = 0x1D5D4
    bold_sans_Z = 0x1D5ED
    bold_sans_a = 0x1D5EE
    bold_sans_z = 0x1D607
    bold_sans_0 = 0x1D7EC
    bold_sans_9 = 0x1D8F5
    ital_sans_A = 0x1D608
    ital_sans_Z = 0x1D621
    ital_sans_a = 0x1D622
    ital_sans_z = 0x1D63B
    bold_ital_sans_A = 0x1D63C
    bold_ital_sans_Z = 0x1D655
    bold_ital_sans_a = 0x1D656
    bold_ital_sans_z = 0x1D66F
    bold_sans_A = 0x1D5D4
    bold_sans_Z = 0x1D5ED
    bold_sans_a = 0x1D5EE
    bold_sans_z = 0x1D607
    #
    norm_fraktur_A = 0x1D504
    norm_fraktur_Y = 0x1D51C
    norm_fraktur_a = 0x1D51E
    norm_fraktur_z = 0x1D537
    bold_fraktur_A = 0x1D56C
    bold_fraktur_Z = 0x1D585
    bold_fraktur_a = 0x1D586
    bold_fraktur_z = 0x1D59F
    ital_fraktur_A = 0x1D56C
    ital_fraktur_Z = 0x1D585
    ital_fraktur_a = 0x1D586
    ital_fraktur_z = 0x1D59F
    # bold_ital_fraktur_A = n/a
    # bold_ital_fraktur_Z = n/a
    # bold_ital_fraktur_a = n/a
    # bold_ital_fraktur_z = n/a
    #
    norm_script_A = 0x1D49C
    norm_script_Z = 0x1D4B5
    norm_script_a = 0x1D4B6
    norm_script_z = 0x1D4CF
    bold_script_A = 0x1D4D0
    bold_script_Z = 0x1D4E9
    bold_script_a = 0x1D4EA
    bold_script_z = 0x1D503
    #
    norm_double_A = 0x1D538
    norm_double_Y = 0x1D550
    norm_double_a = 0x1D552
    norm_double_z = 0x1D56B
    norm_double_0 = 0x1D7D8
    norm_double_9 = 0x1D7E1
    norm_mono_A = 0x1D670
    norm_mono_Z = 0x1D689
    norm_mono_a = 0x1D68A
    norm_mono_z = 0x1D6A3
    norm_mono_0 = 0x1D7F6
    norm_mono_9 = 0x1D7FF
    #
    norm_greek_A = 0x391
    norm_greek_O = 0x3A9
    norm_greek_a = 0x3B1
    norm_greek_o = 0x3C9
    norm_greek_nabla = 0x8711
    norm_greek_diffs = 0x2202
    bold_greek_A = 0x1D6A8
    bold_greek_O = 0x1D6C0
    bold_greek_nabla = 0x1D6C1
    bold_greek_a = 0x1D6C2
    bold_greek_o = 0x1D6DA
    bold_greek_diffs = 0x1D6DB
    ital_greek_A = 0x1D6E2
    ital_greek_O = 0x1D6FA
    ital_greek_nabla = 0x1D6FB
    ital_greek_a = 0x1D6FC
    ital_greek_o = 0x1D714
    ital_greek_diffs = 0x1D715
    bold_ital_greek_A = 0x1D71C
    bold_ital_greek_O = 0x1D734
    bold_ital_greek_nabla = 0x1D735
    bold_ital_greek_a = 0x1D736
    bold_ital_greek_o = 0x1D74E
    bold_ital_greek_diffs = 0x1D74F
    #
    norm_frac_1_4 = 0x00BC
    norm_frac_1_2 = 0x00BD
    norm_frac_3_4 = 0x00BE
    norm_frac_1_7 = 0x2150
    norm_frac_1_9 = 0x2151
    norm_frac_1_10 = 0x2152
    norm_frac_1_3 = 0x2153
    norm_frac_2_3 = 0x2154
    norm_frac_1_5 = 0x2155
    norm_frac_2_5 = 0x2156
    norm_frac_3_5 = 0x2157
    norm_frac_4_5 = 0x2158
    norm_frac_1_6 = 0x2159
    norm_frac_5_6 = 0x215A
    norm_frac_1_8 = 0x215B
    norm_frac_3_8 = 0x215C
    norm_frac_5_8 = 0x215D
    norm_frac_7_8 = 0x215E
    norm_frac_1_x = 0x215F
    norm_frac_0_3 = 0x2189
    norm_base_space = 0x20
    norm_nobr_space = 0x00A0
    norm_thin_space = 0x202F
    norm_numm_space = 0x2007

def nobrspace(text: str) -> str:
    """replace base space by thin nobreak space """
    out = StringIO()
    last_ch = 0
    for c in text:
        ch = ord(c)
        if norm_base_space == ch:
            if norm_base_0 <= last_ch and last_ch <= norm_base_9:
                out.write(chr(norm_numm_space))
            else:
                out.write(chr(norm_nobr_space))
        else:
            out.write(c)
        last_ch = ch
    return out.getvalue()

def thinspace(text: str) -> str:
    """replace base space by thin nobreak space """
    out = StringIO()
    for c in text:
        ch = ord(c)
        if norm_base_space == ch or norm_numm_space == ch:
            out.write(chr(norm_thin_space))
        else:
            out.write(c)
    return out.getvalue()

def fractions(text: str) -> str:
    """replace base space by thin nobreak space """
    def splitfrac(text: str) -> Generator[str, str, None]:
        frac = "0123456789/"
        if text[0] in frac:
            isfrac = True
        else:
            isfrac = False
        value = StringIO()
        for c in text:
            if c in frac:
                if isfrac:
                    value.write(c)
                else:
                    yield value.getvalue()
                    value = StringIO()
                    value.write(c)
                    isfrac = True
            else:
                if isfrac:
                    yield value.getvalue()
                    value = StringIO()
                    value.write(c)
                    isfrac = False
                else:
                    value.write(c)
        if value:
            yield value.getvalue()
    out = StringIO()
    space = ""
    for item in splitfrac(text):
        if item == "1/8":
            space = ""
            out.write(chr(norm_frac_1_8))
        elif item == "2/8" or item == "1/4":
            space = ""
            out.write(chr(norm_frac_1_4))
        elif item == "3/8":
            space = ""
            out.write(chr(norm_frac_3_8))
        elif item == "4/8" or item == "2/4" or item == "1/2":
            space = ""
            out.write(chr(norm_frac_1_2))
        elif item == "5/8":
            space = ""
            out.write(chr(norm_frac_5_8))
        elif item == "6/8" or item == "3/4":
            space = ""
            out.write(chr(norm_frac_3_4))
        elif item == "7/8":
            space = ""
            out.write(chr(norm_frac_7_8))
        elif item == "1/5":
            space = ""
            out.write(chr(norm_frac_1_5))
        elif item == "2/5":
            space = ""
            out.write(chr(norm_frac_2_5))
        elif item == "3/5":
            space = ""
            out.write(chr(norm_frac_3_5))
        elif item == "4/5":
            space = ""
            out.write(chr(norm_frac_4_5))
        elif item == "0/6" or item == "0/3":
            space = ""
            out.write(chr(norm_frac_0_3))
        elif item == "1/6":
            space = ""
            out.write(chr(norm_frac_1_6))
        elif item == "2/6" or item == "1/3":
            space = ""
            out.write(chr(norm_frac_1_3))
        elif item == "3/6":
            space = ""
            out.write(chr(norm_frac_1_2))
        elif item == "4/6" or item == "2/3":
            space = ""
            out.write(chr(norm_frac_2_3))
        elif item == "5/6":
            space = ""
            out.write(chr(norm_frac_5_6))
        else:
            if ord(item[-1]) in [norm_base_space, norm_thin_space, norm_nobr_space, norm_numm_space]:
                out.write(space + item[:-1])
                space = item[-1]
            else:
                out.write(space + item)
                space = ""
    return out.getvalue()

norm_greek_upper: Dict[str, Tuple[int, ...]] = {
    "A": (0x391,),  # Alpha
    "B": (0x392,),  # Beta
    "G": (0x393,),  # Gamma
    "D": (0x394,),  # Delta
    "E": (0x395,),  # Epsilon
    "Z": (0x396,),  # Zeta
    "H": (0x397,),  # Eta
    "TH": (0x398,),  # Theta
    "I": (0x399,),  # Iota
    "K": (0x39A,),  # Kappa
    "L": (0x39B,),  # Lambda
    "M": (0x39C,),  # My
    "N": (0x39D,),  # Ny
    "X": (0x39E,),  # Xi
    "O": (0x39F,),  # Omikron
    "P": (0x3A0,),  # Pi
    "R": (0x3A1,),  # Rho
    # Schluss-Sigma
    "S": (0x3A3,),  # Sigma
    "T": (0x3A4,),  # Tau
    "Y": (0x3A5,),  # Ypsilon
    "F": (0x3A6,),  # Phi
    "PH": (0x3A6,),  # Phi
    "C": (0x3A7,),  # Chi
    "CH": (0x3A7,),  # Chi
    "CK": (0x39A,),  # Kappa
    "W": (0x3A8,),  # Psi
    "U": (0x3A9,),  # Omega
    "OO": (0x3A9,),  # Omega
    #
    "J": (0x399,),  # Iota
    "Q": (0x39A,),  # Kappa
    "QU": (0x39A,),  # Kappa
    "V": (0x2207,),  # Nabla Operatur
}

norm_greek_lower: Dict[str, Tuple[int, ...]] = {
    "a": (0x3B1,),  # Alpha
    "b": (0x3B2,),  # Beta
    "g": (0x3B3,),  # Gamma
    "d": (0x3B4,),  # Delta
    "e": (0x3B5,),  # Epsilon
    "z": (0x3B6,),  # Zeta
    "h": (0x3B7,),  # Eta
    "th": (0x3B8,),  # Theta
    "i": (0x3B9,),  # Iota
    "k": (0x3BA,),  # Kappa
    "l": (0x3BB,),  # Lambda
    "m": (0x3BC,),  # My
    "n": (0x3BD,),  # Ny
    "x": (0x3BE,),  # Xi
    "o": (0x3BF,),  # Omikron
    "p": (0x3C0,),  # Pi
    "r": (0x3C1,),  # Rho
    "s": (0x3C3,),  # Sigma
    "t": (0x3C4,),  # Tau
    "y": (0x3C5,),  # Ypsilon
    "f": (0x3C6,),  # Phi
    "ph": (0x3C6,),  # Phi
    "c": (0x3C7,),  # Chi
    "ch": (0x3C7,),  # Chi
    "ck": (0x3BA,),  # Kappa
    "w": (0x3C8,),  # Psi
    "u": (0x3C9,),  # Omega
    "oo": (0x3C9,),  # Omega
    #
    "j": (0x3B9,),  # Iota
    "q": (0x3BA,),  # Kappa
    "qu": (0x3BA,),  # Kappa
    "v": (0x2202,),  # Differential
}

def greek(text: str) -> str:
    def as_norm(text: str) -> str:
        return text
    def as_ital(text: str) -> str:
        return ital(text)
    def as_bold(text: str) -> str:
        return bold(text)
    def as_bold_ital(text: str) -> str:
        return bold(ital(text))
    out = StringIO()
    skip = False
    for i, c in enumerate(text):
        if skip:
            skip = False
            continue
        # orig_c = c
        ch = ord(c)
        as_style = as_norm
        if ital_base_A <= ch and ch <= ital_base_Z:
            ch = norm_base_A + (ch - ital_base_A)
            c = chr(ch)
            as_style = as_ital
        if ch in ital_base_lower:
            ch = ital_base_lower[ch]
            c = chr(ch)
            as_style = as_ital
        if ital_base_a <= ch and ch <= ital_base_z:
            ch = norm_base_a + (ch - ital_base_a)
            c = chr(ch)
            as_style = as_ital
        if bold_base_A <= ch and ch <= bold_base_Z:
            ch = norm_base_A + (ch - bold_base_A)
            c = chr(ch)
            as_style = as_bold
        if bold_base_a <= ch and ch <= bold_base_z:
            ch = norm_base_a + (ch - bold_base_a)
            c = chr(ch)
            as_style = as_bold
        if bold_ital_base_A <= ch and ch <= bold_ital_base_Z:
            ch = norm_base_A + (ch - bold_ital_base_A)
            c = chr(ch)
            as_style = as_bold_ital
        if bold_ital_base_a <= ch and ch <= bold_ital_base_z:
            ch = norm_base_a + (ch - bold_ital_base_a)
            c = chr(ch)
            as_style = as_bold_ital
        if i + 1 < len(text):
            d = text[i + 1]
        else:
            d = " "
        # orig_d = d
        dh = ord(d)
        if ital_base_A <= dh and dh <= ital_base_Z:
            d = chr(norm_base_A + (dh - ital_base_A))
        if ch in ital_base_lower:
            d = chr(ital_base_lower[dh])
        if ital_base_a <= dh and dh <= ital_base_z:
            d = chr(norm_base_a + (dh - ital_base_a))
        if bold_base_A <= dh and dh <= bold_base_Z:
            d = chr(norm_base_A + (dh - bold_base_A))
        if bold_base_a <= dh and dh <= bold_base_z:
            d = chr(norm_base_a + (dh - bold_base_a))
        if bold_ital_base_A <= dh and dh <= bold_ital_base_Z:
            d = chr(norm_base_A + (dh - bold_ital_base_A))
        if bold_ital_base_a <= dh and dh <= bold_ital_base_z:
            d = chr(norm_base_a + (dh - bold_ital_base_a))
        #
        # logg.info("'%s' => '%s' (%x) & '%s' => '%s'", orig_c, c, ch, orig_d, d)
        #
        if norm_base_A <= ch and ch <= norm_base_Z:
            if c + d in norm_greek_upper:
                for n in norm_greek_upper[c + d]:
                    out.write(as_style(chr(n)))
                skip = True
            elif c in norm_greek_upper:
                for n in norm_greek_upper[c]:
                    out.write(as_style(chr(n)))
            else:
                logg.error("did not find greek for '%s'", c)
                out.write(c)
        elif norm_base_a <= ch and ch <= norm_base_z:
            if c + d in norm_greek_lower:
                for n in norm_greek_lower[c + d]:
                    out.write(as_style(chr(n)))
                skip = True
            elif c in norm_greek_lower:
                for n in norm_greek_lower[c]:
                    out.write(as_style(chr(n)))
            else:
                logg.error("did not find greek for '%s'", c)
                out.write(c)
        else:
            out.write(c)
    return out.getvalue()

norm_rune_lower: Dict[str, Tuple[int, ...]] = {
    "f": (0x16A0,),  # Fehu
    "u": (0x16A2,),  # Uruz
    "th": (0x16A6,),  # Thurs
    "a": (0x16A8,),  # ansuz
    "r": (0x16B1,),  # raido
    "k": (0x16B3,),  # kaunan
    "g": (0x16B7,),  # gebo
    "w": (0x16D5,),  # wunja # 0x1F7 0x1BF
    "h": (0x16BA,),  # hagalaz
    "n": (0x16BE,),  # naudiz
    "i": (0x16C1,),  # isaz
    "j": (0x16E1,),  # jera
    "y": (0x16C7,),  # ihwaz
    "p": (0x16C8,),  # perth
    "z": (0x16C9,),  # algiz
    "s": (0x16CB,),  # sowilo
    "t": (0x16CF,),  # tiwaz
    "b": (0x16D2,),  # berkan
    "e": (0x16D6,),  # ehwaz
    "m": (0x16D7,),  # mannaz
    "l": (0x16DA,),  # laguz
    "ng": (0x16DC,),  # ingwaz
    "o": (0x16DF,),  # othila
    "d": (0x16DE,),  # dagaz
    #
    "c": (0x16B3,),  # kaunan
    "q": (0x16B3,),  # kaunan
    "qu": (0x16B3,),  # kaunan
    "u": (0x16B9,),  # wunja
    "v": (0x16B9,),  # wunja
    "x": (0x16B3, 0x16CB),  # kaunan, sowilo
}

def rune(text: str) -> str:  # gothic, blackletter
    rune_a = 0x16A0
    rune_o = 0x16F8
    out = StringIO()
    skip = False
    for i, c in enumerate(text):
        if skip:
            skip = False
            continue
        ch = ord(c)
        if norm_base_A <= ch and ch <= norm_base_Z:
            ch = norm_base_a + (ch - norm_base_A)
            c = chr(ch)
        if i + 1 < len(text):
            d = text[i + 1]
        else:
            d = " "
        dh = ord(d)
        if norm_base_A <= dh and dh <= norm_base_Z:
            dh = norm_base_a + (dh - norm_base_A)
            d = chr(dh)
        if norm_base_a <= ch and ch <= norm_base_z:
            if c + d in norm_rune_lower:
                for n in norm_rune_lower[c + d]:
                    out.write(chr(n))
                skip = True
            elif c in norm_rune_lower:
                for n in norm_rune_lower[c]:
                    out.write(chr(n))
            else:
                logg.error("did not find rune for '%s'", c)
        else:
            out.write(c)
    return out.getvalue()

norm_fraktur__C = 0x212D  # Complex Numbers
norm_fraktur__H = 0x210C  # Hamilton Numbers
norm_fraktur__I = 0x2111
norm_fraktur__R = 0x211C  # Real Numbers
norm_fraktur__Z = 0x2128  # Integer Numbers
norm_fraktur_encode: Dict[str, int] = {
    'C': norm_fraktur__C,
    'H': norm_fraktur__H,
    'I': norm_fraktur__I,
    'R': norm_fraktur__R,
    'Z': norm_fraktur__Z}
norm_fraktur_upper: Dict[int, int] = {
    norm_fraktur__C: ord('C'),
    norm_fraktur__H: ord('H'),
    norm_fraktur__I: ord('I'),
    norm_fraktur__R: ord('R'),
    norm_fraktur__Z: ord('Z')}

def fraktur(text: str) -> str:  # gothic, blackletter
    out = StringIO()
    for c in text:
        ch = ord(c)
        if c in norm_fraktur_encode:
            out.write(chr(norm_fraktur_encode[c]))
        elif norm_base_A <= ch and ch <= norm_base_Z:
            out.write(chr(norm_fraktur_A + (ch - norm_base_A)))
        elif norm_base_a <= ch and ch <= norm_base_z:
            out.write(chr(norm_fraktur_a + (ch - norm_base_a)))
        # elif norm_base_0 <= ch and ch <= norm_base_9:
        #     out.write(chr(bold_base_0+(ch-norm_base_0)))
        else:
            out.write(c)
    return out.getvalue()

norm_script__B = 0x212C
norm_script__E = 0x2130
norm_script__F = 0x2131
norm_script__H = 0x210B
norm_script__I = 0x2110
norm_script__L = 0x2112
norm_script__M = 0x2133
norm_script__R = 0x211B
norm_script__e = 0x212F
norm_script__g = 0x210A
norm_script__o = 0x2134
norm_script_encode: Dict[str, int] = {
    'B': norm_script__B,
    'E': norm_script__E,
    'F': norm_script__F,
    'H': norm_script__H,
    'I': norm_script__I,
    'L': norm_script__L,
    'M': norm_script__M,
    'R': norm_script__R,
    'e': norm_script__e,
    'g': norm_script__g,
    'o': norm_script__o}
norm_script_upper: Dict[int, int] = {
    norm_script__B: ord('B'),
    norm_script__E: ord('E'),
    norm_script__F: ord('F'),
    norm_script__H: ord('H'),
    norm_script__I: ord('I'),
    norm_script__L: ord('L'),
    norm_script__M: ord('M'),
    norm_script__R: ord('R')}
norm_script_lower: Dict[int, int] = {
    norm_script__e: ord('e'),
    norm_script__g: ord('g'),
    norm_script__o: ord('o')}

def script(text: str) -> str:  # real cursive
    out = StringIO()
    for c in text:
        ch = ord(c)
        if c in norm_script_encode:
            out.write(chr(norm_script_encode[c]))
        elif norm_base_A <= ch and ch <= norm_base_Z:
            out.write(chr(norm_script_A + (ch - norm_base_A)))
        elif norm_base_a <= ch and ch <= norm_base_z:
            out.write(chr(norm_script_a + (ch - norm_base_a)))
        elif bold_base_A <= ch and ch <= bold_base_Z:
            out.write(chr(bold_script_A + (ch - bold_base_A)))
        elif bold_base_a <= ch and ch <= bold_base_z:
            out.write(chr(bold_script_a + (ch - bold_base_a)))
        else:
            out.write(c)
    return out.getvalue()

norm_double__C = 0x2102  # Complex Numbers
norm_double__H = 0x210D  # Hamilton Numbers
norm_double__N = 0x2115  # Natural Numbers
norm_double__P = 0x2119
norm_double__Q = 0x211A  # Rational Numbers
norm_double__R = 0x211D  # Real Numbers
norm_double__Z = 0x2124  # Integer Numbers
norm_double_encode: Dict[str, int] = {
    'C': norm_double__C,
    'H': norm_double__H,
    'N': norm_double__N,
    'P': norm_double__P,
    'Q': norm_double__Q,
    'R': norm_double__R,
    'Z': norm_double__Z}
norm_double_upper: Dict[int, int] = {
    norm_double__C: ord('C'),
    norm_double__H: ord('H'),
    norm_double__N: ord('N'),
    norm_double__P: ord('P'),
    norm_double__Q: ord('Q'),
    norm_double__R: ord('R'),
    norm_double__Z: ord('Z')}

def double(text: str) -> str:  # gothic, blackletter
    out = StringIO()
    for c in text:
        ch = ord(c)
        if c in norm_double_encode:
            out.write(chr(norm_double_encode[c]))
        elif norm_base_A <= ch and ch <= norm_base_Z:
            out.write(chr(norm_double_A + (ch - norm_base_A)))
        elif norm_base_a <= ch and ch <= norm_base_z:
            out.write(chr(norm_double_a + (ch - norm_base_a)))
        elif norm_base_0 <= ch and ch <= norm_base_9:
            out.write(chr(norm_double_0 + (ch - norm_base_0)))
        else:
            out.write(c)
    return out.getvalue()

def courier(text: str) -> str:  # gothic, blackletter
    out = StringIO()
    for c in text:
        ch = ord(c)
        if norm_base_A <= ch and ch <= norm_base_Z:
            out.write(chr(norm_mono_A + (ch - norm_base_A)))
        elif norm_base_a <= ch and ch <= norm_base_z:
            out.write(chr(norm_mono_a + (ch - norm_base_a)))
        elif norm_base_0 <= ch and ch <= norm_base_9:
            out.write(chr(norm_mono_0 + (ch - norm_base_0)))
        else:
            out.write(c)
    return out.getvalue()

def uppercasedouble(text: str) -> str:  # gothic, blackletter
    out = StringIO()
    for c in text:
        ch = ord(c)
        if norm_base_A <= ch and ch <= norm_base_Z:
            out.write(chr(norm_double_A + (ch - norm_base_A)))
        else:
            out.write(c)
    return out.getvalue()

def sans(text: str) -> str:
    out = StringIO()
    for c in text:
        ch = ord(c)
        if norm_base_A <= ch and ch <= norm_base_Z:
            out.write(chr(norm_sans_A + (ch - norm_base_A)))
        elif norm_base_a <= ch and ch <= norm_base_z:
            out.write(chr(norm_sans_a + (ch - norm_base_a)))
        elif norm_base_0 <= ch and ch <= norm_base_9:
            out.write(chr(norm_sans_0 + (ch - norm_base_0)))
        elif bold_base_A <= ch and ch <= bold_base_Z:
            out.write(chr(bold_sans_A + (ch - bold_base_A)))
        elif bold_base_a <= ch and ch <= bold_base_z:
            out.write(chr(bold_sans_a + (ch - bold_base_a)))
        elif bold_base_0 <= ch and ch <= bold_base_9:
            out.write(chr(bold_sans_0 + (ch - bold_base_0)))
        elif ch in ital_base_lower:
            out.write(chr(ital_sans_a + (ital_base_lower[ch] - norm_base_a)))
        elif ital_base_A <= ch and ch <= ital_base_Z:
            out.write(chr(ital_sans_A + (ch - ital_base_A)))
        elif ital_base_a <= ch and ch <= ital_base_z:
            out.write(chr(ital_sans_a + (ch - ital_base_a)))
        elif bold_ital_base_A <= ch and ch <= bold_ital_base_Z:
            out.write(chr(bold_ital_sans_A + (ch - bold_ital_base_A)))
        elif bold_ital_base_a <= ch and ch <= bold_ital_base_z:
            out.write(chr(bold_ital_sans_a + (ch - bold_ital_base_a)))
        else:
            out.write(c)
    return out.getvalue()

ital_base__h = 0x210E  # Planck constant
ital_base_encode: Dict[str, int] = {
    'h': ital_base__h}
ital_base_lower: Dict[int, int] = {
    ital_base__h: ord('h')}

def ital(text: str) -> str:
    logg.debug("apply slant to ascii/black letters")
    out = StringIO()
    for c in text:
        ch = ord(c)
        if c in ital_base_encode:
            out.write(chr(ital_base_encode[c]))
        elif norm_base_A <= ch and ch <= norm_base_Z:
            out.write(chr(ital_base_A + (ch - norm_base_A)))
        elif norm_base_a <= ch and ch <= norm_base_z:
            out.write(chr(ital_base_a + (ch - norm_base_a)))
        elif norm_base_sz == ch:
            out.write(chr(ital_greek_a + 1))  # beta
        elif bold_base_A <= ch and ch <= bold_base_Z:
            out.write(chr(bold_ital_base_A + (ch - bold_base_A)))
        elif bold_base_a <= ch and ch <= bold_base_z:
            out.write(chr(bold_ital_base_a + (ch - bold_base_a)))
        elif norm_sans_A <= ch and ch <= norm_sans_Z:
            out.write(chr(ital_sans_A + (ch - norm_sans_A)))
        elif norm_sans_a <= ch and ch <= norm_sans_z:
            out.write(chr(ital_sans_a + (ch - norm_sans_a)))
        elif bold_sans_A <= ch and ch <= bold_sans_Z:
            out.write(chr(bold_ital_sans_A + (ch - bold_sans_A)))
        elif bold_sans_a <= ch and ch <= bold_sans_z:
            out.write(chr(bold_ital_sans_a + (ch - bold_sans_a)))
        elif norm_fraktur_A <= ch and ch <= norm_fraktur_Y:
            out.write(chr(ital_fraktur_A + (ch - norm_fraktur_A)))
        elif norm_fraktur_a <= ch and ch <= norm_fraktur_z:
            out.write(chr(ital_fraktur_a + (ch - norm_fraktur_a)))
        elif norm_greek_A <= ch and ch <= norm_greek_O:
            out.write(chr(ital_greek_A + (ch - norm_greek_A)))
        elif norm_greek_a <= ch and ch <= norm_greek_o:
            out.write(chr(ital_greek_a + (ch - norm_greek_a)))
        elif norm_greek_nabla == ch:
            out.write(chr(ital_greek_nabla))
        elif norm_greek_diffs == ch:
            out.write(chr(ital_greek_diffs))
        elif bold_greek_A <= ch and ch <= bold_greek_O + 1:
            out.write(chr(bold_ital_greek_A + (ch - bold_greek_A)))
        elif bold_greek_a <= ch and ch <= bold_greek_o + 1:
            out.write(chr(bold_ital_greek_a + (ch - bold_greek_a)))
        else:
            out.write(c)
    return out.getvalue()

def bold(text: str) -> str:
    logg.debug("apply fat to ascii/black letters")
    out = StringIO()
    for c in text:
        ch = ord(c)
        if norm_base_A <= ch and ch <= norm_base_Z:
            out.write(chr(bold_base_A + (ch - norm_base_A)))
        elif norm_base_a <= ch and ch <= norm_base_z:
            out.write(chr(bold_base_a + (ch - norm_base_a)))
        elif norm_base_0 <= ch and ch <= norm_base_9:
            out.write(chr(bold_base_0 + (ch - norm_base_0)))
        elif norm_base_sz == ch:
            out.write(chr(bold_greek_a + 1))
        elif ch in ital_base_lower:
            out.write(chr(bold_ital_base_a + (ital_base_lower[ch] - norm_base_a)))
        elif ital_base_A <= ch and ch <= ital_base_Z:
            out.write(chr(bold_ital_base_A + (ch - ital_base_A)))
        elif ital_base_a <= ch and ch <= ital_base_z:
            out.write(chr(bold_ital_base_a + (ch - ital_base_a)))
        elif norm_sans_A <= ch and ch <= norm_sans_Z:
            out.write(chr(bold_sans_A + (ch - norm_sans_A)))
        elif norm_sans_a <= ch and ch <= norm_sans_z:
            out.write(chr(bold_sans_a + (ch - norm_sans_a)))
        elif norm_sans_0 <= ch and ch <= norm_sans_9:
            out.write(chr(bold_sans_0 + (ch - norm_sans_0)))
        elif ital_sans_A <= ch and ch <= ital_sans_Z:
            out.write(chr(bold_ital_sans_A + (ch - ital_sans_A)))
        elif ital_sans_a <= ch and ch <= ital_sans_z:
            out.write(chr(bold_ital_sans_a + (ch - ital_sans_a)))
        elif ch in norm_fraktur_upper:
            out.write(chr(bold_fraktur_A + (norm_fraktur_upper[ch] - norm_base_A)))
        elif norm_fraktur_A <= ch and ch <= norm_fraktur_Y:
            out.write(chr(bold_fraktur_A + (ch - norm_fraktur_A)))
        elif norm_fraktur_a <= ch and ch <= norm_fraktur_z:
            out.write(chr(bold_fraktur_a + (ch - norm_fraktur_a)))
        elif ch in norm_script_upper:
            out.write(chr(bold_script_A + (norm_script_upper[ch] - norm_base_A)))
        elif ch in norm_script_lower:
            out.write(chr(bold_script_a + (norm_script_lower[ch] - norm_base_a)))
        elif norm_script_A <= ch and ch <= norm_script_Z:
            out.write(chr(bold_script_A + (ch - norm_script_A)))
        elif norm_script_a <= ch and ch <= norm_script_z:
            out.write(chr(bold_script_a + (ch - norm_script_a)))
        elif norm_greek_A <= ch and ch <= norm_greek_O:
            out.write(chr(bold_greek_A + (ch - norm_greek_A)))
        elif norm_greek_a <= ch and ch <= norm_greek_o:
            out.write(chr(bold_greek_a + (ch - norm_greek_a)))
        elif norm_greek_nabla == ch:
            out.write(chr(bold_greek_nabla))
        elif norm_greek_diffs == ch:
            out.write(chr(bold_greek_diffs))
        elif ital_greek_A <= ch and ch <= ital_greek_O + 1:
            out.write(chr(bold_ital_greek_A + (ch - ital_greek_A)))
        elif ital_greek_a <= ch and ch <= ital_greek_o + 1:
            out.write(chr(bold_ital_greek_a + (ch - ital_greek_a)))
        else:
            out.write(c)
    return out.getvalue()


class Scanned:
    text: str = ""
    cmd: str = ""
    verbose: int = 0
    helpinfo: int = 0

def scan(args: List[str]) -> Scanned:
    opt = Scanned()
    stopped = False
    for arg in args:
        if arg.startswith("-") and not stopped:
            if arg.startswith("--"):
                if arg.startswith("--verbose"):
                    opt.verbose += 1
                elif arg.startswith("--help"):
                    opt.helpinfo += 1
                else:
                    print("unknown option {arg} (ignored)".format(**locals()), file=sys.stderr)
            else:
                accept = "hv"
                opt.verbose += arg.count("v")
                opt.helpinfo += arg.count("h")
                for a in arg[1:]:
                    if a not in accept:
                        print("unknown option -{a} (ignored)".format(**locals()), file=sys.stderr)
        else:
            if not opt.cmd:
                opt.cmd = arg
                stopped = True
            elif not opt.text:
                opt.text = arg
            else:
                opt.text += " " + arg
    return opt

def convert(cmd: str, text: str) -> str:
    if cmd and cmd[0] in "0123456789":
        text = cmd + " " + text
        cmd = "value"
    logg.debug("cmd = '%s'", cmd)
    if "nobr" in cmd or "word" in cmd:
        text = nobrspace(text)
    if "thin" in cmd or "value" in cmd:
        text = thinspace(text)
    if "frac" in cmd or "value" in cmd:
        text = fractions(text)
    if "doub" in cmd or "wide" in cmd:
        text = double(text)
    if "caps" in cmd or "init" in cmd:
        text = uppercasedouble(text)
    if "rune" in cmd or "futark" in cmd:
        text = rune(text)
    if "greek" in cmd or "math" in cmd:
        text = greek(text)
    if "black" in cmd or "frak" in cmd:
        text = fraktur(text)
    if "round" in cmd or "script" in cmd or "writ" in cmd:
        text = script(text)
    if "cour" in cmd or "type" in cmd or "mono" in cmd:
        text = courier(text)
    if "sans" in cmd or "vect" in cmd:
        text = sans(text)
    # and the variants in ital and bold
    if "ital" in cmd or "name" in cmd or "slant" in cmd:
        text = ital(text)
    if "bold" in cmd or "fat" in cmd:
        text = bold(text)
    return text

def helpinfo() -> str:
    return """unicoder [-options] command [text..]
    -h / --help       this help info
    -v / --verbose    increase logging level
    command contains:
     *fat*  *bold*    convert to math fat symbols
     *ital* *name*    convert to math slanted symbols
     *round* *script* convert to math writing symbols
     *greek* *math*   convert to math greek
     *frak* *black*   convert to math fraktur 
     *doub* *wide*    convert to math double stroke
     *cour* *type*    convert to math courier monospace
     *caps* *init*    uppercase chars to double stroke
     *nobr* *word*    using base nobr spaces
     *thin* *value*   using thin nobr spaces
     *fract* *vect*   convert fractional values
     *rune* *futark*  transliterate to runic script
    some combinations provide different codepoints:
      italboldbase
      italgreek
      boldgreek
      italboldgreek
      italsans
      boldsans
      italboldsans
      boldfrak
    """

if __name__ == "__main__":
    __opt = scan(sys.argv[1:])
    logging.basicConfig(level=max(0, logging.WARNING - __opt.verbose * 10))
    if __opt.helpinfo:
        print(helpinfo())
    else:
        print(convert(__opt.cmd, __opt.text))
