6.0.0-rc4Source

Using Nt SVG

How to draw SVG files, show them in a widget, and write what a painter draws as SVG.

Nt SVG reads Scalable Vector Graphics and draws them with a QPainter, and writes them from one. It has two libraries: Nt::Svg, with QSvgRenderer and QSvgGenerator, needs only Nt GUI; Nt::SvgWidgets adds QSvgWidget for applications that use Nt Widgets.

find_package(Nt6 REQUIRED)
add_executable(viewer main.cpp)
target_link_libraries(viewer PRIVATE Nt::SvgWidgets)   # or Nt::Svg, without widgets

Drawing a Document

A QSvgRenderer loads a document from a file, a resource or a QByteArray and draws it on any paint device: an image, a window or a widget. The document has no pixels of its own, so it is drawn at the size it is given, and stays sharp:

static QImage renderToImage(const QByteArray &svg, const QSize &size) {
    QSvgRenderer renderer(svg);
    QImage image(size, QImage::Format_ARGB32_Premultiplied);
    image.fill(Qt::transparent);
    if (renderer.isValid()) {
        renderer.setAspectRatioMode(Qt::KeepAspectRatio);
        QPainter painter(&image);
        renderer.render(&painter);
    }
    return image;
}

render() stretches the view box of the document over the whole device, or over the rectangle passed to it. QSvgRenderer::setAspectRatioMode() keeps the proportions instead, and QSvgRenderer::defaultSize() is the size that the document asks for.

QPainter in Nt GUI draws with whole pixels and has no paths. The renderer does not depend on it for the shapes: it fills and strokes them itself, antialiased, at the resolution of the device, and gives the painter the finished picture. On a screen with a device pixel ratio of 2, a document drawn into a widget has twice the pixels.

One File, Many Pictures

An element with an id can be drawn by itself, so one file can hold a set of icons:

QSvgRenderer icons(":/icons.svg");
if (icons.elementExists("save"))
    icons.render(&painter, "save", QRectF(4, 4, 16, 16));

The bounds of the element, from QSvgRenderer::boundsOnElement(), are stretched over the rectangle. Elements inside defs can be drawn this way too.

In a Widget

QSvgWidget shows a document scaled to the size of the widget; its size hint is the default size of the document. Its renderer() gives access to the QSvgRenderer:

QSvgWidget widget;
widget.renderer()->setAspectRatioMode(Qt::KeepAspectRatio);
if (argc > 1) {
    widget.load(QString::fromLocal8Bit(argv[1]));
} else {
    // No file: show what the generator writes, straight from memory.
    QBuffer buffer;
    QSvgGenerator generator;
    generator.setOutputDevice(&buffer);
    generator.setSize(QSize(240, 160));
    paintPicture(&generator);
    widget.load(buffer.data());
}
if (!widget.renderer()->isValid()) {
    std::fprintf(stderr, "svg_viewer: not an SVG document\n");
    return 1;
}
widget.setWindowTitle("Nt SVG");
widget.resize(widget.sizeHint());
widget.show();

Writing SVG

QSvgGenerator is a paint device. What a QPainter draws on it is written as an SVG document, to a file or to any QIODevice, when the painter ends:

static bool paintPicture(QPaintDevice *device) {
    QPainter painter(device);
    if (!painter.isActive())
        return false;
    painter.fillRect(0, 0, 240, 160, QColor(250, 250, 245));
    painter.setPen(QPen(QColor(40, 60, 90), 3));
    painter.setBrush(QColor(120, 170, 220));
    painter.drawRect(20, 20, 120, 80);
    painter.setBrush(QColor(240, 180, 60));
    painter.drawEllipse(110, 60, 100, 80);
    painter.setPen(QColor(40, 60, 90));
    painter.drawLine(20, 140, 100, 140);
    return true;
}

static bool writePicture(const QString &fileName) {
    QSvgGenerator generator;
    generator.setFileName(fileName);
    generator.setSize(QSize(240, 160));
    generator.setViewBox(QRect(0, 0, 240, 160));
    generator.setTitle("A picture");
    generator.setDescription("Painted with QPainter on a QSvgGenerator.");
    return paintPicture(&generator); // the document is complete when the painter is destroyed
}

A document that the generator writes is drawn by the renderer as the same painting looks on a QImage. Text and images are written as embedded PNG images, which the renderer does not draw; see QSvgGenerator for the details.

The svg_viewer example shows a file (svg_viewer picture.svg), and writes one (svg_viewer --write out.svg).

What Is Supported

Shapes, paths, groups, use, transformations, solid colors and gradients, strokes with caps, joins and dashes, opacity, clip paths, plain text and style sheets with simple selectors. Not supported: animation, embedded images, masks, patterns, markers and filters. QSvgRenderer has the full list.

Documents from Elsewhere

A document is data, and may come from anywhere. Loading reads nothing but the bytes it is given: no files, no network, no entities of a document type. Nesting and references are followed to a fixed depth, references that go around in circles end, and one drawing visits at most 500000 elements, so a small file cannot ask for an endless amount of work; QtSvg::AssumeTrustedSource lifts that last limit. A drawing of more than 64 million pixels is refused.

See also QSvgRenderer, QSvgWidget, QSvgGenerator, Using Nt GUI and Using Nt Widgets.