correct a bug in realpolyroots
[PyX/mjg.git] / pyx / type1font.py
bloba1ae1bd51e9b985c5e69f132801035798f89dc56
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2005 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2005 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 import re, tempfile, os, binascii
25 try:
26 import zlib
27 except:
28 pass
29 import bbox, canvas, pswriter, pdfwriter, t1strip
31 try:
32 enumerate([])
33 except NameError:
34 # fallback implementation for Python 2.2 and below
35 def enumerate(list):
36 return zip(xrange(len(list)), list)
39 # _PFB_ASCII = "\200\1"
40 # _PFB_BIN = "\200\2"
41 # _PFB_DONE = "\200\3"
42 # _PFA = "%!"
44 _StandardEncodingMatch = re.compile(r"\b/Encoding\s+StandardEncoding\s+def\b")
45 #_FontBBoxMatch = re.compile(r"\bFontBBox\s*\{\s*(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s*\}\s*readonly\s+def\b")
46 #_ItalicAngle = re.compile(r"\bItalicAngle\s+(-?\d+)\b")
49 def _pfblength(s):
50 if len(s) != 4:
51 raise ValueError("invalid string length")
52 return (ord(s[0]) +
53 ord(s[1])*256 +
54 ord(s[2])*256*256 +
55 ord(s[3])*256*256*256)
57 class _tokenfile:
58 """ ascii file containing tokens separated by spaces.
60 Comments beginning with % are ignored. Strings containing spaces
61 are not handled correctly
62 """
64 def __init__(self, filename):
65 self.file = open(filename, "r")
66 self.line = None
68 def gettoken(self):
69 """ return next token or None if EOF """
70 while not self.line:
71 line = self.file.readline()
72 if line == "":
73 return None
74 self.line = line.split("%")[0].split()
75 token = self.line[0]
76 self.line = self.line[1:]
77 return token
79 def close(self):
80 self.file.close()
83 class encoding:
85 def __init__(self, name, filename):
86 """ font encoding contained in filename """
87 self.name = name
88 self.filename = filename
91 class encodingfile:
93 def __init__(self, name, filename):
94 self.name = name
95 encfile = _tokenfile(filename)
97 # name of encoding
98 self.encname = encfile.gettoken()
99 token = encfile.gettoken()
100 if token != "[":
101 raise RuntimeError("cannot parse encoding file '%s', expecting '[' got '%s'" % (filename, token))
102 self.encvector = []
103 for i in range(256):
104 token = encfile.gettoken()
105 if token is None or token=="]":
106 raise RuntimeError("not enough charcodes in encoding file '%s'" % filename)
107 self.encvector.append(token)
108 if encfile.gettoken() != "]":
109 raise RuntimeError("too many charcodes in encoding file '%s'" % filename)
110 token = encfile.gettoken()
111 if token != "def":
112 raise RuntimeError("cannot parse encoding file '%s', expecting 'def' got '%s'" % (filename, token))
113 token = encfile.gettoken()
114 if token != None:
115 raise RuntimeError("encoding file '%s' too long" % filename)
116 encfile.close()
118 # def decode(self, charcode):
119 # return self.encvector[charcode]
121 def outputPS(self, file, writer, registry):
122 file.write("%%%%BeginProcSet: %s\n" % self.name)
123 file.write("/%s\n"
124 "[" % self.name)
125 for i, glyphname in enumerate(self.encvector):
126 if i and not (i % 8):
127 file.write("\n")
128 else:
129 file.write(" ")
130 file.write(glyphname)
131 file.write(" ] def\n"
132 "%%EndProcSet\n")
134 def outputPDF(self, file, writer, registry):
135 file.write("<<\n"
136 "/Type /Encoding\n"
137 "/Differences\n"
138 "[ 0")
139 for i, glyphname in enumerate(self.encvector):
140 if i and not (i % 8):
141 file.write("\n")
142 else:
143 file.write(" ")
144 file.write(glyphname)
145 file.write(" ]\n"
146 ">>\n")
149 class font:
151 def __init__(self, basefontname, filename, encoding, metric):
152 self.basefontname = basefontname
153 self.filename = filename
154 self.encoding = encoding
155 self.metric = metric
157 if encoding is None:
158 self.name = basefontname
159 else:
160 self.name = "%s-%s" % (basefontname, encoding.name)
163 class fontfile:
165 # TODO: own stripping using glyph names instead of char codes; completely remove encoding
167 def __init__(self, fontname, fontfilename, usedchars, encodingfilename):
168 self.fontname = fontname
169 self.fontfilename = fontfilename
170 self.usedchars = [0]*256
171 for charcode in usedchars.keys():
172 self.usedchars[charcode] = 1
173 self.encodingfilename = encodingfilename
175 def getflags(self):
176 # As a simple heuristics we assume non-symbolic fonts if and only
177 # if the Adobe standard encoding is used. All other font flags are
178 # not specified here.
179 fontfile = open(self.fontfilename, "rb")
180 fontdata = fontfile.read()
181 fontfile.close()
182 if _StandardEncodingMatch.search(fontdata):
183 return 32
184 return 4
186 def outputPS(self, file, writer, registry):
187 file.write("%%%%BeginFont: %s\n" % self.fontname)
188 file.write("%Included char codes:")
189 for i in range(len(self.usedchars)):
190 if self.usedchars[i]:
191 file.write(" %d" % i)
192 file.write("\n")
193 if self.encodingfilename is not None:
194 t1strip.t1strip(file, self.fontfilename, self.usedchars, self.encodingfilename)
195 else:
196 t1strip.t1strip(file, self.fontfilename, self.usedchars)
197 file.write("%%EndFont\n")
199 def outputPDF(self, file, writer, registry):
200 strippedfontfilename = tempfile.mktemp()
201 strippedfontfile = open(strippedfontfilename, "w")
202 if self.usedchars:
203 if self.encodingfilename is not None:
204 t1strip.t1strip(strippedfontfile, self.fontfilename, self.usedchars, self.encodingfilename)
205 else:
206 t1strip.t1strip(strippedfontfile, self.fontfilename, self.usedchars)
207 strippedfontfile.close()
208 strippedfontfile = open(strippedfontfilename, "r")
209 fontdata = strippedfontfile.read()
210 strippedfontfile.close()
211 os.unlink(strippedfontfilename)
213 # split the font into its three parts
214 length1 = fontdata.index("currentfile eexec") + 18
215 length2 = fontdata.index("0"*20)
216 fontdata1 = fontdata[:length1]
217 fontdata2 = fontdata[length1:length2]
218 fontdata3 = fontdata[length2:]
219 length3 = len(fontdata3)
221 # convert to pfa
222 fontdata2 = binascii.a2b_hex(fontdata2.replace(" ", "").replace("\r", "").replace("\n", "").replace("\t", ""))
223 length2 = len(fontdata2)
225 # we might be allowed to skip the third part ...
226 if (fontdata3.replace("\n", "")
227 .replace("\r", "")
228 .replace("\t", "")
229 .replace(" ", "")) == "0"*512 + "cleartomark":
230 length3 = 0
231 fontdata3 = ""
233 data = fontdata1 + fontdata2 + fontdata3
234 if writer.compress:
235 data = zlib.compress(fontdata1 + fontdata2 + fontdata3)
237 file.write("<<\n"
238 "/Length %d\n"
239 "/Length1 %d\n"
240 "/Length2 %d\n"
241 "/Length3 %d\n" % (len(data), length1, length2, length3))
242 if writer.compress:
243 file.write("/Filter /FlateDecode\n")
244 file.write(">>\n"
245 "stream\n")
246 file.write(data)
247 file.write("\n"
248 "endstream\n")
251 class text_pt(canvas.canvasitem):
253 def __init__(self, x_pt, y_pt, font):
254 self.font = font
255 self.x_pt = x_pt
256 self.y_pt = y_pt
257 self.width_pt = 0
258 self.height_pt = 0
259 self.depth_pt = 0
260 self.chars = []
262 def addchar(self, char):
263 metric = self.font.metric
264 self.width_pt += metric.getwidth_pt(char)
265 cheight_pt = metric.getwidth_pt(char)
266 if cheight_pt > self.height_pt:
267 self.height_pt = cheight_pt
268 cdepth_pt = metric.getdepth_pt(char)
269 if cdepth_pt > self.depth_pt:
270 self.depth_pt = cdepth_pt
271 self.chars.append(char)
273 def bbox(self):
274 return bbox.bbox_pt(self.x_pt, self.y_pt-self.depth_pt, self.x_pt+self.width_pt, self.y_pt+self.height_pt)
276 def registerPS(self, registry):
277 # note that we don't register PSfont as it is just a helper resource
278 # which registers the needed components
279 pswriter.PSfont(self.font, self.chars, registry)
281 def registerPDF(self, registry):
282 registry.add(pdfwriter.PDFfont(self.font, self.chars, registry))
284 def outputPS(self, file, writer, context):
285 if ( context.font is None or
286 context.font.name != self.font.name or
287 context.font.metric.getsize_pt() != self.font.metric.getsize_pt() ):
288 file.write("/%s %f selectfont\n" % (self.font.name, self.font.metric.getsize_pt()))
289 context.font = self.font
290 outstring = ""
291 for char in self.chars:
292 if char > 32 and char < 127 and chr(char) not in "()[]<>\\":
293 ascii = "%s" % chr(char)
294 else:
295 ascii = "\\%03o" % char
296 outstring += ascii
297 file.write("%g %g moveto (%s) show\n" % (self.x_pt, self.y_pt, outstring))
300 def outputPDF(self, file, writer, context):
301 if ( context.font is None or
302 context.font.name != self.font.name or
303 context.font.metric.getsize_pt() != self.font.metric.getsize_pt() ):
304 file.write("/%s %f Tf\n" % (self.font.name, self.font.metric.getsize_pt()))
305 context.font = self.font
306 outstring = ""
307 for char in self.chars:
308 if 32 <= char <= 127 and chr(char) not in "()[]<>\\":
309 ascii = "%s" % chr(char)
310 else:
311 ascii = "\\%03o" % char
312 outstring += ascii
313 file.write("1 0 0 1 %f %f Tm (%s) Tj\n" % (self.x_pt, self.y_pt, outstring))