1#!/usr/local/bin/python 2 3""" 4Based on: http://wxpsvg.googlecode.com/svn/trunk/svg/pathdata.py 5According to that project, this file is licensed under the LGPL 6""" 7try: 8 from pyparsing import (ParserElement, Literal, Word, CaselessLiteral, 9 Optional, Combine, Forward, ZeroOrMore, nums, oneOf, Group, ParseException, OneOrMore) 10except ImportError: 11 import sys 12 sys.exit("pyparsing is required") 13 14 15#ParserElement.enablePackrat() 16 17def Command(char): 18 """ Case insensitive but case preserving""" 19 return CaselessPreservingLiteral(char) 20 21def Arguments(token): 22 return Group(token) 23 24 25class CaselessPreservingLiteral(CaselessLiteral): 26 """ Like CaselessLiteral, but returns the match as found 27 instead of as defined. 28 """ 29 def __init__( self, matchString ): 30 super(CaselessPreservingLiteral,self).__init__( matchString.upper() ) 31 self.name = "'%s'" % matchString 32 self.errmsg = "Expected " + self.name 33 self.myException.msg = self.errmsg 34 35 def parseImpl( self, instring, loc, doActions=True ): 36 test = instring[ loc:loc+self.matchLen ] 37 if test.upper() == self.match: 38 return loc+self.matchLen, test 39 #~ raise ParseException( instring, loc, self.errmsg ) 40 exc = self.myException 41 exc.loc = loc 42 exc.pstr = instring 43 raise exc 44 45def Sequence(token): 46 """ A sequence of the token""" 47 return OneOrMore(token+maybeComma) 48 49digit_sequence = Word(nums) 50 51sign = oneOf("+ -") 52 53def convertToFloat(s, loc, toks): 54 try: 55 return float(toks[0]) 56 except: 57 raise ParseException(loc, "invalid float format %s"%toks[0]) 58 59exponent = CaselessLiteral("e")+Optional(sign)+Word(nums) 60 61#note that almost all these fields are optional, 62#and this can match almost anything. We rely on Pythons built-in 63#float() function to clear out invalid values - loosely matching like this 64#speeds up parsing quite a lot 65floatingPointConstant = Combine( 66 Optional(sign) + 67 Optional(Word(nums)) + 68 Optional(Literal(".") + Optional(Word(nums)))+ 69 Optional(exponent) 70) 71 72floatingPointConstant.setParseAction(convertToFloat) 73 74number = floatingPointConstant 75 76#same as FP constant but don't allow a - sign 77nonnegativeNumber = Combine( 78 Optional(Word(nums)) + 79 Optional(Literal(".") + Optional(Word(nums)))+ 80 Optional(exponent) 81) 82nonnegativeNumber.setParseAction(convertToFloat) 83 84coordinate = number 85 86#comma or whitespace can seperate values all over the place in SVG 87maybeComma = Optional(Literal(',')).suppress() 88 89coordinateSequence = Sequence(coordinate) 90 91coordinatePair = (coordinate + maybeComma + coordinate).setParseAction(lambda t: tuple(t)) 92coordinatePairSequence = Sequence(coordinatePair) 93 94coordinatePairPair = coordinatePair + maybeComma + coordinatePair 95coordinatePairPairSequence = Sequence(Group(coordinatePairPair)) 96 97coordinatePairTriple = coordinatePair + maybeComma + coordinatePair + maybeComma + coordinatePair 98coordinatePairTripleSequence = Sequence(Group(coordinatePairTriple)) 99 100#commands 101lineTo = Group(Command("L") + Arguments(coordinatePairSequence)) 102curve = Group(Command("C") + Arguments(coordinatePairSequence)) 103 104moveTo = Group(Command("M") + Arguments(coordinatePairSequence)) 105 106closePath = Group(Command("Z")).setParseAction(lambda t: ('Z', (None,))) 107 108flag = oneOf("1 0").setParseAction(lambda t: bool(int((t[0])))) 109 110arcRadius = ( 111 nonnegativeNumber + maybeComma + #rx 112 nonnegativeNumber #ry 113).setParseAction(lambda t: tuple(t)) 114 115arcFlags = (flag + maybeComma + flag).setParseAction(lambda t: tuple(t)) 116 117ellipticalArcArgument = Group( 118 arcRadius + maybeComma + #rx, ry 119 number + maybeComma +#rotation 120 arcFlags + #large-arc-flag, sweep-flag 121 coordinatePair #(x,y) 122) 123 124ellipticalArc = Group(Command("A") + Arguments(Sequence(ellipticalArcArgument))) 125 126smoothQuadraticBezierCurveto = Group(Command("T") + Arguments(coordinatePairSequence)) 127 128quadraticBezierCurveto = Group(Command("Q") + Arguments(coordinatePairPairSequence)) 129 130smoothCurve = Group(Command("S") + Arguments(coordinatePairPairSequence)) 131 132#curve = Group(Command("C") + Arguments(coordinatePairTripleSequence)) 133 134horizontalLine = Group(Command("H") + Arguments(coordinateSequence)) 135verticalLine = Group(Command("V") + Arguments(coordinateSequence)) 136 137drawToCommand = ( 138 lineTo | moveTo | closePath | ellipticalArc | smoothQuadraticBezierCurveto | 139 quadraticBezierCurveto | smoothCurve | curve | horizontalLine | verticalLine 140 ) 141 142#~ number.debug = True 143moveToDrawToCommands = moveTo + ZeroOrMore(drawToCommand) 144 145path = ZeroOrMore(moveToDrawToCommands) 146path.keepTabs = True 147 148def get_points(d): 149 commands = path.parseString(d) 150 points = [] 151 currentset = None 152 for command in commands: 153 if command[0] == 'M': 154 currentset = [] 155 points.append(currentset) 156 currentset.append(command[1][-1]) 157 elif command[0] == 'L': 158 currentset.extend(command[1]) 159 elif command[0] == 'C': 160 currentset.extend(command[1]) 161 return points 162 163if __name__ == "__main__": 164 print path.parseString("M 242.96145,653.59282 L 244.83646,650.1553 L 247.02397,649.8428 L 247.33647,650.62405 L 245.30521,653.59282 L 242.96145,653.59282 z M 252.80525,649.99905 L 258.74278,652.49906 L 260.77404,652.18656 L 262.33654,648.43654 L 261.71154,645.15528 L 257.64902,644.68653 L 253.74275,646.40528 L 252.80525,649.99905 z M 282.49289,659.6866 L 286.08665,664.99912 L 288.43041,664.68662 L 289.52417,664.21787 L 290.93042,665.46787 L 294.52419,665.31162 L 295.4617,663.90537 L 292.64918,662.18661 L 290.77417,658.59284 L 288.74291,655.15533 L 283.11789,657.96784 L 282.49289,659.6866 z M 302.02423,668.28039 L 303.27423,666.40538 L 307.8055,667.34288 L 308.43051,666.87413 L 314.36803,667.49913 L 314.05553,668.74914 L 311.55552,670.15539 L 307.33675,669.84289 L 302.02423,668.28039 z M 307.1805,673.28041 L 309.05551,677.03043 L 312.02427,675.93667 L 312.33677,674.37416 L 310.77427,672.3429 L 307.1805,672.0304 L 307.1805,673.28041 z M 313.89928,672.18665 L 316.08679,669.37414 L 320.61806,671.7179 L 324.83683,672.81166 L 329.0556,675.46792 L 329.0556,677.34293 L 325.61809,679.06169 L 320.93056,679.99919 L 318.5868,678.59293 L 313.89928,672.18665 z M 329.99311,687.18672 L 331.55561,685.93672 L 334.83688,687.49923 L 342.18066,690.93674 L 345.46193,692.968 L 347.02443,695.31176 L 348.89944,699.53053 L 352.80571,702.03054 L 352.49321,703.28055 L 348.74319,706.40556 L 344.68067,707.81182 L 343.27442,707.18682 L 340.30565,708.90557 L 337.96189,712.03059 L 335.77438,714.8431 L 334.05562,714.68685 L 330.61811,712.18684 L 330.30561,707.81182 L 330.93061,705.46806 L 329.3681,699.99928 L 327.33684,698.28052 L 327.18059,695.78051 L 329.3681,694.84301 L 331.39936,691.87425 L 331.86811,690.93674 L 330.30561,689.21798 L 329.99311,687.18672 z ")