# textrender.py
# module to render text, tries to understand a basic LateX-like syntax

#    Copyright (C) 2003 Jeremy S. Sanders
#    Email: Jeremy Sanders <jeremy@jeremysanders.net>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with this program; if not, write to the Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################

from __future__ import division
import math
import re

import numpy as N

from ..compat import cbasestr, cstr
from .. import qtall as qt4
from . import points

mmlsupport = True
try:
    from ..helpers import qtmml
    from ..helpers import recordpaint
except ImportError:
    mmlsupport = False

# this definition is monkey-patched when veusz is running in self-test
# mode as we need to hack the metrics - urgh
FontMetrics = qt4.QFontMetricsF

# lookup table for special symbols
symbols = {
    # escaped characters
    r'\_': '_',
    r'\^': '^',
    r'\{': '{',
    r'\}': '}',
    r'\[': '[',
    r'\]': ']',
    r'\backslash' : u'\u005c',

    # operators
    r'\pm': u'\u00b1',
    r'\mp': u'\u2213',
    r'\times': u'\u00d7',
    r'\cdot': u'\u22c5',
    r'\ast': u'\u2217',
    r'\star': u'\u22c6',
    r'\deg': u'\u00b0',
    r'\divide': u'\u00f7',
    r'\dagger': u'\u2020',
    r'\ddagger': u'\u2021',
    r'\cup': u'\u22c3',
    r'\cap': u'\u22c2',
    r'\uplus': u'\u228e',
    r'\vee': u'\u22c1',
    r'\wedge': u'\u22c0',
    r'\nabla': u'\u2207',
    r'\lhd': u'\u22b2',
    r'\rhd': u'\u22b3',
    r'\unlhd': u'\u22b4',
    r'\unrhd': u'\u22b5',

    r'\oslash': u'\u2298',
    r'\odot': u'\u2299',
    r'\oplus': u'\u2295',
    r'\ominus': u'\u2296',
    r'\otimes': u'\u2297',

    r'\diamond': u'\u22c4',
    r'\bullet': u'\u2022',
    r'\AA': u'\u212b',
    r'\sqrt': u'\u221a',
    r'\propto': u'\u221d',
    r'\infty': u'\u221e',
    r'\int': u'\u222b',
    r'\leftarrow': u'\u2190',
    r'\Leftarrow': u'\u21d0',
    r'\uparrow': u'\u2191',
    r'\rightarrow': u'\u2192',
    r'\to': u'\u2192',
    r'\Rightarrow': u'\u21d2',
    r'\downarrow': u'\u2193',
    r'\leftrightarrow': u'\u2194',
    r'\Leftrightarrow': u'\u21d4',
    r'\circ': u'\u25cb',
    r'\ell': u'\u2113',

    # relations
    r'\le': u'\u2264',
    r'\ge': u'\u2265',
    r'\neq': u'\u2260',
    r'\sim': u'\u223c',
    r'\ll': u'\u226a',
    r'\gg': u'\u226b',
    r'\doteq': u'\u2250',
    r'\simeq': u'\u2243',
    r'\subset': u'\u2282',
    r'\supset': u'\u2283',
    r'\approx': u'\u2248',
    r'\asymp': u'\u224d',
    r'\subseteq': u'\u2286',
    r'\supseteq': u'\u2287',
    r'\sqsubset': u'\u228f',
    r'\sqsupset': u'\u2290',
    r'\sqsubseteq': u'\u2291',
    r'\sqsupseteq': u'\u2292',
    r'\in': u'\u2208',
    r'\ni': u'\u220b',
    r'\equiv': u'\u2261',
    r'\prec': u'\u227a',
    r'\succ': u'\u227b',
    r'\preceq': u'\u227c',
    r'\succeq': u'\u227d',
    r'\bowtie': u'\u22c8',
    r'\vdash': u'\u22a2',
    r'\dashv': u'\u22a3',
    r'\models': u'\u22a7',
    r'\perp': u'\u22a5',
    r'\parallel': u'\u2225',
    r'\umid': u'\u2223',

    # lower case greek letters
    r'\alpha': u'\u03b1',
    r'\beta': u'\u03b2',
    r'\gamma': u'\u03b3',
    r'\delta': u'\u03b4',
    r'\epsilon': u'\u03b5',
    r'\zeta': u'\u03b6',
    r'\eta': u'\u03b7',
    r'\theta': u'\u03b8',
    r'\iota': u'\u03b9',
    r'\kappa': u'\u03ba',
    r'\lambda': u'\u03bb',
    r'\mu': u'\u03bc',
    r'\nu': u'\u03bd',
    r'\xi': u'\u03be',
    r'\omicron': u'\u03bf',
    r'\pi': u'\u03c0',
    r'\rho': u'\u03c1',
    r'\stigma': u'\u03c2',
    r'\sigma': u'\u03c3',
    r'\tau': u'\u03c4',
    r'\upsilon': u'\u03c5',
    r'\phi': u'\u03c6',
    r'\chi': u'\u03c7',
    r'\psi': u'\u03c8',
    r'\omega': u'\u03c9',

    # upper case greek letters
    r'\Alpha': u'\u0391',
    r'\Beta': u'\u0392',
    r'\Gamma': u'\u0393',
    r'\Delta': u'\u0394',
    r'\Epsilon': u'\u0395',
    r'\Zeta': u'\u0396',
    r'\Eta': u'\u0397',
    r'\Theta': u'\u0398',
    r'\Iota': u'\u0399',
    r'\Kappa': u'\u039a',
    r'\Lambda': u'\u039b',
    r'\Mu': u'\u039c',
    r'\Nu': u'\u039d',
    r'\Xi': u'\u039e',
    r'\Omicron': u'\u039f',
    r'\Pi': u'\u03a0',
    r'\Rho': u'\u03a1',
    r'\Sigma': u'\u03a3',
    r'\Tau': u'\u03a4',
    r'\Upsilon': u'\u03a5',
    r'\Phi': u'\u03a6',
    r'\Chi': u'\u03a7',
    r'\Psi': u'\u03a8',
    r'\Omega': u'\u03a9',

    # hebrew
    r'\aleph': u'\u05d0',
    r'\beth': u'\u05d1',
    r'\daleth': u'\u05d3',
    r'\gimel': u'\u2137',

    # more symbols
    '\\AE'              : u'\xc6',
    '\\Angle'           : u'\u299c',
    '\\Bumpeq'          : u'\u224e',
    '\\Cap'             : u'\u22d2',
    '\\Colon'           : u'\u2237',
    '\\Cup'             : u'\u22d3',
    '\\DH'              : u'\xd0',
    '\\DJ'              : u'\u0110',
    '\\Digamma'         : u'\u03dc',
    '\\Koppa'           : u'\u03de',
    '\\L'               : u'\u0141',
    '\\LeftDownTeeVector': u'\u2961',
    '\\LeftDownVectorBar': u'\u2959',
    '\\LeftRightVector' : u'\u294e',
    '\\LeftTeeVector'   : u'\u295a',
    '\\LeftTriangleBar' : u'\u29cf',
    '\\LeftUpDownVector': u'\u2951',
    '\\LeftUpTeeVector' : u'\u2960',
    '\\LeftUpVectorBar' : u'\u2958',
    '\\LeftVectorBar'   : u'\u2952',
    '\\Lleftarrow'      : u'\u21da',
    '\\Longleftarrow'   : u'\u27f8',
    '\\Longleftrightarrow': u'\u27fa',
    '\\Longrightarrow'  : u'\u27f9',
    '\\Lsh'             : u'\u21b0',
    '\\NG'              : u'\u014a',
    '\\NestedGreaterGreater': u'\u2aa2',
    '\\NestedLessLess'  : u'\u2aa1',
    '\\O'               : u'\xd8',
    '\\OE'              : u'\u0152',
    '\\ReverseUpEquilibrium': u'\u296f',
    '\\RightDownTeeVector': u'\u295d',
    '\\RightDownVectorBar': u'\u2955',
    '\\RightTeeVector'  : u'\u295b',
    '\\RightTriangleBar': u'\u29d0',
    '\\RightUpDownVector': u'\u294f',
    '\\RightUpTeeVector': u'\u295c',
    '\\RightUpVectorBar': u'\u2954',
    '\\RightVectorBar'  : u'\u2953',
    '\\RoundImplies'    : u'\u2970',
    '\\Rrightarrow'     : u'\u21db',
    '\\Rsh'             : u'\u21b1',
    '\\RuleDelayed'     : u'\u29f4',
    '\\Sampi'           : u'\u03e0',
    '\\Stigma'          : u'\u03da',
    '\\Subset'          : u'\u22d0',
    '\\Supset'          : u'\u22d1',
    '\\TH'              : u'\xde',
    '\\UpArrowBar'      : u'\u2912',
    '\\UpEquilibrium'   : u'\u296e',
    '\\Uparrow'         : u'\u21d1',
    '\\Updownarrow'     : u'\u21d5',
    '\\VDash'           : u'\u22ab',
    '\\Vdash'           : u'\u22a9',
    '\\Vert'            : u'\u2016',
    '\\Vvdash'          : u'\u22aa',
    '\\aa'              : u'\xe5',
    '\\ae'              : u'\xe6',
    '\\allequal'        : u'\u224c',
    '\\amalg'           : u'\u2a3f',
    '\\angle'           : u'\u2220',
    '\\approxeq'        : u'\u224a',
    '\\approxnotequal'  : u'\u2246',
    '\\aquarius'        : u'\u2652',
    '\\aries'           : u'\u2648',
    '\\arrowwaveright'  : u'\u219d',
    '\\backepsilon'     : u'\u03f6',
    '\\backprime'       : u'\u2035',
    '\\backsim'         : u'\u223d',
    '\\backsimeq'       : u'\u22cd',
    '\\barwedge'        : u'\u2305',
    '\\because'         : u'\u2235',
    '\\between'         : u'\u226c',
    '\\bigcap'          : u'\u22c2',
    '\\bigcirc'         : u'\u25ef',
    '\\bigcup'          : u'\u22c3',
    '\\bigtriangledown' : u'\u25bd',
    '\\bigtriangleup'   : u'\u25b3',
    '\\blacklozenge'    : u'\u29eb',
    '\\blacksquare'     : u'\u25aa',
    '\\blacktriangle'   : u'\u25b4',
    '\\blacktriangledown': u'\u25be',
    '\\blacktriangleleft': u'\u25c2',
    '\\blacktriangleright': u'\u25b8',
    '\\boxdot'          : u'\u22a1',
    '\\boxminus'        : u'\u229f',
    '\\boxplus'         : u'\u229e',
    '\\boxtimes'        : u'\u22a0',
    '\\bumpeq'          : u'\u224f',
    '\\cancer'          : u'\u264b',
    '\\capricornus'     : u'\u2651',
    '\\cdots'           : u'\u22ef',
    '\\circeq'          : u'\u2257',
    '\\circlearrowleft' : u'\u21ba',
    '\\circlearrowright': u'\u21bb',
    '\\circledS'        : u'\u24c8',
    '\\circledast'      : u'\u229b',
    '\\circledcirc'     : u'\u229a',
    '\\circleddash'     : u'\u229d',
    '\\clockoint'       : u'\u2a0f',
    '\\clwintegral'     : u'\u2231',
    '\\complement'      : u'\u2201',
    '\\cong'            : u'\u2245',
    '\\coprod'          : u'\u2210',
    '\\curlyeqprec'     : u'\u22de',
    '\\curlyeqsucc'     : u'\u22df',
    '\\curlyvee'        : u'\u22ce',
    '\\curlywedge'      : u'\u22cf',
    '\\curvearrowleft'  : u'\u21b6',
    '\\curvearrowright' : u'\u21b7',
    '\\dblarrowupdown'  : u'\u21c5',
    '\\ddddot'          : u'\u20dc',
    '\\dddot'           : u'\u20db',
    '\\dh'              : u'\xf0',
    '\\diagup'          : u'\u2571',
    '\\digamma'         : u'\u03dd',
    '\\div'             : u'\xf7',
    '\\divideontimes'   : u'\u22c7',
    '\\dj'              : u'\u0111',
    '\\doteqdot'        : u'\u2251',
    '\\dotplus'         : u'\u2214',
    '\\downdownarrows'  : u'\u21ca',
    '\\downharpoonleft' : u'\u21c3',
    '\\downharpoonright': u'\u21c2',
    '\\downslopeellipsis': u'\u22f1',
    '\\eighthnote'      : u'\u266a',
    '\\eqcirc'          : u'\u2256',
    '\\eqslantgtr'      : u'\u2a96',
    '\\eqslantless'     : u'\u2a95',
    '\\estimates'       : u'\u2259',
    '\\eth'             : u'\u01aa',
    '\\exists'          : u'\u2203',
    '\\fallingdotseq'   : u'\u2252',
    '\\flat'            : u'\u266d',
    '\\forall'          : u'\u2200',
    '\\forcesextra'     : u'\u22a8',
    '\\frown'           : u'\u2322',
    '\\gemini'          : u'\u264a',
    '\\geq'             : u'\u2265',
    '\\geqq'            : u'\u2267',
    '\\geqslant'        : u'\u2a7e',
    '\\gnapprox'        : u'\u2a8a',
    '\\gneq'            : u'\u2a88',
    '\\gneqq'           : u'\u2269',
    '\\gnsim'           : u'\u22e7',
    '\\greaterequivlnt': u'\u2273',
    '\\gtrapprox'       : u'\u2a86',
    '\\gtrdot'          : u'\u22d7',
    '\\gtreqless'       : u'\u22db',
    '\\gtreqqless'      : u'\u2a8c',
    '\\gtrless'         : u'\u2277',
    '\\guillemotleft'   : u'\xab',
    '\\guillemotright'  : u'\xbb',
    '\\guilsinglleft'   : u'\u2039',
    '\\guilsinglright'  : u'\u203a',
    '\\hermitconjmatrix': u'\u22b9',
    '\\homothetic'      : u'\u223b',
    '\\hookleftarrow'   : u'\u21a9',
    '\\hookrightarrow'  : u'\u21aa',
    '\\hslash'          : u'\u210f',
    '\\i'               : u'\u0131',
    '\\intercal'        : u'\u22ba',
    '\\jupiter'         : u'\u2643',
    '\\k'               : u'\u0328',
    '\\l'               : u'\u0142',
    '\\langle'          : u'\u2329',
    '\\lazysinv'        : u'\u223e',
    '\\lceil'           : u'\u2308',
    '\\ldots'           : u'\u2026',
    '\\leftarrowtail'   : u'\u21a2',
    '\\leftharpoondown' : u'\u21bd',
    '\\leftharpoonup'   : u'\u21bc',
    '\\leftleftarrows'  : u'\u21c7',
    '\\leftrightarrows' : u'\u21c6',
    '\\leftrightharpoons': u'\u21cb',
    '\\leftrightsquigarrow': u'\u21ad',
    '\\leftthreetimes'  : u'\u22cb',
    '\\leo'             : u'\u264c',
    '\\leq'             : u'\u2264',
    '\\leqq'            : u'\u2266',
    '\\leqslant'        : u'\u2a7d',
    '\\lessapprox'      : u'\u2a85',
    '\\lessdot'         : u'\u22d6',
    '\\lesseqgtr'       : u'\u22da',
    '\\lesseqqgtr'      : u'\u2a8b',
    '\\lessequivlnt'    : u'\u2272',
    '\\lessgtr'         : u'\u2276',
    '\\lfloor'          : u'\u230a',
    '\\libra'           : u'\u264e',
    '\\llcorner'        : u'\u231e',
    '\\lmoustache'      : u'\u23b0',
    '\\lnapprox'        : u'\u2a89',
    '\\lneq'            : u'\u2a87',
    '\\lneqq'           : u'\u2268',
    '\\lnot'            : u'\xac',
    '\\lnsim'           : u'\u22e6',
    '\\longleftarrow'   : u'\u27f5',
    '\\longleftrightarrow': u'\u27f7',
    '\\longmapsto'      : u'\u27fc',
    '\\longrightarrow'  : u'\u27f6',
    '\\looparrowleft'   : u'\u21ab',
    '\\looparrowright'  : u'\u21ac',
    '\\lozenge'         : u'\u25ca',
    '\\lrcorner'        : u'\u231f',
    '\\ltimes'          : u'\u22c9',
    '\\male'            : u'\u2642',
    '\\mapsto'          : u'\u21a6',
    '\\measuredangle'   : u'\u2221',
    '\\mercury'         : u'\u263f',
    '\\mho'             : u'\u2127',
    '\\mid'             : u'\u2223',
    '\\mkern1mu'        : u'\u200a',
    '\\mkern4mu'        : u'\u205f',
    '\\multimap'        : u'\u22b8',
    '\\nLeftarrow'      : u'\u21cd',
    '\\nLeftrightarrow' : u'\u21ce',
    '\\nRightarrow'     : u'\u21cf',
    '\\nVDash'          : u'\u22af',
    '\\nVdash'          : u'\u22ae',
    '\\natural'         : u'\u266e',
    '\\nearrow'         : u'\u2197',
    '\\neptune'         : u'\u2646',
    '\\nexists'         : u'\u2204',
    '\\ng'              : u'\u014b',
    '\\nleftarrow'      : u'\u219a',
    '\\nleftrightarrow' : u'\u21ae',
    '\\nmid'            : u'\u2224',
    '\\nolinebreak'     : u'\u2060',
    '\\notgreaterless'  : u'\u2279',
    '\\notlessgreater'  : u'\u2278',
    '\\nparallel'       : u'\u2226',
    '\\nrightarrow'     : u'\u219b',
    '\\ntriangleleft'   : u'\u22ea',
    '\\ntrianglelefteq' : u'\u22ec',
    '\\ntriangleright'  : u'\u22eb',
    '\\ntrianglerighteq': u'\u22ed',
    '\\nvDash'          : u'\u22ad',
    '\\nvdash'          : u'\u22ac',
    '\\nwarrow'         : u'\u2196',
    '\\o'               : u'\xf8',
    '\\oe'              : u'\u0153',
    '\\oint'            : u'\u222e',
    '\\openbracketleft' : u'\u301a',
    '\\openbracketright': u'\u301b',
    '\\original'        : u'\u22b6',
    '\\partial'         : u'\u2202',
    '\\perspcorrespond' : u'\u2a5e',
    '\\pisces'          : u'\u2653',
    '\\pitchfork'       : u'\u22d4',
    '\\pluto'           : u'\u2647',
    '\\precapprox'      : u'\u2ab7',
    '\\preccurlyeq'     : u'\u227c',
    '\\precedesnotsimilar': u'\u22e8',
    '\\precnapprox'     : u'\u2ab9',
    '\\precneqq'        : u'\u2ab5',
    '\\prod'            : u'\u220f',
    '\\quarternote'     : u'\u2669',
    '\\rangle'          : u'\u232a',
    '\\rbrace'          : u'}',
    '\\rceil'           : u'\u2309',
    '\\recorder'        : u'\u2315',
    '\\rfloor'          : u'\u230b',
    '\\rightangle'      : u'\u221f',
    '\\rightanglearc'   : u'\u22be',
    '\\rightarrowtail'  : u'\u21a3',
    '\\rightharpoondown': u'\u21c1',
    '\\rightharpoonup'  : u'\u21c0',
    '\\rightleftarrows' : u'\u21c4',
    '\\rightleftharpoons': u'\u21cc',
    '\\rightmoon'       : u'\u263e',
    '\\rightrightarrows': u'\u21c9',
    '\\rightsquigarrow' : u'\u21dd',
    '\\rightthreetimes' : u'\u22cc',
    '\\risingdotseq'    : u'\u2253',
    '\\rmoustache'      : u'\u23b1',
    '\\rtimes'          : u'\u22ca',
    '\\sagittarius'     : u'\u2650',
    '\\saturn'          : u'\u2644',
    '\\scorpio'         : u'\u264f',
    '\\searrow'         : u'\u2198',
    '\\setminus'        : u'\u2216',
    '\\sharp'           : u'\u266f',
    '\\smile'           : u'\u2323',
    '\\sphericalangle'  : u'\u2222',
    '\\sqcap'           : u'\u2293',
    '\\sqcup'           : u'\u2294',
    '\\sqrint'          : u'\u2a16',
    '\\square'          : u'\u25a1',
    '\\ss'              : u'\xdf',
    '\\starequal'       : u'\u225b',
    '\\subseteqq'       : u'\u2ac5',
    '\\subsetneq'       : u'\u228a',
    '\\subsetneqq'      : u'\u2acb',
    '\\succapprox'      : u'\u2ab8',
    '\\succcurlyeq'     : u'\u227d',
    '\\succnapprox'     : u'\u2aba',
    '\\succneqq'        : u'\u2ab6',
    '\\succnsim'        : u'\u22e9',
    '\\sum'             : u'\u2211',
    '\\supseteqq'       : u'\u2ac6',
    '\\supsetneq'       : u'\u228b',
    '\\supsetneqq'      : u'\u2acc',
    '\\surd'            : u'\u221a',
    '\\surfintegral'    : u'\u222f',
    '\\swarrow'         : u'\u2199',
    '\\taurus'          : u'\u2649',
    '\\textTheta'       : u'\u03f4',
    '\\textasciiacute'  : u'\xb4',
    '\\textasciibreve'  : u'\u02d8',
    '\\textasciicaron'  : u'\u02c7',
    '\\textasciidieresis': u'\xa8',
    '\\textasciigrave'  : u'`',
    '\\textasciimacron' : u'\xaf',
    '\\textasciitilde'  : u'~',
    '\\textbrokenbar'   : u'\xa6',
    '\\textbullet'      : u'\u2022',
    '\\textcent'        : u'\xa2',
    '\\textcopyright'   : u'\xa9',
    '\\textcurrency'    : u'\xa4',
    '\\textdagger'      : u'\u2020',
    '\\textdaggerdbl'   : u'\u2021',
    '\\textdegree'      : u'\xb0',
    '\\textdollar'      : u'$',
    '\\textdoublepipe'  : u'\u01c2',
    '\\textemdash'      : u'\u2014',
    '\\textendash'      : u'\u2013',
    '\\textexclamdown'  : u'\xa1',
    '\\texthvlig'       : u'\u0195',
    '\\textnrleg'       : u'\u019e',
    '\\textonehalf'     : u'\xbd',
    '\\textonequarter'  : u'\xbc',
    '\\textordfeminine' : u'\xaa',
    '\\textordmasculine': u'\xba',
    '\\textparagraph'   : u'\xb6',
    '\\textperiodcentered': u'\u02d9',
    '\\textpertenthousand': u'\u2031',
    '\\textperthousand' : u'\u2030',
    '\\textphi'         : u'\u0278',
    '\\textquestiondown': u'\xbf',
    '\\textquotedblleft': u'\u201c',
    '\\textquotedblright': u'\u201d',
    '\\textquotesingle' : u"'",
    '\\textregistered'  : u'\xae',
    '\\textsection'     : u'\xa7',
    '\\textsterling'    : u'\xa3',
    '\\texttheta'       : u'\u03b8',
    '\\textthreequarters': u'\xbe',
    '\\texttildelow'    : u'\u02dc',
    '\\texttimes'       : u'\xd7',
    '\\texttrademark'   : u'\u2122',
    '\\textturnk'       : u'\u029e',
    '\\textvartheta'    : u'\u03d1',
    '\\textvisiblespace': u'\u2423',
    '\\textyen'         : u'\xa5',
    '\\th'              : u'\xfe',
    '\\therefore'       : u'\u2234',
    '\\tildetrpl'       : u'\u224b',
    '\\top'             : u'\u22a4',
    '\\triangledown'    : u'\u25bf',
    '\\triangleleft'    : u'\u25c3',
    '\\trianglelefteq'  : u'\u22b4',
    '\\triangleq'       : u'\u225c',
    '\\triangleright'   : u'\u25b9',
    '\\trianglerighteq' : u'\u22b5',
    '\\truestate'       : u'\u22a7',
    '\\twoheadleftarrow': u'\u219e',
    '\\twoheadrightarrow': u'\u21a0',
    '\\ulcorner'        : u'\u231c',
    '\\updownarrow'     : u'\u2195',
    '\\upharpoonleft'   : u'\u21bf',
    '\\upharpoonright'  : u'\u21be',
    '\\upslopeellipsis' : u'\u22f0',
    '\\upuparrows'      : u'\u21c8',
    '\\uranus'          : u'\u2645',
    '\\urcorner'        : u'\u231d',
    '\\varepsilon'      : u'\u025b',
    '\\varkappa'        : u'\u03f0',
    '\\varnothing'      : u'\u2205',
    '\\varphi'          : u'\u03c6',
    '\\varpi'           : u'\u03d6',
    '\\varrho'          : u'\u03f1',
    '\\varsigma'        : u'\u03c2',
    '\\vartriangle'     : u'\u25b5',
    '\\vartriangleleft' : u'\u22b2',
    '\\vartriangleright': u'\u22b3',
    '\\vdots'           : u'\u22ee',
    '\\veebar'          : u'\u22bb',
    '\\venus'           : u'\u2640',
    '\\vert'            : u'|',
    '\\verymuchgreater' : u'\u22d9',
    '\\verymuchless'    : u'\u22d8',
    '\\virgo'           : u'\u264d',
    '\\volintegral'     : u'\u2230',
    '\\wp'              : u'\u2118',
    '\\wr'              : u'\u2240',
    }

