2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002, 2003 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002, 2003 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
25 # - should we improve on the arc length -> arg parametrization routine or
26 # should we at least factor it out?
27 # - How should we handle the passing of stroke and fill styles to
28 # arrows? Calls, new instances, ...?
31 """The canvas module provides a PostScript canvas class and related classes
33 A PostScript canvas is the pivotal object for the creation of (E)PS-Files.
34 It collects all the elements that should be displayed (PSCmds) together
35 with attributes if applicable. Furthermore, a canvas can be globally
36 transformed (i.e. translated, rotated, etc.) and clipped.
40 import math
, string
, StringIO
, time
41 import attrlist
, base
, bbox
, helper
, path
, unit
, prolog
, text
, trafo
, version
43 # known paperformats as tuple(width, height)
45 _paperformats
= { "a4" : ("210 t mm", "297 t mm"),
46 "a3" : ("297 t mm", "420 t mm"),
47 "a2" : ("420 t mm", "594 t mm"),
48 "a1" : ("594 t mm", "840 t mm"),
49 "a0" : ("840 t mm", "1188 t mm"),
50 "a0b" : ("910 t mm", "1370 t mm"),
51 "letter" : ("8.5 t inch", "11 t inch"),
52 "legal" : ("8.5 t inch", "14 t inch")}
59 class CanvasException(Exception): pass
62 class linecap(base
.PathStyle
):
64 """linecap of paths"""
66 def __init__(self
, value
=0):
69 def write(self
, file):
70 file.write("%d setlinecap\n" % self
.value
)
72 linecap
.butt
= linecap(0)
73 linecap
.round = linecap(1)
74 linecap
.square
= linecap(2)
77 class linejoin(base
.PathStyle
):
79 """linejoin of paths"""
81 def __init__(self
, value
=0):
84 def write(self
, file):
85 file.write("%d setlinejoin\n" % self
.value
)
87 linejoin
.miter
= linejoin(0)
88 linejoin
.round = linejoin(1)
89 linejoin
.bevel
= linejoin(2)
92 class miterlimit(base
.PathStyle
):
94 """miterlimit of paths"""
96 def __init__(self
, value
=10.0):
99 def write(self
, file):
100 file.write("%f setmiterlimit\n" % self
.value
)
103 miterlimit
.lessthan180deg
= miterlimit(1/math
.sin(math
.pi
*180/360))
104 miterlimit
.lessthan90deg
= miterlimit(1/math
.sin(math
.pi
*90/360))
105 miterlimit
.lessthan60deg
= miterlimit(1/math
.sin(math
.pi
*60/360))
106 miterlimit
.lessthan45deg
= miterlimit(1/math
.sin(math
.pi
*45/360))
107 miterlimit
.lessthan11deg
= miterlimit(10) # the default, approximately 11.4783 degress
109 class dash(base
.PathStyle
):
113 def __init__(self
, pattern
=[], offset
=0):
117 def write(self
, file):
119 for element
in self
.pattern
:
120 patternstring
=patternstring
+ `element`
+ " "
122 file.write("[%s] %d setdash\n" % (patternstring
, self
.offset
))
125 class linestyle(base
.PathStyle
):
127 """linestyle (linecap together with dash) of paths"""
129 def __init__(self
, c
=linecap
.butt
, d
=dash([])):
133 def write(self
, file):
137 linestyle
.solid
= linestyle(linecap
.butt
, dash([]))
138 linestyle
.dashed
= linestyle(linecap
.butt
, dash([2]))
139 linestyle
.dotted
= linestyle(linecap
.round, dash([0, 3]))
140 linestyle
.dashdotted
= linestyle(linecap
.round, dash([0, 3, 3, 3]))
143 class linewidth(base
.PathStyle
, unit
.length
):
145 """linewidth of paths"""
147 def __init__(self
, l
="0 cm"):
148 unit
.length
.__init
__(self
, l
=l
, default_type
="w")
150 def write(self
, file):
151 file.write("%f setlinewidth\n" % unit
.topt(self
))
155 linewidth
.THIN
= linewidth("%f cm" % (_base
/math
.sqrt(32)))
156 linewidth
.THIn
= linewidth("%f cm" % (_base
/math
.sqrt(16)))
157 linewidth
.THin
= linewidth("%f cm" % (_base
/math
.sqrt(8)))
158 linewidth
.Thin
= linewidth("%f cm" % (_base
/math
.sqrt(4)))
159 linewidth
.thin
= linewidth("%f cm" % (_base
/math
.sqrt(2)))
160 linewidth
.normal
= linewidth("%f cm" % _base
)
161 linewidth
.thick
= linewidth("%f cm" % (_base
*math
.sqrt(2)))
162 linewidth
.Thick
= linewidth("%f cm" % (_base
*math
.sqrt(4)))
163 linewidth
.THick
= linewidth("%f cm" % (_base
*math
.sqrt(8)))
164 linewidth
.THIck
= linewidth("%f cm" % (_base
*math
.sqrt(16)))
165 linewidth
.THICk
= linewidth("%f cm" % (_base
*math
.sqrt(32)))
166 linewidth
.THICK
= linewidth("%f cm" % (_base
*math
.sqrt(64)))
173 class decoratedpath(base
.PSCmd
):
176 The main purpose of this class is during the drawing
177 (stroking/filling) of a path. It collects attributes for the
178 stroke and/or fill operations.
182 path
, strokepath
=None, fillpath
=None,
183 styles
=None, strokestyles
=None, fillstyles
=None,
188 # path to be stroked or filled (or None)
189 self
.strokepath
= strokepath
190 self
.fillpath
= fillpath
192 # global style for stroking and filling and subdps
193 self
.styles
= helper
.ensurelist(styles
)
195 # styles which apply only for stroking and filling
196 self
.strokestyles
= helper
.ensurelist(strokestyles
)
197 self
.fillstyles
= helper
.ensurelist(fillstyles
)
199 # additional elements of the path, e.g., arrowheads,
200 # which are by themselves decoratedpaths
201 self
.subdps
= helper
.ensurelist(subdps
)
203 def addsubdp(self
, subdp
):
204 """add a further decorated path to the list of subdps"""
205 self
.subdps
.append(subdp
)
208 return reduce(lambda x
,y
: x
+y
.bbox(),
214 for style
in list(self
.styles
) + list(self
.fillstyles
) + list(self
.strokestyles
):
215 result
.extend(style
.prolog())
218 def write(self
, file):
219 # draw (stroke and/or fill) the decoratedpath on the canvas
220 # while trying to produce an efficient output, e.g., by
221 # not writing one path two times
224 def _writestyles(styles
, file=file):
228 # apply global styles
231 _writestyles(self
.styles
)
233 if self
.fillpath
is not None:
234 _newpath().write(file)
235 self
.fillpath
.write(file)
237 if self
.strokepath
==self
.fillpath
:
238 # do efficient stroking + filling
242 _writestyles(self
.fillstyles
)
245 _grestore().write(file)
247 if self
.strokestyles
:
249 _writestyles(self
.strokestyles
)
251 _stroke().write(file)
253 if self
.strokestyles
:
254 _grestore().write(file)
256 # only fill fillpath - for the moment
259 _writestyles(self
.fillstyles
)
264 _grestore().write(file)
266 if self
.strokepath
is not None and self
.strokepath
!=self
.fillpath
:
267 # this is the only relevant case still left
268 # Note that a possible stroking has already been done.
270 if self
.strokestyles
:
272 _writestyles(self
.strokestyles
)
274 _newpath().write(file)
275 self
.strokepath
.write(file)
276 _stroke().write(file)
278 if self
.strokestyles
:
279 _grestore().write(file)
281 if not self
.strokepath
is not None and not self
.fillpath
:
282 raise RuntimeError("Path neither to be stroked nor filled")
284 # now, draw additional subdps
285 for subdp
in self
.subdps
:
288 # restore global styles
290 _grestore().write(file)
300 In contrast to path styles, path decorators depend on the concrete
301 path to which they are applied. In particular, they don't make
302 sense without any path and can thus not be used in canvas.set!
306 def decorate(self
, dp
):
307 """apply a style to a given decoratedpath object dp
309 decorate accepts a decoratedpath object dp, applies PathStyle
310 by modifying dp in place and returning the new dp.
316 # stroked and filled: basic PathDecos which stroked and fill,
317 # respectively the path
320 class stroked(PathDeco
):
322 """stroked is a PathDecorator, which draws the outline of the path"""
324 def __init__(self
, *styles
):
325 self
.styles
= list(styles
)
327 def decorate(self
, dp
):
328 dp
.strokepath
= dp
.path
329 dp
.strokestyles
= self
.styles
334 class filled(PathDeco
):
336 """filled is a PathDecorator, which fills the interior of the path"""
338 def __init__(self
, *styles
):
339 self
.styles
= list(styles
)
341 def decorate(self
, dp
):
342 dp
.fillpath
= dp
.path
343 dp
.fillstyles
= self
.styles
347 def _arrowheadtemplatelength(anormpath
, size
):
348 "calculate length of arrowhead template (in parametrisation of anormpath)"
350 tx
, ty
= anormpath
.begin()
352 # obtain arrow template by using path up to first intersection
353 # with circle around tip (as suggested by Michael Schindler)
354 ipar
= anormpath
.intersect(path
.circle(tx
, ty
, size
))
358 # if this doesn't work, use first order conversion from pts to
359 # the bezier curve's parametrization
360 tlen
= unit
.topt(anormpath
.tangent(0).arclength())
362 alen
= unit
.topt(size
)/tlen
363 except ArithmeticError:
364 # take maximum, we can get
365 alen
= anormpath
.range()
366 if alen
> anormpath
.range(): alen
= anormpath
.range()
371 def _arrowhead(anormpath
, size
, angle
, constriction
):
373 """helper routine, which returns an arrowhead for a normpath
375 returns arrowhead at begin of anormpath with size,
376 opening angle and relative constriction
379 alen
= _arrowheadtemplatelength(anormpath
, size
)
380 tx
, ty
= anormpath
.begin()
382 # now we construct the template for our arrow but cutting
383 # the path a the corresponding length
384 arrowtemplate
= anormpath
.split(alen
)[0]
386 # from this template, we construct the two outer curves
388 arrowl
= arrowtemplate
.transformed(trafo
.rotate(-angle
/2.0, tx
, ty
))
389 arrowr
= arrowtemplate
.transformed(trafo
.rotate( angle
/2.0, tx
, ty
))
391 # now come the joining backward parts
393 # arrow with constriction
395 # constriction point (cx, cy) lies on path
396 cx
, cy
= anormpath
.at(constriction
*alen
)
398 arrowcr
= path
.line(*(arrowr
.end()+(cx
,cy
)))
400 arrow
= arrowl
.reversed() << arrowr
<< arrowcr
401 arrow
.append(path
.closepath())
403 # arrow without constriction
404 arrow
= arrowl
.reversed() << arrowr
405 arrow
.append(path
.closepath())
409 class arrow(PathDeco
):
411 """A general arrow"""
414 position
, size
, angle
=45, constriction
=0.8,
415 styles
=None, strokestyles
=None, fillstyles
=None):
416 self
.position
= position
419 self
.constriction
= constriction
420 self
.styles
= helper
.ensurelist(styles
)
421 self
.strokestyles
= helper
.ensurelist(strokestyles
)
422 self
.fillstyles
= helper
.ensurelist(fillstyles
)
424 def __call__(self
, *styles
):
425 fillstyles
= [ style
for s
in styles
if isinstance(s
, filled
)
426 for style
in s
.styles
]
428 strokestyles
= [ style
for s
in styles
if isinstance(s
, stroked
)
429 for style
in s
.styles
]
431 styles
= [ style
for style
in styles
432 if not (isinstance(style
, filled
) or
433 isinstance(style
, stroked
)) ]
435 return arrow(position
=self
.position
,
438 constriction
=self
.constriction
,
440 strokestyles
=strokestyles
,
441 fillstyles
=fillstyles
)
443 def decorate(self
, dp
):
445 # TODO: error, when strokepath is not defined
447 # convert to normpath if necessary
448 if isinstance(dp
.strokepath
, path
.normpath
):
449 anormpath
=dp
.strokepath
451 anormpath
=path
.normpath(dp
.path
)
454 anormpath
=anormpath
.reversed()
456 ahead
= _arrowhead(anormpath
, self
.size
, self
.angle
, self
.constriction
)
458 dp
.addsubdp(decoratedpath(ahead
,
459 strokepath
=ahead
, fillpath
=ahead
,
461 strokestyles
=self
.strokestyles
,
462 fillstyles
=self
.fillstyles
))
464 alen
= _arrowheadtemplatelength(anormpath
, self
.size
)
466 if self
.constriction
:
467 ilen
= alen
*self
.constriction
471 # correct somewhat for rotation of arrow segments
472 ilen
= ilen
*math
.cos(math
.pi
*self
.angle
/360.0)
474 # this is the rest of the path, we have to draw
475 anormpath
= anormpath
.split(ilen
)[1]
477 # go back to original orientation, if necessary
479 anormpath
=anormpath
.reversed()
481 # set the new (shortened) strokepath
482 dp
.strokepath
=anormpath
489 """arrow at begin of path"""
491 def __init__(self
, size
, angle
=45, constriction
=0.8,
492 styles
=None, strokestyles
=None, fillstyles
=None):
497 constriction
=constriction
,
499 strokestyles
=strokestyles
,
500 fillstyles
=fillstyles
)
504 barrow
.SMALL
= barrow(_base
/math
.sqrt(64))
505 barrow
.SMALl
= barrow(_base
/math
.sqrt(32))
506 barrow
.SMAll
= barrow(_base
/math
.sqrt(16))
507 barrow
.SMall
= barrow(_base
/math
.sqrt(8))
508 barrow
.Small
= barrow(_base
/math
.sqrt(4))
509 barrow
.small
= barrow(_base
/math
.sqrt(2))
510 barrow
.normal
= barrow(_base
)
511 barrow
.large
= barrow(_base
*math
.sqrt(2))
512 barrow
.Large
= barrow(_base
*math
.sqrt(4))
513 barrow
.LArge
= barrow(_base
*math
.sqrt(8))
514 barrow
.LARge
= barrow(_base
*math
.sqrt(16))
515 barrow
.LARGe
= barrow(_base
*math
.sqrt(32))
516 barrow
.LARGE
= barrow(_base
*math
.sqrt(64))
521 """arrow at end of path"""
523 def __init__(self
, size
, angle
=45, constriction
=0.8,
524 styles
=[], strokestyles
=[], fillstyles
=[]):
529 constriction
=constriction
,
531 strokestyles
=strokestyles
,
532 fillstyles
=fillstyles
)
535 earrow
.SMALL
= earrow(_base
/math
.sqrt(64))
536 earrow
.SMALl
= earrow(_base
/math
.sqrt(32))
537 earrow
.SMAll
= earrow(_base
/math
.sqrt(16))
538 earrow
.SMall
= earrow(_base
/math
.sqrt(8))
539 earrow
.Small
= earrow(_base
/math
.sqrt(4))
540 earrow
.small
= earrow(_base
/math
.sqrt(2))
541 earrow
.normal
= earrow(_base
)
542 earrow
.large
= earrow(_base
*math
.sqrt(2))
543 earrow
.Large
= earrow(_base
*math
.sqrt(4))
544 earrow
.LArge
= earrow(_base
*math
.sqrt(8))
545 earrow
.LARge
= earrow(_base
*math
.sqrt(16))
546 earrow
.LARGe
= earrow(_base
*math
.sqrt(32))
547 earrow
.LARGE
= earrow(_base
*math
.sqrt(64))
553 class clip(base
.PSCmd
):
555 """class for use in canvas constructor which clips to a path"""
557 def __init__(self
, path
):
558 """construct a clip instance for a given path"""
562 # as a PSCmd a clipping path has NO influence on the bbox...
566 # ... but for clipping, we nevertheless need the bbox
567 return self
.path
.bbox()
569 def write(self
, file):
570 _newpath().write(file)
571 self
.path
.write(file)
575 # some very primitive Postscript operators
578 class _newpath(base
.PSOp
):
579 def write(self
, file):
580 file.write("newpath\n")
583 class _stroke(base
.PSOp
):
584 def write(self
, file):
585 file.write("stroke\n")
588 class _fill(base
.PSOp
):
589 def write(self
, file):
593 class _clip(base
.PSOp
):
594 def write(self
, file):
598 class _gsave(base
.PSOp
):
599 def write(self
, file):
600 file.write("gsave\n")
603 class _grestore(base
.PSOp
):
604 def write(self
, file):
605 file.write("grestore\n")
611 class _canvas(base
.PSCmd
, attrlist
.attrlist
):
613 """a canvas is a collection of PSCmds together with PSAttrs"""
615 def __init__(self
, *args
):
617 """construct a canvas
619 The canvas can be modfied by supplying args, which have
620 to be instances of one of the following classes:
621 - trafo.trafo (leading to a global transformation of the canvas)
622 - canvas.clip (clips the canvas)
623 - base.PathStyle (sets some global attributes of the canvas)
625 Note that, while the first two properties are fixed for the
626 whole canvas, the last one can be changed via canvas.set()
631 self
.trafo
= trafo
.trafo()
632 self
.clipbbox
= bbox
._bbox
()
633 self
.texrunner
= text
.defaulttexrunner
636 if isinstance(arg
, trafo
._trafo
):
637 self
.trafo
= self
.trafo
*arg
638 self
.PSOps
.append(arg
)
639 elif isinstance(arg
, clip
):
640 self
.clipbbox
=(self
.clipbbox
*
641 arg
.clipbbox().transformed(self
.trafo
))
642 self
.PSOps
.append(arg
)
647 """returns bounding box of canvas"""
648 obbox
= reduce(lambda x
,y
:
649 isinstance(y
, base
.PSCmd
) and x
+y
.bbox() or x
,
653 # transform according to our global transformation and
654 # intersect with clipping bounding box (which have already been
655 # transformed in canvas.__init__())
656 return obbox
.transformed(self
.trafo
)*self
.clipbbox
660 for cmd
in self
.PSOps
:
661 result
.extend(cmd
.prolog())
664 def write(self
, file):
666 for cmd
in self
.PSOps
:
668 _grestore().write(file)
670 def insert(self
, PSOp
, *args
):
671 """insert PSOp in the canvas.
673 If args are given, then insert a canvas containing PSOp applying args.
682 self
.PSOps
.append(sc
)
684 self
.PSOps
.append(PSOp
)
688 def set(self
, *styles
):
689 """sets styles args globally for the rest of the canvas
696 if not isinstance(style
, base
.PathStyle
):
697 raise NotImplementedError, "can only set PathStyle"
703 def draw(self
, path
, *args
):
704 """draw path on canvas using the style given by args
706 The argument list args consists of PathStyles, which modify
707 the appearance of the path, PathDecos, which add some new
708 visual elements to the path, or trafos, which are applied
709 before drawing the path.
715 self
.attrcheck(args
, allowmulti
=(base
.PathStyle
, PathDeco
, trafo
._trafo
))
717 for t
in self
.attrgetall(args
, trafo
._trafo
, ()):
718 path
= path
.transformed(t
)
720 dp
= decoratedpath(path
)
723 dp
.styles
= self
.attrgetall(args
, base
.PathStyle
, ())
725 # add path decorations and modify path accordingly
726 for deco
in self
.attrgetall(args
, PathDeco
, ()):
727 dp
= deco
.decorate(dp
)
733 def stroke(self
, path
, *args
):
734 """stroke path on canvas using the style given by args
736 The argument list args consists of PathStyles, which modify
737 the appearance of the path, PathDecos, which add some new
738 visual elements to the path, or trafos, which are applied
739 before drawing the path.
745 return self
.draw(path
, stroked(), *args
)
747 def fill(self
, path
, *args
):
748 """fill path on canvas using the style given by args
750 The argument list args consists of PathStyles, which modify
751 the appearance of the path, PathDecos, which add some new
752 visual elements to the path, or trafos, which are applied
753 before drawing the path.
759 return self
.draw(path
, filled(), *args
)
761 def settexrunner(self
, texrunner
):
762 """sets the texrunner to be used to within the text and _text methods"""
764 self
.texrunner
= texrunner
766 def text(self
, x
, y
, atext
, *args
):
767 """insert a text into the canvas
769 inserts a textbox created by self.texrunner.text into the canvas
771 returns the inserted textbox"""
773 return self
.insert(self
.texrunner
.text(x
, y
, atext
, *args
))
776 def _text(self
, x
, y
, atext
, *args
):
777 """insert a text into the canvas
779 inserts a textbox created by self.texrunner._text into the canvas
781 returns the inserted textbox"""
783 return self
.insert(self
.texrunner
._text
(x
, y
, atext
, *args
))
786 # canvas for patterns
789 class pattern(_canvas
, base
.PathStyle
):
791 def __init__(self
, painttype
=1, tilingtype
=1, xstep
=None, ystep
=None, bbox
=None, trafo
=None):
792 _canvas
.__init
__(self
)
793 self
.id = "pattern%d" % id(self
)
794 # XXX: some checks are in order
795 if painttype
not in (1,2):
796 raise ValueError("painttype must be 1 or 2")
797 self
.painttype
= painttype
798 if tilingtype
not in (1,2,3):
799 raise ValueError("tilingtype must be 1, 2 or 3")
800 self
.tilingtype
= tilingtype
803 self
.patternbbox
= bbox
804 self
.patterntrafo
= trafo
809 def write(self
, file):
810 file.write("%s setpattern\n" % self
.id)
813 realpatternbbox
= _canvas
.bbox(self
)
814 if self
.xstep
is None:
815 xstep
= unit
.topt(realpatternbbox
.width())
817 xstep
= unit
.topt(unit
.length(self
.xstep
))
818 if self
.ystep
is None:
819 ystep
= unit
.topt(realpatternbbox
.height())
821 ystep
= unit
.topt(unit
.length(self
.ystep
))
823 raise ValueError("xstep in pattern cannot be zero")
825 raise ValueError("ystep in pattern cannot be zero")
826 patternbbox
= self
.patternbbox
or realpatternbbox
.enlarged("5 pt")
828 patternprefix
= string
.join(("<<",
830 "/PaintType %d" % self
.painttype
,
831 "/TilingType %d" % self
.tilingtype
,
832 "/BBox[%s]" % str(patternbbox
),
835 "/PaintProc {\nbegin\n"),
837 stringfile
= StringIO
.StringIO()
838 _canvas
.write(self
, stringfile
)
839 patternproc
= stringfile
.getvalue()
841 patterntrafostring
= self
.patterntrafo
is None and "matrix" or str(self
.patterntrafo
)
842 patternsuffix
= "end\n} bind\n>>\n%s\nmakepattern" % patterntrafostring
844 pr
= _canvas
.prolog(self
)
845 pr
.append(prolog
.definition(self
.id, string
.join((patternprefix
, patternproc
, patternsuffix
), "")))
849 # The main canvas class
852 class canvas(_canvas
):
854 """a canvas is a collection of PSCmds together with PSAttrs"""
856 def writetofile(self
, filename
, paperformat
=None, rotated
=0, fittosize
=0, margin
="1 t cm",
857 bbox
=None, bboxenlarge
="1 t pt"):
858 """write canvas to EPS file
860 If paperformat is set to a known paperformat, the output will be centered on
863 If rotated is set, the output will first be rotated by 90 degrees.
865 If fittosize is set, then the output is scaled to the size of the
866 page (minus margin). In that case, the paperformat the specification
867 of the paperformat is obligatory.
869 The bbox parameter overrides the automatic bounding box determination.
870 bboxenlarge may be used to enlarge the bbox of the canvas (or the
871 manually specified bbox).
874 if filename
[-4:]!=".eps":
875 filename
= filename
+ ".eps"
878 file = open(filename
, "w")
880 raise IOError("cannot open output file")
882 abbox
= bbox
is not None and bbox
or self
.bbox()
883 abbox
= abbox
.enlarged(bboxenlarge
)
884 ctrafo
= None # global transformation of canvas
887 ctrafo
= trafo
._rotate
(90,
888 0.5*(abbox
.llx
+abbox
.urx
),
889 0.5*(abbox
.lly
+abbox
.ury
))
892 # center (optionally rotated) output on page
894 width
, height
= _paperformats
[paperformat
]
896 raise KeyError, "unknown paperformat '%s'" % paperformat
897 width
= unit
.topt(width
)
898 height
= unit
.topt(height
)
900 if not ctrafo
: ctrafo
=trafo
.trafo()
902 ctrafo
= ctrafo
._translated
(0.5*(width
-(abbox
.urx
-abbox
.llx
))-
904 0.5*(height
-(abbox
.ury
-abbox
.lly
))-
908 # scale output to pagesize - margins
909 margin
= unit
.topt(margin
)
910 if 2*margin
> min(width
, height
):
911 raise RuntimeError("Margins too broad for selected paperformat. Aborting.")
914 sfactor
= min((height
-2*margin
)/(abbox
.urx
-abbox
.llx
),
915 (width
-2*margin
)/(abbox
.ury
-abbox
.lly
))
917 sfactor
= min((width
-2*margin
)/(abbox
.urx
-abbox
.llx
),
918 (height
-2*margin
)/(abbox
.ury
-abbox
.lly
))
920 ctrafo
= ctrafo
._scaled
(sfactor
, sfactor
, 0.5*width
, 0.5*height
)
924 raise ValueError("must specify paper size for fittosize")
926 # if there has been a global transformation, adjust the bounding box
928 if ctrafo
: abbox
= abbox
.transformed(ctrafo
)
930 file.write("%!PS-Adobe-3.0 EPSF 3.0\n")
932 file.write("%%%%Creator: PyX %s\n" % version
.version
)
933 file.write("%%%%Title: %s\n" % filename
)
934 file.write("%%%%CreationDate: %s\n" %
935 time
.asctime(time
.localtime(time
.time())))
936 file.write("%%EndComments\n")
938 file.write("%%BeginProlog\n")
942 for pritem
in self
.prolog():
943 for mpritem
in mergedprolog
:
944 if mpritem
.merge(pritem
) is None: break
946 mergedprolog
.append(pritem
)
948 for pritem
in mergedprolog
:
951 file.write("%%EndProlog\n")
953 # again, if there has occured global transformation, apply it now
954 if ctrafo
: ctrafo
.write(file)
956 file.write("%f setlinewidth\n" % unit
.topt(linewidth
.normal
))
958 # here comes the actual content
961 file.write("showpage\n")
962 file.write("%%Trailer\n")
963 file.write("%%EOF\n")