Ejemplo n.º 1
0
class _LinkIcon(QGraphicsEllipseItem):
    """An icon to show over a Link."""
    def __init__(self, x, y, w, h, parent):
        super().__init__(x, y, w, h, parent)
        self._parent = parent
        color = QColor("slateblue")
        self.setBrush(qApp.palette().window())  # pylint: disable=undefined-variable
        self._text_item = QGraphicsTextItem(self)
        font = QFont('Font Awesome 5 Free Solid')
        self._text_item.setFont(font)
        self._text_item.setDefaultTextColor(color)
        self._svg_item = QGraphicsSvgItem(self)
        self._datapkg_renderer = QSvgRenderer()
        self._datapkg_renderer.load(":/icons/datapkg.svg")
        self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=False)
        self._block_updates = False

    def update_icon(self):
        """Sets the icon (filter, datapkg, or none), depending on Connection state."""
        connection = self._parent.connection
        if connection.use_datapackage:
            self.setVisible(True)
            self._svg_item.setVisible(True)
            self._svg_item.setSharedRenderer(self._datapkg_renderer)
            scale = 0.8 * self.rect().width(
            ) / self._datapkg_renderer.defaultSize().width()
            self._svg_item.setScale(scale)
            self._svg_item.setPos(0, 0)
            self._svg_item.setPos(self.sceneBoundingRect().center() -
                                  self._svg_item.sceneBoundingRect().center())
            self._text_item.setVisible(False)
            return
        if connection.has_filters():
            self.setVisible(True)
            self._text_item.setVisible(True)
            self._text_item.setPlainText("\uf0b0")
            self._svg_item.setPos(0, 0)
            self._text_item.setPos(
                self.sceneBoundingRect().center() -
                self._text_item.sceneBoundingRect().center())
            self._svg_item.setVisible(False)
            return
        self.setVisible(False)
        self._text_item.setVisible(False)
        self._svg_item.setVisible(False)
Ejemplo n.º 2
0
class ExclamationIcon(QGraphicsSvgItem):
    def __init__(self, parent):
        """Exclamation icon graphics item.
        Used to notify that a ProjectItem is missing some configuration.

        Args:
            parent (ProjectItemIcon): the parent item
        """
        super().__init__(parent)
        self._parent = parent
        self._notifications = list()
        self.renderer = QSvgRenderer()
        self.colorizer = QGraphicsColorizeEffect()
        self.colorizer.setColor(QColor("red"))
        # Load SVG
        loading_ok = self.renderer.load(
            ":/icons/project_item_icons/exclamation-circle.svg")
        if not loading_ok:
            return
        size = self.renderer.defaultSize()
        self.setSharedRenderer(self.renderer)
        dim_max = max(size.width(), size.height())
        rect_w = parent.rect().width()  # Parent rect width
        self.setScale(0.2 * rect_w / dim_max)
        self.setGraphicsEffect(self.colorizer)
        self.setAcceptHoverEvents(True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=False)
        self.hide()

    def clear_notifications(self):
        """Clear all notifications."""
        self._notifications.clear()
        self.hide()

    def add_notification(self, text):
        """Add a notification."""
        self._notifications.append(text)
        self.show()

    def hoverEnterEvent(self, event):
        """Shows notifications as tool tip.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        if not self._notifications:
            return
        tip = "<p>" + "<p>".join(self._notifications)
        QToolTip.showText(event.screenPos(), tip)

    def hoverLeaveEvent(self, event):
        """Hides tool tip.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        QToolTip.hideText()
