make bitmap functional after pdf object reorganization
[PyX/mjg.git] / pyx / canvas.py
blob787d6f4671ffb81e7212144fe5b8e88ac8d452c5
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2006 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-2006 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"""
39 pass
41 def processPS(self, file, writer, context, registry, bbox):
42 """process canvasitem by writing the corresponding PS code to file and
43 by updating context, registry as well as bbox
45 - the PS code corresponding to the canvasitem has to be written in the
46 stream file, which provides a write(string) method
47 - writer is the PSwriter used for the output
48 - context is an instance of pswriter.context which is used for keeping
49 track of the graphics state (current linewidth, colorspace and font))
50 - registry is used for tracking resources needed by the canvasitem
51 - bbox has to be updated to include the bounding box of the canvasitem
52 """
53 raise NotImplementedError()
55 def processPDF(self, file, writer, context, registry, bbox):
56 """process canvasitem by writing the corresponding PDF code to file and
57 by updating context, registry as well as bbox
59 - the PDF code corresponding to the canvasitem has to be written in the
60 stream file, which provides a write(string) method
61 - writer is the PDFwriter used for the output, which contains properties
62 like whether streamcompression is used
63 - context is an instance of pdfwriter.context which is used for keeping
64 track of the graphics state, in particular for the emulation of PS
65 behaviour regarding fill and stroke styles, for keeping track of the
66 currently selected font as well as of text regions.
67 - registry is used for tracking resources needed by the canvasitem
68 - bbox has to be updated to include the bounding box of the canvasitem
69 """
70 raise NotImplementedError()
73 import attr, deco, deformer, document, style, trafo, type1font
74 import bbox as bboxmodule
78 # clipping class
81 class clip(canvasitem):
83 """class for use in canvas constructor which clips to a path"""
85 def __init__(self, path):
86 """construct a clip instance for a given path"""
87 self.path = path
89 def bbox(self):
90 # as a canvasitem a clipping path has NO influence on the bbox...
91 return bboxmodule.empty()
93 def clipbbox(self):
94 # ... but for clipping, we nevertheless need the bbox
95 return self.path.bbox()
97 def processPS(self, file, writer, context, registry, bbox):
98 file.write("newpath\n")
99 self.path.outputPS(file, writer)
100 file.write("clip\n")
102 def processPDF(self, file, writer, context, registry, bbox):
103 self.path.outputPDF(file, writer)
104 file.write("W n\n")
108 # general canvas class
111 class _canvas(canvasitem):
113 """a canvas holds a collection of canvasitems"""
115 def __init__(self, attrs=[], texrunner=None):
117 """construct a canvas
119 The canvas can be modfied by supplying args, which have
120 to be instances of one of the following classes:
121 - trafo.trafo (leading to a global transformation of the canvas)
122 - canvas.clip (clips the canvas)
123 - style.strokestyle, style.fillstyle (sets some global attributes of the canvas)
125 Note that, while the first two properties are fixed for the
126 whole canvas, the last one can be changed via canvas.set().
128 The texrunner instance used for the text method can be specified
129 using the texrunner argument. It defaults to text.defaulttexrunner
133 self.items = []
134 self.trafo = trafo.trafo()
135 self.clipbbox = None
136 if texrunner is not None:
137 self.texrunner = texrunner
138 else:
139 # prevent cyclic imports
140 import text
141 self.texrunner = text.defaulttexrunner
143 for attr in attrs:
144 if isinstance(attr, trafo.trafo_pt):
145 self.trafo = self.trafo*attr
146 self.items.append(attr)
147 elif isinstance(attr, clip):
148 if self.clipbbox is None:
149 self.clipbbox = attr.clipbbox().transformed(self.trafo)
150 else:
151 self.clippbox *= attr.clipbbox().transformed(self.trafo)
152 self.items.append(attr)
153 else:
154 self.set([attr])
156 def __len__(self):
157 return len(self.items)
159 def __getitem__(self, i):
160 return self.items[i]
162 def bbox(self):
163 """returns bounding box of canvas
165 Note that this bounding box doesn't take into account the linewidths, so
166 is less accurate than the one used when writing the output to a file.
168 obbox = bboxmodule.empty()
169 for cmd in self.items:
170 obbox += cmd.bbox()
172 # transform according to our global transformation and
173 # intersect with clipping bounding box (which has already been
174 # transformed in canvas.__init__())
175 obbox.transform(self.trafo)
176 if self.clipbbox is not None:
177 obbox *= self.clipbbox
178 return obbox
180 def processPS(self, file, writer, context, registry, bbox):
181 context = context()
182 if self.items:
183 file.write("gsave\n")
184 nbbox = bboxmodule.empty()
185 for item in self.items:
186 item.processPS(file, writer, context, registry, nbbox)
187 # update bounding bbox
188 nbbox.transform(self.trafo)
189 if self.clipbbox is not None:
190 nbbox *= self.clipbbox
191 bbox += nbbox
192 file.write("grestore\n")
194 def processPDF(self, file, writer, context, registry, bbox):
195 context = context()
196 if self.items:
197 file.write("q\n") # gsave
198 nbbox = bboxmodule.empty()
199 for item in self.items:
200 if isinstance(item, type1font.text_pt):
201 if not context.textregion:
202 file.write("BT\n")
203 context.textregion = 1
204 else:
205 if context.textregion:
206 file.write("ET\n")
207 context.textregion = 0
208 context.font = None
209 item.processPDF(file, writer, context, registry, nbbox)
210 if context.textregion:
211 file.write("ET\n")
212 context.textregion = 0
213 context.font = None
214 # update bounding bbox
215 nbbox.transform(self.trafo)
216 if self.clipbbox is not None:
217 nbbox *= self.clipbbox
218 bbox += nbbox
219 file.write("Q\n") # grestore
221 def insert(self, item, attrs=None):
222 """insert item in the canvas.
224 If attrs are passed, a canvas containing the item is inserted applying attrs.
226 returns the item
230 if not isinstance(item, canvasitem):
231 raise RuntimeError("only instances of base.canvasitem can be inserted into a canvas")
233 if attrs:
234 sc = _canvas(attrs)
235 sc.insert(item)
236 self.items.append(sc)
237 else:
238 self.items.append(item)
240 return item
242 def set(self, attrs):
243 """sets styles args globally for the rest of the canvas
246 attr.checkattrs(attrs, [style.strokestyle, style.fillstyle])
247 for astyle in attrs:
248 self.insert(astyle)
250 def draw(self, path, attrs):
251 """draw path on canvas using the style given by args
253 The argument attrs consists of PathStyles, which modify
254 the appearance of the path, PathDecos, which add some new
255 visual elements to the path, or trafos, which are applied
256 before drawing the path.
260 attrs = attr.mergeattrs(attrs)
261 attr.checkattrs(attrs, [deco.deco, deformer.deformer, style.fillstyle, style.strokestyle])
263 for adeformer in attr.getattrs(attrs, [deformer.deformer]):
264 path = adeformer.deform(path)
266 styles = attr.getattrs(attrs, [style.fillstyle, style.strokestyle])
267 dp = deco.decoratedpath(path, styles=styles)
269 # add path decorations and modify path accordingly
270 for adeco in attr.getattrs(attrs, [deco.deco]):
271 adeco.decorate(dp, self.texrunner)
273 self.insert(dp)
275 def stroke(self, path, attrs=[]):
276 """stroke path on canvas using the style given by args
278 The argument attrs consists of PathStyles, which modify
279 the appearance of the path, PathDecos, which add some new
280 visual elements to the path, or trafos, which are applied
281 before drawing the path.
285 self.draw(path, [deco.stroked]+list(attrs))
287 def fill(self, path, attrs=[]):
288 """fill path on canvas using the style given by args
290 The argument attrs consists of PathStyles, which modify
291 the appearance of the path, PathDecos, which add some new
292 visual elements to the path, or trafos, which are applied
293 before drawing the path.
297 self.draw(path, [deco.filled]+list(attrs))
299 def settexrunner(self, texrunner):
300 """sets the texrunner to be used to within the text and text_pt methods"""
302 self.texrunner = texrunner
304 def text(self, x, y, atext, *args, **kwargs):
305 """insert a text into the canvas
307 inserts a textbox created by self.texrunner.text into the canvas
309 returns the inserted textbox"""
311 return self.insert(self.texrunner.text(x, y, atext, *args, **kwargs))
314 def text_pt(self, x, y, atext, *args):
315 """insert a text into the canvas
317 inserts a textbox created by self.texrunner.text_pt into the canvas
319 returns the inserted textbox"""
321 return self.insert(self.texrunner.text_pt(x, y, atext, *args))
324 # user canvas class which adds a few convenience methods for single page output
327 def _wrappedindocument(method):
328 def wrappedindocument(self, filename, *args, **kwargs):
329 d = document.document([document.page(self, *args, **kwargs)])
330 self.__name__ = method.__name__
331 self.__doc__ = method.__doc__
332 return method(d, filename)
333 return wrappedindocument
335 class canvas(_canvas):
337 """a canvas holds a collection of canvasitems"""
339 writeEPSfile = _wrappedindocument(document.document.writeEPSfile)
340 writePSfile = _wrappedindocument(document.document.writePSfile)
341 writePDFfile = _wrappedindocument(document.document.writePDFfile)
342 writetofile = _wrappedindocument(document.document.writetofile)