1#!/usr/bin/env python 2#coding:utf-8 3# Author: mozman --<mozman@gmx.at> 4# Purpose: mixins 5# Created: 19.10.2010 6# Copyright (C) 2010, Manfred Moitzi 7# License: MIT License 8 9from svgwrite.utils import strlist 10from svgwrite.utils import is_string 11 12_horiz = {'center': 'xMid', 'left': 'xMin', 'right': 'xMax'} 13_vert = {'middle': 'YMid', 'top': 'YMin', 'bottom':'YMax'} 14 15class ViewBox(object): 16 """ The **ViewBox** mixin provides the ability to specify that a 17 given set of graphics stretch to fit a particular container element. 18 19 The value of the **viewBox** attribute is a list of four numbers 20 **min-x**, **min-y**, **width** and **height**, separated by whitespace 21 and/or a comma, which specify a rectangle in **user space** which should 22 be mapped to the bounds of the viewport established by the given element, 23 taking into account attribute **preserveAspectRatio**. 24 25 """ 26 def viewbox(self, minx=0, miny=0, width=0, height=0): 27 """ Specify a rectangle in **user space** (no units allowed) which 28 should be mapped to the bounds of the viewport established by the 29 given element. 30 31 :param number minx: left border of the viewBox 32 :param number miny: top border of the viewBox 33 :param number width: width of the viewBox 34 :param number height: height of the viewBox 35 36 """ 37 self['viewBox'] = strlist( [minx, miny, width, height] ) 38 39 def stretch(self): 40 """ Stretch viewBox in x and y direction to fill viewport, does not 41 preserve aspect ratio. 42 """ 43 self['preserveAspectRatio'] = 'none' 44 45 def fit(self, horiz="center", vert="middle", scale="meet"): 46 """ Set the **preserveAspectRatio** attribute. 47 48 :param string horiz: horizontal alignment ``'left | center | right'`` 49 :param string vert: vertical alignment ``'top | middle | bottom'`` 50 :param string scale: scale method ``'meet | slice'`` 51 52 ============= ======================================================= 53 Scale methods Description 54 ============= ======================================================= 55 ``'meet'`` preserve aspect ration and zoom to limits of viewBox 56 ``'slice'`` preserve aspect ration and viewBox touch viewport on 57 all bounds, viewBox will extend beyond the bounds of 58 the viewport 59 ============= ======================================================= 60 61 """ 62 if self.debug and scale not in ('meet', 'slice'): 63 raise ValueError("Invalid scale parameter '%s'" % scale) 64 self['preserveAspectRatio'] = "%s%s %s" % (_horiz[horiz],_vert[vert], scale) 65 66class Transform(object): 67 """ The **Transform** mixin operates on the **transform** attribute. 68 The value of the **transform** attribute is a `<transform-list>`, which 69 is defined as a list of transform definitions, which are applied in the 70 order provided. The individual transform definitions are separated by 71 whitespace and/or a comma. All coordinates are **user 72 space coordinates**. 73 74 """ 75 transformname = 'transform' 76 def translate(self, tx, ty=None): 77 """ 78 Specifies a translation by **tx** and **ty**. If **ty** is not provided, 79 it is assumed to be zero. 80 81 :param number tx: user coordinate - no units allowed 82 :param number ty: user coordinate - no units allowed 83 """ 84 self._add_transformation("translate(%s)" % strlist( [tx, ty] )) 85 86 def rotate(self, angle, center=None): 87 """ 88 Specifies a rotation by **angle** degrees about a given point. 89 If optional parameter **center** are not supplied, the rotate is 90 about the origin of the current user coordinate system. 91 92 :param number angle: rotate-angle in degrees 93 :param 2-tuple center: rotate-center as user coordinate - no units allowed 94 95 """ 96 self._add_transformation("rotate(%s)" % strlist( [angle, center] )) 97 98 def scale(self, sx, sy=None): 99 """ 100 Specifies a scale operation by **sx** and **sy**. If **sy** is not 101 provided, it is assumed to be equal to **sx**. 102 103 :param number sx: scalar factor x-axis, no units allowed 104 :param number sy: scalar factor y-axis, no units allowed 105 106 """ 107 self._add_transformation("scale(%s)" % strlist([sx, sy])) 108 109 def skewX(self, angle): 110 """ Specifies a skew transformation along the x-axis. 111 112 :param number angle: skew-angle in degrees, no units allowed 113 114 """ 115 self._add_transformation("skewX(%s)" % angle) 116 117 def skewY(self, angle): 118 """ Specifies a skew transformation along the y-axis. 119 120 :param number angle: skew-angle in degrees, no units allowed 121 122 """ 123 self._add_transformation("skewY(%s)" % angle) 124 125 def matrix(self, a, b, c, d, e, f): 126 self._add_transformation("matrix(%s)" % strlist( [a, b, c, d, e, f] )) 127 128 def _add_transformation(self, new_transform): 129 old_transform = self.attribs.get(self.transformname, '') 130 self[self.transformname] = ("%s %s" % (old_transform, new_transform)).strip() 131 132 133class XLink(object): 134 """ XLink mixin """ 135 def set_href(self, element): 136 """ 137 Create a reference to **element**. 138 139 :param element: if element is a `string` its the **id** name of the 140 referenced element, if element is a **BaseElement** class the **id** 141 SVG Attribute is used to create the reference. 142 143 """ 144 self.href = element 145 self.update_id() 146 147 def set_xlink(self, title=None, show=None, role=None, arcrole=None): 148 """ Set XLink attributes (for `href` use :meth:`set_href`). 149 """ 150 if role is not None: 151 self['xlink:role'] = role 152 if arcrole is not None: 153 self['xlink:arcrole'] = arcrole 154 if title is not None: 155 self['xlink:title'] = title 156 if show is not None: 157 self['xlink:show'] = show 158 159 def update_id(self): 160 if not hasattr(self, 'href'): 161 return 162 if is_string(self.href): 163 idstr = self.href 164 else: 165 idstr = self.href.get_iri() 166 self.attribs['xlink:href'] = idstr 167 168 169class Presentation(object): 170 """ 171 Helper methods to set presentation attributes. 172 """ 173 def fill(self, color=None, rule=None, opacity=None): 174 """ 175 Set SVG Properties **fill**, **fill-rule** and **fill-opacity**. 176 177 """ 178 if color is not None: 179 if is_string(color): 180 self['fill'] = color 181 else: 182 self['fill'] = color.get_paint_server() 183 if rule is not None: 184 self['fill-rule'] = rule 185 if opacity is not None: 186 self['fill-opacity'] = opacity 187 return self 188 189 def stroke(self, color=None, width=None, opacity=None, linecap=None, 190 linejoin=None, miterlimit=None): 191 """ 192 Set SVG Properties **stroke**, **stroke-width**, **stroke-opacity**, 193 **stroke-linecap** and **stroke-miterlimit**. 194 195 """ 196 197 if color is not None: 198 if is_string(color): 199 self['stroke'] = color 200 else: 201 self['stroke'] = color.get_paint_server() 202 if width is not None: 203 self['stroke-width'] = width 204 if opacity is not None: 205 self['stroke-opacity'] = opacity 206 if linecap is not None: 207 self['stroke-linecap'] = linecap 208 if linejoin is not None: 209 self['stroke-linejoin'] = linejoin 210 if miterlimit is not None: 211 self['stroke-miterlimit'] = miterlimit 212 return self 213 214 def dasharray(self, dasharray=None, offset=None): 215 """ 216 Set SVG Properties **stroke-dashoffset** and **stroke-dasharray**. 217 218 Where *dasharray* specify the lengths of alternating dashes and gaps as 219 <list> of <int> or <float> values or a <string> of comma and/or white 220 space separated <lengths> or <percentages>. (e.g. as <list> dasharray=[1, 0.5] 221 or as <string> dasharray='1 0.5') 222 """ 223 if dasharray is not None: 224 self['stroke-dasharray'] = strlist(dasharray, ' ') 225 if offset is not None: 226 self['stroke-dashoffset'] = offset 227 return self 228 229 230class MediaGroup(object): 231 """ 232 Helper methods to set media group attributes. 233 234 """ 235 236 def viewport_fill(self, color=None, opacity=None): 237 """ 238 Set SVG Properties **viewport-fill** and **viewport-fill-opacity**. 239 240 """ 241 if color is not None: 242 self['viewport-fill'] = color 243 if opacity is not None: 244 self['viewport-fill-opacity'] = opacity 245 return self 246 247 248class Markers(object): 249 """ 250 Helper methods to set marker attributes. 251 252 """ 253 def set_markers(self, markers): 254 """ 255 Set markers for line elements (line, polygon, polyline, path) to 256 values specified by `markers`. 257 258 * if `markers` is a 3-tuple: 259 260 * attribute 'marker-start' = markers[0] 261 * attribute 'marker-mid' = markers[1] 262 * attribute 'marker-end' = markers[2] 263 264 * `markers` is a `string` or a `Marker` class: 265 266 * attribute 'marker' = `FuncIRI` of markers 267 268 """ 269 def get_funciri(value): 270 if is_string(value): 271 # strings has to be a valid reference including the '#' 272 return 'url(%s)' % value 273 else: 274 # else create a reference to the object '#id' 275 return 'url(#%s)' % value['id'] 276 277 if is_string(markers): 278 self['marker'] = get_funciri(markers) 279 else: 280 try: 281 start_marker, mid_marker, end_marker = markers 282 if start_marker: 283 self['marker-start'] = get_funciri(start_marker) 284 if mid_marker: 285 self['marker-mid'] = get_funciri(mid_marker) 286 if end_marker: 287 self['marker-end'] = get_funciri(end_marker) 288 except (TypeError, KeyError): 289 self['marker'] = get_funciri(markers) 290 291 292class Clipping(object): 293 def clip_rect(self, top='auto', right='auto', bottom='auto', left='auto'): 294 """ 295 Set SVG Property **clip**. 296 297 """ 298 self['clip'] = "rect(%s,%s,%s,%s)" % (top, right, bottom, left) 299