Revision: 3610
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3610&view=rev
Author:   mdboom
Date:     2007-07-24 12:23:37 -0700 (Tue, 24 Jul 2007)

Log Message:
-----------
Back to feature-parity with old mathtext system (plus fractions and no
character clashes).

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-24 19:22:53 UTC 
(rev 3609)
+++ branches/mathtext_mgd/lib/matplotlib/mathtext.py    2007-07-24 19:23:37 UTC 
(rev 3610)
@@ -141,7 +141,7 @@
 from matplotlib import verbose
 from matplotlib.pyparsing import Literal, Word, OneOrMore, ZeroOrMore, \
      Combine, Group, Optional, Forward, NotAny, alphas, nums, alphanums, \
-     StringStart, StringEnd, ParseException, FollowedBy, Regex, \
+     StringStart, StringEnd, ParseFatalException, FollowedBy, Regex, \
      operatorPrecedence, opAssoc, ParseResults, Or, Suppress, oneOf
 
 from matplotlib.afm import AFM
@@ -154,6 +154,16 @@
 from matplotlib.numerix import absolute
 from matplotlib import get_data_path, rcParams
 
+####################
+# MGDTODO: Use rcParams for these
+SHRINK_FACTOR = 0.7
+NUM_SIZE_LEVELS = 3
+SUBDROP = 1.0
+SCRIPT_SPACE = 2.0
+SUP1 = 4.0
+SUB1 = 5.0
+DELTA = 1.0
+
 # symbols that have the sub and superscripts over/under
 overunder_symbols = {
     r'\sum'    : 1,
@@ -611,6 +621,9 @@
             self.fonts[basename] = cached_font
         return basename, cached_font
 
+    def get_font(self, font):
+        return self._get_font(font)[1].font
+    
     def get_fonts(self):
         return [x.font for x in self.fonts.values()]
         
@@ -668,11 +681,10 @@
             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
+            iceberg = glyph.horiBearingY/64.0 + offset
             )
-        
+
+        print glyph.vertBearingY/64.0, glyph.vertAdvance/65536.0
         self.glyphd[key] = basename, font, metrics, symbol_name, num, glyph, 
offset
         return self.glyphd[key]
 
@@ -694,8 +706,7 @@
     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 - 1, y2 - 1)
+        font.font.draw_rect_filled(x1, y1, max(x2 - 1, x1), max(y2 - 1, y1))
         
     def _old_get_kern(self, font, symleft, symright, fontsize, dpi):
         """
@@ -723,6 +734,22 @@
         basename, cached_font = self._get_font(font)
         pclt = cached_font.font.get_sfnt_table('pclt')
         return pclt['xHeight'] / 64.0 
+
+    def get_underline_thickness(self, font):
+        basename, cached_font = self._get_font(font)
+        return max(1.0, cached_font.font.underline_thickness / 64.0)
+
+    def get_kern(self, fontleft, symleft, fontsizeleft,
+                 fontright, symright, fontsizeright, dpi):
+        if fontsizeleft == fontsizeright:
+            basename, font1, metrics, symbol_name, num, glyph1, offset = \
+                self._get_info(fontleft, symleft, fontsizeleft, dpi)
+            basename, font2, metrics, symbol_name, num, glyph2, offset = \
+                self._get_info(fontright, symright, fontsizeright, dpi)
+            if font1 == font2:
+                basename, font = self._get_font(font1)
+                return font.font.get_kerning(glyph1, glyph2) / 64.0
+        return 0.0
     
 class BakomaPSFonts(BakomaFonts):
     """
@@ -929,11 +956,13 @@
 #    Typesetting math formulas
 #
 # Many of the docstrings below refer to a numbered "node" in that
-# book, e.g. §123
+# book, e.g. @123
 #
 # Note that (as TeX) y increases downward, unlike many other parts of
 # matplotlib.
 
+# MGDTODO: scale_factor is a non-TeX hack
+    
 class MathTextWarning(Warning):
     pass
     
