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

1"""Symbolic primitives + unicode/ASCII abstraction for pretty.py""" 

2 

3import sys 

4import warnings 

5from string import ascii_lowercase, ascii_uppercase 

6import unicodedata 

7 

8unicode_warnings = '' 

9 

10def U(name): 

11 """ 

12 Get a unicode character by name or, None if not found. 

13 

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 

22 

23from sympy.printing.conventions import split_super_sub 

24from sympy.core.alphabets import greeks 

25from sympy.utilities.exceptions import sympy_deprecation_warning 

26 

27# prefix conventions when constructing tables 

28# L - LATIN i 

29# G - GREEK beta 

30# D - DIGIT 0 

31# S - SYMBOL + 

32 

33 

34__all__ = ['greek_unicode', 'sub', 'sup', 'xsym', 'vobj', 'hobj', 'pretty_symbol', 

35 'annotated'] 

36 

37 

38_use_unicode = False 

39 

40 

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 

47 

48 if flag and unicode_warnings: 

49 # print warnings (if any) on first unicode usage 

50 warnings.warn(unicode_warnings) 

51 unicode_warnings = '' 

52 

53 use_unicode_prev = _use_unicode 

54 _use_unicode = flag 

55 return use_unicode_prev 

56 

57 

58def pretty_try_use_unicode(): 

59 """See if unicode output is available and leverage it if possible""" 

60 

61 encoding = getattr(sys.stdout, 'encoding', None) 

62 

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 

67 

68 symbols = [] 

69 

70 # see if we can represent greek alphabet 

71 symbols += greek_unicode.values() 

72 

73 # and atoms 

74 symbols += atoms_table.values() 

75 

76 for s in symbols: 

77 if s is None: 

78 return # common symbols not present! 

79 

80 try: 

81 s.encode(encoding) 

82 except UnicodeEncodeError: 

83 return 

84 

85 # all the characters were present and encodable 

86 pretty_use_unicode(True) 

87 

88 

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) 

99 

100# GREEK 

101g = lambda l: U('GREEK SMALL LETTER %s' % l.upper()) 

102G = lambda l: U('GREEK CAPITAL LETTER %s' % l.upper()) 

103 

104greek_letters = list(greeks) # make a copy 

105# deal with Unicode's funny spelling of lambda 

106greek_letters[greek_letters.index('lambda')] = 'lamda' 

107 

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) 

111 

112# aliases 

113greek_unicode['lambda'] = greek_unicode['lamda'] 

114greek_unicode['Lambda'] = greek_unicode['Lamda'] 

115greek_unicode['varsigma'] = '\N{GREEK SMALL LETTER FINAL SIGMA}' 

116 

117# BOLD 

118b = lambda l: U('MATHEMATICAL BOLD SMALL %s' % l.upper()) 

119B = lambda l: U('MATHEMATICAL BOLD CAPITAL %s' % l.upper()) 

120 

121bold_unicode = {l: b(l) for l in ascii_lowercase} 

122bold_unicode.update((L, B(L)) for L in ascii_uppercase) 

123 

124# GREEK BOLD 

125gb = lambda l: U('MATHEMATICAL BOLD SMALL %s' % l.upper()) 

126GB = lambda l: U('MATHEMATICAL BOLD CAPITAL %s' % l.upper()) 

127 

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' 

131 

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}' 

138 

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} 

151 

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', 

162 

163 # non-std 

164 '{}': 'CURLY BRACKET', 

165 'sum': 'SUMMATION', 

166 'int': 'INTEGRAL', 

167} 

168 

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]) 

174 

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]) 

178 

179sub = {} # symb -> subscript symbol 

180sup = {} # symb -> superscript symbol 

181 

182# latin subscripts 

183for l in 'aeioruvxhklmnpst': 

184 sub[l] = LSUB(l) 

185 

186for l in 'in': 

187 sup[l] = LSUP(l) 

188 

189for gl in ['beta', 'gamma', 'rho', 'phi', 'chi']: 

190 sub[gl] = GSUB(gl) 

191 

192for d in [str(i) for i in range(10)]: 

193 sub[d] = DSUB(d) 

194 sup[d] = DSUP(d) 

195 

196for s in '+-=()': 

197 sub[s] = SSUB(s) 

198 sup[s] = SSUP(s) 

199 

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} 

231 

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]) 

241 

242# {} '(' -> (extension, start, end, middle) 1-character 

243_xobj_unicode = { 

244 

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'), 

254 

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')), '<'), 

258 

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')), '>'), 

262 

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')), 

267 

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')), 

270 

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'), 

279 

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} 

284 

285_xobj_ascii = { 

286 # vertical symbols 

287 # (( ext, top, bot, mid ), c1) 

288 '(': (( '|', '/', '\\' ), '('), 

289 ')': (( '|', '\\', '/' ), ')'), 

290 

291# XXX this looks ugly 

292# '[': (( '|', '-', '-' ), '['), 

293# ']': (( '|', '-', '-' ), ']'), 

294# XXX not so ugly :( 

295 '[': (( '[', '[', '[' ), '['), 

296 ']': (( ']', ']', ']' ), ']'), 

297 

298 '{': (( '|', '/', '\\', '<' ), '{'), 

299 '}': (( '|', '\\', '/', '>' ), '}'), 

300 '|': '|', 

301 

302 '<': (( '|', '/', '\\' ), '<'), 

303 '>': (( '|', '\\', '/' ), '>'), 

304 

305 'int': ( ' | ', ' /', '/ ' ), 

306 

307 # horizontal objects 

308 '-': '-', 

309 '_': '_', 

310 

311 # diagonal objects '\' & '/' ? 

312 '/': '/', 

313 '\\': '\\', 

314} 

315 

316 

317def xobj(symb, length): 

318 """Construct spatial object of given length. 

