Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 852b441

Browse files
committed
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. svn path=/trunk/matplotlib/; revision=3770
1 parent 9a8a48b commit 852b441

8 files changed

Lines changed: 341 additions & 23 deletions

File tree

API_CHANGES

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
The dviread.py file now has a parser for files like psfonts.map
2+
and pdftex.map, to map TeX font names to external files.
3+
4+
The file type1font.py contains a new class for Type 1 fonts.
5+
Currently it simply reads pfa and pfb format files and stores the
6+
data in pfa format, which is the format for embedding Type 1 fonts
7+
in postscript and pdf files. In the future the class might
8+
actually parse the font to allow e.g. subsetting.
9+
10+
FT2Font now supports FT_Attach_File. In practice this can be used
11+
to read an afm file in addition to a pfa/pfb file, to get metrics
12+
and kerning information for a Type 1 font.
13+
14+
The AFM class now supports querying CapHeight and stem widths.
15+
116
Changed pcolor default to shading='flat'; but as noted now in the
217
docstring, it is preferable to simply use the edgecolor kwarg.
318

CHANGELOG

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
2007-09-03 Created type1font.py, added features to AFM and FT2Font
2+
(see API_CHANGES), started work on embedding Type 1 fonts
3+
in pdf files. - JKS
4+
5+
2007-09-02 Continued work on dviread.py. - JKS
6+
17
2007-08-16 Added a set_extent method to AxesImage, allow data extent
28
to be modified after initial call to imshow - DSD
39

lib/matplotlib/afm.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ def _parse_header(fh):
103103
'Version': _to_str,
104104
'Notice': _to_str,
105105
'EncodingScheme': _to_str,
106-
'CapHeight': _to_float,
106+
'CapHeight': _to_float, # Is the second version a mistake, or
107+
'Capheight': _to_float, # do some AFM files contain 'Capheight'? -JKS
107108
'XHeight': _to_float,
108109
'Ascender': _to_float,
109110
'Descender': _to_float,
@@ -112,7 +113,6 @@ def _parse_header(fh):
112113
'StartCharMetrics': _to_int,
113114
'CharacterSet': _to_str,
114115
'Characters': _to_int,
115-
'Capheight': _to_int,
116116
}
117117

118118
d = {}
@@ -446,13 +446,31 @@ def get_angle(self):
446446
"Return the fontangle as float"
447447
return self._header['ItalicAngle']
448448

449+
def get_capheight(self):
450+
"Return the cap height as float"
451+
return self._header['CapHeight']
452+
449453
def get_xheight(self):
450454
"Return the xheight as float"
451455
return self._header['XHeight']
452456

453457
def get_underline_thickness(self):
454458
"Return the underline thickness as float"
455459
return self._header['UnderlineThickness']
460+
461+
def get_horizontal_stem_width(self):
462+
"""
463+
Return the standard horizontal stem width as float, or None if
464+
not specified in AFM file.
465+
"""
466+
return self._header.get('StdHW', None)
467+
468+
def get_vertical_stem_width(self):
469+
"""
470+
Return the standard vertical stem width as float, or None if
471+
not specified in AFM file.
472+
"""
473+
return self._header.get('StdVW', None)
456474

457475

458476
if __name__=='__main__':

lib/matplotlib/backends/backend_pdf.py

Lines changed: 122 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
from matplotlib.figure import Figure
2727
from matplotlib.font_manager import findfont
2828
from matplotlib.afm import AFM
29-
from matplotlib.dviread import Dvi
29+
import matplotlib.type1font as type1font
30+
import matplotlib.dviread as dviread
3031
from matplotlib.ft2font import FT2Font, FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, \
3132
LOAD_NO_HINTING, KERNING_UNFITTED
3233
from matplotlib.mathtext import MathTextParser
@@ -367,6 +368,7 @@ def __init__(self, width, height, filename):
367368
# self.fontNames maps filenames to internal font names
368369
self.fontNames = {}
369370
self.nextFont = 1 # next free internal font name
371+
self.fontInfo = {} # information on fonts: metrics, encoding
370372

371373
self.alphaStates = {} # maps alpha values to graphics state objects
372374
self.nextAlphaState = 1
@@ -438,6 +440,12 @@ def endStream(self):
438440
self.currentstream = None
439441

440442
def fontName(self, fontprop):
443+
"""
444+
Select a font based on fontprop and return a name suitable for
445+
Op.selectfont. If fontprop is a string, it will be interpreted
446+
as the filename of the font.
447+
"""
448+
441449
if is_string_like(fontprop):
442450
filename = fontprop
443451
elif rcParams['pdf.use14corefonts']:
@@ -458,6 +466,9 @@ def writeFonts(self):
458466
for filename, Fx in self.fontNames.items():
459467
if filename.endswith('.afm'):
460468
fontdictObject = self._write_afm_font(filename)
469+
elif filename.endswith('.pfb') or filename.endswith('.pfa'):
470+
# a Type 1 font; limited support for now
471+
fontdictObject = self.embedType1(filename, self.fontInfo[Fx])
461472
else:
462473
realpath, stat_key = get_realpath_and_stat(filename)
463474
chars = self.used_characters.get(stat_key)
@@ -480,6 +491,97 @@ def _write_afm_font(self, filename):
480491
self.writeObject(fontdictObject, fontdict)
481492
return fontdictObject
482493