@@ -943,6 +972,7 @@
     """
     def __init__(self):
         self.link = None
+        self.size = 0
         
     def __repr__(self):
         s = self.__internal_repr__()
@@ -955,62 +985,127 @@
 
     def get_kerning(self, next):
         return 0.0
-    
+
     def set_link(self, other):
         self.link = other
-    
+
+    def pack(self):
+        if self.link:
+            self.link.pack()
+
+    def shrink(self):
+        """Shrinks one level smaller.  There are only three levels of sizes,
+        after which things will no longer get smaller."""
+        if self.link:
+            self.link.shrink()
+        self.size += 1
+            
     def render(self, x, y):
         pass
 
 class Box(Node):
     """Represents any node with a physical location.
-    §135"""
+    @135"""
     def __init__(self, width, height, depth):
         Node.__init__(self)
         self.width        = width
         self.height       = height
         self.depth        = depth
+
+    def shrink(self):
+        Node.shrink(self)
+        if self.size < NUM_SIZE_LEVELS:
+            if self.width is not None:
+                self.width        *= SHRINK_FACTOR
+            if self.height is not None:
+                self.height       *= SHRINK_FACTOR
+            if self.depth is not None:
+                self.depth        *= SHRINK_FACTOR
+
+    def render(self, x1, y1, x2, y2):
+        pass
+
+class Vbox(Box):
+    def __init__(self, height, depth):
+        Box.__init__(self, 0., height, depth)
+
+class Hbox(Box):
+    def __init__(self, width):
+        Box.__init__(self, width, 0., 0.)
     
-class CharNode(Box):
+class Char(Node):
     """Represents a single character.  Unlike TeX, the font
-    information and metrics are stored with each CharNode to make it
+    information and metrics are stored with each Char 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"""
+    Char is added to its parent Hlist.
+    @134"""
     def __init__(self, c, state):
+        Node.__init__(self)
         self.c = c
         self.font_manager = state.font_manager
         self.font = state.font
         self.fontsize = state.fontsize
         self.dpi = state.dpi
+        # The real width, height and depth will be set during the
+        # pack phase, after we know the real fontsize
+        self._update_metrics()
+        
+    def __internal_repr__(self):
+        return repr(self.c)
+
+    def _update_metrics(self):
         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))
+        print self.c, metrics.height, metrics.ymax, metrics.ymin, 
metrics.iceberg
+        self.width = metrics.width
+        self.height = metrics.iceberg
+        self.depth = -(metrics.iceberg - metrics.height)
         
-    def __internal_repr__(self):
-        return self.c
-
     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
+        advance = self._metrics.advance - self.width
+        kern = 0.
+        #if isinstance(next, Char):
+        #    kern = self.font_manager.get_kern(self.font, self.c, 
self.fontsize, next.font, next.c, next.fontsize, self.dpi)
+        return advance + kern
     
     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)
+
+    def shrink(self):
+        Node.shrink(self)
+        if self.size < NUM_SIZE_LEVELS:
+            self.fontsize *= SHRINK_FACTOR
+            self._update_metrics()
         
+class Accent(Char):
+    """The font metrics need to be dealt with differently for accents."""
+    def _update_metrics(self):
+        metrics = self._metrics = self.font_manager.get_metrics(
+            self.font, self.c, self.fontsize, self.dpi)
+        self.width = metrics.width
+        self.height = metrics.ymax - metrics.ymin
+        self.depth = 0
+
+    def render(self, x, y):
+        """Render the character to the canvas"""
+        self.font_manager.render(
+            x, y + (self._metrics.ymax - self.height),
+            self.font, self.c, self.fontsize, self.dpi)
+        
 class List(Box):
     """A list of nodes (either horizontal or vertical).
-    §135"""
+    @135"""
     def __init__(self, elements):
         Box.__init__(self, 0., 0., 0.)
         self.shift_amount = 0.   # An arbitrary offset
@@ -1028,7 +1123,7 @@
                 elem = next
 
     def __repr__(self):