Ejemplo n.º 3
0
class ProjectItemIcon(QGraphicsRectItem):
    """Base class for project item icons drawn in Design View."""

    ITEM_EXTENT = 64

    def __init__(self, toolbox, icon_file, icon_color, background_color):
        """
        Args:
            toolbox (ToolboxUI): QMainWindow instance
            icon_file (str): Path to icon resource
            icon_color (QColor): Icon's color
            background_color (QColor): Background color
        """
        super().__init__()
        self._toolbox = toolbox
        self.icon_file = icon_file
        self._moved_on_scene = False
        self.previous_pos = QPointF()
        self.current_pos = QPointF()
        self.icon_group = {self}
        self.renderer = QSvgRenderer()
        self.svg_item = QGraphicsSvgItem(self)
        self.colorizer = QGraphicsColorizeEffect()
        self.setRect(
            QRectF(-self.ITEM_EXTENT / 2, -self.ITEM_EXTENT / 2,
                   self.ITEM_EXTENT, self.ITEM_EXTENT))
        self.text_font_size = 10  # point size
        # Make item name graphics item.
        self._name = ""
        self.name_item = QGraphicsSimpleTextItem(self._name, self)
        self.set_name_attributes()  # Set font, size, position, etc.
        # Make connector buttons
        self.connectors = dict(
            bottom=ConnectorButton(self, toolbox, position="bottom"),
            left=ConnectorButton(self, toolbox, position="left"),
            right=ConnectorButton(self, toolbox, position="right"),
        )
        # Make exclamation and rank icons
        self.exclamation_icon = ExclamationIcon(self)
        self.execution_icon = ExecutionIcon(self)
        self.rank_icon = RankIcon(self)
        brush = QBrush(background_color)
        self._setup(brush, icon_file, icon_color)
        shadow_effect = QGraphicsDropShadowEffect()
        shadow_effect.setOffset(1)
        shadow_effect.setEnabled(False)
        self.setGraphicsEffect(shadow_effect)

    def finalize(self, name, x, y):
        """
        Names the icon and moves it by given amount.

        Args:
            name (str): icon's name
            x (int): horizontal offset
            y (int): vertical offset
        """
        self.update_name_item(name)
        self.moveBy(x, y)

    def _setup(self, brush, svg, svg_color):
        """Setup item's attributes.

        Args:
            brush (QBrush): Used in filling the background rectangle
            svg (str): Path to SVG icon file
            svg_color (QColor): Color of SVG icon
        """
        self.setPen(QPen(Qt.black, 1, Qt.SolidLine))
        self.setBrush(brush)
        self.colorizer.setColor(svg_color)
        # Load SVG
        loading_ok = self.renderer.load(svg)
        if not loading_ok:
            self._toolbox.msg_error.emit(
                "Loading SVG icon from resource:{0} failed".format(svg))
            return
        size = self.renderer.defaultSize()
        self.svg_item.setSharedRenderer(self.renderer)
        self.svg_item.setElementId(
            "")  # guess empty string loads the whole file
        dim_max = max(size.width(), size.height())
        rect_w = self.rect().width()  # Parent rect width
        margin = 32
        self.svg_item.setScale((rect_w - margin) / dim_max)
        self.svg_item.setPos(self.rect().center() -
                             self.svg_item.sceneBoundingRect().center())
        self.svg_item.setGraphicsEffect(self.colorizer)
        self.setFlag(QGraphicsItem.ItemIsMovable, enabled=True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, enabled=True)
        self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, enabled=True)
        self.setAcceptHoverEvents(True)
        self.setCursor(Qt.PointingHandCursor)
        # Set exclamation, execution_log, and rank icons position
        self.exclamation_icon.setPos(
            self.rect().topRight() -
            self.exclamation_icon.sceneBoundingRect().topRight())
        self.execution_icon.setPos(
            self.rect().bottomRight() -
            0.5 * self.execution_icon.sceneBoundingRect().bottomRight())
        self.rank_icon.setPos(self.rect().topLeft())

    def name(self):
        """Returns name of the item that is represented by this icon.

        Returns:
            str: icon's name
        """
        return self._name

    def update_name_item(self, new_name):
        """Set a new text to name item.

        Args:
            new_name (str): icon's name
        """
        self._name = new_name
        self.name_item.setText(new_name)
        self.set_name_attributes()

    def set_name_attributes(self):
        """Set name QGraphicsSimpleTextItem attributes (font, size, position, etc.)"""
        # Set font size and style
        font = self.name_item.font()
        font.setPointSize(self.text_font_size)
        font.setBold(True)
        self.name_item.setFont(font)
        # Set name item position (centered on top of the master icon)
        name_width = self.name_item.boundingRect().width()
        name_height = self.name_item.boundingRect().height()
        self.name_item.setPos(
            self.rect().x() + self.rect().width() / 2 - name_width / 2,
            self.rect().y() - name_height - 4)

    def conn_button(self, position="left"):
        """Returns item's connector button.

        Args:
            position (str): "left", "right" or "bottom"

        Returns:
            QWidget: connector button
        """
        return self.connectors.get(position, self.connectors["left"])

    def outgoing_links(self):
        """Collects outgoing links.

        Returns:
            list of LinkBase: outgoing links
        """
        return [
            l for conn in self.connectors.values()
            for l in conn.outgoing_links()
        ]

    def incoming_links(self):
        """Collects incoming links.

        Returns:
            list of LinkBase: outgoing links
        """
        return [
            l for conn in self.connectors.values()
            for l in conn.incoming_links()
        ]

    def run_execution_leave_animation(self, skipped):
        """
        Starts the animation associated with execution leaving the icon.

        Args:
            skipped (bool): True if project item was not actually executed.
        """
        animation_group = QParallelAnimationGroup(self._toolbox)
        for link in self.outgoing_links():
            animation_group.addAnimation(
                link.make_execution_animation(skipped))
        animation_group.start()

    def hoverEnterEvent(self, event):
        """Sets a drop shadow effect to icon when mouse enters its boundaries.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        self.prepareGeometryChange()
        self.graphicsEffect().setEnabled(True)
        event.accept()

    def hoverLeaveEvent(self, event):
        """Disables the drop shadow when mouse leaves icon boundaries.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        self.prepareGeometryChange()
        self.graphicsEffect().setEnabled(False)
        event.accept()

    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        self.icon_group = set(x for x in self.scene().selectedItems()
                              if isinstance(x, ProjectItemIcon)) | {self}
        for icon in self.icon_group:
            icon.previous_pos = icon.scenePos()

    def mouseMoveEvent(self, event):
        """Moves icon(s) while the mouse button is pressed.
        Update links that are connected to selected icons.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        super().mouseMoveEvent(event)
        self.update_links_geometry()

    def moveBy(self, dx, dy):
        super().moveBy(dx, dy)
        self.update_links_geometry()

    def update_links_geometry(self):
        """Updates geometry of connected links to reflect this item's most recent position."""
        links = set(link for icon in self.icon_group
                    for conn in icon.connectors.values()
                    for link in conn.links)
        for link in links:
            link.update_geometry()

    def mouseReleaseEvent(self, event):
        for icon in self.icon_group:
            icon.current_pos = icon.scenePos()
        # pylint: disable=undefined-variable
        if (self.current_pos - self.previous_pos
            ).manhattanLength() > qApp.startDragDistance():
            self._toolbox.undo_stack.push(
                MoveIconCommand(self, self._toolbox.project()))
        super().mouseReleaseEvent(event)

    def notify_item_move(self):
        if self._moved_on_scene:
            self._moved_on_scene = False
            scene = self.scene()
            scene.item_move_finished.emit(self)

    def contextMenuEvent(self, event):
        """Show item context menu.

        Args:
            event (QGraphicsSceneMouseEvent): Mouse event
        """
        event.accept()
        self.scene().clearSelection()
        self.setSelected(True)
        ind = self._toolbox.project_item_model.find_item(self.name())
        self._toolbox.show_project_item_context_menu(event.screenPos(), ind)

    def itemChange(self, change, value):
        """
        Reacts to item removal and position changes.

        In particular, destroys the drop shadow effect when the items is removed from a scene
        and keeps track of item's movements on the scene.

        Args:
            change (GraphicsItemChange): a flag signalling the type of the change
            value: a value related to the change

        Returns:
             Whatever super() does with the value parameter
        """
        if change == QGraphicsItem.ItemScenePositionHasChanged:
            self._moved_on_scene = True
        elif change == QGraphicsItem.GraphicsItemChange.ItemSceneChange and value is None:
            self.prepareGeometryChange()
            self.setGraphicsEffect(None)
        return super().itemChange(change, value)

    def select_item(self):
        """Update GUI to show the details of the selected item."""
        ind = self._toolbox.project_item_model.find_item(self.name())
        self._toolbox.ui.treeView_project.setCurrentIndex(ind)
