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
, trafo
29 # fallback implementation for Python 2.2 and below
31 return zip(xrange(len(list)), list)
36 # fallback implementation for Python 2.1
39 for key
, value
in list:
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
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
)
59 self
.resourceshash
[rkey
] = resource
60 self
.resourceslist
.append(resource
)
62 def mergeregistry(self
, registry
):
63 for resource
in registry
.resources
:
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
)
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
86 def merge(self
, other
):
87 """ merge self with other, which has to be a resource of the same type and with
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"
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")
115 def __init__(self
, font
, chars
, registry
):
117 registry
.add(PSfontfile(font
.basefontname
,
121 if font
.encoding
and font
.slant
:
123 # do first the reencoding and then the slanting:
124 enc_basename
, enc_finalname
= font
.basefontname
, font
.encname
125 slt_basename
, slt_finalname
= tfont
.encname
, font
.name
127 enc_basename
, enc_finalname
= font
.basefontname
, font
.name
129 slt_basename
, slt_finalname
= font
.basefontname
, font
.name
132 registry
.add(_ReEncodeFont
)
133 registry
.add(PSfontencoding(font
.encoding
))
134 registry
.add(PSfontreencoding(enc_finalname
, enc_basename
, font
.encoding
.name
))
137 # we need the current fontmatrix in order to manipulate it:
138 # for this we need to re-read the fontfile as below in
140 # XXX Is there a better way to do this?
141 t
= trafo
.trafo_pt(matrix
=((1, font
.slant
), (0, 1)))
143 # for the builtin fonts, we assume a trivial fontmatrix
144 import font
.t1font
as t1fontmodule
145 t1font
= t1fontmodule
.T1pfbfont(font
.filename
)
146 m
= t1font
.fontmatrixpattern
.search(t1font
.data1
)
147 m11
, m12
, m21
, m22
, v1
, v2
= map(float, m
.groups()[:6])
148 t
*= trafo
.trafo_pt(matrix
=((m11
, m12
), (m21
, m22
)), vector
=(v1
, v2
))
150 raise NotImplementedError(
151 "cannot slant unembedded fonts -- try to include \"download35.map\" in fontmaps")
152 registry
.add(PSfontslanting(slt_finalname
, slt_basename
, t
.__str
__()))
155 class PSfontfile(PSresource
):
157 """ PostScript font definition included in the prolog """
159 def __init__(self
, name
, filename
, encoding
, chars
):
160 """ include type 1 font defined by the following parameters
162 - name: name of the PostScript font
163 - filename: name (without path) of file containing the font definition
164 - encfilename: name (without path) of file containing used encoding of font
165 or None (if no encoding file used)
166 - chars: character list to fill usedchars
170 # Note that here we only need the encoding for selecting the used glyphs!
172 self
.type = "fontfile"
173 self
.id = self
.name
= name
174 self
.filename
= filename
176 self
.encodingfilename
= None
178 self
.encodingfilename
= encoding
.filename
181 self
.usedchars
[char
] = 1
185 def merge(self
, other
):
186 if self
.encodingfilename
== other
.encodingfilename
:
187 self
.usedchars
.update(other
.usedchars
)
189 # TODO: need to resolve the encoding when several encodings are in the play
192 def output(self
, file, writer
, registry
):
194 font
= font
.t1font
.T1pfbfont(self
.filename
)
196 file.write("%%%%BeginFont: %s\n" % self
.name
)
197 # file.write("%%Included glyphs: %s\n" % " ".join(usedglyphs))
199 # XXX: access to the encoding file
200 if self
.encodingfilename
:
201 encodingfile
= type1font
.encodingfile(self
.encodingfilename
, self
.encodingfilename
)
202 usedglyphs
= dict([(encodingfile
.decode(char
)[1:], 1) for char
in self
.usedchars
.keys()])
205 usedglyphs
= dict([(font
.encoding
.decode(char
), 1) for char
in self
.usedchars
.keys()])
206 strippedfont
= font
.getstrippedfont(usedglyphs
)
209 strippedfont
.outputPS(file, writer
)
210 file.write("\n%%EndFont\n")
213 class PSfontencoding(PSresource
):
215 """ PostScript font encoding vector included in the prolog """
217 def __init__(self
, encoding
):
218 """ include font encoding vector specified by encoding """
220 self
.type = "fontencoding"
221 self
.id = encoding
.name
222 self
.encoding
= encoding
224 def output(self
, file, writer
, registry
):
225 encodingfile
= type1font
.encodingfile(self
.encoding
.name
, self
.encoding
.filename
)
226 encodingfile
.outputPS(file, writer
)
229 class PSfontslanting(PSresource
):
231 """ PostScript font slanting directive included in the prolog """
233 def __init__(self
, fontname
, basefontname
, matrixstring
):
234 """ include transformed font directive specified by
236 - fontname: PostScript FontName of the new slanted font
237 - basefontname: PostScript FontName of the original font
238 - slant: the value of slanting
241 self
.type = "fontslanting"
242 self
.id = self
.fontname
= fontname
243 self
.basefontname
= basefontname
244 self
.matrixstring
= matrixstring
246 def output(self
, file, writer
, registry
):
247 file.write("%%%%BeginProcSet: %s\n" % self
.fontname
)
248 file.write("/%s findfont\n" % self
.basefontname
)
249 file.write("dup length dict begin\n")
250 file.write("{ 1 index /FID ne {def} {pop pop} ifelse } forall\n")
251 file.write("/FontMatrix %s readonly def\n" % self
.matrixstring
)
252 file.write("currentdict\n")
254 file.write("/%s exch definefont pop\n" % self
.fontname
)
255 file.write("%%EndProcSet\n")
257 class PSfontreencoding(PSresource
):
259 """ PostScript font re-encoding directive included in the prolog """
261 def __init__(self
, fontname
, basefontname
, encodingname
):
262 """ include font re-encoding directive specified by
264 - fontname: PostScript FontName of the new reencoded font
265 - basefontname: PostScript FontName of the original font
266 - encname: name of the encoding
268 Before being able to reencode a font, you have to include the
269 encoding via a fontencoding prolog item with name=encname
273 self
.type = "fontreencoding"
274 self
.id = self
.fontname
= fontname
275 self
.basefontname
= basefontname
276 self
.encodingname
= encodingname
278 def output(self
, file, writer
, registry
):
279 file.write("%%%%BeginProcSet: %s\n" % self
.fontname
)
280 file.write("/%s /%s %s ReEncodeFont\n" % (self
.basefontname
, self
.fontname
, self
.encodingname
))
281 file.write("%%EndProcSet\n")
284 _ReEncodeFont
= PSdefinition("ReEncodeFont", """{
287 /newencoding exch def
288 /newfontname exch def
289 /basefontname exch def
290 /basefontdict basefontname findfont def
291 /newfontdict basefontdict maxlength dict def
293 exch dup dup /FID ne exch /Encoding ne and
294 { exch newfontdict 3 1 roll put }
298 newfontdict /FontName newfontname put
299 newfontdict /Encoding newencoding put
300 newfontname newfontdict definefont pop
307 def __init__(self
, document
, file):
308 if len(document
.pages
) != 1:
309 raise ValueError("EPS file can be constructed out of a single page document only")
310 page
= document
.pages
[0]
317 if not filename
.endswith(".eps"):
320 file = open(filename
, "w")
322 raise IOError("cannot open output file")
326 pagefile
= cStringIO
.StringIO()
327 registry
= PSregistry()
329 pagebbox
= bbox
.empty()
331 page
.processPS(pagefile
, self
, acontext
, registry
, pagebbox
)
333 file.write("%!PS-Adobe-3.0 EPSF-3.0\n")
335 file.write("%%%%BoundingBox: %d %d %d %d\n" % pagebbox
.lowrestuple_pt())
336 file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % pagebbox
.highrestuple_pt())
337 file.write("%%%%Creator: PyX %s\n" % version
.version
)
338 file.write("%%%%Title: %s\n" % filename
)
339 file.write("%%%%CreationDate: %s\n" %
340 time
.asctime(time
.localtime(time
.time())))
341 file.write("%%EndComments\n")
343 file.write("%%BeginProlog\n")
344 registry
.output(file, self
)
345 file.write("%%EndProlog\n")
347 file.write(pagefile
.getvalue())
350 file.write("showpage\n")
351 file.write("%%Trailer\n")
352 file.write("%%EOF\n")
357 def __init__(self
, document
, file, writebbox
=0):
362 if not filename
.endswith(".ps"):
365 file = open(filename
, "w")
367 raise IOError("cannot open output file")
371 # We first have to process the content of the pages, writing them into the stream pagesfile
372 # Doing so, we fill the registry and also calculate the page bounding boxes, which are
373 # stored in page._bbox for every page
374 pagesfile
= cStringIO
.StringIO()
375 registry
= PSregistry()
377 # calculated bounding boxes of the whole document
378 documentbbox
= bbox
.empty()
380 for nr
, page
in enumerate(document
.pages
):
381 # process contents of page
382 pagefile
= cStringIO
.StringIO()
384 pagebbox
= bbox
.empty()
385 page
.processPS(pagefile
, self
, acontext
, registry
, pagebbox
)
387 documentbbox
+= pagebbox
389 pagesfile
.write("%%%%Page: %s %d\n" % (page
.pagename
is None and str(nr
+1) or page
.pagename
, nr
+1))
391 pagesfile
.write("%%%%PageMedia: %s\n" % page
.paperformat
.name
)
392 pagesfile
.write("%%%%PageOrientation: %s\n" % (page
.rotated
and "Landscape" or "Portrait"))
393 if pagebbox
and writebbox
:
394 pagesfile
.write("%%%%PageBoundingBox: %d %d %d %d\n" % pagebbox
.lowrestuple_pt())
397 pagesfile
.write("%%BeginPageSetup\n")
398 pagesfile
.write("/pgsave save def\n")
400 pagesfile
.write("%%EndPageSetup\n")
401 pagesfile
.write(pagefile
.getvalue())
403 pagesfile
.write("pgsave restore\n")
404 pagesfile
.write("showpage\n")
405 pagesfile
.write("%%PageTrailer\n")
407 file.write("%!PS-Adobe-3.0\n")
408 if documentbbox
and writebbox
:
409 file.write("%%%%BoundingBox: %d %d %d %d\n" % documentbbox
.lowrestuple_pt())
410 file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % documentbbox
.highrestuple_pt())
411 file.write("%%%%Creator: PyX %s\n" % version
.version
)
412 file.write("%%%%Title: %s\n" % filename
)
413 file.write("%%%%CreationDate: %s\n" %
414 time
.asctime(time
.localtime(time
.time())))
416 # required paper formats
418 for page
in document
.pages
:
420 paperformats
[page
.paperformat
] = page
.paperformat
423 for paperformat
in paperformats
.values():
425 file.write("%%DocumentMedia: ")
429 file.write("%s %d %d 75 white ()\n" % (paperformat
.name
,
430 unit
.topt(paperformat
.width
),
431 unit
.topt(paperformat
.height
)))
433 # file.write(%%DocumentNeededResources: ") # register not downloaded fonts here
435 file.write("%%%%Pages: %d\n" % len(document
.pages
))
436 file.write("%%PageOrder: Ascend\n")
437 file.write("%%EndComments\n")
439 # document defaults section
440 #file.write("%%BeginDefaults\n")
441 #file.write("%%EndDefaults\n")
443 # document prolog section
444 file.write("%%BeginProlog\n")
445 registry
.output(file, self
)
446 file.write("%%EndProlog\n")
448 # document setup section
449 #file.write("%%BeginSetup\n")
450 #file.write("%%EndSetup\n")
452 file.write(pagesfile
.getvalue())
455 file.write("%%Trailer\n")
456 file.write("%%EOF\n")
461 self
.linewidth_pt
= None
462 self
.colorspace
= None
465 def __call__(self
, **kwargs
):
466 newcontext
= copy
.copy(self
)
467 for key
, value
in kwargs
.items():
468 setattr(newcontext
, key
, value
)