remove shebang -- see comment 3 on https://bugzilla.redhat.com/bugzilla/show_bug...
[PyX/mjg.git] / pyx / pswriter.py
blob7598d5e86432bc14d6aeadda53b7e7fa57427210
1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2005-2006 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2005-2006 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 cStringIO, copy, time, math
24 import bbox, style, version, type1font, unit
26 try:
27 enumerate([])
28 except NameError:
29 # fallback implementation for Python 2.2 and below
30 def enumerate(list):
31 return zip(xrange(len(list)), list)
33 try:
34 dict([])
35 except NameError:
36 # fallback implementation for Python 2.1
37 def dict(list):
38 result = {}
39 for key, value in list:
40 result[key] = value
41 return result
44 class PSregistry:
46 def __init__(self):
47 # in order to keep a consistent order of the registered resources we
48 # not only store them in a hash but also keep an ordered list (up to a
49 # possible merging of resources, in which case the first instance is
50 # kept)
51 self.resourceshash = {}
52 self.resourceslist = []
54 def add(self, resource):
55 rkey = (resource.type, resource.id)
56 if self.resourceshash.has_key(rkey):
57 self.resourceshash[rkey].merge(resource)
58 else:
59 self.resourceshash[rkey] = resource
60 self.resourceslist.append(resource)
62 def mergeregistry(self, registry):
63 for resource in registry.resources:
64 self.add(resource)
66 def output(self, file, writer):
67 """ write all PostScript code of the prolog resources """
68 for resource in self.resourceslist:
69 resource.output(file, writer, self)
72 # Abstract base class
75 class PSresource:
77 """ a PostScript resource """
79 def __init__(self, type, id):
80 # Every PSresource has to have a type and a unique id.
81 # Resources with the same type and id will be merged
82 # when they are registered in the PSregistry
83 self.type = type
84 self.id = id
86 def merge(self, other):
87 """ merge self with other, which has to be a resource of the same type and with
88 the same id"""
89 pass
91 def output(self, file, writer, registry):
92 raise NotImplementedError("output not implemented for %s" % repr(self))
95 # Different variants of prolog items
98 class PSdefinition(PSresource):
100 """ PostScript function definition included in the prolog """
102 def __init__(self, id, body):
103 self.type = "definition"
104 self.id = id
105 self.body = body
107 def output(self, file, writer, registry):
108 file.write("%%%%BeginRessource: %s\n" % self.id)
109 file.write("%(body)s /%(id)s exch def\n" % self.__dict__)
110 file.write("%%EndRessource\n")
113 class PSfont:
115 def __init__(self, font, chars, registry):
116 if font.filename:
117 registry.add(PSfontfile(font.basefontname,
118 font.filename,
119 font.encoding,
120 chars))
121 if font.encoding:
122 registry.add(_ReEncodeFont)
123 registry.add(PSfontencoding(font.encoding))
124 registry.add(PSfontreencoding(font.name,
125 font.basefontname,
126 font.encoding.name))
129 class PSfontfile(PSresource):
131 """ PostScript font definition included in the prolog """
133 def __init__(self, name, filename, encoding, chars):
134 """ include type 1 font defined by the following parameters
136 - name: name of the PostScript font
137 - filename: name (without path) of file containing the font definition
138 - encfilename: name (without path) of file containing used encoding of font
139 or None (if no encoding file used)
140 - chars: character list to fill usedchars
144 # Note that here we only need the encoding for selecting the used glyphs!
146 self.type = "fontfile"
147 self.id = self.name = name
148 self.filename = filename
149 if encoding is None:
150 self.encodingfilename = None
151 else:
152 self.encodingfilename = encoding.filename
153 self.usedchars = {}
154 for char in chars:
155 self.usedchars[char] = 1
157 self.strip = 1
159 def merge(self, other):
160 if self.encodingfilename == other.encodingfilename:
161 self.usedchars.update(other.usedchars)
162 else:
163 # TODO: need to resolve the encoding when several encodings are in the play
164 self.strip = 0
166 def output(self, file, writer, registry):
167 import font.t1font
168 font = font.t1font.T1pfbfont(self.filename)
170 file.write("%%%%BeginFont: %s\n" % self.name)
171 # file.write("%%Included glyphs: %s\n" % " ".join(usedglyphs))
172 if self.strip:
173 # XXX: access to the encoding file
174 if self.encodingfilename:
175 encodingfile = type1font.encodingfile(self.encodingfilename, self.encodingfilename)
176 usedglyphs = dict([(encodingfile.decode(char)[1:], 1) for char in self.usedchars.keys()])
177 else:
178 font._encoding()
179 usedglyphs = dict([(font.encoding.decode(char), 1) for char in self.usedchars.keys()])
180 strippedfont = font.getstrippedfont(usedglyphs)
181 else:
182 strippedfont = font
183 strippedfont.outputPS(file, writer)
184 file.write("\n%%EndFont\n")
187 class PSfontencoding(PSresource):
189 """ PostScript font encoding vector included in the prolog """
191 def __init__(self, encoding):
192 """ include font encoding vector specified by encoding """
194 self.type = "fontencoding"
195 self.id = encoding.name
196 self.encoding = encoding
198 def output(self, file, writer, registry):
199 encodingfile = type1font.encodingfile(self.encoding.name, self.encoding.filename)
200 encodingfile.outputPS(file, writer)
203 class PSfontreencoding(PSresource):
205 """ PostScript font re-encoding directive included in the prolog """
207 def __init__(self, fontname, basefontname, encodingname):
208 """ include font re-encoding directive specified by
210 - fontname: PostScript FontName of the new reencoded font
211 - basefontname: PostScript FontName of the original font
212 - encname: name of the encoding
213 - font: a reference to the font instance (temporarily added for pdf support)
215 Before being able to reencode a font, you have to include the
216 encoding via a fontencoding prolog item with name=encname
220 self.type = "fontreencoding"
221 self.id = self.fontname = fontname
222 self.basefontname = basefontname
223 self.encodingname = encodingname
225 def output(self, file, writer, registry):
226 file.write("%%%%BeginProcSet: %s\n" % self.fontname)
227 file.write("/%s /%s %s ReEncodeFont\n" % (self.basefontname, self.fontname, self.encodingname))
228 file.write("%%EndProcSet\n")
231 _ReEncodeFont = PSdefinition("ReEncodeFont", """{
232 5 dict
233 begin
234 /newencoding exch def
235 /newfontname exch def
236 /basefontname exch def
237 /basefontdict basefontname findfont def
238 /newfontdict basefontdict maxlength dict def
239 basefontdict {
240 exch dup dup /FID ne exch /Encoding ne and
241 { exch newfontdict 3 1 roll put }
242 { pop pop }
243 ifelse
244 } forall
245 newfontdict /FontName newfontname put
246 newfontdict /Encoding newencoding put
247 newfontname newfontdict definefont pop
249 }""")
252 class epswriter:
254 def __init__(self, document, file):
255 if len(document.pages) != 1:
256 raise ValueError("EPS file can be construced out of a single page document only")
257 page = document.pages[0]
258 canvas = page.canvas
260 try:
261 file.write("")
262 except:
263 filename = file
264 if not filename.endswith(".eps"):
265 filename += ".eps"
266 try:
267 file = open(filename, "w")
268 except IOError:
269 raise IOError("cannot open output file")
270 else:
271 filename = "stream"
273 pagefile = cStringIO.StringIO()
274 registry = PSregistry()
275 acontext = context()
276 pagebbox = bbox.empty()
278 page.processPS(pagefile, self, acontext, registry, pagebbox)
280 file.write("%!PS-Adobe-3.0 EPSF-3.0\n")
281 if pagebbox:
282 file.write("%%%%BoundingBox: %d %d %d %d\n" % pagebbox.lowrestuple_pt())
283 file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % pagebbox.highrestuple_pt())
284 file.write("%%%%Creator: PyX %s\n" % version.version)
285 file.write("%%%%Title: %s\n" % filename)
286 file.write("%%%%CreationDate: %s\n" %
287 time.asctime(time.localtime(time.time())))
288 file.write("%%EndComments\n")
290 file.write("%%BeginProlog\n")
291 registry.output(file, self)
292 file.write("%%EndProlog\n")
294 file.write(pagefile.getvalue())
295 pagefile.close()
297 file.write("showpage\n")
298 file.write("%%Trailer\n")
299 file.write("%%EOF\n")
302 class pswriter:
304 def __init__(self, document, file, writebbox=0):
305 try:
306 file.write("")
307 except:
308 filename = file
309 if not filename.endswith(".ps"):
310 filename += ".ps"
311 try:
312 file = open(filename, "w")
313 except IOError:
314 raise IOError("cannot open output file")
315 else:
316 filename = "stream"
318 # We first have to process the content of the pages, writing them into the stream pagesfile
319 # Doing so, we fill the registry and also calculate the page bounding boxes, which are
320 # stored in page._bbox for every page
321 pagesfile = cStringIO.StringIO()
322 registry = PSregistry()
324 # calculated bounding boxes of the whole document
325 documentbbox = bbox.empty()
327 for nr, page in enumerate(document.pages):
328 # process contents of page
329 pagefile = cStringIO.StringIO()
330 acontext = context()
331 pagebbox = bbox.empty()
332 page.processPS(pagefile, self, acontext, registry, pagebbox)
334 documentbbox += pagebbox
336 pagesfile.write("%%%%Page: %s %d\n" % (page.pagename is None and str(nr+1) or page.pagename, nr+1))
337 if page.paperformat:
338 pagesfile.write("%%%%PageMedia: %s\n" % page.paperformat.name)
339 pagesfile.write("%%%%PageOrientation: %s\n" % (page.rotated and "Landscape" or "Portrait"))
340 if pagebbox and writebbox:
341 pagesfile.write("%%%%PageBoundingBox: %d %d %d %d\n" % pagebbox.lowrestuple_pt())
343 # page setup section
344 pagesfile.write("%%BeginPageSetup\n")
345 pagesfile.write("/pgsave save def\n")
347 pagesfile.write("%%EndPageSetup\n")
348 pagesfile.write(pagefile.getvalue())
349 pagefile.close()
350 pagesfile.write("pgsave restore\n")
351 pagesfile.write("showpage\n")
352 pagesfile.write("%%PageTrailer\n")
354 file.write("%!PS-Adobe-3.0\n")
355 if documentbbox and writebbox:
356 file.write("%%%%BoundingBox: %d %d %d %d\n" % documentbbox.lowrestuple_pt())
357 file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % documentbbox.highrestuple_pt())
358 file.write("%%%%Creator: PyX %s\n" % version.version)
359 file.write("%%%%Title: %s\n" % filename)
360 file.write("%%%%CreationDate: %s\n" %
361 time.asctime(time.localtime(time.time())))
363 # required paper formats
364 paperformats = {}
365 for page in document.pages:
366 if page.paperformat:
367 paperformats[page.paperformat] = page.paperformat
369 first = 1
370 for paperformat in paperformats.values():
371 if first:
372 file.write("%%DocumentMedia: ")
373 first = 0
374 else:
375 file.write("%%+ ")
376 file.write("%s %d %d 75 white ()\n" % (paperformat.name,
377 unit.topt(paperformat.width),
378 unit.topt(paperformat.height)))
380 # file.write(%%DocumentNeededResources: ") # register not downloaded fonts here
382 file.write("%%%%Pages: %d\n" % len(document.pages))
383 file.write("%%PageOrder: Ascend\n")
384 file.write("%%EndComments\n")
386 # document defaults section
387 #file.write("%%BeginDefaults\n")
388 #file.write("%%EndDefaults\n")
390 # document prolog section
391 file.write("%%BeginProlog\n")
392 registry.output(file, self)
393 file.write("%%EndProlog\n")
395 # document setup section
396 #file.write("%%BeginSetup\n")
397 #file.write("%%EndSetup\n")
399 file.write(pagesfile.getvalue())
400 pagesfile.close()
402 file.write("%%Trailer\n")
403 file.write("%%EOF\n")
405 class context:
407 def __init__(self):
408 self.linewidth_pt = None
409 self.colorspace = None
410 self.font = None
412 def __call__(self, **kwargs):
413 newcontext = copy.copy(self)
414 for key, value in kwargs.items():
415 setattr(newcontext, key, value)
416 return newcontext