- path module
[PyX/mjg.git] / pyx / pswriter.py
blobbc80a7b82c0d305cce4142bcee725560678947f6
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2005-2006 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2005-2006 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 cStringIO, copy, time, math
25 import bbox, 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 mergeregistry(self, registry):
54 for resource in registry.resources:
55 self.add(resource)
57 def output(self, file, writer):
58 """ write all PostScript code of the prolog resources """
59 for resource in self.resourceslist:
60 resource.output(file, writer, self)
63 # Abstract base class
66 class PSresource:
68 """ a PostScript resource """
70 def __init__(self, type, id):
71 # Every PSresource has to have a type and a unique id.
72 # Resources with the same type and id will be merged
73 # when they are registered in the PSregistry
74 self.type = type
75 self.id = id
77 def merge(self, other):
78 """ merge self with other, which has to be a resource of the same type and with
79 the same id"""
80 pass
82 def output(self, file, writer, registry):
83 raise NotImplementedError("output not implemented for %s" % repr(self))
86 # Different variants of prolog items
89 class PSdefinition(PSresource):
91 """ PostScript function definition included in the prolog """
93 def __init__(self, id, body):
94 self.type = "definition"
95 self.id = id
96 self.body = body
98 def output(self, file, writer, registry):
99 file.write("%%%%BeginRessource: %s\n" % self.id)
100 file.write("%(body)s /%(id)s exch def\n" % self.__dict__)
101 file.write("%%EndRessource\n")
104 class PSfont:
106 def __init__(self, font, chars, registry):
107 if font.filename:
108 registry.add(PSfontfile(font.basefontname,
109 font.filename,
110 font.encoding,
111 chars))
112 if font.encoding:
113 registry.add(_ReEncodeFont)
114 registry.add(PSfontencoding(font.encoding))
115 registry.add(PSfontreencoding(font.name,
116 font.basefontname,
117 font.encoding.name))
120 class PSfontfile(PSresource):
122 """ PostScript font definition included in the prolog """
124 def __init__(self, name, filename, encoding, chars):
125 """ include type 1 font defined by the following parameters
127 - name: name of the PostScript font
128 - filename: name (without path) of file containing the font definition
129 - encfilename: name (without path) of file containing used encoding of font
130 or None (if no encoding file used)
131 - chars: character list to fill usedchars
135 # Note that here we only need the encoding for selecting the used glyphs!
137 self.type = "fontfile"
138 self.id = self.name = name
139 self.filename = filename
140 if encoding is None:
141 self.encodingfilename = None
142 else:
143 self.encodingfilename = encoding.filename
144 self.usedchars = {}
145 for char in chars:
146 self.usedchars[char] = 1
148 self.strip = 1
150 def merge(self, other):
151 if self.encodingfilename == other.encodingfilename:
152 self.usedchars.update(other.usedchars)
153 else:
154 # TODO: need to resolve the encoding when several encodings are in the play
155 self.strip = 0
157 def output(self, file, writer, registry):
158 import font.t1font
159 font = font.t1font.T1pfbfont(self.filename)
161 file.write("%%%%BeginFont: %s\n" % self.name)
162 # file.write("%%Included glyphs: %s\n" % " ".join(usedglyphs))
163 if self.strip:
164 # XXX: access to the encoding file
165 if self.encodingfilename:
166 encodingfile = type1font.encodingfile(self.encodingfilename, self.encodingfilename)
167 usedglyphs = [encodingfile.decode(char)[1:] for char in self.usedchars.keys()]
168 else:
169 font._encoding()
170 usedglyphs = [font.encoding.decode(char) for char in self.usedchars.keys()]
171 strippedfont = font.getstrippedfont(usedglyphs)
172 else:
173 strippedfont = font
174 strippedfont.outputPS(file)
175 file.write("\n%%EndFont\n")
178 class PSfontencoding(PSresource):
180 """ PostScript font encoding vector included in the prolog """
182 def __init__(self, encoding):
183 """ include font encoding vector specified by encoding """
185 self.type = "fontencoding"
186 self.id = encoding.name
187 self.encoding = encoding
189 def output(self, file, writer, registry):
190 encodingfile = type1font.encodingfile(self.encoding.name, self.encoding.filename)
191 encodingfile.outputPS(file, writer)
194 class PSfontreencoding(PSresource):
196 """ PostScript font re-encoding directive included in the prolog """
198 def __init__(self, fontname, basefontname, encodingname):
199 """ include font re-encoding directive specified by
201 - fontname: PostScript FontName of the new reencoded font
202 - basefontname: PostScript FontName of the original font
203 - encname: name of the encoding
204 - font: a reference to the font instance (temporarily added for pdf support)
206 Before being able to reencode a font, you have to include the
207 encoding via a fontencoding prolog item with name=encname
211 self.type = "fontreencoding"
212 self.id = self.fontname = fontname
213 self.basefontname = basefontname
214 self.encodingname = encodingname
216 def output(self, file, writer, registry):
217 file.write("%%%%BeginProcSet: %s\n" % self.fontname)
218 file.write("/%s /%s %s ReEncodeFont\n" % (self.basefontname, self.fontname, self.encodingname))
219 file.write("%%EndProcSet\n")
222 _ReEncodeFont = PSdefinition("ReEncodeFont", """{
223 5 dict
224 begin
225 /newencoding exch def
226 /newfontname exch def
227 /basefontname exch def
228 /basefontdict basefontname findfont def
229 /newfontdict basefontdict maxlength dict def
230 basefontdict {
231 exch dup dup /FID ne exch /Encoding ne and
232 { exch newfontdict 3 1 roll put }
233 { pop pop }
234 ifelse
235 } forall
236 newfontdict /FontName newfontname put
237 newfontdict /Encoding newencoding put
238 newfontname newfontdict definefont pop
240 }""")
243 class epswriter:
245 def __init__(self, document, filename):
246 if len(document.pages) != 1:
247 raise ValueError("EPS file can be construced out of a single page document only")
248 page = document.pages[0]
249 canvas = page.canvas
251 if not filename.endswith(".eps"):
252 filename = filename + ".eps"
253 try:
254 file = open(filename, "w")
255 except IOError:
256 raise IOError("cannot open output file")
258 canvasfile = cStringIO.StringIO()
259 registry = PSregistry()
260 acontext = context()
261 abbox = bbox.empty()
263 style.linewidth.normal.processPS(canvasfile, self, acontext, registry, abbox)
265 # here comes the page content
266 page.processPS(canvasfile, self, acontext, registry, abbox)
268 pagetrafo = page.pagetrafo(abbox)
270 # if a page transformation is necessary, we have to adjust the bounding box
271 # accordingly
272 if pagetrafo is not None:
273 abbox.transform(pagetrafo)
275 file.write("%!PS-Adobe-3.0 EPSF-3.0\n")
276 if bbox:
277 file.write("%%%%BoundingBox: %d %d %d %d\n" % abbox.lowrestuple_pt())
278 file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % abbox.highrestuple_pt())
279 file.write("%%%%Creator: PyX %s\n" % version.version)
280 file.write("%%%%Title: %s\n" % filename)
281 file.write("%%%%CreationDate: %s\n" %
282 time.asctime(time.localtime(time.time())))
283 file.write("%%EndComments\n")
285 file.write("%%BeginProlog\n")
286 registry.output(file, self)
287 file.write("%%EndProlog\n")
289 # apply a possible page transformation (using fake context and registry)
290 if pagetrafo:
291 pagetrafo.processPS(file, self, context(), PSregistry(), abbox)
293 file.write(canvasfile.getvalue())
295 file.write("showpage\n")
296 file.write("%%Trailer\n")
297 file.write("%%EOF\n")
300 class pswriter:
302 def __init__(self, document, filename, writebbox=0):
303 if not filename.endswith(".ps"):
304 filename = filename + ".ps"
305 try:
306 file = open(filename, "w")
307 except IOError:
308 raise IOError("cannot open output file")
311 # We first have to process the content of the pages, writing them into the stream pagesfile
312 # Doing so, we fill the registry and also calculate the page bounding boxes, which are
313 # stored in page._bbox for every page
314 pagesfile = cStringIO.StringIO()
315 registry = PSregistry()
317 # calculated bounding boxes of the whole document
318 documentbbox = bbox.empty()
320 for nr, page in enumerate(document.pages):
321 # process conents of page
322 pagefile = cStringIO.StringIO()
323 acontext = context()
324 pagebbox = bbox.empty()
325 style.linewidth.normal.processPS(pagefile, self, acontext, registry, pagebbox)
326 page.canvas.processPS(pagefile, self, acontext, registry, pagebbox)
328 # calculate transformed bbox
329 pagetrafo = page.pagetrafo(pagebbox)
330 # if a page transformation is necessary, we have to adjust the bounding box
331 # accordingly
332 if pagetrafo:
333 pagebbox = pagebbox.transformed(pagetrafo)
335 documentbbox += pagebbox
337 pagesfile.write("%%%%Page: %s %d\n" % (page.pagename is None and str(nr+1) or page.pagename, nr+1))
338 if page.paperformat:
339 pagesfile.write("%%%%PageMedia: %s\n" % page.paperformat.name)
340 pagesfile.write("%%%%PageOrientation: %s\n" % (page.rotated and "Landscape" or "Portrait"))
341 if pagebbox and writebbox:
342 pagesfile.write("%%%%PageBoundingBox: %d %d %d %d\n" % pagebbox.lowrestuple_pt())
344 # page setup section
345 pagesfile.write("%%BeginPageSetup\n")
346 pagesfile.write("/pgsave save def\n")
348 # apply a possible page transformation
349 if pagetrafo is not None:
350 pagetrafo.processPS(pagesfile, self, acontext, registry, bbox.empty())
352 pagesfile.write("%%EndPageSetup\n")
353 # insert page content
354 pagesfile.write(pagefile.getvalue())
355 pagesfile.write("pgsave restore\n")
356 pagesfile.write("showpage\n")
357 pagesfile.write("%%PageTrailer\n")
359 file.write("%!PS-Adobe-3.0\n")
360 if documentbbox and writebbox:
361 file.write("%%%%BoundingBox: %d %d %d %d\n" % documentbbox.lowrestuple_pt())
362 file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % documentbbox.highrestuple_pt())
363 file.write("%%%%Creator: PyX %s\n" % version.version)
364 file.write("%%%%Title: %s\n" % filename)
365 file.write("%%%%CreationDate: %s\n" %
366 time.asctime(time.localtime(time.time())))
368 # required paper formats
369 paperformats = {}
370 for page in document.pages:
371 if page.paperformat:
372 paperformats[page.paperformat] = page.paperformat
374 first = 1
375 for paperformat in paperformats.values():
376 if first:
377 file.write("%%DocumentMedia: ")
378 first = 0
379 else:
380 file.write("%%+ ")
381 file.write("%s %d %d 75 white ()\n" % (paperformat.name,
382 unit.topt(paperformat.width),
383 unit.topt(paperformat.height)))
385 # file.write(%%DocumentNeededResources: ") # register not downloaded fonts here
387 file.write("%%%%Pages: %d\n" % len(document.pages))
388 file.write("%%PageOrder: Ascend\n")
389 file.write("%%EndComments\n")
391 # document defaults section
392 #file.write("%%BeginDefaults\n")
393 #file.write("%%EndDefaults\n")
395 # document prolog section
396 file.write("%%BeginProlog\n")
397 registry.output(file, self)
398 file.write("%%EndProlog\n")
400 # document setup section
401 #file.write("%%BeginSetup\n")
402 #file.write("%%EndSetup\n")
404 file.write(pagesfile.getvalue())
406 file.write("%%Trailer\n")
407 file.write("%%EOF\n")
409 class context:
411 def __init__(self):
412 self.linewidth_pt = None
413 self.colorspace = None
414 self.font = None
416 def __call__(self, **kwargs):
417 newcontext = copy.copy(self)
418 for key, value in kwargs.items():
419 setattr(newcontext, key, value)
420 return newcontext