From: <md...@us...> - 2007-07-16 15:08:16
|
Revision: 3540 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3540&view=rev Author: mdboom Date: 2007-07-16 08:08:12 -0700 (Mon, 16 Jul 2007) Log Message: ----------- Got nested sub/superscripts working. Improved formatting of grammar to be more readable to newbies (like me). Modified Paths: -------------- branches/mathtext_mgd/lib/matplotlib/mathtext.py Modified: branches/mathtext_mgd/lib/matplotlib/mathtext.py =================================================================== --- branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-16 14:06:59 UTC (rev 3539) +++ branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-16 15:08:12 UTC (rev 3540) @@ -119,7 +119,6 @@ KNOWN ISSUES: - - nested subscripts, eg, x_i_j not working; but you can do x_{i_j} - nesting fonts changes in sub/superscript groups not parsing - I would also like to add a few more layout commands, like \frac. @@ -136,7 +135,7 @@ from matplotlib.pyparsing import Literal, Word, OneOrMore, ZeroOrMore, \ Combine, Group, Optional, Forward, NotAny, alphas, nums, alphanums, \ StringStart, StringEnd, ParseException, FollowedBy, Regex, \ - operatorPrecedence, opAssoc, ParseResults + operatorPrecedence, opAssoc, ParseResults, Or from matplotlib.afm import AFM from matplotlib.cbook import enumerate, iterable, Bunch @@ -1014,6 +1013,7 @@ self.dpi = dpi for loc, element in self.neighbors.items(): if loc in ('subscript', 'superscript'): + print type(element), element element.set_size_info(0.7*self.fontsize, dpi) else: element.set_size_info(self.fontsize, dpi) @@ -1257,8 +1257,10 @@ def clear(self): self.symbols = [] + self.subscript_stack = [] def expression(self, s, loc, toks): + print "expression", toks self.expr = ExpressionElement(toks) return [self.expr] @@ -1279,7 +1281,7 @@ assert(len(toks)==1) s = toks[0] - #~ print 'sym', toks[0] + print 'sym', toks[0] if charOverChars.has_key(s): under, over, pad = charOverChars[s] font, tok, scale = under @@ -1360,172 +1362,209 @@ grp.set_font(name[1:]) # suppress the slash return [grp] - def subscript(self, s, loc, toks): + _subsuperscript_names = { + 'normal': ['subscript', 'superscript'], + 'overUnder': ['below', 'above'] + } + + _subsuperscript_indices = { + '_': (0, 1), + '^': (1, 0) + } + + def subsuperscript(self, s, loc, toks): assert(len(toks)==1) - #print 'subsup', toks - if len(toks[0])==2: - under, next = toks[0] - prev = SpaceElement(0) - else: - prev, under, next = toks[0] + print 'subscript', toks - if self.is_overunder(prev): - prev.neighbors['below'] = next - else: - prev.neighbors['subscript'] = next + if len(toks[0])==3: + prev, op, next = toks[0] + index, other_index = self._subsuperscript_indices[op] + if self.is_overunder(prev): + names = self._subsuperscript_names['overUnder'] + else: + names = self._subsuperscript_names['normal'] + + prev.neighbors[names[index]] = next - return loc, [prev] + for compound in self._subsuperscript_names.values(): + if compound[other_index] in next.neighbors: + prev.neighbors[names[other_index]] = next.neighbors[compound[other_index]] + del next.neighbors[compound[other_index]] + return [prev] + return toks[0].asList() def is_overunder(self, prev): return isinstance(prev, SymbolElement) and overunder.has_key(prev.sym) - def superscript(self, s, loc, toks): - assert(len(toks)==1) - #print 'subsup', toks - if len(toks[0])==2: - under, next = toks[0] - prev = SpaceElement(0,0.6) - else: - prev, under, next = toks[0] - if self.is_overunder(prev): - prev.neighbors['above'] = next - else: - prev.neighbors['superscript'] = next - - return [prev] - - def subsuperscript(self, s, loc, toks): - assert(len(toks)==1) - #print 'subsup', toks - prev, undersym, down, oversym, up = toks[0] - - if self.is_overunder(prev): - prev.neighbors['below'] = down - prev.neighbors['above'] = up - else: - prev.neighbors['subscript'] = down - prev.neighbors['superscript'] = up - - return [prev] - - - handler = Handler() -lbrace = Literal('{').suppress() -rbrace = Literal('}').suppress() -lbrack = Literal('[') -rbrack = Literal(']') -lparen = Literal('(') -rparen = Literal(')') -grouping = lbrack | rbrack | lparen | rparen +# All forward declarations are here +font = Forward().setParseAction(handler.font).setName("font") +subsuper = Forward().setParseAction(handler.subsuperscript).setName("subsuper") +placeable = Forward().setName("placeable") -bslash = Literal('\\') +lbrace = Literal('{').suppress() +rbrace = Literal('}').suppress() +lbrack = Literal('[') +rbrack = Literal(']') +lparen = Literal('(') +rparen = Literal(')') +grouping =(lbrack + | rbrack + | lparen + | rparen) -langle = Literal('<') -rangle = Literal('>') -equals = Literal('=') -relation = langle | rangle | equals +subscript = Literal('_') +superscript = Literal('^') -colon = Literal(':') -comma = Literal(',') -period = Literal('.') -semicolon = Literal(';') -exclamation = Literal('!') +bslash = Literal('\\') -punctuation = colon | comma | period | semicolon +langle = Literal('<') +rangle = Literal('>') +equals = Literal('=') +relation =(langle + | rangle + | equals) -at = Literal('@') -percent = Literal('%') -ampersand = Literal('&') -misc = exclamation | at | percent | ampersand +colon = Literal(':') +comma = Literal(',') +period = Literal('.') +semicolon = Literal(';') +exclamation = Literal('!') +punctuation =(colon + | comma + | period + | semicolon) -over = Literal('over') -under = Literal('under') -#~ composite = over | under -overUnder = over | under +at = Literal('@') +percent = Literal('%') +ampersand = Literal('&') +misc =(exclamation + | at + | percent + | ampersand) -accent = Literal('hat') | Literal('check') | Literal('dot') | \ - Literal('breve') | Literal('acute') | Literal('ddot') | \ - Literal('grave') | Literal('tilde') | Literal('bar') | \ - Literal('vec') | Literal('"') | Literal("`") | Literal("'") |\ - Literal('~') | Literal('.') | Literal('^') +over = Literal('over') +under = Literal('under') +overUnder =(over + | under) +accent =(Literal('hat') | Literal('check') | Literal('dot') | + Literal('breve') | Literal('acute') | Literal('ddot') | + Literal('grave') | Literal('tilde') | Literal('bar') | + Literal('vec') | Literal('"') | Literal("`") | Literal("'") | + Literal('~') | Literal('.') | Literal('^')) +number = Combine(Word(nums) + Optional(Literal('.')) + Optional( Word(nums) )) +plus = Literal('+') +minus = Literal('-') +times = Literal('*') +div = Literal('/') +binop =(plus + | minus + | times + | div) -number = Combine(Word(nums) + Optional(Literal('.')) + Optional( Word(nums) )) +roman = Literal('rm') +cal = Literal('cal') +italics = Literal('it') +typewriter = Literal('tt') +fontname =(roman + | cal + | italics + | typewriter) -plus = Literal('+') -minus = Literal('-') -times = Literal('*') -div = Literal('/') -binop = plus | minus | times | div +texsym = Combine(bslash + Word(alphanums) + NotAny("{")) +char = Word(alphanums + ' ', exact=1).leaveWhitespace() -roman = Literal('rm') -cal = Literal('cal') -italics = Literal('it') -typewriter = Literal('tt') -fontname = roman | cal | italics | typewriter +space =(FollowedBy(bslash) + + (Literal(r'\ ') + | Literal(r'\/') + | Group(Literal(r'\hspace{') + number + Literal('}')) + ) + ).setParseAction(handler.space).setName('space') -texsym = Combine(bslash + Word(alphanums) + NotAny("{")) +symbol = Regex("(" + ")|(".join( + [ + r"\\[a-zA-Z0-9]+(?!{)", + r"[a-zA-Z0-9 ]", + r"[+\-*/]", + r"[<>=]", + r"[:,.;!]", + r"[!@%&]", + r"[[\]()]", + ]) + + ")" + ).setParseAction(handler.symbol).leaveWhitespace() -char = Word(alphanums + ' ', exact=1).leaveWhitespace() +_symbol =(texsym + | char + | binop + | relation + | punctuation + | misc + | grouping + ).setParseAction(handler.symbol).leaveWhitespace() -space = FollowedBy(bslash) + (Literal(r'\ ') | Literal(r'\/') | Group(Literal(r'\hspace{') + number + Literal('}'))).setParseAction(handler.space).setName('space') +accent = Group( + Combine(bslash + accent) + + Optional(lbrace) + + symbol + + Optional(rbrace) + ).setParseAction(handler.accent).setName("accent") -symbol = Regex("("+")|(".join( - [ - r"\\[a-zA-Z0-9]+(?!{)", - r"[a-zA-Z0-9 ]", - r"[+\-*/]", - r"[<>=]", - r"[:,.;!]", - r"[!@%&]", - r"[[\]()]", - ])+")" - ).setParseAction(handler.symbol).leaveWhitespace() +group = Group( + lbrace + + OneOrMore( + space + ^ font + ^ subsuper + ^ placeable + ) + + rbrace + ).setParseAction(handler.group).setName("group") -#~ symbol = (texsym ^ char ^ binop ^ relation ^ punctuation ^ misc ^ grouping ).setParseAction(handler.symbol).leaveWhitespace() -_symbol = (texsym | char | binop | relation | punctuation | misc | grouping ).setParseAction(handler.symbol).leaveWhitespace() +composite = Group( + Combine( + bslash + + overUnder + ) + + group + + group + ).setParseAction(handler.composite).setName("composite") -subscript = Forward().setParseAction(handler.subscript).setName("subscript") -superscript = Forward().setParseAction(handler.superscript).setName("superscript") -subsuperscript = Forward().setParseAction(handler.subsuperscript).setName("subsuperscript") +font << Group( + Combine( + bslash + + fontname) + + group) -font = Forward().setParseAction(handler.font).setName("font") +placeable <<(accent + ^ symbol + ^ group + ^ composite + ) +subsuper << Group( + placeable + + ZeroOrMore( + ( subscript + | superscript + ) + + subsuper + ) + ) -accent = Group( Combine(bslash + accent) + Optional(lbrace) + symbol + Optional(rbrace)).setParseAction(handler.accent).setName("accent") -group = Group( lbrace + OneOrMore(symbol^subscript^superscript^subsuperscript^space^font^accent) + rbrace).setParseAction(handler.group).setName("group") -#~ group = Group( lbrace + OneOrMore(subsuperscript | subscript | superscript | symbol | space ) + rbrace).setParseAction(handler.group).setName("group") +expression = OneOrMore( + space + ^ font + ^ subsuper + ^ placeable + ).setParseAction(handler.expression).setName("expression") -#composite = Group( Combine(bslash + composite) + lbrace + symbol + rbrace + lbrace + symbol + rbrace).setParseAction(handler.composite).setName("composite") -#~ composite = Group( Combine(bslash + composite) + group + group).setParseAction(handler.composite).setName("composite") -composite = Group( Combine(bslash + overUnder) + group + group).setParseAction(handler.composite).setName("composite") - - - - - - -symgroup = font | group | symbol - -subscript << Group( Optional(symgroup) + Literal('_') + symgroup ) -superscript << Group( Optional(symgroup) + Literal('^') + symgroup ) -subsuperscript << Group( symgroup + Literal('_') + symgroup + Literal('^') + symgroup ) - -font << Group( Combine(bslash + fontname) + group) - - - -expression = OneOrMore( - space ^ font ^ accent ^ symbol ^ subscript ^ superscript ^ subsuperscript ^ group ^ composite ).setParseAction(handler.expression).setName("expression") -#~ expression = OneOrMore( - #~ group | composite | space | font | subsuperscript | subscript | superscript | symbol ).setParseAction(handler.expression).setName("expression") - #### @@ -1577,7 +1616,9 @@ expression.parseString( s ) handler.expr.set_size_info(fontsize, dpi) - + print handler.expr + print handler.symbols + # set the origin once to allow w, h compution handler.expr.set_origin(0, 0) xmin = min([e.xmin() for e in handler.symbols]) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-07-16 15:42:27
|
Revision: 3541 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3541&view=rev Author: mdboom Date: 2007-07-16 08:42:25 -0700 (Mon, 16 Jul 2007) Log Message: ----------- Minor cleanup and simplifications. Handle sub/superscript as a unary operator. Modified Paths: -------------- branches/mathtext_mgd/lib/matplotlib/mathtext.py Modified: branches/mathtext_mgd/lib/matplotlib/mathtext.py =================================================================== --- branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-16 15:08:12 UTC (rev 3540) +++ branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-16 15:42:25 UTC (rev 3541) @@ -1013,7 +1013,6 @@ self.dpi = dpi for loc, element in self.neighbors.items(): if loc in ('subscript', 'superscript'): - print type(element), element element.set_size_info(0.7*self.fontsize, dpi) else: element.set_size_info(self.fontsize, dpi) @@ -1260,7 +1259,6 @@ self.subscript_stack = [] def expression(self, s, loc, toks): - print "expression", toks self.expr = ExpressionElement(toks) return [self.expr] @@ -1281,7 +1279,6 @@ assert(len(toks)==1) s = toks[0] - print 'sym', toks[0] if charOverChars.has_key(s): under, over, pad = charOverChars[s] font, tok, scale = under @@ -1374,25 +1371,32 @@ def subsuperscript(self, s, loc, toks): assert(len(toks)==1) - print 'subscript', toks + #~ print 'subsuperscript', toks - if len(toks[0])==3: + if len(toks[0]) == 1: + return toks[0].asList() + if len(toks[0]) == 3: prev, op, next = toks[0] - index, other_index = self._subsuperscript_indices[op] - if self.is_overunder(prev): - names = self._subsuperscript_names['overUnder'] - else: - names = self._subsuperscript_names['normal'] - - prev.neighbors[names[index]] = next + elif len(toks[0]) == 2: + prev = SpaceElement(0) + op, next = toks[0] + else: + raise ParseException("Unable to parse subscript/superscript construct.") - for compound in self._subsuperscript_names.values(): - if compound[other_index] in next.neighbors: - prev.neighbors[names[other_index]] = next.neighbors[compound[other_index]] - del next.neighbors[compound[other_index]] - return [prev] - return toks[0].asList() + index, other_index = self._subsuperscript_indices[op] + if self.is_overunder(prev): + names = self._subsuperscript_names['overUnder'] + else: + names = self._subsuperscript_names['normal'] + prev.neighbors[names[index]] = next + + for compound in self._subsuperscript_names.values(): + if compound[other_index] in next.neighbors: + prev.neighbors[names[other_index]] = next.neighbors[compound[other_index]] + del next.neighbors[compound[other_index]] + return [prev] + def is_overunder(self, prev): return isinstance(prev, SymbolElement) and overunder.has_key(prev.sym) @@ -1522,7 +1526,6 @@ space ^ font ^ subsuper - ^ placeable ) + rbrace ).setParseAction(handler.group).setName("group") @@ -1549,20 +1552,22 @@ ) subsuper << Group( - placeable - + ZeroOrMore( - ( subscript - | superscript - ) - + subsuper + ( + placeable + + ZeroOrMore( + ( subscript + | superscript + ) + + subsuper + ) ) + | (( subscript | superscript) + placeable) ) expression = OneOrMore( space ^ font ^ subsuper - ^ placeable ).setParseAction(handler.expression).setName("expression") #### @@ -1616,8 +1621,6 @@ expression.parseString( s ) handler.expr.set_size_info(fontsize, dpi) - print handler.expr - print handler.symbols # set the origin once to allow w, h compution handler.expr.set_origin(0, 0) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-07-16 17:59:18
|
Revision: 3542 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3542&view=rev Author: mdboom Date: 2007-07-16 10:59:17 -0700 (Mon, 16 Jul 2007) Log Message: ----------- Deal with font tags (\cal \it \rm etc.) more like TeX Modified Paths: -------------- branches/mathtext_mgd/lib/matplotlib/mathtext.py Modified: branches/mathtext_mgd/lib/matplotlib/mathtext.py =================================================================== --- branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-16 15:42:25 UTC (rev 3541) +++ branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-16 17:59:17 UTC (rev 3542) @@ -135,7 +135,7 @@ from matplotlib.pyparsing import Literal, Word, OneOrMore, ZeroOrMore, \ Combine, Group, Optional, Forward, NotAny, alphas, nums, alphanums, \ StringStart, StringEnd, ParseException, FollowedBy, Regex, \ - operatorPrecedence, opAssoc, ParseResults, Or + operatorPrecedence, opAssoc, ParseResults, Or, Suppress from matplotlib.afm import AFM from matplotlib.cbook import enumerate, iterable, Bunch @@ -927,7 +927,6 @@ class Element: fontsize = 12 dpi = 72 - font = 'it' _padx, _pady = 2, 2 # the x and y padding in points _scale = 1.0 @@ -965,10 +964,14 @@ 'get the ymax of ink rect' raise NotImplementedError('derived must override') + def determine_font(self, font_stack): + 'a first pass to determine the font of this element (one of tt, it, rm , cal)' + raise NotImplementedError('derived must override') + def set_font(self, font): 'set the font (one of tt, it, rm , cal)' raise NotImplementedError('derived must override') - + def render(self): 'render to the fonts canvas' for element in self.neighbors.values(): @@ -1044,6 +1047,45 @@ def __repr__(self): return str(self.__class__) + str(self.neighbors) +class FontElement(Element): + def __init__(self, name): + Element.__init__(self) + self.name = name + + def advance(self): + 'get the horiz advance' + return 0 + + def height(self): + 'get the element height: ymax-ymin' + return 0 + + def width(self): + 'get the element width: xmax-xmin' + return 0 + + def xmin(self): + 'get the xmin of ink rect' + return 0 + + def xmax(self): + 'get the xmax of ink rect' + return 0 + + def ymin(self): + 'get the ymin of ink rect' + return 0 + + def ymax(self): + 'get the ymax of ink rect' + return 0 + + def determine_font(self, font_stack): + font_stack[-1] = self.name + + def set_font(self, font): + return + class SpaceElement(Element): 'blank horizontal space' def __init__(self, space, height=0): @@ -1083,10 +1125,17 @@ 'get the max ink in y' return self.oy + self.height() - def set_font(self, f): + def determine_font(self, font_stack): # space doesn't care about font, only size + for neighbor_type in ('above', 'below', 'subscript', 'superscript'): + neighbor = self.neighbors.get(neighbor_type) + if neighbor is not None: + neighbor.determine_font(font_stack) + + def set_font(self, font_stack): + # space doesn't care about font, only size pass - + class SymbolElement(Element): def __init__(self, sym): Element.__init__(self) @@ -1094,10 +1143,19 @@ self.kern = None self.widthm = 1 # the width of an m; will be resized below + def determine_font(self, font_stack): + 'set the font (one of tt, it, rm, cal)' + self.set_font(font_stack[-1]) + for neighbor_type in ('above', 'below', 'subscript', 'superscript'): + neighbor = self.neighbors.get(neighbor_type) + if neighbor is not None: + neighbor.determine_font(font_stack) + def set_font(self, font): - 'set the font (one of tt, it, rm , cal)' + # space doesn't care about font, only size + assert not hasattr(self, 'font') self.font = font - + def set_origin(self, ox, oy): Element.set_origin(self, ox, oy) @@ -1163,6 +1221,8 @@ def __repr__(self): return self.sym +class AccentElement(SymbolElement): + pass class GroupElement(Element): """ @@ -1174,20 +1234,25 @@ for i in range(len(elements)-1): self.elements[i].neighbors['right'] = self.elements[i+1] - def set_font(self, font): + def determine_font(self, font_stack): 'set the font (one of tt, it, rm , cal)' + font_stack.append(font_stack[-1]) for element in self.elements: - element.set_font(font) + element.determine_font(font_stack) + font_stack.pop() - + def set_font(self, font): + return + + # MGDTODO: The code below is probably now broken #print 'set fonts' - for i in range(len(self.elements)-1): - if not isinstance(self.elements[i], SymbolElement): continue - if not isinstance(self.elements[i+1], SymbolElement): continue - symleft = self.elements[i].sym - symright = self.elements[i+1].sym - self.elements[i].kern = None - #self.elements[i].kern = Element.fonts.get_kern(font, symleft, symright, self.fontsize, self.dpi) +# for i in range(len(self.elements)-1): +# if not isinstance(self.elements[i], SymbolElement): continue +# if not isinstance(self.elements[i+1], SymbolElement): continue +# symleft = self.elements[i].sym +# symright = self.elements[i+1].sym +# self.elements[i].kern = None +# #self.elements[i].kern = Element.fonts.get_kern(font, symleft, symright, self.fontsize, self.dpi) def set_size_info(self, fontsize, dpi): @@ -1250,6 +1315,9 @@ def __repr__(self): return 'Expression: [ %s ]' % ' '.join([str(e) for e in self.elements]) + def determine_font(self): + font_stack = ['it'] + GroupElement.determine_font(self, font_stack) class Handler: symbols = [] @@ -1275,29 +1343,30 @@ return [element] def symbol(self, s, loc, toks): - assert(len(toks)==1) - + #print "symbol", toks + s = toks[0] - if charOverChars.has_key(s): - under, over, pad = charOverChars[s] - font, tok, scale = under - sym = SymbolElement(tok) - if font is not None: - sym.set_font(font) - sym.set_scale(scale) - sym.set_pady(pad) + # MGDTODO: This clause is probably broken due to font changes +# if charOverChars.has_key(s): +# under, over, pad = charOverChars[s] +# font, tok, scale = under +# sym = SymbolElement(tok) +# if font is not None: +# sym.set_font(font) +# sym.set_scale(scale) +# sym.set_pady(pad) - font, tok, scale = over - sym2 = SymbolElement(tok) - if font is not None: - sym2.set_font(font) - sym2.set_scale(scale) +# font, tok, scale = over +# sym2 = SymbolElement(tok) +# if font is not None: +# sym2.set_font(font) +# sym2.set_scale(scale) - sym.neighbors['above'] = sym2 - self.symbols.append(sym2) - else: - sym = SymbolElement(toks[0]) +# sym.neighbors['above'] = sym2 +# self.symbols.append(sym2) +# else: + sym = SymbolElement(toks[0]) self.symbols.append(sym) return [sym] @@ -1339,7 +1408,7 @@ r'\.' : r'\combiningdotabove', r'\^' : r'\circumflexaccent', } - above = SymbolElement(d[accent]) + above = AccentElement(d[accent]) sym.neighbors['above'] = above sym.set_pady(1) self.symbols.append(above) @@ -1352,12 +1421,11 @@ return [grp] def font(self, s, loc, toks): - assert(len(toks)==1) - name, grp = toks[0] + name = toks[0] #print 'fontgrp', toks - grp.set_font(name[1:]) # suppress the slash - return [grp] + font = FontElement(name) + return [font] _subsuperscript_names = { 'normal': ['subscript', 'superscript'], @@ -1524,8 +1592,8 @@ lbrace + OneOrMore( space - ^ font - ^ subsuper + | font + | subsuper ) + rbrace ).setParseAction(handler.group).setName("group") @@ -1539,11 +1607,8 @@ + group ).setParseAction(handler.composite).setName("composite") -font << Group( - Combine( - bslash - + fontname) - + group) +font <<(Suppress(bslash) + + fontname) placeable <<(accent ^ symbol @@ -1566,8 +1631,8 @@ expression = OneOrMore( space - ^ font - ^ subsuper + | font + | subsuper ).setParseAction(handler.expression).setName("expression") #### @@ -1616,10 +1681,11 @@ elif self.output == 'PDF': self.font_object = BakomaPDFFonts(character_tracker) Element.fonts = self.font_object - + handler.clear() expression.parseString( s ) + handler.expr.determine_font() handler.expr.set_size_info(fontsize, dpi) # set the origin once to allow w, h compution This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-07-17 12:22:06
|
Revision: 3548 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3548&view=rev Author: mdboom Date: 2007-07-17 05:21:59 -0700 (Tue, 17 Jul 2007) Log Message: ----------- Add support for Latex2e-style math fonts (\mathrm, \mathit etc.) Add support for function name shortcuts (\sin, \cos etc.) Raise an exception when encountering double subscript or superscripts (e.g. x_i_j) Modified Paths: -------------- branches/mathtext_mgd/lib/matplotlib/mathtext.py Modified: branches/mathtext_mgd/lib/matplotlib/mathtext.py =================================================================== --- branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-17 10:09:27 UTC (rev 3547) +++ branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-17 12:21:59 UTC (rev 3548) @@ -135,7 +135,7 @@ from matplotlib.pyparsing import Literal, Word, OneOrMore, ZeroOrMore, \ Combine, Group, Optional, Forward, NotAny, alphas, nums, alphanums, \ StringStart, StringEnd, ParseException, FollowedBy, Regex, \ - operatorPrecedence, opAssoc, ParseResults, Or, Suppress + operatorPrecedence, opAssoc, ParseResults, Or, Suppress, oneOf from matplotlib.afm import AFM from matplotlib.cbook import enumerate, iterable, Bunch @@ -1230,6 +1230,8 @@ """ def __init__(self, elements): Element.__init__(self) + if not isinstance(elements, list): + elements = elements.asList() self.elements = elements for i in range(len(elements)-1): self.elements[i].neighbors['right'] = self.elements[i+1] @@ -1414,6 +1416,15 @@ self.symbols.append(above) return [sym] + def function(self, s, loc, toks): + #~ print "function", toks + symbols = [FontElement("rm")] + for c in toks[0]: + sym = SymbolElement(c) + symbols.append(sym) + self.symbols.append(sym) + return [GroupElement(symbols)] + def group(self, s, loc, toks): assert(len(toks)==1) #print 'grp', toks @@ -1427,6 +1438,16 @@ font = FontElement(name) return [font] + def latexfont(self, s, loc, toks): + assert(len(toks)==1) + name, grp = toks[0] + if len(grp.elements): + font = FontElement(name[4:]) + font.neighbors['right'] = grp.elements[0] + grp.elements.insert(0, font) + return [grp] + return [] + _subsuperscript_names = { 'normal': ['subscript', 'superscript'], 'overUnder': ['below', 'above'] @@ -1463,6 +1484,10 @@ if compound[other_index] in next.neighbors: prev.neighbors[names[other_index]] = next.neighbors[compound[other_index]] del next.neighbors[compound[other_index]] + elif compound[index] in next.neighbors: + raise ValueError( + "Double %ss" % + self._subsuperscript_names['normal'][index]) return [prev] def is_overunder(self, prev): @@ -1472,6 +1497,7 @@ # All forward declarations are here font = Forward().setParseAction(handler.font).setName("font") +latexfont = Forward().setParseAction(handler.latexfont).setName("latexfont") subsuper = Forward().setParseAction(handler.subsuperscript).setName("subsuper") placeable = Forward().setName("placeable") @@ -1522,12 +1548,13 @@ overUnder =(over | under) -accent =(Literal('hat') | Literal('check') | Literal('dot') | - Literal('breve') | Literal('acute') | Literal('ddot') | - Literal('grave') | Literal('tilde') | Literal('bar') | - Literal('vec') | Literal('"') | Literal("`") | Literal("'") | - Literal('~') | Literal('.') | Literal('^')) +accent = oneOf("hat check dot breve acute ddot grave tilde bar vec " + "\" ` ' ~ . ^") +function = oneOf("arccos csc ker min arcsin deg lg Pr arctan det lim sec " + "arg dim liminf sin cos exp limsup sinh cosh gcd ln sup " + "cot hom log tan coth inf max tanh") + number = Combine(Word(nums) + Optional(Literal('.')) + Optional( Word(nums) )) plus = Literal('+') @@ -1539,14 +1566,9 @@ | times | div) -roman = Literal('rm') -cal = Literal('cal') -italics = Literal('it') -typewriter = Literal('tt') -fontname =(roman - | cal - | italics - | typewriter) +fontname = oneOf("rm cal it tt") + # mathbf and mathsf not supported yet +latex2efont = oneOf("mathrm mathcal mathit mathtt") texsym = Combine(bslash + Word(alphanums) + NotAny("{")) @@ -1588,16 +1610,28 @@ + Optional(rbrace) ).setParseAction(handler.accent).setName("accent") +function =(Suppress(bslash) + + function).setParseAction(handler.function).setName("function") + group = Group( lbrace + OneOrMore( space | font + | latexfont | subsuper ) + rbrace ).setParseAction(handler.group).setName("group") +font <<(Suppress(bslash) + + fontname) + +latexfont << Group( + Suppress(bslash) + + latex2efont + + group) + composite = Group( Combine( bslash @@ -1607,10 +1641,8 @@ + group ).setParseAction(handler.composite).setName("composite") -font <<(Suppress(bslash) - + fontname) - placeable <<(accent + ^ function ^ symbol ^ group ^ composite @@ -1632,6 +1664,7 @@ expression = OneOrMore( space | font + | latexfont | subsuper ).setParseAction(handler.expression).setName("expression") This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-07-17 12:55:08
|
Revision: 3550 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3550&view=rev Author: mdboom Date: 2007-07-17 05:55:05 -0700 (Tue, 17 Jul 2007) Log Message: ----------- Fix charOverChars (\angstrom) Modified Paths: -------------- branches/mathtext_mgd/lib/matplotlib/mathtext.py Modified: branches/mathtext_mgd/lib/matplotlib/mathtext.py =================================================================== --- branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-17 12:40:56 UTC (rev 3549) +++ branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-17 12:55:05 UTC (rev 3550) @@ -971,11 +971,11 @@ raise NotImplementedError('derived must override') def determine_font(self, font_stack): - 'a first pass to determine the font of this element (one of tt, it, rm , cal)' + 'a first pass to determine the font of this element (one of tt, it, rm , cal, bf, sf)' raise NotImplementedError('derived must override') def set_font(self, font): - 'set the font (one of tt, it, rm , cal)' + 'set the font (one of tt, it, rm, cal, bf, sf)' raise NotImplementedError('derived must override') def render(self): @@ -1143,6 +1143,8 @@ pass class SymbolElement(Element): + hardcoded_font = False + def __init__(self, sym): Element.__init__(self) self.sym = sym @@ -1150,17 +1152,20 @@ self.widthm = 1 # the width of an m; will be resized below def determine_font(self, font_stack): - 'set the font (one of tt, it, rm, cal)' + 'set the font (one of tt, it, rm, cal, bf, sf)' self.set_font(font_stack[-1]) for neighbor_type in ('above', 'below', 'subscript', 'superscript'): neighbor = self.neighbors.get(neighbor_type) if neighbor is not None: neighbor.determine_font(font_stack) - def set_font(self, font): - # space doesn't care about font, only size - assert not hasattr(self, 'font') - self.font = font + def set_font(self, font, hardcoded=False): + if hardcoded: + self.hardcoded_font = True + self.font = font + if not self.hardcoded_font: + assert not hasattr(self, 'font') + self.font = font def set_origin(self, ox, oy): Element.set_origin(self, ox, oy) @@ -1355,26 +1360,25 @@ #print "symbol", toks s = toks[0] - # MGDTODO: This clause is probably broken due to font changes -# if charOverChars.has_key(s): -# under, over, pad = charOverChars[s] -# font, tok, scale = under -# sym = SymbolElement(tok) -# if font is not None: -# sym.set_font(font) -# sym.set_scale(scale) -# sym.set_pady(pad) + if charOverChars.has_key(s): + under, over, pad = charOverChars[s] + font, tok, scale = under + sym = SymbolElement(tok) + if font is not None: + sym.set_font(font, hardcoded=True) + sym.set_scale(scale) + sym.set_pady(pad) -# font, tok, scale = over -# sym2 = SymbolElement(tok) -# if font is not None: -# sym2.set_font(font) -# sym2.set_scale(scale) + font, tok, scale = over + sym2 = SymbolElement(tok) + if font is not None: + sym2.set_font(font, hardcoded=True) + sym2.set_scale(scale) -# sym.neighbors['above'] = sym2 -# self.symbols.append(sym2) -# else: - sym = SymbolElement(toks[0]) + sym.neighbors['above'] = sym2 + self.symbols.append(sym2) + else: + sym = SymbolElement(toks[0]) self.symbols.append(sym) return [sym] @@ -1673,8 +1677,8 @@ | subsuper ).setParseAction(handler.expression).setName("expression") + - #### This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-07-18 17:18:16
|
Revision: 3564 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3564&view=rev Author: mdboom Date: 2007-07-18 10:18:15 -0700 (Wed, 18 Jul 2007) Log Message: ----------- Make \under and \over behave as in TeX. (i.e. it's {x \over y} not \over{x}{y}) Modified Paths: -------------- branches/mathtext_mgd/lib/matplotlib/mathtext.py Modified: branches/mathtext_mgd/lib/matplotlib/mathtext.py =================================================================== --- branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-18 16:59:39 UTC (rev 3563) +++ branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-18 17:18:15 UTC (rev 3564) @@ -1088,6 +1088,7 @@ return self.oy + self.height() def determine_font(self, font_stack): + print "Space" # space doesn't care about font, only size for neighbor_type in ('above', 'below', 'subscript', 'superscript'): neighbor = self.neighbors.get(neighbor_type) @@ -1109,6 +1110,7 @@ def determine_font(self, font_stack): 'set the font (one of tt, it, rm, cal, bf, sf)' + print "sym:", self.sym, self.neighbors.keys() self.set_font(font_stack[-1]) for neighbor_type in ('above', 'below', 'subscript', 'superscript'): neighbor = self.neighbors.get(neighbor_type) @@ -1208,6 +1210,10 @@ font_stack.append(font_stack[-1]) for element in self.elements: element.determine_font(font_stack) + for neighbor_type in ('above', 'below', 'subscript', 'superscript'): + neighbor = self.neighbors.get(neighbor_type) + if neighbor is not None: + neighbor.determine_font(font_stack) font_stack.pop() def set_font(self, font): @@ -1359,16 +1365,17 @@ return [sym] - def composite(self, s, loc, toks): - + def over_under(self, s, loc, toks): assert(len(toks)==1) where, sym0, sym1 = toks[0] #keys = ('above', 'below', 'subscript', 'superscript', 'right') + print "where:", toks[0] if where==r'\over': sym0.neighbors['above'] = sym1 elif where==r'\under': sym0.neighbors['below'] = sym1 - + print sym0.neighbors.keys() + self.symbols.append(sym0) self.symbols.append(sym1) @@ -1440,8 +1447,10 @@ } _subsuperscript_indices = { - '_': (0, 1), - '^': (1, 0) + '_' : ('normal', (0, 1)), + '^' : ('normal', (1, 0)), + 'over' : ('overUnder', (0, 1)), + 'under' : ('overUnder', (1, 0)) } def subsuperscript(self, s, loc, toks): @@ -1458,17 +1467,17 @@ else: raise ParseException("Unable to parse subscript/superscript construct.") - index, other_index = self._subsuperscript_indices[op] + relation_type, (index, other_index) = self._subsuperscript_indices[op] if self.is_overunder(prev): - names = self._subsuperscript_names['overUnder'] - else: - names = self._subsuperscript_names['normal'] + relation_type = 'overUnder' + names = self._subsuperscript_names[relation_type] prev.neighbors[names[index]] = next for compound in self._subsuperscript_names.values(): if compound[other_index] in next.neighbors: - prev.neighbors[names[other_index]] = next.neighbors[compound[other_index]] + prev.neighbors[names[other_index]] = \ + next.neighbors[compound[other_index]] del next.neighbors[compound[other_index]] elif compound[index] in next.neighbors: raise ValueError( @@ -1486,6 +1495,7 @@ latexfont = Forward().setParseAction(handler.latexfont).setName("latexfont") subsuper = Forward().setParseAction(handler.subsuperscript).setName("subsuper") placeable = Forward().setName("placeable") +simple = Forward().setName("simple") expression = Forward().setParseAction(handler.expression).setName("expression") lbrace = Literal('{').suppress() @@ -1499,9 +1509,6 @@ | lparen | rparen) -subscript = Literal('_') -superscript = Literal('^') - bslash = Literal('\\') langle = Literal('<') @@ -1529,11 +1536,6 @@ | percent | ampersand) -over = Literal('over') -under = Literal('under') -overUnder =(over - | under) - accent = oneOf("hat check dot breve acute ddot grave tilde bar vec " "\" ` ' ~ . ^") @@ -1602,10 +1604,7 @@ group = Group( lbrace + OneOrMore( - space - | font - | latexfont - | subsuper + simple ) + rbrace ).setParseAction(handler.group).setName("group") @@ -1618,40 +1617,36 @@ + latex2efont + group) -composite = Group( - Combine( - bslash - + overUnder - ) - + group - + group - ).setParseAction(handler.composite).setName("composite") - placeable <<(accent ^ function ^ symbol ^ group - ^ composite ) +simple <<(space + | font + | latexfont + | subsuper) + +subsuperop =(Literal("_") + | Literal("^") + | (Suppress(bslash) + Literal("under")) + | (Suppress(bslash) + Literal("over")) + ) + subsuper << Group( ( placeable + ZeroOrMore( - ( subscript - | superscript - ) + subsuperop + subsuper ) ) - | (( subscript | superscript) + placeable) + | (subsuperop + placeable) ) math = OneOrMore( - space - | font - | latexfont - | subsuper + simple ).setParseAction(handler.math).setName("math") math_delim =(~bslash @@ -1669,7 +1664,6 @@ + non_math ) ) - #### This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-07-18 17:21:12
|
Revision: 3565 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3565&view=rev Author: mdboom Date: 2007-07-18 10:21:11 -0700 (Wed, 18 Jul 2007) Log Message: ----------- Clean up logging. Remove dead function. Modified Paths: -------------- branches/mathtext_mgd/lib/matplotlib/mathtext.py Modified: branches/mathtext_mgd/lib/matplotlib/mathtext.py =================================================================== --- branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-18 17:18:15 UTC (rev 3564) +++ branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-18 17:21:11 UTC (rev 3565) @@ -1088,7 +1088,6 @@ return self.oy + self.height() def determine_font(self, font_stack): - print "Space" # space doesn't care about font, only size for neighbor_type in ('above', 'below', 'subscript', 'superscript'): neighbor = self.neighbors.get(neighbor_type) @@ -1110,7 +1109,6 @@ def determine_font(self, font_stack): 'set the font (one of tt, it, rm, cal, bf, sf)' - print "sym:", self.sym, self.neighbors.keys() self.set_font(font_stack[-1]) for neighbor_type in ('above', 'below', 'subscript', 'superscript'): neighbor = self.neighbors.get(neighbor_type) @@ -1365,22 +1363,6 @@ return [sym] - def over_under(self, s, loc, toks): - assert(len(toks)==1) - where, sym0, sym1 = toks[0] - #keys = ('above', 'below', 'subscript', 'superscript', 'right') - print "where:", toks[0] - if where==r'\over': - sym0.neighbors['above'] = sym1 - elif where==r'\under': - sym0.neighbors['below'] = sym1 - print sym0.neighbors.keys() - - self.symbols.append(sym0) - self.symbols.append(sym1) - - return [sym0] - def accent(self, s, loc, toks): assert(len(toks)==1) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-07-18 18:31:56
|
Revision: 3559 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3559&view=rev Author: mdboom Date: 2007-07-18 08:41:59 -0700 (Wed, 18 Jul 2007) Log Message: ----------- Fix spacing when going from non-math to math. Modified Paths: -------------- branches/mathtext_mgd/lib/matplotlib/mathtext.py Modified: branches/mathtext_mgd/lib/matplotlib/mathtext.py =================================================================== --- branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-18 15:27:58 UTC (rev 3558) +++ branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-18 15:41:59 UTC (rev 3559) @@ -1311,7 +1311,10 @@ def non_math(self, s, loc, toks): #~ print "non_math", toks - symbols = [SymbolElement(c) for c in toks[0]] + # This is a hack, but it allows the system to use the + # proper amount of advance when going from non-math to math + s = toks[0] + ' ' + symbols = [SymbolElement(c) for c in s] self.symbols.extend(symbols) non_math = NonMathGroupElement(symbols) return [non_math] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-07-20 14:19:52
|
Revision: 3590 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3590&view=rev Author: mdboom Date: 2007-07-20 07:19:48 -0700 (Fri, 20 Jul 2007) Log Message: ----------- First pass with a real TeX box model. Lots of things broken -- just want to mark this spot in the revision history. Modified Paths: -------------- branches/mathtext_mgd/lib/matplotlib/mathtext.py Modified: branches/mathtext_mgd/lib/matplotlib/mathtext.py =================================================================== --- branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-20 14:15:29 UTC (rev 3589) +++ branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-20 14:19:48 UTC (rev 3590) @@ -127,6 +127,7 @@ math text yet. They are most likely broken. -- Michael Droettboom, July 2007 Author : John Hunter <jdh...@ac...> + Michael Droettboom <md...@st...> (rewrite based on TeX algorithms) Copyright : John Hunter (2004,2005) License : matplotlib license (PSF compatible) @@ -135,6 +136,7 @@ import os, sys from cStringIO import StringIO from sets import Set +from warnings import warn from matplotlib import verbose from matplotlib.pyparsing import Literal, Word, OneOrMore, ZeroOrMore, \ @@ -152,11 +154,12 @@ from matplotlib import get_data_path, rcParams # symbols that have the sub and superscripts over/under -overunder = { r'\sum' : 1, - r'\int' : 1, - r'\prod' : 1, - r'\coprod' : 1, - } +overunder_symbols = { + r'\sum' : 1, + r'\int' : 1, + r'\prod' : 1, + r'\coprod' : 1, + } # a character over another character charOverChars = { # The first 2 entires in the tuple are (font, char, sizescale) for @@ -165,6 +168,8 @@ r'\angstrom' : ( ('rm', 'A', 1.0), (None, '\circ', 0.5), 0.0 ), } +############################################################################## +# FONTS def font_open(filename): ext = filename.rsplit('.',1)[1] @@ -269,6 +274,9 @@ def render(self, ox, oy, facename, sym, fontsize, dpi): pass + def render_rect_filled(self, x1, y1, x2, y2): + pass + def get_used_characters(self): return {} @@ -659,6 +667,9 @@ xmax = xmax, ymin = ymin+offset, ymax = ymax+offset, + # iceberg is the amount of character that floats above the baseline + # This is equivalent to TeX' "height" + iceberg = glyph.horiBearingY/64.0 ) self.glyphd[key] = basename, font, metrics, symbol_name, num, glyph, offset @@ -673,11 +684,18 @@ def render(self, ox, oy, font, sym, fontsize, dpi): basename, font, metrics, symbol_name, num, glyph, offset = \ - self._get_info(font, sym, fontsize, dpi) + self._get_info(font, sym, fontsize, dpi) + font.draw_rect(0, 0, self.width - 1, self.height - 1) font.draw_glyph_to_bitmap( - int(ox), int(self.height - oy - metrics.ymax), glyph) + int(ox), int(oy - metrics.ymax), glyph) + def render_rect_filled(self, x1, y1, x2, y2): + assert len(self.fonts) + font = self.fonts.values()[0] + print "filled rect:", x1, y1, x2, y2 + font.font.draw_rect_filled(x1, y1, x2, y2) + def _old_get_kern(self, font, symleft, symright, fontsize, dpi): """ Get the kerning distance for font between symleft and symright. @@ -699,6 +717,11 @@ def get_used_characters(self): return self.used_characters + + def get_xheight(self, font): + basename, cached_font = self._get_font(font) + pclt = cached_font.font.get_sfnt_table('pclt') + return pclt['xHeight'] / 64.0 class BakomaPSFonts(BakomaFonts): """ @@ -885,443 +908,905 @@ basename, font = self._get_font(font) return font.get_kern_dist(glyph1, glyph2) * 0.001 * fontsize return 0 - -class Element: - fontsize = 12 - dpi = 72 - _padx, _pady = 2, 2 # the x and y padding in points - _scale = 1.0 - def __init__(self): - # a dict mapping the keys above, below, subscript, - # superscript, right to Elements in that position - self.neighbors = {} - self.ox, self.oy = 0, 0 +############################################################################## +# TeX-LIKE BOX MODEL - def advance(self): - 'get the horiz advance' - raise NotImplementedError('derived must override') +# The following is based directly on the document 'woven' from the +# TeX82 source code. This information is also available in printed +# form: +# +# Knuth, Donald E.. 1986. Computers and Typesetting, Volume B: +# TeX: The Program. Addison-Wesley Professional. +# +# The most relevant "chapters" are: +# Data structures for boxes and their friends +# Shipping pages out (Ship class) +# Packaging (hpack and vpack) +# Data structures for math mode +# Subroutines for math mode +# Typesetting math formulas +# +# Many of the docstrings below refer to a numbered "node" in that +# book, e.g. §123 +# +# Note that (as TeX) y increases downward, unlike many other parts of +# matplotlib. - def height(self): - 'get the element height: ymax-ymin' - raise NotImplementedError('derived must override') +class MathTextWarning(Warning): + pass + +class Node(object): + """A node in a linked list. + §133 + """ + def __init__(self): + self.link = None + + def __repr__(self): + s = self.__internal_repr__() + if self.link: + s += ' ' + self.link.__repr__() + return s - def width(self): - 'get the element width: xmax-xmin' - raise NotImplementedError('derived must override') + def __internal_repr__(self): + return self.__class__.__name__ - def xmin(self): - 'get the xmin of ink rect' - raise NotImplementedError('derived must override') + def get_kerning(self, next): + return 0.0 + + def set_link(self, other): + self.link = other + + def render(self, x, y): + pass - def xmax(self): - 'get the xmax of ink rect' - raise NotImplementedError('derived must override') +class Box(Node): + """Represents any node with a physical location. + §135""" + def __init__(self, width, height, depth): + Node.__init__(self) + self.width = width + self.height = height + self.depth = depth + +class CharNode(Box): + """Represents a single character. Unlike TeX, the font + information and metrics are stored with each CharNode to make it + easier to lookup the font metrics when needed. Note that TeX + boxes have a width, height, and depth, unlike Type1 and Truetype + which use a full bounding box and an advance in the x-direction. + The metrics must be converted to the TeX way, and the advance (if + different from width) must be converted into a Kern node when the + CharNode is added to its parent Hlist. + §134""" + def __init__(self, c, state): + self.c = c + self.font_manager = state.font_manager + self.font = state.font + self.fontsize = state.fontsize + self.dpi = state.dpi + metrics = self._metrics = self.font_manager.get_metrics( + self.font, self.c, self.fontsize, self.dpi) + Box.__init__(self, metrics.width, metrics.iceberg, + -(metrics.iceberg - metrics.height)) + + def __internal_repr__(self): + return self.c - def ymin(self): - 'get the ymin of ink rect' - raise NotImplementedError('derived must override') + def get_kerning(self, next): + """Return the amount of kerning between this and the given + character. Called when characters are strung together into + Hlists to create Kern nodes.""" + # MGDTODO: Actually use kerning pairs + return self._metrics.advance - self.width + + def render(self, x, y): + """Render the character to the canvas""" + self.font_manager.render( + x, y, + self.font, self.c, self.fontsize, self.dpi) + +class List(Box): + """A list of nodes (either horizontal or vertical). + §135""" + def __init__(self, elements): + Box.__init__(self, 0., 0., 0.) + self.shift_amount = 0. # An arbitrary offset + self.list_head = None # The head of a linked list of Nodes in this box + # The following parameters are set in the vpack and hpack functions + self.glue_set = 0. # The glue setting of this list + self.glue_sign = 0 # 0: normal, -1: shrinking, 1: stretching + self.glue_order = 0 # The order of infinity (0 - 3) for the glue + + # Convert the Python list to a linked list + if len(elements): + elem = self.list_head = elements[0] + for next in elements[1:]: + elem.set_link(next) + elem = next - def ymax(self): - 'get the ymax of ink rect' - raise NotImplementedError('derived must override') + def __repr__(self): + s = '[' + self.__internal_repr__() + if self.list_head: + s += ' ' + self.list_head.__repr__() + s += ']' + if self.link: + s += ' ' + self.link.__repr__() + return s - def determine_font(self, font_stack): - 'a first pass to determine the font of this element (one of tt, it, rm , cal, bf, sf)' - raise NotImplementedError('derived must override') - - def set_font(self, font): - 'set the font (one of tt, it, rm, cal, bf, sf)' - raise NotImplementedError('derived must override') + def _determine_order(self, totals): + """A helper function to determine the highest order of glue + used by the members of this list. Used by vpack and hpack.""" + o = 0 + for i in range(len(totals), 0, -1): + if totals[i] != 0.0: + o = i + break + return o - def render(self): - 'render to the fonts canvas' - for element in self.neighbors.values(): - element.render() +class Hlist(List): + """A horizontal list of boxes. + §135""" + def __init__(self, elements, w=0., m='additional'): + List.__init__(self, elements) + self.do_kerning() + self.hpack(w, m) - def set_origin(self, ox, oy): - self.ox, self.oy = ox, oy + def do_kerning(self): + """Insert Kern nodes between CharNodes to set kerning. The + CharNodes themselves determine the amount of kerning they need + (in get_kerning), and this function just creates the linked + list in the correct way.""" + elem = self.list_head + while elem is not None: + next = elem.link + kerning_distance = elem.get_kerning(next) + if kerning_distance != 0.: + kern = Kern(kerning_distance) + elem.link = kern + kern.link = next + elem = next + + def hpack(self, w=0., m='additional'): + """The main duty of hpack is to compute the dimensions of the + resulting boxes, and to adjust the glue if one of those dimensions is + pre-specified. The computed sizes normally enclose all of the material + inside the new box; but some items may stick out if negative glue is + used, if the box is overfull, or if a \vbox includes other boxes that + have been shifted left. - # order matters! right needs to be evaled last - keys = ('above', 'below', 'subscript', 'superscript', 'right') - for loc in keys: - element = self.neighbors.get(loc) - if element is None: continue + w: specifies a width + m: is either 'exactly' or 'additional'. - if loc=='above': - nx = self.centerx() - element.width()/2.0 - ny = self.ymax() + self.pady() + (element.oy - element.ymax() + element.height()) - #print element, self.ymax(), element.height(), element.ymax(), element.ymin(), ny - elif loc=='below': - nx = self.centerx() - element.width()/2.0 - ny = self.ymin() - self.pady() - element.height() - elif loc=='superscript': - nx = self.xmax() - ny = self.ymax() - self.pady() - elif loc=='subscript': - nx = self.xmax() - ny = self.oy - 0.5*element.height() - elif loc=='right': - nx = self.ox + self.advance() - if self.neighbors.has_key('subscript'): - o = self.neighbors['subscript'] - nx = max(nx, o.ox + o.advance()) - if self.neighbors.has_key('superscript'): - o = self.neighbors['superscript'] - nx = max(nx, o.ox + o.advance()) + Thus, hpack(w, exactly) produces a box whose width is exactly w, while + hpack (w, additional ) yields a box whose width is the natural width + plus w. The default values produce a box with the natural width. + §644, §649""" + self.shift_amount = 0. + h = 0. + d = 0. + x = 0. + total_stretch = [0.] * 4 + total_shrink = [0.] * 4 + p = self.list_head + while p is not None: + # Layout characters in a tight inner loop (common case) + while isinstance(p, CharNode): + x += p.width + h = max(h, p.height) + d = max(d, p.depth) + p = p.link + if p is None: + break + + if isinstance(p, (List, Rule, Unset)): + x += p.width + if hasattr(p, 'shift_amount'): + s = p.shift_amount + else: + s = 0. + if p.height is not None and p.depth is not None: + h = max(h, p.height - s) + d = max(d, p.depth + s) + elif isinstance(p, Glue): + glue_spec = p.glue_spec + x += glue_spec.width + total_stretch[glue_spec.stretch_order] += glue_spec.stretch + total_shrink[glue_spec.shrink_order] += glue_spec.shrink + elif isinstance(p, Kern): + x += p.width + p = p.link + self.height = h + self.depth = d - ny = self.oy - element.set_origin(nx, ny) - - def set_size_info(self, fontsize, dpi): - self.fontsize = self._scale*fontsize - self.dpi = dpi - for loc, element in self.neighbors.items(): - if loc in ('subscript', 'superscript'): - element.set_size_info(0.7*self.fontsize, dpi) + if m == 'additional': + w += x + self.width = w + x = w - x + + if x == 0.: + self.glue_sign = 0 + self.glue_order = 0 + self.glue_ratio = 0. + return + if x > 0.: + o = self._determine_order(total_stretch) + self.glue_order = o + self.glue_sign = 1 + if total_stretch[o] != 0.: + self.glue_set = x / total_stretch[o] else: - element.set_size_info(self.fontsize, dpi) + self.glue_sign = 0 + self.glue_ratio = 0. + if o == 0: + if self.list_head is not None: + warn("Overfull hbox: %r" % self, MathTextWarning) + else: + o = self._determine_order(total_shrink) + self.glue_order = o + self.glue_sign = -1 + if total_shrink[o] != 0.: + self.glue_set = x / total_shrink[o] + else: + self.glue_sign = 0 + self.glue_ratio = 0. + if o == 0: + if self.list_head is not None: + warn("Underfull vbox: %r" % self, MathTextWarning) - def pady(self): - return self.dpi/72.0*self._pady +class Vlist(List): + """A vertical list of boxes. + §137""" + def __init__(self, elements, h=0., m='additional'): + List.__init__(self, elements) + self.vpack(h, m) - def padx(self): - return self.dpi/72.0*self._padx + def vpack(self, h=0., m='additional', l=float('inf')): + """The main duty of vpack is to compute the dimensions of the + resulting boxes, and to adjust the glue if one of those dimensions is + pre-specified. - def set_padx(self, pad): - 'set the y padding in points' - self._padx = pad + h: specifies a height + m: is either 'exactly' or 'additional'. + l: a maximum height - def set_pady(self, pad): - 'set the y padding in points' - self._pady = pad + Thus, vpack(h, exactly) produces a box whose width is exactly w, while + hpack (w, additional ) yields a box whose width is the natural width + plus w. The default values produce a box with the natural width. + §644, §668""" + self.shift_amount = 0. + w = 0. + d = 0. + x = 0. + total_stretch = [0.] * 4 + total_shrink = [0.] * 4 + p = self.list_head + while p is not None: + if isinstance(p, CharNode): + raise RuntimeError("Internal error in mathtext") + elif isinstance(p, (List, Rule, Unset)): + x += d + p.height + d = p.depth + if hasattr(p, 'shift_amount'): + s = p.shift_amount + else: + s = 0. + if p.width is not None: + w = max(w, p.width + s) + elif isinstance(p, Glue): + x += d + d = 0. + glue_spec = p.glue_spec + x += glue_spec.width + total_stretch[glue_spec.stretch_order] += glue_spec.stretch + total_shrink[glue_spec.shrink_order] += glue_spec.shrink + elif isinstance(p, Kern): + x += d + p.width + d = 0. + p = p.link - def set_scale(self, scale): - 'scale the element by scale' - self._scale = scale + self.width = w + if d > l: + x += d - l + self.depth = l + else: + self.depth = d - def centerx(self): - return 0.5 * (self.xmax() + self.xmin() ) + if m == 'additional': + h += x + self.height = h + x = h - x - def centery(self): - return 0.5 * (self.ymax() + self.ymin() ) + if x == 0: + self.glue_sign = 0 + self.glue_order = 0 + self.glue_ratio = 0. + return + if x > 0.: + o = self._determine_order(total_stretch) + self.glue_order = o + self.glue_sign = 1 + if total_stretch[o] != 0.: + self.glue_set = x / total_stretch[o] + else: + self.glue_sign = 0 + self.glue_ratio = 0. + if o == 0: + if self.list_head is not None: + warn("Overfull vbox: %r" % self, MathTextWarning) + else: + o = self._determine_order(total_shrink) + self.glue_order = o + self.glue_sign = -1 + if total_shrink[o] != 0.: + self.glue_set = x / total_shrink[o] + else: + self.glue_sign = 0 + self.glue_ratio = 0. + if o == 0: + if self.list_head is not None: + warn("Underfull vbox: %r" % self, MathTextWarning) + +class Rule(Box): + """A Rule node stands for a solid black rectangle; it has width, + depth, and height fields just as in an Hlist. However, if any of these + dimensions is None, the actual value will be determined by running the + rule up to the boundary of the innermost enclosing box. This is called + a “running dimension.” The width is never running in an Hlist; the + height and depth are never running in a Vlist. + §138""" + def __init__(self, width, height, depth, state): + Box.__init__(self, width, height, depth) + self.font_manager = state.font_manager + + def render(self, x, y, w, h): + self.font_manager.render_rect_filled(x, y, x + w, y + h) + +class Hrule(Rule): + """Convenience class to create a horizontal rule.""" + def __init__(self, state): + # MGDTODO: Get the line width from the font information + Rule.__init__(self, None, 0.5, 0.5, state) - def __repr__(self): - return str(self.__class__) + str(self.neighbors) +class Vrule(Rule): + """Convenience class to create a vertical rule.""" + def __init__(self, state): + # MGDTODO: Get the line width from the font information + Rule.__init__(self, 1.0, None, None, state) + +class Glue(Node): + """Most of the information in this object is stored in the underlying + GlueSpec class, which is shared between multiple glue objects. (This + is a memory optimization which probably doesn't matter anymore, but it's + easier to stick to what TeX does.) + §149, §152""" + def __init__(self, glue_type, copy=False): + Node.__init__(self) + self.glue_subtype = 'normal' + if is_string_like(glue_type): + glue_spec = GlueSpec.factory(glue_type) + elif isinstance(glue_type, GlueSpec): + glue_spec = glue_type + else: + raise ArgumentError("glue_type must be a glue spec name or instance.") + if copy: + glue_spec = glue_spec.copy() + self.glue_spec = glue_spec -class FontElement(Element): - def __init__(self, name): - Element.__init__(self) - self.name = name +class GlueSpec(object): + """§150, §151""" + def __init__(self, width=0., stretch=0., stretch_order=0, shrink=0., shrink_order=0): + self.width = width + self.stretch = stretch + self.stretch_order = stretch_order + self.shrink = shrink + self.shrink_order = shrink_order - def advance(self): - 'get the horiz advance' - return 0 + def copy(self): + return GlueSpec( + self.width, + self.stretch, + self.stretch_order, + self.shrink, + self.shrink_order) - def height(self): - 'get the element height: ymax-ymin' - return 0 + def factory(glue_type): + return self._types[glue_type] + factory = staticmethod(factory) + +GlueSpec._types = { + 'lineskip': GlueSpec(0, 0, 0, 0, 0) +} + +class Kern(Node): + """A Kern node has a width field to specify a (normally negative) + amount of spacing. This spacing correction appears in horizontal lists + between letters like A and V when the font designer said that it looks + better to move them closer together or further apart. A kern node can + also appear in a vertical list, when its ‘width ’ denotes additional + spacing in the vertical direction. + §155""" + def __init__(self, width, subtype='normal'): + Node.__init__(self) + self.width = width + self.subtype = subtype + +class Unset(Node): + pass - def width(self): - 'get the element width: xmax-xmin' - return 0 +# MGDTODO: Move this to cbook +def clamp(value, min, max): + if value < min: + return min + if value > max: + return max + return value - def xmin(self): - 'get the xmin of ink rect' - return 0 +class Ship(object): + """Since boxes can be inside of boxes inside of boxes, the main + work of Ship is done by two mutually recursive routines, hlist_out + and vlist_out , which traverse the Hlists and Vlists inside of + horizontal and vertical boxes. The global variables used in TeX to + store state as it processes have become member variables here. + §592.""" + def __call__(self, ox, oy, box): + self.max_push = 0 # Deepest nesting of push commands so far + self.cur_s = 0 + self.cur_v = 0. + self.cur_h = 0. + print box + self.off_h = ox + self.off_v = oy + box.height + self.hlist_out(box) - def xmax(self): - 'get the xmax of ink rect' - return 0 - - def ymin(self): - 'get the ymin of ink rect' - return 0 - - def ymax(self): - 'get the ymax of ink rect' - return 0 - - def determine_font(self, font_stack): - font_stack[-1] = self.name + def hlist_out(self, box): + cur_g = 0 + cur_glue = 0. + glue_order = box.glue_order + glue_sign = box.glue_sign + p = box.list_head + base_line = self.cur_v + left_edge = self.cur_h + self.cur_s += 1 + self.max_push = max(self.cur_s, self.max_push) - def set_font(self, font): - return + while p: + while isinstance(p, CharNode): + p.render(self.cur_h + self.off_h, self.cur_v + self.off_v) + self.cur_h += p.width + p = p.link + if p is None: + break + + if isinstance(p, List): + # §623 + if p.list_head is None: + self.cur_h += p.width + else: + edge = self.cur_h + self.cur_v = base_line + p.shift_amount + if isinstance(p, Hlist): + self.hlist_out(p) + else: + self.vlist_out(p) + self.cur_h = edge + p.width + self.cur_v = base_line + elif isinstance(p, Rule): + # §624 + rule_height = p.height + rule_depth = p.depth + rule_width = p.width + if rule_height is None: + rule_height = box.height + if rule_depth is None: + rule_depth = box.depth + if rule_height > 0 and rule_width > 0: + self.cur_v = baseline + rule_depth + p.render(self.cur_h + self.off_h, + self.cur_v + self.off_v, + rule_width, rule_height) + self.cur_v = baseline + self.cur_h += rule_width + elif isinstance(p, Glue): + # §625 + glue_spec = p.glue_spec + rule_width = glue_spec.width - cur_g + if g_sign != 0: # normal + if g_sign == 1: # stretching + if glue_spec.stretch_order == glue_order: + cur_glue += glue_spec.stretch + glue_temp = clamp(float(box.glue_set) * cur_glue, + 1000000000., -10000000000.) + cur_g = round(glue_temp) + elif glue_spec.shrink_order == glue_order: + cur_glue += glue_spec.shrink + glue_temp = clamp(float(box.glue_set) * cur_glue, + 1000000000., -10000000000.) + cur_g = round(glue_temp) + rule_width += cur_g + self.cur_h += rule_width + elif isinstance(p, Kern): + self.cur_h += p.width + p = p.link + self.cur_s -= 1 + + def vlist_out(self, box): + cur_g = 0 + cur_glue = 0. + glue_order = box.glue_order + glue_sign = box.glue_sign + p = box.list_head + self.cur_s += 1 + self.max_push = max(self.max_push, self.cur_s) + left_edge = self.cur_h + self.cur_v -= box.height + top_edge = self.cur_v + + while p: + if isinstance(p, CharNode): + raise RuntimeError("Internal error in mathtext") + elif isinstance(p, List): + if p.list_head is None: + self.cur_v += p.height + p.depth + else: + self.cur_v += p.height + self.cur_h = left_edge + p.shift_amount + save_v = self.cur_v + if isinstance(p, Hlist): + self.hlist_out(p) + else: + self.vlist_out(p) + self.cur_v = save_v + p.depth + self.cur_h = left_edge + elif isinstance(p, Rule): + rule_height = p.height + rule_depth = p.depth + rule_width = p.width + if rule_width is None: + rule_width = box.width + rule_height += rule_depth + if rule_height > 0 and rule_depth > 0: + self.cur_v += rule_height + p.render(self.cur_h + self.off_h, + self.cur_v + self.off_v, + rule_width, rule_height) + elif isinstance(p, Glue): + glue_spec = p.glue_spec + rule_height = glue_spec.width - cur_g + if g_sign != 0: # normal + if g_sign == 1: # stretching + if glue_spec.stretch_order == glue_order: + cur_glue += glue_spec.stretch + glue_temp = clamp(float(box.glue_set) * cur_glue, + 1000000000., -10000000000.) + cur_g = round(glue_temp) + elif glue_spec.shrink_order == glue_order: # shrinking + cur_glue += glue_spec.shrink + glue_temp = clamp(float(box.glue_set) * cur_glue, + 1000000000., -10000000000.) + cur_g = round(glue_temp) + rule_height += cur_g + self.cur_v += rule_height + elif isinstance(p, Kern): + self.cur_v += p.width + + p = p.link + self.cur_s -= 1 -class SpaceElement(Element): - 'blank horizontal space' - def __init__(self, space, height=0): - """ - space is the amount of blank space in fraction of fontsize - height is the height of the space in fraction of fontsize - """ - Element.__init__(self) - self.space = space - self._height = height +ship = Ship() - def advance(self): - 'get the horiz advance' - return self.dpi/72.0*self.space*self.fontsize +# TODO: Ligature nodes? (143) - def height(self): - 'get the element height: ymax-ymin' - return self._height*self.dpi/72.0*self.fontsize +# TODO: Unset box? - def width(self): - 'get the element width: xmax-xmin' - return self.advance() +############################################################################## +# NOADS - def xmin(self): - 'get the minimum ink in x' - return self.ox +class Noad: + def __init__(self, nucleus=None, subscr=None, superscr=None): + self.link = None + self.nucleus = nucleus + self.subscr = subscr + self.superscr = superscr - def xmax(self): - 'get the max ink in x' - return self.ox + self.advance() +class OrdNoad(Noad): + pass - def ymin(self): - 'get the minimum ink in y' - return self.oy +class OpNoad(Noad): + pass - def ymax(self): - 'get the max ink in y' - return self.oy + self.height() +class BinNoad(Noad): + pass - def determine_font(self, font_stack): - # space doesn't care about font, only size - for neighbor_type in ('above', 'below', 'subscript', 'superscript'): - neighbor = self.neighbors.get(neighbor_type) - if neighbor is not None: - neighbor.determine_font(font_stack) +class RelNoad(Noad): + pass - def set_font(self, font_stack): - # space doesn't care about font, only size - pass - -class SymbolElement(Element): - hardcoded_font = False +class OpenNoad(Noad): + pass - def __init__(self, sym): - Element.__init__(self) - self.sym = sym - self.kern = None - self.widthm = 1 # the width of an m; will be resized below +class CloseNoad(Noad): + pass - def determine_font(self, font_stack): - 'set the font (one of tt, it, rm, cal, bf, sf)' - self.set_font(font_stack[-1]) - for neighbor_type in ('above', 'below', 'subscript', 'superscript'): - neighbor = self.neighbors.get(neighbor_type) - if neighbor is not None: - neighbor.determine_font(font_stack) - - def set_font(self, font, hardcoded=False): - if hardcoded: - self.hardcoded_font = True - self.font = font - if not self.hardcoded_font: - assert not hasattr(self, 'font') - self.font = font - - def set_origin(self, ox, oy): - Element.set_origin(self, ox, oy) +class PunctNoad(Noad): + pass - def set_size_info(self, fontsize, dpi): - Element.set_size_info(self, fontsize, dpi) - self.metrics = Element.fonts.get_metrics( - self.font, self.sym, self.fontsize, dpi) +class InnerNoad(Noad): + pass - mmetrics = Element.fonts.get_metrics( - self.font, 'm', self.fontsize, dpi) - self.widthm = mmetrics.width - #print self.widthm +class RadicalNoad(Noad): + def __init__(self, nucleus=None, subscr=None, superscr=None, left_delim_font=None, left_delim_char=None): + Noad.__init__(self, nucleus, subscr, superscr) + self.left_delim = left_delim - def advance(self): - 'get the horiz advance' - if self.kern is None: - self.kern = 0 - if self.neighbors.has_key('right'): - sym = None - o = self.neighbors['right'] - if hasattr(o, 'sym'): - sym = o.sym - elif isinstance(o, SpaceElement): - sym = ' ' - if sym is not None: - self.kern = Element.fonts.get_kern( - self.font, self.sym, sym, self.fontsize, self.dpi) - return self.metrics.advance + self.kern - #return self.metrics.advance # how to handle cm units?+ self.kern*self.widthm +class NoadField: + def __init__(self): + pass +class MathChar(NoadField): + def __init__(self, char, font): + self.char = char + self.font = font - def height(self): - 'get the element height: ymax-ymin' - return self.metrics.height +class SubMlist(NoadField): + def __init__(self): + pass - def width(self): - 'get the element width: xmax-xmin' - return self.metrics.width +############################################################################## +# PARSER + +class Parser: + class State: + def __init__(self, font_manager, font, fontsize, dpi): + self.font_manager = font_manager + self.font = font + self.fontsize = fontsize + self.dpi = dpi - def xmin(self): - 'get the minimum ink in x' - return self.ox + self.metrics.xmin + def copy(self): + return Parser.State( + self.font_manager, + self.font, + self.fontsize, + self.dpi) + + def __init__(self): + # All forward declarations are here + font = Forward().setParseAction(self.font).setName("font") + latexfont = Forward().setParseAction(self.latexfont).setName("latexfont") + subsuper = Forward().setParseAction(self.subsuperscript).setName("subsuper") + overunder = Forward().setParseAction(self.overunder).setName("overunder") + placeable = Forward().setName("placeable") + simple = Forward().setName("simple") + self._expression = Forward().setParseAction(self.finish).setName("finish") - def xmax(self): - 'get the max ink in x' - return self.ox + self.metrics.xmax + lbrace = Literal('{').suppress().setParseAction(self.start_group).setName("start_group") + rbrace = Literal('}').suppress().setParseAction(self.end_group).setName("end_group") + lbrack = Literal('[') + rbrack = Literal(']') + lparen = Literal('(') + rparen = Literal(')') + grouping =(lbrack + | rbrack + | lparen + | rparen) - def ymin(self): - 'get the minimum ink in y' - return self.oy + self.metrics.ymin + bslash = Literal('\\') - def ymax(self): - 'get the max ink in y' - return self.oy + self.metrics.ymax + langle = Literal('<') + rangle = Literal('>') + equals = Literal('=') + relation =(langle + | rangle + | equals) - def render(self): - 'render to the fonts canvas' - Element.fonts.render( - self.ox, self.oy, - self.font, self.sym, self.fontsize, self.dpi) - Element.render(self) + colon = Literal(':') + comma = Literal(',') + period = Literal('.') + semicolon = Literal(';') + exclamation = Literal('!') + punctuation =(colon + | comma + | period + | semicolon) - def __repr__(self): - return self.sym + at = Literal('@') + percent = Literal('%') + ampersand = Literal('&') + misc =(exclamation + | at + | percent + | ampersand) -class AccentElement(SymbolElement): - pass + accent = oneOf("hat check dot breve acute ddot grave tilde bar vec " + "\" ` ' ~ . ^") -class GroupElement(Element): - """ - A group is a collection of elements - """ - def __init__(self, elements): - Element.__init__(self) - if not isinstance(elements, list): - elements = elements.asList() - self.elements = elements - for i in range(len(elements)-1): - self.elements[i].neighbors['right'] = self.elements[i+1] + function = oneOf("arccos csc ker min arcsin deg lg Pr arctan det lim sec " + "arg dim liminf sin cos exp limsup sinh cosh gcd ln sup " + "cot hom log tan coth inf max tanh") - def determine_font(self, font_stack): - 'set the font (one of tt, it, rm , cal)' - font_stack.append(font_stack[-1]) - for element in self.elements: - element.determine_font(font_stack) - for neighbor_type in ('above', 'below', 'subscript', 'superscript'): - neighbor = self.neighbors.get(neighbor_type) - if neighbor is not None: - neighbor.determine_font(font_stack) - font_stack.pop() + number = Combine(Word(nums) + Optional(Literal('.')) + Optional( Word(nums) )) - def set_font(self, font): - return + plus = Literal('+') + minus = Literal('-') + times = Literal('*') + div = Literal('/') + binop =(plus + | minus + | times + | div) - def set_size_info(self, fontsize, dpi): - if len(self.elements): - self.elements[0].set_size_info(self._scale*fontsize, dpi) - Element.set_size_info(self, fontsize, dpi) - #print 'set size' + fontname = oneOf("rm cal it tt sf bf") + latex2efont = oneOf("mathrm mathcal mathit mathtt mathsf mathbf") + texsym = Combine(bslash + Word(alphanums) + NotAny("{")) - def set_origin(self, ox, oy): - if len(self.elements): - self.elements[0].set_origin(ox, oy) - Element.set_origin(self, ox, oy) + char = Word(alphanums + ' ', exact=1).leaveWhitespace() + space =(FollowedBy(bslash) + + (Literal(r'\ ') + | Literal(r'\/') + | Group(Literal(r'\hspace{') + number + Literal('}')) + ) + ).setParseAction(self.space).setName('space') - def advance(self): - 'get the horiz advance' - if len(self.elements): - return self.elements[-1].xmax() - self.elements[0].ox - return 0 + symbol = Regex("(" + ")|(".join( + [ + r"\\[a-zA-Z0-9]+(?!{)", + r"[a-zA-Z0-9 ]", + r"[+\-*/]", + r"[<>=]", + r"[:,.;!]", + r"[!@%&]", + r"[[\]()]", + r"\\\$" + ]) + + ")" + ).setParseAction(self.symbol).leaveWhitespace() + _symbol =(texsym + | char + | binop + | relation + | punctuation + | misc + | grouping + ).setParseAction(self.symbol).leaveWhitespace() - def height(self): - 'get the element height: ymax-ymin' - ymax = max([e.ymax() for e in self.elements]) - ymin = min([e.ymin() for e in self.elements]) - return ymax-ymin + accent = Group( + Combine(bslash + accent) + + Optional(lbrace) + + symbol + + Optional(rbrace) + ).setParseAction(self.accent).setName("accent") - def width(self): - 'get the element width: xmax-xmin' - xmax = max([e.xmax() for e in self.elements]) - xmin = min([e.xmin() for e in self.elements]) - return xmax-xmin + function =(Suppress(bslash) + + function).setParseAction(self.function).setName("function") - def render(self): - 'render to the fonts canvas' - if len(self.elements): - self.elements[0].render() - Element.render(self) + group = Group( + lbrace + + OneOrMore( + simple + ) + + rbrace + ).setParseAction(self.group).setName("group") - def xmin(self): - 'get the minimum ink in x' - return min([e.xmin() for e in self.elements]) + font <<(Suppress(bslash) + + fontname) - def xmax(self): - 'get the max ink in x' - return max([e.xmax() for e in self.elements]) + latexfont << Group( + Suppress(bslash) + + latex2efont + + group) - def ymin(self): - 'get the minimum ink in y' - return max([e.ymin() for e in self.elements]) + frac = Group( + Suppress( + bslash + + Literal("frac") + ) + + group + + group + ).setParseAction(self.frac).setName("frac") - def ymax(self): - 'get the max ink in y' - return max([e.ymax() for e in self.elements]) + placeable <<(accent + ^ function + ^ symbol + ^ group + ^ frac + ) - def __repr__(self): - return 'Group: [ %s ]' % ' '.join([str(e) for e in self.elements]) + simple <<(space + | font + | latexfont + | overunder) -class MathGroupElement(GroupElement): - def determine_font(self, font_stack): - font_stack.append('it') - for element in self.elements: - element.determine_font(font_stack) - font_stack.pop() + subsuperop =(Literal("_") + | Literal("^") + ) -class NonMathGroupElement(GroupElement): - def determine_font(self, font_stack): - for element in self.elements: - element.determine_font(font_stack) - -class ExpressionElement(GroupElement): - """ - The entire mathtext expression - """ + subsuper << Group( + ( + placeable + + ZeroOrMore( + subsuperop + + subsuper + ) + ) + | (subsuperop + placeable) + ) - def __repr__(self): - return 'Expression: [ %s ]' % ' '.join([str(e) for e in self.elements]) + overunderop =( + ( Suppress(bslash) + + Literal(r"over") + ) + | ( Suppress(bslash) + + Literal(r"under") + ) + ) - def determine_font(self, font_stack): - GroupElement.determine_font(self, font_stack) + overunder << Group( + ( + subsuper + + ZeroOrMore( + overunderop + + overunder + ) + ) + ) -class Handler: - symbols = [] + math = OneOrMore( + simple + ).setParseAction(self.math).setName("math") - def clear(self): - self.symbols = [] + math_delim =(~bslash + + Literal('$')) - def expression(self, s, loc, toks): - #~ print "expr", toks - self.expr = ExpressionElement(toks) - return [self.expr] + non_math = Regex(r"(?:[^$]|(?:\\\$))*" + ).setParseAction(self.non_math).setName("non_math").leaveWhitespace() + self._expression <<( + non_math + + ZeroOrMore( + Suppress(math_delim) + + math + + Suppress(math_delim) + + non_math + ) + ) + + def parse(self, s, fonts_object, default_font, fontsize, dpi): + self._state_stack = [self.State(fonts_object, default_font, fontsize, dpi)] + self._expression.parseString(s) + return self._expr + + def get_state(self): + return self._state_stack[-1] + + def pop_state(self): + self._state_stack.pop() + + def push_state(self): + self._state_stack.append(self.get_state().copy()) + + def finish(self, s, loc, toks): + self._expr = Hlist(toks) + return [self._expr] + def math(self, s, loc, toks): - #~ print "math", toks - math = MathGroupElement(toks) - return [math] + hlist = Hlist(toks) + self.pop_state() + return [hlist] def non_math(self, s, loc, toks): #~ print "non_math", toks # This is a hack, but it allows the system to use the # proper amount of advance when going from non-math to math s = toks[0] + ' ' - symbols = [SymbolElement(c) for c in s] - self.symbols.extend(symbols) - non_math = NonMathGroupElement(symbols) - return [non_math] + symbols = [CharNode(c, self.get_state()) for c in s] + hlist = Hlist(symbols) + self.push_state() + self.get_state().font = 'it' + return [hlist] def space(self, s, loc, toks): assert(len(toks)==1) @@ -1335,36 +1820,40 @@ self.symbols.append(element) return [element] - def symbol(self, s, loc, toks): - assert(len(toks)==1) - #print "symbol", toks +# def symbol(self, s, loc, toks): +# assert(len(toks)==1) +# #print "symbol", toks - s = toks[0] - if charOverChars.has_key(s): - under, over, pad = charOverChars[s] - font, tok, scale = under - sym = SymbolElement(tok) - if font is not None: - sym.set_font(font, hardcoded=True) - sym.set_scale(scale) - sym.set_pady(pad) +# s = toks[0] +# if charOverChars.has_key(s): +# under, over, pad = charOverChars[s] +# font, tok, scale = under +# sym = SymbolElement(tok) +# if font is not None: +# sym.set_font(font, hardcoded=True) +# sym.set_scale(scale) +# sym.set_pady(pad) - font, tok, scale = over - sym2 = SymbolElement(tok) - if font is not None: - sym2.set_font(font, hardcoded=True) - sym2.set_scale(scale) +# font, tok, scale = over +# sym2 = SymbolElement(tok) +# if font is not None: +# sym2.set_font(font, hardcoded=True) +# sym2.set_scale(scale) - sym.neighbors['above'] = sym2 - self.symbols.append(sym2) - else: - sym = SymbolElement(toks[0]) - self.symbols.append(sym) +# sym.neighbors['above'] = sym2 +# self.symbols.append(sym2) +# else: +# sym = SymbolElement(toks[0], self.current_font) +# self.symbols.append(sym) - return [sym] +# return [sym] + def symbol(self, s, loc, toks): + return [CharNode(toks[0], self.get_state())] + + space = symbol + def accent(self, s, loc, toks): - assert(len(toks)==1) accent, sym = toks[0] @@ -1399,19 +1888,22 @@ symbols.append(sym) self.symbols.append(sym) return [GroupElement(symbols)] + + def start_group(self, s, loc, toks): + self.push_state() def group(self, s, loc, toks): - assert(len(toks)==1) - #print 'grp', toks - grp = GroupElement(toks[0]) + grp = Hlist(toks[0]) return [grp] + def end_group(self, s, loc, toks): + self.pop_state() + def font(self, s, loc, toks): assert(len(toks)==1) name = toks[0] - #print 'fontgrp', toks - font = FontElement(name) - return [font] + self.get_state().font = name + return [] def latexfont(self, s, loc, toks): assert(len(toks)==1) @@ -1468,189 +1960,26 @@ return [prev] def is_overunder(self, prev): - return isinstance(prev, SymbolElement) and overunder.has_key(prev.sym) + return isinstance(prev, SymbolElement) and overunder_symbols.has_key(prev.sym) -handler = Handler() + def frac(self, s, loc, toks): + assert(len(toks)==1) + assert(len(toks[0])==2) + #~ print 'subsuperscript', toks + + top, bottom = toks[0] + vlist = Vlist([bottom, Hrule(self.get_state()), top]) + # vlist.shift_amount = 8 + return [vlist] -# All forward declarations are here -font = Forward().setParseAction(handler.font).setName("font") -latexfont = Forward().setParseAction(handler.latexfont).setName("latexfont") -subsuper = Forward().setParseAction(handler.subsuperscript).setName("subsuper") -placeable = Forward().setName("placeable") -simple = Forward().setName("simple") -expression = Forward().setParseAction(handler.expression).setName("expression") + overunder = subsuperscript + -lbrace = Literal('{').suppress() -rbrace = Literal('}').suppress() -lbrack = Literal('[') -rbrack = Literal(']') -lparen = Literal('(') -rparen = Literal(')') -grouping =(lbrack - | rbrack - | lparen - | rparen) - -bslash = Literal('\\') - -langle = Literal('<') -rangle = Literal('>') -equals = Literal('=') -relation =(langle - | rangle - | equals) - -colon = Literal(':') -comma = Literal(',') -period = Literal('.') -semicolon = Literal(';') -exclamation = Literal('!') -punctuation =(colon - | comma - | period - | semicolon) - -at = Literal('@') -percent = Literal('%') -ampersand = Literal('&') -misc =(exclamation - | at - | percent - | ampersand) - -accent = oneOf("hat check dot breve acute ddot grave tilde bar vec " - "\" ` ' ~ . ^") - -function = oneOf("arccos csc ker min arcsin deg lg Pr arctan det lim sec " - "arg dim liminf sin cos exp limsup sinh cosh gcd ln sup " - "cot hom log tan coth inf max tanh") - -number = Combine(Word(nums) + Optional(Literal('.')) + Optional( Word(nums) )) - -plus = Literal('+') -minus = Literal('-') -times = Literal('*') -div = Literal('/') -binop =(plus - | minus - | times - | div) - -fontname = oneOf("rm cal it tt sf bf") -latex2efont = oneOf("mathrm mathcal mathit mathtt mathsf mathbf") - -texsym = Combine(bslash + Word(alphanums) + NotAny("{")) - -char = Word(alphanums + ' ', exact=1).leaveWhitespace() - -space =(FollowedBy(bslash) - + (Literal(r'\ ') - | Literal(r'\/') - | Group(Literal(r'\hspace{') + number + Literal('}')) - ) - ).setParseAction(handler.space).setName('space') - -symbol = Regex("(" + ")|(".join( - [ - r"\\[a-zA-Z0-9]+(?!{)", - r"[a-zA-Z0-9 ]", - r"[+\-*/]", - r"[<>=]", - r"[:,.;!]", - r"[!@%&]", - r"[[\]()]", - r"\\\$" - ]) - + ")" - ).setParseAction(handler.symbol).leaveWhitespace() - -_symbol =(texsym - | char - | binop - | relation - | punctuation - | misc - | grouping - ).setParseAction(handler.symbol).leaveWhitespace() - -accent = Group( - Combine(bslash + accent) - + Optional(lbrace) - + symbol - + Optional(rbrace) - ).setParseAction(handler.accent).setName("accent") - -function =(Suppress(bslash) - + function).setParseAction(handler.function).setName("function") - -group = Group( - lbrace - + OneOrMore( - simple - ) - + rbrace - ).setParseAction(handler.group).setName("group") - -font <<(Suppress(bslash) - + fontname) - -latexfont << Group( - Suppress(bslash) - + latex2efont - + group) - -placeable <<(accent - ^ function - ^ symbol - ^ group - ) - -simple <<(space - | font - | latexfont - | subsuper) - -subsuperop =(Literal("_") - | Literal("^") - | (Suppress(bslash) + Literal("under")) - | (Suppress(bslash) + Literal("over")) - ) - -subsuper << Group( - ( - placeable - + ZeroOrMore( - subsuperop - + subsuper - ) - ) - | (subsuperop + placeable) - ) - -math = OneOrMore( - simple - ).setParseAction(handler.math).setName("math") - -math_delim =(~bslash - + Literal('$')) - -non_math = Regex(r"(?:[^$]|(?:\\\$))*" - ).setParseAction(handler.non_math).setName("non_math").leaveWhitespace() - -expression <<( - non_math - + ZeroOrMore( - Suppress(math_delim) - + math - + Suppress(math_delim) - + non_math - ) - ) - #### +############################################################################## +# MAIN - class math_parse_s_ft2font_common: """ Parse the math expression s, return the (bbox, fonts) tuple needed @@ -1664,6 +1993,8 @@ if major==2 and minor1==2: raise SystemExit('mathtext broken on python2.2. We hope to get this fixed soon') + parser = None + def __init__(self, output): self.output = output self.cache = {} @@ -1676,27 +2007,20 @@ use_afm = False if self.output == 'SVG': - self.font_object = BakomaSVGFonts() - #self.font_object = MyUnicodeFonts(output='SVG') + font_manager = BakomaSVGFonts() elif self.output == 'Agg': - self.font_object = BakomaFonts() - #self.font_object = MyUnicodeFonts() + font_manager = BakomaFonts() elif self.output == 'PS': if rcParams['ps.useafm']: - self.font_object = StandardPSFonts() + font_manager = StandardPSFonts() use_afm = True else: - self.font_object = BakomaPSFonts() - #self.font_object = MyUnicodeFonts(output='PS') + font_manager = BakomaPSFonts() elif self.output == 'PDF': - self.font_object = BakomaPDFFonts() - Element.fonts = self.font_object + font_manager = BakomaPDFFonts() fontsize = prop.get_size_in_points() - handler.clear() - expression.parseString( s ) - if use_afm: fname = fontManager.findfont(prop, fontext='afm') default_font = AFM(file(fname, 'r')) @@ -1704,49 +2028,29 @@ else: fname = fontManager.findfont(prop) default_font = FT2Font(fname) + + if self.parser is None: + self.__class__.parser = Parser() + box = self.parser.parse(s, font_manager, default_font, fontsize, dpi) + w, h = box.width, box.height + box.depth + w += 4 + h += 4 + font_manager.set_canvas_size(w,h) - handler.expr.determine_font([default_font]) - handler.expr.set_size_info(fontsize, dpi) + ship(2, 2, box) - # set the origin once to allow w, h compution - handler.expr.set_origin(0, 0) - xmin = min([e.xmin() for e in handler.symbols]) - xmax = max([e.xmax() for e in handler.symbols]) - ymin = min([e.ymin() for e in handler.symbols]) - ymax = max([e.ymax() for e in handler.symbols]) - - # now set the true origin - doesn't affect with and height - w, h = xmax-xmin, ymax-ymin - # a small pad for the canvas size - w += 2 - h += 2 - - handler.expr.set_origin(0, h-ymax) - - if self.output in ('SVG', 'Agg'): - Element.fonts.set_canvas_size(w,h) - elif self.output == 'PS': - pswriter = StringIO() ... [truncated message content] |
From: <md...@us...> - 2007-07-20 15:47:56
|
Revision: 3594 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3594&view=rev Author: mdboom Date: 2007-07-20 08:47:53 -0700 (Fri, 20 Jul 2007) Log Message: ----------- Got glue working, as demonstrated by frac Modified Paths: -------------- branches/mathtext_mgd/lib/matplotlib/mathtext.py Modified: branches/mathtext_mgd/lib/matplotlib/mathtext.py =================================================================== --- branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-20 15:47:00 UTC (rev 3593) +++ branches/mathtext_mgd/lib/matplotlib/mathtext.py 2007-07-20 15:47:53 UTC (rev 3594) @@ -145,7 +145,8 @@ operatorPrecedence, opAssoc, ParseResults, Or, Suppress, oneOf from matplotlib.afm import AFM -from matplotlib.cbook import enumerate, iterable, Bunch, get_realpath_and_stat +from matplotlib.cbook import enumerate, iterable, Bunch, get_realpath_and_stat, \ + is_string_like from matplotlib.ft2font import FT2Font from matplotlib.font_manager import fontManager, FontProperties from matplotlib._mathtext_data import latex_to_bakoma, cmkern, \ @@ -694,7 +695,7 @@ assert len(self.fonts) font = self.fonts.values()[0] print "filled rect:", x1, y1, x2, y2 - font.font.draw_rect_filled(x1, y1, x2, y2) + font.font.draw_rect_filled(x1, y1, x2 - 1, y2 - 1) def _old_get_kern(self, font, symleft, symright, fontsize, dpi): """ @@ -1027,7 +1028,7 @@ elem = next def __repr__(self): - s = '[' + self.__internal_repr__() + s = '[' + self.__internal_repr__() + "%f %d %d " % (self.glue_set, self.glue_sign, self.glue_order) if self.list_head: s += ' ' + self.list_head.__repr__() s += ']' @@ -1039,7 +1040,7 @@ """A helper function to determine the highest order of glue used by the members of this list. Used by vpack and hpack.""" o = 0 - for i in range(len(totals), 0, -1): + for i in range(len(totals) - 1, 0, -1): if totals[i] != 0.0: o = i break @@ -1124,7 +1125,8 @@ w += x self.width = w x = w - x - + + print "total_stretch:", total_stretch if x == 0.: self.glue_sign = 0 self.glue_order = 0 @@ -1154,7 +1156,7 @@ if o == 0: if self.list_head is not None: warn("Underfull vbox: %r" % self, MathTextWarning) - + class Vlist(List): """A vertical list of boxes. §137""" @@ -1311,14 +1313,36 @@ self.shrink, self.shrink_order) - def factory(glue_type): - return self._types[glue_type] - factory = staticmethod(factory) + def factory(cls, glue_type): + return cls._types[glue_type] + factory = classmethod(factory) GlueSpec._types = { - 'lineskip': GlueSpec(0, 0, 0, 0, 0) + 'fil': GlueSpec(0., 1., 1, 0., 0), + 'fill': GlueSpec(0., 1., 2, 0., 0), + 'filll': GlueSpec(0., 1., 3, 0., 0) } - + +# Some convenient ways to get common kinds of glue + +class Fil(Glue): + def __init__(self): + Glue.__init__(self, 'fil') + +class Fill(Glue): + def __init__(self): + Glue.__init__(self, 'fill') + +class Filll(Glue): + def __init__(self): + Glue.__init__(self, 'filll') + +class HCentered(Hlist): + """A convenience class to create an Hlist whose contents are centered + within its enclosing box.""" + def __init__(self, elements): + Hlist.__init__(self, [Fill()] + elements + [Fill()]) + class Kern(Node): """A Kern node has a width field to specify a (normally negative) amount of spacing. This spacing correction appears in horizontal lists @@ -1334,15 +1358,7 @@ class Unset(Node): pass - -# MGDTODO: Move this to cbook -def clamp(value, min, max): - if value < min: - return min - if value > max: - return max - return value - + class Ship(object): """Since boxes can be inside of boxes inside of boxes, the main work of Ship is done by two mutually recursive routines, hlist_out @@ -1359,6 +1375,14 @@ self.off_h = ox self.off_v = oy + box.height self.hlist_out(box) + + def clamp(value): + if value < -1000000000.: + return -1000000000. + if value > 1000000000.: + return 1000000000. + return value + clamp = staticmethod(clamp) def hlist_out(self, box): cur_g = 0 @@ -1389,6 +1413,7 @@ if isinstance(p, Hlist): self.hlist_out(p) else: + p.vpack(box.height, 'exactly') self.vlist_out(p) self.cur_h = edge + p.width self.cur_v = base_line @@ -1412,18 +1437,14 @@ # §625 glue_spec = p.glue_spec rule_width = glue_spec.width - cur_g - if g_sign != 0: # normal - if g_sign == 1: # stretching + if glue_sign != 0: # normal + if glue_sign == 1: # stretching if glue_spec.stretch_order == glue_order: cur_glue += glue_spec.stretch - glue_temp = clamp(float(box.glue_set) * cur_glue, - 1000000000., -10000000000.) - cur_g = round(glue_temp) + cur_g = round(self.clamp(float(box.glue_set) * cur_glue)) elif glue_spec.shrink_order == glue_order: cur_glue += glue_spec.shrink - glue_temp = clamp(float(box.glue_set) * cur_glue, - 1000000000., -10000000000.) - cur_g = round(glue_temp) + cur_g = round(self.clamp(float(box.glue_set) * cur_glue)) rule_width += cur_g self.cur_h += rule_width elif isinstance(p, Kern): @@ -1453,7 +1474,9 @@ self.cur_v += p.height self.cur_h = left_edge + p.shift_amount save_v = self.cur_v + p.width = box.width if isinstance(p, Hlist): + p.hpack(box.width, 'exactly') self.hlist_out(p) else: self.vlist_out(p) @@ -1474,18 +1497,14 @@ elif isinstance(p, Glue): glue_spec = p.glue_spec rule_height = glue_spec.width - cur_g - if g_sign != 0: # normal - if g_sign == 1: # stretching + if glue_sign != 0: # normal + if glue_sign == 1: # stretching if glue_spec.stretch_order == glue_order: cur_glue += glue_spec.stretch - glue_temp = clamp(float(box.glue_set) * cur_glue, - 1000000000., -10000000000.) - cur_g = round(glue_temp) + cur_g = round(self.clamp(float(box.glue_set) * cur_glue)) elif glue_spec.shrink_order == glue_order: # shrinking cur_glue += glue_spec.shrink - glue_temp = clamp(float(box.glue_set) * cur_glue, - 1000000000., -10000000000.) - cur_g = round(glue_temp) + cur_g = round(self.clamp(float(box.glue_set) * cur_glue)) rule_height += cur_g self.cur_v += rule_height elif isinstance(p, Kern): @@ -1968,7 +1987,12 @@ #~ print 'subsuperscript', toks top, bottom = toks[0] - vlist = Vlist([bottom, Hrule(self.get_state()), top]) + vlist = Vlist([HCentered([top]), + Kern(4.0), + Hrule(self.get_state()), + Kern(4.0), + HCentered([bottom]) + ]) # vlist.shift_amount = 8 return [vlist] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |