add UnicodeEngine (MultiEngineText and axis texters returning MultiEngineText), texte...
[PyX.git] / pyx / pattern.py
blobe41680864feabfa925f8a2a49bb5614af788123a
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2002-2011 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002-2011 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 import io, logging, math
24 from . import attr, canvas, path, pdfwriter, pswriter, svgwriter, style, unit, trafo
25 from . import writer as writermodule
26 from . import bbox as bboxmodule
28 logger = logging.getLogger("pyx")
30 class _marker: pass
32 # TODO: pattern should not derive from canvas but wrap a canvas
34 class pattern(canvas.canvas, attr.exclusiveattr, style.fillstyle):
36 def __init__(self, painttype=1, tilingtype=1, xstep=None, ystep=None,
37 bbox=None, trafo=None, bboxenlarge=None, **kwargs):
38 canvas.canvas.__init__(self, **kwargs)
39 attr.exclusiveattr.__init__(self, pattern)
40 self.id = "pattern%d" % id(self)
41 self.patterntype = 1
42 if painttype not in (1, 2):
43 raise ValueError("painttype must be 1 or 2")
44 self.painttype = painttype
45 if tilingtype not in (1, 2, 3):
46 raise ValueError("tilingtype must be 1, 2, or 3")
47 self.tilingtype = tilingtype
48 self.xstep = xstep
49 self.ystep = ystep
50 self.patternbbox = bbox
51 self.patterntrafo = trafo
52 self.bboxenlarge = bboxenlarge
54 def __call__(self, painttype=_marker, tilingtype=_marker, xstep=_marker, ystep=_marker,
55 bbox=_marker, trafo=_marker, bboxenlarge=_marker):
56 if painttype is not _marker:
57 painttype = self.painttype
58 if tilingtype is not _marker:
59 tilingtype = self.tilingtype
60 if xstep is not _marker:
61 xstep = self.xstep
62 if ystep is not _marker:
63 ystep = self.ystep
64 if bbox is not _marker:
65 bbox = self.bbox
66 if trafo is not _marker:
67 trafo = self.trafo
68 if bboxenlarge is not _marker:
69 bboxenlarge = self.bboxenlarge
70 return pattern(painttype, tilingtype, xstep, ystep, bbox, trafo, bboxenlarge)
72 def processPS(self, file, writer, context, registry):
73 # process pattern, letting it register its resources and calculate the bbox of the pattern
74 patternfile = writermodule.writer(io.BytesIO())
75 realpatternbbox = bboxmodule.empty()
76 canvas.canvas.processPS(self, patternfile, writer, pswriter.context(), registry, realpatternbbox)
77 patternproc = patternfile.file.getvalue()
79 if self.xstep is None:
80 xstep = unit.topt(realpatternbbox.width())
81 else:
82 xstep = unit.topt(self.xstep)
83 if self.ystep is None:
84 ystep = unit.topt(realpatternbbox.height())
85 else:
86 ystep = unit.topt(self.ystep)
87 if not xstep:
88 raise ValueError("xstep in pattern cannot be zero")
89 if not ystep:
90 raise ValueError("ystep in pattern cannot be zero")
91 if self.patternbbox:
92 patternbbox = self.patternbbox
93 else:
94 patternbbox = realpatternbbox
95 if self.bboxenlarge:
96 patternbbox.enlarge(self.bboxenlarge)
98 patternprefix = "\n".join(("<<",
99 "/PatternType %d" % self.patterntype,
100 "/PaintType %d" % self.painttype,
101 "/TilingType %d" % self.tilingtype,
102 "/BBox [%g %g %g %g]" % patternbbox.highrestuple_pt(),
103 "/XStep %g" % xstep,
104 "/YStep %g" % ystep,
105 "/PaintProc {\nbegin\n"))
106 patterntrafostring = self.patterntrafo is None and "matrix" or str(self.patterntrafo)
107 patternsuffix = "end\n} bind\n>>\n%s\nmakepattern" % patterntrafostring
109 registry.add(pswriter.PSdefinition(self.id, patternprefix.encode("ascii") + patternproc + patternsuffix.encode("ascii")))
111 # activate pattern
112 file.write("%s setpattern\n" % self.id)
114 def processPDF(self, file, writer, context, registry):
115 # we need to keep track of the resources used by the pattern, hence
116 # we create our own registry, which we merge immediately in the main registry
117 patternregistry = pdfwriter.PDFregistry()
119 patternfile = writermodule.writer(io.BytesIO())
120 realpatternbbox = bboxmodule.empty()
121 canvas.canvas.processPDF(self, patternfile, writer, pdfwriter.context(), patternregistry, realpatternbbox)
122 patternproc = patternfile.file.getvalue()
124 registry.mergeregistry(patternregistry)
126 if self.xstep is None:
127 xstep = unit.topt(realpatternbbox.width())
128 else:
129 xstep = unit.topt(self.xstep)
130 if self.ystep is None:
131 ystep = unit.topt(realpatternbbox.height())
132 else:
133 ystep = unit.topt(self.ystep)
134 if not xstep:
135 raise ValueError("xstep in pattern cannot be zero")
136 if not ystep:
137 raise ValueError("ystep in pattern cannot be zero")
138 if self.patternbbox:
139 patternbbox = self.patternbbox
140 else:
141 patternbbox = realpatternbbox
142 if self.bboxenlarge:
143 patternbbox.enlarge(self.bboxenlarge)
144 patterntrafo = self.patterntrafo or trafo.trafo()
146 registry.add(PDFpattern(self.id, self.patterntype, self.painttype, self.tilingtype,
147 patternbbox, xstep, ystep, patterntrafo, patternproc, writer, registry, patternregistry))
149 # activate pattern
150 if context.colorspace != "Pattern":
151 # we only set the fill color space (see next comment)
152 file.write("/Pattern cs\n")
153 context.colorspace = "Pattern"
154 if context.strokeattr:
155 # using patterns as stroke colors doesn't seem to work, so
156 # we just don't do this...
157 logger.warning("ignoring stroke color for patterns in PDF")
158 if context.fillattr:
159 file.write("/%s scn\n"% self.id)
161 def processSVGattrs(self, attrs, writer, context, registry):
162 assert self.patterntype == 1
163 if self.painttype != 1:
164 raise ValueError("grayscale patterns not supported")
165 # tilingtype is an implementation detail in PS and PDF and is ignored
166 if self.xstep is not None or self.ystep is not None or self.bboxenlarge is not None:
167 raise ValueError("step and bbox modifications not supported")
169 svgpattern = SVGpattern(self)
170 registry.add(svgpattern)
171 if context.strokeattr:
172 context.strokecolor = "url(#%s)" % svgpattern.svgid
173 if context.fillattr:
174 context.fillcolor = "url(#%s)" % svgpattern.svgid
177 pattern.clear = attr.clearclass(pattern)
180 _base = 0.1 * unit.v_cm
182 class hatched(pattern):
183 def __init__(self, dist, angle, strokestyles=[]):
184 pattern.__init__(self, painttype=1, tilingtype=1, xstep=dist, ystep=100*unit.t_pt, bbox=None, trafo=trafo.rotate(angle))
185 self.strokestyles = attr.mergeattrs([style.linewidth.THIN] + strokestyles)
186 attr.checkattrs(self.strokestyles, [style.strokestyle])
187 self.dist = dist
188 self.angle = angle
189 self.stroke(path.line_pt(0, -50, 0, 50), self.strokestyles)
191 def __call__(self, dist=None, angle=None, strokestyles=None):
192 if dist is None:
193 dist = self.dist
194 if angle is None:
195 angle = self.angle
196 if strokestyles is None:
197 strokestyles = self.strokestyles
198 return hatched(dist, angle, strokestyles)
200 hatched0 = hatched(_base, 0)
201 hatched0.SMALL = hatched0(_base/math.sqrt(64))
202 hatched0.SMALL = hatched0(_base/math.sqrt(64))
203 hatched0.SMALl = hatched0(_base/math.sqrt(32))
204 hatched0.SMAll = hatched0(_base/math.sqrt(16))
205 hatched0.SMall = hatched0(_base/math.sqrt(8))
206 hatched0.Small = hatched0(_base/math.sqrt(4))
207 hatched0.small = hatched0(_base/math.sqrt(2))
208 hatched0.normal = hatched0(_base)
209 hatched0.large = hatched0(_base*math.sqrt(2))
210 hatched0.Large = hatched0(_base*math.sqrt(4))
211 hatched0.LArge = hatched0(_base*math.sqrt(8))
212 hatched0.LARge = hatched0(_base*math.sqrt(16))
213 hatched0.LARGe = hatched0(_base*math.sqrt(32))
214 hatched0.LARGE = hatched0(_base*math.sqrt(64))
216 hatched45 = hatched(_base, 45)
217 hatched45.SMALL = hatched45(_base/math.sqrt(64))
218 hatched45.SMALl = hatched45(_base/math.sqrt(32))
219 hatched45.SMAll = hatched45(_base/math.sqrt(16))
220 hatched45.SMall = hatched45(_base/math.sqrt(8))
221 hatched45.Small = hatched45(_base/math.sqrt(4))
222 hatched45.small = hatched45(_base/math.sqrt(2))
223 hatched45.normal = hatched45(_base)
224 hatched45.large = hatched45(_base*math.sqrt(2))
225 hatched45.Large = hatched45(_base*math.sqrt(4))
226 hatched45.LArge = hatched45(_base*math.sqrt(8))
227 hatched45.LARge = hatched45(_base*math.sqrt(16))
228 hatched45.LARGe = hatched45(_base*math.sqrt(32))
229 hatched45.LARGE = hatched45(_base*math.sqrt(64))
231 hatched90 = hatched(_base, 90)
232 hatched90.SMALL = hatched90(_base/math.sqrt(64))
233 hatched90.SMALl = hatched90(_base/math.sqrt(32))
234 hatched90.SMAll = hatched90(_base/math.sqrt(16))
235 hatched90.SMall = hatched90(_base/math.sqrt(8))
236 hatched90.Small = hatched90(_base/math.sqrt(4))
237 hatched90.small = hatched90(_base/math.sqrt(2))
238 hatched90.normal = hatched90(_base)
239 hatched90.large = hatched90(_base*math.sqrt(2))
240 hatched90.Large = hatched90(_base*math.sqrt(4))
241 hatched90.LArge = hatched90(_base*math.sqrt(8))
242 hatched90.LARge = hatched90(_base*math.sqrt(16))
243 hatched90.LARGe = hatched90(_base*math.sqrt(32))
244 hatched90.LARGE = hatched90(_base*math.sqrt(64))
246 hatched135 = hatched(_base, 135)
247 hatched135.SMALL = hatched135(_base/math.sqrt(64))
248 hatched135.SMALl = hatched135(_base/math.sqrt(32))
249 hatched135.SMAll = hatched135(_base/math.sqrt(16))
250 hatched135.SMall = hatched135(_base/math.sqrt(8))
251 hatched135.Small = hatched135(_base/math.sqrt(4))
252 hatched135.small = hatched135(_base/math.sqrt(2))
253 hatched135.normal = hatched135(_base)
254 hatched135.large = hatched135(_base*math.sqrt(2))
255 hatched135.Large = hatched135(_base*math.sqrt(4))
256 hatched135.LArge = hatched135(_base*math.sqrt(8))
257 hatched135.LARge = hatched135(_base*math.sqrt(16))
258 hatched135.LARGe = hatched135(_base*math.sqrt(32))
259 hatched135.LARGE = hatched135(_base*math.sqrt(64))
262 class crosshatched(pattern):
263 def __init__(self, dist, angle, strokestyles=[]):
264 pattern.__init__(self, painttype=1, tilingtype=1, xstep=dist, ystep=dist, bbox=None, trafo=trafo.rotate(angle))
265 self.strokestyles = attr.mergeattrs([style.linewidth.THIN] + strokestyles)
266 attr.checkattrs(self.strokestyles, [style.strokestyle])
267 self.dist = dist
268 self.angle = angle
269 self.stroke(path.line_pt(0, 0, 0, unit.topt(dist)), self.strokestyles)
270 self.stroke(path.line_pt(0, 0, unit.topt(dist), 0), self.strokestyles)
272 def __call__(self, dist=None, angle=None, strokestyles=None):
273 if dist is None:
274 dist = self.dist
275 if angle is None:
276 angle = self.angle
277 if strokestyles is None:
278 strokestyles = self.strokestyles
279 return crosshatched(dist, angle, strokestyles)
281 crosshatched0 = crosshatched(_base, 0)
282 crosshatched0.SMALL = crosshatched0(_base/math.sqrt(64))
283 crosshatched0.SMALl = crosshatched0(_base/math.sqrt(32))
284 crosshatched0.SMAll = crosshatched0(_base/math.sqrt(16))
285 crosshatched0.SMall = crosshatched0(_base/math.sqrt(8))
286 crosshatched0.Small = crosshatched0(_base/math.sqrt(4))
287 crosshatched0.small = crosshatched0(_base/math.sqrt(2))
288 crosshatched0.normal = crosshatched0
289 crosshatched0.large = crosshatched0(_base*math.sqrt(2))
290 crosshatched0.Large = crosshatched0(_base*math.sqrt(4))
291 crosshatched0.LArge = crosshatched0(_base*math.sqrt(8))
292 crosshatched0.LARge = crosshatched0(_base*math.sqrt(16))
293 crosshatched0.LARGe = crosshatched0(_base*math.sqrt(32))
294 crosshatched0.LARGE = crosshatched0(_base*math.sqrt(64))
296 crosshatched45 = crosshatched(_base, 45)
297 crosshatched45.SMALL = crosshatched45(_base/math.sqrt(64))
298 crosshatched45.SMALl = crosshatched45(_base/math.sqrt(32))
299 crosshatched45.SMAll = crosshatched45(_base/math.sqrt(16))
300 crosshatched45.SMall = crosshatched45(_base/math.sqrt(8))
301 crosshatched45.Small = crosshatched45(_base/math.sqrt(4))
302 crosshatched45.small = crosshatched45(_base/math.sqrt(2))
303 crosshatched45.normal = crosshatched45
304 crosshatched45.large = crosshatched45(_base*math.sqrt(2))
305 crosshatched45.Large = crosshatched45(_base*math.sqrt(4))
306 crosshatched45.LArge = crosshatched45(_base*math.sqrt(8))
307 crosshatched45.LARge = crosshatched45(_base*math.sqrt(16))
308 crosshatched45.LARGe = crosshatched45(_base*math.sqrt(32))
309 crosshatched45.LARGE = crosshatched45(_base*math.sqrt(64))
312 class PDFpattern(pdfwriter.PDFobject):
314 def __init__(self, name, patterntype, painttype, tilingtype, bbox, xstep, ystep, trafo,
315 patternproc, writer, registry, patternregistry):
316 self.patternregistry = patternregistry
317 pdfwriter.PDFobject.__init__(self, "pattern", name)
318 registry.addresource("Pattern", name, self)
320 self.name = name
321 self.patterntype = patterntype
322 self.painttype = painttype
323 self.tilingtype = tilingtype
324 self.bbox = bbox
325 self.xstep = xstep
326 self.ystep = ystep
327 self.trafo = trafo
328 self.patternproc = patternproc
330 def write(self, file, writer, registry):
331 file.write("<<\n"
332 "/Type /Pattern\n"
333 "/PatternType %d\n" % self.patterntype)
334 file.write("/PaintType %d\n" % self.painttype)
335 file.write("/TilingType %d\n" % self.tilingtype)
336 file.write("/BBox [%d %d %d %d]\n" % self.bbox.lowrestuple_pt())
337 file.write("/XStep %f\n" % self.xstep)
338 file.write("/YStep %f\n" % self.ystep)
339 file.write("/Matrix %s\n" % str(self.trafo))
340 file.write("/Resources ")
341 self.patternregistry.writeresources(file)
342 if writer.compress:
343 import zlib
344 content = zlib.compress(self.patternproc)
345 else:
346 content = self.patternproc
348 file.write("/Length %i\n" % len(content))
349 if writer.compress:
350 file.write("/Filter /FlateDecode\n")
351 file.write(">>\n"
352 "stream\n")
353 file.write_bytes(content)
354 file.write("endstream\n")
357 class SVGpattern(svgwriter.SVGresource):
359 def __init__(self, pattern):
360 self.svgid = "pattern%d" % id(pattern)
361 super().__init__("pattern", self.svgid)
362 self.pattern = pattern
364 def output(self, xml, writer, registry):
365 if self.pattern.patternbbox:
366 patternbbox = self.pattern.patternbbox
367 else:
368 patternfile = io.BytesIO()
369 patternxml = svgwriter.SVGGenerator(patternfile)
370 patternbbox = bboxmodule.empty()
371 patternxml.startSVGDocument()
372 self.pattern.processSVG(patternxml, writer, svgwriter.context(), svgwriter.SVGregistry(), patternbbox)
374 attrs = {"id": self.svgid, "patternUnits": "userSpaceOnUse"}
375 llx, lly, urx, ury = patternbbox.highrestuple_pt()
376 attrs["viewBox"] = "%g %g %g %g" % (llx, -ury, urx-llx, ury-lly)
377 attrs["width"] = "%g" % (urx-llx)
378 attrs["height"] = "%g" % (ury-lly)
379 if self.pattern.patterntrafo:
380 self.pattern.patterntrafo.processSVGattrs(attrs, self, svgwriter.context(), registry)
381 attrs["patternTransform"] = attrs["transform"]
382 del attrs["transform"]
384 xml.startSVGElement("pattern", attrs)
385 self.pattern.processSVG(xml, writer, svgwriter.context(), registry, bboxmodule.empty())
386 xml.endSVGElement("pattern")