ps resource registry uses resource types + a list of used resources (to keep the...
[PyX/mjg.git] / pyx / pswriter.py
blob315b073533f91fe947bb9ad309863821ac682a17
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import time
25 import resource, style, version
26 import pykpathsea, t1strip
29 class PSregistry:
31 def __init__(self):
32 self.resources = []
33 self.types = {}
35 def add(self, resource):
36 resources = self.types.setdefault(resource.type, {})
37 if resources.has_key(resource.id):
38 resources[resource.id].merge(resource)
39 else:
40 self.resources.append(resource)
41 resources[resource.id] = resource
43 def outputPS(self, file):
44 """ write all PostScript code of the prolog resources """
45 for resource in self.resources:
46 resource.outputPS(file)
49 # Abstract base class
52 class PSresource:
54 """ a PostScript resource """
56 def __init__(self, type, id):
57 # every PSresource has to have a type and a unique id
58 self.type = type
59 self.id = id
61 def merge(self, other):
62 pass
64 def register(self, registry):
65 raise NotImplementedError("register not implemented for %s" % repr(self))
67 def outputPS(self, file):
68 raise NotImplementedError("outputPS not implemented for %s" % repr(self))
71 # Different variants of prolog items
74 class PSdefinition(PSresource):
76 """ PostScript function definition included in the prolog """
78 def __init__(self, id, body):
79 # every PSresource has to have a unique id
80 self.type = "definition"
81 self.id = id
82 self.body = body
84 def register(self, registry):
85 registry.addresource(registry.definitions, self)
87 def outputPS(self, file):
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 PSfontfile(PSresource):
95 """ PostScript font definition included in the prolog """
97 def __init__(self, fontname, filename, encfilename, usedchars):
98 """ include type 1 font defined by the following parameters
100 - fontname: PostScript FontName of font
101 - filename: name (without path) of file containing the font definition
102 - encfilename: name (without path) of file containing used encoding of font
103 or None (if no encoding file used)
104 - usechars: list with 256 elements containing used charcodes of font
108 # Note that here we only need the encoding for selecting the used glyphs!
110 # XXX rewrite
112 self.type = "fontfile"
113 self.id = self.fontname = fontname
114 self.filename = filename
115 self.encfilename = encfilename
116 self.usedchars = usedchars
118 def merge(self, other):
119 if self.encfilename != other.encfilename:
120 self.usedchars = None # stripping of font not possible
121 else:
122 for i in range(len(self.usedchars)):
123 self.usedchars[i] = self.usedchars[i] or other.usedchars[i]
125 def register(self, registry):
126 registry.addresource(registry.fontfiles, self)
128 def outputPS(self, file):
129 file.write("%%%%BeginFont: %s\n" % self.fontname)
130 if self.usedchars:
131 file.write("%Included char codes:")
132 for i in range(len(self.usedchars)):
133 if self.usedchars[i]:
134 file.write(" %d" % i)
135 file.write("\n")
136 pfbpath = pykpathsea.find_file(self.filename, pykpathsea.kpse_type1_format)
137 if not pfbpath:
138 raise RuntimeError("cannot find type 1 font %s" % self.filename)
139 if self.usedchars:
140 if self.encfilename is not None:
141 encpath = pykpathsea.find_file(self.encfilename, pykpathsea.kpse_tex_ps_header_format)
142 if not encpath:
143 raise RuntimeError("cannot find font encoding file %s" % self.encfilename)
144 t1strip.t1strip(file, pfbpath, self.usedchars, encpath)
145 else:
146 t1strip.t1strip(file, pfbpath, self.usedchars)
147 file.write("%%EndFont\n")
150 class PSfontencoding(PSresource):
152 """ PostScript font encoding vector included in the prolog """
154 def __init__(self, name, filename):
155 """ include font encoding vector specified by
157 - name: name of the encoding
158 - filename: name (without path) of file containing the font encoding
162 self.type = "fontencoding"
163 self.id = self.name = name
164 self.filename = filename
166 def register(self, registry):
167 registry.addresource(registry.fontencodings, self)
169 def outputPS(self, file):
170 file.write("%%%%BeginProcSet: %s\n" % self.name)
171 path = pykpathsea.find_file(self.filename, pykpathsea.kpse_tex_ps_header_format)
172 encfile = open(path, "r")
173 file.write(encfile.read())
174 encfile.close()
175 file.write("%%EndProcSet\n")
178 class PSfontreencoding(PSresource):
180 """ PostScript font re-encoding directive included in the prolog """
182 def __init__(self, fontname, basefontname, encname):
183 """ include font re-encoding directive specified by
185 - fontname: PostScript FontName of the new reencoded font
186 - basefontname: PostScript FontName of the original font
187 - encname: name of the encoding
188 - font: a reference to the font instance (temporarily added for pdf support)
190 Before being able to reencode a font, you have to include the
191 encoding via a fontencoding prolog item with name=encname
195 self.type = "fontreencoding"
196 self.id = self.fontname = fontname
197 self.basefontname = basefontname
198 self.encname = encname
200 def register(self, registry):
201 registry.addresource(registry.fontreencodings, self)
203 def outputPS(self, file):
204 file.write("%%%%BeginProcSet: %s\n" % self.fontname)
205 file.write("/%s /%s %s ReEncodeFont\n" % (self.basefontname, self.fontname, self.encname))
206 file.write("%%EndProcSet\n")
209 _ReEncodeFont = PSdefinition("ReEncodeFont", """{
210 5 dict
211 begin
212 /newencoding exch def
213 /newfontname exch def
214 /basefontname exch def
215 /basefontdict basefontname findfont def
216 /newfontdict basefontdict maxlength dict def
217 basefontdict {
218 exch dup dup /FID ne exch /Encoding ne and
219 { exch newfontdict 3 1 roll put }
220 { pop pop }
221 ifelse
222 } forall
223 newfontdict /FontName newfontname put
224 newfontdict /Encoding newencoding put
225 newfontname newfontdict definefont pop
227 }""")
230 class epswriter:
232 def __init__(self, document, filename):
233 if len(document.pages) != 1:
234 raise ValueError("EPS file can be construced out of a single page document only")
235 page = document.pages[0]
236 canvas = page.canvas
238 if filename[-4:] != ".eps":
239 filename = filename + ".eps"
240 try:
241 file = open(filename, "w")
242 except IOError:
243 raise IOError("cannot open output file")
245 bbox = canvas.bbox()
246 bbox.enlarge(page.bboxenlarge)
247 pagetrafo = page.pagetrafo(bbox)
249 # if a page transformation is necessary, we have to adjust the bounding box
250 # accordingly
251 if pagetrafo is not None:
252 bbox.transform(pagetrafo)
254 file.write("%!PS-Adobe-3.0 EPSF-3.0\n")
255 bbox.outputPS(file)
256 file.write("%%%%Creator: PyX %s\n" % version.version)
257 file.write("%%%%Title: %s\n" % filename)
258 file.write("%%%%CreationDate: %s\n" %
259 time.asctime(time.localtime(time.time())))
260 file.write("%%EndComments\n")
262 file.write("%%BeginProlog\n")
263 registry = PSregistry()
264 for resource in canvas.resources():
265 resource.PSregister(registry)
266 registry.outputPS(file)
267 file.write("%%EndProlog\n")
269 # apply a possible page transformation
270 if pagetrafo is not None:
271 pagetrafo.outputPS(file)
273 style.linewidth.normal.outputPS(file)
275 # here comes the canvas content
276 canvas.outputPS(file)
278 file.write("showpage\n")
279 file.write("%%Trailer\n")
280 file.write("%%EOF\n")
283 class pswriter:
284 pass
286 # def outputPS(self, file):
287 # file.write("%%%%PageMedia: %s\n" % self.paperformat)
288 # file.write("%%%%PageOrientation: %s\n" % (self.rotated and "Landscape" or "Portrait"))
289 # # file.write("%%%%PageBoundingBox: %d %d %d %d\n" % (math.floor(pbbox.llx_pt), math.floor(pbbox.lly_pt),
290 # # math.ceil(pbbox.urx_pt), math.ceil(pbbox.ury_pt)))
292 # # page setup section
293 # file.write("%%BeginPageSetup\n")
294 # file.write("/pgsave save def\n")
295 # # for scaling, we need the real bounding box of the page contents
296 # pbbox = canvas.bbox(self)
297 # pbbox.enlarge(self.bboxenlarge)
298 # ptrafo = calctrafo(pbbox, self.paperformat, self.margin, self.rotated, self.fittosize)
299 # if ptrafo:
300 # ptrafo.outputPS(file)
301 # file.write("%f setlinewidth\n" % unit.topt(style.linewidth.normal))
302 # file.write("%%EndPageSetup\n")
304 # # here comes the actual content
305 # canvas.outputPS(self, file)
306 # file.write("pgsave restore\n")
307 # file.write("showpage\n")
308 # # file.write("%%PageTrailer\n")
311 # def writePSfile(self, filename):
312 # """write pages to PS file """
314 # if filename[-3:]!=".ps":
315 # filename = filename + ".ps"
317 # try:
318 # file = open(filename, "w")
319 # except IOError:
320 # raise IOError("cannot open output file")
322 # docbbox = None
323 # for apage in self.pages:
324 # pbbox = apage.bbox()
325 # if docbbox is None:
326 # docbbox = pbbox
327 # else:
328 # docbbox += pbbox
330 # # document header
331 # file.write("%!PS-Adobe-3.0\n")
332 # docbbox.outputPS(file)
333 # file.write("%%%%Creator: PyX %s\n" % version.version)
334 # file.write("%%%%Title: %s\n" % filename)
335 # file.write("%%%%CreationDate: %s\n" %
336 # time.asctime(time.localtime(time.time())))
337 # # required paper formats
338 # paperformats = {}
339 # for apage in self.pages:
340 # if isinstance(apage, page):
341 # paperformats[apage.paperformat] = _paperformats[apage.paperformat]
342 # first = 1
343 # for paperformat, size in paperformats.items():
344 # if first:
345 # file.write("%%DocumentMedia: ")
346 # first = 0
347 # else:
348 # file.write("%%+ ")
349 # file.write("%s %d %d 75 white ()\n" % (paperformat, unit.topt(size[0]), unit.topt(size[1])))
351 # file.write("%%%%Pages: %d\n" % len(self.pages))
352 # file.write("%%PageOrder: Ascend\n")
353 # file.write("%%EndComments\n")
355 # # document default section
356 # #file.write("%%BeginDefaults\n")
357 # #if paperformat:
358 # # file.write("%%%%PageMedia: %s\n" % paperformat)
359 # #file.write("%%%%PageOrientation: %s\n" % (rotated and "Landscape" or "Portrait"))
360 # #file.write("%%EndDefaults\n")
362 # # document prolog section
363 # file.write("%%BeginProlog\n")
364 # mergedprolog = []
365 # for apage in self.pages:
366 # for pritem in apage.prolog():
367 # for mpritem in mergedprolog:
368 # if mpritem.merge(pritem) is None: break
369 # else:
370 # mergedprolog.append(pritem)
371 # for pritem in mergedprolog:
372 # pritem.outputPS(file)
373 # file.write("%%EndProlog\n")
375 # # document setup section
376 # #file.write("%%BeginSetup\n")
377 # #file.write("%%EndSetup\n")
379 # # pages section
380 # for nr, apage in enumerate(self.pages):
381 # file.write("%%%%Page: %s %d\n" % (apage.pagename is None and str(nr) or apage.pagename , nr+1))
382 # apage.outputPS(file)
384 # file.write("%%Trailer\n")
385 # file.write("%%EOF\n")