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, 2008, 2012 Paul Boddie <paul@boddie.org.uk> 9 10 This program is free software; you can redistribute it and/or modify it under 11 the terms of the GNU Lesser General Public License as published by the Free 12 Software Foundation; either version 3 of the License, or (at your option) any 13 later version. 14 15 This program is distributed in the hope that it will be useful, but WITHOUT 16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 18 details. 19 20 You should have received a copy of the GNU Lesser General Public License along 21 with this program. If not, see <http://www.gnu.org/licenses/>. 22 """ 23 24 import libxml2dom 25 from libxml2dom.events import * 26 from libxml2dom.macrolib import * 27 from libxml2dom.macrolib import \ 28 createDocument as Node_createDocument 29 import xml.dom 30 import urllib 31 import math 32 import re 33 34 SVG_NAMESPACE = "http://www.w3.org/2000/svg" 35 36 comma_wsp = re.compile("\s*,\s*|\s+") 37 38 TYPE_MISMATCH_ERR = 17 39 40 class TypeMismatchErr(xml.dom.DOMException): 41 code = TYPE_MISMATCH_ERR 42 43 class _Exception(Exception): 44 45 "A generic SVG exception." 46 47 def __init__(self, code): 48 Exception.__init__(self, code) 49 self.code = code 50 51 class SVGException(_Exception): 52 53 "An SVG exception." 54 55 SVG_WRONG_TYPE_ERR = 0 56 SVG_INVALID_VALUE_ERR = 1 57 SVG_MATRIX_NOT_INVERTABLE = 2 58 59 class GlobalException(_Exception): 60 61 "A global exception." 62 63 NOT_CONNECTED_ERR = 1 64 ENCODING_ERR = 2 65 DENIED_ERR = 3 66 UNKNOWN_ERR = 4 67 68 class SVGImplementation(libxml2dom.Implementation): 69 70 "Contains an SVG-specific implementation." 71 72 # Wrapping of documents. 73 74 def adoptDocument(self, node): 75 return SVGDocument(node, self) 76 77 # Factory functions. 78 79 def get_node(self, _node, context_node): 80 if Node_nodeType(_node) == context_node.ELEMENT_NODE and \ 81 Node_namespaceURI(_node) == SVG_NAMESPACE: 82 83 if Node_localName(_node) == "svg": 84 return SVGSVGElement(_node, self, context_node.ownerDocument) 85 else: 86 return SVGElement(_node, self, context_node.ownerDocument) 87 else: 88 return libxml2dom.Implementation.get_node(self, _node, context_node) 89 90 def get_global(self, doc): 91 return SVGGlobal(doc) 92 93 # Convenience functions. 94 95 def createSVGDocument(self): 96 97 "Create a new SVG document." 98 99 return SVGDocument(Node_createDocument(SVG_NAMESPACE, "svg", None), self) 100 101 # Interfaces and helper classes. 102 103 class AsyncStatusCallback: 104 105 "An asynchronous callback interface." 106 107 def operationComplete(self, status): 108 pass 109 110 class AsyncURLStatus: 111 112 "The status of a URL retrieval operation." 113 114 def __init__(self, success, contentType, content): 115 self.success, self.contentType, self.content = success, contentType, content 116 117 class ElementTraversal: 118 119 "An interface for element traversal." 120 121 def _firstElementChild(self): 122 l = self.xpath("*") 123 if l: 124 return l[0] 125 else: 126 return None 127 128 def _lastElementChild(self): 129 l = self.xpath("*") 130 if l: 131 return l[-1] 132 else: 133 return None 134 135 def _nextElementSibling(self): 136 l = self.xpath("following-sibling::*") 137 if l: 138 return l[0] 139 else: 140 return None 141 142 def _previousElementSibling(self): 143 l = self.xpath("preceding-sibling::*") 144 if l: 145 return l[0] 146 else: 147 return None 148 149 firstElementChild = property(_firstElementChild) 150 lastElementChild = property(_lastElementChild) 151 nextElementSibling = property(_nextElementSibling) 152 previousElementSibling = property(_previousElementSibling) 153 154 class EventListenerInitializer2: 155 156 "An event listener initialisation interface." 157 158 def initializeEventListeners(self, scriptElement): 159 pass 160 161 def createEventListener(self, handlerElement): 162 pass 163 164 class Global: 165 166 "An empty global interface." 167 168 pass 169 170 class SVGGlobal(Global, EventListenerInitializer2): 171 172 "An SVG global." 173 174 def __init__(self, document): # parent 175 176 "Initialise the global with the given 'document'." 177 178 self.document = document 179 180 # Listener management. 181 182 self.listeners = {} 183 184 def createConnection(self): 185 raise NotImplementedError, "createConnection" 186 187 def createTimer(self, initialInterval, repeatInterval): 188 raise NotImplementedError, "createTimer" 189 190 def gotoLocation(self, newIRI): 191 raise NotImplementedError, "gotoLocation" 192 193 def binaryToString(self, octets, encoding): 194 try: 195 return unicode(octets, encoding) 196 except UnicodeDecodeError, exc: 197 raise GlobalException(GlobalException.ENCODING_ERR) 198 199 def stringToBinary(self, data, encoding): 200 try: 201 return data.encode(encoding) 202 except UnicodeEncodeError, exc: 203 raise GlobalException(GlobalException.ENCODING_ERR) 204 205 def getURL(self, iri, callback): 206 207 # NOTE: Not asynchronous. 208 # NOTE: The urlopen function may not support IRIs. 209 # No exceptions are supposed to be raised, which is a bit nasty. 210 211 f = urllib.urlopen(iri) 212 try: 213 try: 214 content = f.read() 215 contentType = f.headers["Content-Type"] 216 callback.operationComplete(AsyncURLStatus(1, contentType, content)) 217 except: 218 callback.operationComplete(AsyncURLStatus(0, None, None)) 219 finally: 220 f.close() 221 222 def postURL(self, iri, data, callback, type=None, encoding=None): 223 224 # NOTE: Not asynchronous. 225 # NOTE: The urlopen function may not support IRIs. 226 # No exceptions are supposed to be raised, which is a bit nasty. 227 228 opener = urllib.URLopener() 229 opener.addheader("Content-Type", type or "text/plain") 230 if encoding: 231 opener.addheader("Content-Encoding", encoding) 232 f = opener.open(iri, data) 233 try: 234 try: 235 content = f.read() 236 contentType = f.headers["Content-Type"] 237 callback.operationComplete(AsyncURLStatus(1, contentType, content)) 238 except: 239 callback.operationComplete(AsyncURLStatus(0, None, None)) 240 finally: 241 f.close() 242 opener.close() 243 244 def parseXML(self, data, contextDoc): 245 doc = parseString(data) 246 return contextDoc.importNode(doc.documentElement, 1) 247 248 class SVGLocatable: 249 250 "A locatable interface." 251 252 pass 253 254 class SVGMatrix: 255 256 """ 257 A matrix. 258 See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__SVGMatrix 259 """ 260 261 transform_regexp = re.compile( 262 "(translate|scale|rotate|skewX|skewY|matrix)\((.*?)\)" 263 ) 264 265 def __init__(self, a=0, b=0, c=0, d=0, e=0, f=0): 266 self.matrix = a, b, c, d, e, f 267 268 def __repr__(self): 269 return "SVGMatrix(%f, %f, %f, %f, %f, %f)" % self.matrix 270 271 def __eq__(self, other): 272 return self.matrix == other.matrix 273 274 def __ne__(self, other): 275 return not (self == other) 276 277 def _get_params(self, param_string): 278 return [float(s.strip()) for s in comma_wsp.split(param_string.strip())] 279 280 def fromNode(self, node, name): 281 282 """ 283 Initialise this object from the trait on the 'node' having the given 284 'name'. 285 """ 286 287 value = node.getAttribute(name) 288 if value is None: 289 raise xml.dom.NotSupportedErr() 290 291 last = 0 292 293 matrix = 1, 0, 0, 1, 0, 0 294 295 for m in self.transform_regexp.finditer(value): 296 start, end = m.span() 297 298 # Make sure that nothing significant was found between the last 299 # match and this one. 300 301 if value[last:start].strip(): 302 raise TypeMismatchErr() 303 304 last = end 305 transform, arguments = m.groups() 306 307 # Translation. 308 309 if transform == "translate": 310 a, b, c, d = 1, 0, 0, 1 311 e, f = self._get_params(arguments) 312 313 # Scaling. 314 315 elif transform == "scale": 316 b, c, e, f = 0, 0, 0, 0 317 a, d = self._get_params(arguments) 318 319 # Rotation. 320 321 elif transform == "rotate": 322 e, f = 0, 0 323 angle = float(arguments.strip()) 324 a = d = math.cos(math.radians(angle)) 325 b = math.sin(math.radians(angle)) 326 c = -b 327 328 # Skew. 329 330 elif transform == "skewX": 331 a, b, d, e, f = 1, 0, 1, 0, 0 332 angle = float(arguments.strip()) 333 c = math.tan(math.radians(angle)) 334 335 elif transform == "skewY": 336 a, c, d, e, f = 1, 0, 1, 0, 0 337 angle = float(arguments.strip()) 338 b = math.tan(math.radians(angle)) 339 340 # Generic. 341 342 elif transform == "matrix": 343 a, b, c, d, e, f = self._get_params(arguments) 344 345 else: 346 raise TypeMismatchErr() 347 348 # Combine the existing matrix with the new one. 349 350 matrix = self._multiply(matrix, (a, b, c, d, e, f)) 351 352 else: 353 # Make sure that nothing significant was found after the final 354 # match. 355 356 if value[last:].strip(): 357 raise TypeMismatchErr() 358 359 if last != 0: 360 self.matrix = matrix 361 362 def toNode(self, node, name): 363 364 """ 365 Set the trait on the given 'node' using the given 'name' according to 366 this object's attributes. 367 """ 368 369 a, b, c, d, e, f = self.matrix 370 371 # Translation. 372 373 if (a, b, c, d) == (1, 0, 0, 1): 374 node.setAttribute(name, "translate(%f, %f)" % (e, f)) 375 376 # Scaling. 377 378 elif (b, c, e, f) == (0, 0, 0, 0): 379 node.setAttribute(name, "scale(%f, %f)" % (a, d)) 380 381 # Rotation. 382 383 elif a == d and b == -c and (e, f) == (0, 0) and math.degrees(math.acos(a)) == math.degrees(math.asin(b)): 384 node.setAttribute(name, "rotate(%f)" % math.degrees(math.acos(a))) 385 386 # Skew. 387 388 elif (a, b, d, e, f) == (1, 0, 1, 0, 0) and c != 0: 389 node.setAttribute(name, "skewX(%f)" % math.degrees(math.atan(c))) 390 391 elif (a, c, d, e, f) == (1, 0, 1, 0, 0) and b != 0: 392 node.setAttribute(name, "skewX(%f)" % math.degrees(math.atan(b))) 393 394 # Generic matrix. 395 396 else: 397 node.setAttribute(name, "matrix(%f, %f, %f, %f, %f, %f)" % (a, b, c, d, e, f)) 398 399 def getComponent(self, index): 400 401 """ 402 Return the component with the given 'index' (starting at zero) from the 403 sequence a, b, c, d, e, f where each element corresponds to the matrix 404 as follows: 405 406 [ a c e ] 407 [ b d f ] 408 [ 0 0 1 ] 409 """ 410 411 try: 412 return self.matrix[index] 413 except IndexError: 414 raise xml.dom.IndexSizeErr() 415 416 def _multiply(self, matrix1, matrix2): 417 a1, b1, c1, d1, e1, f1 = matrix1 418 a2, b2, c2, d2, e2, f2 = matrix2 419 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 420 421 def mMultiply(self, secondMatrix): 422 423 """ 424 Post-multiply this matrix with 'secondMatrix' and update its contents to 425 the result of the multiplication operation defined as follows: 426 427 [ A C E ] [ a c e ] 428 [ B D F ] [ b d f ] 429 [ 0 0 1 ] [ 0 0 1 ] 430 431 Return this object as a result. 432 """ 433 434 self.matrix = self._multiply(secondMatrix.matrix, self.matrix) 435 return self 436 437 def inverse(self): 438 439 """ 440 det = ad - cb 441 442 See (for example): http://mathworld.wolfram.com/MatrixInverse.html 443 """ 444 445 det = a*d - c*b 446 if det != 0: 447 m = 1/det 448 a, b, c, d, e, f = self.matrix 449 self.matrix = m * d, m * -b, m * -c, m * a, m * (c*f - e*d), m * (e*b - a*f) 450 return self 451 else: 452 raise SVGException(SVGException.SVG_MATRIX_NOT_INVERTABLE) 453 454 def mTranslate(self, x, y): 455 456 """ 457 [ 1 0 x ] 458 [ 0 1 y ] 459 [ 0 0 1 ] 460 """ 461 462 return self.mMultiply(SVGMatrix(1, 0, 0, 1, x, y)) 463 464 def mScale(self, scaleFactor): 465 466 """ 467 [ scaleFactor 0 0 ] 468 [ 0 scaleFactor 0 ] 469 [ 0 0 1 ] 470 """ 471 472 return self.mMultiply(SVGMatrix(scaleFactor, 0, 0, scaleFactor, 0, 0)) 473 474 def mRotate(self, angle): 475 476 """ 477 [ cos(angle) -sin(angle) 0 ] 478 [ sin(angle) cos(angle) 0 ] 479 [ 0 0 1 ] 480 """ 481 482 return self.mMultiply( 483 SVGMatrix( 484 math.cos(math.radians(angle)), 485 math.sin(math.radians(angle)), 486 -math.sin(math.radians(angle)), 487 math.cos(math.radians(angle)), 488 0, 0 489 ) 490 ) 491 492 class SVGPath: 493 494 """ 495 A path. 496 See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__SVGPath 497 See: http://www.w3.org/TR/SVGMobile12/paths.html 498 """ 499 500 path_regexp = re.compile("([mMzZlLhHvVcCsSqQtTaA])\s*([^mMzZlLhHvVcCsSqQtTaA]*)") 501 502 # NOTE: Upper case involves absolute coordinates; lower case involves 503 # NOTE: relative coordinates. The IDL constants only seem to represent the 504 # NOTE: former commands. 505 506 MOVE_TO = ord("M") 507 LINE_TO = ord("L") 508 CURVE_TO = ord("C") 509 QUAD_TO = ord("Q") 510 CLOSE = ord("Z") 511 512 nparams = { 513 "A" : 7, 514 "C" : 6, 515 "H" : 1, 516 "L" : 2, 517 "M" : 2, 518 "Q" : 4, 519 "S" : 4, 520 "T" : 2, 521 "V" : 1, 522 "Z" : 0, 523 } 524 525 def __init__(self): 526 self.segments = [] 527 528 def __eq__(self, other): 529 return self.segments == other.segments 530 531 def __ne__(self, other): 532 return not (self == other) 533 534 def fromNode(self, node, name): 535 536 """ 537 Initialise this object from the trait on the 'node' having the given 538 'name'. 539 """ 540 541 value = node.getAttribute(name) 542 if value is None: 543 raise xml.dom.NotSupportedErr() 544 545 # Try and unpack the attribute value. 546 # NOTE: This does not yet satisfy the permissive parsing behaviour 547 # NOTE: described in the specification. 548 # See: http://www.w3.org/TR/SVG11/paths.html#PathDataBNF 549 550 self.segments = [] 551 last = 0 552 553 for m in self.path_regexp.finditer(value): 554 start, end = m.span() 555 if value[last:start].strip(): 556 raise TypeMismatchErr() 557 558 last = end 559 cmd, arguments = m.groups() 560 561 try: 562 n = self.nparams[cmd.upper()] 563 564 if arguments.strip(): 565 params = [float(s.strip()) for s in comma_wsp.split(arguments.strip())] 566 else: 567 params = [] 568 569 if n != 0 and len(params) % n != 0: 570 raise TypeMismatchErr(cmd, arguments) 571 572 self.segments.append((cmd, params)) 573 574 except (IndexError, ValueError): 575 raise TypeMismatchErr(cmd, arguments) 576 577 else: 578 if value[last:start].strip(): 579 raise TypeMismatchErr() 580 581 def toNode(self, node, name): 582 583 """ 584 Set the trait on the given 'node' using the given 'name' according to 585 this object's attributes. 586 """ 587 588 try: 589 l = [] 590 for cmd, params in self.segments: 591 l.append(unichr(cmd)) 592 for param in params: 593 l.append(str(param)) 594 node.setAttribute(name, " ".join(l)) 595 except (IndexError, ValueError): 596 raise TypeMismatchErr() 597 598 # Interface methods. 599 600 def _numberOfSegments(self): 601 return len(self.segments) 602 603 numberOfSegments = property(_numberOfSegments) 604 605 def getSegment(self, cmdIndex): 606 try: 607 return ord(self.segments[cmdIndex][0]) 608 except IndexError: 609 raise xml.dom.IndexSizeErr() 610 611 def getSegmentParam(self, cmdIndex, paramIndex): 612 try: 613 return self.segments[cmdIndex][1][paramIndex] 614 except IndexError: 615 raise xml.dom.IndexSizeErr() 616 617 def moveTo(self, x, y): 618 self.segments.append(("M", (x, y))) 619 620 def lineTo(self, x, y): 621 self.segments.append(("L", (x, y))) 622 623 def quadTo(self, x1, y1, x2, y2): 624 self.segments.append(("Q", (x1, y1, x2, y2))) 625 626 def curveTo(self, x1, y1, x2, y2, x3, y3): 627 self.segments.append(("C", (x1, y1, x2, y2, x3, y3))) 628 629 def close(self): 630 self.segments.append(("Z",)) 631 632 class SVGPoint: 633 634 "A point used to provide currentTranslate." 635 636 def __init__(self, x, y): 637 self.x = x 638 self.y = y 639 640 def __repr__(self): 641 return "SVGPoint(%f, %f)" % (self.x, self.y) 642 643 class SVGRect: 644 645 "A rectangle." 646 647 def __init__(self, x=0, y=0, width=0, height=0): 648 self.x, self.y, self.width, self.height = x, y, width, height 649 650 def __eq__(self, other): 651 return (self.x, self.y, self.width, self.height) == (other.x, other.y, other.width, other.height) 652 653 def __ne__(self, other): 654 return not (self == other) 655 656 def fromNode(self, node, name): 657 658 """ 659 Initialise this object from the trait on the 'node' having the given 660 'name'. 661 """ 662 663 value = node.getAttribute(name) 664 if value is None: 665 raise xml.dom.NotSupportedErr() 666 try: 667 values = map(float, value.split()) 668 self.x, self.y, self.width, self.height = values 669 except (IndexError, ValueError): 670 raise TypeMismatchErr() 671 672 def toNode(self, node, name): 673 674 """ 675 Set the trait on the given 'node' using the given 'name' according to 676 this object's attributes. 677 """ 678 679 try: 680 values = map(str, [self.x, self.y, self.width, self.height]) 681 node.setAttribute(name, " ".join(values)) 682 except (IndexError, ValueError): 683 raise TypeMismatchErr() 684 685 def __repr__(self): 686 return "SVGRect(%f, %f, %f, %f)" % (self.x, self.y, self.width, self.height) 687 688 class SVGRGBColor: 689 690 """ 691 A colour. 692 See: http://www.w3.org/TR/SVGMobile12/painting.html#colorSyntax 693 """ 694 695 def __init__(self, red, green, blue): 696 self.red, self.green, self.blue = red, green, blue 697 698 def __repr__(self): 699 return "SVGRGBColor(%f, %f, %f)" % (self.red, self.green, self.blue) 700 701 class TraitAccess: 702 703 """ 704 Access to traits stored on elements. 705 See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__TraitAccess 706 """ 707 708 def getPathTrait(self, name): 709 path = SVGPath() 710 path.fromNode(self, name) 711 return path 712 713 def setPathTrait(self, name, path): 714 path.toNode(self, name) 715 716 def getRectTrait(self, name): 717 rect = SVGRect() 718 rect.fromNode(self, name) 719 return rect 720 721 def setRectTrait(self, name, rect): 722 rect.toNode(self, name) 723 724 def getMatrixTrait(self, name): 725 if name != "transform": 726 raise xml.dom.NotSupportedErr() 727 matrix = SVGMatrix() 728 matrix.fromNode(self, name) 729 return matrix 730 731 def setMatrixTrait(self, name, matrix): 732 if name != "transform": 733 raise xml.dom.NotSupportedErr() 734 matrix.toNode(self, name) 735 736 # Node classes. 737 738 class SVGNode(libxml2dom.Node): 739 740 "Convenience modifications to nodes specific to libxml2dom.svg." 741 742 def xpath(self, expr, variables=None, namespaces=None): 743 744 """ 745 Evaluate the given 'expr' using the optional 'variables' and 746 'namespaces'. If not otherwise specified, the "svg" prefix will be bound 747 to SVG_NAMESPACE as defined in this module. 748 """ 749 750 namespaces = namespaces or {} 751 if not namespaces.has_key("svg"): 752 namespaces["svg"] = SVG_NAMESPACE 753 return libxml2dom.Node.xpath(self, expr, variables, namespaces) 754 755 # NOTE: DocumentEvent is from DOM Level 3 Events. 756 # NOTE: EventSystem is a special libxml2dom.events class. 757 758 class SVGDocument(libxml2dom._Document, SVGNode, EventTarget, DocumentEvent, EventSystem): 759 760 "An SVG-specific document node." 761 762 def __init__(self, node, impl): 763 764 """ 765 Initialise the document with the given 'node', implementation 'impl', 766 and global (SVGGlobal) details. 767 """ 768 769 libxml2dom._Document.__init__(self, node, impl) 770 self.global_ = self.impl.get_global(self) # parent 771 772 class SVGElement(SVGNode, EventTarget, TraitAccess, ElementTraversal): # NOTE: SVGNode instead of Element. 773 774 "An SVG-specific element." 775 776 def __init__(self, *args, **kw): 777 SVGNode.__init__(self, *args, **kw) 778 779 def _id(self): 780 return self.getAttribute("id") 781 782 def _setId(self, value): 783 self.setAttribute("id", value) 784 785 id = property(_id, _setId) 786 787 class SVGLocatableElement(SVGElement, SVGLocatable): 788 789 "A locatable element." 790 791 pass 792 793 class SVGTimedElement(SVGElement): # smil::ElementTimeControl 794 795 "A timed element." 796 797 def __init__(self, *args): 798 799 "Initialise the element with the underlying 'args'." 800 801 SVGElement.__init__(self, *args) 802 self.document_time = 0 803 self.paused = 0 804 805 def _isPaused(self): 806 return self.paused 807 808 def pauseElement(self): 809 self.paused = 1 810 811 def resumeElement(self): 812 self.paused = 0 813 814 class SVGSVGElement(SVGLocatableElement, SVGTimedElement): 815 816 "An SVG-specific top-level element." 817 818 NAV_AUTO = 1 819 NAV_NEXT = 2 820 NAV_PREV = 3 821 NAV_UP = 4 822 NAV_UP_RIGHT = 5 823 NAV_RIGHT = 6 824 NAV_DOWN_RIGHT = 7 825 NAV_DOWN = 8 826 NAV_DOWN_LEFT = 9 827 NAV_LEFT = 10 828 NAV_UP_LEFT = 11 829 830 def __init__(self, *args): 831 832 "Initialise the element with the underlying 'args'." 833 834 SVGTimedElement.__init__(self, *args) 835 self.scale = 1 836 self.rotate = 0 837 self.translate = SVGPoint(0, 0) 838 839 # NOTE: The scale, rotate and translate properties are not persistent, and 840 # NOTE: are specific to individual objects. 841 842 def _currentScale(self): 843 return self.scale 844 845 def _currentRotate(self): 846 return self.rotate 847 848 def _currentTranslate(self): 849 return self.translate 850 851 def _setCurrentScale(self, scale): 852 if scale == 0: 853 raise xml.dom.InvalidAccessErr() 854 self.scale = scale 855 856 def _setCurrentRotate(self, rotate): 857 self.rotate = rotate 858 859 def _viewport(self): 860 if self.hasAttribute("viewBox"): 861 return self.getRectTrait("viewBox") 862 elif self.hasAttribute("width") and self.hasAttribute("height"): 863 return SVGRect(0, 0, self._convertMeasurement(self.getAttribute("width")), 864 self._convertMeasurement(self.getAttribute("height"))) 865 else: 866 return None 867 868 # Utility methods. 869 870 units = ["in", "cm", "mm", "pt", "pc", "px", "%"] 871 872 def _convertMeasurement(self, value): 873 value = value.strip() 874 for unit in self.units: 875 if value.endswith(unit): 876 # NOTE: No conversion yet! 877 return float(value[:-len(unit)].strip()) 878 879 raise TypeMismatchErr() 880 881 # Standard methods. 882 883 def getCurrentTime(self): 884 return self.document_time 885 886 def setCurrentTime(self, setCurrentTime): 887 self.document_time = setCurrentTime 888 889 def createSVGMatrixComponents(self, a, b, c, d, e, f): 890 return SVGMatrix(a, b, c, d, e, f) 891 892 def createSVGRect(self): 893 return SVGRect() 894 895 def createSVGPath(self): 896 return SVGPath() 897 898 def createSVGRGBColor(self, red, green, blue): 899 return SVGRGBColor(red, green, blue) 900 901 def moveFocus(self, motionType): 902 raise NotImplementedError, "moveFocus" 903 904 def setFocus(self, object): 905 raise NotImplementedError, "setFocus" 906 907 def getCurrentFocusedObject(self): 908 raise NotImplementedError, "getCurrentFocusedObject" 909 910 currentScale = property(_currentScale, _setCurrentScale) 911 currentRotate = property(_currentRotate, _setCurrentRotate) 912 currentTranslate = property(_currentTranslate) 913 viewport = property(_viewport) 914 915 # Event handler initialisation. 916 917 def initialiseEvents(doc): 918 919 """ 920 See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__EventListenerInitializer2 921 See: http://www.w3.org/TR/xml-events/#section-listener-element 922 """ 923 924 # Initialise script element listeners. 925 926 for script in doc.xpath("//svg:script"): 927 doc.global_.initializeEventListeners(script) 928 929 # Initialise handler element listeners using XML Events. 930 931 for handler in doc.xpath("//svg:handler"): 932 listener = doc.global_.createEventListener(handler) 933 934 # Attempt to parameterise the registration using the XML Events attributes. 935 936 phase = handler.getAttributeNS(libxml2dom.events.XML_EVENTS_NAMESPACE, "phase") 937 938 # Add the listener for the appropriate type and phases. 939 940 handler.parentNode.addEventListener( 941 handler.getAttributeNS(libxml2dom.events.XML_EVENTS_NAMESPACE, "event"), 942 listener, 943 phase == "capture" 944 ) 945 946 # Utility functions. 947 948 createDocument = libxml2dom.createDocument 949 createDocumentType = libxml2dom.createDocumentType 950 951 def createSVGDocument(): 952 return default_impl.createSVGDocument() 953 954 def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): 955 doc = libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 956 initialiseEvents(doc) 957 return doc 958 959 def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): 960 doc = libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 961 initialiseEvents(doc) 962 return doc 963 964 def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): 965 doc = libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 966 initialiseEvents(doc) 967 return doc 968 969 def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): 970 doc = libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 971 initialiseEvents(doc) 972 return doc 973 974 # Single instance of the implementation. 975 976 default_impl = SVGImplementation() 977 978 # vim: tabstop=4 expandtab shiftwidth=4