1.1 --- a/libxml2dom/svg.py Sun Jan 29 00:35:51 2012 +0100
1.2 +++ b/libxml2dom/svg.py Sun Jan 29 00:36:39 2012 +0100
1.3 @@ -5,7 +5,7 @@
1.4 See: http://www.w3.org/TR/SVGMobile12/python-binding.html
1.5 See: http://www.w3.org/TR/SVGMobile12/svgudom.html
1.6
1.7 -Copyright (C) 2007 Paul Boddie <paul@boddie.org.uk>
1.8 +Copyright (C) 2007, 2008, 2012 Paul Boddie <paul@boddie.org.uk>
1.9
1.10 This program is free software; you can redistribute it and/or modify it under
1.11 the terms of the GNU Lesser General Public License as published by the Free
1.12 @@ -33,6 +33,13 @@
1.13
1.14 SVG_NAMESPACE = "http://www.w3.org/2000/svg"
1.15
1.16 +comma_wsp = re.compile("\s*,\s*|\s+")
1.17 +
1.18 +TYPE_MISMATCH_ERR = 17
1.19 +
1.20 +class TypeMismatchErr(xml.dom.DOMException):
1.21 + code = TYPE_MISMATCH_ERR
1.22 +
1.23 class _Exception(Exception):
1.24
1.25 "A generic SVG exception."
1.26 @@ -251,12 +258,9 @@
1.27 See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__SVGMatrix
1.28 """
1.29
1.30 - translate_regexp = re.compile("translate\((.*)\)$")
1.31 - scale_regexp = re.compile("scale\((.*)\)$")
1.32 - rotate_regexp = re.compile("rotate\((.*)\)$")
1.33 - skewX_regexp = re.compile("skewX\((.*)\)$")
1.34 - skewY_regexp = re.compile("skewY\((.*)\)$")
1.35 - matrix_regexp = re.compile("matrix\((.*)\)$")
1.36 + transform_regexp = re.compile(
1.37 + "(translate|scale|rotate|skewX|skewY|matrix)\((.*?)\)"
1.38 + )
1.39
1.40 def __init__(self, a=0, b=0, c=0, d=0, e=0, f=0):
1.41 self.matrix = a, b, c, d, e, f
1.42 @@ -271,7 +275,7 @@
1.43 return not (self == other)
1.44
1.45 def _get_params(self, param_string):
1.46 - return map(float, map(lambda s: s.strip(), param_string.split(",")))
1.47 + return [float(s.strip()) for s in comma_wsp.split(param_string.strip())]
1.48
1.49 def fromNode(self, node, name):
1.50
1.51 @@ -282,68 +286,78 @@
1.52
1.53 value = node.getAttribute(name)
1.54 if value is None:
1.55 - raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR)
1.56 + raise xml.dom.NotSupportedErr()
1.57 +
1.58 + last = 0
1.59 +
1.60 + matrix = 1, 0, 0, 1, 0, 0
1.61
1.62 - value = value.strip()
1.63 + for m in self.transform_regexp.finditer(value):
1.64 + start, end = m.span()
1.65
1.66 - # Translation.
1.67 + # Make sure that nothing significant was found between the last
1.68 + # match and this one.
1.69 +
1.70 + if value[last:start].strip():
1.71 + raise TypeMismatchErr()
1.72
1.73 - m = self.translate_regexp.match(value)
1.74 - if m:
1.75 - a, b, c, d = 1, 0, 0, 1
1.76 - e, f = self._get_params(m.group(1))
1.77 - self.matrix = a, b, c, d, e, f
1.78 - return
1.79 + last = end
1.80 + transform, arguments = m.groups()
1.81
1.82 - # Scaling.
1.83 + # Translation.
1.84
1.85 - m = self.scale_regexp.match(value)
1.86 - if m:
1.87 - b, c, e, f = 0, 0, 0, 0
1.88 - a, d = self._get_params(m.group(1))
1.89 - self.matrix = a, b, c, d, e, f
1.90 - return
1.91 + if transform == "translate":
1.92 + a, b, c, d = 1, 0, 0, 1
1.93 + e, f = self._get_params(arguments)
1.94 +
1.95 + # Scaling.
1.96
1.97 - # Rotation.
1.98 + elif transform == "scale":
1.99 + b, c, e, f = 0, 0, 0, 0
1.100 + a, d = self._get_params(arguments)
1.101 +
1.102 + # Rotation.
1.103
1.104 - m = self.rotate_regexp.match(value)
1.105 - if m:
1.106 - e, f = 0, 0
1.107 - angle = float(m.group(1).strip())
1.108 - a = d = math.cos(math.radians(angle))
1.109 - b = math.sin(math.radians(angle))
1.110 - c = -b
1.111 - self.matrix = a, b, c, d, e, f
1.112 - return
1.113 + elif transform == "rotate":
1.114 + e, f = 0, 0
1.115 + angle = float(arguments.strip())
1.116 + a = d = math.cos(math.radians(angle))
1.117 + b = math.sin(math.radians(angle))
1.118 + c = -b
1.119 +
1.120 + # Skew.
1.121
1.122 - # Skew.
1.123 + elif transform == "skewX":
1.124 + a, b, d, e, f = 1, 0, 1, 0, 0
1.125 + angle = float(arguments.strip())
1.126 + c = math.tan(math.radians(angle))
1.127 +
1.128 + elif transform == "skewY":
1.129 + a, c, d, e, f = 1, 0, 1, 0, 0
1.130 + angle = float(arguments.strip())
1.131 + b = math.tan(math.radians(angle))
1.132
1.133 - m = self.skewX_regexp.match(value)
1.134 - if m:
1.135 - a, b, d, e, f = 1, 0, 1, 0, 0
1.136 - angle = float(m.group(1).strip())
1.137 - c = math.tan(math.radians(angle))
1.138 - self.matrix = a, b, c, d, e, f
1.139 - return
1.140 + # Generic.
1.141 +
1.142 + elif transform == "matrix":
1.143 + a, b, c, d, e, f = self._get_params(arguments)
1.144 +
1.145 + else:
1.146 + raise TypeMismatchErr()
1.147 +
1.148 + # Combine the existing matrix with the new one.
1.149
1.150 - m = self.skewY_regexp.match(value)
1.151 - if m:
1.152 - a, c, d, e, f = 1, 0, 1, 0, 0
1.153 - angle = float(m.group(1).strip())
1.154 - b = math.tan(math.radians(angle))
1.155 - self.matrix = a, b, c, d, e, f
1.156 - return
1.157 + matrix = self._multiply(matrix, (a, b, c, d, e, f))
1.158 +
1.159 + else:
1.160 + # Make sure that nothing significant was found after the final
1.161 + # match.
1.162
1.163 - # Generic.
1.164 + if value[last:].strip():
1.165 + raise TypeMismatchErr()
1.166
1.167 - m = self.matrix_regexp.match(value)
1.168 - if m:
1.169 - self.matrix = self._get_params(m.group(1))
1.170 - return
1.171 -
1.172 - # Otherwise, complain.
1.173 -
1.174 - raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.175 + if last != 0:
1.176 + self.matrix = matrix
1.177
1.178 def toNode(self, node, name):
1.179
1.180 @@ -397,7 +411,12 @@
1.181 try:
1.182 return self.matrix[index]
1.183 except IndexError:
1.184 - raise xml.dom.DOMException(xml.dom.INDEX_SIZE_ERR)
1.185 + raise xml.dom.IndexSizeErr()
1.186 +
1.187 + def _multiply(self, matrix1, matrix2):
1.188 + a1, b1, c1, d1, e1, f1 = matrix1
1.189 + a2, b2, c2, d2, e2, f2 = matrix2
1.190 + return a1*a2 + c1*b2, b1*a2 + d1*b2, a1*c2 + c1*d2, b1*c2 + d1*d2, a1*e2 + c1*f2 + e1, b1*e2 + d1*f2 + f1
1.191
1.192 def mMultiply(self, secondMatrix):
1.193
1.194 @@ -412,9 +431,7 @@
1.195 Return this object as a result.
1.196 """
1.197
1.198 - a, b, c, d, e, f = self.matrix
1.199 - A, B, C, D, E, F = secondMatrix.matrix
1.200 - self.matrix = A*a + C*b, B*a + D*b, A*c + C*d, B*c + D*d, A*e + C*f + E, B*e + D*f + F
1.201 + self.matrix = self._multiply(secondMatrix.matrix, self.matrix)
1.202 return self
1.203
1.204 def inverse(self):
1.205 @@ -480,20 +497,29 @@
1.206 See: http://www.w3.org/TR/SVGMobile12/paths.html
1.207 """
1.208
1.209 - MOVE_TO = 77
1.210 - LINE_TO = 76
1.211 - CURVE_TO = 67
1.212 - QUAD_TO = 81
1.213 - CLOSE = 90
1.214 - _CLOSE = 122 # More baggage (name not standard).
1.215 + path_regexp = re.compile("([mMzZlLhHvVcCsSqQtTaA])\s*([^mMzZlLhHvVcCsSqQtTaA]*)")
1.216 +
1.217 + # NOTE: Upper case involves absolute coordinates; lower case involves
1.218 + # NOTE: relative coordinates. The IDL constants only seem to represent the
1.219 + # NOTE: former commands.
1.220 +
1.221 + MOVE_TO = ord("M")
1.222 + LINE_TO = ord("L")
1.223 + CURVE_TO = ord("C")
1.224 + QUAD_TO = ord("Q")
1.225 + CLOSE = ord("Z")
1.226
1.227 nparams = {
1.228 - MOVE_TO : 2,
1.229 - LINE_TO : 2,
1.230 - CURVE_TO : 6,
1.231 - QUAD_TO : 4,
1.232 - CLOSE : 0,
1.233 - _CLOSE : 0
1.234 + "A" : 7,
1.235 + "C" : 6,
1.236 + "H" : 1,
1.237 + "L" : 2,
1.238 + "M" : 2,
1.239 + "Q" : 4,
1.240 + "S" : 4,
1.241 + "T" : 2,
1.242 + "V" : 1,
1.243 + "Z" : 0,
1.244 }
1.245
1.246 def __init__(self):
1.247 @@ -514,25 +540,43 @@
1.248
1.249 value = node.getAttribute(name)
1.250 if value is None:
1.251 - raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR)
1.252 + raise xml.dom.NotSupportedErr()
1.253
1.254 # Try and unpack the attribute value.
1.255 + # NOTE: This does not yet satisfy the permissive parsing behaviour
1.256 + # NOTE: described in the specification.
1.257 + # See: http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
1.258
1.259 - data = value.split()
1.260 self.segments = []
1.261 - try:
1.262 - i = 0
1.263 - while i < len(data):
1.264 - cmd = ord(data[i])
1.265 - if cmd == self._CLOSE:
1.266 - cmd = self.CLOSE
1.267 - i += 1
1.268 - n = self.nparams[cmd]
1.269 - params = map(float, data[i:i+n])
1.270 + last = 0
1.271 +
1.272 + for m in self.path_regexp.finditer(value):
1.273 + start, end = m.span()
1.274 + if value[last:start].strip():
1.275 + raise TypeMismatchErr()
1.276 +
1.277 + last = end
1.278 + cmd, arguments = m.groups()
1.279 +
1.280 + try:
1.281 + n = self.nparams[cmd.upper()]
1.282 +
1.283 + if arguments.strip():
1.284 + params = [float(s.strip()) for s in comma_wsp.split(arguments.strip())]
1.285 + else:
1.286 + params = []
1.287 +
1.288 + if n != 0 and len(params) % n != 0:
1.289 + raise TypeMismatchErr(cmd, arguments)
1.290 +
1.291 self.segments.append((cmd, params))
1.292 - i += n
1.293 - except (IndexError, ValueError):
1.294 - raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.295 +
1.296 + except (IndexError, ValueError):
1.297 + raise TypeMismatchErr(cmd, arguments)
1.298 +
1.299 + else:
1.300 + if value[last:start].strip():
1.301 + raise TypeMismatchErr()
1.302
1.303 def toNode(self, node, name):
1.304
1.305 @@ -549,7 +593,7 @@
1.306 l.append(str(param))
1.307 node.setAttribute(name, " ".join(l))
1.308 except (IndexError, ValueError):
1.309 - raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.310 + raise TypeMismatchErr()
1.311
1.312 # Interface methods.
1.313
1.314 @@ -560,30 +604,30 @@
1.315
1.316 def getSegment(self, cmdIndex):
1.317 try:
1.318 - return self.segments[cmdIndex][0]
1.319 + return ord(self.segments[cmdIndex][0])
1.320 except IndexError:
1.321 - raise xml.dom.DOMException(xml.dom.INDEX_SIZE_ERR)
1.322 + raise xml.dom.IndexSizeErr()
1.323
1.324 def getSegmentParam(self, cmdIndex, paramIndex):
1.325 try:
1.326 return self.segments[cmdIndex][1][paramIndex]
1.327 except IndexError:
1.328 - raise xml.dom.DOMException(xml.dom.INDEX_SIZE_ERR)
1.329 + raise xml.dom.IndexSizeErr()
1.330
1.331 def moveTo(self, x, y):
1.332 - self.segments.append((self.MOVE_TO, (x, y)))
1.333 + self.segments.append(("M", (x, y)))
1.334
1.335 def lineTo(self, x, y):
1.336 - self.segments.append((self.LINE_TO, (x, y)))
1.337 + self.segments.append(("L", (x, y)))
1.338
1.339 def quadTo(self, x1, y1, x2, y2):
1.340 - self.segments.append((self.QUAD_TO, (x1, y1, x2, y2)))
1.341 + self.segments.append(("Q", (x1, y1, x2, y2)))
1.342
1.343 def curveTo(self, x1, y1, x2, y2, x3, y3):
1.344 - self.segments.append((self.CURVE_TO, (x1, y1, x2, y2, x3, y3)))
1.345 + self.segments.append(("C", (x1, y1, x2, y2, x3, y3)))
1.346
1.347 def close(self):
1.348 - self.segments.append((self.CLOSE,))
1.349 + self.segments.append(("Z",))
1.350
1.351 class SVGPoint:
1.352
1.353 @@ -618,12 +662,12 @@
1.354
1.355 value = node.getAttribute(name)
1.356 if value is None:
1.357 - raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR)
1.358 + raise xml.dom.NotSupportedErr()
1.359 try:
1.360 values = map(float, value.split())
1.361 self.x, self.y, self.width, self.height = values
1.362 except (IndexError, ValueError):
1.363 - raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.364 + raise TypeMismatchErr()
1.365
1.366 def toNode(self, node, name):
1.367
1.368 @@ -636,7 +680,7 @@
1.369 values = map(str, [self.x, self.y, self.width, self.height])
1.370 node.setAttribute(name, " ".join(values))
1.371 except (IndexError, ValueError):
1.372 - raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.373 + raise TypeMismatchErr()
1.374
1.375 def __repr__(self):
1.376 return "SVGRect(%f, %f, %f, %f)" % (self.x, self.y, self.width, self.height)
1.377 @@ -679,14 +723,14 @@
1.378
1.379 def getMatrixTrait(self, name):
1.380 if name != "transform":
1.381 - raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR)
1.382 + raise xml.dom.NotSupportedErr()
1.383 matrix = SVGMatrix()
1.384 matrix.fromNode(self, name)
1.385 return matrix
1.386
1.387 def setMatrixTrait(self, name, matrix):
1.388 if name != "transform":
1.389 - raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR)
1.390 + raise xml.dom.NotSupportedErr()
1.391 matrix.toNode(self, name)
1.392
1.393 # Node classes.
1.394 @@ -806,7 +850,7 @@
1.395
1.396 def _setCurrentScale(self, scale):
1.397 if scale == 0:
1.398 - raise xml.dom.DOMException(xml.dom.INVALID_ACCESS_ERR)
1.399 + raise xml.dom.InvalidAccessErr()
1.400 self.scale = scale
1.401
1.402 def _setCurrentRotate(self, rotate):
1.403 @@ -815,9 +859,11 @@
1.404 def _viewport(self):
1.405 if self.hasAttribute("viewBox"):
1.406 return self.getRectTrait("viewBox")
1.407 - else:
1.408 + elif self.hasAttribute("width") and self.hasAttribute("height"):
1.409 return SVGRect(0, 0, self._convertMeasurement(self.getAttribute("width")),
1.410 self._convertMeasurement(self.getAttribute("height")))
1.411 + else:
1.412 + return None
1.413
1.414 # Utility methods.
1.415
1.416 @@ -830,7 +876,7 @@
1.417 # NOTE: No conversion yet!
1.418 return float(value[:-len(unit)].strip())
1.419
1.420 - raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.421 + raise TypeMismatchErr()
1.422
1.423 # Standard methods.
1.424