예제 #1
0
파일: svg2rlg.py 프로젝트: eseifert/svg2rlg
    def render(self, node, parent=None):
        if parent is None:
            parent = self.mainGroup

        # ignore if display = none
        display = node.get('display')
        if display == "none":
            return

        if node.tag == self.SVG_ROOT:
            self.level += 1

            if not self.drawing is None:
                raise SVGError('drawing already created!')

            self.root = node

            # default styles
            style = {
                'color':'none',
                'fill':'none',
                'stroke':'none',
                'font-family':'Helvetica',
                'font-size':'12'
            }

            self.styles[self.level] = style

            # iterate children
            for child in node:
                self.render(child, self.mainGroup)

            # create drawing
            width = node.get('width', '100%')
            height = node.get('height', '100%')

            if node.get("viewBox"):
                try:
                    minx, miny, width, height = node.get("viewBox").split()
                except ValueError:
                    raise SVGError("viewBox values not valid")

            if width.endswith('%') and height.endswith('%'):
                # handle relative size
                wscale = parseLength(width) / 100.
                hscale = parseLength(height) / 100.

                xL,yL,xH,yH =  self.mainGroup.getBounds()
                self.drawing = Drawing(xH*wscale + xL, yH*hscale + yL)

            else:
                self.drawing = Drawing(parseLength(width), parseLength(height))

            height = self.drawing.height
            self.mainGroup.scale(1, -1)
            self.mainGroup.translate(0, -height)
            self.drawing.add(self.mainGroup)

            self.level -= 1

            return self.drawing

        elif node.tag in (self.SVG_G, self.SVG_A):
            self.level += 1

            # set this levels style
            style = self.styles[self.level - 1].copy()
            style = self.nodeStyle(node, style)
            self.styles[self.level] = style

            group = Group()

            # iterate children
            for child in node:
                self.render(child, group)

            parent.add(group)

            transforms = node.get('transform')
            if transforms:
                for op in parseTransform.iterparse(transforms):
                    self.applyTransformOnGroup(group, op)

            self.level -= 1

        elif node.tag == self.SVG_USE:
            self.level += 1

            # set this levels style
            style = self.styles[self.level - 1].copy()
            style = self.nodeStyle(node, style)
            self.styles[self.level] = style

            group = Group()

            # link id
            link_id = node.get(self.LINK).lstrip('#')

            # find linked node in defs or symbol section
            target = None
            for defs in self.root.getiterator(self.SVG_DEFS):
                for element in defs:
                    if element.get('id') == link_id:
                        target = element
                        break

            if target is None:
                for defs in self.root.getiterator(self.SVG_SYMBOL):
                    for element in defs:
                        if element.get('id') == link_id:
                            target = element
                            break

                if target is None:
                    msg = "Could not find use node '%s'" % link_id
                    raise SVGError(msg)

            self.render(target, group)

            parent.add(group)

            # apply transform
            transforms = node.get('transform')
            if transforms:
                for op in parseTransform.iterparse(transforms):
                    self.applyTransformOnGroup(group, op)

            # apply 'x' and 'y' attribute as translation of defs object
            if node.get('x') or node.get('y'):
                dx = parseLength(node.get('x','0'))
                dy = parseLength(node.get('y','0'))

                self.applyTransformOnGroup(group, ('translate', (dx,dy)))

            self.level -= 1

        elif node.tag == self.SVG_LINE:
            # get coordinates
            x1 = parseLength(node.get('x1', '0'))
            y1 = parseLength(node.get('y1', '0'))
            x2 = parseLength(node.get('x2', '0'))
            y2 = parseLength(node.get('y2', '0'))

            shape = Line(x1, y1, x2, y2)
            self.addShape(parent, node, shape)

        elif node.tag == self.SVG_RECT:
            # get coordinates
            x = parseLength(node.get('x', '0'))
            y = parseLength(node.get('y', '0'))
            width = parseLength(node.get('width'))
            height = parseLength(node.get('height'))

            rx = parseLength(node.get('rx', '0'))
            ry = parseLength(node.get('ry', '0'))

            shape = Rect(x, y, width, height, rx=rx, ry=ry)
            self.addShape(parent, node, shape)

        elif node.tag == self.SVG_CIRCLE:
            cx = parseLength(node.get('cx', '0'))
            cy = parseLength(node.get('cy', '0'))
            r = parseLength(node.get('r'))

            if r > 0.:
                shape = Circle(cx, cy, r)
                self.addShape(parent, node, shape)

        elif node.tag == self.SVG_ELLIPSE:
            cx = parseLength(node.get('cx', '0'))
            cy = parseLength(node.get('cy', '0'))
            rx = parseLength(node.get('rx'))
            ry = parseLength(node.get('ry'))

            if rx > 0. and ry > 0.:
                shape = Ellipse(cx, cy, rx, ry)
                self.addShape(parent, node, shape)

        elif node.tag == self.SVG_POLYLINE:
            # convert points
            points = node.get('points').strip()
            if len(points) == 0:
                return

            points = list(map(parseLength, re.split('[ ,]+', points)))

            # Need to use two shapes, because standard RLG polylines
            # do not support filling...
            group = Group()
            shape = Polygon(points)
            self.applyStyleToShape(shape, node)
            shape.strokeColor = None
            group.add(shape)

            shape = PolyLine(points)
            self.applyStyleToShape(shape, node)
            group.add(shape)

            self.addShape(parent, node, group)

        elif node.tag == self.SVG_POLYGON:
            # convert points
            points = node.get('points').strip()
            if len(points) == 0:
                return

            points = list(map(parseLength, re.split('[ ,]+', points)))

            shape = Polygon(points)
            self.addShape(parent, node, shape)

        elif node.tag == self.SVG_IMAGE:
            x = parseLength(node.get('x', '0'))
            y = parseLength(node.get('y', '0'))
            width = parseLength(node.get('width', '0'))
            height = parseLength(node.get('height', '0'))

            # link id
            link_id = node.get(self.LINK)

            filename = os.path.join(os.path.dirname(self.filename), link_id)
            shape = Image(x, y, width, height, filename)

            self.addShape(parent, node, shape)

        elif node.tag == self.SVG_TEXT:
            # Todo:
            # - rotation not handled
            # - baseshift not handled
            # - embedded span node not handled
            #
            def parsePos(node, subnode, name, default='0'):
                values = subnode.get(name)
                if values is None:
                    if node is not None:
                        values = node.get(name, default)
                    else:
                        values = default

                return list(map(parseLength, values.split()))

            def getPos(values, i, default=None):
                if i >= len(values):
                    if default is None:
                        return values[-1]
                    else:
                        return default
                else:
                    return values[i]

            def handleText(node, subnode, text):
                # get position variables
                xs = parsePos(node, subnode, 'x')
                dxs = parsePos(node, subnode, 'dx')
                ys = parsePos(node, subnode,'y')
                dys = parsePos(node, subnode,'dy')

                if sum(map(len, (xs,ys,dxs,dys))) == 4:
                    # single value
                    shape = String(xs[0] + dxs[0], -ys[0] - dys[0], text)
                    self.applyStyleToShape(shape, subnode)
                    group.add(shape)

                else:
                    # multiple values
                    for i, c in enumerate(text):
                        x = getPos(xs, i)
                        dx = getPos(dxs, i, 0)
                        y = getPos(ys, i)
                        dy = getPos(dys, i, 0)

                        shape = String(x + dx, -y -dy, c)
                        self.applyStyleToShape(shape, subnode)
                        group.add(shape)

            if node.text and node.text.strip():
                group = Group()

                handleText(None, node, node.text.strip())

                group.scale(1, -1)

                self.addShape(parent, node, group)

            if len(node) > 0:
                group = Group()

                self.level += 1

                # set this levels style
                style = self.styles[self.level - 1].copy()
                nodestylestyle = self.nodeStyle(node, style)
                self.styles[self.level] = nodestylestyle

                for subnode in node:
                    if subnode.tag == self.SVG_TSPAN:
                        handleText(node, subnode, subnode.text.strip())

                self.level -= 1

                group.scale(1, -1)
                self.addShape(parent, node, group)

        elif node.tag == self.SVG_PATH:
            def convertQuadratic(Q0, Q1, Q2):
                C1 = (Q0[0] + 2./3*(Q1[0] - Q0[0]), Q0[1] + 2./3*(Q1[1] - Q0[1]))
                C2 = (C1[0] + 1./3*(Q2[0] - Q0[0]), C1[1] + 1./3*(Q2[1] - Q0[1]))
                C3 = Q2
                return C1[0], C1[1], C2[0], C2[1], C3[0], C3[1]

            def prevCtrl(lastOp, lastArgs, currentX, currentY):
                # fetch last controll point
                if lastOp in 'CScsQqTt':
                    x, y = lastArgs[-2]

                    # mirror about current point
                    return currentX + (currentX-x), currentY + (currentY-y)

                else:
                    # defaults to current point
                    return currentX, currentY

            # store sub paths in 'paths' list
            shape = Path()

            # keep track of current point and path start point
            startX, startY = 0., 0.
            currentX, currentY = 0., 0.

            # keep track of last operation
            lastOp = None
            lastArgs = None

            # avoid empty path data
            data = node.get('d')
            if data is None or len(data) == 0:
                return

            for op, args in parsePath.iterparse(data):
                if op == 'z' or op == 'Z':
                    # close path or subpath
                    shape.closePath()

                    # next sub path starts at begining of current path
                    currentX, currentY = startX, startY

                elif op == 'M':
                    # moveto absolute
                    if lastOp is not None and lastOp not in ('z', 'Z'):
                        # close sub path
                        shape.closePath()

                    x, y = args[0]
                    shape.moveTo(x, y)

                    startX, startY = x, y

                    # multiple moveto arge result in line
                    for x, y in args[1:]:
                        shape.lineTo(x, y)

                    currentX, currentY = x, y

                elif op == 'm':
                    if lastOp is not None and lastOp not in ('z', 'Z'):
                        # close sub path
                        shape.closePath()

                    # moveto relative
                    rx, ry = args[0]
                    x, y = currentX + rx, currentY + ry
                    shape.moveTo(x, y)

                    startX, startY = x, y
                    currentX, currentY = x, y

                    # multiple moveto arge result in line
                    for rx, ry in args[1:]:
                        x, y = currentX + rx, currentY + ry
                        shape.lineTo(x, y)
                        currentX, currentY = x, y

                elif op == 'L':
                    # lineto absolute
                    for x, y in args:
                        shape.lineTo(x, y)

                    currentX, currentY = x, y

                elif op == 'l':
                    # lineto relative
                    for rx, ry in args:
                        x, y = currentX + rx, currentY + ry
                        shape.lineTo(x, y)
                        currentX, currentY = x, y

                elif op == 'V':
                    # vertical line absolute
                    for y in args:
                        shape.lineTo(currentX, y)

                    currentY = y

                elif op == 'v':
                    # vertical line relative
                    for ry in args:
                        y = currentY + ry
                        shape.lineTo(currentX, y)
                        currentY = y

                elif op == 'H':
                    # horisontal line absolute
                    for x in args:
                        shape.lineTo(x, currentY)
                    currentX = x

                elif op == 'h':
                    # horisontal line relative
                    for rx in args:
                        x = currentX + rx
                        shape.lineTo(x, currentY)
                    currentX = x

                elif op == 'C':
                    # cubic bezier absolute
                    for p1, p2, p3 in zip(*([iter(args)] * 3)):
                        shape.curveTo(*(p1 + p2 + p3))
                        currentX, currentY = p3

                elif op == 'c':
                    # cubic bezier relative
                    for pnts in zip(*([iter(args)] * 3)):
                        (x1, y1), (x2, y2), (x3, y3) = pnts
                        pnts = tuple((p[0] + currentX, p[1] + currentY) for p in pnts)
                        shape.curveTo(*(pnts[0] + pnts[1] + pnts[2]))
                        currentX, currentY = pnts[2]
                        lastOp = op
                        lastArgs = pnts
                    continue

                elif op == 'S':
                    # shorthand cubic bezier absolute
                    for p2, p3 in zip(*([iter(args)] * 2)):
                        x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY)
                        x2, y2 = p2
                        x3, y3 = p3
                        shape.curveTo(x1, y1, x2, y2, x3, y3)
                        lastOp = op
                        lastArgs = (x2, y2), (x3, y3)
                        currentX, currentY = x3, y3
                    continue

                elif op == 's':
                    # shorthand cubic bezier relative
                    for p2, p3 in zip(*([iter(args)] * 2)):
                        x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY)
                        x2, y2 = p2
                        x2, y2 = x2 + currentX, y2 + currentY
                        x3, y3 = p3
                        x3, y3 = x3 + currentX, y3 + currentY
                        shape.curveTo(x1, y1, x2, y2, x3, y3)
                        currentX, currentY = x3, y3
                        lastOp = op
                        lastArgs = (x1, y1), (x2, y2), (x3, y3)
                    continue

                elif op == 'Q':
                    # quadratic bezier absolute
                    for p2, p3 in zip(*([iter(args)] * 2)):
                        x1, y1 = currentX, currentY
                        x2, y2 = p2
                        x3, y3 = p3
                        ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3))
                        shape.curveTo(*ctrls)
                        currentX, currentY = x3, y3
                    lastOp = op
                    lastArgs = (x2, y2), (x3, y3)
                    continue

                elif op == 'q':
                    # quadratic bezier relative
                    for p2, p3 in zip(*([iter(args)] * 2)):
                        x1, y1 = currentX, currentY
                        x2, y2 = p2
                        x2, y2 = x2 + currentX, y2 + currentY
                        x3, y3 = p3
                        x3, y3 = x3 + currentX, y3 + currentY
                        ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3))
                        shape.curveTo(*ctrls)
                        currentX, currentY = x3, y3

                    lastOp = op
                    lastArgs = (x2, y2), (x3, y3)
                    continue

                elif op == 'T':
                    # shorthand quadratic bezier absolute
                    for i in range(len(args)):
                        x1, y1 = currentX, currentY
                        x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY)
                        x3, y3 = args[i]
                        ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3))
                        shape.curveTo(*ctrls)
                        currentX, currentY = x3, y3
                        lastOp = op
                        lastArgs = (x2, y2), (x3, y3)

                    continue

                elif op == 't':
                    # shorthand quadratic bezier relative
                    for i in range(len(args)):
                        x1, y1 = currentX, currentY
                        x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY)
                        x3, y3 = args[i]
                        x3, y3 = x3 + currentX, y3 + currentY
                        ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3))
                        shape.curveTo(*ctrls)
                        currentX, currentY = x3, y3
                        lastOp = op
                        lastArgs = (x2, y2), (x3, y3)

                    continue

                elif op == 'A' or op == 'a':
                    # elliptic arc missing
                    continue

                lastOp = op
                lastArgs = args

            # check if fill applies to path
            fill = None
            if node.get('fill'):
                # inline style
                fill = node.get('fill')
            else:
                # try local style
                if node.get('style'):
                    style = parseStyle.parse(node.get('style'))

                    if 'fill' in style:
                        fill = style['fill']

                # try global style
                if fill is None:
                    style = self.styles[self.level]

                    if 'fill' in style:
                        fill = style['fill']
                    else:
                        fill = 'none'

            # hack because RLG has no "semi-closed" paths...
            if lastOp == 'z' or lastOp == 'Z' or fill == 'none':
                self.addShape(parent, node, shape)

            else:
                group = Group()
                strokeshape = shape.copy()
                self.addShape(group, node, strokeshape, fill='none')
                shape.closePath()
                self.addShape(group, node, shape, stroke='none')
                self.addShape(parent, node, group)

        elif node.tag == self.SVG_TITLE or node.tag == self.SVG_DESC:
            # Skip non-graphics elements
            return
