2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2005 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-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 # XXX what are the correct base classes of clip and pattern
26 """The canvas module provides a PostScript canvas class and related classes
28 A canvas holds a collection of all elements and corresponding attributes to be
32 import attr
, base
, deco
, deformer
, unit
, prolog
, style
, trafo
38 class clip(base
.canvasitem
):
40 """class for use in canvas constructor which clips to a path"""
42 def __init__(self
, path
):
43 """construct a clip instance for a given path"""
47 # as a canvasitem a clipping path has NO influence on the bbox...
51 # ... but for clipping, we nevertheless need the bbox
52 return self
.path
.bbox()
54 def outputPS(self
, file):
55 file.write("newpath\n")
56 self
.path
.outputPS(file)
59 def outputPDF(self
, file):
60 self
.path
.outputPDF(file)
64 # general canvas class
67 class _canvas(base
.canvasitem
):
69 """a canvas holds a collection of canvasitems"""
71 def __init__(self
, attrs
=[], texrunner
=None):
75 The canvas can be modfied by supplying args, which have
76 to be instances of one of the following classes:
77 - trafo.trafo (leading to a global transformation of the canvas)
78 - canvas.clip (clips the canvas)
79 - style.strokestyle, style.fillstyle (sets some global attributes of the canvas)
81 Note that, while the first two properties are fixed for the
82 whole canvas, the last one can be changed via canvas.set().
84 The texrunner instance used for the text method can be specified
85 using the texrunner argument. It defaults to text.defaulttexrunner
90 self
.trafo
= trafo
.trafo()
92 if texrunner
is not None:
93 self
.texrunner
= texrunner
95 # prevent cyclic imports
97 self
.texrunner
= text
.defaulttexrunner
100 if isinstance(attr
, trafo
.trafo_pt
):
101 self
.trafo
= self
.trafo
*attr
102 self
.items
.append(attr
)
103 elif isinstance(attr
, clip
):
104 if self
.clipbbox
is None:
105 self
.clipbbox
= attr
.clipbbox().transformed(self
.trafo
)
107 self
.clippbox
*= attr
.clipbbox().transformed(self
.trafo
)
108 self
.items
.append(attr
)
113 """returns bounding box of canvas"""
115 for cmd
in self
.items
:
119 elif abbox
is not None:
122 # transform according to our global transformation and
123 # intersect with clipping bounding box (which has already been
124 # transformed in canvas.__init__())
125 if obbox
is not None and self
.clipbbox
is not None:
126 return obbox
.transformed(self
.trafo
)*self
.clipbbox
127 elif obbox
is not None:
128 return obbox
.transformed(self
.trafo
)
134 for cmd
in self
.items
:
135 result
.extend(cmd
.prolog())
138 def outputPS(self
, file):
140 file.write("gsave\n")
141 for cmd
in self
.items
:
143 file.write("grestore\n")
145 def outputPDF(self
, file):
147 file.write("q\n") # gsave
148 for cmd
in self
.items
:
150 file.write("Q\n") # grestore
152 def insert(self
, item
, attrs
=None):
153 """insert item in the canvas.
155 If attrs are passed, a canvas containing the item is inserted applying attrs.
161 if not isinstance(item
, base
.canvasitem
):
162 raise RuntimeError("only instances of base.canvasitem can be inserted into a canvas")
167 self
.items
.append(sc
)
169 self
.items
.append(item
)
173 def set(self
, attrs
):
174 """sets styles args globally for the rest of the canvas
177 attr
.checkattrs(attrs
, [style
.strokestyle
, style
.fillstyle
])
181 def draw(self
, path
, attrs
):
182 """draw path on canvas using the style given by args
184 The argument attrs consists of PathStyles, which modify
185 the appearance of the path, PathDecos, which add some new
186 visual elements to the path, or trafos, which are applied
187 before drawing the path.
191 attrs
= attr
.mergeattrs(attrs
)
192 attr
.checkattrs(attrs
, [deco
.deco
, deformer
.deformer
, style
.fillstyle
, style
.strokestyle
])
194 for adeformer
in attr
.getattrs(attrs
, [deformer
.deformer
]):
195 path
= adeformer
.deform(path
)
197 styles
= attr
.getattrs(attrs
, [style
.fillstyle
, style
.strokestyle
])
198 dp
= deco
.decoratedpath(path
, styles
=styles
)
200 # add path decorations and modify path accordingly
201 for adeco
in attr
.getattrs(attrs
, [deco
.deco
]):
202 dp
= adeco
.decorate(dp
)
206 def stroke(self
, path
, attrs
=[]):
207 """stroke path on canvas using the style given by args
209 The argument attrs consists of PathStyles, which modify
210 the appearance of the path, PathDecos, which add some new
211 visual elements to the path, or trafos, which are applied
212 before drawing the path.
216 self
.draw(path
, [deco
.stroked
]+list(attrs
))
218 def fill(self
, path
, attrs
=[]):
219 """fill path on canvas using the style given by args
221 The argument attrs consists of PathStyles, which modify
222 the appearance of the path, PathDecos, which add some new
223 visual elements to the path, or trafos, which are applied
224 before drawing the path.
228 self
.draw(path
, [deco
.filled
]+list(attrs
))
230 def settexrunner(self
, texrunner
):
231 """sets the texrunner to be used to within the text and text_pt methods"""
233 self
.texrunner
= texrunner
235 def text(self
, x
, y
, atext
, *args
, **kwargs
):
236 """insert a text into the canvas
238 inserts a textbox created by self.texrunner.text into the canvas
240 returns the inserted textbox"""
242 return self
.insert(self
.texrunner
.text(x
, y
, atext
, *args
, **kwargs
))
245 def text_pt(self
, x
, y
, atext
, *args
):
246 """insert a text into the canvas
248 inserts a textbox created by self.texrunner.text_pt into the canvas
250 returns the inserted textbox"""
252 return self
.insert(self
.texrunner
.text_pt(x
, y
, atext
, *args
))
255 # canvas for patterns
258 class pattern(_canvas
, attr
.exclusiveattr
, style
.fillstyle
):
260 def __init__(self
, painttype
=1, tilingtype
=1, xstep
=None, ystep
=None, bbox
=None, trafo
=None):
261 _canvas
.__init
__(self
)
262 attr
.exclusiveattr
.__init
__(self
, pattern
)
263 self
.id = "pattern%d" % id(self
)
264 if painttype
not in (1,2):
265 raise ValueError("painttype must be 1 or 2")
266 self
.painttype
= painttype
267 if tilingtype
not in (1,2,3):
268 raise ValueError("tilingtype must be 1, 2 or 3")
269 self
.tilingtype
= tilingtype
272 self
.patternbbox
= bbox
273 self
.patterntrafo
= trafo
278 def outputPS(self
, file):
279 file.write("%s setpattern\n" % self
.id)
282 realpatternbbox
= _canvas
.bbox(self
)
283 if self
.xstep
is None:
284 xstep
= unit
.topt(realpatternbbox
.width())
286 xstep
= unit
.topt(self
.xstep
)
287 if self
.ystep
is None:
288 ystep
= unit
.topt(realpatternbbox
.height())
290 ystep
= unit
.topt(self
.ystep
)
292 raise ValueError("xstep in pattern cannot be zero")
294 raise ValueError("ystep in pattern cannot be zero")
295 patternbbox
= self
.patternbbox
or realpatternbbox
.enlarged(5*unit
.pt
)
297 patternprefix
= "\n".join(("<<",
299 "/PaintType %d" % self
.painttype
,
300 "/TilingType %d" % self
.tilingtype
,
301 "/BBox[%s]" % str(patternbbox
),
304 "/PaintProc {\nbegin\n"))
305 stringfile
= cStringIO
.StringIO()
306 _canvas
.outputPS(self
, stringfile
)
307 patternproc
= stringfile
.getvalue()
309 patterntrafostring
= self
.patterntrafo
is None and "matrix" or str(self
.patterntrafo
)
310 patternsuffix
= "end\n} bind\n>>\n%s\nmakepattern" % patterntrafostring
312 pr
= _canvas
.prolog(self
)
313 pr
.append(prolog
.definition(self
.id, "".join((patternprefix
, patternproc
, patternsuffix
))))
316 pattern
.clear
= attr
.clearclass(pattern
)
319 # user canvas class which adds a few convenience methods for single page output
322 class canvas(_canvas
):
324 """a canvas holds a collection of canvasitems"""
326 def writeEPSfile(self
, filename
, **kwargs
):
328 document
.document([document
.page(self
, **kwargs
)]).writeEPSfile(filename
)
330 def writePDFfile(self
, filename
, **kwargs
):
332 document
.document([document
.page(self
, **kwargs
)]).writePDFfile(filename
)
334 def writetofile(self
, filename
, *args
, **kwargs
):
335 if filename
[-4:] == ".eps":
336 self
.writeEPSfile(filename
, *args
, **kwargs
)
337 elif filename
[-4:] == ".pdf":
338 self
.writePDFfile(filename
, *args
, **kwargs
)
340 sys
.stderr
.write("*** PyX Warning: deprecated usage of writetofile -- writetofile needs a filename extension or use an explicit call to writeEPSfile or the like\n")
341 self
.writeEPSfile(filename
, *args
, **kwargs
)