494+
def embedType1(self, filename, fontinfo):
495+
fh = open(filename, 'rb')
496+
try:
497+
fontdata = fh.read()
498+
finally:
499+
fh.close()
500+
501+
fh = open(fontinfo.afmfile, 'rb')
502+
try:
503+
afmdata = AFM(fh)
504+
finally:
505+
fh.close()
506+
507+
font = FT2Font(filename)
508+
font.attach_file(fontinfo.afmfile)
509+
510+
widthsObject, fontdescObject, fontdictObject, fontfileObject = \
511+
[ self.reserveObject(n) for n in
512+
('font widths', 'font descriptor',
513+
'font dictionary', 'font file') ]
514+
515+
_, _, fullname, familyname, weight, italic_angle, fixed_pitch, \
516+
ul_position, ul_thickness = font.get_ps_font_info()
517+
518+
differencesArray = [ 0 ] + [ Name(ch) for ch in
519+
dviread.Encoding(fontinfo.encoding) ]
520+
521+
fontdict = {
522+
'Type': Name('Font'),
523+
'Subtype': Name('Type1'),
524+
'BaseFont': Name(font.postscript_name),
525+
'FirstChar': 0,
526+
'LastChar': len(differencesArray) - 2,
527+
'Widths': widthsObject,
528+
'FontDescriptor': fontdescObject,
529+
'Encoding': { 'Type': Name('Encoding'),
530+
'Differences': differencesArray },
531+
}
532+
533+
flags = 0
534+
if fixed_pitch: flags |= 1 << 0 # fixed width
535+
if 0: flags |= 1 << 1 # TODO: serif
536+
if 0: flags |= 1 << 2 # TODO: symbolic
537+
else: flags |= 1 << 5 # non-symbolic
538+
if italic_angle: flags |= 1 << 6 # italic
539+
if 0: flags |= 1 << 16 # TODO: all caps
540+
if 0: flags |= 1 << 17 # TODO: small caps
541+
if 0: flags |= 1 << 18 # TODO: force bold
542+
543+
descriptor = {
544+
'Type': Name('FontDescriptor'),
545+
'FontName': Name(font.postscript_name),
546+
'Flags': flags,
547+
'FontBBox': font.bbox,
548+
'ItalicAngle': italic_angle,
549+
'Ascent': font.ascender,
550+
'Descent': font.descender,
551+
'CapHeight': afmdata.get_capheight(),
552+
'XHeight': afmdata.get_xheight(),
553+
'FontFile': fontfileObject,
554+
'FontFamily': Name(familyname),
555+
#'FontWeight': a number where 400 = Regular, 700 = Bold
556+
}
557+
558+
# StemV is obligatory in PDF font descriptors but optional in
559+
# AFM files. The collection of AFM files in my TeX Live 2007
560+
# collection has values ranging from 22 to 219, with both
561+
# median and mode 50, so if the AFM file is silent, I'm
562+
# guessing 50. -JKS
563+
StemV = afmdata.get_vertical_stem_width()
564+
if StemV is None: StemV = 50
565+
descriptor['StemV'] = StemV
566+
567+
# StemH is entirely optional:
568+
StemH = afmdata.get_horizontal_stem_width()
569+
if StemH is not None:
570+
descriptor['StemH'] = StemH
571+
572+
self.writeObject(fontdictObject, fontdict)
573+
self.writeObject(widthsObject, widths)
574+
self.writeObject(fontdescObject, descriptor)
575+
576+
fontdata = type1font.Type1Font(filename)
577+
len1, len2, len3 = fontdata.lenghts()
578+
self.beginStream(fontfileObject.id, None,
579+
{ 'Length1': len1,
580+
'Length2': len2,
581+
'Length3': len3 })
582+
self.currentstream.write(fontdata.data)
583+
self.endStream()
584+
483585
def _get_xobject_symbol_name(self, filename, symbol_name):
484586
return "%s-%s" % (
485587
os.path.splitext(os.path.basename(filename))[0],
@@ -1034,6 +1136,7 @@ def __init__(self, file, dpi):
10341136
self.encode_string = self.encode_string_type42
10351137
self.mathtext_parser = MathTextParser("Pdf")
10361138
self.image_magnification = dpi/72.0
1139+
self.tex_font_map = None
10371140

10381141
def finalize(self):
10391142
self.gc.finalize()
@@ -1050,6 +1153,12 @@ def check_gc(self, gc, fillcolor=None):
10501153
# Restore gc to avoid unwanted side effects
10511154
gc._fillcolor = orig_fill
10521155

1156+
def tex_font_mapping(self, texfont):
1157+
if self.tex_font_map is None:
1158+
self.tex_font_map = \
1159+
dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
1160+
return self.tex_font_map[texfont]
1161+
10531162
def track_characters(self, font, s):
10541163
"""Keeps track of which characters are required from
10551164
each font."""
@@ -1288,9 +1397,8 @@ def _draw_tex(self, gc, x, y, s, prop, angle):
12881397
texmanager = self.get_texmanager()
12891398
fontsize = prop.get_size_in_points()
12901399
dvifile = texmanager.make_dvi(s, fontsize)
1291-
dvi = Dvi(dvifile, 72)
1400+
dvi = dviread.Dvi(dvifile, 72)
12921401
text, boxes = iter(dvi).next()
1293-
fontdir = os.path.join(get_data_path(), 'fonts', 'ttf')
12941402

12951403
if angle == 0: # avoid rounding errors in common case
12961404
def mytrans(x1, y1):
@@ -1303,14 +1411,17 @@ def mytrans(x1, y1, x=x, y=y, a=angle / 180.0 * pi):
13031411

13041412
self.check_gc(gc, gc._rgb)
13051413
self.file.output(Op.begin_text)
1306-
oldfont, oldx, oldy = None, 0, 0
1307-
for x1, y1, font, glyph in text:
1308-
if font != oldfont:
1309-
fontname, fontsize = dvi.fontinfo(font)
1310-
fontfile = os.path.join(fontdir, fontname+'.ttf')
1311-
self.file.output(self.file.fontName(fontfile),
1312-
fontsize, Op.selectfont)
1313-
oldfont = font
1414+
oldfontnum, oldx, oldy = None, 0, 0
1415+
for x1, y1, fontnum, glyph in text:
1416+
if fontnum != oldfontnum:
1417+
texname, fontsize = dvi.fontinfo(fontnum)
1418+
fontinfo = self.tex_font_mapping(texname)
1419+
pdfname = self.file.fontName(fontinfo.filename)
1420+
self.file.fontInfo[pdfname] = Bunch(
1421+
encodingfile=fontinfo.encoding,
1422+
afmfile=fontinfo.afm)
1423+
self.file.output(pdfname, fontsize, Op.selectfont)
1424+
oldfontnum = fontnum
13141425
x1, y1 = mytrans(x1, y1)
13151426
self._setup_textpos(x1, y1, angle, oldx, oldy)
13161427
self.file.output(chr(glyph), Op.show)

lib/matplotlib/dviread.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,11 @@ def __init__(self, filename):
410410

411411
def __getitem__(self, texname):
412412
result = self._font[texname]
413-
if result.filename is not None \
414-
and not result.filename.startswith('/'):
415-
result.filename = find_tex_file(result.filename)
416-
if result.encoding is not None \
417-
and not result.encoding.startswith('/'):
413+
fn, enc = result.filename, result.encoding
414+
if fn is not None and not fn.startswith('/'):
415+
result.filename = find_tex_file(fn)
416+
result.afm = find_tex_file(fn[:-4] + '.afm')
417+
if enc is not None and not enc.startswith('/'):
418418
result.encoding = find_tex_file(result.encoding)
419419
return result
420420

@@ -473,6 +473,51 @@ def _register(self, words):
473473
texname=texname, psname=psname, effects=effects,
474474
encoding=encoding, filename=filename)
475475

