diff --git a/extensions/fablabchemnitz/braille-l18n/braille-l18n.inx b/extensions/fablabchemnitz/braille-l18n/braille-l18n.inx new file mode 100644 index 00000000..92ad17c9 --- /dev/null +++ b/extensions/fablabchemnitz/braille-l18n/braille-l18n.inx @@ -0,0 +1,25 @@ + + + Convert To Localized Braille + fablabchemnitz.de.braille-l18n + + + + + + + + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/braille-l18n/braille-l18n.py b/extensions/fablabchemnitz/braille-l18n/braille-l18n.py new file mode 100644 index 00000000..57cf2e4e --- /dev/null +++ b/extensions/fablabchemnitz/braille-l18n/braille-l18n.py @@ -0,0 +1,504 @@ +#!/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() \ No newline at end of file