class RenderState(object):
    """Holds the state of the rendering."""
    def __init__(self, font, painter, x, y, alignhorz,
                 actually_render=True):
        self.font = font
        self.painter = painter
        self.device = painter.device()
        self.x = x     # current x position
        self.y = y     # current y position
        self.alignhorz = alignhorz
        self.actually_render = actually_render
        self.maxlines = 1 # maximim number of lines drawn

    def fontMetrics(self):
        """Returns font metrics object."""
        return FontMetrics(self.font, self.device)

    def getPixelsPerPt(self):
        """Return number of pixels per point in the rendering."""
        painter = self.painter
        pixperpt = painter.device().logicalDpiY() / 72.
        try:
            pixperpt *= painter.scaling
        except AttributeError:
            pass
        return pixperpt

class Part(object):
    """Represents a part of the text to be rendered, made up of smaller parts."""
    def __init__(self, children):
        self.children = children

    def render(self, state):
        for p in self.children:
            p.render(state)

class PartText(Part):
    """Fundamental bit of text to be rendered: some text."""
    def __init__(self, text):
        self.text = text

    def addText(self, text):
        self.text += text

    def render(self, state):
        """Render some text."""

        width = state.fontMetrics().width(self.text)

        # actually write the text if requested
        if state.actually_render:
            state.painter.drawText( qt4.QPointF(state.x, state.y), self.text )

        # move along, nothing to see
        state.x += width