-        s = '[' + self.__internal_repr__() + "%f %d %d " % (self.glue_set, 
self.glue_sign, self.glue_order)
+        s = '[' + self.__internal_repr__() + " <%d %d %d %d> " % (self.width, 
self.height, self.depth, self.shift_amount)
         if self.list_head:
             s += ' ' + self.list_head.__repr__()
         s += ']'
@@ -1045,18 +1140,39 @@
                 o = i
                 break
         return o
-    
+
+    def _set_glue(self, x, sign, totals, error_type):
+        o = self._determine_order(totals)
+        self.glue_order = o
+        self.glue_sign = sign
+        if totals[o] != 0.:
+            self.glue_set = x / totals[o]
+        else:
+            self.glue_sign = 0
+            self.glue_ratio = 0.
+        if o == 0:
+            if self.list_head is not None:
+                warn("%s %s: %r" % (error_type, self.__class__.__name__, self),
+                     MathTextWarning)
+
+    def shrink(self):
+        if self.list_head:
+            self.list_head.shrink()
+        Box.shrink(self)
+        if self.size < NUM_SIZE_LEVELS:
+            self.shift_amount *= SHRINK_FACTOR
+
 class Hlist(List):
     """A horizontal list of boxes.
-    §135"""
+    @135"""
     def __init__(self, elements, w=0., m='additional'):
         List.__init__(self, elements)
-        self.do_kerning()
-        self.hpack(w, m)
+        self.kern()
+        self.hpack()
 
-    def do_kerning(self):
-        """Insert Kern nodes between CharNodes to set kerning.  The
-        CharNodes themselves determine the amount of kerning they need
+    def kern(self):
+        """Insert Kern nodes between Chars to set kerning.  The
+        Chars 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
@@ -1068,7 +1184,13 @@
                 elem.link = kern
                 kern.link = next
             elem = next
-        
+
+    def pack(self):
+        if self.list_head:
+            self.list_head.pack()
+        self.hpack()
+        Node.pack(self)
+            
     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
@@ -1083,8 +1205,10 @@
         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.
+        @644, @649"""
+        # I don't know why these get reset in TeX.  Shift_amount is pretty
+        # much useless if we do.
+        #self.shift_amount = 0.
         h = 0.
         d = 0.
         x = 0.
@@ -1093,21 +1217,18 @@
         p = self.list_head
         while p is not None:
             # Layout characters in a tight inner loop (common case)
-            while isinstance(p, CharNode):
+            while isinstance(p, Char):
                 x += p.width
                 h = max(h, p.height)
                 d = max(d, p.depth)
-                p = p.link
+                p = p.link # Go to next node in list
             if p is None:
                 break
             