476+
class Encoding(object):
477+
478+
def __init__(self, filename):
479+
file = open(filename, 'rt')
480+
try:
481+
self.encoding = self._parse(file)
482+
finally:
483+
file.close()
484+
485+
def __iter__(self):
486+
for name in self.encoding:
487+
yield name
488+
489+
def _parse(self, file):
490+
result = []
491+
492+
state = 0
493+
for line in file:
494+
comment_start = line.find('%')
495+
if comment_start > -1:
496+
line = line[:comment_start]
497+
line = line.strip()
498+
499+
if state == 0:
500+
# Expecting something like /FooEncoding [
501+
if '[' in line:
502+
state = 1
503+
line = line[line.index('[')+1].strip()
504+
505+
if state == 1:
506+
words = line.split()
507+
for w in words:
508+
if w.startswith('/'):
509+
# Allow for /abc/def/ghi
510+
subwords = w.split('/')
511+
result.extend(subwords[1:])
512+
else:
513+
raise ValueError, "Broken name in encoding file: " + w
514+
515+
# Expecting ] def
516+
if ']' in line:
517+
break
518+
519+
return result
520+
476521
def find_tex_file(filename, format=None):
477522
"""
478523
Call kpsewhich to find a file in the texmf tree.

0 commit comments

Comments
 (0)