class PartLines(Part):
    """Render multiple lines."""

    def __init__(self, children):
        Part.__init__(self, children)
        self.widths = []

    def render(self, state):
        """Render multiple lines."""
        # record widths of individual lines
        if not state.actually_render:
            self.widths = []

        height = state.fontMetrics().height()
        inity = state.y
        initx = state.x

        state.y -= height*(len(self.children)-1)

        # iterate over lines (reverse as we draw from bottom up)
        for i, part in enumerate(self.children):
            if state.actually_render and self.widths:
                xwidth = max(self.widths)
                # if we're rendering, use max width to justify line
                if state.alignhorz < 0:
                    # left alignment
                    state.x = initx
                elif state.alignhorz == 0:
                    # centre alignment
                    state.x = initx + (xwidth - self.widths[i])*0.5
                elif state.alignhorz > 0:
                    # right alignment
                    state.x = initx + (xwidth - self.widths[i])
            else:
                # if not, just left justify to get widths
                state.x = initx

            # render the line itself
            part.render(state)

            # record width if we're not rendering
            if not state.actually_render:
                self.widths.append( state.x - initx )
            # move up a line
            state.y += height

        # move on x posn
        if self.widths:
            state.x = initx + max(self.widths)
        else:
            state.x = initx
        state.y = inity
        # keep track of number of lines rendered
        state.maxlines = max(state.maxlines, len(self.children))

