|
From: <md...@us...> - 2007-07-26 14:45:59
|
Revision: 3617
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3617&view=rev
Author: mdboom
Date: 2007-07-26 07:45:57 -0700 (Thu, 26 Jul 2007)
Log Message:
-----------
Merging mathtext changes into trunk.
Modified Paths:
--------------
trunk/matplotlib/examples/mathtext_demo.py
trunk/matplotlib/lib/matplotlib/_mathtext_data.py
trunk/matplotlib/lib/matplotlib/afm.py
trunk/matplotlib/lib/matplotlib/backends/backend_agg.py
trunk/matplotlib/lib/matplotlib/backends/backend_cairo.py
trunk/matplotlib/lib/matplotlib/backends/backend_gdk.py
trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py
trunk/matplotlib/lib/matplotlib/backends/backend_ps.py
trunk/matplotlib/lib/matplotlib/backends/backend_svg.py
trunk/matplotlib/lib/matplotlib/mathtext.py
trunk/matplotlib/lib/matplotlib/pyparsing.py
trunk/matplotlib/lib/matplotlib/text.py
trunk/matplotlib/src/ft2font.cpp
trunk/matplotlib/src/mplutils.h
Added Paths:
-----------
trunk/matplotlib/examples/mathtext_examples.py
trunk/matplotlib/lib/matplotlib/mpl-data/fonts/ttf/cmb10.ttf
trunk/matplotlib/lib/matplotlib/mpl-data/fonts/ttf/cmss10.ttf
Modified: trunk/matplotlib/examples/mathtext_demo.py
===================================================================
--- trunk/matplotlib/examples/mathtext_demo.py 2007-07-26 13:46:56 UTC (rev 3616)
+++ trunk/matplotlib/examples/mathtext_demo.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -1,28 +1,25 @@
#!/usr/bin/env python
"""
+Use matplotlib's internal LaTex parser and layout engine. For true
+latex rendering, see the text.usetex option
+"""
+import numpy as npy
+from pylab import figure, show
+fig = figure()
+fig.subplots_adjust(bottom=0.2)
-In order to use mathtext, you must build matplotlib.ft2font. This is
-built by default in the windows installer.
+ax = fig.add_subplot(111, axisbg='y')
+ax.plot([1,2,3], 'r')
+x = npy.arange(0.0, 3.0, 0.1)
-For other platforms, edit setup.py and set
+ax.grid(True)
+ax.set_xlabel(r'$\Delta_i^j$', fontsize=20)
+ax.set_ylabel(r'$\Delta_{i+1}^j$', fontsize=20)
+tex = r'$\mathcal{R}\prod_{i=\alpha_{i+1}}^\infty a_i\sin(2 \pi f x_i)$'
-BUILD_FT2FONT = True
+ax.text(1, 1.6, tex, fontsize=20, va='bottom')
-"""
-from pylab import *
-subplot(111, axisbg='y')
-plot([1,2,3], 'r')
-x = arange(0.0, 3.0, 0.1)
-
-grid(True)
-xlabel(r'$\Delta_i^j$', fontsize=20)
-ylabel(r'$\Delta_{i+1}^j$', fontsize=20)
-tex = r'$\cal{R}\prod_{i=\alpha_{i+1}}^\infty a_i\rm{sin}(2 \pi f x_i)$'
-text(1, 1.6, tex, fontsize=20)
-
#title(r'$\Delta_i^j \hspace{0.4} \rm{versus} \hspace{0.4} \Delta_{i+1}^j$', fontsize=20)
-savefig('mathtext_demo')
+fig.savefig('mathtext_demo')
-
-
show()
Copied: trunk/matplotlib/examples/mathtext_examples.py (from rev 3616, branches/mathtext_mgd/examples/mathtext_examples.py)
===================================================================
--- trunk/matplotlib/examples/mathtext_examples.py (rev 0)
+++ trunk/matplotlib/examples/mathtext_examples.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+
+import os, sys
+
+stests = [
+ r'Kerning: AVA $AVA$',
+ r'$x y$',
+ r'$x+y\ x=y\ x<y\ x:y\ x,y\ x@y$',
+ r'$100\%y\ x*y\ x/y x\$y$',
+ r'$x\leftarrow y\ x\forall y$',
+ r'$x \sf x \bf x {\cal X} \rm x$',
+ r'$\{ \rm braces \}$',
+ r'$\left[\left\lfloor\frac{5}{\frac{\left(3\right)}{4}} y\right)\right]$',
+ r'$\left(x\right)$',
+ r'$\sin(x)$',
+ r'$x_2$',
+ r'$x^2$',
+ r'$x^2_y$',
+ r'$x_y^2$',
+ r'$\prod_{i=\alpha_{i+1}}^\infty$',
+ r'$x = \frac{x+\frac{5}{2}}{\frac{y+3}{8}}$',
+ r'$dz/dt \/ = \/ \gamma x^2 \/ + \/ {\rm sin}(2\pi y+\phi)$',
+ r'Foo: $\alpha_{i+1}^j \/ = \/ {\rm sin}(2\pi f_j t_i) e^{-5 t_i/\tau}$',
+ r'$\mathcal{R}\prod_{i=\alpha_{i+1}}^\infty a_i \sin(2 \pi f x_i)$',
+# r'$\bigodot \bigoplus {\sf R} a_i{\rm sin}(2 \pi f x_i)$',
+ r'Variable $i$ is good',
+ r'$\Delta_i^j$',
+ r'$\Delta^j_{i+1}$',
+ r'$\ddot{o}\acute{e}\grave{e}\hat{O}\breve{\imath}\tilde{n}\vec{q}$',
+ r'$_i$',
+ r"$\arccos((x^i))$",
+ r"$\gamma = \frac{x=\frac{6}{8}}{y} \delta$",
+ r'$\"o\ddot o \'e\`e\~n\.x\^y$',
+
+ ]
+
+from pylab import *
+
+if '--latex' in sys.argv:
+ fd = open("mathtext_examples.ltx", "w")
+ fd.write("\\documentclass{article}\n")
+ fd.write("\\begin{document}\n")
+ fd.write("\\begin{enumerate}\n")
+
+ for i, s in enumerate(stests):
+ fd.write("\\item %s\n" % s)
+
+ fd.write("\\end{enumerate}\n")
+ fd.write("\\end{document}\n")
+ fd.close()
+
+ os.system("pdflatex mathtext_examples.ltx")
+else:
+ for i, s in enumerate(stests):
+ print "%02d: %s" % (i, s)
+ plot([1,2,3], 'r')
+ x = arange(0.0, 3.0, 0.1)
+
+ grid(True)
+ text(1, 1.6, s, fontsize=20)
+
+ savefig('mathtext_example%02d' % i)
+ figure()
+
Property changes on: trunk/matplotlib/examples/mathtext_examples.py
___________________________________________________________________
Name: svn:executable
+ *
Modified: trunk/matplotlib/lib/matplotlib/_mathtext_data.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/_mathtext_data.py 2007-07-26 13:46:56 UTC (rev 3616)
+++ trunk/matplotlib/lib/matplotlib/_mathtext_data.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -38,8 +38,10 @@
r'\SQRT' : ('cmex10', 53),
r'\leftbrace' : ('cmex10', 92),
r'{' : ('cmex10', 92),
+ r'\{' : ('cmex10', 92),
r'\rightbrace' : ('cmex10', 130),
r'}' : ('cmex10', 130),
+ r'\}' : ('cmex10', 130),
r'\leftangle' : ('cmex10', 97),
r'\rightangle' : ('cmex10', 64),
r'\Leftparen' : ('cmex10', 112),
@@ -112,7 +114,7 @@
r'\phi' : ('cmmi10', 42),
r'\chi' : ('cmmi10', 17),
r'\psi' : ('cmmi10', 31),
-
+
r'(' : ('cmr10', 119),
r'\leftparen' : ('cmr10', 119),
r'\rightparen' : ('cmr10', 68),
@@ -135,7 +137,11 @@
r'[' : ('cmr10', 62),
r'\rightbracket' : ('cmr10', 72),
r']' : ('cmr10', 72),
-
+ r'\%' : ('cmr10', 48),
+ r'%' : ('cmr10', 48),
+ r'\$' : ('cmr10', 99),
+ r'@' : ('cmr10', 111),
+
# these are mathml names, I think. I'm just using them for the
# tex methods noted
r'\circumflexaccent' : ('cmr10', 124), # for \hat
@@ -749,7 +755,17 @@
r'\langle' : ('psyr', 225),
r'\Sigma' : ('psyr', 229),
r'\sum' : ('psyr', 229),
-
+ # these are mathml names, I think. I'm just using them for the
+ # tex methods noted
+ r'\circumflexaccent' : ('pncri8a', 124), # for \hat
+ r'\combiningbreve' : ('pncri8a', 81), # for \breve
+ r'\combininggraveaccent' : ('pncri8a', 114), # for \grave
+ r'\combiningacuteaccent' : ('pncri8a', 63), # for \accute
+ r'\combiningdiaeresis' : ('pncri8a', 91), # for \ddot
+ r'\combiningtilde' : ('pncri8a', 75), # for \tilde
+ r'\combiningrightarrowabove' : ('pncri8a', 110), # for \vec
+ r'\combiningdotabove' : ('pncri8a', 26), # for \dot
+ r'\imath' : ('pncri8a', 105)
}
# Automatically generated.
Modified: trunk/matplotlib/lib/matplotlib/afm.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/afm.py 2007-07-26 13:46:56 UTC (rev 3616)
+++ trunk/matplotlib/lib/matplotlib/afm.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -394,7 +394,14 @@
"Return the fontangle as float"
return self._header['ItalicAngle']
+ def get_xheight(self):
+ "Return the xheight as float"
+ return self._header['XHeight']
+ def get_underline_thickness(self):
+ "Return the underline thickness as float"
+ return self._header['UnderlineThickness']
+
if __name__=='__main__':
#pathname = '/usr/local/lib/R/afm/'
Modified: trunk/matplotlib/lib/matplotlib/backends/backend_agg.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_agg.py 2007-07-26 13:46:56 UTC (rev 3616)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_agg.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -173,10 +173,9 @@
"""
if __debug__: verbose.report('RendererAgg.draw_mathtext',
'debug-annoying')
- size = prop.get_size_in_points()
- width, height, fonts = math_parse_s_ft2font(
- s, self.dpi.get(), size, angle)
-
+ width, height, fonts, used_characters = math_parse_s_ft2font(
+ s, self.dpi.get(), prop, angle)
+
if angle == 90:
width, height = height, width
for font in fonts:
@@ -225,7 +224,6 @@
# texmanager more efficient. It is not meant to be used
# outside the backend
"""
-
if ismath=='TeX':
# todo: handle props
size = prop.get_size_in_points()
@@ -235,8 +233,8 @@
return n,m
if ismath:
- width, height, fonts = math_parse_s_ft2font(
- s, self.dpi.get(), prop.get_size_in_points())
+ width, height, fonts, used_characters = math_parse_s_ft2font(
+ s, self.dpi.get(), prop)
return width, height
font = self._get_agg_font(prop)
font.set_text(s, 0.0) # the width and height of unrotated string
Modified: trunk/matplotlib/lib/matplotlib/backends/backend_cairo.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_cairo.py 2007-07-26 13:46:56 UTC (rev 3616)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_cairo.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -314,9 +314,8 @@
# "_draw_mathtext()")
# return
- size = prop.get_size_in_points()
- width, height, fonts = math_parse_s_ft2font(
- s, self.dpi.get(), size)
+ width, height, fonts, used_characters = math_parse_s_ft2font(
+ s, self.dpi.get(), prop)
if angle==90:
width, height = height, width
@@ -372,8 +371,8 @@
def get_text_width_height(self, s, prop, ismath):
if _debug: print '%s.%s()' % (self.__class__.__name__, _fn_name())
if ismath:
- width, height, fonts = math_parse_s_ft2font(
- s, self.dpi.get(), prop.get_size_in_points())
+ width, height, fonts, used_characters = math_parse_s_ft2font(
+ s, self.dpi.get(), prop)
return width, height
ctx = self.text_ctx
Modified: trunk/matplotlib/lib/matplotlib/backends/backend_gdk.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_gdk.py 2007-07-26 13:46:56 UTC (rev 3616)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_gdk.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -198,9 +198,8 @@
def _draw_mathtext(self, gc, x, y, s, prop, angle):
- size = prop.get_size_in_points()
- width, height, fonts = math_parse_s_ft2font(
- s, self.dpi.get(), size)
+ width, height, fonts, used_characters = math_parse_s_ft2font(
+ s, self.dpi.get(), prop)
if angle==90:
width, height = height, width
@@ -342,8 +341,8 @@
def get_text_width_height(self, s, prop, ismath):
if ismath:
- width, height, fonts = math_parse_s_ft2font(
- s, self.dpi.get(), prop.get_size_in_points())
+ width, height, fonts, used_characters = math_parse_s_ft2font(
+ s, self.dpi.get(), prop)
return width, height
layout, inkRect, logicalRect = self._get_pango_layout(s, prop)
Modified: trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2007-07-26 13:46:56 UTC (rev 3616)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -457,8 +457,9 @@
fontdictObject = self._write_afm_font(filename)
else:
realpath, stat_key = get_realpath_and_stat(filename)
- fontdictObject = self.embedTTF(
- *self.used_characters[stat_key])
+ chars = self.used_characters.get(stat_key)
+ if chars is not None and len(chars[1]):
+ fontdictObject = self.embedTTF(realpath, chars[1])
fonts[Fx] = fontdictObject
#print >>sys.stderr, filename
self.writeObject(self.fontObject, fonts)
@@ -1092,37 +1093,36 @@
def draw_mathtext(self, gc, x, y, s, prop, angle):
# TODO: fix positioning and encoding
- fontsize = prop.get_size_in_points()
width, height, pswriter, used_characters = \
- math_parse_s_pdf(s, 72, fontsize, 0)
+ math_parse_s_pdf(s, 72, prop, 0)
self.merge_used_characters(used_characters)
-
+
self.check_gc(gc, gc._rgb)
self.file.output(Op.begin_text)
prev_font = None, None
oldx, oldy = 0, 0
- for ox, oy, fontname, fontsize, glyph in pswriter:
- #print ox, oy, glyph
- fontname = fontname.lower()
- a = angle / 180.0 * pi
- newx = x + cos(a)*ox - sin(a)*oy
- newy = y + sin(a)*ox + cos(a)*oy
- self._setup_textpos(newx, newy, angle, oldx, oldy)
- oldx, oldy = newx, newy
- if (fontname, fontsize) != prev_font:
- self.file.output(self.file.fontName(fontname), fontsize,
- Op.selectfont)
- prev_font = fontname, fontsize
+ for record in pswriter:
+ if record[0] == 'glyph':
+ rec_type, ox, oy, fontname, fontsize, glyph = record
+ a = angle / 180.0 * pi
+ newx = x + cos(a)*ox - sin(a)*oy
+ newy = y + sin(a)*ox + cos(a)*oy
+ self._setup_textpos(newx, newy, angle, oldx, oldy)
+ oldx, oldy = newx, newy
+ if (fontname, fontsize) != prev_font:
+ self.file.output(self.file.fontName(fontname), fontsize,
+ Op.selectfont)
+ prev_font = fontname, fontsize
- #if fontname.endswith('cmsy10.ttf') or \
- #fontname.endswith('cmmi10.ttf') or \
- #fontname.endswith('cmex10.ttf'):
- # string = '\0' + chr(glyph)
-
- string = chr(glyph)
- self.file.output(string, Op.show)
+ string = chr(glyph)
+ self.file.output(string, Op.show)
self.file.output(Op.end_text)
+ for record in pswriter:
+ if record[0] == 'rect':
+ rec_type, ox, oy, width, height = record
+ self.file.output(Op.gsave, x + ox, y + oy, width, height, Op.rectangle, Op.fill, Op.grestore)
+
def _draw_tex(self, gc, x, y, s, prop, angle):
# Rename to draw_tex to enable, but note the following:
# TODO:
@@ -1208,9 +1208,7 @@
s = s.encode('cp1252', 'replace')
if ismath:
- fontsize = prop.get_size_in_points()
- w, h, pswriter, used_characters = math_parse_s_pdf(
- s, 72, fontsize, 0)
+ w, h, pswriter, used_characters = math_parse_s_pdf(s, 72, prop, 0)
elif rcParams['pdf.use14corefonts']:
font = self._get_font_afm(prop)
Modified: trunk/matplotlib/lib/matplotlib/backends/backend_ps.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_ps.py 2007-07-26 13:46:56 UTC (rev 3616)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_ps.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -278,7 +278,7 @@
if ismath:
width, height, pswriter, used_characters = math_parse_s_ps(
- s, 72, prop.get_size_in_points(), 0)
+ s, 72, prop, 0)
return width, height
if rcParams['ps.useafm']:
@@ -813,11 +813,9 @@
if debugPS:
self._pswriter.write("% mathtext\n")
- fontsize = prop.get_size_in_points()
width, height, pswriter, used_characters = \
- math_parse_s_ps(s, 72, fontsize, angle)
+ math_parse_s_ps(s, 72, prop, angle)
self.merge_used_characters(used_characters)
-
self.set_color(*gc.get_rgb())
thetext = pswriter.getvalue()
ps = """gsave
@@ -1038,13 +1036,14 @@
print >>fh, l.strip()
if not rcParams['ps.useafm']:
for font_filename, chars in renderer.used_characters.values():
- font = FT2Font(font_filename)
- cmap = font.get_charmap()
- glyph_ids = []
- for c in chars:
- gind = cmap.get(ord(c)) or 0
- glyph_ids.append(gind)
- convert_ttf_to_ps(font_filename, fh, rcParams['ps.fonttype'], glyph_ids)
+ if len(chars):
+ font = FT2Font(font_filename)
+ cmap = font.get_charmap()
+ glyph_ids = []
+ for c in chars:
+ gind = cmap.get(ord(c)) or 0
+ glyph_ids.append(gind)
+ convert_ttf_to_ps(font_filename, fh, rcParams['ps.fonttype'], glyph_ids)
print >>fh, "end"
print >>fh, "%%EndProlog"
Modified: trunk/matplotlib/lib/matplotlib/backends/backend_svg.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_svg.py 2007-07-26 13:46:56 UTC (rev 3616)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_svg.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -291,9 +291,12 @@
self._svgwriter.write (svg)
def _add_char_def(self, prop, char):
- newprop = prop.copy()
- newprop.set_size(self.FONT_SCALE)
- font = self._get_font(newprop)
+ if isinstance(prop, FontProperties):
+ newprop = prop.copy()
+ font = self._get_font(newprop)
+ else:
+ font = prop
+ font.set_size(self.FONT_SCALE, 72)
ps_name = font.get_sfnt()[(1,0,0,6)]
char_id = urllib.quote('%s-%d' % (ps_name, ord(char)))
if char_id in self._char_defs:
@@ -332,10 +335,10 @@
"""
Draw math text using matplotlib.mathtext
"""
- fontsize = prop.get_size_in_points()
- width, height, svg_elements = math_parse_s_ft2font_svg(s, 72, fontsize)
+ width, height, svg_elements, used_characters = \
+ math_parse_s_ft2font_svg(s, 72, prop)
svg_glyphs = svg_elements.svg_glyphs
- svg_lines = svg_elements.svg_lines
+ svg_rects = svg_elements.svg_rects
color = rgb2hex(gc.get_rgb())
self.open_group("mathtext")
@@ -349,10 +352,9 @@
svg.append('translate(%f,%f)' % (x, y))
svg.append('">\n')
- for fontname, fontsize, thetext, new_x, new_y_mtc, metrics in svg_glyphs:
- prop = FontProperties(family=fontname, size=fontsize)
- charid = self._add_char_def(prop, thetext)
-
+ for font, fontsize, thetext, new_x, new_y_mtc, metrics in svg_glyphs:
+ charid = self._add_char_def(font, thetext)
+
svg.append('<use xlink:href="#%s" transform="translate(%s, %s) scale(%s)"/>\n' %
(charid, new_x, -new_y_mtc, fontsize / self.FONT_SCALE))
svg.append('</g>\n')
@@ -366,7 +368,7 @@
curr_x,curr_y = 0.0,0.0
- for fontname, fontsize, thetext, new_x, new_y_mtc, metrics in svg_glyphs:
+ for font, fontsize, thetext, new_x, new_y_mtc, metrics in svg_glyphs:
if rcParams["mathtext.mathtext2"]:
new_y = new_y_mtc - height
else:
@@ -392,13 +394,20 @@
svg.append('</text>\n')
+ if len(svg_rects):
+ svg.append('<g style="fill: black; stroke: none" transform="')
+ if angle != 0:
+ svg.append('translate(%f,%f) rotate(%1.1f)'
+ % (x,y,-angle) )
+ else:
+ svg.append('translate(%f,%f)' % (x, y))
+ svg.append('">\n')
+
+ for x, y, width, height in svg_rects:
+ svg.append('<rect x="%s" y="%s" width="%s" height="%s" fill="black" stroke="none" />' % (x, -y + height, width, height))
+ svg.append("</g>")
+
self._svgwriter.write (''.join(svg))
- rgbFace = gc.get_rgb()
- for xmin, ymin, xmax, ymax in svg_lines:
- newx, newy = x + xmin, y + height - ymax#-(ymax-ymin)/2#-height
- self.draw_rectangle(gc, rgbFace, newx, newy, xmax-xmin, ymax-ymin)
- #~ self.draw_line(gc, x + xmin, y + ymin,# - height,
- #~ x + xmax, y + ymax)# - height)
self.close_group("mathtext")
def finish(self):
@@ -417,8 +426,8 @@
def get_text_width_height(self, s, prop, ismath):
if ismath:
- width, height, trash = math_parse_s_ft2font_svg(
- s, 72, prop.get_size_in_points())
+ width, height, trash, used_characters = \
+ math_parse_s_ft2font_svg(s, 72, prop)
return width, height
font = self._get_font(prop)
font.set_text(s, 0.0)
Modified: trunk/matplotlib/lib/matplotlib/mathtext.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/mathtext.py 2007-07-26 13:46:56 UTC (rev 3616)
+++ trunk/matplotlib/lib/matplotlib/mathtext.py 2007-07-26 14:45:57 UTC (rev 3617)
@@ -14,7 +14,7 @@
handle fairly complex TeX expressions Eg, the following renders
correctly
- s = r'$\cal{R}\prod_{i=\alpha\cal{B}}^\infty a_i\rm{sin}(2 \pi f x_i)$'
+ s = r'$\mathcal{R}\prod_{i=\alpha\mathcal{B}}^\infty a_i\sin(2 \pi f x_i)$'
The fonts \cal, \rm, \it, and \tt are allowed.
@@ -59,17 +59,10 @@
^
use raw strings
- The $ symbols must be the first and last symbols in the string. Eg,
- you cannot do
+ Math and non-math can be interpresed in the same string. E.g.,
r'My label $x_i$'.
- but you can change fonts, as in
-
- r'$\rm{My label} x_i$'
-
- to achieve the same effect.
-
A large set of the TeX symbols are provided. Subscripting and
superscripting are supported, as well as the over/under style of
subscripting with \sum, \int, etc.
@@ -77,6 +70,8 @@
Allowed TeX symbols:
+ [MGDTODO: This list is no longer exhaustive and needs to be updated]
+
\/ \Delta \Downarrow \Gamma \Im \LEFTangle \LEFTbrace \LEFTbracket
\LEFTparen \Lambda \Leftarrow \Leftbrace \Leftbracket \Leftparen
\Leftrightarrow \Omega \P \Phi \Pi \Psi \RIGHTangle \RIGHTbrace
@@ -119,11 +114,16 @@
KNOWN ISSUES:
- - nested subscripts, eg, x_i_j not working; but you can do x_{i_j}
- - nesting fonts changes in sub/superscript groups not parsing
- - I would also like to add a few more layout commands, like \frac.
+ - Certainly there are some...
+STATUS:
+ The *Unicode* classes were incomplete when I found them, and have
+ not been refactored to support intermingling of regular text and
+ 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 box layout algorithms)
Copyright : John Hunter (2004,2005)
License : matplotlib license (PSF compatible)
@@ -132,26 +132,32 @@
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, \
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
-from matplotlib.cbook import enumerate, iterable, Bunch, get_realpath_and_stat
-from matplotlib.ft2font import FT2Font
+from matplotlib.cbook import enumerate, iterable, Bunch, get_realpath_and_stat, \
+ is_string_like
+from matplotlib.ft2font import FT2Font, KERNING_UNFITTED
from matplotlib.font_manager import fontManager, FontProperties
from matplotlib._mathtext_data import latex_to_bakoma, cmkern, \
latex_to_standard, tex2uni, type12uni, tex2type1, uni2type1
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
@@ -160,6 +166,8 @@
r'\angstrom' : ( ('rm', 'A', 1.0), (None, '\circ', 0.5), 0.0 ),
}
+##############################################################################
+# FONTS
def font_open(filename):
ext = filename.rsplit('.',1)[1]
@@ -224,7 +232,7 @@
The class must be able to take symbol keys and font file names and
return the character metrics as well as do the drawing
"""
-
+
def get_kern(self, facename, symleft, symright, fontsize, dpi):
"""
Get the kerning distance for font between symleft and symright.
@@ -264,7 +272,13 @@
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 {}
+
+
class DummyFonts(Fonts):
'dummy class for debugging parser'
def get_metrics(self, font, sym, fontsize, dpi):
@@ -475,6 +489,8 @@
'rm' : 'cmr10.ttf',
'tt' : 'cmtt10.ttf',
'it' : 'cmmi10.ttf',
+ 'bf' : 'cmb10.ttf',
+ 'sf' : 'cmss10.ttf',
None : 'cmmi10.ttf',
}
@@ -542,78 +558,108 @@
# Old classes
-class BakomaTrueTypeFonts(Fonts):
+class BakomaFonts(Fonts):
"""
Use the Bakoma true type fonts for rendering
"""
- fnames = ('cmmi10', 'cmsy10', 'cmex10',
- 'cmtt10', 'cmr10')
# allocate a new set of fonts
basepath = os.path.join( get_data_path(), 'fonts', 'ttf' )
- fontmap = { 'cal' : 'cmsy10',
- 'rm' : 'cmr10',
- 'tt' : 'cmtt10',
- 'it' : 'cmmi10',
- None : 'cmmi10',
+ fontmap = { 'cal' : 'Cmsy10',
+ 'rm' : 'Cmr10',
+ 'tt' : 'Cmtt10',
+ 'it' : 'Cmmi10',
+ 'bf' : 'Cmb10',
+ 'sf' : 'Cmss10',
+ None : 'Cmmi10',
+ 'ex' : 'Cmex10'
}
- def __init__(self, useSVG=False):
- self.glyphd = {}
- self.fonts = dict(
- [ (name, FT2Font(os.path.join(self.basepath, name) + '.ttf'))
- for name in self.fnames])
+ class CachedFont:
+ def __init__(self, font):
+ self.font = font
+ self.charmap = font.get_charmap()
+ self.glyphmap = dict(
+ [(glyphind, ccode) for ccode, glyphind in self.charmap.items()])
+
+ def __init__(self):
+ self.glyphd = {}
+ self.fonts = {}
+ self.used_characters = {}
- self.charmaps = dict(
- [ (name, self.fonts[name].get_charmap()) for name in self.fnames])
- # glyphmaps is a dict names to a dict of glyphindex -> charcode
- self.glyphmaps = {}
- for name in self.fnames:
- cmap = self.charmaps[name]
- self.glyphmaps[name] = dict([(glyphind, ccode) for ccode, glyphind in cmap.items()])
+ def _get_font(self, font):
+ """Looks up a CachedFont with its charmap and inverse charmap.
+ font may be a TeX font name (cal, rm, it etc.), a Computer Modern
+ font name (cmtt10, cmr10, etc.) or an FT2Font object."""
+ if isinstance(font, str):
+ if font not in self.fontmap.values():
+ basename = self.fontmap[font]
+ else:
+ basename = font
+ else:
+ basename = font.postscript_name
- for font in self.fonts.values():
- font.clear()
- if useSVG:
- self.svg_glyphs=[] # a list of "glyphs" we need to render this thing in SVG
- else: pass
- self.usingSVG = useSVG
+ cached_font = self.fonts.get(basename)
+ if cached_font is None:
+ if isinstance(font, str):
+ font = FT2Font(os.path.join(self.basepath, basename.lower() + ".ttf"))
+ basename = font.postscript_name
+ cached_font = self.CachedFont(font)
+ 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()]
+
def get_metrics(self, font, sym, fontsize, dpi):
- cmfont, metrics, glyph, offset = \
+ basename, font, metrics, symbol_name, num, glyph, offset = \
self._get_info(font, sym, fontsize, dpi)
return metrics
+ def _get_offset(self, basename, cached_font, glyph, fontsize, dpi):
+ if basename.lower() == 'cmex10':
+ return glyph.height/64.0/2 + 256.0/64.0*dpi/72.0
+ return 0.
+
def _get_info (self, font, sym, fontsize, dpi):
'load the cmfont, metrics and glyph with caching'
- key = font, sym, fontsize, dpi
+ if hasattr(font, 'postscript_name'):
+ fontname = font.postscript_name
+ else:
+ fontname = font
+
+ key = fontname, sym, fontsize, dpi
tup = self.glyphd.get(key)
if tup is not None: return tup
-
- basename = self.fontmap[font]
-
- if latex_to_bakoma.has_key(sym):
+
+ if font in self.fontmap and latex_to_bakoma.has_key(sym):
basename, num = latex_to_bakoma[sym]
- num = self.glyphmaps[basename][num]
+ basename, cached_font = self._get_font(basename.capitalize())
+ symbol_name = cached_font.font.get_glyph_name(num)
+ num = cached_font.glyphmap[num]
elif len(sym) == 1:
+ basename, cached_font = self._get_font(font)
num = ord(sym)
+ symbol_name = cached_font.font.get_glyph_name(cached_font.charmap[num])
else:
num = 0
raise ValueError('unrecognized symbol "%s"' % sym)
- #print sym, basename, num
- cmfont = self.fonts[basename]
- cmfont.set_size(fontsize, dpi)
- head = cmfont.get_sfnt_table('head')
- glyph = cmfont.load_char(num)
+ font = cached_font.font
+ font.set_size(fontsize, dpi)
+ glyph = font.load_char(num)
+ realpath, stat_key = get_realpath_and_stat(font.fname)
+ used_characters = self.used_characters.setdefault(
+ stat_key, (realpath, Set()))
+ used_characters[1].update(unichr(num))
+
xmin, ymin, xmax, ymax = [val/64.0 for val in glyph.bbox]
- if basename == 'cmex10':
- offset = glyph.height/64.0/2 + 256.0/64.0*dpi/72.0
- #offset = -(head['yMin']+512)/head['unitsPerEm']*10.
- else:
- offset = 0.
+ offset = self._get_offset(basename, cached_font, glyph, fontsize, dpi)
metrics = Bunch(
advance = glyph.linearHoriAdvance/65536.0,
height = glyph.height/64.0,
@@ -622,211 +668,120 @@
xmax = xmax,
ymin = ymin+offset,
ymax = ymax+offset,
+ # iceberg is the equivalent of TeX's "height"
+ iceberg = glyph.horiBearingY/64.0 + offset
)
- self.glyphd[key] = cmfont, metrics, glyph, offset
+ self.glyphd[key] = basename, font, metrics, symbol_name, num, glyph, offset
return self.glyphd[key]
def set_canvas_size(self, w, h):
'Dimension the drawing canvas; may be a noop'
self.width = int(w)
self.height = int(h)
- for font in self.fonts.values():
- font.set_bitmap_size(int(w), int(h))
+ for cached_font in self.fonts.values():
+ cached_font.font.set_bitmap_size(int(w), int(h))
def render(self, ox, oy, font, sym, fontsize, dpi):
- cmfont, metrics, glyph, offset = \
- self._get_info(font, sym, fontsize, dpi)
+ basename, font, metrics, symbol_name, num, glyph, offset = \
+ self._get_info(font, sym, fontsize, dpi)
- if not self.usingSVG:
- cmfont.draw_glyph_to_bitmap(
- int(ox), int(self.height - oy - metrics.ymax), glyph)
- else:
- oy += offset - 512/2048.*10.
- basename = self.fontmap[font]
- if latex_to_bakoma.has_key(sym):
- basename, num = latex_to_bakoma[sym]
- num = self.glyphmaps[basename][num]
- elif len(sym) == 1:
- num = ord(sym)
- else:
- num = 0
- print >>sys.stderr, 'unrecognized symbol "%s"' % sym
- thetext = unichr(num)
- thetext.encode('utf-8')
- self.svg_glyphs.append((basename, fontsize, thetext, ox, oy, metrics))
+ font.draw_glyph_to_bitmap(
+ 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]
+ font.font.draw_rect_filled(x1, y1, max(x2 - 1, x1), max(y2 - 1, y1))
+
+ def get_used_characters(self):
+ return self.used_characters
- def _old_get_kern(self, font, symleft, symright, fontsize, dpi):
- """
- Get the kerning distance for font between symleft and symright.
+ def get_xheight(self, font, fontsize, dpi):
+ basename, cached_font = self._get_font(font)
+ cached_font.font.set_size(fontsize, dpi)
+ pclt = cached_font.font.get_sfnt_table('pclt')
+ xHeight = pclt['xHeight'] / 64.0
+ return xHeight
- font is one of tt, it, rm, cal or None
+ def get_underline_thickness(self, font, fontsize, dpi):
+ basename, cached_font = self._get_font(font)
+ cached_font.font.set_size(fontsize, dpi)
+ return max(1.0, cached_font.font.underline_thickness / 64.0)
- sym is a single symbol(alphanum, punct) or a special symbol
- like \sigma.
-
- """
- basename = self.fontmap[font]
- cmfont = self.fonts[basename]
- cmfont.set_size(fontsize, dpi)
- kernd = cmkern[basename]
- key = symleft, symright
- kern = kernd.get(key,0)
- #print basename, symleft, symright, key, kern
- return kern
-
- def _get_num(self, font, sym):
- 'get charcode for sym'
- basename = self.fontmap[font]
- if latex_to_bakoma.has_key(sym):
- basename, num = latex_to_bakoma[sym]
- num = self.glyphmaps[basename][num]
- elif len(sym) == 1:
- num = ord(sym)
- else:
- num = 0
- return num
-
-
-class BakomaPSFonts(Fonts):
+ def get_kern(self, fontleft, symleft, fontsizeleft,
+ fontright, symright, fontsizeright, dpi):
+ if fontsizeleft == fontsizeright:
+ basename, font1, metrics, symbol_name, num1, glyph1, offset = \
+ self._get_info(fontleft, symleft, fontsizeleft, dpi)
+ basename, font2, metrics, symbol_name, num2, glyph2, offset = \
+ self._get_info(fontright, symright, fontsizeright, dpi)
+ if font1 == font2:
+ basename, font = self._get_font(font1)
+ return font.font.get_kerning(num1, num2, KERNING_UNFITTED) / 64.0
+ return 0.0
+
+class BakomaPSFonts(BakomaFonts):
"""
Use the Bakoma postscript fonts for rendering to backend_ps
"""
- facenames = ('cmmi10', 'cmsy10', 'cmex10',
- 'cmtt10', 'cmr10')
- # allocate a new set of fonts
- basepath = os.path.join( get_data_path(), 'fonts', 'ttf' )
- fontmap = { 'cal' : 'cmsy10',
- 'rm' : 'cmr10',
- 'tt' : 'cmtt10',
- 'it' : 'cmmi10',
- None : 'cmmi10',
- }
-
- def __init__(self):
- self.glyphd = {}
- self.fonts = dict(
- [ (name, FT2Font(os.path.join(self.basepath, name) + '.ttf'))
- for name in self.facenames])
-
- self.glyphmaps = {}
- for facename in self.facenames:
- charmap = self.fonts[facename].get_charmap()
- self.glyphmaps[facename] = dict([(glyphind, charcode)
- for charcode, glyphind in charmap.items()])
- for font in self.fonts.values():
- font.clear()
-
- self.used_characters = {}
-
- def _get_info (self, font, sym, fontsize, dpi):
- 'load the cmfont, metrics and glyph with caching'
- key = font, sym, fontsize, dpi
- tup = self.glyphd.get(key)
-
- if tup is not None:
- return tup
-
- basename = self.fontmap[font]
-
- if latex_to_bakoma.has_key(sym):
- basename, num = latex_to_bakoma[sym]
- sym = self.fonts[basename].get_glyph_name(num)
- num = self.glyphmaps[basename][num]
- elif len(sym) == 1:
- num = ord(sym)
- else:
- num = 0
- #sym = '.notdef'
- raise ValueError('unrecognized symbol "%s, %d"' % (sym, num))
-
- cmfont = self.fonts[basename]
- cmfont.set_size(fontsize, dpi)
- head = cmfont.get_sfnt_table('head')
- glyph = cmfont.load_char(num)
-
- realpath, stat_key = get_realpath_and_stat(cmfont.fname)
- used_characters = self.used_characters.setdefault(
- stat_key, (realpath, Set()))
- used_characters[1].update(unichr(num))
-
- xmin, ymin, xmax, ymax = [val/64.0 for val in glyph.bbox]
- if basename == 'cmex10':
- offset = -(head['yMin']+512)/head['unitsPerEm']*10.
- else:
- offset = 0.
- metrics = Bunch(
- advance = glyph.linearHoriAdvance/65536.0,
- height = glyph.height/64.0,
- width = glyph.width/64.0,
- xmin = xmin,
- xmax = xmax,
- ymin = ymin+offset,
- ymax = ymax+offset
- )
-
- self.glyphd[key] = basename, metrics, sym, offset
- return basename, metrics, '/'+sym, offset
-
def set_canvas_size(self, w, h, pswriter):
'Dimension the drawing canvas; may be a noop'
self.width = w
self.height = h
self.pswriter = pswriter
-
def render(self, ox, oy, font, sym, fontsize, dpi):
- fontname, metrics, glyphname, offset = \
+ basename, font, metrics, symbol_name, num, glyph, offset = \
self._get_info(font, sym, fontsize, dpi)
- fontname = fontname.capitalize()
- if fontname == 'Cmex10':
- oy += offset - 512/2048.*10.
-
- ps = """/%(fontname)s findfont
+ oy = self.height - oy + offset
+
+ ps = """/%(basename)s findfont
%(fontsize)s scalefont
setfont
%(ox)f %(oy)f moveto
-/%(glyphname)s glyphshow
+/%(symbol_name)s glyphshow
""" % locals()
self.pswriter.write(ps)
-
- def get_metrics(self, font, sym, fontsize, dpi):
- basename, metrics, sym, offset = \
- self._get_info(font, sym, fontsize, dpi)
- return metrics
-
+ def render_rect_filled(self, x1, y1, x2, y2):
+ ps = "%f %f %f %f rectfill" % (x1, self.height - y2, x2 - x1, y2 - y1)
+ self.pswriter.write(ps)
+
class BakomaPDFFonts(BakomaPSFonts):
"""Hack of BakomaPSFonts for PDF support."""
- def _get_filename_and_num (self, font, sym, fontsize, dpi):
- 'should be part of _get_info'
- basename = self.fontmap[font]
+ def render(self, ox, oy, font, sym, fontsize, dpi):
+ basename, font, metrics, symbol_name, num, glyph, offset = \
+ self._get_info(font, sym, fontsize, dpi)
+ filename = font.fname
+ oy = self.height - oy + offset
- if latex_to_bakoma.has_key(sym):
- basename, num = latex_to_bakoma[sym]
- sym = self.fonts[basename].get_glyph_name(num)
- num = self.glyphmaps[basename][num]
- elif len(sym) == 1:
- num = ord(sym)
- else:
- num = 0
- raise ValueError('unrecognized symbol "%s"' % (sym,))
+ self.pswriter.append(('glyph', ox, oy, filename, fontsize, num))
- return os.path.join(self.basepath, basename) + '.ttf', num
-
+ def render_rect_filled(self, x1, y1, x2, y2):
+ self.pswriter.append(('rect', x1, self.height - y2, x2 - x1, y2 - y1))
+
+class BakomaSVGFonts(BakomaFonts):
+ """Hack of BakomaFonts for SVG support."""
+ def __init__(self):
+ BakomaFonts.__init__(self)
+ self.svg_glyphs = []
+ self.svg_rects = []
+
def render(self, ox, oy, font, sym, fontsize, dpi):
- fontname, metrics, glyphname, offset = \
+ basename, font, metrics, symbol_name, num, glyph, offset = \
self._get_info(font, sym, fontsize, dpi)
- filename, num = self._get_filename_and_num(font, sym, fontsize, dpi)
- if fontname.lower() == 'cmex10':
- oy += offset - 512/2048.*10.
- self.pswriter.append((ox, oy, filename, fontsize, num))
+ oy = self.height - oy + offset
+ thetext = unichr(num)
+ thetext.encode('utf-8')
+ self.svg_glyphs.append((font, fontsize, thetext, ox, oy, metrics))
-
+ def render_rect_filled(self, x1, y1, x2, y2):
+ self.svg_rects.append((x1, self.height - y2, x2 - x1, y2 - y1))
+
class StandardPSFonts(Fonts):
"""
Use the standard postscript fonts for rendering to backend_ps
@@ -835,21 +790,51 @@
# allocate a new set of fonts
basepath = os.path.join( get_data_path(), 'fonts', 'afm' )
- fontmap = { 'cal' : 'pzcmi8a',
- 'rm' : 'pncr8a',
- 'tt' : 'pcrr8a',
- 'it' : 'pncri8a',
+ fontmap = { 'cal' : 'pzcmi8a', # Zapf Chancery
+ 'rm' : 'pncr8a', # New Century Schoolbook
+ 'tt' : 'pcrr8a', # Courier
+ 'it' : 'pncri8a', # New Century Schoolbook Italic
+ 'sf' : 'phvr8a', # Helvetica
+ 'bf' : 'pncb8a', # New Century Schoolbook Bold
+ None : 'psyr' # Symbol
}
def __init__(self):
self.glyphd = {}
- self.fonts = dict(
- [ (name, AFM(file(os.path.join(self.basepath, name) + '.afm')))
- for name in self.fnames])
+ self.fonts = {}
+ def _get_font(self, font):
+ if isinstance(font, str):
+ if font not in self.fontmap.values():
+ basename = self.fontmap[font]
+ else:
+ basename = font
+ else:
+ basename = font.get_fontname()
+
+ cached_font = self.fonts.get(basename)
+ if cached_font is None:
+ if isinstance(font, str):
+ fname = os.path.join(self.basepath, basename + ".afm")
+ cached_font = AFM(file(fname, 'r'))
+ cached_font.fname = fname
+ basename = cached_font.get_fontname()
+ else:
+ cached_font = font
+ self.fonts[basename] = cached_font
+ return basename, cached_font
+
+ def get_fonts(self):
+ return [x.font for x in self.fonts.values()]
+
def _get_info (self, font, sym, fontsize, dpi):
'load the cmfont, metrics and glyph with caching'
- key = font, sym, fontsize, dpi
+ if hasattr(font, 'get_fontname'):
+ fontname = font.get_fontname()
+ else:
+ fontname = font
+
+ key = fontname, sym, fontsize, dpi
tup = self.glyphd.get(key)
if tup is not None:
@@ -857,41 +842,42 @@
if sym in "0123456789()" and font == 'it':
font = 'rm'
- basename = self.fontmap[font]
if latex_to_standard.has_key(sym):
- basename, num = latex_to_standard[sym]
- char = chr(num)
+ font, num = latex_to_standard[sym]
+ glyph = chr(num)
elif len(sym) == 1:
- char = sym
+ glyph = sym
+ num = ord(glyph)
else:
raise ValueError('unrecognized symbol "%s"' % (sym))
-
+ basename, font = self._get_font(font)
+
try:
- sym = self.fonts[basename].get_name_char(char)
+ symbol_name = font.get_name_char(glyph)
except KeyError:
raise ValueError('unrecognized symbol "%s"' % (sym))
offset = 0
- cmfont = self.fonts[basename]
- fontname = cmfont.get_fontname()
scale = 0.001 * fontsize
xmin, ymin, xmax, ymax = [val * scale
- for val in cmfont.get_bbox_char(char)]
+ for val in font.get_bbox_char(glyph)]
metrics = Bunch(
advance = (xmax-xmin),
- width = cmfont.get_width_char(char) * scale,
- height = cmfont.get_width_char(char) * scale,
+ width = font.get_width_char(glyph) * scale,
+ height = font.get_height_char(glyph) * scale,
xmin = xmin,
xmax = xmax,
ymin = ymin+offset,
- ymax = ymax+offset
+ ymax = ymax+offset,
+ # iceberg is the equivalent of TeX's "height"
+ iceberg = ymax + offset
)
- self.glyphd[key] = fontname, basename, metrics, sym, offset, char
- return fontname, basename, metrics, '/'+sym, offset, char
+ self.glyphd[key] = basename, font, metrics, symbol_name, num, glyph, offset
+ return self.glyphd[key]
def set_canvas_size(self, w, h, pswriter):
'Dimension the drawing canvas; may be a noop'
@@ -899,637 +885,1292 @@
self.height = h
self.pswriter = pswriter
-
def render(self, ox, oy, font, sym, fontsize, dpi):
- fontname, basename, metrics, glyphname, offset, char = \
+ basename, font, metrics, symbol_name, num, glyph, offset = \
self._get_info(font, sym, fontsize, dpi)
- ps = """/%(fontname)s findfont
+ oy = self.height - oy
+ ps = """/%(basename)s findfont
%(fontsize)s scalefont
setfont
%(ox)f %(oy)f moveto
-/%(glyphname)s glyphshow
+/%(symbol_name)s glyphshow
""" % locals()
self.pswriter.write(ps)
def get_metrics(self, font, sym, fontsize, dpi):
- fontname, basename, metrics, sym, offset, char = \
+ basename, font, metrics, symbol_name, num, glyph, offset = \
self._get_info(font, sym, fontsize, dpi)
return metrics
- def get_kern(self, font, symleft, symright, fontsize, dpi):
- fontname, basename, metrics, sym, offset, char1 = \
- self._get_info(font, symleft, fontsize, dpi)
- fontname, basename, metrics, sym, offset, char2 = \
- self._get_info(font, symright, fontsize, dpi)
- cmfont = self.fonts[basename]
- return cmfont.get_kern_dist(char1, char2) * 0.001 * fontsize
+ 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.get_fontname() == font2.get_fontname():
+ basename, font = self._get_font(font1)
+ return font.get_kern_dist(glyph1, glyph2) * 0.001 * fontsizeleft
+ return 0.0
-class Element:
- fontsize = 12
- dpi = 72
- font = 'it'
- _padx, _pady = 2, 2 # the x and y padding in points
- _scale = 1.0
+ def render_rect_filled(self, x1, y1, x2, y2):
+ ps = "%f %f %f %f rectfill" % (x1, self.height - y2, x2 - x1, y2 - y1)
+ self.pswriter.write(ps)
- 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
+ def get_xheight(self, font, fontsize, dpi):
+ basename, cached_font = self._get_font(font)
+ return cached_font.get_xheight() * 0.001 * fontsize
- def advance(self):
- 'get the horiz advance'
- raise NotImplementedError('derived must override')
+ def get_underline_thickness(self, font, fontsize, dpi):
+ basename, cached_font = self._get_font(font)
+ return cached_font.get_underline_thickness() * 0.001 * fontsize
+
+##############################################################################
+# TeX-LIKE BOX MODEL
- def height(self):
- 'get the element height: ymax-ymin'
- 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 width(self):
- 'get the element width: xmax-xmin'
- raise NotImplementedError('derived must override')
+# How much text shrinks when going to the next-smallest level
+SHRINK_FACTOR = 0.7
+# The number of different sizes of chars to use, beyond which they will not
+# get any smaller
+NUM_SIZE_LEVELS = 3
+# Percentage of x-height that subscripts drop below the baseline
+SUBDROP = 0.05
+# Percentage of x-height that superscripts drop below the baseline
+SUP1 = 0.2
+# Percentage of x-height that subscripts drop below the baseline
+SUB1 = 0.3
+# Percentage of x-height that superscripts are offset relative to the subscript
+DELTA = 0.05
+
+class MathTextWarning(Warning):
+ pass
+
+class Node(object):
+ """A node in a linked list.
+ @133
+ """
+ def __init__(self):
+ self.link = None
+ self.size = 0
+
+ def __repr__(self):
+ s = self.__internal_repr__()
+ if self.link:
+ s += ' ' + self.link.__repr__()
+ return s
- def xmin(self):
- 'get the xmin of ink rect'
- raise NotImplementedError('derived must override')
+ def __internal_repr__(self):
+ return self.__class__.__name__
- def xmax(self):
- 'get the xmax of ink rect'
- raise NotImplementedError('derived must override')
+ def get_kerning(self, next):
+ return 0.0
- def ymin(self):
- 'get the ymin of ink rect'
- raise NotImplementedError('derived must override')
+ def set_link(self, other):
+ self.link = other
- def ymax(self):
- 'get the ymax of ink rect'
- raise NotImplementedError('derived must override')
+ def pack(self):
+ if self.link:
+ self.link.pack()
- def set_font(self, font):
- 'set the font (one of tt, it, rm , cal)'
- raise NotImplementedError('derived must override')
+ 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
- def render(self):
- 'render to the fonts canvas'
- for element in self.neighbors.values():
- element.render()
+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
- def set_origin(self, ox, oy):
- self.ox, self.oy = ox, oy
+ 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
- # 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
+ def render(self, x1, y1, x2, y2):
+ pass
- 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())
+class Vbox(Box):
+ def __init__(self, height, depth):
+ Box.__init__(self, 0., height, depth)
- ny = self.oy
- element.set_origin(nx, ny)
+class Hbox(Box):
+ def __init__(self, width):
+ Box.__init__(self, width, 0., 0.)
+
+class Char(Node):
+ """Represents a single character. Unlike TeX, the font
+ 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
+ Char is added to its parent Hlist.
+ @134"""
+ def __init__(self, c, state):
+ Node.__init__(self)
+ self.c = c
+ self.font_output = state.font_output
+ 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 '`%s`' % self.c
- 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)
- else:
- element.set_size_info(self.fontsize, dpi)
+ def _update_metrics(self):
+ metrics = self._metrics = self.font_output.get_metrics(
+ self.font, self.c, self.fontsize, self.dpi)
+ self.width = metrics.width
+ self.height = metrics.iceberg
+ self.depth = -(metrics.iceberg - metrics.height)
+
+ 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."""
+ advance = self._metrics.advance - self.width
+ kern = 0.
+ if isinstance(next, Char):
+ kern = self.font_output.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_output.render(
+ x, y,
+ self.font, self.c, self.fontsize, self.dpi)
- def pady(self):
- return self.dpi/72.0*self._pady
+ def shrink(self):
+ Node.shrink(self)
+ if self.size < NUM_SIZE_LEVELS:
+ self.fontsize *= SHRINK_FACTOR
+ self._update_metrics()
- def padx(self):
- return self.dpi/72.0*self._padx
+class Accent(Char):
+ """The font metrics need to be dealt with differently for accents, since they
+ are already offset correctly from the baseline in TrueType fonts."""
+ def _update_metrics(self):
+ metrics = self._metrics = self.font_output.get_metrics(
+ self.font, self.c, self.fontsize, self.dpi)
+ self.width = metrics.width
+ self.height = metrics.ymax - metrics.ymin
+ self.depth = 0
- def set_padx(self, pad):
- 'set the y padding in points'
- self._padx = pad
+ def render(self, x, y):
+ """Render the character to the canvas"""
+ self.font_output.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"""
+ 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 set_pady(self, pad):
- 'set the y padding in points'
- self._pady = pad
+ def ...
[truncated message content] |