1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2007-2011 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2007-2011 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 import io
, logging
, os
.path
, re
24 from pyx
import font
, config
25 from pyx
.font
import t1file
, afmfile
, pfmfile
26 from pyx
.dvi
import encfile
28 logger
= logging
.getLogger("pyx")
30 class UnsupportedFontFormat(Exception):
33 class UnsupportedPSFragment(Exception):
36 class ParseError(Exception):
43 tokenpattern
= re
.compile(r
'"(.*?)("\s+|"$|$)|(.*?)(\s+|$)')
45 def __init__(self
, s
):
46 """ construct font mapping from line s of font mapping file """
47 self
.texname
= self
.basepsname
= self
.fontfilename
= None
50 self
.encodingfilename
= None
52 # supported postscript fragments occuring in psfonts.map
53 # XXX extendfont not yet implemented
54 self
.reencodefont
= self
.extendfont
= self
.slant
= None
56 # cache for openend font and encoding
58 self
._encoding
= _marker
62 match
= self
.tokenpattern
.match(s
)
64 if match
.groups()[0] is not None:
65 tokens
.append('"%s"' % match
.groups()[0])
67 tokens
.append(match
.groups()[2])
70 raise ParseError("Cannot tokenize string '%s'" % s
)
72 next_token_is_encfile
= False
74 if next_token_is_encfile
:
75 self
.encodingfilename
= token
76 next_token_is_encfile
= False
77 elif token
.startswith("<"):
79 next_token_is_encfile
= True
80 elif token
.startswith("<<"):
81 # XXX: support non-partial download here
82 self
.fontfilename
= token
[2:]
83 elif token
.startswith("<["):
84 self
.encodingfilename
= token
[2:]
85 elif token
.endswith(".pfa") or token
.endswith(".pfb"):
86 self
.fontfilename
= token
[1:]
87 elif token
.endswith(".enc"):
88 self
.encodingfilename
= token
[1:]
89 elif token
.endswith(".ttf"):
90 raise UnsupportedFontFormat("TrueType font")
91 elif token
.endswith(".t42"):
92 raise UnsupportedFontFormat("Type 42 font")
94 raise ParseError("Unknown token '%s'" % token
)
95 elif token
.startswith('"'):
96 pscode
= token
[1:-1].split()
97 # parse standard postscript code fragments
100 arg
, cmd
= pscode
[:2]
102 raise UnsupportedPSFragment("Unsupported Postscript fragment '%s'" % pscode
)
104 if cmd
== "ReEncodeFont":
105 self
.reencodefont
= arg
106 elif cmd
== "ExtendFont":
107 self
.extendfont
= arg
108 elif cmd
== "SlantFont":
109 self
.slant
= float(arg
)
111 raise UnsupportedPSFragment("Unsupported Postscript fragment '%s %s'" % (arg
, cmd
))
113 if self
.texname
is None:
116 self
.basepsname
= token
117 if self
.basepsname
is None:
118 self
.basepsname
= self
.texname
120 def getfontname(self
):
121 return self
.basepsname
124 if self
._font
is None:
125 if self
.fontfilename
is not None:
126 with config
.open(self
.fontfilename
, [config
.format
.type1
]) as fontfile
:
127 t1font
= t1file
.T1File
.from_PF_bytes(fontfile
.read())
128 assert self
.basepsname
== t1font
.name
, "corrupt MAP file"
130 with config
.open(os
.path
.splitext(self
.fontfilename
)[0], [config
.format
.afm
], ascii
=True) as metricfile
:
131 self
._font
= font
.T1font(t1font
, afmfile
.AFMfile(metricfile
))
132 except EnvironmentError:
134 # fallback by using the pfm instead of the afm font metric
135 # (in all major TeX distributions there is no pfm file format defined by kpsewhich, but
136 # we can use the type1 format and search for the file including the expected suffix)
137 with config
.open("%s.pfm" % os
.path
.splitext(self
.fontfilename
)[0], [config
.format
.type1
]) as metricfile
:
138 self
._font
= font
.T1font(t1font
, pfmfile
.PFMfile(metricfile
, t1font
))
139 except EnvironmentError:
140 # we need to continue without any metric file
141 self
._font
= font
.T1font(t1font
)
144 with config
.open(self
.basepsname
, [config
.format
.afm
], ascii
=True) as metricfile
:
145 self
._font
= font
.T1builtinfont(self
.basepsname
, afmfile
.AFMfile(metricfile
))
148 def getencoding(self
):
149 if self
._encoding
is _marker
:
150 if self
.encodingfilename
is not None:
151 with config
.open(self
.encodingfilename
, [config
.format
.tex_ps_header
]) as encodingfile
:
152 ef
= encfile
.ENCfile(encodingfile
.read().decode("ascii", errors
="surrogateescape"))
153 assert ef
.name
== "/%s" % self
.reencodefont
154 self
._encoding
= ef
.vector
157 self
._encoding
= None
158 return self
._encoding
161 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
162 (self
.texname
, self
.basepsname
, self
.fontfile
, repr(self
.encodingfile
)))
166 def readfontmap(filenames
):
167 """ read font map from filename (without path) """
169 for filename
in filenames
:
170 with config
.open(filename
, [config
.format
.fontmap
, config
.format
.dvips_config
], ascii
=True) as mapfile
:
172 for line
in mapfile
.readlines():
175 if not (line
=="" or line
[0] in (" ", "%", "*", ";" , "#")):
178 except (ParseError
, UnsupportedPSFragment
) as e
:
179 logger
.warning("Ignoring line %i in mapping file '%s': %s" % (lineno
, filename
, e
))
180 except UnsupportedFontFormat
as e
:
183 fontmap
[fm
.texname
] = fm