Coverage for /usr/lib/python3/dist-packages/sympy/printing/pretty/pretty_symbology.py: 47%
220 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Symbolic primitives + unicode/ASCII abstraction for pretty.py"""
3import sys
4import warnings
5from string import ascii_lowercase, ascii_uppercase
6import unicodedata
8unicode_warnings = ''
10def U(name):
11 """
12 Get a unicode character by name or, None if not found.
14 This exists because older versions of Python use older unicode databases.
15 """
16 try:
17 return unicodedata.lookup(name)
18 except KeyError:
19 global unicode_warnings
20 unicode_warnings += 'No \'%s\' in unicodedata\n' % name
21 return None
23from sympy.printing.conventions import split_super_sub
24from sympy.core.alphabets import greeks
25from sympy.utilities.exceptions import sympy_deprecation_warning
27# prefix conventions when constructing tables
28# L - LATIN i
29# G - GREEK beta
30# D - DIGIT 0
31# S - SYMBOL +
34__all__ = ['greek_unicode', 'sub', 'sup', 'xsym', 'vobj', 'hobj', 'pretty_symbol',
35 'annotated']
38_use_unicode = False
41def pretty_use_unicode(flag=None):
42 """Set whether pretty-printer should use unicode by default"""
43 global _use_unicode
44 global unicode_warnings
45 if flag is None:
46 return _use_unicode
48 if flag and unicode_warnings:
49 # print warnings (if any) on first unicode usage
50 warnings.warn(unicode_warnings)
51 unicode_warnings = ''
53 use_unicode_prev = _use_unicode
54 _use_unicode = flag
55 return use_unicode_prev
58def pretty_try_use_unicode():
59 """See if unicode output is available and leverage it if possible"""
61 encoding = getattr(sys.stdout, 'encoding', None)
63 # this happens when e.g. stdout is redirected through a pipe, or is
64 # e.g. a cStringIO.StringO
65 if encoding is None:
66 return # sys.stdout has no encoding
68 symbols = []
70 # see if we can represent greek alphabet
71 symbols += greek_unicode.values()
73 # and atoms
74 symbols += atoms_table.values()
76 for s in symbols:
77 if s is None:
78 return # common symbols not present!
80 try:
81 s.encode(encoding)
82 except UnicodeEncodeError:
83 return
85 # all the characters were present and encodable
86 pretty_use_unicode(True)
89def xstr(*args):
90 sympy_deprecation_warning(
91 """
92 The sympy.printing.pretty.pretty_symbology.xstr() function is
93 deprecated. Use str() instead.
94 """,
95 deprecated_since_version="1.7",
96 active_deprecations_target="deprecated-pretty-printing-functions"
97 )
98 return str(*args)
100# GREEK
101g = lambda l: U('GREEK SMALL LETTER %s' % l.upper())
102G = lambda l: U('GREEK CAPITAL LETTER %s' % l.upper())
104greek_letters = list(greeks) # make a copy
105# deal with Unicode's funny spelling of lambda
106greek_letters[greek_letters.index('lambda')] = 'lamda'
108# {} greek letter -> (g,G)
109greek_unicode = {L: g(L) for L in greek_letters}
110greek_unicode.update((L[0].upper() + L[1:], G(L)) for L in greek_letters)
112# aliases
113greek_unicode['lambda'] = greek_unicode['lamda']
114greek_unicode['Lambda'] = greek_unicode['Lamda']
115greek_unicode['varsigma'] = '\N{GREEK SMALL LETTER FINAL SIGMA}'
117# BOLD
118b = lambda l: U('MATHEMATICAL BOLD SMALL %s' % l.upper())
119B = lambda l: U('MATHEMATICAL BOLD CAPITAL %s' % l.upper())
121bold_unicode = {l: b(l) for l in ascii_lowercase}
122bold_unicode.update((L, B(L)) for L in ascii_uppercase)
124# GREEK BOLD
125gb = lambda l: U('MATHEMATICAL BOLD SMALL %s' % l.upper())
126GB = lambda l: U('MATHEMATICAL BOLD CAPITAL %s' % l.upper())
128greek_bold_letters = list(greeks) # make a copy, not strictly required here
129# deal with Unicode's funny spelling of lambda
130greek_bold_letters[greek_bold_letters.index('lambda')] = 'lamda'
132# {} greek letter -> (g,G)
133greek_bold_unicode = {L: g(L) for L in greek_bold_letters}
134greek_bold_unicode.update((L[0].upper() + L[1:], G(L)) for L in greek_bold_letters)
135greek_bold_unicode['lambda'] = greek_unicode['lamda']
136greek_bold_unicode['Lambda'] = greek_unicode['Lamda']
137greek_bold_unicode['varsigma'] = '\N{MATHEMATICAL BOLD SMALL FINAL SIGMA}'
139digit_2txt = {
140 '0': 'ZERO',
141 '1': 'ONE',
142 '2': 'TWO',
143 '3': 'THREE',
144 '4': 'FOUR',
145 '5': 'FIVE',
146 '6': 'SIX',
147 '7': 'SEVEN',
148 '8': 'EIGHT',
149 '9': 'NINE',
150}
152symb_2txt = {
153 '+': 'PLUS SIGN',
154 '-': 'MINUS',
155 '=': 'EQUALS SIGN',
156 '(': 'LEFT PARENTHESIS',
157 ')': 'RIGHT PARENTHESIS',
158 '[': 'LEFT SQUARE BRACKET',
159 ']': 'RIGHT SQUARE BRACKET',
160 '{': 'LEFT CURLY BRACKET',
161 '}': 'RIGHT CURLY BRACKET',
163 # non-std
164 '{}': 'CURLY BRACKET',
165 'sum': 'SUMMATION',
166 'int': 'INTEGRAL',
167}
169# SUBSCRIPT & SUPERSCRIPT
170LSUB = lambda letter: U('LATIN SUBSCRIPT SMALL LETTER %s' % letter.upper())
171GSUB = lambda letter: U('GREEK SUBSCRIPT SMALL LETTER %s' % letter.upper())
172DSUB = lambda digit: U('SUBSCRIPT %s' % digit_2txt[digit])
173SSUB = lambda symb: U('SUBSCRIPT %s' % symb_2txt[symb])
175LSUP = lambda letter: U('SUPERSCRIPT LATIN SMALL LETTER %s' % letter.upper())
176DSUP = lambda digit: U('SUPERSCRIPT %s' % digit_2txt[digit])
177SSUP = lambda symb: U('SUPERSCRIPT %s' % symb_2txt[symb])
179sub = {} # symb -> subscript symbol
180sup = {} # symb -> superscript symbol
182# latin subscripts
183for l in 'aeioruvxhklmnpst':
184 sub[l] = LSUB(l)
186for l in 'in':
187 sup[l] = LSUP(l)
189for gl in ['beta', 'gamma', 'rho', 'phi', 'chi']:
190 sub[gl] = GSUB(gl)
192for d in [str(i) for i in range(10)]:
193 sub[d] = DSUB(d)
194 sup[d] = DSUP(d)
196for s in '+-=()':
197 sub[s] = SSUB(s)
198 sup[s] = SSUP(s)
200# Variable modifiers
201# TODO: Make brackets adjust to height of contents
202modifier_dict = {
203 # Accents
204 'mathring': lambda s: center_accent(s, '\N{COMBINING RING ABOVE}'),
205 'ddddot': lambda s: center_accent(s, '\N{COMBINING FOUR DOTS ABOVE}'),
206 'dddot': lambda s: center_accent(s, '\N{COMBINING THREE DOTS ABOVE}'),
207 'ddot': lambda s: center_accent(s, '\N{COMBINING DIAERESIS}'),
208 'dot': lambda s: center_accent(s, '\N{COMBINING DOT ABOVE}'),
209 'check': lambda s: center_accent(s, '\N{COMBINING CARON}'),
210 'breve': lambda s: center_accent(s, '\N{COMBINING BREVE}'),
211 'acute': lambda s: center_accent(s, '\N{COMBINING ACUTE ACCENT}'),
212 'grave': lambda s: center_accent(s, '\N{COMBINING GRAVE ACCENT}'),
213 'tilde': lambda s: center_accent(s, '\N{COMBINING TILDE}'),
214 'hat': lambda s: center_accent(s, '\N{COMBINING CIRCUMFLEX ACCENT}'),
215 'bar': lambda s: center_accent(s, '\N{COMBINING OVERLINE}'),
216 'vec': lambda s: center_accent(s, '\N{COMBINING RIGHT ARROW ABOVE}'),
217 'prime': lambda s: s+'\N{PRIME}',
218 'prm': lambda s: s+'\N{PRIME}',
219 # # Faces -- these are here for some compatibility with latex printing
220 # 'bold': lambda s: s,
221 # 'bm': lambda s: s,
222 # 'cal': lambda s: s,
223 # 'scr': lambda s: s,
224 # 'frak': lambda s: s,
225 # Brackets
226 'norm': lambda s: '\N{DOUBLE VERTICAL LINE}'+s+'\N{DOUBLE VERTICAL LINE}',
227 'avg': lambda s: '\N{MATHEMATICAL LEFT ANGLE BRACKET}'+s+'\N{MATHEMATICAL RIGHT ANGLE BRACKET}',
228 'abs': lambda s: '\N{VERTICAL LINE}'+s+'\N{VERTICAL LINE}',
229 'mag': lambda s: '\N{VERTICAL LINE}'+s+'\N{VERTICAL LINE}',
230}
232# VERTICAL OBJECTS
233HUP = lambda symb: U('%s UPPER HOOK' % symb_2txt[symb])
234CUP = lambda symb: U('%s UPPER CORNER' % symb_2txt[symb])
235MID = lambda symb: U('%s MIDDLE PIECE' % symb_2txt[symb])
236EXT = lambda symb: U('%s EXTENSION' % symb_2txt[symb])
237HLO = lambda symb: U('%s LOWER HOOK' % symb_2txt[symb])
238CLO = lambda symb: U('%s LOWER CORNER' % symb_2txt[symb])
239TOP = lambda symb: U('%s TOP' % symb_2txt[symb])
240BOT = lambda symb: U('%s BOTTOM' % symb_2txt[symb])
242# {} '(' -> (extension, start, end, middle) 1-character
243_xobj_unicode = {
245 # vertical symbols
246 # (( ext, top, bot, mid ), c1)
247 '(': (( EXT('('), HUP('('), HLO('(') ), '('),
248 ')': (( EXT(')'), HUP(')'), HLO(')') ), ')'),
249 '[': (( EXT('['), CUP('['), CLO('[') ), '['),
250 ']': (( EXT(']'), CUP(']'), CLO(']') ), ']'),
251 '{': (( EXT('{}'), HUP('{'), HLO('{'), MID('{') ), '{'),
252 '}': (( EXT('{}'), HUP('}'), HLO('}'), MID('}') ), '}'),
253 '|': U('BOX DRAWINGS LIGHT VERTICAL'),
255 '<': ((U('BOX DRAWINGS LIGHT VERTICAL'),
256 U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT'),
257 U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT')), '<'),
259 '>': ((U('BOX DRAWINGS LIGHT VERTICAL'),
260 U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT'),
261 U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT')), '>'),
263 'lfloor': (( EXT('['), EXT('['), CLO('[') ), U('LEFT FLOOR')),
264 'rfloor': (( EXT(']'), EXT(']'), CLO(']') ), U('RIGHT FLOOR')),
265 'lceil': (( EXT('['), CUP('['), EXT('[') ), U('LEFT CEILING')),
266 'rceil': (( EXT(']'), CUP(']'), EXT(']') ), U('RIGHT CEILING')),
268 'int': (( EXT('int'), U('TOP HALF INTEGRAL'), U('BOTTOM HALF INTEGRAL') ), U('INTEGRAL')),
269 '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')),
271 # horizontal objects
272 #'-': '-',
273 '-': U('BOX DRAWINGS LIGHT HORIZONTAL'),
274 '_': U('LOW LINE'),
275 # We used to use this, but LOW LINE looks better for roots, as it's a
276 # little lower (i.e., it lines up with the / perfectly. But perhaps this
277 # one would still be wanted for some cases?
278 # '_': U('HORIZONTAL SCAN LINE-9'),
280 # diagonal objects '\' & '/' ?
281 '/': U('BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT'),
282 '\\': U('BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT'),
283}
285_xobj_ascii = {
286 # vertical symbols
287 # (( ext, top, bot, mid ), c1)
288 '(': (( '|', '/', '\\' ), '('),
289 ')': (( '|', '\\', '/' ), ')'),
291# XXX this looks ugly
292# '[': (( '|', '-', '-' ), '['),
293# ']': (( '|', '-', '-' ), ']'),
294# XXX not so ugly :(
295 '[': (( '[', '[', '[' ), '['),
296 ']': (( ']', ']', ']' ), ']'),
298 '{': (( '|', '/', '\\', '<' ), '{'),
299 '}': (( '|', '\\', '/', '>' ), '}'),
300 '|': '|',
302 '<': (( '|', '/', '\\' ), '<'),
303 '>': (( '|', '\\', '/' ), '>'),
305 'int': ( ' | ', ' /', '/ ' ),
307 # horizontal objects
308 '-': '-',
309 '_': '_',
311 # diagonal objects '\' & '/' ?
312 '/': '/',
313 '\\': '\\',
314}
317def xobj(symb, length):
318 """Construct spatial object of given length.
320 return: [] of equal-length strings
321 """
323 if length <= 0:
324 raise ValueError("Length should be greater than 0")
326 # TODO robustify when no unicodedat available
327 if _use_unicode:
328 _xobj = _xobj_unicode
329 else:
330 _xobj = _xobj_ascii
332 vinfo = _xobj[symb]
334 c1 = top = bot = mid = None
336 if not isinstance(vinfo, tuple): # 1 entry
337 ext = vinfo
338 else:
339 if isinstance(vinfo[0], tuple): # (vlong), c1
340 vlong = vinfo[0]
341 c1 = vinfo[1]
342 else: # (vlong), c1
343 vlong = vinfo
345 ext = vlong[0]
347 try:
348 top = vlong[1]
349 bot = vlong[2]
350 mid = vlong[3]
351 except IndexError:
352 pass
354 if c1 is None:
355 c1 = ext
356 if top is None:
357 top = ext
358 if bot is None:
359 bot = ext
360 if mid is not None:
361 if (length % 2) == 0:
362 # even height, but we have to print it somehow anyway...
363 # XXX is it ok?
364 length += 1
366 else:
367 mid = ext
369 if length == 1:
370 return c1
372 res = []
373 next = (length - 2)//2
374 nmid = (length - 2) - next*2
376 res += [top]
377 res += [ext]*next
378 res += [mid]*nmid
379 res += [ext]*next
380 res += [bot]
382 return res
385def vobj(symb, height):
386 """Construct vertical object of a given height
388 see: xobj
389 """
390 return '\n'.join( xobj(symb, height) )
393def hobj(symb, width):
394 """Construct horizontal object of a given width
396 see: xobj
397 """
398 return ''.join( xobj(symb, width) )
400# RADICAL
401# n -> symbol
402root = {
403 2: U('SQUARE ROOT'), # U('RADICAL SYMBOL BOTTOM')
404 3: U('CUBE ROOT'),
405 4: U('FOURTH ROOT'),
406}
409# RATIONAL
410VF = lambda txt: U('VULGAR FRACTION %s' % txt)
412# (p,q) -> symbol
413frac = {
414 (1, 2): VF('ONE HALF'),
415 (1, 3): VF('ONE THIRD'),
416 (2, 3): VF('TWO THIRDS'),
417 (1, 4): VF('ONE QUARTER'),
418 (3, 4): VF('THREE QUARTERS'),
419 (1, 5): VF('ONE FIFTH'),
420 (2, 5): VF('TWO FIFTHS'),
421 (3, 5): VF('THREE FIFTHS'),
422 (4, 5): VF('FOUR FIFTHS'),
423 (1, 6): VF('ONE SIXTH'),
424 (5, 6): VF('FIVE SIXTHS'),
425 (1, 8): VF('ONE EIGHTH'),
426 (3, 8): VF('THREE EIGHTHS'),
427 (5, 8): VF('FIVE EIGHTHS'),
428 (7, 8): VF('SEVEN EIGHTHS'),
429}
432# atom symbols
433_xsym = {
434 '==': ('=', '='),
435 '<': ('<', '<'),
436 '>': ('>', '>'),
437 '<=': ('<=', U('LESS-THAN OR EQUAL TO')),
438 '>=': ('>=', U('GREATER-THAN OR EQUAL TO')),
439 '!=': ('!=', U('NOT EQUAL TO')),
440 ':=': (':=', ':='),
441 '+=': ('+=', '+='),
442 '-=': ('-=', '-='),
443 '*=': ('*=', '*='),
444 '/=': ('/=', '/='),
445 '%=': ('%=', '%='),
446 '*': ('*', U('DOT OPERATOR')),
447 '-->': ('-->', U('EM DASH') + U('EM DASH') +
448 U('BLACK RIGHT-POINTING TRIANGLE') if U('EM DASH')
449 and U('BLACK RIGHT-POINTING TRIANGLE') else None),
450 '==>': ('==>', U('BOX DRAWINGS DOUBLE HORIZONTAL') +
451 U('BOX DRAWINGS DOUBLE HORIZONTAL') +
452 U('BLACK RIGHT-POINTING TRIANGLE') if
453 U('BOX DRAWINGS DOUBLE HORIZONTAL') and
454 U('BOX DRAWINGS DOUBLE HORIZONTAL') and
455 U('BLACK RIGHT-POINTING TRIANGLE') else None),
456 '.': ('*', U('RING OPERATOR')),
457}
460def xsym(sym):
461 """get symbology for a 'character'"""
462 op = _xsym[sym]
464 if _use_unicode:
465 return op[1]
466 else:
467 return op[0]
470# SYMBOLS
472atoms_table = {
473 # class how-to-display
474 'Exp1': U('SCRIPT SMALL E'),
475 'Pi': U('GREEK SMALL LETTER PI'),
476 'Infinity': U('INFINITY'),
477 'NegativeInfinity': U('INFINITY') and ('-' + U('INFINITY')), # XXX what to do here
478 #'ImaginaryUnit': U('GREEK SMALL LETTER IOTA'),
479 #'ImaginaryUnit': U('MATHEMATICAL ITALIC SMALL I'),
480 'ImaginaryUnit': U('DOUBLE-STRUCK ITALIC SMALL I'),
481 'EmptySet': U('EMPTY SET'),
482 'Naturals': U('DOUBLE-STRUCK CAPITAL N'),
483 'Naturals0': (U('DOUBLE-STRUCK CAPITAL N') and
484 (U('DOUBLE-STRUCK CAPITAL N') +
485 U('SUBSCRIPT ZERO'))),
486 'Integers': U('DOUBLE-STRUCK CAPITAL Z'),
487 'Rationals': U('DOUBLE-STRUCK CAPITAL Q'),
488 'Reals': U('DOUBLE-STRUCK CAPITAL R'),
489 'Complexes': U('DOUBLE-STRUCK CAPITAL C'),
490 'Union': U('UNION'),
491 'SymmetricDifference': U('INCREMENT'),
492 'Intersection': U('INTERSECTION'),
493 'Ring': U('RING OPERATOR'),
494 'Modifier Letter Low Ring':U('Modifier Letter Low Ring'),
495 'EmptySequence': 'EmptySequence',
496}
499def pretty_atom(atom_name, default=None, printer=None):
500 """return pretty representation of an atom"""
501 if _use_unicode:
502 if printer is not None and atom_name == 'ImaginaryUnit' and printer._settings['imaginary_unit'] == 'j':
503 return U('DOUBLE-STRUCK ITALIC SMALL J')
504 else:
505 return atoms_table[atom_name]
506 else:
507 if default is not None:
508 return default
510 raise KeyError('only unicode') # send it default printer
513def pretty_symbol(symb_name, bold_name=False):
514 """return pretty representation of a symbol"""
515 # let's split symb_name into symbol + index
516 # UC: beta1
517 # UC: f_beta
519 if not _use_unicode:
520 return symb_name
522 name, sups, subs = split_super_sub(symb_name)
524 def translate(s, bold_name) :
525 if bold_name:
526 gG = greek_bold_unicode.get(s)
527 else:
528 gG = greek_unicode.get(s)
529 if gG is not None:
530 return gG
531 for key in sorted(modifier_dict.keys(), key=lambda k:len(k), reverse=True) :
532 if s.lower().endswith(key) and len(s)>len(key):
533 return modifier_dict[key](translate(s[:-len(key)], bold_name))
534 if bold_name:
535 return ''.join([bold_unicode[c] for c in s])
536 return s
538 name = translate(name, bold_name)
540 # Let's prettify sups/subs. If it fails at one of them, pretty sups/subs are
541 # not used at all.
542 def pretty_list(l, mapping):
543 result = []
544 for s in l:
545 pretty = mapping.get(s)
546 if pretty is None:
547 try: # match by separate characters
548 pretty = ''.join([mapping[c] for c in s])
549 except (TypeError, KeyError):
550 return None
551 result.append(pretty)
552 return result
554 pretty_sups = pretty_list(sups, sup)
555 if pretty_sups is not None:
556 pretty_subs = pretty_list(subs, sub)
557 else:
558 pretty_subs = None
560 # glue the results into one string
561 if pretty_subs is None: # nice formatting of sups/subs did not work
562 if subs:
563 name += '_'+'_'.join([translate(s, bold_name) for s in subs])
564 if sups:
565 name += '__'+'__'.join([translate(s, bold_name) for s in sups])
566 return name
567 else:
568 sups_result = ' '.join(pretty_sups)
569 subs_result = ' '.join(pretty_subs)
571 return ''.join([name, sups_result, subs_result])
574def annotated(letter):
575 """
576 Return a stylised drawing of the letter ``letter``, together with
577 information on how to put annotations (super- and subscripts to the
578 left and to the right) on it.
580 See pretty.py functions _print_meijerg, _print_hyper on how to use this
581 information.
582 """
583 ucode_pics = {
584 'F': (2, 0, 2, 0, '\N{BOX DRAWINGS LIGHT DOWN AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\n'
585 '\N{BOX DRAWINGS LIGHT VERTICAL AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\n'
586 '\N{BOX DRAWINGS LIGHT UP}'),
587 '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'
588 '\N{BOX DRAWINGS LIGHT VERTICAL}\N{BOX DRAWINGS LIGHT RIGHT}\N{BOX DRAWINGS LIGHT DOWN AND LEFT}\n'
589 '\N{BOX DRAWINGS LIGHT ARC UP AND RIGHT}\N{BOX DRAWINGS LIGHT HORIZONTAL}\N{BOX DRAWINGS LIGHT ARC UP AND LEFT}')
590 }
591 ascii_pics = {
592 'F': (3, 0, 3, 0, ' _\n|_\n|\n'),
593 'G': (3, 0, 3, 1, ' __\n/__\n\\_|')
594 }
596 if _use_unicode:
597 return ucode_pics[letter]
598 else:
599 return ascii_pics[letter]
601_remove_combining = dict.fromkeys(list(range(ord('\N{COMBINING GRAVE ACCENT}'), ord('\N{COMBINING LATIN SMALL LETTER X}')))
602 + list(range(ord('\N{COMBINING LEFT HARPOON ABOVE}'), ord('\N{COMBINING ASTERISK ABOVE}'))))
604def is_combining(sym):
605 """Check whether symbol is a unicode modifier. """
607 return ord(sym) in _remove_combining
610def center_accent(string, accent):
611 """
612 Returns a string with accent inserted on the middle character. Useful to
613 put combining accents on symbol names, including multi-character names.
615 Parameters
616 ==========
618 string : string
619 The string to place the accent in.
620 accent : string
621 The combining accent to insert
623 References
624 ==========
626 .. [1] https://en.wikipedia.org/wiki/Combining_character
627 .. [2] https://en.wikipedia.org/wiki/Combining_Diacritical_Marks
629 """
631 # Accent is placed on the previous character, although it may not always look
632 # like that depending on console
633 midpoint = len(string) // 2 + 1
634 firstpart = string[:midpoint]
635 secondpart = string[midpoint:]
636 return firstpart + accent + secondpart
639def line_width(line):
640 """Unicode combining symbols (modifiers) are not ever displayed as
641 separate symbols and thus should not be counted
642 """
643 return len(line.translate(_remove_combining))