pimageformats/imageformats/svg/dsvgrenderer.cpp

281 lines
6.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#ifndef DTK_DISABLE_LIBRSVG
#include <librsvg/rsvg.h>
#else
#include <QSvgRenderer>
#endif
#include "dsvgrenderer.h"
#include <QPainter>
#include <QFile>
#include <QDebug>
#include <QGuiApplication>
#include <QXmlStreamReader>
class DSvgRendererPrivate : public QObject
{
public:
explicit DSvgRendererPrivate(DSvgRenderer *qq);
QImage getImage(const QSize &size, const QString &elementId) const;
RsvgHandle *handle = nullptr;
QSize defaultSize;
mutable QRectF viewBox;
};
DSvgRendererPrivate::DSvgRendererPrivate(DSvgRenderer *qq)
: QObject(qq) // qq ==> DObject
{
}
QImage DSvgRendererPrivate::getImage(const QSize &size, const QString &elementId) const
{
QImage image(size, QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
cairo_surface_t *surface = cairo_image_surface_create_for_data(image.bits(), CAIRO_FORMAT_ARGB32, image.width(), image.height(), image.bytesPerLine());
cairo_t *cairo = cairo_create(surface);
cairo_scale(cairo, image.width() / viewBox.width(), image.height() / viewBox.height());
cairo_translate(cairo, -viewBox.x(), -viewBox.y());
if (elementId.isEmpty())
rsvg_handle_render_cairo(handle, cairo);
else
rsvg_handle_render_cairo_sub(handle, cairo, elementId.toUtf8().constData());
cairo_destroy(cairo);
cairo_surface_destroy(surface);
return image;
}
/*!
\class Dtk::Gui::DSvgRenderer
\inmodule dtkgui
\brief 提供了将SVG文件的内容绘制到绘制设备上的方法.
SVG图形可以在构造 DSvgRenderer 时加载也可以稍后使用load函数加载。
因为渲染是使用 QPainter 执行的,所以可以在 QPaintDevice 的任何子类上渲染SVG图形。
如果加载了有效文件则无论是在构造时还是以后某个时间isValid都将返回true否则将返回false。
DSvgRenderer提供render插槽用于使用给定的 QPainter 渲染当前文档或动画文档的当前帧
\note 使用 DSvgRenderer 需要 librsvg库
*/
DSvgRenderer::DSvgRenderer(QObject *parent)
: QObject(parent)
{
}
DSvgRenderer::DSvgRenderer(const QString &filename, QObject *parent)
: DSvgRenderer(parent)
{
load(filename);
}
DSvgRenderer::DSvgRenderer(const QByteArray &contents, QObject *parent)
: DSvgRenderer(parent)
{
load(contents);
}
DSvgRenderer::~DSvgRenderer()
{
Q_D(DSvgRenderer);
if (d->handle) {
Q_ASSERT(isValid());
g_object_unref(d->handle);
}
}
bool DSvgRenderer::isValid() const
{
Q_D(const DSvgRenderer);
return d->handle;
}
QSize DSvgRenderer::defaultSize() const
{
Q_D(const DSvgRenderer);
return d->defaultSize;
}
QRect DSvgRenderer::viewBox() const
{
Q_D(const DSvgRenderer);
return d->handle ? d->viewBox.toRect() : QRect();
}
QRectF DSvgRenderer::viewBoxF() const
{
Q_D(const DSvgRenderer);
return d->handle ? d->viewBox : QRectF();
}
void DSvgRenderer::setViewBox(const QRect &viewbox)
{
setViewBox(QRectF(viewbox));
}
void DSvgRenderer::setViewBox(const QRectF &viewbox)
{
Q_D(DSvgRenderer);
if (d->handle)
d->viewBox = viewbox;
}
QRectF DSvgRenderer::boundsOnElement(const QString &id) const
{
Q_D(const DSvgRenderer);
if (!d->handle)
return QRectF();
const QByteArray &id_data = id.toUtf8();
RsvgDimensionData dimension_data;
if (!rsvg_handle_get_dimensions_sub(d->handle, &dimension_data, id_data.constData()))
return QRectF();
RsvgPositionData pos_data;
if (!rsvg_handle_get_position_sub(d->handle, &pos_data, id_data.constData()))
return QRectF();
return QRectF(pos_data.x, pos_data.y, dimension_data.width, dimension_data.height);
}
bool DSvgRenderer::elementExists(const QString &id) const
{
Q_D(const DSvgRenderer);
if (!d->handle)
return false;
return rsvg_handle_has_sub(d->handle, id.toUtf8().constData());
}
QImage DSvgRenderer::toImage(const QSize sz, const QString &elementId) const
{
Q_D(const DSvgRenderer);
return d->getImage(sz, elementId);
}
static QByteArray updateXmlAttribute(const QString &contents)
{
QByteArray data;
QXmlStreamWriter writer(&data);
QXmlStreamReader reader(contents);
while(reader.readNext() != QXmlStreamReader::Invalid && !reader.atEnd()) {
if (reader.tokenType() != QXmlStreamReader::StartElement ||
!reader.attributes().hasAttribute("href")) {
writer.writeCurrentToken(reader);
continue;
}
for (const auto &nd : reader.namespaceDeclarations())
writer.writeNamespace(nd.namespaceUri().toString(), nd.prefix().toString());
writer.writeStartElement(reader.namespaceUri().toString(), reader.name().toString());
for (const auto &attr : reader.attributes()) {
if (attr.name() == "href") {
writer.writeAttribute("xlink:href", attr.value().toString());
continue;
}
writer.writeAttribute(attr);
}
}
return data;
}
static QByteArray format(const QByteArray &contents)
{
QXmlStreamReader reader(contents);
while (reader.readNextStartElement()) {
if (reader.attributes().hasAttribute("href"))
return updateXmlAttribute(contents);
}
return contents;
}
bool DSvgRenderer::load(const QString &filename)
{
QFile file(filename);
if (file.open(QIODevice::ReadOnly)) {
// TODO: if `href` attribute is adapted after librsvg upgrade revert me
return load(format(file.readAll()));
}
return false;
}
bool DSvgRenderer::load(const QByteArray &contents)
{
Q_D(DSvgRenderer);qDebug() << "drsvg load()";
if (d->handle) {
g_object_unref(d->handle);
d->handle = nullptr;
}
GError *error = nullptr;
d->handle = rsvg_handle_new_from_data((const guint8*)contents.constData(), contents.length(), &error);
if (error) {
qWarning("DSvgRenderer::load: %s", error->message);
g_error_free(error);
return false;
}
RsvgDimensionData rsvg_data;
rsvg_handle_get_dimensions(d->handle, &rsvg_data);
d->defaultSize.setWidth(rsvg_data.width);
d->defaultSize.setHeight(rsvg_data.height);
d->viewBox = QRectF(QPointF(0, 0), d->defaultSize);
return true;
}
void DSvgRenderer::render(QPainter *p)
{
render(p, QString(), QRectF());
}
void DSvgRenderer::render(QPainter *p, const QRectF &bounds)
{
render(p, QString(), bounds);
}
void DSvgRenderer::render(QPainter *p, const QString &elementId, const QRectF &bounds)
{
Q_D(DSvgRenderer);
if (!d->handle)
return;
p->save();
const QImage image = d->getImage(QSize(p->device()->width(), p->device()->height()), elementId);
if (bounds.isEmpty())
p->drawImage(0, 0, image);
else
p->drawImage(bounds, image);
p->restore();
}