added code example to 4.3.4 at request of J. Owens
[PyX/mjg.git] / pyx / canvas.py
blob0a8d04b239e0f045f613a4397ce4a1eca2a9e805
1 #!/usr/bin/env python
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 """The canvas module provides a PostScript canvas class and related classes
26 A canvas holds a collection of all elements and corresponding attributes to be
27 displayed. """
30 # canvas item
33 class canvasitem:
35 """Base class for everything which can be inserted into a canvas"""
37 def bbox(self):
38 """return bounding box of canvasitem or None"""
39 pass
41 def registerPS(self, registry):
42 """register resources needed for the canvasitem in the PS registry"""
43 pass
45 def registerPDF(self, registry):
46 """register resources needed for the canvasitem in the PDF registry"""
47 pass
49 def outputPS(self, file, writer, context):
50 """write PS code corresponding to canvasitem to file
52 - file has to provide a write(string) method
53 - writer is the PSwriter used for the output
54 - context is used for keeping track of the graphics state, in particular
55 for the emulation of PS behaviour regarding fill and stroke styles, for
56 keeping track of the currently selected font as well as of text regions.
57 """
58 pass
60 def outputPDF(self, file, writer, context):
61 """write PDF code corresponding to canvasitem to file using the given writer
62 and context.
64 - file has to provide a write(string) method
65 - writer contains properties like whether streamcompression is used.
66 - context is used for keeping track of the graphics state, in particular
67 for the emulation of PS behaviour regarding fill and stroke styles, for
68 keeping track of the currently selected font as well as of text regions.
69 """
70 pass
75 import attr, deco, deformer, document, style, trafo, type1font
79 # clipping class
82 class clip(canvasitem):
84 """class for use in canvas constructor which clips to a path"""
86 def __init__(self, path):
87 """construct a clip instance for a given path"""
88 self.path = path
90 def bbox(self):
91 # as a canvasitem a clipping path has NO influence on the bbox...
92 return None
94 def clipbbox(self):
95 # ... but for clipping, we nevertheless need the bbox
96 return self.path.bbox()
98 def outputPS(self, file, writer, context):
99 file.write("newpath\n")
100 self.path.outputPS(file, writer, context)
101 file.write("clip\n")
103 def outputPDF(self, file, writer, context):
104 self.path.outputPDF(file, writer, context)
105 file.write("W n\n")
109 # general canvas class
112 class _canvas(canvasitem):
114 """a canvas holds a collection of canvasitems"""
116 def __init__(self, attrs=[], texrunner=None):
118 """construct a canvas
120 The canvas can be modfied by supplying args, which have
121 to be instances of one of the following classes:
122 - trafo.trafo (leading to a global transformation of the canvas)
123 - canvas.clip (clips the canvas)
124 - style.strokestyle, style.fillstyle (sets some global attributes of the canvas)
126 Note that, while the first two properties are fixed for the
127 whole canvas, the last one can be changed via canvas.set().
129 The texrunner instance used for the text method can be specified
130 using the texrunner argument. It defaults to text.defaulttexrunner
134 self.items = []
135 self.trafo = trafo.trafo()
136 self.clipbbox = None
137 if texrunner is not None:
138 self.texrunner = texrunner
139 else:
140 # prevent cyclic imports
141 import text
142 self.texrunner = text.defaulttexrunner
144 for attr in attrs:
145 if isinstance(attr, trafo.trafo_pt):
146 self.trafo = self.trafo*attr
147 self.items.append(attr)
148 elif isinstance(attr, clip):
149 if self.clipbbox is None:
150 self.clipbbox = attr.clipbbox().transformed(self.trafo)
151 else:
152 self.clippbox *= attr.clipbbox().transformed(self.trafo)
153 self.items.append(attr)
154 else:
155 self.set([attr])
157 def __len__(self):
158 return len(self.items)
160 def __getitem__(self, i):
161 return self.items[i]
163 def bbox(self):
164 """returns bounding box of canvas"""
165 obbox = None
166 for cmd in self.items:
167 abbox = cmd.bbox()
168 if obbox is None:
169 obbox = abbox
170 elif abbox is not None:
171 obbox += abbox
173 # transform according to our global transformation and
174 # intersect with clipping bounding box (which has already been
175 # transformed in canvas.__init__())
176 if obbox is not None and self.clipbbox is not None:
177 return obbox.transformed(self.trafo)*self.clipbbox
178 elif obbox is not None:
179 return obbox.transformed(self.trafo)
180 else:
181 return self.clipbbox
183 def registerPS(self, registry):
184 for item in self.items:
185 item.registerPS(registry)
187 def registerPDF(self, registry):
188 for item in self.items:
189 item.registerPDF(registry)
191 def outputPS(self, file, writer, context):
192 context = context()
193 if self.items:
194 file.write("gsave\n")
195 for item in self.items:
196 item.outputPS(file, writer, context)
197 file.write("grestore\n")
199 def outputPDF(self, file, writer, context):
200 context = context()
201 if self.items:
202 file.write("q\n") # gsave
203 for item in self.items:
204 if isinstance(item, type1font.text_pt):
205 if not context.textregion:
206 file.write("BT\n")
207 context.textregion = 1
208 else:
209 if context.textregion:
210 file.write("ET\n")
211 context.textregion = 0
212 context.font = None
213 item.outputPDF(file, writer, context)
214 if context.textregion:
215 file.write("ET\n")
216 context.textregion = 0
217 context.font = None
218 file.write("Q\n") # grestore
220 def insert(self, item, attrs=None):
221 """insert item in the canvas.
223 If attrs are passed, a canvas containing the item is inserted applying attrs.
225 returns the item
229 if not isinstance(item, canvasitem):
230 raise RuntimeError("only instances of base.canvasitem can be inserted into a canvas")
232 if attrs:
233 sc = _canvas(attrs)
234 sc.insert(item)
235 self.items.append(sc)
236 else:
237 self.items.append(item)
239 return item
241 def set(self, attrs):
242 """sets styles args globally for the rest of the canvas
245 attr.checkattrs(attrs, [style.strokestyle, style.fillstyle])
246 for astyle in attrs:
247 self.insert(astyle)
249 def draw(self, path, attrs):
250 """draw path on canvas using the style given by args
252 The argument attrs consists of PathStyles, which modify
253 the appearance of the path, PathDecos, which add some new
254 visual elements to the path, or trafos, which are applied
255 before drawing the path.
259 attrs = attr.mergeattrs(attrs)
260 attr.checkattrs(attrs, [deco.deco, deformer.deformer, style.fillstyle, style.strokestyle])
262 for adeformer in attr.getattrs(attrs, [deformer.deformer]):
263 path = adeformer.deform(path)
265 styles = attr.getattrs(attrs, [style.fillstyle, style.strokestyle])
266 dp = deco.decoratedpath(path, styles=styles)
268 # add path decorations and modify path accordingly
269 for adeco in attr.getattrs(attrs, [deco.deco]):
270 adeco.decorate(dp, self.texrunner)
272 self.insert(dp)
274 def stroke(self, path, attrs=[]):
275 """stroke path on canvas using the style given by args
277 The argument attrs consists of PathStyles, which modify
278 the appearance of the path, PathDecos, which add some new
279 visual elements to the path, or trafos, which are applied
280 before drawing the path.
284 self.draw(path, [deco.stroked]+list(attrs))
286 def fill(self, path, attrs=[]):
287 """fill path on canvas using the style given by args
289 The argument attrs consists of PathStyles, which modify
290 the appearance of the path, PathDecos, which add some new
291 visual elements to the path, or trafos, which are applied
292 before drawing the path.
296 self.draw(path, [deco.filled]+list(attrs))
298 def settexrunner(self, texrunner):
299 """sets the texrunner to be used to within the text and text_pt methods"""
301 self.texrunner = texrunner
303 def text(self, x, y, atext, *args, **kwargs):
304 """insert a text into the canvas
306 inserts a textbox created by self.texrunner.text into the canvas
308 returns the inserted textbox"""
310 return self.insert(self.texrunner.text(x, y, atext, *args, **kwargs))
313 def text_pt(self, x, y, atext, *args):
314 """insert a text into the canvas
316 inserts a textbox created by self.texrunner.text_pt into the canvas
318 returns the inserted textbox"""
320 return self.insert(self.texrunner.text_pt(x, y, atext, *args))
323 # user canvas class which adds a few convenience methods for single page output
326 def _wrappedindocument(method):
327 def wrappedindocument(self, filename, *args, **kwargs):
328 d = document.document([document.page(self, *args, **kwargs)])
329 self.__doc__ = method.__doc__
330 return method(d, filename)
331 return wrappedindocument
333 class canvas(_canvas):
335 """a canvas holds a collection of canvasitems"""
337 writeEPSfile = _wrappedindocument(document.document.writeEPSfile)
338 writePSfile = _wrappedindocument(document.document.writePSfile)
339 writePDFfile = _wrappedindocument(document.document.writePDFfile)
340 writetofile = _wrappedindocument(document.document.writetofile)