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