Ejemplo n.º 4
0
class ProjectItemIcon(QGraphicsRectItem):

    ITEM_EXTENT = 64

    def __init__(self, toolbox, x, y, project_item, icon_file, icon_color,
                 background_color):
        """Base class for project item icons drawn in Design View.

        Args:
            toolbox (ToolBoxUI): QMainWindow instance
            x (float): Icon x coordinate
            y (float): Icon y coordinate
            project_item (ProjectItem): Item
            icon_file (str): Path to icon resource
            icon_color (QColor): Icon's color
            background_color (QColor): Background color
        """
        super().__init__()
        self._toolbox = toolbox
        self._project_item = project_item
        self._moved_on_scene = False
        self._previous_pos = QPointF()
        self._current_pos = QPointF()
        self.icon_group = {self}
        self.renderer = QSvgRenderer()
        self.svg_item = QGraphicsSvgItem(self)
        self.colorizer = QGraphicsColorizeEffect()
        self.setRect(
            QRectF(x - self.ITEM_EXTENT / 2, y - self.ITEM_EXTENT / 2,
                   self.ITEM_EXTENT, self.ITEM_EXTENT))
        self.text_font_size = 10  # point size
        # Make item name graphics item.
        name = project_item.name if project_item else ""
        self.name_item = QGraphicsSimpleTextItem(name, self)
        self.set_name_attributes()  # Set font, size, position, etc.
        # Make connector buttons
        self.connectors = dict(
            bottom=ConnectorButton(self, toolbox, position="bottom"),
            left=ConnectorButton(self, toolbox, position="left"),
            right=ConnectorButton(self, toolbox, position="right"),
        )
        # Make exclamation and rank icons
        self.exclamation_icon = ExclamationIcon(self)
        self.rank_icon = RankIcon(self)
        brush = QBrush(background_color)
        self._setup(brush, icon_file, icon_color)
        self.activate()

    def activate(self):
        """Adds items to scene and setup graphics effect.
        Called in the constructor and when re-adding the item to the project in the context of undo/redo.
        """
        scene = self._toolbox.ui.graphicsView.scene()
        scene.addItem(self)
        shadow_effect = QGraphicsDropShadowEffect()
        shadow_effect.setOffset(1)
        shadow_effect.setEnabled(False)
        self.setGraphicsEffect(shadow_effect)

    def _setup(self, brush, svg, svg_color):
        """Setup item's attributes.

        Args:
            brush (QBrush): Used in filling the background rectangle
            svg (str): Path to SVG icon file
            svg_color (QColor): Color of SVG icon
        """
        self.setPen(QPen(Qt.black, 1, Qt.SolidLine))
        self.setBrush(brush)
        self.colorizer.setColor(svg_color)
        # Load SVG
        loading_ok = self.renderer.load(svg)
        if not loading_ok:
            self._toolbox.msg_error.emit(
                "Loading SVG icon from resource:{0} failed".format(svg))
            return
        size = self.renderer.defaultSize()
        self.svg_item.setSharedRenderer(self.renderer)
        self.svg_item.setElementId(
            "")  # guess empty string loads the whole file
        dim_max = max(size.width(), size.height())
        rect_w = self.rect().width()  # Parent rect width
        margin = 32
        self.svg_item.setScale((rect_w - margin) / dim_max)
        x_offset = (rect_w - self.svg_item.sceneBoundingRect().width()) / 2
        y_offset = (rect_w - self.svg_item.sceneBoundingRect().height()) / 2
        self.svg_item.setPos(self.rect().x() + x_offset,
                             self.rect().y() + y_offset)
        self.svg_item.setGraphicsEffect(self.colorizer)
        self.setFlag(QGraphicsItem.ItemIsMovable, enabled=True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, enabled=True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, enabled=True)
        self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, enabled=True)
        self.setAcceptHoverEvents(True)
        self.setCursor(Qt.PointingHandCursor)
        # Set exclamation and rank icons position
        self.exclamation_icon.setPos(
            self.rect().topRight() -
            self.exclamation_icon.sceneBoundingRect().topRight())
        self.rank_icon.setPos(self.rect().topLeft())

    def name(self):
        """Returns name of the item that is represented by this icon."""
        return self._project_item.name

    def update_name_item(self, new_name):
        """Set a new text to name item. Used when a project item is renamed."""
        self.name_item.setText(new_name)
        self.set_name_attributes()

    def set_name_attributes(self):
        """Set name QGraphicsSimpleTextItem attributes (font, size, position, etc.)"""
        # Set font size and style
        font = self.name_item.font()
        font.setPointSize(self.text_font_size)
        font.setBold(True)
        self.name_item.setFont(font)
        # Set name item position (centered on top of the master icon)
        name_width = self.name_item.boundingRect().width()
        name_height = self.name_item.boundingRect().height()
        self.name_item.setPos(
            self.rect().x() + self.rect().width() / 2 - name_width / 2,
            self.rect().y() - name_height - 4)

    def conn_button(self, position="left"):
        """Returns items connector button (QWidget)."""
        return self.connectors.get(position, self.connectors["left"])

    def outgoing_links(self):
        return [
            l for conn in self.connectors.values()
            for l in conn.outgoing_links()
        ]

    def incoming_links(self):
        return [
            l for conn in self.connectors.values()
            for l in conn.incoming_links()
        ]

    def hoverEnterEvent(self, event):
        """Sets a drop shadow effect to icon when mouse enters its boundaries.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        self.prepareGeometryChange()
        self.graphicsEffect().setEnabled(True)
        event.accept()

    def hoverLeaveEvent(self, event):
        """Disables the drop shadow when mouse leaves icon boundaries.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        self.prepareGeometryChange()
        self.graphicsEffect().setEnabled(False)
        event.accept()

    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        self.icon_group = set(x for x in self.scene().selectedItems()
                              if isinstance(x, ProjectItemIcon)) | {self}
        for icon in self.icon_group:
            icon._previous_pos = icon.scenePos()

    def mouseMoveEvent(self, event):
        """Moves icon(s) while the mouse button is pressed.
        Update links that are connected to selected icons.

        Args:
            event (QGraphicsSceneMouseEvent): Event
        """
        super().mouseMoveEvent(event)
        self.update_links_geometry()

    def moveBy(self, dx, dy):
        super().moveBy(dx, dy)
        self.update_links_geometry()

    def update_links_geometry(self):
        """Updates geometry of connected links to reflect this item's most recent position."""
        links = set(link for icon in self.icon_group
                    for conn in icon.connectors.values()
                    for link in conn.links)
        for link in links:
            link.update_geometry()

    def mouseReleaseEvent(self, event):
        for icon in self.icon_group:
            icon._current_pos = icon.scenePos()
        # pylint: disable=undefined-variable
        if (self._current_pos - self._previous_pos
            ).manhattanLength() > qApp.startDragDistance():
            self._toolbox.undo_stack.push(MoveIconCommand(self))
        super().mouseReleaseEvent(event)

    def notify_item_move(self):
        if self._moved_on_scene:
            self._moved_on_scene = False
            scene = self.scene()
            scene.item_move_finished.emit(self)

    def contextMenuEvent(self, event):
        """Show item context menu.

        Args:
            event (QGraphicsSceneMouseEvent): Mouse event
        """
        self.scene().clearSelection()
        self.setSelected(True)
        self._toolbox.show_item_image_context_menu(event.screenPos(),
                                                   self.name())

    def keyPressEvent(self, event):
        """Handles deleting and rotating the selected
        item when dedicated keys are pressed.

        Args:
            event (QKeyEvent): Key event
        """
        if event.key() == Qt.Key_Delete and self.isSelected():
            self._project_item._project.remove_item(self.name())
            event.accept()
        elif event.key() == Qt.Key_R and self.isSelected():
            # TODO:
            # 1. Change name item text direction when rotating
            # 2. Save rotation into project file
            rect = self.mapToScene(self.boundingRect()).boundingRect()
            center = rect.center()
            t = QTransform()
            t.translate(center.x(), center.y())
            t.rotate(90)
            t.translate(-center.x(), -center.y())
            self.setPos(t.map(self.pos()))
            self.setRotation(self.rotation() + 90)
            links = set(lnk for conn in self.connectors.values()
                        for lnk in conn.links)
            for link in links:
                link.update_geometry()
            event.accept()
        else:
            super().keyPressEvent(event)

    def itemChange(self, change, value):
        """
        Reacts to item removal and position changes.

        In particular, destroys the drop shadow effect when the items is removed from a scene
        and keeps track of item's movements on the scene.

        Args:
            change (GraphicsItemChange): a flag signalling the type of the change
            value: a value related to the change

        Returns:
             Whatever super() does with the value parameter
        """
        if change == QGraphicsItem.ItemScenePositionHasChanged:
            self._moved_on_scene = True
        elif change == QGraphicsItem.GraphicsItemChange.ItemSceneChange and value is None:
            self.prepareGeometryChange()
            self.setGraphicsEffect(None)
        return super().itemChange(change, value)

    def show_item_info(self):
        """Update GUI to show the details of the selected item."""
        ind = self._toolbox.project_item_model.find_item(self.name())
        self._toolbox.ui.treeView_project.setCurrentIndex(ind)
