|
From: <jo...@us...> - 2007-09-03 21:36:32
|
Revision: 3770
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3770&view=rev
Author: jouni
Date: 2007-09-03 14:36:17 -0700 (Mon, 03 Sep 2007)
Log Message:
-----------
Created new file type1font.py for supporting Type 1 fonts;
quite preliminary for now. Started adding Type 1 support to
PDF backend for purposes of usetex.
Modified Paths:
--------------
trunk/matplotlib/API_CHANGES
trunk/matplotlib/CHANGELOG
trunk/matplotlib/lib/matplotlib/afm.py
trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py
trunk/matplotlib/lib/matplotlib/dviread.py
trunk/matplotlib/src/ft2font.cpp
trunk/matplotlib/src/ft2font.h
Added Paths:
-----------
trunk/matplotlib/lib/matplotlib/type1font.py
Modified: trunk/matplotlib/API_CHANGES
===================================================================
--- trunk/matplotlib/API_CHANGES 2007-09-03 18:27:22 UTC (rev 3769)
+++ trunk/matplotlib/API_CHANGES 2007-09-03 21:36:17 UTC (rev 3770)
@@ -1,3 +1,18 @@
+ The dviread.py file now has a parser for files like psfonts.map
+ and pdftex.map, to map TeX font names to external files.
+
+ The file type1font.py contains a new class for Type 1 fonts.
+ Currently it simply reads pfa and pfb format files and stores the
+ data in pfa format, which is the format for embedding Type 1 fonts
+ in postscript and pdf files. In the future the class might
+ actually parse the font to allow e.g. subsetting.
+
+ FT2Font now supports FT_Attach_File. In practice this can be used
+ to read an afm file in addition to a pfa/pfb file, to get metrics
+ and kerning information for a Type 1 font.
+
+ The AFM class now supports querying CapHeight and stem widths.
+
Changed pcolor default to shading='flat'; but as noted now in the
docstring, it is preferable to simply use the edgecolor kwarg.
Modified: trunk/matplotlib/CHANGELOG
===================================================================
--- trunk/matplotlib/CHANGELOG 2007-09-03 18:27:22 UTC (rev 3769)
+++ trunk/matplotlib/CHANGELOG 2007-09-03 21:36:17 UTC (rev 3770)
@@ -1,3 +1,9 @@
+2007-09-03 Created type1font.py, added features to AFM and FT2Font
+ (see API_CHANGES), started work on embedding Type 1 fonts
+ in pdf files. - JKS
+
+2007-09-02 Continued work on dviread.py. - JKS
+
2007-08-16 Added a set_extent method to AxesImage, allow data extent
to be modified after initial call to imshow - DSD
Modified: trunk/matplotlib/lib/matplotlib/afm.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/afm.py 2007-09-03 18:27:22 UTC (rev 3769)
+++ trunk/matplotlib/lib/matplotlib/afm.py 2007-09-03 21:36:17 UTC (rev 3770)
@@ -103,7 +103,8 @@
'Version': _to_str,
'Notice': _to_str,
'EncodingScheme': _to_str,
- 'CapHeight': _to_float,
+ 'CapHeight': _to_float, # Is the second version a mistake, or
+ 'Capheight': _to_float, # do some AFM files contain 'Capheight'? -JKS
'XHeight': _to_float,
'Ascender': _to_float,
'Descender': _to_float,
@@ -112,7 +113,6 @@
'StartCharMetrics': _to_int,
'CharacterSet': _to_str,
'Characters': _to_int,
- 'Capheight': _to_int,
}
d = {}
@@ -446,6 +446,10 @@
"Return the fontangle as float"
return self._header['ItalicAngle']
+ def get_capheight(self):
+ "Return the cap height as float"
+ return self._header['CapHeight']
+
def get_xheight(self):
"Return the xheight as float"
return self._header['XHeight']
@@ -453,6 +457,20 @@
def get_underline_thickness(self):
"Return the underline thickness as float"
return self._header['UnderlineThickness']
+
+ def get_horizontal_stem_width(self):
+ """
+ Return the standard horizontal stem width as float, or None if
+ not specified in AFM file.
+ """
+ return self._header.get('StdHW', None)
+
+ def get_vertical_stem_width(self):
+ """
+ Return the standard vertical stem width as float, or None if
+ not specified in AFM file.
+ """
+ return self._header.get('StdVW', None)
if __name__=='__main__':
Modified: trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2007-09-03 18:27:22 UTC (rev 3769)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2007-09-03 21:36:17 UTC (rev 3770)
@@ -26,7 +26,8 @@
from matplotlib.figure import Figure
from matplotlib.font_manager import findfont
from matplotlib.afm import AFM
-from matplotlib.dviread import Dvi
+import matplotlib.type1font as type1font
+import matplotlib.dviread as dviread
from matplotlib.ft2font import FT2Font, FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, \
LOAD_NO_HINTING, KERNING_UNFITTED
from matplotlib.mathtext import MathTextParser
@@ -367,6 +368,7 @@
# self.fontNames maps filenames to internal font names
self.fontNames = {}
self.nextFont = 1 # next free internal font name
+ self.fontInfo = {} # information on fonts: metrics, encoding
self.alphaStates = {} # maps alpha values to graphics state objects
self.nextAlphaState = 1
@@ -438,6 +440,12 @@
self.currentstream = None
def fontName(self, fontprop):
+ """
+ Select a font based on fontprop and return a name suitable for
+ Op.selectfont. If fontprop is a string, it will be interpreted
+ as the filename of the font.
+ """
+
if is_string_like(fontprop):
filename = fontprop
elif rcParams['pdf.use14corefonts']:
@@ -458,6 +466,9 @@
for filename, Fx in self.fontNames.items():
if filename.endswith('.afm'):
fontdictObject = self._write_afm_font(filename)
+ elif filename.endswith('.pfb') or filename.endswith('.pfa'):
+ # a Type 1 font; limited support for now
+ fontdictObject = self.embedType1(filename, self.fontInfo[Fx])
else:
realpath, stat_key = get_realpath_and_stat(filename)
chars = self.used_characters.get(stat_key)
@@ -480,6 +491,97 @@
self.writeObject(fontdictObject, fontdict)
return fontdictObject
+ def embedType1(self, filename, fontinfo):
+ fh = open(filename, 'rb')
+ try:
+ fontdata = fh.read()
+ finally:
+ fh.close()
+
+ fh = open(fontinfo.afmfile, 'rb')
+ try:
+ afmdata = AFM(fh)
+ finally:
+ fh.close()
+
+ font = FT2Font(filename)
+ font.attach_file(fontinfo.afmfile)
+
+ widthsObject, fontdescObject, fontdictObject, fontfileObject = \
+ [ self.reserveObject(n) for n in
+ ('font widths', 'font descriptor',
+ 'font dictionary', 'font file') ]
+
+ _, _, fullname, familyname, weight, italic_angle, fixed_pitch, \
+ ul_position, ul_thickness = font.get_ps_font_info()
+
+ differencesArray = [ 0 ] + [ Name(ch) for ch in
+ dviread.Encoding(fontinfo.encoding) ]
+
+ fontdict = {
+ 'Type': Name('Font'),
+ 'Subtype': Name('Type1'),
+ 'BaseFont': Name(font.postscript_name),
+ 'FirstChar': 0,
+ 'LastChar': len(differencesArray) - 2,
+ 'Widths': widthsObject,
+ 'FontDescriptor': fontdescObject,
+ 'Encoding': { 'Type': Name('Encoding'),
+ 'Differences': differencesArray },
+ }
+
+ flags = 0
+ if fixed_pitch: flags |= 1 << 0 # fixed width
+ if 0: flags |= 1 << 1 # TODO: serif
+ if 0: flags |= 1 << 2 # TODO: symbolic
+ else: flags |= 1 << 5 # non-symbolic
+ if italic_angle: flags |= 1 << 6 # italic
+ if 0: flags |= 1 << 16 # TODO: all caps
+ if 0: flags |= 1 << 17 # TODO: small caps
+ if 0: flags |= 1 << 18 # TODO: force bold
+
+ descriptor = {
+ 'Type': Name('FontDescriptor'),
+ 'FontName': Name(font.postscript_name),
+ 'Flags': flags,
+ 'FontBBox': font.bbox,
+ 'ItalicAngle': italic_angle,
+ 'Ascent': font.ascender,
+ 'Descent': font.descender,
+ 'CapHeight': afmdata.get_capheight(),
+ 'XHeight': afmdata.get_xheight(),
+ 'FontFile': fontfileObject,
+ 'FontFamily': Name(familyname),
+ #'FontWeight': a number where 400 = Regular, 700 = Bold
+ }
+
+ # StemV is obligatory in PDF font descriptors but optional in
+ # AFM files. The collection of AFM files in my TeX Live 2007
+ # collection has values ranging from 22 to 219, with both
+ # median and mode 50, so if the AFM file is silent, I'm
+ # guessing 50. -JKS
+ StemV = afmdata.get_vertical_stem_width()
+ if StemV is None: StemV = 50
+ descriptor['StemV'] = StemV
+
+ # StemH is entirely optional:
+ StemH = afmdata.get_horizontal_stem_width()
+ if StemH is not None:
+ descriptor['StemH'] = StemH
+
+ self.writeObject(fontdictObject, fontdict)
+ self.writeObject(widthsObject, widths)
+ self.writeObject(fontdescObject, descriptor)
+
+ fontdata = type1font.Type1Font(filename)
+ len1, len2, len3 = fontdata.lenghts()
+ self.beginStream(fontfileObject.id, None,
+ { 'Length1': len1,
+ 'Length2': len2,
+ 'Length3': len3 })
+ self.currentstream.write(fontdata.data)
+ self.endStream()
+
def _get_xobject_symbol_name(self, filename, symbol_name):
return "%s-%s" % (
os.path.splitext(os.path.basename(filename))[0],
@@ -1034,6 +1136,7 @@
self.encode_string = self.encode_string_type42
self.mathtext_parser = MathTextParser("Pdf")
self.image_magnification = dpi/72.0
+ self.tex_font_map = None
def finalize(self):
self.gc.finalize()
@@ -1050,6 +1153,12 @@
# Restore gc to avoid unwanted side effects
gc._fillcolor = orig_fill
+ def tex_font_mapping(self, texfont):
+ if self.tex_font_map is None:
+ self.tex_font_map = \
+ dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
+ return self.tex_font_map[texfont]
+
def track_characters(self, font, s):
"""Keeps track of which characters are required from
each font."""
@@ -1288,9 +1397,8 @@
texmanager = self.get_texmanager()
fontsize = prop.get_size_in_points()
dvifile = texmanager.make_dvi(s, fontsize)
- dvi = Dvi(dvifile, 72)
+ dvi = dviread.Dvi(dvifile, 72)
text, boxes = iter(dvi).next()
- fontdir = os.path.join(get_data_path(), 'fonts', 'ttf')
if angle == 0: # avoid rounding errors in common case
def mytrans(x1, y1):
@@ -1303,14 +1411,17 @@
self.check_gc(gc, gc._rgb)
self.file.output(Op.begin_text)
- oldfont, oldx, oldy = None, 0, 0
- for x1, y1, font, glyph in text:
- if font != oldfont:
- fontname, fontsize = dvi.fontinfo(font)
- fontfile = os.path.join(fontdir, fontname+'.ttf')
- self.file.output(self.file.fontName(fontfile),
- fontsize, Op.selectfont)
- oldfont = font
+ oldfontnum, oldx, oldy = None, 0, 0
+ for x1, y1, fontnum, glyph in text:
+ if fontnum != oldfontnum:
+ texname, fontsize = dvi.fontinfo(fontnum)
+ fontinfo = self.tex_font_mapping(texname)
+ pdfname = self.file.fontName(fontinfo.filename)
+ self.file.fontInfo[pdfname] = Bunch(
+ encodingfile=fontinfo.encoding,
+ afmfile=fontinfo.afm)
+ self.file.output(pdfname, fontsize, Op.selectfont)
+ oldfontnum = fontnum
x1, y1 = mytrans(x1, y1)
self._setup_textpos(x1, y1, angle, oldx, oldy)
self.file.output(chr(glyph), Op.show)
Modified: trunk/matplotlib/lib/matplotlib/dviread.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/dviread.py 2007-09-03 18:27:22 UTC (rev 3769)
+++ trunk/matplotlib/lib/matplotlib/dviread.py 2007-09-03 21:36:17 UTC (rev 3770)
@@ -410,11 +410,11 @@
def __getitem__(self, texname):
result = self._font[texname]
- if result.filename is not None \
- and not result.filename.startswith('/'):
- result.filename = find_tex_file(result.filename)
- if result.encoding is not None \
- and not result.encoding.startswith('/'):
+ fn, enc = result.filename, result.encoding
+ if fn is not None and not fn.startswith('/'):
+ result.filename = find_tex_file(fn)
+ result.afm = find_tex_file(fn[:-4] + '.afm')
+ if enc is not None and not enc.startswith('/'):
result.encoding = find_tex_file(result.encoding)
return result
@@ -473,6 +473,51 @@
texname=texname, psname=psname, effects=effects,
encoding=encoding, filename=filename)
+class Encoding(object):
+
+ def __init__(self, filename):
+ file = open(filename, 'rt')
+ try:
+ self.encoding = self._parse(file)
+ finally:
+ file.close()
+
+ def __iter__(self):
+ for name in self.encoding:
+ yield name
+
+ def _parse(self, file):
+ result = []
+
+ state = 0
+ for line in file:
+ comment_start = line.find('%')
+ if comment_start > -1:
+ line = line[:comment_start]
+ line = line.strip()
+
+ if state == 0:
+ # Expecting something like /FooEncoding [
+ if '[' in line:
+ state = 1
+ line = line[line.index('[')+1].strip()
+
+ if state == 1:
+ words = line.split()
+ for w in words:
+ if w.startswith('/'):
+ # Allow for /abc/def/ghi
+ subwords = w.split('/')
+ result.extend(subwords[1:])
+ else:
+ raise ValueError, "Broken name in encoding file: " + w
+
+ # Expecting ] def
+ if ']' in line:
+ break
+
+ return result
+
def find_tex_file(filename, format=None):
"""
Call kpsewhich to find a file in the texmf tree.
Added: trunk/matplotlib/lib/matplotlib/type1font.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/type1font.py (rev 0)
+++ trunk/matplotlib/lib/matplotlib/type1font.py 2007-09-03 21:36:17 UTC (rev 3770)
@@ -0,0 +1,95 @@
+"""
+A class representing a Type 1 font.
+
+This version merely allows reading in pfa and pfb files, and stores
+the data in pfa format (which can be embedded in PostScript or PDF
+files). A more complete class might support subsetting.
+
+Usage: font = Type1Font(filename)
+ somefile.write(font.data) # writes out font in pfa format
+ len1, len2, len3 = font.lengths() # needed for pdf embedding
+
+Source: Adobe Technical Note #5040, Supporting Downloadable PostScript
+Language Fonts.
+
+If extending this class, see also: Adobe Type 1 Font Format, Adobe
+Systems Incorporated, third printing, v1.1, 1993. ISBN 0-201-57044-0.
+"""
+
+import struct
+
+class Type1Font(object):
+
+ def __init__(self, filename):
+ file = open(filename, 'rb')
+ try:
+ self._read(file)
+ finally:
+ file.close()
+
+ def _read(self, file):
+ rawdata = file.read()
+ if not rawdata.startswith(chr(128)):
+ self.data = rawdata
+ return
+
+ self.data = ''
+ while len(rawdata) > 0:
+ if not rawdata.startswith(chr(128)):
+ raise RuntimeError, \
+ 'Broken pfb file (expected byte 128, got %d)' % \
+ ord(rawdata[0])
+ type = ord(rawdata[1])
+ if type in (1,2):
+ length, = struct.unpack('<i', rawdata[2:6])
+ segment = rawdata[6:6+length]
+ rawdata = rawdata[6+length:]
+
+ if type == 1: # ASCII text: include verbatim
+ self.data += segment
+ elif type == 2: # binary data: encode in hexadecimal
+ self.data += ''.join(['%02x' % ord(char)
+ for char in segment])
+ elif type == 3: # end of file
+ break
+ else:
+ raise RuntimeError, \
+ 'Unknown segment type %d in pfb file' % type
+
+ def lengths(self):
+ """
+ Compute the lengths of the three parts of a Type 1 font.
+
+ The three parts are: (1) the cleartext part, which ends in a
+ eexec operator; (2) the encrypted part; (3) the fixed part,
+ which contains 512 ASCII zeros possibly divided on various
+ lines, a cleartomark operator, and possibly something else.
+ """
+
+ # Cleartext part: just find the eexec and skip the eol char(s)
+ idx = self.data.index('eexec')
+ idx += len('eexec')
+ while self.data[idx] in ('\n', '\r'):
+ idx += 1
+ len1 = idx
+
+ # Encrypted part: find the cleartomark operator and count
+ # zeros backward
+ idx = self.data.rindex('cleartomark') - 1
+ zeros = 512
+ while zeros and self.data[idx] in ('0', '\n', '\r'):
+ if self.data[idx] == '0':
+ zeros -= 1
+ idx -= 1
+ if zeros:
+ raise RuntimeError, 'Insufficiently many zeros in Type 1 font'
+
+ len2 = idx - len1
+ len3 = len(self.data) - idx
+
+ return len1, len2, len3
+
+if __name__ == '__main__':
+ import sys
+ font = Type1Font(sys.argv[1])
+ sys.stdout.write(font.data)
Modified: trunk/matplotlib/src/ft2font.cpp
===================================================================
--- trunk/matplotlib/src/ft2font.cpp 2007-09-03 18:27:22 UTC (rev 3769)
+++ trunk/matplotlib/src/ft2font.cpp 2007-09-03 21:36:17 UTC (rev 3770)
@@ -1552,11 +1552,11 @@
}
Py::Tuple info(9);
- info[0] = Py::String(fontinfo.version);
- info[1] = Py::String(fontinfo.notice);
- info[2] = Py::String(fontinfo.full_name);
- info[3] = Py::String(fontinfo.family_name);
- info[4] = Py::String(fontinfo.weight);
+ info[0] = Py::String(fontinfo.version ? fontinfo.version : "");
+ info[1] = Py::String(fontinfo.notice ? fontinfo.notice : "");
+ info[2] = Py::String(fontinfo.full_name ? fontinfo.full_name : "");
+ info[3] = Py::String(fontinfo.family_name ? fontinfo.family_name : "");
+ info[4] = Py::String(fontinfo.weight ? fontinfo.weight : "");
info[5] = Py::Long(fontinfo.italic_angle);
info[6] = Py::Int(fontinfo.is_fixed_pitch);
info[7] = Py::Int(fontinfo.underline_position);
@@ -1788,7 +1788,30 @@
return Py::asObject(image);
}
+char FT2Font::attach_file__doc__ [] =
+ "attach_file(filename)\n"
+ "\n"
+ "Attach a file with extra information on the font\n"
+ "(in practice, an AFM file with the metrics of a Type 1 font).\n"
+ "Throws an exception if unsuccessful.\n";
Py::Object
+FT2Font::attach_file (const Py::Tuple &args) {
+ args.verify_length(1);
+
+ std::string filename = Py::String(args[0]);
+ FT_Error error =
+ FT_Attach_File(face, filename.c_str());
+
+ if (error) {
+ std::ostringstream s;
+ s << "Could not attach file " << filename
+ << " (freetype error code " << error << ")" << std::endl;
+ throw Py::RuntimeError(s.str());
+ }
+ return Py::Object();
+}
+
+Py::Object
ft2font_module::new_ft2image (const Py::Tuple &args) {
args.verify_length(2);
@@ -1894,6 +1917,8 @@
FT2Font::get_sfnt_table__doc__);
add_varargs_method("get_image", &FT2Font::get_image,
FT2Font::get_image__doc__);
+ add_varargs_method("attach_file", &FT2Font::attach_file,
+ FT2Font::attach_file__doc__);
behaviors().supportGetattr();
behaviors().supportSetattr();
@@ -1949,6 +1974,7 @@
" max_advance_height same for vertical layout\n"
" underline_position vertical position of the underline bar\n"
" underline_thickness vertical thickness of the underline\n"
+" postscript_name PostScript name of the font\n"
;
#if defined(_MSC_VER)
Modified: trunk/matplotlib/src/ft2font.h
===================================================================
--- trunk/matplotlib/src/ft2font.h 2007-09-03 18:27:22 UTC (rev 3769)
+++ trunk/matplotlib/src/ft2font.h 2007-09-03 21:36:17 UTC (rev 3770)
@@ -122,6 +122,7 @@
Py::Object get_ps_font_info(const Py::Tuple & args);
Py::Object get_sfnt_table(const Py::Tuple & args);
Py::Object get_image(const Py::Tuple & args);
+ Py::Object attach_file(const Py::Tuple & args);
int setattr( const char *_name, const Py::Object &value );
Py::Object getattr( const char *_name );
FT2Image* image;
@@ -163,6 +164,7 @@
static char get_ps_font_info__doc__[];
static char get_sfnt_table__doc__[];
static char get_image__doc__[];
+ static char attach_file__doc__[];
};
// the extension module
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|