class PartSuperScript(Part):
    """Represents superscripted part."""
    def render(self, state):
        font = state.font
        painter = state.painter

        # change text height
        oldheight = state.fontMetrics().height()
        size = font.pointSizeF()
        font.setPointSizeF(size*0.6)
        painter.setFont(font)

        # set position
        oldy = state.y
        state.y -= oldheight*0.4

        # draw children
        Part.render(self, state)

        # restore font and position
        state.y = oldy
        font.setPointSizeF(size)
        painter.setFont(font)

class PartFrac(Part):
    """"A fraction, do latex \frac{a}{b}."""

    def render(self, state):
        if len(self.children) != 2:
            return

        font = state.font
        painter = state.painter

        # make font half size
        size = font.pointSizeF()
        font.setPointSizeF(size*0.5)
        painter.setFont(font)

        # keep track of width above and below line
        if not state.actually_render:
            self.widths = []

        initx = state.x
        inity = state.y

        # render bottom of fraction
        if state.actually_render and len(self.widths) == 2:
            # centre line
            state.x = initx + (max(self.widths) - self.widths[0])*0.5
        self.children[1].render(state)
        if not state.actually_render:
            # get width if not rendering
            self.widths.append(state.x - initx)

        # render top of fraction
        m = state.fontMetrics()
        state.y -= (m.ascent() + m.descent())
        if state.actually_render and len(self.widths) == 2:
            # centre line
            state.x = initx + (max(self.widths) - self.widths[1])*0.5
        else:
            state.x = initx
        self.children[0].render(state)
        if not state.actually_render:
            self.widths.append(state.x - initx)

        state.x = initx + max(self.widths)
        state.y = inity

        # restore font
        font.setPointSizeF(size)
        painter.setFont(font)
        height = state.fontMetrics().ascent()

        # draw line between lines with 0.5pt thickness
        painter.save()
        pen = painter.pen()
        painter.setPen( qt4.QPen(painter.pen().brush(),
                                 state.getPixelsPerPt()*0.5) )
        painter.setPen(pen)

        painter.drawLine(qt4.QPointF(initx,
                                     inity-height/2.),
                         qt4.QPointF(initx+max(self.widths),
                                     inity-height/2.))

        painter.restore()

