This is a long one.
The problem with the last patch I submitted was a no brainer.
Instead of:
math_parse_s_ft2font_ps = math_parse_s_ft2font_common('PS')
should have been:
math_parse_s_ps = math_parse_s_ft2font_common('PS')
I am now in the process of creating a UnicodeFonts class, and a
derived BakomaUnicodeFonts, which is a hack to add "unicode support"
to the
Bakoma fonts. Is BakomaUnicodeFonts worth of implementing?
I must say that I had a lot of problems in reading/understanding the code
of the current Fonts classes, especially regarding the ambiguous usage
of the word "font". I also tried to make a common parent class in order to
abstract the common properties of the BakomaTrueTypeFonts and
BakomaPSFonts classes (something along the way of what I did with the
math_parse_s_ft2font_common class), but I hit the wall - there was a lot
of black magic under the hood, IMHO.
So I decided to make naming changes in the new Unicode aware classes,
and that's the purpose of writing this email before I got the new classes to
work. So please John, do comment on this, because I don't know whether
I'm breaking something, and whether the names are chosen appropriately.
The changes I made, make no effect on the current code - that is, everything
still works. I'm sending you a patch against the SVN tree. I also added
the automatically genarated tex2uni etc. files, and they are now incorporated
in the patch.
What should we do with the script that generates those file. Should I try to
integrate it into the setup/build process?
These are the new imports:
from matplotlib.tex2uni import tex2uni
from matplotlib.type12uni import type12uni
These are the new top-level functions I defined in mathtext:
def font_open(filename):
ext = filename.rsplit('.',1)[1]
if ext == 'afm':
return AFM(filename)
else:
return FT2Font(filename)
def get_unicode_index(symbol):
"""get_unicode_index(symbol) -> integer
Return the integer index from the Unicode table of an Unicode symbol
"""
try:# This will succeed if symbol is a single unicode char
return ord(symbol)
except TypeError:
pass
try:# Is symbol a TeX symbol (i.e. \alpha)
return tex2uni[symbol]
except KeyError:
pass
try:# Is symbol a Type1 name (i.e. degree)? If not raise error
return type12uni[symbol]
except KeyError:
message = """'%(symbol)s' is not a valid Unicode character or
TeX/Type1 symbol"""%locals()
raise ValueError, message
And finally, these are the new classes:
svn.patch (103 KB)
···
====
class UnicodeFonts(Fonts):
"""An abstract base class for handling Unicode fonts.
Specific terminology:
* fontface: an FT2Font object, corresponding to a facename
* facename: a string that defines the (type)face's name - 'rm', 'it' etc.
* filename: a string that is used for generating a fontface object
* symbol*: a single Unicode character or a TeX command,
or to be precise, a TeX symbol command like \alpha (but not \frac) or
even a Type1/PS name
* filenamesdict: a dict that maps the face's name to the filename:
filenamesdict = { 'cal' : 'fontnamecal.ext',
'rm' : 'fontnamerm.ext',
'tt' : 'fontnamett.ext',
'it' : 'fontnameit.ext',
None : 'fontnamesmth.ext'}
filenamesdict should be declared as a class atribute
* glyphdict: a dict used for caching of glyph specific data
* fontsdict: a dict of facename -> fontface pairs
* charmaps: a dict of facename -> charmap pairs
* glyphmaps: a dict of facename -> glyphmap pairs. A glyphmap is an
inverted charmap
* output: a string in ['BMP','SVG','PS'], coresponding to the backends
* index: Fontfile specific index of a glyph/char. Taken from a charmap.
"""
# The path to the dir with the fontfiles
basepath = get_data_path()
def __init__(self, output='BMP'):
if output:
self.output = output
# dict self.glyphdict[key] = facename, metrics, glyph, offset
self.glyphdict = {}
self.fontsdict = dict(
[ (facename, font_open(os.path.join(self.basepath,
filenamesdict[facename]))) for facename in
self.facenames])
# a dict of glyphindex -> charcode pairs
self.charmaps = dict(
[ (facename, self.fontsdict[facename].get_charmap())\
for facename in self.facenames])
# a dict of charcode -> glyphindex pairs
self.glyphmaps = {}
for facename in self.facenames:
charmap = self.charmaps[facename]
self.glyphmaps[facename] = dict([(charcode, glyphind)
for glyphind, charcode in charmap.items()])
for fontface in self.fontsdict.values():
fontface.clear()
if self.output == 'SVG':
# a list of "glyphs" we need to render this thing in SVG
self.svg_glyphs=[]
def set_canvas_size(self, w, h, pswriter=None):
'Dimension the drawing canvas; may be a noop'
# self.width = int(w)
# self.height = int(h)
# I don't know why this was different than the PS version
self.width = w
self.height = h
if pswriter:
self.pswriter = pswriter
else:
for fontface in self.fontsdict.values():
fontface.set_bitmap_size(int(w), int(h))
def render(self, ox, oy, facename, symbol, fontsize, dpi):
filename = self.filenamesdict[facename]
metrics, glyph, offset = self._get_info(facename, symbol, fontsize,
dpi)
index = self._get_unicode_index(symbol)
if self.output == 'SVG':
oy += offset - 512/2048.*10.
self.svg_glyphs.append((filename, fontsize, index, ox, oy,
metrics))
elif self.output == 'PS':
# This should be changed to check for math mode or smth.
#if filename == 'cmex10.ttf':
# oy += offset - 512/2048.*10.
psfontname = self.fontsdict[facename].postscript_name
ps = """/%(psfontname)s findfont
%(fontsize)s scalefont
setfont
%(ox)f %(oy)f moveto
/%(glyphname)s glyphshow
""" % locals()
self.pswriter.write(ps)
else: # BMP
fontface = self.fontsdict[facename]
fontface.draw_glyph_to_bitmap(
int(ox), int(self.height - oy - metrics.ymax), glyph)
def get_metrics(self, facename, symbol, fontsize, dpi):
metrics, glyph, offset = \
self._get_info(facename, symbol, fontsize, dpi)
return metrics
def _get_unicode_index(self, symbol):
return get_unicode_index(symbol)
# Methods that must be overridden
def _get_info(self, facename, symbol, fontsize, dpi):
raise NotImplementedError('Derived must override')
class BakomaUnicodeFonts(UnicodeFonts):
"""A class that simulates Unicode support in the BaKoMa fonts"""
filenamesdict = { 'cal' : 'cmsy10.ttf',
'rm' : 'cmr10.ttf',
'tt' : 'cmtt10.ttf',
'it' : 'cmmi10.ttf',
}
def _get_info(self, facename, symbol, fontsize, dpi):
'load the facename, metrics and glyph'
#print hex(index), symbol, filename, facename
key = facename, symbol, fontsize, dpi
tup = self.glyphdict.get(key)
if tup is not None:
return tup
filename = self.fontmap[facename]
# This is black magic to me (Edin)
if self.output == 'PS':
if filename not in bakoma_fonts:
bakoma_fonts.append(filename)
fontface = self.fontsdict[facename]
fontface.set_size(fontsize, dpi)
head = fontface.get_sfnt_table('head')
index = self._get_index(symbol)
glyph = fontface.load_char(index)
xmin, ymin, xmax, ymax = [val/64.0 for val in glyph.bbox]
# This is black magic to me (Edin)
if filename == 'cmex10.ttf':
if self.output == 'PS':
offset = -(head['yMin']+512)/head['unitsPerEm']*10.
else:
offset = glyph.height/64.0/2 + 256.0/64.0*dpi/72.0
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.glyphdict[key] = metrics, glyph, offset
return self.glyphdict[key]
# We override the UnicodeFonts method
def _get_unicode_index(self, symbol):
pass
Cheers,
Edin