-            if isinstance(p, (List, Rule, Unset)):
+            if isinstance(p, (Box, 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:
+                    s = getattr(p, 'shift_amount', 0.)
                     h = max(h, p.height - s)
                     d = max(d, p.depth + s)
             elif isinstance(p, Glue):
@@ -1117,7 +1238,7 @@
                 total_shrink[glue_spec.shrink_order] += glue_spec.shrink
             elif isinstance(p, Kern):
                 x += p.width
-            p = p.link
+            p = p.link # Go to next node in list
         self.height = h
         self.depth = d
 
@@ -1126,44 +1247,29 @@
         self.width = w
         x = w - x
 
-        print "total_stretch:", total_stretch
         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 hbox: %r" % self, MathTextWarning)
+            self._set_glue(x, 1, total_stretch, "Overfull")
         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)
+            self._set_glue(x, -1, total_shrink, "Underfull")
                     
 class Vlist(List):
     """A vertical list of boxes.
-    §137"""
+    @137"""
     def __init__(self, elements, h=0., m='additional'):
         List.__init__(self, elements)
-        self.vpack(h, m)
+        self.vpack()
 
+    def pack(self):
+        if self.list_head:
+            self.list_head.pack()
+        self.vpack()
+        Node.pack(self)
+        
     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
@@ -1174,10 +1280,12 @@
         l: a maximum height
 
         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
+        vpack(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.
+        @644, @668"""
+        # I don't know why these get reset in TeX.  Shift_amount is pretty
+        # much useless if we do.
+        # self.shift_amount = 0.
         w = 0.
         d = 0.
         x = 0.
@@ -1185,16 +1293,13 @@
         total_shrink = [0.] * 4
         p = self.list_head
         while p is not None:
-            if isinstance(p, CharNode):
+            if isinstance(p, Char):
                 raise RuntimeError("Internal error in mathtext")
-            elif isinstance(p, (List, Rule, Unset)):
+            elif isinstance(p, (Box, 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:
+                    s = getattr(p, 'shift_amount', 0.)
                     w = max(w, p.width + s)
             elif isinstance(p, Glue):
                 x += d
@@ -1225,30 +1330,11 @@
             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)
+            self._set_glue(x, 1, total_stretch, "Overfull")
         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)
+            self._set_glue(x, -1, total_shrink, "Underfull")
                     
 class Rule(Box):
     """A Rule node stands for a solid black rectangle; it has width,
@@ -1257,7 +1343,7 @@
     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"""
+    @138"""
     def __init__(self, width, height, depth, state):
         Box.__init__(self, width, height, depth)
         self.font_manager = state.font_manager
@@ -1268,21 +1354,22 @@
 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)
+        thickness = state.font_manager.get_underline_thickness(state.font)
+        height = depth = thickness * 0.5
+        Rule.__init__(self, None, height, depth, state)
 
 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)
+        thickness = state.font_manager.get_underline_thickness(state.font)
+        Rule.__init__(self, thickness, 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"""
+    @149, @152"""
     def __init__(self, glue_type, copy=False):
         Node.__init__(self)
         self.glue_subtype   = 'normal'
@@ -1297,7 +1384,7 @@
         self.glue_spec      = glue_spec
 
 class GlueSpec(object):
-    """§150, §151"""
+    """@150, @151"""
     def __init__(self, width=0., stretch=0., stretch_order=0, shrink=0., 
shrink_order=0):
         self.width         = width
         self.stretch       = stretch
@@ -1318,9 +1405,14 @@
     factory = classmethod(factory)
 
 GlueSpec._types = {
-    'fil':     GlueSpec(0., 1., 1, 0., 0),
-    'fill':     GlueSpec(0., 1., 2, 0., 0),
-    'filll':     GlueSpec(0., 1., 3, 0., 0)
+    'fil':         GlueSpec(0., 1., 1, 0., 0),
+    'fill':        GlueSpec(0., 1., 2, 0., 0),
+    'filll':       GlueSpec(0., 1., 3, 0., 0),
+    'neg_fil':     GlueSpec(0., 0., 0, 1., 1),
+    'neg_fill':    GlueSpec(0., 0., 0, 1., 2),
+    'neg_filll':   GlueSpec(0., 0., 0, 1., 3),
+    'empty':       GlueSpec(0., 0., 0, 0., 0),
+    'ss':          GlueSpec(0., 1., 1, -1., 1)
 }
 
 # Some convenient ways to get common kinds of glue
@@ -1337,11 +1429,38 @@
     def __init__(self):
         Glue.__init__(self, 'filll')
 
+class NegFil(Glue):
+    def __init__(self):
+        Glue.__init__(self, 'neg_fil')
+
+class NegFill(Glue):
+    def __init__(self):
+        Glue.__init__(self, 'neg_fill')
+
+class NegFilll(Glue):
+    def __init__(self):
+        Glue.__init__(self, 'neg_filll')
+        
+class FixedGlue(Glue):
+    def __init__(self, width):
+        Glue.__init__(self, 'empty', copy=True)
+        self.glue_spec.width = width
+
+class SsGlue(Glue):
+    def __init__(self):
+        Glue.__init__(self, 'ss')
+        
 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()])
+        Hlist.__init__(self, [SsGlue()] + elements + [SsGlue()])
+
+class VCentered(Hlist):
+    """A convenience class to create an Hlist whose contents are centered
+    within its enclosing box."""
+    def __init__(self, elements):
+        Vlist.__init__(self, [Fill()] + elements + [Fill()])
         
 class Kern(Node):
     """A Kern node has a width field to specify a (normally negative)
@@ -1350,28 +1469,123 @@
     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'):
+    @155"""
+    def __init__(self, width):
         Node.__init__(self)
         self.width = width
-        self.subtype = subtype
 
+    def shrink(self):
+        Node.shrink(self)
+        if self.size < NUM_SIZE_LEVELS:
+            self.width *= SHRINK_FACTOR
+        
 class Unset(Node):
     pass
 
+class SubSuperCluster(Hlist):
+    """This class is a sort of hack to get around that fact that this
+    code doesn't parse to an mlist and then an hlist, but goes directly
+    to hlists.  This lets us store enough information in the hlist itself,
+    namely the nucleas, sub- and super-script, such that if another script
+    follows that needs to be attached, it can be reconfigured on the fly."""
+    def __init__(self):
+        self.nucleus = None
+        self.sub = None
+        self.super = None
+        Hlist.__init__(self, [])
+
+    def is_overunder(self):
+        if isinstance(self.nucleus, Char):
+            return overunder_symbols.has_key(self.nucleus.c)
+        return False
+        
+    def reconfigure(self, state):
+        """Lays out the nucleus, subscript and superscript, with
+        either the subscript or superscript being optional.
+        @756"""
+        rule_thickness = state.font_manager.get_underline_thickness(state.font)
+        xHeight = state.font_manager.get_xheight(state.font)
+
+        if self.nucleus is None:
+            raise ParseError("Internal mathtext error.  No nucleus in 
sub/superscript cluster.")
+        
+        if self.super is None and self.sub is None:
+            self.list_head = self.nucleus
+            return
+
+        if self.is_overunder():
+            vlist = []
+            shift = 0.
+            width = max(self.super.width, self.nucleus.width, self.sub.width)
+            if self.super is not None:
+                hlist = HCentered([self.super])
+                hlist.hpack(width, 'exactly')
+                vlist.extend([hlist, FixedGlue(rule_thickness * 2.0)])
+            hlist = HCentered([self.nucleus])
+            hlist.hpack(width, 'exactly')
+            vlist.append(hlist)
+            if self.sub is not None:
+                hlist = HCentered([self.sub])
+                hlist.hpack(width, 'exactly')
+                vlist.extend([FixedGlue(rule_thickness), hlist])
+                shift = hlist.height + hlist.depth + rule_thickness * 2.0
+            x = Vlist(vlist)
+            x.shift_amount = shift
+            self.list_head = x
+            self.hpack()
+            return
+        
+        p = Hlist([self.nucleus])
+        p.hpack()
+        shift_up = p.height - SUBDROP
+        shift_down = p.depth + SUBDROP
+        if self.super is None:
+            # @757
+            x = Hlist([self.sub])
+            x.width += SCRIPT_SPACE
+            shift_down = max(shift_down, SUB1)
+            clr = x.height - (abs(xHeight * 4.0) / 5.0)
+            shift_down = max(shift_down, clr)
+            x.shift_amount = shift_down
+        else:
+            x = Hlist([self.super])
+            x.width += SCRIPT_SPACE
+            clr = SUP1
+            shift_up = max(shift_up, SUP1)
+            clr = x.depth + (abs(xHeight) / 4.0)
+            shift_up = max(shift_up, clr)
+            if self.sub is None:
+                x.shift_amount = -shift_up
+            else: # Both sub and superscript
+                y = Hlist([self.sub])
+                y.width += SCRIPT_SPACE
+                shift_down = max(shift_down, SUB1)
+                clr = 4.0 * rule_thickness - ((shift_up - x.depth) - (y.height 
- shift_down))
+                if clr > 0.:
+                    shift_up += clr
+                    shift_down += clr
+                x.shift_amount = DELTA
+                x = Vlist([x,
+                           Kern((shift_up - x.depth) - (y.height - 
shift_down)),
+                           y])
+                x.shift_amount = shift_down
+
+        self.list_head = p
+        p.link = x
+        self.hpack()
+        
 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."""
+    @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)
@@ -1396,7 +1610,7 @@
         self.max_push = max(self.cur_s, self.max_push)
 
         while p:
-            while isinstance(p, CharNode):
+            while isinstance(p, Char):
                 p.render(self.cur_h + self.off_h, self.cur_v + self.off_v)
                 self.cur_h += p.width
                 p = p.link
@@ -1404,7 +1618,7 @@
                 break
                 
             if isinstance(p, List):
-                # §623
+                # @623
                 if p.list_head is None:
                     self.cur_h += p.width
                 else:
@@ -1413,12 +1627,12 @@
                     if isinstance(p, Hlist):
                         self.hlist_out(p)
                     else:
-                        p.vpack(box.height, 'exactly')
+                        # p.vpack(box.height + box.depth, 'exactly')
                         self.vlist_out(p)
                     self.cur_h = edge + p.width
                     self.cur_v = base_line
-            elif isinstance(p, Rule):
-                # §624
+            elif isinstance(p, Box):
+                # @624
                 rule_height = p.height
                 rule_depth  = p.depth
                 rule_width  = p.width
@@ -1434,7 +1648,7 @@
                     self.cur_v = baseline
                 self.cur_h += rule_width
             elif isinstance(p, Glue):
-                # §625
+                # @625
                 glue_spec = p.glue_spec
                 rule_width = glue_spec.width - cur_g
                 if glue_sign != 0: # normal
@@ -1465,7 +1679,7 @@
         top_edge      = self.cur_v
 
         while p:
-            if isinstance(p, CharNode):
+            if isinstance(p, Char):
                 raise RuntimeError("Internal error in mathtext")
             elif isinstance(p, List):
                 if p.list_head is None:
@@ -1476,13 +1690,12 @@
                     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)
                     self.cur_v = save_v + p.depth
                     self.cur_h = left_edge
-            elif isinstance(p, Rule):
+            elif isinstance(p, Box):
                 rule_height = p.height
                 rule_depth = p.depth
                 rule_width = p.width
@@ -1523,7 +1736,7 @@
 # NOADS
 
 class Noad:
-    def __init__(self, nucleus=None, subscr=None, superscr=None):
+    def __init__(self):
         self.link = None
         self.nucleus = nucleus
         self.subscr = subscr
@@ -1554,26 +1767,46 @@
     pass
 
 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 __init__(self):
+        Noad.__init__(self)
+        self.left_delim = None
 
-class NoadField:
+class FractionNoad(Noad):
     def __init__(self):
-        pass
+        Noad.__init__(self)
+        self.num         = None
+        self.denom       = None
+        self.thickness   = None
+        self.left_delim  = None
+        self.right_delim = None
+        
+class UnderNoad(Noad):
+    pass
 
-class MathChar(NoadField):
-    def __init__(self, char, font):
-        self.char = char
-        self.font = font
+class OverNoad(Noad):
+    pass
 
-class SubMlist(NoadField):
+class AccentNoad(Noad):
     def __init__(self):
-        pass
+        Noad__init__(self)
+        self.accent = None
 
+class VCenterNoad(Noad):
+    pass
+
+class LeftNoad(Noad):
+    pass
+
+class RightNoad(Noad):
+    pass
+
+class StyleNoad(Noad):
+    def __init__(self, subtype):
+        self.subtype = subtype
+
 ##############################################################################
 # PARSER
-    
+
 class Parser:
     class State:
         def __init__(self, font_manager, font, fontsize, dpi):
@@ -1820,94 +2053,73 @@
         #~ 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 = [CharNode(c, self.get_state()) for c in s]
+        symbols = [Char(c, self.get_state()) for c in toks[0]]
         hlist = Hlist(symbols)
         self.push_state()