class PartSubScript(Part):
    """Represents subscripted part."""
    def render(self, state):
        font = state.font

        # change text height
        size = font.pointSizeF()
        font.setPointSizeF(size*0.6)
        state.painter.setFont(font)

        # set position
        oldy = state.y
        state.y += state.fontMetrics().descent()

        # draw children
        Part.render(self, state)

        # restore font and position
        state.y = oldy
        font.setPointSizeF(size)
        state.painter.setFont(font)

class PartMultiScript(Part):
    """Represents multiple parts with the same starting x, e.g. a combination of
       super- and subscript parts."""
    def render(self, state):
        oldx = state.x
        newx = oldx
        for p in self.children:
            state.x = oldx
            p.render(state)
            newx = max([state.x, newx])
        state.x = newx

    def append(self, p):
        self.children.append(p)

class PartItalic(Part):
    """Represents italic part."""
    def render(self, state):
        font = state.font

        font.setItalic( not font.italic() )
        state.painter.setFont(font)

        Part.render(self, state)

        font.setItalic( not font.italic() )
        state.painter.setFont(font)

class PartBold(Part):
    """Represents bold part."""
    def render(self, state):
        font = state.font

        font.setBold( not font.bold() )
        state.painter.setFont(font)

        Part.render(self, state)

        font.setBold( not font.bold() )
        state.painter.setFont(font)

