transparenty support (PDF only); manage pdf page resources in registry
[PyX/mjg.git] / pyx / pattern.py
blobe95a860ac57836a52d9909944db4d7384b9445c4
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 from __future__ import nested_scopes
26 import cStringIO, math, warnings
27 import attr, canvas, helper, path, pdfwriter, pswriter, style, unit, trafo
29 # TODO: pattern should not derive from canvas but wrap a canvas
31 class pattern(canvas._canvas, attr.exclusiveattr, style.fillstyle):
33 def __init__(self, painttype=1, tilingtype=1, xstep=None, ystep=None, bbox=None, trafo=None, **kwargs):
34 canvas._canvas.__init__(self, **kwargs)
35 attr.exclusiveattr.__init__(self, pattern)
36 self.id = "pattern%d" % id(self)
37 self.patterntype = 1
38 if painttype not in (1,2):
39 raise ValueError("painttype must be 1 or 2")
40 self.painttype = painttype
41 if tilingtype not in (1,2,3):
42 raise ValueError("tilingtype must be 1, 2 or 3")
43 self.tilingtype = tilingtype
44 self.xstep = xstep
45 self.ystep = ystep
46 self.patternbbox = bbox
47 self.patterntrafo = trafo
49 def __call__(self, painttype=helper.nodefault, tilingtype=helper.nodefault, xstep=helper.nodefault, ystep=helper.nodefault,
50 bbox=helper.nodefault, trafo=helper.nodefault):
51 if painttype is helper.nodefault:
52 painttype = self.painttype
53 if tilingtype is helper.nodefault:
54 tilingtype = self.tilingtype
55 if xstep is helper.nodefault:
56 xstep = self.xstep
57 if ystep is helper.nodefault:
58 ystep = self.ystep
59 if bbox is helper.nodefault:
60 bbox = self.bbox
61 if trafo is helper.nodefault:
62 trafo = self.trafo
63 return pattern(painttype, tilingtype, xstep, ystep, bbox, trafo)
65 def bbox(self):
66 return None
68 def outputPS(self, file, writer, context):
69 file.write("%s setpattern\n" % self.id)
71 def outputPDF(self, file, writer, context):
72 if context.colorspace != "Pattern":
73 # we only set the fill color space (see next comment)
74 file.write("/Pattern cs\n")
75 context.colorspace = "Pattern"
76 if context.strokeattr:
77 # using patterns as stroke colors doesn't seem to work, so
78 # we just don't do this...
79 warnings.warn("ignoring stroke color for patterns in PDF")
80 if context.fillattr:
81 file.write("/%s scn\n"% self.id)
83 def registerPS(self, registry):
84 canvas._canvas.registerPS(self, registry)
85 realpatternbbox = canvas._canvas.bbox(self)
86 if self.xstep is None:
87 xstep = unit.topt(realpatternbbox.width())
88 else:
89 xstep = unit.topt(self.xstep)
90 if self.ystep is None:
91 ystep = unit.topt(realpatternbbox.height())
92 else:
93 ystep = unit.topt(self.ystep)
94 if not xstep:
95 raise ValueError("xstep in pattern cannot be zero")
96 if not ystep:
97 raise ValueError("ystep in pattern cannot be zero")
98 patternbbox = self.patternbbox or realpatternbbox.enlarged(5*unit.pt)
100 patternprefix = "\n".join(("<<",
101 "/PatternType %d" % self.patterntype,
102 "/PaintType %d" % self.painttype,
103 "/TilingType %d" % self.tilingtype,
104 "/BBox[%d %d %d %d]" % patternbbox.lowrestuple_pt(),
105 "/XStep %g" % xstep,
106 "/YStep %g" % ystep,
107 "/PaintProc {\nbegin\n"))
108 stringfile = cStringIO.StringIO()
109 # XXX here, we have a problem since the writer is not definined at that point
110 # for the moment, we just path None since we do not use it anyway
111 canvas._canvas.outputPS(self, stringfile, None, pswriter.context())
112 patternproc = stringfile.getvalue()
113 stringfile.close()
114 patterntrafostring = self.patterntrafo is None and "matrix" or str(self.patterntrafo)
115 patternsuffix = "end\n} bind\n>>\n%s\nmakepattern" % patterntrafostring
117 registry.add(pswriter.PSdefinition(self.id, "".join((patternprefix, patternproc, patternsuffix))))
119 def registerPDF(self, registry):
120 realpatternbbox = canvas._canvas.bbox(self)
121 if self.xstep is None:
122 xstep = unit.topt(realpatternbbox.width())
123 else:
124 xstep = unit.topt(self.xstep)
125 if self.ystep is None:
126 ystep = unit.topt(realpatternbbox.height())
127 else:
128 ystep = unit.topt(self.ystep)
129 if not xstep:
130 raise ValueError("xstep in pattern cannot be zero")
131 if not ystep:
132 raise ValueError("ystep in pattern cannot be zero")
133 patternbbox = self.patternbbox or realpatternbbox.enlarged(5*unit.pt)
134 patterntrafo = self.patterntrafo or trafo.trafo()
136 registry.add(PDFpattern(self.id, self.patterntype, self.painttype, self.tilingtype,
137 patternbbox, xstep, ystep, patterntrafo,
138 lambda file, writer, context: canvas._canvas.outputPDF(self, file, writer, context()),
139 lambda registry: canvas._canvas.registerPDF(self, registry),
140 registry))
142 pattern.clear = attr.clearclass(pattern)
145 _base = 0.1 * unit.v_cm
147 class hatched(pattern):
148 def __init__(self, dist, angle, strokestyles=[]):
149 pattern.__init__(self, painttype=1, tilingtype=1, xstep=dist, ystep=1000*unit.t_pt, bbox=None, trafo=trafo.rotate(angle))
150 self.strokestyles = attr.mergeattrs([style.linewidth.THIN] + strokestyles)
151 attr.checkattrs(self.strokestyles, [style.strokestyle])
152 self.dist = dist
153 self.angle = angle
154 self.stroke(path.line_pt(0, -500, 0, 500), self.strokestyles)
156 def __call__(self, dist=None, angle=None, strokestyles=None):
157 if dist is None:
158 dist = self.dist
159 if angle is None:
160 angle = self.angle
161 if strokestyles is None:
162 strokestyles = self.strokestyles
163 return hatched(dist, angle, strokestyles)
165 hatched0 = hatched(_base, 0)
166 hatched0.SMALL = hatched0(_base/math.sqrt(64))
167 hatched0.SMALL = hatched0(_base/math.sqrt(64))
168 hatched0.SMALl = hatched0(_base/math.sqrt(32))
169 hatched0.SMAll = hatched0(_base/math.sqrt(16))
170 hatched0.SMall = hatched0(_base/math.sqrt(8))
171 hatched0.Small = hatched0(_base/math.sqrt(4))
172 hatched0.small = hatched0(_base/math.sqrt(2))
173 hatched0.normal = hatched0(_base)
174 hatched0.large = hatched0(_base*math.sqrt(2))
175 hatched0.Large = hatched0(_base*math.sqrt(4))
176 hatched0.LArge = hatched0(_base*math.sqrt(8))
177 hatched0.LARge = hatched0(_base*math.sqrt(16))
178 hatched0.LARGe = hatched0(_base*math.sqrt(32))
179 hatched0.LARGE = hatched0(_base*math.sqrt(64))
181 hatched45 = hatched(_base, 45)
182 hatched45.SMALL = hatched45(_base/math.sqrt(64))
183 hatched45.SMALl = hatched45(_base/math.sqrt(32))
184 hatched45.SMAll = hatched45(_base/math.sqrt(16))
185 hatched45.SMall = hatched45(_base/math.sqrt(8))
186 hatched45.Small = hatched45(_base/math.sqrt(4))
187 hatched45.small = hatched45(_base/math.sqrt(2))
188 hatched45.normal = hatched45(_base)
189 hatched45.large = hatched45(_base*math.sqrt(2))
190 hatched45.Large = hatched45(_base*math.sqrt(4))
191 hatched45.LArge = hatched45(_base*math.sqrt(8))
192 hatched45.LARge = hatched45(_base*math.sqrt(16))
193 hatched45.LARGe = hatched45(_base*math.sqrt(32))
194 hatched45.LARGE = hatched45(_base*math.sqrt(64))
196 hatched90 = hatched(_base, 90)
197 hatched90.SMALL = hatched90(_base/math.sqrt(64))
198 hatched90.SMALl = hatched90(_base/math.sqrt(32))
199 hatched90.SMAll = hatched90(_base/math.sqrt(16))
200 hatched90.SMall = hatched90(_base/math.sqrt(8))
201 hatched90.Small = hatched90(_base/math.sqrt(4))
202 hatched90.small = hatched90(_base/math.sqrt(2))
203 hatched90.normal = hatched90(_base)
204 hatched90.large = hatched90(_base*math.sqrt(2))
205 hatched90.Large = hatched90(_base*math.sqrt(4))
206 hatched90.LArge = hatched90(_base*math.sqrt(8))
207 hatched90.LARge = hatched90(_base*math.sqrt(16))
208 hatched90.LARGe = hatched90(_base*math.sqrt(32))
209 hatched90.LARGE = hatched90(_base*math.sqrt(64))
211 hatched135 = hatched(_base, 135)
212 hatched135.SMALL = hatched135(_base/math.sqrt(64))
213 hatched135.SMALl = hatched135(_base/math.sqrt(32))
214 hatched135.SMAll = hatched135(_base/math.sqrt(16))
215 hatched135.SMall = hatched135(_base/math.sqrt(8))
216 hatched135.Small = hatched135(_base/math.sqrt(4))
217 hatched135.small = hatched135(_base/math.sqrt(2))
218 hatched135.normal = hatched135(_base)
219 hatched135.large = hatched135(_base*math.sqrt(2))
220 hatched135.Large = hatched135(_base*math.sqrt(4))
221 hatched135.LArge = hatched135(_base*math.sqrt(8))
222 hatched135.LARge = hatched135(_base*math.sqrt(16))
223 hatched135.LARGe = hatched135(_base*math.sqrt(32))
224 hatched135.LARGE = hatched135(_base*math.sqrt(64))
227 class crosshatched(pattern):
228 def __init__(self, dist, angle, strokestyles=[]):
229 pattern.__init__(self, painttype=1, tilingtype=1, xstep=dist, ystep=dist, bbox=None, trafo=trafo.rotate(angle))
230 self.strokestyles = attr.mergeattrs([style.linewidth.THIN] + strokestyles)
231 attr.checkattrs(self.strokestyles, [style.strokestyle])
232 self.dist = dist
233 self.angle = angle
234 self.stroke(path.line_pt(0, 0, 0, unit.topt(dist)), self.strokestyles)
235 self.stroke(path.line_pt(0, 0, unit.topt(dist), 0), self.strokestyles)
237 def __call__(self, dist=None, angle=None, strokestyles=None):
238 if dist is None:
239 dist = self.dist
240 if angle is None:
241 angle = self.angle
242 if strokestyles is None:
243 strokestyles = self.strokestyles
244 return crosshatched(dist, angle, strokestyles)
246 crosshatched0 = crosshatched(_base, 0)
247 crosshatched0.SMALL = crosshatched0(_base/math.sqrt(64))
248 crosshatched0.SMALl = crosshatched0(_base/math.sqrt(32))
249 crosshatched0.SMAll = crosshatched0(_base/math.sqrt(16))
250 crosshatched0.SMall = crosshatched0(_base/math.sqrt(8))
251 crosshatched0.Small = crosshatched0(_base/math.sqrt(4))
252 crosshatched0.small = crosshatched0(_base/math.sqrt(2))
253 crosshatched0.normal = crosshatched0
254 crosshatched0.large = crosshatched0(_base*math.sqrt(2))
255 crosshatched0.Large = crosshatched0(_base*math.sqrt(4))
256 crosshatched0.LArge = crosshatched0(_base*math.sqrt(8))
257 crosshatched0.LARge = crosshatched0(_base*math.sqrt(16))
258 crosshatched0.LARGe = crosshatched0(_base*math.sqrt(32))
259 crosshatched0.LARGE = crosshatched0(_base*math.sqrt(64))
261 crosshatched45 = crosshatched(_base, 45)
262 crosshatched45.SMALL = crosshatched45(_base/math.sqrt(64))
263 crosshatched45.SMALl = crosshatched45(_base/math.sqrt(32))
264 crosshatched45.SMAll = crosshatched45(_base/math.sqrt(16))
265 crosshatched45.SMall = crosshatched45(_base/math.sqrt(8))
266 crosshatched45.Small = crosshatched45(_base/math.sqrt(4))
267 crosshatched45.small = crosshatched45(_base/math.sqrt(2))
268 crosshatched45.normal = crosshatched45
269 crosshatched45.large = crosshatched45(_base*math.sqrt(2))
270 crosshatched45.Large = crosshatched45(_base*math.sqrt(4))
271 crosshatched45.LArge = crosshatched45(_base*math.sqrt(8))
272 crosshatched45.LARge = crosshatched45(_base*math.sqrt(16))
273 crosshatched45.LARGe = crosshatched45(_base*math.sqrt(32))
274 crosshatched45.LARGE = crosshatched45(_base*math.sqrt(64))
277 class PDFpattern(pdfwriter.PDFobject):
279 def __init__(self, name, patterntype, painttype, tilingtype, bbox, xstep, ystep, trafo,
280 canvasoutputPDF, canvasregisterPDF, registry):
281 pdfwriter.PDFobject.__init__(self, "pattern", name, "Pattern")
282 self.name = name
283 self.patterntype = patterntype
284 self.painttype = painttype
285 self.tilingtype = tilingtype
286 self.bbox = bbox
287 self.xstep = xstep
288 self.ystep = ystep
289 self.trafo = trafo
290 self.canvasoutputPDF = canvasoutputPDF
292 self.contentlength = pdfwriter.PDFcontentlength((self.type, self.id))
293 registry.add(self.contentlength)
295 # we need to keep track of the resources used by the pattern, hence
296 # we create our own registry, which we merge immediately in the main registry
297 self.patternregistry = pdfwriter.PDFregistry()
298 # XXX passing canvasregisterPDF is a Q&D way to get access to the registerPDF method
299 # of the _canvas superclass of the pattern
300 canvasregisterPDF(self.patternregistry)
301 registry.mergeregistry(self.patternregistry)
303 def outputPDF(self, file, writer, registry):
304 file.write("<<\n"
305 "/Type /Pattern\n"
306 "/PatternType %d\n" % self.patterntype)
307 file.write("/PaintType %d\n" % self.painttype)
308 file.write("/TilingType %d\n" % self.tilingtype)
309 file.write("/BBox [%d %d %d %d]\n" % self.bbox.lowrestuple_pt())
310 file.write("/XStep %f\n" % self.xstep)
311 file.write("/YStep %f\n" % self.ystep)
312 file.write("/Matrix %s\n" % str(self.trafo))
313 file.write("/Resources <<\n")
314 if self.patternregistry.types.has_key("font"):
315 file.write("/Font << %s >>\n" % " ".join(["/%s %i 0 R" % (font.name, registry.getrefno(font))
316 for font in self.patternregistry.types["font"].values()]))
317 if self.patternregistry.types.has_key("pattern"):
318 file.write("/Pattern << %s >>\n" % " ".join(["/%s %i 0 R" % (pattern.name, registry.getrefno(pattern))
319 for pattern in self.patternregistry.types["pattern"].values()]))
320 file.write(">>\n")
321 file.write("/Length %i 0 R\n" % registry.getrefno(self.contentlength))
322 if writer.compress:
323 file.write("/Filter /FlateDecode\n")
324 file.write(">>\n")
326 file.write("stream\n")
327 beginstreampos = file.tell()
329 if writer.compress:
330 stream = pdfwriter.compressedstream(file, writer.compresslevel)
331 else:
332 stream = file
334 acontext = pdfwriter.context()
335 self.canvasoutputPDF(stream, writer, acontext)
336 if writer.compress:
337 stream.flush()
339 self.contentlength.contentlength = file.tell() - beginstreampos
340 file.write("\n"
341 "endstream\n")