pimageformats/imageformats/svg/dsvgrenderer.cpp

281 lines
6.9 KiB
C++
Raw Normal View History

// 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图形
isValidtruefalse
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();
}