class PartUnderline(Part):
    """Represents underlined part."""
    def render(self, state):
        font = state.font

        font.setUnderline( not font.underline() )
        state.painter.setFont(font)

        Part.render(self, state)

        font.setUnderline( not font.underline() )
        state.painter.setFont(font)

class PartFont(Part):
    """Change font name in part."""
    def __init__(self, children):
        try:
            self.fontname = children[0].text
        except (AttributeError, IndexError):
            self.fontname = ''
        self.children = children[1:]

    def render(self, state):
        font = state.font
        oldfamily = font.family()
        font.setFamily(self.fontname)
        state.painter.setFont(font)

        Part.render(self, state)

        font.setFamily(oldfamily)
        state.painter.setFont(font)

class PartSize(Part):
    """Change font size in part."""
    def __init__(self, children):
        self.size = None
        self.deltasize = None

        # convert size
        try:
            size = children[0].text.replace('pt', '') # crap code
            if size[:1] in '+-':
                # is a modification of font size
                self.deltasize = float(size)
            else:
                # is an absolute font size
                self.size = float(size)
        except (AttributeError, ValueError):
            self.deltasize = 0.

        self.children = children[1:]

    def render(self, state):
        font = state.font
        size = oldsize = font.pointSizeF()

        if self.size:
            # absolute size
            size = self.size
        elif self.deltasize:
            # change of size
            size = max(size+self.deltasize, 0.1)

        font.setPointSizeF(size)
        state.painter.setFont(font)

        Part.render(self, state)

        font.setPointSizeF(oldsize)
        state.painter.setFont(font)

class PartBar(Part):
    """Draw a bar over text."""

    def render(self, state):
        initx = state.x

        # draw material under bar
        Part.render(self, state)

        # draw line over text with 0.5pt thickness
        painter = state.painter
        height = state.fontMetrics().ascent()

        painter.save()
        penw = state.getPixelsPerPt()*0.5
        painter.setPen( qt4.QPen(painter.pen().brush(), penw) )
        painter.drawLine(qt4.QPointF(initx,
                                     state.y-height+penw),
                         qt4.QPointF(state.x,
                                     state.y-height+penw))
        painter.restore()

class PartDot(Part):
    """Draw a dot over text."""

    def render(self, state):
        initx = state.x

        # draw material under bar
        Part.render(self, state)

        # draw circle over text with 1pt radius
        painter = state.painter
        height = state.fontMetrics().ascent()

        painter.save()
        circsize = state.getPixelsPerPt()
        painter.setBrush( qt4.QBrush(painter.pen().color()) )
        painter.setPen( qt4.QPen(qt4.Qt.NoPen) )

        x = 0.5*(initx + state.x)
        y = state.y-height + circsize
        painter.drawEllipse( qt4.QRectF(
                qt4.QPointF(x-circsize,y-circsize),
                qt4.QPointF(x+circsize,y+circsize)) )
        painter.restore()

class PartMarker(Part):
    """Draw a marker symbol."""

    def render(self, state):
        painter = state.painter
        size = state.fontMetrics().ascent()

        painter.save()
        pen = painter.pen()
        pen.setWidthF( state.getPixelsPerPt() * 0.5 )
        painter.setPen(pen)

        try:
            points.plotMarker(
                painter, state.x + size/2.,
                state.y - size/2.,
                self.children[0].text, size*0.3)
        except ValueError:
            pass

        painter.restore()

        state.x += size

class PartColor(Part):
    def __init__(self, children):
        try:
            self.colorname = children[0].text
        except AttributeError:
            self.colorname = ''
        self.children = children[1:]

    def render(self, state):
        painter = state.painter
        pen = painter.pen()
        oldcolor = pen.color()

        pen.setColor( qt4.QColor(self.colorname) )
        painter.setPen(pen)

        Part.render(self, state)

        pen.setColor(oldcolor)
        painter.setPen(pen)

# a dict of latex commands, the part object they correspond to,
# and the number of arguments
part_commands = {
    '^': (PartSuperScript, 1),
    '_': (PartSubScript, 1),
    r'\italic': (PartItalic, 1),
    r'\emph': (PartItalic, 1),
    r'\bold': (PartBold, 1),
    r'\underline': (PartUnderline, 1),
    r'\textbf': (PartBold, 1),
    r'\textit': (PartItalic, 1),
    r'\font': (PartFont, 2),
    r'\size': (PartSize, 2),
    r'\frac': (PartFrac, 2),
    r'\bar': (PartBar, 1),
    r'\overline': (PartBar, 1),
    r'\dot': (PartDot, 1),
    r'\marker': (PartMarker, 1),
    r'\color': (PartColor, 2),
    }

# split up latex expression into bits
splitter_re = re.compile(r'''
(
\\[A-Za-z]+[ ]* |   # normal latex command
\\[\[\]{}_^] |      # escaped special characters
\\\\ |              # line end
\{ |                # begin block
\} |                # end block
\^ |                # power
_                   # subscript
)
''', re.VERBOSE)

