281 lines
6.9 KiB
C++
281 lines
6.9 KiB
C++
|
// 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();
|
|||
|
}
|
|||
|
|