dtk article sources
[PyX/mjg.git] / pyx / canvas.py
blobda38cb55b42b3e573bbfeb565e85613aa8ab335a
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 bbox(self):
158 """returns bounding box of canvas"""
159 obbox = None
160 for cmd in self.items:
161 abbox = cmd.bbox()
162 if obbox is None:
163 obbox = abbox
164 elif abbox is not None:
165 obbox += abbox
167 # transform according to our global transformation and
168 # intersect with clipping bounding box (which has already been
169 # transformed in canvas.__init__())
170 if obbox is not None and self.clipbbox is not None:
171 return obbox.transformed(self.trafo)*self.clipbbox
172 elif obbox is not None:
173 return obbox.transformed(self.trafo)
174 else:
175 return self.clipbbox
177 def registerPS(self, registry):
178 for item in self.items:
179 item.registerPS(registry)
181 def registerPDF(self, registry):
182 for item in self.items:
183 item.registerPDF(registry)
185 def outputPS(self, file, writer, context):
186 context = context()
187 if self.items:
188 file.write("gsave\n")
189 for item in self.items:
190 item.outputPS(file, writer, context)
191 file.write("grestore\n")
193 def outputPDF(self, file, writer, context):
194 context = context()
195 if self.items:
196 file.write("q\n") # gsave
197 for item in self.items:
198 if isinstance(item, type1font.text_pt):
199 if not context.textregion:
200 file.write("BT\n")
201 context.textregion = 1
202 else:
203 if context.textregion:
204 file.write("ET\n")
205 context.textregion = 0
206 context.font = None
207 item.outputPDF(file, writer, context)
208 if context.textregion:
209 file.write("ET\n")
210 context.textregion = 0
211 context.font = None
212 file.write("Q\n") # grestore
214 def insert(self, item, attrs=None):
215 """insert item in the canvas.
217 If attrs are passed, a canvas containing the item is inserted applying attrs.
219 returns the item
223 if not isinstance(item, canvasitem):
224 raise RuntimeError("only instances of base.canvasitem can be inserted into a canvas")
226 if attrs:
227 sc = _canvas(attrs)
228 sc.insert(item)
229 self.items.append(sc)
230 else:
231 self.items.append(item)
233 return item
235 def set(self, attrs):
236 """sets styles args globally for the rest of the canvas
239 attr.checkattrs(attrs, [style.strokestyle, style.fillstyle])
240 for astyle in attrs:
241 self.insert(astyle)
243 def draw(self, path, attrs):
244 """draw path on canvas using the style given by args
246 The argument attrs consists of PathStyles, which modify
247 the appearance of the path, PathDecos, which add some new
248 visual elements to the path, or trafos, which are applied
249 before drawing the path.
253 attrs = attr.mergeattrs(attrs)
254 attr.checkattrs(attrs, [deco.deco, deformer.deformer, style.fillstyle, style.strokestyle])
256 for adeformer in attr.getattrs(attrs, [deformer.deformer]):
257 path = adeformer.deform(path)
259 styles = attr.getattrs(attrs, [style.fillstyle, style.strokestyle])
260 dp = deco.decoratedpath(path, styles=styles)
262 # add path decorations and modify path accordingly
263 for adeco in attr.getattrs(attrs, [deco.deco]):
264 dp = adeco.decorate(dp)
266 self.insert(dp)
268 def stroke(self, path, attrs=[]):
269 """stroke path on canvas using the style given by args
271 The argument attrs consists of PathStyles, which modify
272 the appearance of the path, PathDecos, which add some new
273 visual elements to the path, or trafos, which are applied
274 before drawing the path.
278 self.draw(path, [deco.stroked]+list(attrs))
280 def fill(self, path, attrs=[]):
281 """fill path on canvas using the style given by args
283 The argument attrs consists of PathStyles, which modify
284 the appearance of the path, PathDecos, which add some new
285 visual elements to the path, or trafos, which are applied
286 before drawing the path.
290 self.draw(path, [deco.filled]+list(attrs))
292 def settexrunner(self, texrunner):
293 """sets the texrunner to be used to within the text and text_pt methods"""
295 self.texrunner = texrunner
297 def text(self, x, y, atext, *args, **kwargs):
298 """insert a text into the canvas
300 inserts a textbox created by self.texrunner.text into the canvas
302 returns the inserted textbox"""
304 return self.insert(self.texrunner.text(x, y, atext, *args, **kwargs))
307 def text_pt(self, x, y, atext, *args):
308 """insert a text into the canvas
310 inserts a textbox created by self.texrunner.text_pt into the canvas
312 returns the inserted textbox"""
314 return self.insert(self.texrunner.text_pt(x, y, atext, *args))
317 # user canvas class which adds a few convenience methods for single page output
320 def _wrappedindocument(method):
321 def wrappedindocument(self, filename, *args, **kwargs):
322 d = document.document([document.page(self, *args, **kwargs)])
323 return method(d, filename)
324 return wrappedindocument
326 class canvas(_canvas):
328 """a canvas holds a collection of canvasitems"""
330 writeEPSfile = _wrappedindocument(document.document.writeEPSfile)
331 writePSfile = _wrappedindocument(document.document.writePSfile)
332 writePDFfile = _wrappedindocument(document.document.writePDFfile)
333 writetofile = _wrappedindocument(document.document.writetofile)