1 #!/usr/bin/env python 2 3 """ 4 SVG-specific document support. 5 See: http://www.w3.org/TR/SVGMobile12/python-binding.html 6 See: http://www.w3.org/TR/SVGMobile12/svgudom.html 7 8 Copyright (C) 2007 Paul Boddie <paul@boddie.org.uk> 9 10 This library is free software; you can redistribute it and/or 11 modify it under the terms of the GNU Lesser General Public 12 License as published by the Free Software Foundation; either 13 version 2.1 of the License, or (at your option) any later version. 14 15 This library 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 GNU 18 Lesser General Public License for more details. 19 20 You should have received a copy of the GNU Lesser General Public 21 License along with this library; if not, write to the Free Software 22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 23 """ 24 25 import libxml2dom 26 from libxml2dom.events import * 27 from libxml2dom.macrolib import * 28 import xml.dom 29 import math 30 31 SVG_NAMESPACE = "http://www.w3.org/2000/svg" 32 33 class _Exception(Exception): 34 35 "A generic SVG exception." 36 37 def __init__(self, code): 38 Exception.__init__(self, code) 39 self.code = code 40 41 class SVGException(_Exception): 42 43 "An SVG exception." 44 45 SVG_WRONG_TYPE_ERR = 0 46 SVG_INVALID_VALUE_ERR = 1 47 SVG_MATRIX_NOT_INVERTABLE = 2 48 49 class GlobalException(_Exception): 50 51 "A global exception." 52 53 NOT_CONNECTED_ERR = 1 54 ENCODING_ERR = 2 55 DENIED_ERR = 3 56 UNKNOWN_ERR = 4 57 58 class SVGImplementation(libxml2dom.Implementation): 59 60 "Contains an SVG-specific implementation." 61 62 # Wrapping of documents. 63 64 def adoptDocument(self, node): 65 return SVGDocument(node, self) 66 67 # Factory functions. 68 69 def get_node(self, _node, context_node): 70 if Node_nodeType(_node) == context_node.ELEMENT_NODE and \ 71 Node_namespaceURI(_node) == SVG_NAMESPACE: 72 73 if Node_localName(_node) == "svg": 74 return SVGSVGElement(_node, self, context_node.ownerDocument) 75 else: 76 return SVGElement(_node, self, context_node.ownerDocument) 77 else: 78 return libxml2dom.Implementation.get_node(self, _node, context_node) 79 80 # Interfaces and helper classes. 81 82 class AsyncStatusCallback: 83 84 "An asynchronous callback interface." 85 86 def operationComplete(self, status): 87 pass 88 89 class AsyncURLStatus: 90 91 "The status of a URL retrieval operation." 92 93 def __init__(self, success, contentType, content): 94 self.success, self.contentType, self.content = success, contentType, content 95 96 class SVGGlobal: # Global, EventListenerInitializer2 97 98 "An SVG global." 99 100 def __init__(self, document): # parent 101 102 "Initialise the global with the given 'document'." 103 104 self.document = document 105 106 def createConnection(self): 107 raise NotImplementedError, "createConnection" 108 109 def createTimer(self, initialInterval, repeatInterval): 110 raise NotImplementedError, "createTimer" 111 112 def gotoLocation(self, newIRI): 113 raise NotImplementedError, "gotoLocation" 114 115 def binaryToString(self, octets, encoding): 116 raise NotImplementedError, "binaryToString" 117 118 def stringToBinary(self, data, encoding): 119 raise NotImplementedError, "stringToBinary" 120 121 def getURL(self, iri, callback): 122 123 # NOTE: Not asynchronous. 124 # NOTE: The urlopen function may not support IRIs. 125 # No exceptions are supposed to be raised, which is a bit nasty. 126 127 f = urllib.urlopen(iri) 128 try: 129 try: 130 content = f.read() 131 contentType = f.headers["Content-Type"] 132 callback.operationComplete(AsyncURLStatus(1, contentType, content)) 133 except: 134 callback.operationComplete(AsyncURLStatus(0, None, None)) 135 finally: 136 f.close() 137 138 def postURL(self, iri, data, callback, type, encoding): 139 raise NotImplementedError, "postURL" 140 141 def parseXML(self, data, contextDoc): 142 doc = parseString(data) 143 return contextDoc.importNode(doc.documentElement, 1) 144 145 class SVGLocatable: 146 147 "A locatable interface." 148 149 pass 150 151 class SVGMatrix: 152 153 """ 154 A matrix. 155 See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__SVGMatrix 156 """ 157 158 def __init__(self, a, b, c, d, e, f): 159 self.matrix = a, b, c, d, e, f 160 161 def getComponent(self, index): 162 163 """ 164 Return the component with the given 'index' (starting at zero) from the 165 sequence a, b, c, d, e, f where each element corresponds to the matrix 166 as follows: 167 168 [ a c e ] 169 [ b d f ] 170 [ 0 0 1 ] 171 """ 172 173 try: 174 return self.matrix[index] 175 except IndexError: 176 raise xml.dom.DOMException(xml.dom.INDEX_SIZE_ERR) 177 178 def mMultiply(self, secondMatrix): 179 180 """ 181 Multiply this matrix with 'secondMatrix' and update its contents to the 182 result of the multiplication operation defined as follows: 183 184 [ a c e ] [ A C E ] 185 [ b d f ] [ B D F ] 186 [ 0 0 1 ] [ 0 0 1 ] 187 188 Return this object as a result. 189 """ 190 191 a, b, c, d, e, f = self.matrix 192 A, B, C, D, E, F = secondMatrix.matrix 193 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 194 return self 195 196 def inverse(self): 197 198 """ 199 det = ad - cb 200 201 See (for example): http://mathworld.wolfram.com/MatrixInverse.html 202 """ 203 204 det = a*d - c*b 205 if det != 0: 206 m = 1/det 207 a, b, c, d, e, f = self.matrix 208 self.matrix = m * d, m * -b, m * -c, m * a, m * (c*f - e*d), m * (e*b - a*f) 209 return self 210 else: 211 raise SVGException(SVGException.SVG_MATRIX_NOT_INVERTABLE) 212 213 def mTranslate(self, x, y): 214 215 """ 216 [ 1 0 x ] 217 [ 0 1 y ] 218 [ 0 0 1 ] 219 """ 220 221 return self.mMultiply(SVGMatrix(1, 0, 0, 1, x, y)) 222 223 def mScale(self, scaleFactor): 224 225 """ 226 [ scaleFactor 0 0 ] 227 [ 0 scaleFactor 0 ] 228 [ 0 0 1 ] 229 """ 230 231 return self.mMultiply(SVGMatrix(scaleFactor, 0, 0, scaleFactor, 0, 0)) 232 233 def mRotate(self, angle): 234 235 """ 236 [ cos(angle) -sin(angle) 0 ] 237 [ sin(angle) cos(angle) 0 ] 238 [ 0 0 1 ] 239 """ 240 241 return self.mMultiply(SVGMatrix(math.cos(angle), math.sin(angle), -math.sin(angle), math.cos(angle), 0, 0)) 242 243 class SVGPath: 244 245 """ 246 A path. 247 See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__SVGPath 248 See: http://www.w3.org/TR/SVGMobile12/paths.html 249 """ 250 251 MOVE_TO = 77 252 LINE_TO = 76 253 CURVE_TO = 67 254 QUAD_TO = 81 255 CLOSE = 90 256 257 def __init__(self): 258 self.segments = [] 259 260 def _numberOfSegments(self): 261 return len(self.segments) 262 263 numberOfSegments = property(_numberOfSegments) 264 265 def getSegment(self, cmdIndex): 266 try: 267 return self.segments[cmdIndex][0] 268 except IndexError: 269 raise xml.dom.DOMException(xml.dom.INDEX_SIZE_ERR) 270 271 def getSegmentParam(self, cmdIndex, paramIndex): 272 try: 273 return self.segments[cmdIndex][1][paramIndex] 274 except IndexError: 275 raise xml.dom.DOMException(xml.dom.INDEX_SIZE_ERR) 276 277 def moveTo(self, x, y): 278 self.segments.append((self.MOVE_TO, (x, y))) 279 280 def lineTo(self, x, y): 281 self.segments.append((self.LINE_TO, (x, y))) 282 283 def quadTo(self, x1, y1, x2, y2): 284 self.segments.append((self.QUAD_TO, (x1, y1, x2, y2))) 285 286 def curveTo(self, x1, y1, x2, y2, x3, y3): 287 self.segments.append((self.CURVE_TO, (x1, y1, x2, y2, x3, y3))) 288 289 def close(self): 290 self.segments.append((self.CLOSE,)) 291 292 class SVGPoint: 293 294 "A point." 295 296 def __init__(self, x, y): 297 self.x = x 298 self.y = y 299 300 class SVGRect: 301 302 "A rectangle." 303 304 def __init__(self, x, y, width, height): 305 self.x, self.y, self.width, self.height = x, y, width, height 306 307 class SVGRGBColor: 308 309 "A colour." 310 311 def __init__(self, red, green, blue): 312 self.red, self.green, self.blue = red, green, blue 313 314 # Node classes. 315 316 class SVGDocument(libxml2dom.Document, DocumentEvent, EventTarget): 317 318 "An SVG-specific document node." 319 320 def __init__(self, node, impl): 321 322 """ 323 Initialise the document with the given 'node', implementation 'impl', 324 and global (SVGGlobal) details. 325 """ 326 327 libxml2dom.Document.__init__(self, node, impl) 328 self.global_ = SVGGlobal(self) # parent 329 330 class SVGElement(libxml2dom.Node, EventTarget): # (Element), TraitAccess, ElementTraversal 331 332 "An SVG-specific element." 333 334 def _id(self): 335 return self.getAttribute("id") 336 337 def _setId(self, value): 338 self.setAttribute("id", value) 339 340 id = property(_id, _setId) 341 342 class SVGLocatableElement(SVGElement, SVGLocatable): 343 344 "A locatable element." 345 346 pass 347 348 class SVGTimedElement(SVGElement): # smil::ElementTimeControl 349 350 "A timed element." 351 352 def __init__(self, *args): 353 354 "Initialise the element with the underlying 'args'." 355 356 SVGElement.__init__(self, *args) 357 self.document_time = 0 358 self.paused = 0 359 360 def _isPaused(self): 361 return self.paused 362 363 def pauseElement(self): 364 self.paused = 1 365 366 def resumeElement(self): 367 self.paused = 0 368 369 class SVGSVGElement(SVGLocatableElement, SVGTimedElement): 370 371 "An SVG-specific top-level element." 372 373 NAV_AUTO = 1 374 NAV_NEXT = 2 375 NAV_PREV = 3 376 NAV_UP = 4 377 NAV_UP_RIGHT = 5 378 NAV_RIGHT = 6 379 NAV_DOWN_RIGHT = 7 380 NAV_DOWN = 8 381 NAV_DOWN_LEFT = 9 382 NAV_LEFT = 10 383 NAV_UP_LEFT = 11 384 385 def __init__(self, *args): 386 387 "Initialise the element with the underlying 'args'." 388 389 SVGTimedElement.__init__(self, *args) 390 self.scale = 1 391 self.rotate = 0 392 self.translate = SVGPoint(0, 0) 393 394 def _currentScale(self): 395 return self.scale 396 397 def _currentRotate(self): 398 return self.rotate 399 400 def _currentTranslate(self): 401 return self.translate 402 403 def _viewport(self): 404 attr = self.getAttribute("viewBox") 405 if attr is None: 406 return attr 407 l = map(int, attr.split()) 408 return SVGRect(*l) 409 410 def getCurrentTime(self): 411 return self.document_time 412 413 def setCurrentTime(self, setCurrentTime): 414 self.document_time = setCurrentTime 415 416 def createSVGMatrixComponents(self, a, b, c, d, e, f): 417 return SVGMatrix(a, b, c, d, e, f) 418 419 def createSVGRect(self): 420 return SVGRect(0, 0, 0, 0) 421 422 def createSVGPath(self): 423 return SVGPath() 424 425 def createSVGRGBColor(self, red, green, blue): 426 return SVGRGBColor(red, green, blue) 427 428 def moveFocus(self, motionType): 429 raise NotImplementedError, "moveFocus" 430 431 def setFocus(self, object): 432 raise NotImplementedError, "setFocus" 433 434 def getCurrentFocusedObject(self): 435 raise NotImplementedError, "getCurrentFocusedObject" 436 437 currentScale = property(_currentScale) 438 currentRotate = property(_currentRotate) 439 currentTranslate = property(_currentTranslate) 440 viewport = property(_viewport) 441 442 # Convenience functions. 443 444 def parse(stream_or_string, html=0, htmlencoding=None): 445 return libxml2dom.parse(stream_or_string, html, htmlencoding, default_impl) 446 447 def parseFile(filename, html=0, htmlencoding=None): 448 return libxml2dom.parseFile(filename, html, htmlencoding, default_impl) 449 450 def parseString(s, html=0, htmlencoding=None): 451 return libxml2dom.parseString(s, html, htmlencoding, default_impl) 452 453 def parseURI(uri, html=0, htmlencoding=None): 454 return libxml2dom.parseURI(uri, html, htmlencoding, default_impl) 455 456 # Single instance of the implementation. 457 458 default_impl = SVGImplementation() 459 460 # vim: tabstop=4 expandtab shiftwidth=4