+        # We're going into math now, so set font to 'it'
         self.get_state().font = 'it'
         return [hlist]
     
     def space(self, s, loc, toks):
         assert(len(toks)==1)
-
+        state = self.get_state()
+        metrics = state.font_manager.get_metrics(
+            state.font, 'm', state.fontsize, state.dpi)
+        em = metrics.width
+        
         if toks[0]==r'\ ': num = 0.30 # 30% of fontsize
         elif toks[0]==r'\/': num = 0.1 # 10% of fontsize
         else:  # vspace
             num = float(toks[0][1]) # get the num out of \hspace{num}
 
-        element = SpaceElement(num)
-        self.symbols.append(element)
-        return [element]
+        box = Hbox(num * em)
+        return [box]
 
-#     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)
-
-#             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.current_font)
-#         self.symbols.append(sym)
-
-#         return [sym]
-
     def symbol(self, s, loc, toks):
-        return [CharNode(toks[0], self.get_state())]
+        return [Char(toks[0], self.get_state())]
 
-    space = symbol
+    _accent_map = {
+        r'\hat'   : r'\circumflexaccent',
+        r'\breve' : r'\combiningbreve',
+        r'\bar'   : r'\combiningoverline',
+        r'\grave' : r'\combininggraveaccent',
+        r'\acute' : r'\combiningacuteaccent',
+        r'\ddot'  : r'\combiningdiaeresis',
+        r'\tilde' : r'\combiningtilde',
+        r'\dot'   : r'\combiningdotabove',
+        r'\vec'   : r'\combiningrightarrowabove',
+        r'\"'     : r'\combiningdiaeresis',
+        r"\`"     : r'\combininggraveaccent',
+        r"\'"     : r'\combiningacuteaccent',
+        r'\~'     : r'\combiningtilde',
+        r'\.'     : r'\combiningdotabove',
+        r'\^'     : r'\circumflexaccent',
+        }
     
     def accent(self, s, loc, toks):
         assert(len(toks)==1)
+        state = self.get_state()
+        thickness = state.font_manager.get_underline_thickness(state.font)
         accent, sym = toks[0]
+        accent = Accent(self._accent_map[accent], self.get_state())
+        centered = HCentered([accent])
+        centered.hpack(sym.width, 'exactly')
+        centered.shift_amount = accent._metrics.xmin
+        return Vlist([
+                centered,
+                FixedGlue(thickness * 2.0),
+                Hlist([sym])
+                ])
 
-        d = {
-            r'\hat'   : r'\circumflexaccent',
-            r'\breve' : r'\combiningbreve',
-            r'\bar'   : r'\combiningoverline',
-            r'\grave' : r'\combininggraveaccent',
-            r'\acute' : r'\combiningacuteaccent',
-            r'\ddot'  : r'\combiningdiaeresis',
-            r'\tilde' : r'\combiningtilde',
-            r'\dot'   : r'\combiningdotabove',
-            r'\vec'   : r'\combiningrightarrowabove',
-            r'\"'     : r'\combiningdiaeresis',
-            r"\`"     : r'\combininggraveaccent',
-            r"\'"     : r'\combiningacuteaccent',
-            r'\~'     : r'\combiningtilde',
-            r'\.'     : r'\combiningdotabove',
-            r'\^'   : r'\circumflexaccent',
-             }
-        above = AccentElement(d[accent])
-        sym.neighbors['above'] = above
-        sym.set_pady(1)
-        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)]
-
+        self.push_state()
+        state = self.get_state()
+        state.font = 'rm'
+        hlist = Hlist([Char(c, state) for c in toks[0]])
+        self.pop_state()
+        return hlist
+        
     def start_group(self, s, loc, toks):
         self.push_state()
     
@@ -1925,27 +2137,17 @@
         return []
 
     def latexfont(self, s, loc, toks):
+        # MGDTODO: Not really working
         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']
