#!/usr/bin/env python3 import inkex # --------------------------------- # UTILITIES # Common standards UPPERCASE_PREFIXES = { chr(15): 0x2828, # uppercase prefix: https://codepoints.net/U+000F } LOUIS_BRAILLE_NUMBERS_PREFIX = 0x283c # Louis Braille's numbers prefix LOUIS_BRAILLE_NUMBERS = { # Louis Braille's original numbers codification "0": 0x281a, "1": 0x2801, "2": 0x2803, "3": 0x2809, "4": 0x2819, "5": 0x2811, "6": 0x280B, "7": 0x281b, "8": 0x2813, "9": 0x280a, } # --------------------- # English based locales EN_ASCII = " A1B'K2L@CIF/MSP\"E3H9O6R^DJG>NTQ,*5<-U8V.%[$+X!&;:4\\0Z7(_?W]#Y)=" # Spanish based locales ES_LETTERS = { "A": 0x2801, "B": 0x2803, "C": 0x2809, "D": 0x2819, "E": 0x2811, "F": 0x280B, "G": 0x281b, "H": 0x2813, "I": 0x280a, "J": 0x281a, "K": 0x2805, "L": 0x2807, "M": 0x280d, "N": 0x281d, "Ñ": 0x283b, "O": 0x2815, "P": 0x280f, "Q": 0x281f, "R": 0x2817, "S": 0x280e, "T": 0x281e, "U": 0x2825, "V": 0x2827, "W": 0x283a, "X": 0x282d, "Y": 0x283d, "Z": 0x2835, } ES_SIGNS = { " ": 0x2800, # braille space "ª": 0x2801, # ordinal (feminine) -> same as A "º": 0x2815, # ordinal (masculine) -> same as O "&": 0x282f, ".": 0x2804, ",": 0x2802, ":": 0x2812, ";": 0x2806, "¿": 0x2822, "?": 0x2822, "¡": 0x2816, "!": 0x2816, '"': 0x2826, "(": 0x2823, ")": 0x281c, # "[": 0x2837, collides with "Á" (Spanish and Catalan) # "]": 0x283e, collides with "Ú" (Spanish and Catalan) "*": 0x2814, # math "-": 0x2824, "=": 0x2836, "×": 0x2826, # multiplication "÷": 0x2832, # division "+": 0x2816, "@": 0x2810, } ES_ACCENT_MARKS = { "Á": 0x2837, "É": 0x282e, "Í": 0x280c, "Ó": 0x282c, "Ú": 0x283e, "Ü": 0x2833, } ES_COMBINATIONS = { # signs "%": (0x2838, 0x2834), "‰": (0x2838, 0x2834, 0x2834), # per mile "/": (0x2820, 0x2802), "\\": (0x2810, 0x2804), "<": (0x2810, 0x2805), ">": (0x2828, 0x2802), "|": (0x2838, 0x2807), "{": (0x2810, 0x2807), "}": (0x2838, 0x2802), "–": (0x2824, 0x2824), # two different unicode dashes "—": (0x2824, 0x2824), "…": (0x2804, 0x2804, 0x2804), # legal "©": (0x2823, 0x2828, 0x2809, 0x281c), # copyright "®": (0x2823, 0x2828, 0x2817, 0x281c), # registered "℗": (0x2823, 0x2828, 0x280f, 0x281c), "🄯": (0x2823, 0x2828, 0x2807, 0x281c), # currencies "€": (0x2838, 0x2811), "$": (0x2838, 0x280e), "¢": (0x2818, 0x2809), "£": (0x2810, 0x282e), "¥": (0x2838, 0x283d), "¥": (0x2838, 0x283d), } CA_ACCENT_MARKS = { "É": 0x283f, "Í": 0x280c, "Ó": 0x282a, "Ú": 0x283e, "À": 0x2837, "È": 0x282e, "Ò": 0x282c, "Ï": 0x283b, "Ü": 0x2833, "Ç": 0x282f, } # French based locales FR_LETTERS = { "A": 0x2801, "B": 0x2803, "C": 0x2809, "D": 0x2819, "E": 0x2811, "F": 0x280b, "G": 0x281b, "H": 0x2813, "I": 0x280a, "J": 0x281a, "K": 0x2805, "L": 0x2807, "M": 0x280d, "N": 0x281d, "O": 0x2815, "P": 0x280f, "Q": 0x281f, "R": 0x2817, "S": 0x280e, "T": 0x281e, "U": 0x2825, "V": 0x2827, "W": 0x283a, "X": 0x282d, "Y": 0x283d, "Z": 0x2835, } FR_ACCENT_MARKS = { "É": 0x283f, "À": 0x2837, "È": 0x282e, "Ù": 0x283e, "Â": 0x2821, "Ê": 0x2823, "Î": 0x2829, "Ô": 0x2839, "Û": 0x2831, "Ë": 0x282b, "Ï": 0x283b, "Ü": 0x2833, "Ç": 0x282f, "Œ": 0x282a, # oe ligature } FR_SIGNS = { " ": 0x2800, # braille space ",": 0x2802, ";": 0x2806, ":": 0x2812, ".": 0x2832, "?": 0x2822, "!": 0x2816, "«": 0x2836, "»": 0x2836, "“": 0x2836, "”": 0x2836, '"': 0x2836, "‘": 0x2836, "’": 0x2836, "(": 0x2826, ")": 0x2834, "'": 0x2804, "'": 0x2804, "/": 0x280c, "@": 0x281c, "^": 0x2808, # elevation exponent "-": 0x2824, "+": 0x2816, "×": 0x2814, # multiplication "÷": 0x2812, # division "=": 0x2836, } FR_COMBINATIONS = { "↔": (0x282a, 0x2812, 0x2815), # bidirectional arrow "←": (0x282a, 0x2812, 0x2812), # left arrow "→": (0x2812, 0x2812, 0x2815), # right arrow "…": (0x2832, 0x2832, 0x2832), # unicode ellipsis "–": (0x2824, 0x2824), "—": (0x2824, 0x2824), "_": (0x2810, 0x2824), "[": (0x2818, 0x2826), "]": (0x2834, 0x2803), "°": (0x2810, 0x2815), # degrees "§": (0x2810, 0x280f), # paragraph/section symbol "&": (0x2810, 0x283f), "\\": (0x2810, 0x280c), "#": (0x2810, 0x283c), "{": (0x2820, 0x2820, 0x2826), "}": (0x2834, 0x2804, 0x2804), # math "µ": (0x2818, 0x280d), # micron "π": (0x2818, 0x280f), "≤": (0x2818, 0x2823), "≥": (0x2818, 0x281c), "<": (0x2810, 0x2823), ">": (0x2810, 0x281c), "~": (0x2810, 0x2822), "*": (0x2810, 0x2814), "%": (0x2810, 0x282c), "‰": (0x2810, 0x282c, 0x282c), # per mile # legal "©": (0x2810, 0x2809), # copyright "®": (0x2810, 0x2817), # registered "™": (0x2810, 0x281e), # trademark # currencies "¢": (0x2818, 0x2809), "€": (0x2818, 0x2811), "£": (0x2818, 0x2807), "$": (0x2818, 0x280e), "¥": (0x2818, 0x283d), "¥": (0x2818, 0x283d), } # German based locales DE_ACCENT_MARKS = { "Ä": 0x281c, "Ö": 0x282a, "Ü": 0x2833, } DE_SIGNS = { " ": 0x2800, # braille space ",": 0x2802, ";": 0x2806, ":": 0x2812, "?": 0x2822, "!": 0x2816, "„": 0x2826, "“": 0x2834, "§": 0x282c, ".": 0x2804, "–": 0x2824, "‚": 0x2820, } DE_COMBINATIONS = { # signs "ß": (0x282e,), # converted to 'SS' if uppercased, so defined in combinations "|": (0x2810, 0x2824), "[": (0x2818, 0x2837), "]": (0x2818, 0x283e), "/": (0x2818, 0x280c), "`": (0x2820, 0x2826), "´": (0x2820, 0x2834), "/": (0x2810, 0x2802), "&": (0x2810, 0x2825), "*": (0x2820, 0x2814), "→": (0x2812, 0x2812, 0x2815), "←": (0x282a, 0x2812, 0x2812), "↔": (0x282a, 0x2812, 0x2812, 0x2815), "%": (0x283c, 0x281a, 0x2834), "‰": (0x283c, 0x281a, 0x2834, 0x2834), "°": (0x2808, 0x2834), "′": (0x2808, 0x2814), "″": (0x2808, 0x2814, 0x2814), "@": (0x2808, 0x281c), "_": (0x2808, 0x2838), "#": (0x2808, 0x283c), # currencies "€": (0x2808, 0x2811), "$": (0x2808, 0x280e), "¢": (0x2808, 0x2809), "£": (0x2808, 0x2807), # legal "©": (0x2836, 0x2818, 0x2809, 0x2836), "®": (0x2836, 0x2818, 0x2817, 0x2836), } # END: UTILITIES # --------------------------------- # LOCALE FUNCTIONS def en_char_map(char): """English chars mapper. Source: https://en.wikipedia.org/wiki/Braille_ASCII#Braille_ASCII_values """ try: mapint = EN_ASCII.index(char.upper()) except ValueError: return char return chr(mapint + 0x2800) def numbers_singleuppers_combinations_factory( numbers_map, singleuppers_map, combinations_map, # also individual characters that are modified if uppercased number_prefix, uppercase_prefix, ): """Wrapper for various character mappers implementations.""" def char_mapper(char): if char.isnumeric(): # numeric prefix + number return "".join([chr(number_prefix), chr(numbers_map[char])]) try: bcharint = singleuppers_map[char.upper()] except KeyError: try: # combinations return "".join([chr(num) for num in combinations_map[char]]) except KeyError: return char else: # if uppercase, add uppercase prefix before letter if char.isupper(): return "".join([chr(uppercase_prefix), chr(bcharint)]) return chr(bcharint) return char_mapper def es_char_map_loader(): """Spanish/Galician chars mappers. Source: https://sid.usal.es/idocs/F8/FDO12069/signografiabasica.pdf """ return numbers_singleuppers_combinations_factory( LOUIS_BRAILLE_NUMBERS, { **ES_LETTERS, **ES_ACCENT_MARKS, **ES_SIGNS, **UPPERCASE_PREFIXES, }, ES_COMBINATIONS, 0x283c, 0x2828, ) def eu_char_map_loader(): """Euskera chars mapper. Uses the sample implementation as Spanish but without accent marks. Source: https://sid.usal.es/idocs/F8/FDO12069/signografiabasica.pdf """ return numbers_singleuppers_combinations_factory( LOUIS_BRAILLE_NUMBERS, { **ES_LETTERS, **ES_SIGNS, **UPPERCASE_PREFIXES, }, ES_COMBINATIONS, 0x283c, 0x2828, ) def ca_char_map_loader(): """Catalan/Valencian chars mappers. Uses the same implementation as Spanish but different accent marks. Source: https://sid.usal.es/idocs/F8/FDO12069/signografiabasica.pdf """ return numbers_singleuppers_combinations_factory( LOUIS_BRAILLE_NUMBERS, { **ES_LETTERS, **CA_ACCENT_MARKS, **ES_SIGNS, **UPPERCASE_PREFIXES, }, ES_COMBINATIONS, 0x283c, 0x2828, ) def fr_char_map_loader(): """French chars mapper. Source: https://sid.usal.es/idocs/F8/FDO12069/signografiabasica.pdf """ return numbers_singleuppers_combinations_factory( LOUIS_BRAILLE_NUMBERS, { **FR_LETTERS, **FR_ACCENT_MARKS, **FR_SIGNS, **UPPERCASE_PREFIXES, }, FR_COMBINATIONS, 0x283c, 0x2828, ) def de_char_map_loader(): """German chars mapper. - For letters, uses the same dictionary as French implementation. Source: http://bskdl.org/textschrift.html """ return numbers_singleuppers_combinations_factory( LOUIS_BRAILLE_NUMBERS, { **FR_LETTERS, # Same as French implementation **DE_ACCENT_MARKS, **DE_SIGNS, **UPPERCASE_PREFIXES, }, DE_COMBINATIONS, 0x283c, 0x2828, ) # END: LOCALE FUNCTIONS LOCALE_CHARMAPS = { "en": en_char_map, # English "es": es_char_map_loader, # Spanish "fr": fr_char_map_loader, # French "de": de_char_map_loader, # German "gl": es_char_map_loader, # Galician "eu": eu_char_map_loader, # Euskera "ca": ca_char_map_loader, # Catalan/Valencian } # --------------------------------- # EXTENSION class BrailleL18n(inkex.TextExtension): """Convert to Braille giving a localized map of replacements.""" def add_arguments(self, parser): parser.add_argument( "-l", "--locale", type=str, dest="locale", default="en", choices=LOCALE_CHARMAPS.keys(), help="Locale to use converting to Braille.", ) def process_chardata(self, text): """Replaceable chardata method for processing the text.""" chars_mapper = LOCALE_CHARMAPS[self.options.locale] # `chars_mapper` could be a function loader or a characters mapper # itself, so check if the characters mapper is loaded and load it # if is created from a factory if "loader" in chars_mapper.__name__: chars_mapper = chars_mapper() return ''.join(map(chars_mapper, text)) if __name__ == '__main__': BrailleL18n().run()