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")
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
)
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
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
:
62 if ystep
is not _marker
:
64 if bbox
is not _marker
:
66 if trafo
is not _marker
:
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())
82 xstep
= unit
.topt(self
.xstep
)
83 if self
.ystep
is None:
84 ystep
= unit
.topt(realpatternbbox
.height())
86 ystep
= unit
.topt(self
.ystep
)
88 raise ValueError("xstep in pattern cannot be zero")
90 raise ValueError("ystep in pattern cannot be zero")
92 patternbbox
= self
.patternbbox
94 patternbbox
= realpatternbbox
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(),
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")))
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())
129 xstep
= unit
.topt(self
.xstep
)
130 if self
.ystep
is None:
131 ystep
= unit
.topt(realpatternbbox
.height())
133 ystep
= unit
.topt(self
.ystep
)
135 raise ValueError("xstep in pattern cannot be zero")
137 raise ValueError("ystep in pattern cannot be zero")
139 patternbbox
= self
.patternbbox
141 patternbbox
= realpatternbbox
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
))
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")
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
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
])
189 self
.stroke(path
.line_pt(0, -50, 0, 50), self
.strokestyles
)
191 def __call__(self
, dist
=None, angle
=None, strokestyles
=None):
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
])
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):
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
)
321 self
.patterntype
= patterntype
322 self
.painttype
= painttype
323 self
.tilingtype
= tilingtype
328 self
.patternproc
= patternproc
330 def write(self
, file, writer
, registry
):
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)
344 content
= zlib
.compress(self
.patternproc
)
346 content
= self
.patternproc
348 file.write("/Length %i\n" % len(content
))
350 file.write("/Filter /FlateDecode\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
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")