-    }
-
-    _subsuperscript_indices = {
-        '_'      : ('normal', (0, 1)),
-        '^'      : ('normal', (1, 0)),
-        'over'   : ('overUnder', (0, 1)),
-        'under'  : ('overUnder', (1, 0))
-    }
-         
     def subsuperscript(self, s, loc, toks):
         assert(len(toks)==1)
         #~ print 'subsuperscript', toks
@@ -1955,45 +2157,68 @@
         if len(toks[0]) == 3:
             prev, op, next = toks[0]
         elif len(toks[0]) == 2:
-            prev = SpaceElement(0)
+            prev = Hbox(0.)
             op, next = toks[0]
         else:
-            raise ParseException("Unable to parse subscript/superscript 
construct.")
+            raise ParseFatalException("Unable to parse subscript/superscript 
construct.")
 
-        relation_type, (index, other_index) = self._subsuperscript_indices[op]
-        if self.is_overunder(prev):
-            relation_type = 'overUnder'
-        names = self._subsuperscript_names[relation_type]
+        # Handle the case of double scripts
+        if isinstance(next, SubSuperCluster):
+            x = next
+            if op == '_':
+                if next.sub is not None:
+                    raise ParseFatalException("Double subscript")
+                x.sub = x.nucleus
+                x.sub.shrink()
+                x.sub.pack()
+                x.nucleus = prev
+            elif op == '^':
+                if next.super is not None:
+                    raise ParseFatalException("Double superscript")
+                x.super = x.nucleus
+                x.super.shrink()
+                x.super.pack()
+                x.nucleus = prev
+        else:
+            x = SubSuperCluster()
+            x.nucleus = prev
+            if op == '_':
+                x.sub = next
+                x.sub.shrink()
+                x.sub.pack()
+            else:
+                x.super = next
+                x.super.shrink()
+                x.super.pack()
+        x.reconfigure(self.get_state())
 
-        prev.neighbors[names[index]] = next
+        return [x]
 
-        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]]
-            elif compound[index] in next.neighbors:
-                raise ValueError(
-                    "Double %ss" %
-                    self._subsuperscript_names['normal'][index])
-        return [prev]
-
-    def is_overunder(self, prev):
-        return isinstance(prev, SymbolElement) and 
overunder_symbols.has_key(prev.sym)
-
     def frac(self, s, loc, toks):
         assert(len(toks)==1)
         assert(len(toks[0])==2)
-        #~ print 'subsuperscript', toks
-        
-        top, bottom = toks[0]
-        vlist = Vlist([HCentered([top]),
-                       Kern(4.0),
+        num, den = toks[0]
+        num.shrink()
+        den.shrink()
+        cnum = HCentered([num])
+        cden = HCentered([den])
+        width = max(num.width, den.height)
+        cnum.hpack(width, 'exactly')
+        cden.hpack(width, 'exactly')
+        state = self.get_state()
+        thickness = state.font_manager.get_underline_thickness(state.font)
+        space = thickness * 3.0
+        vlist = Vlist([cnum,
+                       FixedGlue(thickness * 2.0),
                        Hrule(self.get_state()),
-                       Kern(4.0),
-                       HCentered([bottom])
+                       FixedGlue(thickness * 3.0),
+                       cden
                        ])
-        # vlist.shift_amount = 8
+
+        metrics = state.font_manager.get_metrics(
+            state.font, '=', state.fontsize, state.dpi)
+        shift = cden.height - (metrics.ymax + metrics.ymin) / 2 + thickness * 
2.5
+        vlist.shift_amount = shift
         return [vlist]
 
     overunder = subsuperscript
@@ -2060,7 +2285,6 @@
         w += 4
         h += 4
         font_manager.set_canvas_size(w,h)
-        
         ship(2, 2, box)
         
         if self.output == 'SVG':


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
This SF.net email is sponsored by: Splunk Inc.
Still grepping through log files to find problems?  Stop.
Now Search log events and configuration files using AJAX and a browser.
Download your FREE copy of Splunk now >>  http://get.splunk.com/
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to