Revision: 3590
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3590&view=rev
Author: mdboom
Date: 2007年07月20日 07:19:48 -0700 (2007年7月20日)
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]