def makePartList(text):
    """Make list of parts from text"""
    parts = []
    parents = [parts]

    def doAdd(p):
        """Add the part at the correct level."""
        parents[-1].append(p)
        return p

    for p in splitter_re.split(text):
        if p[:1] == '\\':
            # we may need to drop excess spaces after \foo commands
            ps = p.rstrip()
            if ps in symbols:
                # it will become a symbol, so preserve whitespace
                doAdd(ps)
                if ps != p:
                    doAdd(p[len(ps)-len(p):])
            else:
                # add as possible command, so drop excess whitespace
                doAdd(ps)
        elif p == '{':
            # add a new level
            parents.append( doAdd([]) )
        elif p == '}':
            if len(parents) > 1:
                parents.pop()
        elif p:
            # if not blank, keep it
            doAdd(p)
    return parts

def makePartTree(partlist):
    """Make a tree of parts from the part list."""

    lines = []
    itemlist = []
    length = len(partlist)

    def addText(text):
        """Try to merge consecutive text items for better rendering."""
        if itemlist and isinstance(itemlist[-1], PartText):
            itemlist[-1].addText(text)
        else:
            itemlist.append( PartText(text) )

    i = 0
    while i < length:
        p = partlist[i]
        if p == r'\\':
            lines.append( Part(itemlist) )
            itemlist = []
        elif isinstance(p, cbasestr):
            if p in symbols:
                addText(symbols[p])
            elif p in part_commands:
                klass, numargs = part_commands[p]
                if numargs == 1 and len(partlist) > i+1 and isinstance(partlist[i+1], cbasestr):
                    # coerce a single argument to a partlist so that things
                    # like "A^\dagger" render correctly without needing
                    # curly brackets
                    partargs = [makePartTree([partlist[i+1]])]
                else:
                    partargs = [makePartTree(k) for k in partlist[i+1:i+numargs+1]]

                if (p == '^' or p == '_'):
                    if len(itemlist) > 0 and (
                        isinstance(itemlist[-1], PartSubScript) or
                        isinstance(itemlist[-1], PartSuperScript) or
                        isinstance(itemlist[-1], PartMultiScript)):
                        # combine sequences of multiple sub-/superscript parts into
                        # a MultiScript item so that a single text item can have
                        # both super and subscript indicies
                        # e.g. X^{(q)}_{i}
                        if isinstance(itemlist[-1], PartMultiScript):
                            itemlist.append( klass(partargs) )
                        else:
                            itemlist[-1] = PartMultiScript([itemlist[-1], klass(partargs)])
                    else:
                        itemlist.append( klass(partargs) )
                else:
                    itemlist.append( klass(partargs) )
                i += numargs
            else:
                addText(p)
        else:
            itemlist.append( makePartTree(p) )
        i += 1
    # remaining items
    lines.append( Part(itemlist) )

    if len(lines) == 1:
        # single line, so optimize (itemlist == lines[0] still)
        if len(itemlist) == 1:
            # try to flatten any excess layers
            return itemlist[0]
        else:
            return lines[0]
    else:
        return PartLines(lines)

class _Renderer:
    """Different renderer types based on this."""

    def __init__(self, painter, font, x, y, text,
                 alignhorz = -1, alignvert = -1, angle = 0,
                 usefullheight = False):

        self.painter = painter
        self.font = font
        self.alignhorz = alignhorz
        self.alignvert = alignvert
        self.angle = angle
        self.usefullheight = usefullheight

        # x and y are the original coordinates
        # xi and yi are adjusted for alignment
        self.x = self.xi = x
        self.y = self.yi = y
        self.calcbounds = None

        self._initText(text)

    def _initText(self, text):
        """Override this to set up renderer with text."""

    def ensureInBox(self, minx = -32767, maxx = 32767,
                    miny = -32767, maxy = 32767, extraspace = False):
        """Adjust position of text so that it is within this box."""

        if self.calcbounds is None:
            self.getBounds()

        cb = self.calcbounds

        # add a small amount of extra room if requested
        if extraspace:
            self.painter.setFont(self.font)
            l = FontMetrics(
                self.font,
                self.painter.device()).height()*0.2
            miny += l

        # twiddle positions and bounds
        if cb[2] > maxx:
            dx = cb[2] - maxx
            self.xi -= dx
            cb[2] -= dx
            cb[0] -= dx

        if cb[0] < minx:
            dx = minx - cb[0]
            self.xi += dx
            cb[2] += dx
            cb[0] += dx

        if cb[3] > maxy:
            dy = cb[3] - maxy
            self.yi -= dy
            cb[3] -= dy
            cb[1] -= dy

        if cb[1] < miny:
            dy = miny - cb[1]
            self.yi += dy
            cb[3] += dy
            cb[1] += dy

    def getDimensions(self):
        """Get the (w, h) of the bounding box."""

        if self.calcbounds is None:
            self.getBounds()
        cb = self.calcbounds
        return (cb[2]-cb[0]+1, cb[3]-cb[1]+1)

    def _getWidthHeight(self):
        """Calculate the width and height of rendered text.

        Return totalwidth, totalheight, dy
        dy is a descent to add, to include in the alignment, if wanted
        """

    def getBounds(self):
        """Get bounds in standard version."""

        if self.calcbounds is not None:
            return self.calcbounds

        totalwidth, totalheight, dy = self._getWidthHeight()

        # in order to work out text position, we rotate a bounding box
        # in fact we add two extra points to account for descent if reqd
        tw = totalwidth / 2
        th = totalheight / 2
        coordx = N.array( [-tw,  tw,  tw, -tw, -tw,    tw   ] )
        coordy = N.array( [ th,  th, -th, -th,  th+dy, th+dy] )

        # rotate angles by theta
        theta = -self.angle * (math.pi / 180.)
        c = math.cos(theta)
        s = math.sin(theta)
        newx = coordx*c + coordy*s
        newy = coordy*c - coordx*s

        # calculate bounding box
        newbound = (newx.min(), newy.min(), newx.max(), newy.max())

        # use rotated bounding box to find position of start text posn
        if self.alignhorz < 0:
            xr = ( self.x, self.x+(newbound[2]-newbound[0]) )
            self.xi += (newx[0] - newbound[0])
        elif self.alignhorz > 0:
            xr = ( self.x-(newbound[2]-newbound[0]), self.x )
            self.xi += (newx[0] - newbound[2])
        else:
            xr = ( self.x+newbound[0], self.x+newbound[2] )
            self.xi += newx[0]

        # y alignment
        # adjust y by these values to ensure proper alignment
        if self.alignvert < 0:
            yr = ( self.y + (newbound[1]-newbound[3]), self.y )
            self.yi += (newy[0] - newbound[3])
        elif self.alignvert > 0:
            yr = ( self.y, self.y + (newbound[3]-newbound[1]) )
            self.yi += (newy[0] - newbound[1])
        else:
            yr = ( self.y+newbound[1], self.y+newbound[3] )
            self.yi += newy[0]

        self.calcbounds = [xr[0], yr[0], xr[1], yr[1]]
        return self.calcbounds

