prevent double call of _cleanup, which harms usefiles (and is a bad idea in general)
[PyX.git] / pyx / dvi / mapfile.py
blob6d593081e0f9443d885182dd79e33abd960cd68f
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):
31 pass
33 class UnsupportedPSFragment(Exception):
34 pass
36 class ParseError(Exception):
37 pass
39 _marker = object()
41 class MAPline:
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
49 # standard encoding
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
57 self._font = None
58 self._encoding = _marker
60 tokens = []
61 while len(s):
62 match = self.tokenpattern.match(s)
63 if match:
64 if match.groups()[0] is not None:
65 tokens.append('"%s"' % match.groups()[0])
66 else:
67 tokens.append(match.groups()[2])
68 s = s[match.end():]
69 else:
70 raise ParseError("Cannot tokenize string '%s'" % s)
72 next_token_is_encfile = False
73 for token in tokens:
74 if next_token_is_encfile:
75 self.encodingfilename = token
76 next_token_is_encfile = False
77 elif token.startswith("<"):
78 if token == "<":
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")
93 else:
94 raise ParseError("Unknown token '%s'" % token)
95 elif token.startswith('"'):
96 pscode = token[1:-1].split()
97 # parse standard postscript code fragments
98 while pscode:
99 try:
100 arg, cmd = pscode[:2]
101 except:
102 raise UnsupportedPSFragment("Unsupported Postscript fragment '%s'" % pscode)
103 pscode = pscode[2:]
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)
110 else:
111 raise UnsupportedPSFragment("Unsupported Postscript fragment '%s %s'" % (arg, cmd))
112 else:
113 if self.texname is None:
114 self.texname = token
115 else:
116 self.basepsname = token
117 if self.basepsname is None:
118 self.basepsname = self.texname
120 def getfontname(self):
121 return self.basepsname
123 def getfont(self):
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.from_PF_bytes(fontfile.read())
128 assert self.basepsname == t1font.name, "corrupt MAP file"
129 try:
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:
133 try:
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)
142 else:
143 # builtin font
144 with config.open(self.basepsname, [config.format.afm], ascii=True) as metricfile:
145 self._font = font.T1builtinfont(self.basepsname, afmfile.AFMfile(metricfile))
146 return self._font
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
156 else:
157 self._encoding = None
158 return self._encoding
160 def __str__(self):
161 return ("'%s' is '%s' read from '%s' encoded as '%s'" %
162 (self.texname, self.basepsname, self.fontfile, repr(self.encodingfile)))
164 # generate fontmap
166 def readfontmap(filenames):
167 """ read font map from filename (without path) """
168 fontmap = {}
169 for filename in filenames:
170 with config.open(filename, [config.format.fontmap, config.format.dvips_config], ascii=True) as mapfile:
171 lineno = 0
172 for line in mapfile.readlines():
173 lineno += 1
174 line = line.rstrip()
175 if not (line=="" or line[0] in (" ", "%", "*", ";" , "#")):
176 try:
177 fm = MAPline(line)
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:
181 pass
182 else:
183 fontmap[fm.texname] = fm
184 return fontmap