From: <md...@us...> - 2007-07-24 19:23:41
|
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. |