319 

320 return: [] of equal-length strings 

321 """ 

322 

323 if length <= 0: 

324 raise ValueError("Length should be greater than 0") 

325 

326 # TODO robustify when no unicodedat available 

327 if _use_unicode: 

328 _xobj = _xobj_unicode 

329 else: 

330 _xobj = _xobj_ascii 

331 

332 vinfo = _xobj[symb] 

333 

334 c1 = top = bot = mid = None 

335 

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 

344 

345 ext = vlong[0] 

346 

347 try: 

348 top = vlong[1] 

349 bot = vlong[2] 

350 mid = vlong[3] 

351 except IndexError: 

352 pass 

353 

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 

365 

366 else: 

367 mid = ext 

368 

369 if length == 1: 

370 return c1 

371 

372 res = [] 

373 next = (length - 2)//2 

374 nmid = (length - 2) - next*2 

375 

376 res += [top] 

377 res += [ext]*next 

378 res += [mid]*nmid 

379 res += [ext]*next 

380 res += [bot] 

381 

382 return res 

383 

384 

385def vobj(symb, height): 

386 """Construct vertical object of a given height 

387 

388 see: xobj 

389 """ 

390 return '\n'.join( xobj(symb, height) ) 

391 

392 

393def hobj(symb, width): 

394 """Construct horizontal object of a given width 

395 

396 see: xobj 

397 """ 

398 return ''.join( xobj(symb, width) ) 

399 

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} 

407 

408 

409# RATIONAL 

410VF = lambda txt: U('VULGAR FRACTION %s' % txt) 

411 

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} 

430 

431 

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} 

458 

459 

460def xsym(sym): 

461 """get symbology for a 'character'""" 

462 op = _xsym[sym] 

463 

464 if _use_unicode: 

465 return op[1] 

466 else: 

467 return op[0] 

468 

469 

470# SYMBOLS 

471 

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} 

497 

498 

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 

509 

510 raise KeyError('only unicode') # send it default printer 

511 

512 

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 

518 

519 if not _use_unicode: 

520 return symb_name 

521 

522 name, sups, subs = split_super_sub(symb_name) 

523 

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 

537 

538 name = translate(name, bold_name) 

539 

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 

553 

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 

559 

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) 

570 

571 return ''.join([name, sups_result, subs_result]) 

572 

573 

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. 

579 

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 } 

595 

596 if _use_unicode: 

597 return ucode_pics[letter] 

598 else: 

599 return ascii_pics[letter] 

600 

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}')))) 

603 

604def is_combining(sym): 

605 """Check whether symbol is a unicode modifier. """ 

606 

607 return ord(sym) in _remove_combining 

608 

609 

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. 

614 

615 Parameters 

616 ========== 

617 

618 string : string 

619 The string to place the accent in. 

620 accent : string 

621 The combining accent to insert 

622 

623 References 

624 ========== 

625 

626 .. [1] https://en.wikipedia.org/wiki/Combining_character 

627 .. [2] https://en.wikipedia.org/wiki/Combining_Diacritical_Marks 

628 

629 """ 

630 

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 

637 

638 

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))