// SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #ifndef DTK_DISABLE_LIBRSVG #include #else #include #endif #include "dsvgrenderer.h" #include #include #include #include #include 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(); }