// Copyright (C) 2017 Keelan Lightfoot // Copyright (C) 2007-2008 Board of Regents of the University of Wisconsin // System (Univ. of Wisconsin-Madison, Trace R&D Center) // Copyright (C) 2007-2008 Omnitor AB // Copyright (C) 2007-2008 Voiceriver Inc // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2.1 of the License, or (at // your option) any later version. package gortty import ( "fmt" "unicode" ) const ( shiftUnknown = iota shiftLetter shiftFigure shiftWhitespace ) var ( defaultSubstitutes = map[rune]rune{ '{': '(', '}': ')', '[': '(', ']': ')', } codeSets = map[string]CodeSet{ // TDD Telecommunications Device for the Deaf standard "TDD": CodeSet{ Codes: [2][]rune{ {'\b', 'E', '\n', 'A', ' ', 'S', 'I', 'U', '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', '\x0e', 'M', 'X', 'V', '\x0f'}, {'\b', '3', '\n', '-', ' ', ',', '8', '7', '\r', '$', '4', '\'', ',', '!', ':', '(', '5', '"', ')', '2', '=', '6', '0', '1', '9', '?', '+', '\x0e', '.', '/', ';', '\x0f'}, }, Substitutes: defaultSubstitutes, Fallback: '\'', ShiftCode: 0x1b, UnshiftCode: 0x1f, }, // USTTY US Teletype Corporation "USTTY": CodeSet{ Codes: [2][]rune{ {'\x00', 'E', '\n', 'A', ' ', 'S', 'I', 'U', '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', '\x0e', 'M', 'X', 'V', '\x0f'}, {'\x00', '3', '\n', '-', ' ', '\a', '8', '7', '\r', '$', '4', '\'', ',', '!', ':', '(', '5', '"', ')', '2', '#', '6', '0', '1', '9', '?', '&', '\x0e', '.', '/', ';', '\x0f'}, }, Substitutes: defaultSubstitutes, Fallback: '-', ShiftCode: 0x1b, UnshiftCode: 0x1f, }, // USITA2 USITA2 "USITA2": CodeSet{ Codes: [2][]rune{ {'\x00', 'E', '\n', 'A', ' ', 'S', 'I', 'U', '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', '\x0e', 'M', 'X', 'V', '\x0f'}, {'\x00', '3', '\n', '-', ' ', '\'', '8', '7', '\r', '$', '4', '\a', ',', '!', ':', '(', '5', '"', ')', '2', '=', '6', '0', '1', '9', '?', '&', '\x0e', '.', '/', '=', '\x0f'}, }, Substitutes: defaultSubstitutes, Fallback: '-', ShiftCode: 0x1b, UnshiftCode: 0x1f, }, // Weather US Weather "Weather": CodeSet{ Codes: [2][]rune{ {'\x00', 'E', '\n', 'A', ' ', 'S', 'I', 'U', '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', '\x0e', 'M', 'X', 'V', '\x0f'}, {'\x00', '3', '\n', '↑', ' ', '\a', '8', '7', '\r', '↗', '4', '↙', '⦷', '→', '○', '←', '5', '+', '↖', '2', '↓', '6', '0', '1', '9', '⊕', '↘', '\x0e', '.', '/', '⦶', '\x0f'}, }, Substitutes: defaultSubstitutes, Fallback: '-', ShiftCode: 0x1b, UnshiftCode: 0x1f, }, // Fractions US Fractions "Fractions": CodeSet{ Codes: [2][]rune{ {'\x00', 'E', '\n', 'A', ' ', 'S', 'I', 'U', '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', 'O', 'B', 'G', '\x0e', 'M', 'X', 'V', '\x0f'}, {'\x00', '3', '\n', '↑', ' ', '\a', '8', '7', '\r', '$', '4', '\'', '⅞', '¼', '⅛', '½', '5', '"', '¾', '2', ' ', '6', '0', '1', '9', '⅝', '&', '\x0e', '.', '/', '⅜', '\x0f'}, }, Substitutes: defaultSubstitutes, Fallback: '-', ShiftCode: 0x1b, UnshiftCode: 0x1f, }, } ) // CodeSet represents a teletype character set. type CodeSet struct { // Codes specifies a mapping of runes to codes. There are two tables, 0 for ltrs, 1 for figs Codes [2][]rune // Substitutes specifies what characters to re-map to existing table entries Substitutes map[rune]rune // Fallback specifies what rune to use when a table entry or substitution cant be found Fallback rune // ShiftRune is fun ShiftCode byte // UnshiftRune is fun UnshiftCode byte runeToCodeSet map[rune]code initialized bool } type code struct { shift int v byte } //LoadCodeSet is fun func LoadCodeSet(n string) (*CodeSet, error) { cs, ok := codeSets[n] if !ok { return nil, fmt.Errorf("Attempt to load nonexistent codeset: %s", n) } cs.initialize() return &cs, nil } func (cs *CodeSet) initialize() *CodeSet { cs.runeToCodeSet = make(map[rune]code) for i, runes := range cs.Codes { var shift int switch i { case 0: shift = shiftLetter case 1: shift = shiftFigure } for v, r := range runes { if c, exists := cs.runeToCodeSet[r]; exists { c.shift = shiftWhitespace cs.runeToCodeSet[r] = c } else { cs.runeToCodeSet[r] = code{ shift: shift, v: byte(v), } } } } for in, out := range cs.Substitutes { if _, ok := cs.runeToCodeSet[in]; ok { // dont load a translation if it overlaps an existing table entry continue } if b, ok := cs.runeToCodeSet[out]; ok { // only set the translation if the output exists in table cs.runeToCodeSet[in] = b } } return cs } func (cs *CodeSet) toCode(r rune) code { if c, ok := cs.runeToCodeSet[unicode.ToUpper(r)]; ok { return c } return cs.runeToCodeSet[cs.Fallback] } func (cs *CodeSet) toByte(r rune) byte { if c, ok := cs.runeToCodeSet[unicode.ToUpper(r)]; ok { return c.v } return cs.runeToCodeSet[cs.Fallback].v } func (c code) toByte() byte { return c.v } func (cs *CodeSet) shift() byte { return cs.ShiftCode } func (cs *CodeSet) unshift() byte { return cs.UnshiftCode } func (cs *CodeSet) toRune(shift int, b byte) rune { var i int switch shift { case shiftLetter: i = 0 case shiftFigure: i = 1 } if int(b) < len(cs.Codes[i]) { return cs.Codes[i][int(b)] } return '�' }