예제 #2
0
    def render(self, node, parent=None):
        if parent is None:
            parent = self.mainGroup

        # ignore if display = none
        display = node.get('display')
        if display == "none":
            return

        if node.tag == self.SVG_ROOT:
            self.level += 1

            if not self.drawing is None:
                raise SVGError('drawing already created!')

            self.root = node

            # default styles
            style = {
                'color': 'none',
                'fill': 'none',
                'stroke': 'none',
                'font-family': 'Helvetica',
                'font-size': '12'
            }

            self.styles[self.level] = style

            # iterate children
            for child in node:
                self.render(child, self.mainGroup)

            # create drawing
            width = node.get('width', '100%')
            height = node.get('height', '100%')

            if node.get("viewBox"):
                try:
                    minx, miny, width, height = node.get("viewBox").split()
                except ValueError:
                    raise SVGError("viewBox values not valid")

            if width.endswith('%') and height.endswith('%'):
                # handle relative size
                wscale = parseLength(width) / 100.
                hscale = parseLength(height) / 100.

                xL, yL, xH, yH = self.mainGroup.getBounds()
                self.drawing = Drawing(xH * wscale + xL, yH * hscale + yL)

            else:
                self.drawing = Drawing(parseLength(width), parseLength(height))

            height = self.drawing.height
            self.mainGroup.scale(1, -1)
            self.mainGroup.translate(0, -height)
            self.drawing.add(self.mainGroup)

            self.level -= 1

            return self.drawing

        elif node.tag in (self.SVG_G, self.SVG_A):
            self.level += 1

            # set this levels style
            style = self.styles[self.level - 1].copy()
            style = self.nodeStyle(node, style)
            self.styles[self.level] = style

            group = Group()

            # iterate children
            for child in node:
                self.render(child, group)

            parent.add(group)

            transforms = node.get('transform')
            if transforms:
                for op in parseTransform.iterparse(transforms):
                    self.applyTransformOnGroup(group, op)

            self.level -= 1

        elif node.tag == self.SVG_USE:
            self.level += 1

            # set this levels style
            style = self.styles[self.level - 1].copy()
            style = self.nodeStyle(node, style)
            self.styles[self.level] = style

            group = Group()

            # link id
            link_id = node.get(self.LINK).lstrip('#')

            # find linked node in defs or symbol section
            target = None
            for defs in self.root.getiterator(self.SVG_DEFS):
                for element in defs:
                    if element.get('id') == link_id:
                        target = element
                        break

            if target is None:
                for defs in self.root.getiterator(self.SVG_SYMBOL):
                    for element in defs:
                        if element.get('id') == link_id:
                            target = element
                            break

                if target is None:
                    msg = "Could not find use node '%s'" % link_id
                    raise SVGError(msg)

            self.render(target, group)

            parent.add(group)

            # apply transform
            transforms = node.get('transform')
            if transforms:
                for op in parseTransform.iterparse(transforms):
                    self.applyTransformOnGroup(group, op)

            # apply 'x' and 'y' attribute as translation of defs object
            if node.get('x') or node.get('y'):
                dx = parseLength(node.get('x', '0'))
                dy = parseLength(node.get('y', '0'))

                self.applyTransformOnGroup(group, ('translate', (dx, dy)))

            self.level -= 1

        elif node.tag == self.SVG_LINE:
            # get coordinates
            x1 = parseLength(node.get('x1', '0'))
            y1 = parseLength(node.get('y1', '0'))
            x2 = parseLength(node.get('x2', '0'))
            y2 = parseLength(node.get('y2', '0'))

            shape = Line(x1, y1, x2, y2)
            self.addShape(parent, node, shape)

        elif node.tag == self.SVG_RECT:
            # get coordinates
            x = parseLength(node.get('x', '0'))
            y = parseLength(node.get('y', '0'))
            width = parseLength(node.get('width'))
            height = parseLength(node.get('height'))

            rx = parseLength(node.get('rx', '0'))
            ry = parseLength(node.get('ry', '0'))

            shape = Rect(x, y, width, height, rx=rx, ry=ry)
            self.addShape(parent, node, shape)

        elif node.tag == self.SVG_CIRCLE:
            cx = parseLength(node.get('cx', '0'))
            cy = parseLength(node.get('cy', '0'))
            r = parseLength(node.get('r'))

            if r > 0.:
                shape = Circle(cx, cy, r)
                self.addShape(parent, node, shape)

        elif node.tag == self.SVG_ELLIPSE:
            cx = parseLength(node.get('cx', '0'))
            cy = parseLength(node.get('cy', '0'))
            rx = parseLength(node.get('rx'))
            ry = parseLength(node.get('ry'))

            if rx > 0. and ry > 0.:
                shape = Ellipse(cx, cy, rx, ry)
                self.addShape(parent, node, shape)

        elif node.tag == self.SVG_POLYLINE:
            # convert points
            points = node.get('points').strip()
            if len(points) == 0:
                return

            points = list(map(parseLength, re.split('[ ,]+', points)))

            # Need to use two shapes, because standard RLG polylines
            # do not support filling...
            group = Group()
            shape = Polygon(points)
            self.applyStyleToShape(shape, node)
            shape.strokeColor = None
            group.add(shape)

            shape = PolyLine(points)
            self.applyStyleToShape(shape, node)
            group.add(shape)

            self.addShape(parent, node, group)

        elif node.tag == self.SVG_POLYGON:
            # convert points
            points = node.get('points').strip()
            if len(points) == 0:
                return

            points = list(map(parseLength, re.split('[ ,]+', points)))

            shape = Polygon(points)
            self.addShape(parent, node, shape)

        elif node.tag == self.SVG_IMAGE:
            x = parseLength(node.get('x', '0'))
            y = parseLength(node.get('y', '0'))
            width = parseLength(node.get('width', '0'))
            height = parseLength(node.get('height', '0'))

            # link id
            link_id = node.get(self.LINK)

            filename = os.path.join(os.path.dirname(self.filename), link_id)
            shape = Image(x, y, width, height, filename)

            self.addShape(parent, node, shape)

        elif node.tag == self.SVG_TEXT:
            # Todo:
            # - rotation not handled
            # - baseshift not handled
            # - embedded span node not handled
            #
            def parsePos(node, subnode, name, default='0'):
                values = subnode.get(name)
                if values is None:
                    if node is not None:
                        values = node.get(name, default)
                    else:
                        values = default

                return list(map(parseLength, values.split()))

            def getPos(values, i, default=None):
                if i >= len(values):
                    if default is None:
                        return values[-1]
                    else:
                        return default
                else:
                    return values[i]

            def handleText(node, subnode, text):
                # get position variables
                xs = parsePos(node, subnode, 'x')
                dxs = parsePos(node, subnode, 'dx')
                ys = parsePos(node, subnode, 'y')
                dys = parsePos(node, subnode, 'dy')

                if sum(map(len, (xs, ys, dxs, dys))) == 4:
                    # single value
                    shape = String(xs[0] + dxs[0], -ys[0] - dys[0], text)
                    self.applyStyleToShape(shape, subnode)
                    group.add(shape)

                else:
                    # multiple values
                    for i, c in enumerate(text):
                        x = getPos(xs, i)
                        dx = getPos(dxs, i, 0)
                        y = getPos(ys, i)
                        dy = getPos(dys, i, 0)

                        shape = String(x + dx, -y - dy, c)
                        self.applyStyleToShape(shape, subnode)
                        group.add(shape)

            if node.text and node.text.strip():
                group = Group()

                handleText(None, node, node.text.strip())

                group.scale(1, -1)

                self.addShape(parent, node, group)

            if len(node) > 0:
                group = Group()

                self.level += 1

                # set this levels style
                style = self.styles[self.level - 1].copy()
                nodestylestyle = self.nodeStyle(node, style)
                self.styles[self.level] = nodestylestyle

                for subnode in node:
                    if subnode.tag == self.SVG_TSPAN:
                        handleText(node, subnode, subnode.text.strip())

                self.level -= 1

                group.scale(1, -1)
                self.addShape(parent, node, group)

        elif node.tag == self.SVG_PATH:

            def convertQuadratic(Q0, Q1, Q2):
                C1 = (Q0[0] + 2. / 3 * (Q1[0] - Q0[0]),
                      Q0[1] + 2. / 3 * (Q1[1] - Q0[1]))
                C2 = (C1[0] + 1. / 3 * (Q2[0] - Q0[0]),
                      C1[1] + 1. / 3 * (Q2[1] - Q0[1]))
                C3 = Q2
                return C1[0], C1[1], C2[0], C2[1], C3[0], C3[1]

            def prevCtrl(lastOp, lastArgs, currentX, currentY):
                # fetch last controll point
                if lastOp in 'CScsQqTt':
                    x, y = lastArgs[-2]

                    # mirror about current point
                    return currentX + (currentX - x), currentY + (currentY - y)

                else:
                    # defaults to current point
                    return currentX, currentY

            # store sub paths in 'paths' list
            shape = Path()

            # keep track of current point and path start point
            startX, startY = 0., 0.
            currentX, currentY = 0., 0.

            # keep track of last operation
            lastOp = None
            lastArgs = None

            # avoid empty path data
            data = node.get('d')
            if data is None or len(data) == 0:
                return

            for op, args in parsePath.iterparse(data):
                if op == 'z' or op == 'Z':
                    # close path or subpath
                    shape.closePath()

                    # next sub path starts at begining of current path
                    currentX, currentY = startX, startY

                elif op == 'M':
                    # moveto absolute
                    if lastOp is not None and lastOp not in ('z', 'Z'):
                        # close sub path
                        shape.closePath()

                    x, y = args[0]
                    shape.moveTo(x, y)

                    startX, startY = x, y

                    # multiple moveto arge result in line
                    for x, y in args[1:]:
                        shape.lineTo(x, y)

                    currentX, currentY = x, y

                elif op == 'm':
                    if lastOp is not None and lastOp not in ('z', 'Z'):
                        # close sub path
                        shape.closePath()

                    # moveto relative
                    rx, ry = args[0]
                    x, y = currentX + rx, currentY + ry
                    shape.moveTo(x, y)

                    startX, startY = x, y
                    currentX, currentY = x, y

                    # multiple moveto arge result in line
                    for rx, ry in args[1:]:
                        x, y = currentX + rx, currentY + ry
                        shape.lineTo(x, y)
                        currentX, currentY = x, y

                elif op == 'L':
                    # lineto absolute
                    for x, y in args:
                        shape.lineTo(x, y)

                    currentX, currentY = x, y

                elif op == 'l':
                    # lineto relative
                    for rx, ry in args:
                        x, y = currentX + rx, currentY + ry
                        shape.lineTo(x, y)
                        currentX, currentY = x, y

                elif op == 'V':
                    # vertical line absolute
                    for y in args:
                        shape.lineTo(currentX, y)

                    currentY = y

                elif op == 'v':
                    # vertical line relative
                    for ry in args:
                        y = currentY + ry
                        shape.lineTo(currentX, y)
                        currentY = y

                elif op == 'H':
                    # horisontal line absolute
                    for x in args:
                        shape.lineTo(x, currentY)
                    currentX = x

                elif op == 'h':
                    # horisontal line relative
                    for rx in args:
                        x = currentX + rx
                        shape.lineTo(x, currentY)
                    currentX = x

                elif op == 'C':
                    # cubic bezier absolute
                    for p1, p2, p3 in zip(*([iter(args)] * 3)):
                        shape.curveTo(*(p1 + p2 + p3))
                        currentX, currentY = p3

                elif op == 'c':
                    # cubic bezier relative
                    for pnts in zip(*([iter(args)] * 3)):
                        (x1, y1), (x2, y2), (x3, y3) = pnts
                        pnts = tuple(
                            (p[0] + currentX, p[1] + currentY) for p in pnts)
                        shape.curveTo(*(pnts[0] + pnts[1] + pnts[2]))
                        currentX, currentY = pnts[2]
                        lastOp = op
                        lastArgs = pnts
                    continue

                elif op == 'S':
                    # shorthand cubic bezier absolute
                    for p2, p3 in zip(*([iter(args)] * 2)):
                        x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY)
                        x2, y2 = p2
                        x3, y3 = p3
                        shape.curveTo(x1, y1, x2, y2, x3, y3)
                        lastOp = op
                        lastArgs = (x2, y2), (x3, y3)
                        currentX, currentY = x3, y3
                    continue

                elif op == 's':
                    # shorthand cubic bezier relative
                    for p2, p3 in zip(*([iter(args)] * 2)):
                        x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY)
                        x2, y2 = p2
                        x2, y2 = x2 + currentX, y2 + currentY
                        x3, y3 = p3
                        x3, y3 = x3 + currentX, y3 + currentY
                        shape.curveTo(x1, y1, x2, y2, x3, y3)
                        currentX, currentY = x3, y3
                        lastOp = op
                        lastArgs = (x1, y1), (x2, y2), (x3, y3)
                    continue

                elif op == 'Q':
                    # quadratic bezier absolute
                    for p2, p3 in zip(*([iter(args)] * 2)):
                        x1, y1 = currentX, currentY
                        x2, y2 = p2
                        x3, y3 = p3
                        ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3))
                        shape.curveTo(*ctrls)
                        currentX, currentY = x3, y3
                    lastOp = op
                    lastArgs = (x2, y2), (x3, y3)
                    continue

                elif op == 'q':
                    # quadratic bezier relative
                    for p2, p3 in zip(*([iter(args)] * 2)):
                        x1, y1 = currentX, currentY
                        x2, y2 = p2
                        x2, y2 = x2 + currentX, y2 + currentY
                        x3, y3 = p3
                        x3, y3 = x3 + currentX, y3 + currentY
                        ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3))
                        shape.curveTo(*ctrls)
                        currentX, currentY = x3, y3

                    lastOp = op
                    lastArgs = (x2, y2), (x3, y3)
                    continue

                elif op == 'T':
                    # shorthand quadratic bezier absolute
                    for i in range(len(args)):
                        x1, y1 = currentX, currentY
                        x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY)
                        x3, y3 = args[i]
                        ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3))
                        shape.curveTo(*ctrls)
                        currentX, currentY = x3, y3
                        lastOp = op
                        lastArgs = (x2, y2), (x3, y3)

                    continue

                elif op == 't':
                    # shorthand quadratic bezier relative
                    for i in range(len(args)):
                        x1, y1 = currentX, currentY
                        x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY)
                        x3, y3 = args[i]
                        x3, y3 = x3 + currentX, y3 + currentY
                        ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3))
                        shape.curveTo(*ctrls)
                        currentX, currentY = x3, y3
                        lastOp = op
                        lastArgs = (x2, y2), (x3, y3)

                    continue

                elif op == 'A' or op == 'a':
                    # elliptic arc missing
                    continue

                lastOp = op
                lastArgs = args

            # check if fill applies to path
            fill = None
            if node.get('fill'):
                # inline style
                fill = node.get('fill')
            else:
                # try local style
                if node.get('style'):
                    style = parseStyle.parse(node.get('style'))

                    if 'fill' in style:
                        fill = style['fill']

                # try global style
                if fill is None:
                    style = self.styles[self.level]

                    if 'fill' in style:
                        fill = style['fill']
                    else:
                        fill = 'none'

            # hack because RLG has no "semi-closed" paths...
            if lastOp == 'z' or lastOp == 'Z' or fill == 'none':
                self.addShape(parent, node, shape)

            else:
                group = Group()
                strokeshape = shape.copy()
                self.addShape(group, node, strokeshape, fill='none')
                shape.closePath()
                self.addShape(group, node, shape, stroke='none')
                self.addShape(parent, node, group)

        elif node.tag == self.SVG_TITLE or node.tag == self.SVG_DESC:
            # Skip non-graphics elements
            return