- simplify PSregistry as already proposed in comments
[PyX/mjg.git] / pyx / pswriter.py
blob9d295f6010aed4c4320f145d9677b9338ee98ff5
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 # in order to keep a consistent order of the registered resources we
33 # not only store them in a hash but also keep an ordered list (up to a
34 # possible merging of resources, in which case the first instance is
35 # kept)
36 self.resourceshash = {}
37 self.resourceslist = []
39 def add(self, resource):
40 rkey = (resource.type, resource.id)
41 if self.resourceshash.has_key(rkey):
42 self.resourceshash[rkey].merge(resource)
43 else:
44 self.resourceshash[rkey] = resource
45 self.resourceslist.append(resource)
47 def outputPS(self, file):
48 """ write all PostScript code of the prolog resources """
49 for resource in self.resourceslist:
50 resource.outputPS(file)
53 # Abstract base class
56 class PSresource:
58 """ a PostScript resource """
60 def __init__(self, type, id):
61 # Every PSresource has to have a type and a unique id.
62 # Resources with the same type and id will be merged
63 # when they are registered in the PSregistry
64 self.type = type
65 self.id = id
67 def merge(self, other):
68 """ merge self with other, which has to be a resource of the same type and with
69 the same id"""
70 pass
72 def outputPS(self, file):
73 raise NotImplementedError("outputPS not implemented for %s" % repr(self))
76 # Different variants of prolog items
79 class PSdefinition(PSresource):
81 """ PostScript function definition included in the prolog """
83 def __init__(self, id, body):
84 self.type = "definition"
85 self.id = id
86 self.body = body
88 def outputPS(self, file):
89 file.write("%%%%BeginRessource: %s\n" % self.id)
90 file.write("%(body)s /%(id)s exch def\n" % self.__dict__)
91 file.write("%%EndRessource\n")
94 class PSfontfile(PSresource):
96 """ PostScript font definition included in the prolog """
98 def __init__(self, fontname, filename, encfilename, usedchars):
99 """ include type 1 font defined by the following parameters
101 - fontname: PostScript FontName of font
102 - filename: name (without path) of file containing the font definition
103 - encfilename: name (without path) of file containing used encoding of font
104 or None (if no encoding file used)
105 - usechars: list with 256 elements containing used charcodes of font
109 # Note that here we only need the encoding for selecting the used glyphs!
111 # XXX rewrite
113 self.type = "fontfile"
114 self.id = self.fontname = fontname
115 self.filename = filename
116 self.encfilename = encfilename
117 self.usedchars = usedchars
119 def merge(self, other):
120 if self.encfilename != other.encfilename:
121 self.usedchars = None # stripping of font not possible
122 else:
123 for i in range(len(self.usedchars)):
124 self.usedchars[i] = self.usedchars[i] or other.usedchars[i]
126 def outputPS(self, file):
127 file.write("%%%%BeginFont: %s\n" % self.fontname)
128 if self.usedchars:
129 file.write("%Included char codes:")
130 for i in range(len(self.usedchars)):
131 if self.usedchars[i]:
132 file.write(" %d" % i)
133 file.write("\n")
134 pfbpath = pykpathsea.find_file(self.filename, pykpathsea.kpse_type1_format)
135 if not pfbpath:
136 raise RuntimeError("cannot find type 1 font %s" % self.filename)
137 if self.usedchars:
138 if self.encfilename is not None:
139 encpath = pykpathsea.find_file(self.encfilename, pykpathsea.kpse_tex_ps_header_format)
140 if not encpath:
141 raise RuntimeError("cannot find font encoding file %s" % self.encfilename)
142 t1strip.t1strip(file, pfbpath, self.usedchars, encpath)
143 else:
144 t1strip.t1strip(file, pfbpath, self.usedchars)
145 file.write("%%EndFont\n")
148 class PSfontencoding(PSresource):
150 """ PostScript font encoding vector included in the prolog """
152 def __init__(self, name, filename):
153 """ include font encoding vector specified by
155 - name: name of the encoding
156 - filename: name (without path) of file containing the font encoding
160 self.type = "fontencoding"
161 self.id = self.name = name
162 self.filename = filename
164 def outputPS(self, file):
165 file.write("%%%%BeginProcSet: %s\n" % self.name)
166 path = pykpathsea.find_file(self.filename, pykpathsea.kpse_tex_ps_header_format)
167 encfile = open(path, "r")
168 file.write(encfile.read())
169 encfile.close()
170 file.write("%%EndProcSet\n")
173 class PSfontreencoding(PSresource):
175 """ PostScript font re-encoding directive included in the prolog """
177 def __init__(self, fontname, basefontname, encname):
178 """ include font re-encoding directive specified by
180 - fontname: PostScript FontName of the new reencoded font
181 - basefontname: PostScript FontName of the original font
182 - encname: name of the encoding
183 - font: a reference to the font instance (temporarily added for pdf support)
185 Before being able to reencode a font, you have to include the
186 encoding via a fontencoding prolog item with name=encname
190 self.type = "fontreencoding"
191 self.id = self.fontname = fontname
192 self.basefontname = basefontname
193 self.encname = encname
195 def outputPS(self, file):
196 file.write("%%%%BeginProcSet: %s\n" % self.fontname)
197 file.write("/%s /%s %s ReEncodeFont\n" % (self.basefontname, self.fontname, self.encname))
198 file.write("%%EndProcSet\n")
201 _ReEncodeFont = PSdefinition("ReEncodeFont", """{
202 5 dict
203 begin
204 /newencoding exch def
205 /newfontname exch def
206 /basefontname exch def
207 /basefontdict basefontname findfont def
208 /newfontdict basefontdict maxlength dict def
209 basefontdict {
210 exch dup dup /FID ne exch /Encoding ne and
211 { exch newfontdict 3 1 roll put }
212 { pop pop }
213 ifelse
214 } forall
215 newfontdict /FontName newfontname put
216 newfontdict /Encoding newencoding put
217 newfontname newfontdict definefont pop
219 }""")
222 class epswriter:
224 def __init__(self, document, filename):
225 if len(document.pages) != 1:
226 raise ValueError("EPS file can be construced out of a single page document only")
227 page = document.pages[0]
228 canvas = page.canvas
230 if filename[-4:] != ".eps":
231 filename = filename + ".eps"
232 try:
233 file = open(filename, "w")
234 except IOError:
235 raise IOError("cannot open output file")
237 bbox = canvas.bbox()
238 bbox.enlarge(page.bboxenlarge)
239 pagetrafo = page.pagetrafo(bbox)
241 # if a page transformation is necessary, we have to adjust the bounding box
242 # accordingly
243 if pagetrafo is not None:
244 bbox.transform(pagetrafo)
246 file.write("%!PS-Adobe-3.0 EPSF-3.0\n")
247 bbox.outputPS(file)
248 file.write("%%%%Creator: PyX %s\n" % version.version)
249 file.write("%%%%Title: %s\n" % filename)
250 file.write("%%%%CreationDate: %s\n" %
251 time.asctime(time.localtime(time.time())))
252 file.write("%%EndComments\n")
254 file.write("%%BeginProlog\n")
255 registry = PSregistry()
256 for resource in canvas.resources():
257 resource.PSregister(registry)
258 registry.outputPS(file)
259 file.write("%%EndProlog\n")
261 # apply a possible page transformation
262 if pagetrafo is not None:
263 pagetrafo.outputPS(file)
265 style.linewidth.normal.outputPS(file)
267 # here comes the canvas content
268 canvas.outputPS(file)
270 file.write("showpage\n")
271 file.write("%%Trailer\n")
272 file.write("%%EOF\n")
275 class pswriter:
276 pass
278 # def outputPS(self, file):
279 # file.write("%%%%PageMedia: %s\n" % self.paperformat)
280 # file.write("%%%%PageOrientation: %s\n" % (self.rotated and "Landscape" or "Portrait"))
281 # # file.write("%%%%PageBoundingBox: %d %d %d %d\n" % (math.floor(pbbox.llx_pt), math.floor(pbbox.lly_pt),
282 # # math.ceil(pbbox.urx_pt), math.ceil(pbbox.ury_pt)))
284 # # page setup section
285 # file.write("%%BeginPageSetup\n")
286 # file.write("/pgsave save def\n")
287 # # for scaling, we need the real bounding box of the page contents
288 # pbbox = canvas.bbox(self)
289 # pbbox.enlarge(self.bboxenlarge)
290 # ptrafo = calctrafo(pbbox, self.paperformat, self.margin, self.rotated, self.fittosize)
291 # if ptrafo:
292 # ptrafo.outputPS(file)
293 # file.write("%f setlinewidth\n" % unit.topt(style.linewidth.normal))
294 # file.write("%%EndPageSetup\n")
296 # # here comes the actual content
297 # canvas.outputPS(self, file)
298 # file.write("pgsave restore\n")
299 # file.write("showpage\n")
300 # # file.write("%%PageTrailer\n")
303 # def writePSfile(self, filename):
304 # """write pages to PS file """
306 # if filename[-3:]!=".ps":
307 # filename = filename + ".ps"
309 # try:
310 # file = open(filename, "w")
311 # except IOError:
312 # raise IOError("cannot open output file")
314 # docbbox = None
315 # for apage in self.pages:
316 # pbbox = apage.bbox()
317 # if docbbox is None:
318 # docbbox = pbbox
319 # else:
320 # docbbox += pbbox
322 # # document header
323 # file.write("%!PS-Adobe-3.0\n")
324 # docbbox.outputPS(file)
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())))
329 # # required paper formats
330 # paperformats = {}
331 # for apage in self.pages:
332 # if isinstance(apage, page):
333 # paperformats[apage.paperformat] = _paperformats[apage.paperformat]
334 # first = 1
335 # for paperformat, size in paperformats.items():
336 # if first:
337 # file.write("%%DocumentMedia: ")
338 # first = 0
339 # else:
340 # file.write("%%+ ")
341 # file.write("%s %d %d 75 white ()\n" % (paperformat, unit.topt(size[0]), unit.topt(size[1])))
343 # file.write("%%%%Pages: %d\n" % len(self.pages))
344 # file.write("%%PageOrder: Ascend\n")
345 # file.write("%%EndComments\n")
347 # # document default section
348 # #file.write("%%BeginDefaults\n")
349 # #if paperformat:
350 # # file.write("%%%%PageMedia: %s\n" % paperformat)
351 # #file.write("%%%%PageOrientation: %s\n" % (rotated and "Landscape" or "Portrait"))
352 # #file.write("%%EndDefaults\n")
354 # # document prolog section
355 # file.write("%%BeginProlog\n")
356 # mergedprolog = []
357 # for apage in self.pages:
358 # for pritem in apage.prolog():
359 # for mpritem in mergedprolog:
360 # if mpritem.merge(pritem) is None: break
361 # else:
362 # mergedprolog.append(pritem)
363 # for pritem in mergedprolog:
364 # pritem.outputPS(file)
365 # file.write("%%EndProlog\n")
367 # # document setup section
368 # #file.write("%%BeginSetup\n")
369 # #file.write("%%EndSetup\n")
371 # # pages section
372 # for nr, apage in enumerate(self.pages):
373 # file.write("%%%%Page: %s %d\n" % (apage.pagename is None and str(nr) or apage.pagename , nr+1))
374 # apage.outputPS(file)
376 # file.write("%%Trailer\n")
377 # file.write("%%EOF\n")