class _StdRenderer(_Renderer):
    """Standard rendering class."""

    def _initText(self, text):
        # make internal tree
        partlist = makePartList(text)
        self.parttree = makePartTree(partlist)

    def _getWidthHeight(self):
        """Get size of box around text."""

        # work out total width and height
        self.painter.setFont(self.font)

        # work out height of box, and
        # make the bounding box a bit bigger if we want to include descents

        state = RenderState(self.font, self.painter, 0, 0,
                            self.alignhorz,
                            actually_render = False)
        fm = state.fontMetrics()

        if self.usefullheight:
            totalheight = fm.ascent()
            dy = fm.descent()
        else:
            if self.alignvert == 0:
                # if want vertical centering, better to centre around middle
                # of typical letter (i.e. where strike position is)
                #totalheight = fm.strikeOutPos()*2
                totalheight = fm.boundingRectChar('0').height()
            else:
                # if top/bottom alignment, better to use maximum letter height
                totalheight = fm.ascent()
            dy = 0

        # work out width
        self.parttree.render(state)
        totalwidth = state.x
        # add number of lines for height
        totalheight += fm.height()*(state.maxlines-1)

        return totalwidth, totalheight, dy

    def render(self):
        """Render the text."""

        if self.calcbounds is None:
            self.getBounds()

        state = RenderState(self.font, self.painter,
                            self.xi, self.yi,
                            self.alignhorz)

        # if the text is rotated, change the coordinate frame
        if self.angle != 0:
            self.painter.save()
            self.painter.translate( qt4.QPointF(state.x, state.y) )
            self.painter.rotate(self.angle)
            state.x = 0
            state.y = 0

        # actually paint the string
        self.painter.setFont(self.font)
        self.parttree.render(state)

        # restore coordinate frame if text was rotated
        if self.angle != 0:
            self.painter.restore()

        # caller might want this information
        return self.calcbounds

class _MmlRenderer(_Renderer):
    """MathML renderer."""

    def _initText(self, text):
        """Setup MML document and draw it in recording paint device."""

        self.error = ''
        self.size = qt4.QSize(1, 1)
        if not mmlsupport:
            self.mmldoc = None
            self.error = 'Error: MathML support not built\n'
            return

        self.mmldoc = doc = qtmml.QtMmlDocument()
        try:
            self.mmldoc.setContent(text)
        except ValueError as e:
            self.mmldoc = None
            self.error = ('Error interpreting MathML: %s\n' %
                          cstr(e))
            return

        # this is pretty horrible :-(

        # We write the mathmml document to a RecordPaintDevice device
        # at the same DPI as the screen, because the MML code breaks
        # for other DPIs. We then repaint the output to the real
        # device, scaling to make the size correct.

        screendev = qt4.QApplication.desktop()
        self.record = recordpaint.RecordPaintDevice(
            1024, 1024, screendev.logicalDpiX(), screendev.logicalDpiY())

        rpaint = qt4.QPainter(self.record)
        # painting code relies on these attributes of the painter
        rpaint.pixperpt = screendev.logicalDpiY() / 72.
        rpaint.scaling = 1.0

        # Upscale any drawing by this factor, then scale back when
        # drawing. We have to do this to get consistent output at
        # different zoom factors (I hate this code).
        upscale = 5.

        doc.setFontName( qtmml.QtMmlWidget.NormalFont, self.font.family() )

        ptsize = self.font.pointSizeF()
        if ptsize < 0:
            ptsize = self.font.pixelSize() / self.painter.pixperpt
        ptsize /= self.painter.scaling

        doc.setBaseFontPointSize(ptsize * upscale)

        # the output will be painted finally scaled
        self.drawscale = (
            self.painter.scaling * self.painter.dpi / screendev.logicalDpiY()
            / upscale )
        self.size = doc.size() * self.drawscale

        doc.paint(rpaint, qt4.QPoint(0, 0))
        rpaint.end()

    def _getWidthHeight(self):
        return self.size.width(), self.size.height(), 0

    def render(self):
        """Render the text."""

        if self.calcbounds is None:
            self.getBounds()

        p = self.painter
        p.save()
        if self.mmldoc is not None:
            p.translate(self.xi, self.yi)
            p.rotate(self.angle)
            # is drawn from bottom of box, not top
            p.translate(0, -self.size.height())
            p.scale(self.drawscale, self.drawscale)
            self.record.play(p)
        else:
            # display an error - must be a better way to do this
            p.setFont(qt4.QFont())
            p.setPen(qt4.QPen(qt4.QColor("red")))
            p.drawText( qt4.QRectF(self.xi, self.yi, 200, 200),
                        qt4.Qt.AlignLeft | qt4.Qt.AlignTop |
                        qt4.Qt.TextWordWrap,
                        self.error )
        p.restore()

        return self.calcbounds

# identify mathml text
mml_re = re.compile(r'^\s*<math.*</math\s*>\s*$', re.DOTALL)

def Renderer(painter, font, x, y, text,
                alignhorz = -1, alignvert = -1, angle = 0,
                usefullheight = False):
    """Return an appropriate Renderer object depending on the text.
    This looks like a class name, because it was a class originally.

    painter is the painter to draw on
    font is the starting font to use
    x and y are the x and y positions to draw the text at
    alignhorz = (-1, 0, 1) for (left, centre, right) alignment
    alignvert = (-1, 0, 1) for (above, centre, below) alignment
    angle is the angle to draw the text at
    usefullheight means include descenders in calculation of height
    of text

    alignment is in the painter frame, not the text frame
    """

    if mml_re.match(text):
        r = _MmlRenderer
    else:
        r = _StdRenderer

    return r(
        painter, font, x, y, text,
        alignhorz=alignhorz, alignvert=alignvert,
        angle=angle, usefullheight=usefullheight
        )
