added first version of AFM parser#
[PyX/mjg.git] / pyx / pswriter.py
blob154f20ca6a23f4c5ad35341450ed2bf103118f3b
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 copy, time, math
25 import style, version, type1font, unit
27 try:
28 enumerate([])
29 except NameError:
30 # fallback implementation for Python 2.2 and below
31 def enumerate(list):
32 return zip(xrange(len(list)), list)
35 class PSregistry:
37 def __init__(self):
38 # in order to keep a consistent order of the registered resources we
39 # not only store them in a hash but also keep an ordered list (up to a
40 # possible merging of resources, in which case the first instance is
41 # kept)
42 self.resourceshash = {}
43 self.resourceslist = []
45 def add(self, resource):
46 rkey = (resource.type, resource.id)
47 if self.resourceshash.has_key(rkey):
48 self.resourceshash[rkey].merge(resource)
49 else:
50 self.resourceshash[rkey] = resource
51 self.resourceslist.append(resource)
53 def outputPS(self, file, writer):
54 """ write all PostScript code of the prolog resources """
55 for resource in self.resourceslist:
56 resource.outputPS(file, writer, self)
59 # Abstract base class
62 class PSresource:
64 """ a PostScript resource """
66 def __init__(self, type, id):
67 # Every PSresource has to have a type and a unique id.
68 # Resources with the same type and id will be merged
69 # when they are registered in the PSregistry
70 self.type = type
71 self.id = id
73 def merge(self, other):
74 """ merge self with other, which has to be a resource of the same type and with
75 the same id"""
76 pass
78 def outputPS(self, file, writer, registry):
79 raise NotImplementedError("outputPS not implemented for %s" % repr(self))
82 # Different variants of prolog items
85 class PSdefinition(PSresource):
87 """ PostScript function definition included in the prolog """
89 def __init__(self, id, body):
90 self.type = "definition"
91 self.id = id
92 self.body = body
94 def outputPS(self, file, writer, registry):
95 file.write("%%%%BeginRessource: %s\n" % self.id)
96 file.write("%(body)s /%(id)s exch def\n" % self.__dict__)
97 file.write("%%EndRessource\n")
100 class PSfont:
102 def __init__(self, font, chars, registry):
103 if font.filename:
104 registry.add(PSfontfile(font.basefontname,
105 font.filename,
106 font.encoding,
107 chars))
108 if font.encoding:
109 registry.add(_ReEncodeFont)
110 registry.add(PSfontencoding(font.encoding))
111 registry.add(PSfontreencoding(font.name,
112 font.basefontname,
113 font.encoding.name))
116 class PSfontfile(PSresource):
118 """ PostScript font definition included in the prolog """
120 def __init__(self, name, filename, encoding, chars):
121 """ include type 1 font defined by the following parameters
123 - name: name of the PostScript font
124 - filename: name (without path) of file containing the font definition
125 - encfilename: name (without path) of file containing used encoding of font
126 or None (if no encoding file used)
127 - chars: character list to fill usedchars
131 # Note that here we only need the encoding for selecting the used glyphs!
133 self.type = "fontfile"
134 self.id = self.name = name
135 self.filename = filename
136 if encoding is None:
137 self.encodingfilename = None
138 else:
139 self.encodingfilename = encoding.filename
140 self.usedchars = {}
141 for char in chars:
142 self.usedchars[char] = 1
144 self.strip = 1
146 def merge(self, other):
147 if self.encodingfilename == other.encodingfilename:
148 self.usedchars.update(other.usedchars)
149 else:
150 # TODO: need to resolve the encoding when several encodings are in the play
151 self.strip = 0
153 def outputPS(self, file, writer, registry):
154 import font.t1font
155 font = font.t1font.T1pfbfont(self.filename)
157 file.write("%%%%BeginFont: %s\n" % self.name)
158 # file.write("%%Included glyphs: %s\n" % " ".join(usedglyphs))
159 if self.strip:
160 # XXX: access to the encoding file
161 if self.encodingfilename:
162 encodingfile = type1font.encodingfile(self.encodingfilename, self.encodingfilename)
163 usedglyphs = [encodingfile.decode(char)[1:] for char in self.usedchars.keys()]
164 else:
165 font._encoding()
166 usedglyphs = [font.encoding.decode(char) for char in self.usedchars.keys()]
167 strippedfont = font.getstrippedfont(usedglyphs)
168 else:
169 strippedfont = font
170 strippedfont.outputPS(file)
171 file.write("\n%%EndFont\n")
174 class PSfontencoding(PSresource):
176 """ PostScript font encoding vector included in the prolog """
178 def __init__(self, encoding):
179 """ include font encoding vector specified by encoding """
181 self.type = "fontencoding"
182 self.id = encoding.name
183 self.encoding = encoding
185 def outputPS(self, file, writer, registry):
186 encodingfile = type1font.encodingfile(self.encoding.name, self.encoding.filename)
187 encodingfile.outputPS(file, writer, registry)
190 class PSfontreencoding(PSresource):
192 """ PostScript font re-encoding directive included in the prolog """
194 def __init__(self, fontname, basefontname, encodingname):
195 """ include font re-encoding directive specified by
197 - fontname: PostScript FontName of the new reencoded font
198 - basefontname: PostScript FontName of the original font
199 - encname: name of the encoding
200 - font: a reference to the font instance (temporarily added for pdf support)
202 Before being able to reencode a font, you have to include the
203 encoding via a fontencoding prolog item with name=encname
207 self.type = "fontreencoding"
208 self.id = self.fontname = fontname
209 self.basefontname = basefontname
210 self.encodingname = encodingname
212 def outputPS(self, file, writer, registry):
213 file.write("%%%%BeginProcSet: %s\n" % self.fontname)
214 file.write("/%s /%s %s ReEncodeFont\n" % (self.basefontname, self.fontname, self.encodingname))
215 file.write("%%EndProcSet\n")
218 _ReEncodeFont = PSdefinition("ReEncodeFont", """{
219 5 dict
220 begin
221 /newencoding exch def
222 /newfontname exch def
223 /basefontname exch def
224 /basefontdict basefontname findfont def
225 /newfontdict basefontdict maxlength dict def
226 basefontdict {
227 exch dup dup /FID ne exch /Encoding ne and
228 { exch newfontdict 3 1 roll put }
229 { pop pop }
230 ifelse
231 } forall
232 newfontdict /FontName newfontname put
233 newfontdict /Encoding newencoding put
234 newfontname newfontdict definefont pop
236 }""")
239 class epswriter:
241 def __init__(self, document, filename):
242 if len(document.pages) != 1:
243 raise ValueError("EPS file can be construced out of a single page document only")
244 page = document.pages[0]
245 canvas = page.canvas
247 if not filename.endswith(".eps"):
248 filename = filename + ".eps"
249 try:
250 file = open(filename, "w")
251 except IOError:
252 raise IOError("cannot open output file")
254 bbox = page.bbox()
255 pagetrafo = page.pagetrafo(bbox)
257 # if a page transformation is necessary, we have to adjust the bounding box
258 # accordingly
259 if pagetrafo is not None:
260 bbox.transform(pagetrafo)
262 file.write("%!PS-Adobe-3.0 EPSF-3.0\n")
263 if bbox:
264 file.write("%%%%BoundingBox: %d %d %d %d\n" % bbox.lowrestuple_pt())
265 file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % bbox.highrestuple_pt())
266 file.write("%%%%Creator: PyX %s\n" % version.version)
267 file.write("%%%%Title: %s\n" % filename)
268 file.write("%%%%CreationDate: %s\n" %
269 time.asctime(time.localtime(time.time())))
270 file.write("%%EndComments\n")
272 file.write("%%BeginProlog\n")
273 registry = PSregistry()
274 canvas.registerPS(registry)
275 registry.outputPS(file, self)
276 file.write("%%EndProlog\n")
278 acontext = context()
279 # apply a possible page transformation
280 if pagetrafo:
281 pagetrafo.outputPS(file, self, acontext)
283 style.linewidth.normal.outputPS(file, self, acontext)
285 # here comes the canvas content
286 canvas.outputPS(file, self, acontext)
288 file.write("showpage\n")
289 file.write("%%Trailer\n")
290 file.write("%%EOF\n")
293 class pswriter:
295 def __init__(self, document, filename, writebbox=0):
296 if not filename.endswith(".ps"):
297 filename = filename + ".ps"
298 try:
299 file = open(filename, "w")
300 except IOError:
301 raise IOError("cannot open output file")
303 # calculated bounding boxes of separate pages and the bounding box of the whole document
304 documentbbox = None
305 for page in document.pages:
306 canvas = page.canvas
307 page._bbox = page.bbox()
308 page._pagetrafo = page.pagetrafo(page._bbox)
309 # if a page transformation is necessary, we have to adjust the bounding box
310 # accordingly
311 if page._pagetrafo:
312 page._transformedbbox = page._bbox.transformed(page._pagetrafo)
313 else:
314 page._transformedbbox = page._bbox
315 if page._transformedbbox:
316 if documentbbox:
317 documentbbox += page._transformedbbox
318 else:
319 documentbbox = page._transformedbbox.copy() # make a copy
321 file.write("%!PS-Adobe-3.0\n")
322 if documentbbox and writebbox:
323 file.write("%%%%BoundingBox: %d %d %d %d\n" % documentbbox.lowrestuple_pt())
324 file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % documentbbox.highrestuple_pt())
325 file.write("%%%%Creator: PyX %s\n" % version.version)
326 file.write("%%%%Title: %s\n" % filename)
327 file.write("%%%%CreationDate: %s\n" %
328 time.asctime(time.localtime(time.time())))
330 # required paper formats
331 paperformats = {}
332 for page in document.pages:
333 if page.paperformat:
334 paperformats[page.paperformat] = page.paperformat
336 first = 1
337 for paperformat in paperformats.values():
338 if first:
339 file.write("%%DocumentMedia: ")
340 first = 0
341 else:
342 file.write("%%+ ")
343 file.write("%s %d %d 75 white ()\n" % (paperformat.name,
344 unit.topt(paperformat.width),
345 unit.topt(paperformat.height)))
347 # file.write(%%DocumentNeededResources: ") # register not downloaded fonts here
349 file.write("%%%%Pages: %d\n" % len(document.pages))
350 file.write("%%PageOrder: Ascend\n")
351 file.write("%%EndComments\n")
353 # document defaults section
354 #file.write("%%BeginDefaults\n")
355 #file.write("%%EndDefaults\n")
357 # document prolog section
358 file.write("%%BeginProlog\n")
359 registry = PSregistry()
360 for page in document.pages:
361 page.canvas.registerPS(registry)
362 registry.outputPS(file, self)
363 file.write("%%EndProlog\n")
365 # document setup section
366 #file.write("%%BeginSetup\n")
367 #file.write("%%EndSetup\n")
369 # pages section
370 for nr, page in enumerate(document.pages):
371 file.write("%%%%Page: %s %d\n" % (page.pagename is None and str(nr+1) or page.pagename, nr+1))
372 if page.paperformat:
373 file.write("%%%%PageMedia: %s\n" % page.paperformat.name)
374 file.write("%%%%PageOrientation: %s\n" % (page.rotated and "Landscape" or "Portrait"))
375 if page._transformedbbox and writebbox:
376 file.write("%%%%PageBoundingBox: %d %d %d %d\n" % page._transformedbbox.lowrestuple_pt())
378 # page setup section
379 file.write("%%BeginPageSetup\n")
380 file.write("/pgsave save def\n")
382 acontext = context()
383 # apply a possible page transformation
384 if page._pagetrafo is not None:
385 page._pagetrafo.outputPS(file, self, acontext)
387 style.linewidth.normal.outputPS(file, self, acontext)
388 file.write("%%EndPageSetup\n")
390 # here comes the actual content
391 page.canvas.outputPS(file, self, acontext)
392 file.write("pgsave restore\n")
393 file.write("showpage\n")
394 file.write("%%PageTrailer\n")
396 file.write("%%Trailer\n")
397 file.write("%%EOF\n")
399 class context:
401 def __init__(self):
402 self.linewidth_pt = None
403 self.colorspace = None
404 self.font = None
406 def __call__(self, **kwargs):
407 newcontext = copy.copy(self)
408 for key, value in kwargs.items():
409 setattr(newcontext, key, value)
410 return newcontext