Ejemplo n.º 5
0
def svg_to_outlines(root, width_mm=None, height_mm=None, pixels_per_mm=5.0):
    """
    Given an SVG as a Python ElementTree, return a set of straight line
    segments which approximate the outlines in that SVG when rendered.

    Occlusion is not accounted for in the returned list of outlines. Even if
    one shape is completely occluded by another, both of their outlines will be
    reported. Simillarly, overlapping lines will also be passed through.

    .. note::

        This function internally uses the QSvg library from Qt to render the
        provided SVG to a special painting backend which captures the output
        lines. As a consequence, SVG feature support is as good or as bad as
        that library. QSvg is generally regarded as a high quality and
        relatively complete SVG implementation and so most SVG features should
        be supported.

    .. note::

        Due to its internal use of Qt, a PySide2.QtGui.QGuiApplication will be
        created if one has not already been created. Non-Qt users and most Qt
        users should not be affected by this.

    Parameters
    ----------
    root : ElementTree
        The SVG whose outlines should be extracted.
    width_mm, height_mm : float or None
        The page size to render the SVG at (in milimeters). If omitted, this
        will be determined automatically from the SVG's width and height
        attributes. These arguments are mandatory for SVGs lacking these
        attributes.
    pixels_per_mm : float
        Curved outlines will be approximated by straight lines in the output.
        This parameter controls how exactly curves will be approximated.
        Specifically, the curve approximation will be at least fine enough for
        rasterised versions of the lines to 'look right' at the specified pixel
        density.

    Returns
    -------
    [((r, g, b, a) or None, width, [(x, y), ...]), ...]
        A list of polylines described by (colour, line) pairs.

        The 'colour' values define the colour used to draw the line (if a solid
        colour was used) or None (if a gradient or patterned stroke was used).
        Colours are given as four-tuples in RGBA order with values from 0.0 to
        1.0.

        The 'width' value gives the line width used to draw the line (given in
        mm). If the shape being drawn has a non-uniform scaling applied, this
        value may not be meaningful and its actual value should be considered
        undefined.

        The 'line' part of each tuple defines the outline as a series of (x, y)
        coordinates (given in mm) which describe a continuous polyline. These
        polylines may be considered open. Closed lines in the input SVG will
        result in polylines where the first and last coordinate are identical.
        Lines may go beyond the bounds of the designated page size (as in the
        input SVG).
    """
    # This method internally uses various parts of Qt which require that a Qt
    # application exists. If one does not exist, one will be created.
    if QGuiApplication.instance() is None:
        QGuiApplication()

    # Determine the page size from the document if necessary
    if width_mm is None or height_mm is None:
        width_mm, height_mm = get_svg_page_size(root)

    # Load the SVG into QSvg
    xml_stream_reader = QXmlStreamReader()
    xml_stream_reader.addData(ElementTree.tostring(root, "unicode"))
    svg_renderer = QSvgRenderer()
    svg_renderer.load(xml_stream_reader)

    # Paint the SVG into the OutlinePaintDevice which will capture the set of
    # line segments which make up the SVG as rendered.
    outline_paint_device = OutlinePaintDevice(width_mm, height_mm,
                                              pixels_per_mm)
    painter = QPainter(outline_paint_device)
    try:
        svg_renderer.render(painter)
    finally:
        painter.end()

    return outline_paint_device.getOutlines()