remove the pre-defined smooth instances
[PyX/mjg.git] / pyx / pswriter.py
blob402ea7e25643e999c9e363f7b62515407ac56baf
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
28 class PSregistry:
30 def __init__(self):
31 # in order to keep a consistent order of the registered resources we
32 # not only store them in a hash but also keep an ordered list (up to a
33 # possible merging of resources, in which case the first instance is
34 # kept)
35 self.resourceshash = {}
36 self.resourceslist = []
38 def add(self, resource):
39 rkey = (resource.type, resource.id)
40 if self.resourceshash.has_key(rkey):
41 self.resourceshash[rkey].merge(resource)
42 else:
43 self.resourceshash[rkey] = resource
44 self.resourceslist.append(resource)
46 def outputPS(self, file, writer):
47 """ write all PostScript code of the prolog resources """
48 for resource in self.resourceslist:
49 resource.outputPS(file, writer, self)
52 # Abstract base class
55 class PSresource:
57 """ a PostScript resource """
59 def __init__(self, type, id):
60 # Every PSresource has to have a type and a unique id.
61 # Resources with the same type and id will be merged
62 # when they are registered in the PSregistry
63 self.type = type
64 self.id = id
66 def merge(self, other):
67 """ merge self with other, which has to be a resource of the same type and with
68 the same id"""
69 pass
71 def outputPS(self, file, writer, registry):
72 raise NotImplementedError("outputPS not implemented for %s" % repr(self))
75 # Different variants of prolog items
78 class PSdefinition(PSresource):
80 """ PostScript function definition included in the prolog """
82 def __init__(self, id, body):
83 self.type = "definition"
84 self.id = id
85 self.body = body
87 def outputPS(self, file, writer, registry):
88 file.write("%%%%BeginRessource: %s\n" % self.id)
89 file.write("%(body)s /%(id)s exch def\n" % self.__dict__)
90 file.write("%%EndRessource\n")
93 class PSfont:
95 def __init__(self, font, chars, registry):
96 if font.filename:
97 registry.add(PSfontfile(font.basefontname,
98 font.filename,
99 font.encoding,
100 chars))
101 if font.encoding:
102 registry.add(_ReEncodeFont)
103 registry.add(PSfontencoding(font.encoding))
104 registry.add(PSfontreencoding(font.name,
105 font.basefontname,
106 font.encoding.name))
109 class PSfontfile(PSresource):
111 """ PostScript font definition included in the prolog """
113 def __init__(self, name, filename, encoding, chars):
114 """ include type 1 font defined by the following parameters
116 - name: name of the PostScript font
117 - filename: name (without path) of file containing the font definition
118 - encfilename: name (without path) of file containing used encoding of font
119 or None (if no encoding file used)
120 - chars: character list to fill usedchars
124 # Note that here we only need the encoding for selecting the used glyphs!
126 self.type = "fontfile"
127 self.id = self.name = name
128 self.filename = filename
129 if encoding is None:
130 self.encodingfilename = None
131 else:
132 self.encodingfilename = encoding.filename
133 self.usedchars = {}
134 for char in chars:
135 self.usedchars[char] = 1
137 def merge(self, other):
138 if self.encodingfilename == other.encodingfilename:
139 self.usedchars.update(other.usedchars)
140 else:
141 self.usedchars = None # stripping of font not possible
143 def outputPS(self, file, writer, registry):
144 fontfile = type1font.fontfile(self.name, self.filename, self.usedchars, self.encodingfilename)
145 fontfile.outputPS(file, writer, registry)
148 class PSfontencoding(PSresource):
150 """ PostScript font encoding vector included in the prolog """
152 def __init__(self, encoding):
153 """ include font encoding vector specified by encoding """
155 self.type = "fontencoding"
156 self.id = encoding.name
157 self.encoding = encoding
159 def outputPS(self, file, writer, registry):
160 encodingfile = type1font.encodingfile(self.encoding.name, self.encoding.filename)
161 encodingfile.outputPS(file, writer, registry)
164 class PSfontreencoding(PSresource):
166 """ PostScript font re-encoding directive included in the prolog """
168 def __init__(self, fontname, basefontname, encodingname):
169 """ include font re-encoding directive specified by
171 - fontname: PostScript FontName of the new reencoded font
172 - basefontname: PostScript FontName of the original font
173 - encname: name of the encoding
174 - font: a reference to the font instance (temporarily added for pdf support)
176 Before being able to reencode a font, you have to include the
177 encoding via a fontencoding prolog item with name=encname
181 self.type = "fontreencoding"
182 self.id = self.fontname = fontname
183 self.basefontname = basefontname
184 self.encodingname = encodingname
186 def outputPS(self, file, writer, registry):
187 file.write("%%%%BeginProcSet: %s\n" % self.fontname)
188 file.write("/%s /%s %s ReEncodeFont\n" % (self.basefontname, self.fontname, self.encodingname))
189 file.write("%%EndProcSet\n")
192 _ReEncodeFont = PSdefinition("ReEncodeFont", """{
193 5 dict
194 begin
195 /newencoding exch def
196 /newfontname exch def
197 /basefontname exch def
198 /basefontdict basefontname findfont def
199 /newfontdict basefontdict maxlength dict def
200 basefontdict {
201 exch dup dup /FID ne exch /Encoding ne and
202 { exch newfontdict 3 1 roll put }
203 { pop pop }
204 ifelse
205 } forall
206 newfontdict /FontName newfontname put
207 newfontdict /Encoding newencoding put
208 newfontname newfontdict definefont pop
210 }""")
213 class epswriter:
215 def __init__(self, document, filename):
216 if len(document.pages) != 1:
217 raise ValueError("EPS file can be construced out of a single page document only")
218 page = document.pages[0]
219 canvas = page.canvas
221 if not filename.endswith(".eps"):
222 filename = filename + ".eps"
223 try:
224 file = open(filename, "w")
225 except IOError:
226 raise IOError("cannot open output file")
228 bbox = page.bbox()
229 pagetrafo = page.pagetrafo(bbox)
231 # if a page transformation is necessary, we have to adjust the bounding box
232 # accordingly
233 if pagetrafo is not None:
234 bbox.transform(pagetrafo)
236 file.write("%!PS-Adobe-3.0 EPSF-3.0\n")
237 if bbox:
238 file.write("%%%%BoundingBox: %d %d %d %d\n" % bbox.lowrestuple_pt())
239 file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % bbox.highrestuple_pt())
240 file.write("%%%%Creator: PyX %s\n" % version.version)
241 file.write("%%%%Title: %s\n" % filename)
242 file.write("%%%%CreationDate: %s\n" %
243 time.asctime(time.localtime(time.time())))
244 file.write("%%EndComments\n")
246 file.write("%%BeginProlog\n")
247 registry = PSregistry()
248 canvas.registerPS(registry)
249 registry.outputPS(file, self)
250 file.write("%%EndProlog\n")
252 acontext = context()
253 # apply a possible page transformation
254 if pagetrafo:
255 pagetrafo.outputPS(file, self, acontext)
257 style.linewidth.normal.outputPS(file, self, acontext)
259 # here comes the canvas content
260 canvas.outputPS(file, self, acontext)
262 file.write("showpage\n")
263 file.write("%%Trailer\n")
264 file.write("%%EOF\n")
267 class pswriter:
269 def __init__(self, document, filename):
270 if not filename.endswith(".ps"):
271 filename = filename + ".ps"
272 try:
273 file = open(filename, "w")
274 except IOError:
275 raise IOError("cannot open output file")
277 # calculated bounding boxes of separate pages and the bounding box of the whole document
278 documentbbox = None
279 for page in document.pages:
280 canvas = page.canvas
281 page._bbox = page.bbox()
282 page._pagetrafo = page.pagetrafo(page._bbox)
283 # if a page transformation is necessary, we have to adjust the bounding box
284 # accordingly
285 if page._pagetrafo:
286 page._transformedbbox = page._bbox.transformed(page._pagetrafo)
287 else:
288 page._transformedbbox = page._bbox
289 if page._transformedbbox:
290 if documentbbox:
291 documentbbox += page._transformedbbox
292 else:
293 documentbbox = page._transformedbbox.enlarge(0) # make a copy
295 file.write("%!PS-Adobe-3.0\n")
296 if documentbbox:
297 file.write("%%%%BoundingBox: %d %d %d %d\n" % documentbbox.lowrestuple_pt())
298 file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % documentbbox.highrestuple_pt())
299 file.write("%%%%Creator: PyX %s\n" % version.version)
300 file.write("%%%%Title: %s\n" % filename)
301 file.write("%%%%CreationDate: %s\n" %
302 time.asctime(time.localtime(time.time())))
304 # required paper formats
305 paperformats = {}
306 for page in document.pages:
307 paperformats[page.paperformat] = page.paperformat
309 first = 1
310 for paperformat in paperformats.values():
311 if first:
312 file.write("%%DocumentMedia: ")
313 first = 0
314 else:
315 file.write("%%+ ")
316 file.write("%s %d %d 75 white ()\n" % (paperformat.name,
317 unit.topt(paperformat.width),
318 unit.topt(paperformat.height)))
320 # file.write(%%DocumentNeededResources: ") # register not downloaded fonts here
322 file.write("%%%%Pages: %d\n" % len(document.pages))
323 file.write("%%PageOrder: Ascend\n")
324 file.write("%%EndComments\n")
326 # document defaults section
327 #file.write("%%BeginDefaults\n")
328 #file.write("%%EndDefaults\n")
330 # document prolog section
331 file.write("%%BeginProlog\n")
332 registry = PSregistry()
333 for page in document.pages:
334 page.canvas.registerPS(registry)
335 registry.outputPS(file, self)
336 file.write("%%EndProlog\n")
338 # document setup section
339 #file.write("%%BeginSetup\n")
340 #file.write("%%EndSetup\n")
342 # pages section
343 for nr, page in enumerate(document.pages):
344 file.write("%%%%Page: %s %d\n" % (page.pagename is None and str(nr+1) or page.pagename, nr+1))
345 file.write("%%%%PageMedia: %s\n" % page.paperformat.name)
346 file.write("%%%%PageOrientation: %s\n" % (page.rotated and "Landscape" or "Portrait"))
347 if page._transformedbbox:
348 file.write("%%%%PageBoundingBox: %d %d %d %d\n" % page._transformedbbox.lowrestuple_pt())
350 # page setup section
351 file.write("%%BeginPageSetup\n")
352 file.write("/pgsave save def\n")
354 acontext = context()
355 # apply a possible page transformation
356 if page._pagetrafo is not None:
357 page._pagetrafo.outputPS(file, self, acontext)
359 style.linewidth.normal.outputPS(file, self, acontext)
360 file.write("%%EndPageSetup\n")
362 # here comes the actual content
363 page.canvas.outputPS(file, self, acontext)
364 file.write("pgsave restore\n")
365 file.write("showpage\n")
366 file.write("%%PageTrailer\n")
368 file.write("%%Trailer\n")
369 file.write("%%EOF\n")
371 class context:
373 def __init__(self):
374 self.linewidth_pt = None
375 self.colorspace = None
376 self.font = None
378 def __call__(self, **kwargs):
379 newcontext = copy.copy(self)
380 for key, value in kwargs.items():
381 setattr(newcontext, key, value)
382 return newcontext