summaryrefslogtreecommitdiff
path: root/capi
diff options
context:
space:
mode:
authorReizner Evgeniy <razrfalcon@gmail.com>2018-05-04 13:26:09 +0300
committerReizner Evgeniy <razrfalcon@gmail.com>2018-05-04 13:26:09 +0300
commit44160ce8a1b8f31fe219fc6a8a122b5832fe8875 (patch)
tree4dbd5f89060a2a43e8f08ec0a3335b8f22d0a0ed /capi
parent5561b547c4ded7ec24ff22db62e8c3d3edc620a2 (diff)
Added Qt wrapper for C-API.
A lot of small changes.
Diffstat (limited to 'capi')
-rw-r--r--capi/.gitignore1
-rw-r--r--capi/include/Doxyfile5
-rw-r--r--capi/include/resvg.h177
-rw-r--r--capi/qt-wrapper/ResvgQt.cpp287
-rw-r--r--capi/qt-wrapper/ResvgQt.h154
-rw-r--r--capi/qtests/.gitignore2
-rw-r--r--capi/qtests/invalid.svg1
-rw-r--r--capi/qtests/test.svg8
-rw-r--r--capi/qtests/test_renderFile_result.pngbin0 -> 2086 bytes
-rw-r--r--capi/qtests/tests.pro26
-rw-r--r--capi/qtests/tst_resvgqt.cpp87
-rw-r--r--capi/src/lib.rs239
12 files changed, 806 insertions, 181 deletions
diff --git a/capi/.gitignore b/capi/.gitignore
index eacffab..1e6eda2 100644
--- a/capi/.gitignore
+++ b/capi/.gitignore
@@ -1 +1,2 @@
/include/html
+/include/Doxyfile
diff --git a/capi/include/Doxyfile b/capi/include/Doxyfile
deleted file mode 100644
index d143338..0000000
--- a/capi/include/Doxyfile
+++ /dev/null
@@ -1,5 +0,0 @@
-PROJECT_NAME = "resvg C-API"
-OPTIMIZE_OUTPUT_FOR_C = YES
-QUIET = YES
-GENERATE_LATEX = NO
-ENABLE_PREPROCESSING = NO
diff --git a/capi/include/resvg.h b/capi/include/resvg.h
index 55eb2f8..c7a7119 100644
--- a/capi/include/resvg.h
+++ b/capi/include/resvg.h
@@ -15,6 +15,7 @@
#include <stdbool.h>
#include <stdint.h>
+#include <stddef.h>
#ifdef RESVG_CAIRO_BACKEND
#include <cairo.h>
@@ -44,6 +45,26 @@ typedef struct resvg_handle resvg_handle;
typedef struct resvg_render_tree resvg_render_tree;
/**
+ * @brief List of possible errors.
+ */
+typedef enum resvg_error {
+ /** Everything is ok. */
+ RESVG_OK = 0,
+ /** Only UTF-8 content are supported. */
+ RESVG_ERROR_NOT_AN_UTF8_STR,
+ /** Failed to open the provided file. */
+ RESVG_ERROR_FILE_OPEN_FAILED,
+ /** Failed to write to the provided file. */
+ RESVG_ERROR_FILE_WRITE_FAILED,
+ /** Only \b svg and \b svgz suffixes are supported. */
+ RESVG_ERROR_INVALID_FILE_SUFFIX,
+ /** Compressed SVG must use the GZip algorithm. */
+ RESVG_ERROR_MALFORMED_GZIP,
+ /** Failed to allocate an image. */
+ RESVG_ERROR_NO_CANVAS,
+} resvg_error;
+
+/**
* @brief An RGB color representation.
*/
typedef struct resvg_color {
@@ -76,19 +97,24 @@ typedef struct resvg_fit_to {
* @brief Rendering options.
*/
typedef struct resvg_options {
- /// SVG image path. Used to resolve relative image paths.
+ /** SVG image path. Used to resolve relative image paths. */
const char *path;
- /// Output DPI. Default: 96.
+ /** Output DPI. Default: 96. */
double dpi;
- /// Fits the image using specified options.
- /// Default: \b RESVG_FIT_TO_ORIGINAL.
+ /**
+ * Fits the image using specified options.
+ *
+ * Default: \b RESVG_FIT_TO_ORIGINAL.
+ */
resvg_fit_to fit_to;
- /// Draw background. Default: false.
+ /** Draw background. Default: false. */
bool draw_background;
- /// Background color.
+ /** Background color. */
resvg_color background;
- /// Keep named groups. If set to \b true, all non-empty
- /// groups with \b id attribute will not be removed.
+ /**
+ * Keep named groups. If set to \b true, all non-empty
+ * groups with \b id attribute will not be removed.
+ */
bool keep_named_groups;
} resvg_options;
@@ -143,6 +169,8 @@ void resvg_destroy(resvg_handle *handle);
*
* Use it if you want to see any warnings.
*
+ * Must be called only once.
+ *
* All warnings will be printed to the \b stderr.
*/
void resvg_init_log();
@@ -168,85 +196,100 @@ void resvg_init_options(resvg_options *opt)
*
* .svg and .svgz files are supported.
*
+ * See #resvg_is_image_empty for details.
+ *
* @param file_path UTF-8 file path.
* @param opt Rendering options.
- * @param error The error string if NULL was returned. Should be destroyed via #resvg_error_msg_destroy.
- * @return Parsed render tree. NULL on error. Should be destroyed via #resvg_rtree_destroy.
+ * @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.
+ * @return #resvg_error
*/
-resvg_render_tree *resvg_parse_rtree_from_file(const char *file_path,
- const resvg_options *opt,
- char **error);
+int resvg_parse_tree_from_file(const char *file_path,
+ const resvg_options *opt,
+ resvg_render_tree **tree);
/**
- * @brief Creates #resvg_render_tree from UTF-8 string.
+ * @brief Creates #resvg_render_tree from data.
+ *
+ * See #resvg_is_image_empty for details.
*
- * @param text UTF-8 string.
+ * @param data SVG data. Can contain SVG string or gzip compressed data.
+ * @param len Data length.
* @param opt Rendering options.
- * @param error The error string if NULL was returned. Should be destroyed via #resvg_error_msg_destroy.
- * @return Parsed render tree. NULL on error. Should be destroyed via #resvg_rtree_destroy.
+ * @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.
+ * @return #resvg_error
*/
-resvg_render_tree *resvg_parse_rtree_from_data(const char *text,
- const resvg_options *opt,
- char **error);
+int resvg_parse_tree_from_data(const char *data,
+ const size_t len,
+ const resvg_options *opt,
+ resvg_render_tree **tree);
+
+/**
+ * @brief Checks that tree has any nodes.
+ *
+ * #resvg_parse_tree_from_file and #resvg_parse_tree_from_data methods
+ * will return an error only if a file does not exist or it has a non-UTF-8 encoding.
+ * All other errors will result in an empty tree with a 100x100px size.
+ *
+ * @param tree Render tree.
+ * @return Returns \b true if tree has any nodes.
+ */
+bool resvg_is_image_empty(const resvg_render_tree *tree);
/**
* @brief Returns an image size.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @return Image size.
*/
-resvg_size resvg_get_image_size(const resvg_render_tree *rtree);
+resvg_size resvg_get_image_size(const resvg_render_tree *tree);
/**
* @brief Returns an image viewbox.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @return Image viewbox.
*/
-resvg_rect resvg_get_image_viewbox(const resvg_render_tree *rtree);
+resvg_rect resvg_get_image_viewbox(const resvg_render_tree *tree);
/**
- * @brief Returns \b true if a node with such an ID exists.
+ * @brief Returns \b true if a renderable node with such an ID exists.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @param id Node's ID. UTF-8 string.
- * @return \b true if a node exists. \b false if a node doesn't exist or ID isn't a UTF-8 string.
+ * @return \b true if a node exists.
+ * @return \b false if a node doesn't exist or ID isn't a UTF-8 string.
+ * @return \b false if a node exists, but not renderable.
*/
-bool resvg_node_exists(const resvg_render_tree *rtree,
+bool resvg_node_exists(const resvg_render_tree *tree,
const char *id);
/**
* @brief Returns node's transform by ID.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @param id Node's ID. UTF-8 string.
* @param ts Node's transform.
- * @return \b false if a node with such an ID does not exist or ID isn't a UTF-8 string.
+ * @return \b true if a node exists.
+ * @return \b false if a node doesn't exist or ID isn't a UTF-8 string.
+ * @return \b false if a node exists, but not renderable.
*/
-bool resvg_get_node_transform(const resvg_render_tree *rtree,
+bool resvg_get_node_transform(const resvg_render_tree *tree,
const char *id,
resvg_transform *ts);
/**
* @brief Destroys the #resvg_render_tree.
*
- * @param rtree Render tree.
- */
-void resvg_rtree_destroy(resvg_render_tree *rtree);
-
-/**
- * @brief Destroys the error message.
- *
- * @param msg Error message.
+ * @param tree Render tree.
*/
-void resvg_error_msg_destroy(char *msg);
+void resvg_tree_destroy(resvg_render_tree *tree);
#ifdef RESVG_CAIRO_BACKEND
/**
* @brief Returns node's bounding box by ID.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @param opt Rendering options.
* @param id Node's ID.
* @param bbox Node's bounding box.
@@ -254,7 +297,7 @@ void resvg_error_msg_destroy(char *msg);
* @return \b false if ID isn't a UTF-8 string.
* @return \b false if ID is an empty string
*/
-bool resvg_cairo_get_node_bbox(const resvg_render_tree *rtree,
+bool resvg_cairo_get_node_bbox(const resvg_render_tree *tree,
const resvg_options *opt,
const char *id,
resvg_rect *bbox);
@@ -262,26 +305,24 @@ bool resvg_cairo_get_node_bbox(const resvg_render_tree *rtree,
/**
* @brief Renders the #resvg_render_tree to file.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @param opt Rendering options.
* @param file_path File path.
- * @return \b false if \b file_path isn't an UTF-8 string.
- * @return \b false on "Out of memory".
- * @return \b false on file write error.
+ * @return #resvg_error
*/
-bool resvg_cairo_render_to_image(const resvg_render_tree *rtree,
- const resvg_options *opt,
- const char *file_path);
+int resvg_cairo_render_to_image(const resvg_render_tree *tree,
+ const resvg_options *opt,
+ const char *file_path);
/**
* @brief Renders the #resvg_render_tree to canvas.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @param opt Rendering options.
* @param size Canvas size.
* @param cr Canvas.
*/
-void resvg_cairo_render_to_canvas(const resvg_render_tree *rtree,
+void resvg_cairo_render_to_canvas(const resvg_render_tree *tree,
const resvg_options *opt,
resvg_size size,
cairo_t *cr);
@@ -291,31 +332,31 @@ void resvg_cairo_render_to_canvas(const resvg_render_tree *rtree,
*
* Does nothing on error.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @param opt Rendering options.
* @param size Canvas size.
* @param id Node's ID.
* @param cr Canvas.
*/
-void resvg_cairo_render_to_canvas_by_id(const resvg_render_tree *rtree,
+void resvg_cairo_render_to_canvas_by_id(const resvg_render_tree *tree,
const resvg_options *opt,
resvg_size size,
const char *id,
cairo_t *cr);
-#endif // RESVG_CAIRO_BACKEND
+#endif /* RESVG_CAIRO_BACKEND */
#ifdef RESVG_QT_BACKEND
/**
* @brief Returns node's bounding box by ID.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @param opt Rendering options.
* @param id Node's ID.
* @param bbox Node's bounding box.
* @return \b false if a node with such an ID does not exist,
* ID is an empty string or ID isn't a UTF-8 string.
*/
-bool resvg_qt_get_node_bbox(const resvg_render_tree *rtree,
+bool resvg_qt_get_node_bbox(const resvg_render_tree *tree,
const resvg_options *opt,
const char *id,
resvg_rect *bbox);
@@ -323,26 +364,24 @@ bool resvg_qt_get_node_bbox(const resvg_render_tree *rtree,
/**
* @brief Renders the #resvg_render_tree to file.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @param opt Rendering options.
* @param file_path File path.
- * @return \b false if \b file_path isn't an UTF-8 string.
- * @return \b false on "Out of memory".
- * @return \b false on file write error.
+ * @return #resvg_error
*/
-bool resvg_qt_render_to_image(const resvg_render_tree *rtree,
- const resvg_options *opt,
- const char *file_path);
+int resvg_qt_render_to_image(const resvg_render_tree *tree,
+ const resvg_options *opt,
+ const char *file_path);
/**
* @brief Renders the #resvg_render_tree to canvas.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @param opt Rendering options.
* @param size Canvas size.
* @param painter Canvas.
*/
-void resvg_qt_render_to_canvas(const resvg_render_tree *rtree,
+void resvg_qt_render_to_canvas(const resvg_render_tree *tree,
const resvg_options *opt,
resvg_size size,
void *painter);
@@ -352,17 +391,17 @@ void resvg_qt_render_to_canvas(const resvg_render_tree *rtree,
*
* Does nothing on error.
*
- * @param rtree Render tree.
+ * @param tree Render tree.
* @param opt Rendering options.
* @param size Canvas size.
* @param id Node's ID.
* @param painter Canvas.
*/
-void resvg_qt_render_to_canvas_by_id(const resvg_render_tree *rtree,
+void resvg_qt_render_to_canvas_by_id(const resvg_render_tree *tree,
const resvg_options *opt,
resvg_size size,
const char *id,
void *painter);
-#endif // RESVG_QT_BACKEND
+#endif /* RESVG_QT_BACKEND */
-#endif // RESVG_H
+#endif /* RESVG_H */
diff --git a/capi/qt-wrapper/ResvgQt.cpp b/capi/qt-wrapper/ResvgQt.cpp
new file mode 100644
index 0000000..e9a1a62
--- /dev/null
+++ b/capi/qt-wrapper/ResvgQt.cpp
@@ -0,0 +1,287 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <ResvgQt.h>
+
+extern "C" {
+#define RESVG_QT_BACKEND
+#include <resvg.h>
+}
+
+#include <QGuiApplication>
+#include <QScreen>
+#include <QPainter>
+#include <QFile>
+#include <QDebug>
+
+
+class ResvgRendererPrivate
+{
+public:
+ ~ResvgRendererPrivate()
+ {
+ reset();
+ }
+
+ void reset()
+ {
+ if (tree) {
+ resvg_tree_destroy(tree);
+ tree = nullptr;
+ }
+
+ if (opt.path) {
+ delete[] opt.path; // do not use free() because was allocated via qstrdup()
+ opt.path = NULL;
+ }
+
+ viewBox = QRectF();
+ errMsg = QString();
+ }
+
+ resvg_render_tree *tree = nullptr;
+ resvg_options opt;
+ QRectF viewBox;
+ QString errMsg;
+};
+
+
+static void initOptions(resvg_options &opt)
+{
+ resvg_init_options(&opt);
+
+ const auto screens = qApp->screens();
+ if (!screens.isEmpty()) {
+ const auto screen = screens.at(0);
+ opt.dpi = screen->logicalDotsPerInch() * screen->devicePixelRatio();
+ }
+}
+
+static QString errorToString(const int err)
+{
+ switch (err) {
+ case RESVG_OK :
+ return QString(); break;
+ case RESVG_ERROR_NOT_AN_UTF8_STR :
+ return QLatin1Literal("The SVG content has not an UTF-8 encoding."); break;
+ case RESVG_ERROR_FILE_OPEN_FAILED :
+ return QLatin1Literal("Failed to open the file."); break;
+ case RESVG_ERROR_FILE_WRITE_FAILED :
+ return QLatin1Literal("Failed to write to the file."); break;
+ case RESVG_ERROR_INVALID_FILE_SUFFIX :
+ return QLatin1Literal("Invalid file suffix."); break;
+ case RESVG_ERROR_MALFORMED_GZIP :
+ return QLatin1Literal("Not a GZip compressed data."); break;
+ case RESVG_ERROR_NO_CANVAS :
+ return QLatin1Literal("Failed to allocate the canvas."); break;
+ }
+
+ Q_UNREACHABLE();
+}
+
+ResvgRenderer::ResvgRenderer()
+ : d(new ResvgRendererPrivate())
+{
+}
+
+ResvgRenderer::ResvgRenderer(const QString &filePath)
+ : d(new ResvgRendererPrivate())
+{
+ load(filePath);
+}
+
+ResvgRenderer::ResvgRenderer(const QByteArray &data)
+ : d(new ResvgRendererPrivate())
+{
+ load(data);
+}
+
+ResvgRenderer::~ResvgRenderer() {}
+
+bool ResvgRenderer::load(const QString &filePath)
+{
+ // Check for Qt resource path.
+ if (filePath.startsWith(":/")) {
+ QFile file(filePath);
+ if (file.open(QFile::ReadOnly)) {
+ return load(file.readAll());
+ } else {
+ return false;
+ }
+ }
+
+ d->reset();
+
+ resvg_options opt;
+ initOptions(opt);
+
+ const auto utf8Str = filePath.toUtf8();
+ const auto rawFilePath = utf8Str.constData();
+ opt.path = qstrdup(rawFilePath);
+
+ resvg_render_tree *tree = NULL;
+ const auto err = resvg_parse_tree_from_file(opt.path, &opt, &tree);
+ if (err != RESVG_OK) {
+ d->errMsg = errorToString(err);
+ return false;
+ }
+
+ d->tree = tree;
+ d->opt = opt;
+
+ const auto r = resvg_get_image_viewbox(d->tree);
+ d->viewBox = QRectF(r.x, r.y, r.width, r.height);
+
+ return true;
+}
+
+bool ResvgRenderer::load(const QByteArray &data)
+{
+ d->reset();
+
+ resvg_options opt;
+ initOptions(opt);
+
+ resvg_render_tree *tree = NULL;
+ resvg_parse_tree_from_data(data.constData(), data.size(), &opt, &tree);
+
+ d->tree = tree;
+ d->opt = opt;
+
+ const auto r = resvg_get_image_viewbox(d->tree);
+ d->viewBox = QRectF(r.x, r.y, r.width, r.height);
+
+ return true;
+}
+
+bool ResvgRenderer::isValid() const
+{
+ return d->tree;
+}
+
+QString ResvgRenderer::errorString() const
+{
+ return d->errMsg;
+}
+
+bool ResvgRenderer::isEmpty() const
+{
+ if (d->tree)
+ return !resvg_is_image_empty(d->tree);
+ else
+ return true;
+}
+
+QSize ResvgRenderer::defaultSize() const
+{
+ return defaultSizeF().toSize();
+}
+
+QSizeF ResvgRenderer::defaultSizeF() const
+{
+ if (d->tree)
+ return d->viewBox.size();
+ else
+ return QSizeF();
+}
+
+QRect ResvgRenderer::viewBox() const
+{
+ return viewBoxF().toRect();
+}
+
+QRectF ResvgRenderer::viewBoxF() const
+{
+ if (d->tree)
+ return d->viewBox;
+ else
+ return QRectF();
+}
+
+bool ResvgRenderer::elementExists(const QString &id) const
+{
+ if (d->tree) {
+ const auto utf8Str = id.toUtf8();
+ const auto rawId = utf8Str.constData();
+ return resvg_node_exists(d->tree, rawId);
+ }
+
+ return false;
+}
+
+QTransform ResvgRenderer::transformForElement(const QString &id) const
+{
+ if (d->tree) {
+ const auto utf8Str = id.toUtf8();
+ const auto rawId = utf8Str.constData();
+ resvg_transform ts;
+ if (resvg_get_node_transform(d->tree, rawId, &ts)) {
+ return QTransform(ts.a, ts.b, ts.c, ts.d, ts.e, ts.f);
+ }
+ }
+
+ return QTransform();
+}
+
+void ResvgRenderer::render(QPainter *p)
+{
+ render(p, QRectF());
+}
+
+void ResvgRenderer::render(QPainter *p, const QRectF &bounds)
+{
+ if (!d->tree)
+ return;
+
+ const auto r = bounds.isValid() ? bounds : p->viewport();
+
+ p->save();
+ p->setRenderHint(QPainter::Antialiasing);
+
+ const double sx = (double)r.width() / d->viewBox.width();
+ const double sy = (double)r.height() / d->viewBox.height();
+
+ p->setTransform(QTransform(sx, 0, 0, sy, r.x(), r.y()), true);
+
+ resvg_size imgSize { (uint)d->viewBox.width(), (uint)d->viewBox.height() };
+ resvg_qt_render_to_canvas(d->tree, &d->opt, imgSize, p);
+
+ p->restore();
+}
+
+void ResvgRenderer::render(QPainter *p, const QString &elementId, const QRectF &bounds)
+{
+ if (!d->tree)
+ return;
+
+ const auto utf8Str = elementId.toUtf8();
+ const auto rawId = utf8Str.constData();
+
+ resvg_rect bbox;
+ if (!resvg_qt_get_node_bbox(d->tree, &d->opt, rawId, &bbox)) {
+ qWarning() << QString("Element '%1' has no bounding box.").arg(elementId);
+ return;
+ }
+
+ p->save();
+ p->setRenderHint(QPainter::Antialiasing);
+
+ const auto r = bounds.isValid() ? bounds : p->viewport();
+
+ const double sx = (double)r.width() / bbox.width;
+ const double sy = (double)r.height() / bbox.height;
+ p->setTransform(QTransform(sx, 0, 0, sy, bounds.x(), bounds.y()), true);
+
+ resvg_size imgSize { (uint)bbox.width, (uint)bbox.height };
+ resvg_qt_render_to_canvas_by_id(d->tree, &d->opt, imgSize, rawId, p);
+
+ p->restore();
+}
+
+void ResvgRenderer::initLog()
+{
+ resvg_init_log();
+}
diff --git a/capi/qt-wrapper/ResvgQt.h b/capi/qt-wrapper/ResvgQt.h
new file mode 100644
index 0000000..2572c88
--- /dev/null
+++ b/capi/qt-wrapper/ResvgQt.h
@@ -0,0 +1,154 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * @file ResvgQt.h
+ *
+ * Qt wrapper for resvg C-API
+ */
+
+#ifndef RESVGQT_H
+#define RESVGQT_H
+
+#include <QString>
+#include <QScopedPointer>
+#include <QRectF>
+#include <QTransform>
+
+class QPainter;
+
+class ResvgRendererPrivate;
+
+/**
+ * @brief QSvgRenderer-like wrapper for resvg C-API
+ */
+class ResvgRenderer {
+public:
+ /**
+ * @brief Constructs a new renderer.
+ */
+ ResvgRenderer();
+
+ /**
+ * @brief Constructs a new renderer and loads the contents of the SVG(Z) file.
+ *
+ * @param filePath File path.
+ */
+ ResvgRenderer(const QString &filePath);
+
+ /**
+ * @brief Constructs a new renderer and loads the SVG data.
+ *
+ * @param data SVG data. Must be already unzipped.
+ */
+ ResvgRenderer(const QByteArray &data);
+
+ /**
+ * @brief Destructs the renderer.
+ */
+ ~ResvgRenderer();
+
+ /**
+ * @brief Loads the contents of the SVG(Z) file.
+ */
+ bool load(const QString &filePath);
+
+ /**
+ * @brief Loads the SVG data.
+ *
+ * @param data SVG data. Must be already unzipped.
+ */
+ bool load(const QByteArray &data);
+
+ /**
+ * @brief Returns \b true if the file or data were loaded successful.
+ */
+ bool isValid() const;
+
+ /**
+ * @brief Returns an underling error when #isValid is \b false.
+ */
+ QString errorString() const;
+
+ /**
+ * @brief Checks that underling tree has any nodes.
+ *
+ * #ResvgRenderer and #ResvgRenderer constructors
+ * will set an error only if a file does not exist or it has a non-UTF-8 encoding.
+ * All other errors will result in an empty tree with a 100x100px size.
+ *
+ * @return Returns \b true if tree has any nodes.
+ */
+ bool isEmpty() const;
+
+ /**
+ * @brief Returns an SVG size.
+ */
+ QSize defaultSize() const;
+
+ /**
+ * @brief Returns an SVG size.
+ */
+ QSizeF defaultSizeF() const;
+
+ /**
+ * @brief Returns an SVG viewbox.
+ */
+ QRect viewBox() const;
+
+ /**
+ * @brief Returns an SVG viewbox.
+ */
+ QRectF viewBoxF() const;
+
+ /**
+ * @brief Returns \b true if element with such an ID exists.
+ */
+ bool elementExists(const QString &id) const;
+
+ /**
+ * @brief Returns element's transform.
+ */
+ QTransform transformForElement(const QString &id) const;
+
+ /**
+ * @brief Renders the SVG data to canvas.
+ */
+ void render(QPainter *p);
+
+ /**
+ * @brief Renders the SVG data to canvas with the specified \b bounds.
+ *
+ * If the bounding rectangle is not specified
+ * the SVG file is mapped to the whole paint device.
+ */
+ void render(QPainter *p, const QRectF &bounds);
+
+ /**
+ * @brief Renders the given element with \b elementId on the specified \b bounds.
+ *
+ * If the bounding rectangle is not specified
+ * the SVG element is mapped to the whole paint device.
+ */
+ void render(QPainter *p, const QString &elementId,
+ const QRectF &bounds = QRectF());
+
+ /**
+ * @brief Initializes the library log.
+ *
+ * Use it if you want to see any warnings.
+ *
+ * Must be called only once.
+ *
+ * All warnings will be printed to the \b stderr.
+ */
+ static void initLog();
+
+private:
+ QScopedPointer<ResvgRendererPrivate> d;
+};
+
+#endif // RESVGQT_H
diff --git a/capi/qtests/.gitignore b/capi/qtests/.gitignore
new file mode 100644
index 0000000..c1b81fb
--- /dev/null
+++ b/capi/qtests/.gitignore
@@ -0,0 +1,2 @@
+tst_resvgqt*
+test_renderFile.png
diff --git a/capi/qtests/invalid.svg b/capi/qtests/invalid.svg
new file mode 100644
index 0000000..96322cc
--- /dev/null
+++ b/capi/qtests/invalid.svg
@@ -0,0 +1 @@
+<rect/>
diff --git a/capi/qtests/test.svg b/capi/qtests/test.svg
new file mode 100644
index 0000000..d75f48f
--- /dev/null
+++ b/capi/qtests/test.svg
@@ -0,0 +1,8 @@
+<svg id="svg1" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <pattern id="patt1" patternUnits="userSpaceOnUse" width="20" height="20">
+ <rect id="rect1" x="0" y="0" width="10" height="10" fill="grey"/>
+ <rect id="rect2" x="10" y="10" width="10" height="10" fill="green"/>
+ </pattern>
+ <circle id="circle1" cx="50" cy="50" r="40" fill="url(#patt1)"
+ transform="scale(2)"/>
+</svg>
diff --git a/capi/qtests/test_renderFile_result.png b/capi/qtests/test_renderFile_result.png
new file mode 100644
index 0000000..059903e
--- /dev/null
+++ b/capi/qtests/test_renderFile_result.png
Binary files differ
diff --git a/capi/qtests/tests.pro b/capi/qtests/tests.pro
new file mode 100644
index 0000000..39c5a83
--- /dev/null
+++ b/capi/qtests/tests.pro
@@ -0,0 +1,26 @@
+QT += testlib
+
+TARGET = tst_resvgqt
+CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+QMAKE_CXXFLAGS += -Wextra -Wpedantic
+
+QMAKE_CXXFLAGS += -fsanitize=address
+QMAKE_LFLAGS += -fsanitize=address
+
+SOURCES += \
+ tst_resvgqt.cpp \
+ $$PWD/../qt-wrapper/ResvgQt.cpp
+
+DEFINES += SRCDIR=\\\"$$PWD\\\"
+
+CONFIG(release, debug|release): LIBS += -L$$PWD/../../target/release/ -lresvg
+else:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../target/debug/ -lresvg
+
+INCLUDEPATH += $$PWD/../include
+DEPENDPATH += $$PWD/../include
+
+INCLUDEPATH += $$PWD/../qt-wrapper
diff --git a/capi/qtests/tst_resvgqt.cpp b/capi/qtests/tst_resvgqt.cpp
new file mode 100644
index 0000000..2a2fc75
--- /dev/null
+++ b/capi/qtests/tst_resvgqt.cpp
@@ -0,0 +1,87 @@
+#include <QString>
+#include <QPainter>
+#include <QtTest>
+
+#include <ResvgQt.h>
+
+class ResvgQtTests : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void test_parseFile();
+ void test_parseInvalidFile();
+
+ void test_renderFile();
+
+ void test_elementExists();
+ void test_transformForElement();
+};
+
+static QString localPath(const QString &fileName)
+{
+ return QString("%1/%2").arg(SRCDIR).arg(fileName);
+}
+
+void ResvgQtTests::test_parseFile()
+{
+ ResvgRenderer render(localPath("test.svg"));
+ QVERIFY(render.isValid());
+ QVERIFY(!render.isEmpty());
+ QCOMPARE(render.defaultSize(), QSize(200, 200));
+}
+
+void ResvgQtTests::test_parseInvalidFile()
+{
+ ResvgRenderer render(localPath("invalid.svg"));
+ QVERIFY(render.isValid());
+ QCOMPARE(render.defaultSize(), QSize(100, 100));
+ QVERIFY(!render.isEmpty());
+}
+
+void ResvgQtTests::test_renderFile()
+{
+ ResvgRenderer render(localPath("test.svg"));
+ QVERIFY(!render.isEmpty());
+ QCOMPARE(render.defaultSize(), QSize(200, 200));
+
+ QImage img(render.defaultSize(), QImage::Format_ARGB32);
+ img.fill(Qt::transparent);
+
+ QPainter p(&img);
+ render.render(&p);
+ p.end();
+
+ img.save("test_renderFile.png");
+
+ QCOMPARE(img, QImage(localPath("test_renderFile_result.png")));
+}
+
+void ResvgQtTests::test_elementExists()
+{
+ ResvgRenderer render(localPath("test.svg"));
+ QVERIFY(!render.isEmpty());
+
+ // Existing element.
+ QVERIFY(render.elementExists("circle1"));
+
+ // Non-existing element.
+ QVERIFY(!render.elementExists("invalid"));
+
+ // Non-renderable elements.
+ QVERIFY(!render.elementExists("rect1"));
+ QVERIFY(!render.elementExists("rect2"));
+ QVERIFY(!render.elementExists("patt1"));
+}
+
+void ResvgQtTests::test_transformForElement()
+{
+ ResvgRenderer render(localPath("test.svg"));
+ QVERIFY(!render.isEmpty());
+ QCOMPARE(render.transformForElement("circle1"), QTransform(2, 0, 0, 2, 0, 0));
+ QCOMPARE(render.transformForElement("invalid"), QTransform());
+}
+
+QTEST_APPLESS_MAIN(ResvgQtTests)
+
+#include "tst_resvgqt.moc"
diff --git a/capi/src/lib.rs b/capi/src/lib.rs
index 882f948..b59a9fe 100644
--- a/capi/src/lib.rs
+++ b/capi/src/lib.rs
@@ -15,9 +15,9 @@ extern crate cairo_sys;
use std::fmt;
use std::path;
-use std::ptr;
-use std::ffi::{ CStr, CString };
+use std::ffi::CStr;
use std::os::raw::c_char;
+use std::slice;
#[cfg(feature = "qt-backend")]
use resvg::qt;
@@ -27,6 +27,7 @@ use resvg::cairo;
use resvg::usvg;
use resvg::tree;
+use resvg::tree::NodeExt;
use resvg::geom::*;
@@ -40,6 +41,16 @@ pub struct resvg_options {
pub keep_named_groups: bool,
}
+enum ErrorId {
+ Ok = 0,
+ NotAnUtf8Str,
+ FileOpenFailed,
+ FileWriteFailed,
+ InvalidFileSuffix,
+ MalformedGZip,
+ NoCanvas,
+}
+
#[repr(C)]
pub struct resvg_color {
pub r: u8,
@@ -91,13 +102,6 @@ pub struct resvg_render_tree(resvg::tree::Tree);
#[repr(C)]
pub struct resvg_handle(resvg::InitObject);
-macro_rules! on_err {
- ($err:expr, $msg:expr) => ({
- let c_str = CString::new($msg).unwrap();
- unsafe { *$err = c_str.into_raw(); }
- return ptr::null_mut();
- })
-}
#[no_mangle]
pub extern fn resvg_init() -> *mut resvg_handle {
@@ -141,14 +145,14 @@ fn log_format(out: fern::FormatCallback, message: &fmt::Arguments, record: &log:
}
#[no_mangle]
-pub extern fn resvg_parse_rtree_from_file(
+pub extern fn resvg_parse_tree_from_file(
file_path: *const c_char,
opt: *const resvg_options,
- error: *mut *mut c_char,
-) -> *mut resvg_render_tree {
+ raw_tree: *mut *mut resvg_render_tree,
+) -> i32 {
let file_path = match cstr_to_str(file_path) {
Some(v) => v,
- None => on_err!(error, "Error: file path is not an UTF-8 string."),
+ None => return ErrorId::NotAnUtf8Str as i32,
};
let opt = to_native_opt(unsafe {
@@ -156,89 +160,86 @@ pub extern fn resvg_parse_rtree_from_file(
&*opt
});
- let rtree = match resvg::parse_rtree_from_file(file_path, &opt) {
- Ok(rtree) => rtree,
- Err(e) => on_err!(error, e.to_string()),
+ let tree = match resvg::parse_tree_from_file(file_path, &opt.usvg) {
+ Ok(tree) => tree,
+ Err(e) => return convert_error(e) as i32,
};
- let rtree_box = Box::new(resvg_render_tree(rtree));
- Box::into_raw(rtree_box)
+ let tree_box = Box::new(resvg_render_tree(tree));
+ unsafe { *raw_tree = Box::into_raw(tree_box); }
+
+ ErrorId::Ok as i32
}
#[no_mangle]
-pub extern fn resvg_parse_rtree_from_data(
- text: *const c_char,
+pub extern fn resvg_parse_tree_from_data(
+ data: *const c_char,
+ len: usize,
opt: *const resvg_options,
- error: *mut *mut c_char,
-) -> *mut resvg_render_tree {
- let text = match cstr_to_str(text) {
- Some(v) => v,
- None => on_err!(error, "Error: SVG data is not an UTF-8 string."),
- };
+ raw_tree: *mut *mut resvg_render_tree,
+) -> i32 {
+ let data = unsafe { slice::from_raw_parts(data as *const u8, len) };
let opt = to_native_opt(unsafe {
assert!(!opt.is_null());
&*opt
});
- let rtree = resvg::parse_rtree_from_data(text, &opt);
+ let tree = match resvg::parse_tree_from_data(data, &opt.usvg) {
+ Ok(tree) => tree,
+ Err(e) => return convert_error(e) as i32,
+ };
- let rtree_box = Box::new(resvg_render_tree(rtree));
- Box::into_raw(rtree_box)
-}
+ let tree_box = Box::new(resvg_render_tree(tree));
+ unsafe { *raw_tree = Box::into_raw(tree_box); }
-#[no_mangle]
-pub extern fn resvg_error_msg_destroy(msg: *mut c_char) {
- unsafe {
- assert!(!msg.is_null());
- CString::from_raw(msg)
- };
+ ErrorId::Ok as i32
}
#[no_mangle]
-pub extern fn resvg_rtree_destroy(rtree: *mut resvg_render_tree) {
+pub extern fn resvg_tree_destroy(tree: *mut resvg_render_tree) {
unsafe {
- assert!(!rtree.is_null());
- Box::from_raw(rtree)
+ assert!(!tree.is_null());
+ Box::from_raw(tree)
};
}
#[cfg(feature = "qt-backend")]
#[no_mangle]
pub extern fn resvg_qt_render_to_image(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
opt: *const resvg_options,
file_path: *const c_char,
-) -> bool {
+) -> i32 {
let backend = Box::new(resvg::render_qt::Backend);
- render_to_image(rtree, opt, file_path, backend)
+ render_to_image(tree, opt, file_path, backend)
}
#[cfg(feature = "cairo-backend")]
#[no_mangle]
pub extern fn resvg_cairo_render_to_image(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
opt: *const resvg_options,
file_path: *const c_char,
-) -> bool {
+) -> i32 {
let backend = Box::new(resvg::render_cairo::Backend);
- render_to_image(rtree, opt, file_path, backend)
+ render_to_image(tree, opt, file_path, backend)
}
fn render_to_image(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
opt: *const resvg_options,
file_path: *const c_char,
backend: Box<resvg::Render>,
-) -> bool {
- let rtree = unsafe {
- assert!(!rtree.is_null());
- &*rtree
+) -> i32 {
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
};
let file_path = match cstr_to_str(file_path) {
Some(v) => v,
- None => return false,
+ None => return ErrorId::NotAnUtf8Str as i32,
};
let opt = to_native_opt(unsafe {
@@ -246,29 +247,31 @@ fn render_to_image(
&*opt
});
- let img = backend.render_to_image(&rtree.0, &opt);
+ let img = backend.render_to_image(&tree.0, &opt);
let img = match img {
- Ok(img) => img,
- Err(e) => {
- warn!("{}", e);
- return false;
+ Some(img) => img,
+ None => {
+ return ErrorId::NoCanvas as i32;
}
};
- img.save(path::Path::new(file_path))
+ match img.save(path::Path::new(file_path)) {
+ true => ErrorId::Ok as i32,
+ false => ErrorId::FileWriteFailed as i32,
+ }
}
#[cfg(feature = "qt-backend")]
#[no_mangle]
pub extern fn resvg_qt_render_to_canvas(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
opt: *const resvg_options,
size: resvg_size,
painter: *mut qt::qtc_qpainter,
) {
- let rtree = unsafe {
- assert!(!rtree.is_null());
- &*rtree
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
};
let painter = unsafe { qt::Painter::from_raw(painter) };
@@ -278,20 +281,20 @@ pub extern fn resvg_qt_render_to_canvas(
&*opt
});
- resvg::render_qt::render_to_canvas(&rtree.0, &opt, size, &painter);
+ resvg::render_qt::render_to_canvas(&tree.0, &opt, size, &painter);
}
#[cfg(feature = "cairo-backend")]
#[no_mangle]
pub extern fn resvg_cairo_render_to_canvas(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
opt: *const resvg_options,
size: resvg_size,
cr: *mut cairo_sys::cairo_t,
) {
- let rtree = unsafe {
- assert!(!rtree.is_null());
- &*rtree
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
};
use glib::translate::FromGlibPtrNone;
@@ -304,21 +307,21 @@ pub extern fn resvg_cairo_render_to_canvas(
&*opt
});
- resvg::render_cairo::render_to_canvas(&rtree.0, &opt, size, &cr);
+ resvg::render_cairo::render_to_canvas(&tree.0, &opt, size, &cr);
}
#[cfg(feature = "qt-backend")]
#[no_mangle]
pub extern fn resvg_qt_render_to_canvas_by_id(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
opt: *const resvg_options,
size: resvg_size,
id: *const c_char,
painter: *mut qt::qtc_qpainter,
) {
- let rtree = unsafe {
- assert!(!rtree.is_null());
- &*rtree
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
};
let painter = unsafe { qt::Painter::from_raw(painter) };
@@ -338,7 +341,7 @@ pub extern fn resvg_qt_render_to_canvas_by_id(
return;
}
- if let Some(node) = rtree.0.node_by_id(id) {
+ if let Some(node) = tree.0.node_by_id(id) {
if let Some(bbox) = resvg::render_qt::calc_node_bbox(&node, &opt) {
let vbox = tree::ViewBox {
rect: bbox,
@@ -357,15 +360,15 @@ pub extern fn resvg_qt_render_to_canvas_by_id(
#[cfg(feature = "cairo-backend")]
#[no_mangle]
pub extern fn resvg_cairo_render_to_canvas_by_id(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
opt: *const resvg_options,
size: resvg_size,
id: *const c_char,
cr: *mut cairo_sys::cairo_t,
) {
- let rtree = unsafe {
- assert!(!rtree.is_null());
- &*rtree
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
};
let id = match cstr_to_str(id) {
@@ -388,7 +391,7 @@ pub extern fn resvg_cairo_render_to_canvas_by_id(
&*opt
});
- if let Some(node) = rtree.0.node_by_id(id) {
+ if let Some(node) = tree.0.node_by_id(id) {
if let Some(bbox) = resvg::render_cairo::calc_node_bbox(&node, &opt) {
let vbox = tree::ViewBox {
rect: bbox,
@@ -406,14 +409,14 @@ pub extern fn resvg_cairo_render_to_canvas_by_id(
#[no_mangle]
pub extern fn resvg_get_image_size(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
) -> resvg_size {
- let rtree = unsafe {
- assert!(!rtree.is_null());
- &*rtree
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
};
- let size = rtree.0.svg_node().size;
+ let size = tree.0.svg_node().size;
resvg_size {
width: size.width as u32,
@@ -423,14 +426,14 @@ pub extern fn resvg_get_image_size(
#[no_mangle]
pub extern fn resvg_get_image_viewbox(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
) -> resvg_rect {
- let rtree = unsafe {
- assert!(!rtree.is_null());
- &*rtree
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
};
- let r = rtree.0.svg_node().view_box.rect;
+ let r = tree.0.svg_node().view_box.rect;
resvg_rect {
x: r.x(),
@@ -440,32 +443,44 @@ pub extern fn resvg_get_image_viewbox(
}
}
+#[no_mangle]
+pub extern fn resvg_is_image_empty(
+ tree: *const resvg_render_tree,
+) -> bool {
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
+ };
+
+ tree.0.root().has_children()
+}
+
#[cfg(feature = "qt-backend")]
#[no_mangle]
pub extern fn resvg_qt_get_node_bbox(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
opt: *const resvg_options,
id: *const c_char,
bbox: *mut resvg_rect,
) -> bool {
let backend = Box::new(resvg::render_qt::Backend);
- get_node_bbox(rtree, opt, id, bbox, backend)
+ get_node_bbox(tree, opt, id, bbox, backend)
}
#[cfg(feature = "cairo-backend")]
#[no_mangle]
pub extern fn resvg_cairo_get_node_bbox(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
opt: *const resvg_options,
id: *const c_char,
bbox: *mut resvg_rect,
) -> bool {
let backend = Box::new(resvg::render_cairo::Backend);
- get_node_bbox(rtree, opt, id, bbox, backend)
+ get_node_bbox(tree, opt, id, bbox, backend)
}
fn get_node_bbox(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
opt: *const resvg_options,
id: *const c_char,
bbox: *mut resvg_rect,
@@ -484,9 +499,9 @@ fn get_node_bbox(
return false;
}
- let rtree = unsafe {
- assert!(!rtree.is_null());
- &*rtree
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
};
@@ -495,7 +510,7 @@ fn get_node_bbox(
&*opt
});
- match rtree.0.node_by_id(id) {
+ match tree.0.node_by_id(id) {
Some(node) => {
if let Some(r) = backend.calc_node_bbox(&node, &opt) {
unsafe {
@@ -519,7 +534,7 @@ fn get_node_bbox(
#[no_mangle]
pub extern fn resvg_node_exists(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
id: *const c_char,
) -> bool {
let id = match cstr_to_str(id) {
@@ -530,17 +545,17 @@ pub extern fn resvg_node_exists(
}
};
- let rtree = unsafe {
- assert!(!rtree.is_null());
- &*rtree
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
};
- rtree.0.node_by_id(id).is_some()
+ tree.0.node_by_id(id).is_some()
}
#[no_mangle]
pub extern fn resvg_get_node_transform(
- rtree: *const resvg_render_tree,
+ tree: *const resvg_render_tree,
id: *const c_char,
ts: *mut resvg_transform,
) -> bool {
@@ -552,13 +567,14 @@ pub extern fn resvg_get_node_transform(
}
};
- let rtree = unsafe {
- assert!(!rtree.is_null());
- &*rtree
+ let tree = unsafe {
+ assert!(!tree.is_null());
+ &*tree
};
- if let Some(node) = rtree.0.node_by_id(id) {
- let abs_ts = resvg::utils::abs_transform(&node);
+ if let Some(node) = tree.0.node_by_id(id) {
+ let mut abs_ts = resvg::utils::abs_transform(&node);
+ abs_ts.append(&node.transform());
unsafe {
(*ts).a = abs_ts.a;
@@ -633,3 +649,12 @@ fn to_native_opt(opt: &resvg_options) -> resvg::Options {
background,
}
}
+
+fn convert_error(e: usvg::Error) -> ErrorId {
+ match e {
+ usvg::Error::InvalidFileSuffix => ErrorId::InvalidFileSuffix,
+ usvg::Error::FileOpenFailed => ErrorId::FileOpenFailed,
+ usvg::Error::NotAnUtf8Str => ErrorId::NotAnUtf8Str,
+ usvg::Error::MalformedGZip => ErrorId::MalformedGZip,
+ }
+}