"""Symbolic primitives + unicode/ASCII abstraction for pretty.py""" import sys import warnings from string import ascii_lowercase, ascii_uppercase import unicodedata unicode_warnings = '' def U(name): """ Get a unicode character by name or, None if not found. This exists because older versions of Python use older unicode databases. """ try: return unicodedata.lookup(name) except KeyError: global unicode_warnings unicode_warnings += 'No \'%s\' in unicodedata\n' % name return None from sympy.printing.conventions import split_super_sub from sympy.core.alphabets import greeks from sympy.utilities.exceptions import sympy_deprecation_warning # prefix conventions when constructing tables # L - LATIN i # G - GREEK beta # D - DIGIT 0 # S - SYMBOL + __all__ = ['greek_unicode', 'sub', 'sup', 'xsym', 'vobj', 'hobj', 'pretty_symbol', 'annotated'] _use_unicode = False def pretty_use_unicode(flag=None): """Set whether pretty-printer should use unicode by default""" global _use_unicode global unicode_warnings if flag is None: return _use_unicode if flag and unicode_warnings: # print warnings (if any) on first unicode usage warnings.warn(unicode_warnings) unicode_warnings = '' use_unicode_prev = _use_unicode _use_unicode = flag return use_unicode_prev def pretty_try_use_unicode(): """See if unicode output is available and leverage it if possible""" encoding = getattr(sys.stdout, 'encoding', None) # this happens when e.g. stdout is redirected through a pipe, or is # e.g. a cStringIO.StringO if encoding is None: return # sys.stdout has no encoding symbols = [] # see if we can represent greek alphabet symbols += greek_unicode.values() # and atoms symbols += atoms_table.values() for s in symbols: if s is None: return # common symbols not present! try: s.encode(encoding) except UnicodeEncodeError: return # all the characters were present and encodable pretty_use_unicode(True) def xstr(*args): sympy_deprecation_warning( """ The sympy.printing.pretty.pretty_symbology.xstr() function is deprecated. Use str() instead. """, deprecated_since_version="1.7", active_deprecations_target="deprecated-pretty-printing-functions" ) return str(*args) # GREEK g = lambda l: U('GREEK SMALL LETTER %s' % l.upper()) G = lambda l: U('GREEK CAPITAL LETTER %s' % l.upper()) greek_letters = list(greeks) # make a copy # deal with Unicode's funny spelling of lambda greek_letters[greek_letters.index('lambda')] = 'lamda' # {} greek letter -> (g,G) greek_unicode = {L: g(L) for L in greek_letters} greek_unicode.update((L[0].upper() + L[1:], G(L)) for L in greek_letters) # aliases greek_unicode['lambda'] = greek_unicode['lamda'] greek_unicode['Lambda'] = greek_unicode['Lamda'] greek_unicode['varsigma'] = '\N{GREEK SMALL LETTER FINAL SIGMA}' # BOLD b = lambda l: U('MATHEMATICAL BOLD SMALL %s' % l.upper()) B = lambda l: U('MATHEMATICAL BOLD CAPITAL %s' % l.upper()) bold_unicode = {l: b(l) for l in ascii_lowercase} bold_unicode.update((L, B(L)) for L in ascii_uppercase) # GREEK BOLD gb = lambda l: U('MATHEMATICAL BOLD SMALL %s' % l.upper()) GB = lambda l: U('MATHEMATICAL BOLD CAPITAL %s' % l.upper()) greek_bold_letters = list(greeks) # make a copy, not strictly required here # deal with Unicode's funny spelling of lambda greek_bold_letters[greek_bold_letters.index('lambda')] = 'lamda' # {} greek letter -> (g,G) greek_bold_unicode = {L: g(L) for L in greek_bold_letters} greek_bold_unicode.update((L[0].upper() + L[1:], G(L)) for L in greek_bold_letters) greek_bold_unicode['lambda'] = greek_unicode['lamda'] greek_bold_unicode['Lambda'] = greek_unicode['Lamda'] greek_bold_unicode['varsigma'] = '\N{MATHEMATICAL BOLD SMALL FINAL SIGMA}' digit_2txt = { '0': 'ZERO', '1': 'ONE', '2': 'TWO', '3': 'THREE', '4': 'FOUR', '5': 'FIVE', '6': 'SIX', '7': 'SEVEN', '8': 'EIGHT', '9': 'NINE', } symb_2txt = { '+': 'PLUS SIGN', '-': 'MINUS', '=': 'EQUALS SIGN', '(': 'LEFT PARENTHESIS', ')': 'RIGHT PARENTHESIS', '[': 'LEFT SQUARE BRACKET', ']': 'RIGHT SQUARE BRACKET', '{': 'LEFT CURLY BRACKET', '}': 'RIGHT CURLY BRACKET', # non-std '{}': 'CURLY BRACKET', 'sum': 'SUMMATION', 'int': 'INTEGRAL', } # SUBSCRIPT & SUPERSCRIPT LSUB = lambda letter: U('LATIN SUBSCRIPT SMALL LETTER %s' % letter.upper()) GSUB = lambda letter: U('GREEK SUBSCRIPT SMALL LETTER %s' % letter.upper()) DSUB = lambda digit: U('SUBSCRIPT %s' % digit_2txt[digit]) SSUB = lambda symb: U('SUBSCRIPT %s' % symb_2txt[symb]) LSUP = lambda letter: U('SUPERSCRIPT LATIN SMALL LETTER %s' % letter.upper()) DSUP = lambda digit: U('SUPERSCRIPT %s' % digit_2txt[digit]) SSUP = lambda symb: U('SUPERSCRIPT %s' % symb_2txt[symb]) sub = {} # symb -> subscript symbol sup = {} # symb -> superscript symbol # latin subscripts for l in 'aeioruvxhklmnpst': sub[l] = LSUB(l) for l in 'in': sup[l] = LSUP(l) for gl in ['beta', 'gamma', 'rho', 'phi', 'chi']: sub[gl] = GSUB(gl) for d in [str(i) for i in range(10)]: sub[d] = DSUB(d) sup[d] = DSUP(d) for s in '+-=()': sub[s] = SSUB(s) sup[s] = SSUP(s) # Variable modifiers # TODO: Make brackets adjust to height of contents modifier_dict = { # Accents 'mathring': lambda s: center_accent(s, '\N{COMBINING RING ABOVE}'), 'ddddot': lambda s: center_accent(s, '\N{COMBINING FOUR DOTS ABOVE}'), 'dddot': lambda s: center_accent(s, '\N{COMBINING THREE DOTS ABOVE}'), 'ddot': lambda s: center_accent(s, '\N{COMBINING DIAERESIS}'), 'dot': lambda s: center_accent(s, '\N{COMBINING DOT ABOVE}'), 'check': lambda s: center_accent(s, '\N{COMBINING CARON}'), 'breve': lambda s: center_accent(s, '\N{COMBINING BREVE}'), 'acute': lambda s: center_accent(s, '\N{COMBINING ACUTE ACCENT}'), 'grave': lambda s: center_accent(s, '\N{COMBINING GRAVE ACCENT}'), 'tilde': lambda s: center_accent(s, '\N{COMBINING TILDE}'), 'hat': lambda s: center_accent(s, '\N{COMBINING CIRCUMFLEX ACCENT}'), 'bar': lambda s: center_accent(s, '\N{COMBINING OVERLINE}'), 'vec': lambda s: center_accent(s, '\N{COMBINING RIGHT ARROW ABOVE}'), 'prime': lambda s: s+'\N{PRIME}', 'prm': lambda s: s+'\N{PRIME}', # # Faces -- these are here for some compatibility with latex printing # 'bold': lambda s: s, # 'bm': lambda s: s, # 'cal': lambda s: s, # 'scr': lambda s: s, # 'frak': lambda s: s, # Brackets 'norm': lambda s: '\N{DOUBLE VERTICAL LINE}'+s+'\N{DOUBLE VERTICAL LINE}', 'avg': lambda s: '\N{MATHEMATICAL LEFT ANGLE BRACKET}'+s+'\N{MATHEMATICAL RIGHT ANGLE BRACKET}', 'abs': lambda s: '\N{VERTICAL LINE}'+s+'\N{VERTICAL LINE}', 'mag': lambda s: '\N{VERTICAL LINE}'+s+'\N{VERTICAL LINE}', } # VERTICAL OBJECTS HUP = lambda symb: U('%s UPPER HOOK' % symb_2txt[symb]) CUP = lambda symb: U('%s UPPER CORNER' % symb_2txt[symb]) MID = lambda symb: U('%s MIDDLE PIECE' % symb_2txt[symb]) EXT = lambda symb: U('%s EXTENSION' % symb_2txt[symb]) HLO = lambda symb: U('%s LOWER HOOK' % symb_2txt[symb]) CLO = lambda symb: U('%s LOWER CORNER' % symb_2txt[symb]) TOP = lambda symb: U('%s TOP' % symb_2txt[symb]) BOT = lambda symb: U('%s BOTTOM' % symb_2txt[symb]) # {} '(' -> (extension, start, end, middle) 1-character _xobj_unicode = { # vertical symbols # (( ext, top, bot, mid ), c1) '(': (( EXT('('), HUP('('), HLO('(') ), '('), ')': (( EXT(')'), HUP(')'), HLO(')') ), ')'), '[': (( EXT('['), CUP('['), CLO('[') ), '['), ']': (( EXT(']'), CUP(']'), CLO(']') ), ']'), '{': (( EXT('{}'), HUP('{'), HLO('{'), MID('{') ), '{'), '}': (( EXT('{}'), HUP('}'), HLO('}'), MID('}') ), '}'), '|': U('BOX DRAWINGS LIGHT VERTICAL'), '<': ((U('BOX DRAWINGS LIGHT VERTICAL'), U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT'), U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT')), '<'), '>': ((U('BOX DRAWINGS LIGHT VERTICAL'), U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT'), U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT')), '>'), 'lfloor': (( EXT('['), EXT('['), CLO('[') ), U('LEFT FLOOR')), 'rfloor': (( EXT(']'), EXT(']'), CLO(']') ), U('RIGHT FLOOR')), 'lceil': (( EXT('['), CUP('['), EXT('[') ), U('LEFT CEILING')), 'rceil': (( EXT(']'), CUP(']'), EXT(']') ), U('RIGHT CEILING')), 'int': (( EXT('int'), U('TOP HALF INTEGRAL'), U('BOTTOM HALF INTEGRAL') ), U('INTEGRAL')), 'sum': (( U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT'), '_', U('OVERLINE'), U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT')), U('N-ARY SUMMATION')), # horizontal objects #'-': '-', '-': U('BOX DRAWINGS LIGHT HORIZONTAL'), '_': U('LOW LINE'), # We used to use this, but LOW LINE looks better for roots, as it's a # little lower (i.e., it lines up with the / perfectly. But perhaps this # one would still be wanted for some cases? # '_': U('HORIZONTAL SCAN LINE-9'), # diagonal objects '\' & '/' ? '/': U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT'), '\\': U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT'), } _xobj_ascii = { # vertical symbols # (( ext, top, bot, mid ), c1) '(': (( '|', '/', '\\' ), '('), ')': (( '|', '\\', '/' ), ')'), # XXX this looks ugly # '[': (( '|', '-', '-' ), '['), # ']': (( '|', '-', '-' ), ']'), # XXX not so ugly :( '[': (( '[', '[', '[' ), '['), ']': (( ']', ']', ']' ), ']'), '{': (( '|', '/', '\\', '<' ), '{'), '}': (( '|', '\\', '/', '>' ), '}'), '|': '|', '<': (( '|', '/', '\\' ), '<'), '>': (( '|', '\\', '/' ), '>'), 'int': ( ' | ', ' /', '/ ' ), # horizontal objects '-': '-', '_': '_', # diagonal objects '\' & '/' ? '/': '/', '\\': '\\', } def xobj(symb, length): """Construct spatial object of given length. return: [] of equal-length strings """ if length <= 0: raise ValueError("Length should be greater than 0") # TODO robustify when no unicodedat available if _use_unicode: _xobj = _xobj_unicode else: _xobj = _xobj_ascii vinfo = _xobj[symb] c1 = top = bot = mid = None if not isinstance(vinfo, tuple): # 1 entry ext = vinfo else: if isinstance(vinfo[0], tuple): # (vlong), c1 vlong = vinfo[0] c1 = vinfo[1] else: # (vlong), c1 vlong = vinfo ext = vlong[0] try: top = vlong[1] bot = vlong[2] mid = vlong[3] except IndexError: pass if c1 is None: c1 = ext if top is None: top = ext if bot is None: bot = ext if mid is not None: if (length % 2) == 0: # even height, but we have to print it somehow anyway... # XXX is it ok? length += 1 else: mid = ext if length == 1: return c1 res = [] next = (length - 2)//2 nmid = (length - 2) - next*2 res += [top] res += [ext]*next res += [mid]*nmid res += [ext]*next res += [bot] return res def vobj(symb, height): """Construct vertical object of a given height see: xobj """ return '\n'.join( xobj(symb, height) ) def hobj(symb, width): """Construct horizontal object of a given width see: xobj """ return ''.join( xobj(symb, width) ) # RADICAL # n -> symbol root = { 2: U('SQUARE ROOT'), # U('RADICAL SYMBOL BOTTOM') 3: U('CUBE ROOT'), 4: U('FOURTH ROOT'), } # RATIONAL VF = lambda txt: U('VULGAR FRACTION %s' % txt) # (p,q) -> symbol frac = { (1, 2): VF('ONE HALF'), (1, 3): VF('ONE THIRD'), (2, 3): VF('TWO THIRDS'), (1, 4): VF('ONE QUARTER'), (3, 4): VF('THREE QUARTERS'), (1, 5): VF('ONE FIFTH'), (2, 5): VF('TWO FIFTHS'), (3, 5): VF('THREE FIFTHS'), (4, 5): VF('FOUR FIFTHS'), (1, 6): VF('ONE SIXTH'), (5, 6): VF('FIVE SIXTHS'), (1, 8): VF('ONE EIGHTH'), (3, 8): VF('THREE EIGHTHS'), (5, 8): VF('FIVE EIGHTHS'), (7, 8): VF('SEVEN EIGHTHS'), } # atom symbols _xsym = { '==': ('=', '='), '<': ('<', '<'), '>': ('>', '>'), '<=': ('<=', U('LESS-THAN OR EQUAL TO')), '>=': ('>=', U('GREATER-THAN OR EQUAL TO')), '!=': ('!=', U('NOT EQUAL TO')), ':=': (':=', ':='), '+=': ('+=', '+='), '-=': ('-=', '-='), '*=': ('*=', '*='), '/=': ('/=', '/='), '%=': ('%=', '%='), '*': ('*', U('DOT OPERATOR')), '-->': ('-->', U('EM DASH') + U('EM DASH') + U('BLACK RIGHT-POINTING TRIANGLE') if U('EM DASH') and U('BLACK RIGHT-POINTING TRIANGLE') else None), '==>': ('==>', U('BOX DRAWINGS DOUBLE HORIZONTAL') + U('BOX DRAWINGS DOUBLE HORIZONTAL') + U('BLACK RIGHT-POINTING TRIANGLE') if U('BOX DRAWINGS DOUBLE HORIZONTAL') and U('BOX DRAWINGS DOUBLE HORIZONTAL') and U('BLACK RIGHT-POINTING TRIANGLE') else None), '.': ('*', U('RING OPERATOR')), } def xsym(sym): """get symbology for a 'character'""" op = _xsym[sym] if _use_unicode: return op[1] else: return op[0] # SYMBOLS atoms_table = { # class how-to-display 'Exp1': U('SCRIPT SMALL E'), 'Pi': U('GREEK SMALL LETTER PI'), 'Infinity': U('INFINITY'), 'NegativeInfinity': U('INFINITY') and ('-' + U('INFINITY')), # XXX what to do here #'ImaginaryUnit': U('GREEK SMALL LETTER IOTA'), #'ImaginaryUnit': U('MATHEMATICAL ITALIC SMALL I'), 'ImaginaryUnit': U('DOUBLE-STRUCK ITALIC SMALL I'), 'EmptySet': U('EMPTY SET'), 'Naturals': U('DOUBLE-STRUCK CAPITAL N'), 'Naturals0': (U('DOUBLE-STRUCK CAPITAL N') and (U('DOUBLE-STRUCK CAPITAL N') + U('SUBSCRIPT ZERO'))), 'Integers': U('DOUBLE-STRUCK CAPITAL Z'), 'Rationals': U('DOUBLE-STRUCK CAPITAL Q'), 'Reals': U('DOUBLE-STRUCK CAPITAL R'), 'Complexes': U('DOUBLE-STRUCK CAPITAL C'), 'Union': U('UNION'), 'SymmetricDifference': U('INCREMENT'), 'Intersection': U('INTERSECTION'), 'Ring': U('RING OPERATOR'), 'Modifier Letter Low Ring':U('Modifier Letter Low Ring'), 'EmptySequence': 'EmptySequence', } def pretty_atom(atom_name, default=None, printer=None): """return pretty representation of an atom""" if _use_unicode: if printer is not None and atom_name == 'ImaginaryUnit' and printer._settings['imaginary_unit'] == 'j': return U('DOUBLE-STRUCK ITALIC SMALL J') else: return atoms_table[atom_name] else: if default is not None: return default raise KeyError('only unicode') # send it default printer def pretty_symbol(symb_name, bold_name=False): """return pretty representation of a symbol""" # let's split symb_name into symbol + index # UC: beta1 # UC: f_beta if not _use_unicode: return symb_name name, sups, subs = split_super_sub(symb_name) def translate(s, bold_name) : if bold_name: gG = greek_bold_unicode.get(s) else: gG = greek_unicode.get(s) if gG is not None: return gG for key in sorted(modifier_dict.keys(), key=lambda k:len(k), reverse=True) : if s.lower().endswith(key) and len(s)>len(key): return modifier_dict[key](translate(s[:-len(key)], bold_name)) if bold_name: return ''.join([bold_unicode[c] for c in s]) return s name = translate(name, bold_name) # Let's prettify sups/subs. If it fails at one of them, pretty sups/subs are # not used at all. def pretty_list(l, mapping): result = [] for s in l: pretty = mapping.get(s) if pretty is None: try: # match by separate characters pretty = ''.join([mapping[c] for c in s]) except (TypeError, KeyError): return None result.append(pretty) return result pretty_sups = pretty_list(sups, sup) if pretty_sups is not None: pretty_subs = pretty_list(subs, sub) else: pretty_subs = None # glue the results into one string if pretty_subs is None: # nice formatting of sups/subs did not work if subs: name += '_'+'_'.join([translate(s, bold_name) for s in subs]) if sups: name += '__'+'__'.join([translate(s, bold_name) for s in sups]) return name else: sups_result = ' '.join(pretty_sups) subs_result = ' '.join(pretty_subs) return ''.join([name, sups_result, subs_result]) def annotated(letter): """ Return a stylised drawing of the letter ``letter``, together with information on how to put annotations (super- and subscripts to the left and to the right) on it. See pretty.py functions _print_meijerg, _print_hyper on how to use this information. """ ucode_pics = { 'F': (2, 0, 2, 0, '\N{BOX DRAWINGS LIGHT DOWN AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\n' '\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\n' '\N{BOX DRAWINGS LIGHT UP}'), 'G': (3, 0, 3, 1, '\N{BOX DRAWINGS LIGHT ARC DOWN AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\N{BOX DRAWINGS LIGHT ARC DOWN AND LEFT}\n' '\N{BOX DRAWINGS LIGHT VERTICAL}\N{BOX DRAWINGS LIGHT RIGHT}\N{BOX DRAWINGS LIGHT DOWN AND LEFT}\n' '\N{BOX DRAWINGS LIGHT ARC UP AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\N{BOX DRAWINGS LIGHT ARC UP AND LEFT}') } ascii_pics = { 'F': (3, 0, 3, 0, ' _\n|_\n|\n'), 'G': (3, 0, 3, 1, ' __\n/__\n\\_|') } if _use_unicode: return ucode_pics[letter] else: return ascii_pics[letter] _remove_combining = dict.fromkeys(list(range(ord('\N{COMBINING GRAVE ACCENT}'), ord('\N{COMBINING LATIN SMALL LETTER X}'))) + list(range(ord('\N{COMBINING LEFT HARPOON ABOVE}'), ord('\N{COMBINING ASTERISK ABOVE}')))) def is_combining(sym): """Check whether symbol is a unicode modifier. """ return ord(sym) in _remove_combining def center_accent(string, accent): """ Returns a string with accent inserted on the middle character. Useful to put combining accents on symbol names, including multi-character names. Parameters ========== string : string The string to place the accent in. accent : string The combining accent to insert References ========== .. [1] https://en.wikipedia.org/wiki/Combining_character .. [2] https://en.wikipedia.org/wiki/Combining_Diacritical_Marks """ # Accent is placed on the previous character, although it may not always look # like that depending on console midpoint = len(string) // 2 + 1 firstpart = string[:midpoint] secondpart = string[midpoint:] return firstpart + accent + secondpart def line_width(line): """Unicode combining symbols (modifiers) are not ever displayed as separate symbols and thus should not be counted """ return len(line.translate(_remove_combining))