summaryrefslogtreecommitdiff
path: root/librsvg-c/tests-c
diff options
context:
space:
mode:
Diffstat (limited to 'librsvg-c/tests-c')
-rw-r--r--librsvg-c/tests-c/Makefile.am27
-rw-r--r--librsvg-c/tests-c/api.c1709
-rw-r--r--librsvg-c/tests-c/test-utils.c257
-rw-r--r--librsvg-c/tests-c/test-utils.h35
4 files changed, 2028 insertions, 0 deletions
diff --git a/librsvg-c/tests-c/Makefile.am b/librsvg-c/tests-c/Makefile.am
new file mode 100644
index 00000000..68870eb6
--- /dev/null
+++ b/librsvg-c/tests-c/Makefile.am
@@ -0,0 +1,27 @@
+include $(top_srcdir)/glib-tap.mk
+
+test_programs = api
+
+api_SOURCES = \
+ api.c \
+ test-utils.c \
+ test-utils.h \
+ $(NULL)
+
+api_LDADD = $(top_builddir)/librsvg_c_api.la \
+ $(LIBRSVG_LIBS) \
+ $(LIBM)
+
+api_LDFLAGS = -static
+
+AM_CPPFLAGS = \
+ -I$(srcdir) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ -DTEST_DATA_DIR="\"$(srcdir)\"" \
+ -DTEST_SRC_DIR="\"$(PWD)\"" \
+ -DTOP_SRC_DIR="\"$(top_srcdir)\"" \
+ $(LIBRSVG_CFLAGS)
+
+clean-local:
+ rm -rf output
diff --git a/librsvg-c/tests-c/api.c b/librsvg-c/tests-c/api.c
new file mode 100644
index 00000000..f7b01c2e
--- /dev/null
+++ b/librsvg-c/tests-c/api.c
@@ -0,0 +1,1709 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 nowrap ai expandtab sw=4: */
+
+/* These are the C API tests for librsvg. These test the complete C
+ * API, especially its historical peculiarities to ensure ABI
+ * compatibility.
+ *
+ * These tests are not meant to exhaustively test librsvg's features.
+ * For those, you should look at the Rust integration tests. See
+ * tests/README.md for details.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <glib.h>
+#include <cairo.h>
+
+#define RSVG_DISABLE_DEPRECATION_WARNINGS /* so we can test deprecated API */
+#include <librsvg/rsvg.h>
+#include "test-utils.h"
+
+/*
+ Untested:
+ rsvg_handle_internal_set_testing
+*/
+
+static void
+handle_has_correct_type_info (void)
+{
+ GTypeQuery q;
+ RsvgHandle *handle;
+
+ g_type_query (RSVG_TYPE_HANDLE, &q);
+ g_assert (q.type == RSVG_TYPE_HANDLE);
+ g_assert (q.type == rsvg_handle_get_type ());
+
+ g_assert_cmpstr (q.type_name, ==, "RsvgHandle");
+
+ /* These test that the sizes of the structs in the header file actually match the
+ * sizes of structs and the glib-subclass machinery in the Rust side.
+ */
+ g_assert (sizeof (RsvgHandleClass) == (gsize) q.class_size);
+ g_assert (sizeof (RsvgHandle) == (gsize) q.instance_size);
+
+ handle = rsvg_handle_new();
+ g_assert (G_OBJECT_TYPE (handle) == RSVG_TYPE_HANDLE);
+ g_object_unref (handle);
+}
+
+static void
+assert_flags_value_matches (GFlagsValue *v,
+ guint value,
+ const char *value_name,
+ const char *value_nick)
+{
+ g_assert_cmpint(v->value, ==, value);
+ g_assert_cmpstr(v->value_name, ==, value_name);
+ g_assert_cmpstr(v->value_nick, ==, value_nick);
+}
+
+static void
+flags_registration (void)
+{
+ GType ty;
+ GTypeQuery q;
+ GTypeClass *type_class;
+ GFlagsClass *flags_class;
+
+ ty = RSVG_TYPE_HANDLE_FLAGS;
+
+ g_assert (ty != G_TYPE_INVALID);
+
+ g_type_query (RSVG_TYPE_HANDLE_FLAGS, &q);
+ g_assert (q.type == ty);
+ g_assert (G_TYPE_IS_FLAGS (q.type));
+ g_assert_cmpstr (q.type_name, ==, "RsvgHandleFlags");
+
+ type_class = g_type_class_ref (ty);
+ g_assert (G_IS_FLAGS_CLASS (type_class));
+ g_assert (G_FLAGS_CLASS_TYPE (type_class) == ty);
+
+ flags_class = G_FLAGS_CLASS (type_class);
+ g_assert_cmpint (flags_class->n_values, ==, 3);
+
+ assert_flags_value_matches(&flags_class->values[0],
+ RSVG_HANDLE_FLAGS_NONE,
+ "RSVG_HANDLE_FLAGS_NONE",
+ "flags-none");
+
+ assert_flags_value_matches(&flags_class->values[1],
+ RSVG_HANDLE_FLAG_UNLIMITED,
+ "RSVG_HANDLE_FLAG_UNLIMITED",
+ "flag-unlimited");
+
+ assert_flags_value_matches(&flags_class->values[2],
+ RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA,
+ "RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA",
+ "flag-keep-image-data");
+
+ g_type_class_unref (type_class);
+}
+
+static void
+assert_enum_value_matches (GEnumValue *v,
+ gint value,
+ const char *value_name,
+ const char *value_nick)
+{
+ g_assert_cmpint (v->value, ==, value);
+ g_assert_cmpstr (v->value_name, ==, value_name);
+ g_assert_cmpstr (v->value_nick, ==, value_nick);
+}
+
+static void
+error_registration (void)
+{
+ GType ty;
+ GTypeQuery q;
+ GTypeClass *type_class;
+ GEnumClass *enum_class;
+
+ g_assert_cmpint (RSVG_ERROR, !=, 0);
+
+ ty = RSVG_TYPE_ERROR;
+
+ g_assert (ty != G_TYPE_INVALID);
+
+ g_type_query (ty, &q);
+ g_assert (q.type == ty);
+ g_assert (G_TYPE_IS_ENUM (q.type));
+ g_assert_cmpstr (q.type_name, ==, "RsvgError");
+
+ type_class = g_type_class_ref (ty);
+ g_assert (G_IS_ENUM_CLASS (type_class));
+ g_assert (G_ENUM_CLASS_TYPE (type_class) == ty);
+
+ enum_class = G_ENUM_CLASS (type_class);
+ g_assert_cmpint (enum_class->n_values, ==, 1);
+
+ assert_enum_value_matches (&enum_class->values[0],
+ RSVG_ERROR_FAILED,
+ "RSVG_ERROR_FAILED",
+ "failed");
+
+ g_type_class_unref (type_class);
+}
+
+static char *
+get_test_filename (const char *basename) {
+ return g_build_filename (test_utils_get_test_data_path (),
+ "api",
+ basename,
+ NULL);
+}
+
+static RsvgHandle *
+load_test_document (const char *basename) {
+ char *filename = get_test_filename (basename);
+ GError *error = NULL;
+
+ RsvgHandle *handle = rsvg_handle_new_from_file (filename, &error);
+ g_free (filename);
+
+ g_assert_nonnull (handle);
+ g_assert_no_error (error);
+
+ return handle;
+}
+
+#define EXAMPLE_WIDTH 100
+#define EXAMPLE_HEIGHT 400
+
+#define XZOOM 2
+#define YZOOM 3
+
+#define MAX_WIDTH 10
+#define MAX_HEIGHT 40
+
+#define MAX_ZOOMED_WIDTH 20
+#define MAX_ZOOMED_HEIGHT 120
+
+#define EXAMPLE_ONE_ID "#one"
+#define EXAMPLE_TWO_ID "#two"
+#define EXAMPLE_NONEXISTENT_ID "#nonexistent"
+
+#define EXAMPLE_ONE_X 0
+#define EXAMPLE_ONE_Y 0
+#define EXAMPLE_ONE_W 100
+#define EXAMPLE_ONE_H 200
+
+#define EXAMPLE_TWO_X 0
+#define EXAMPLE_TWO_Y 200
+#define EXAMPLE_TWO_W 100
+#define EXAMPLE_TWO_H 200
+
+static GdkPixbuf *
+pixbuf_from_file (const char *filename, GError **error)
+{
+ return rsvg_pixbuf_from_file (filename, error);
+}
+
+static GdkPixbuf *
+pixbuf_from_file_at_zoom (const char *filename, GError **error)
+{
+ return rsvg_pixbuf_from_file_at_zoom (filename, (double) XZOOM, (double) YZOOM, error);
+}
+
+static GdkPixbuf *
+pixbuf_from_file_at_size (const char *filename, GError **error)
+{
+ return rsvg_pixbuf_from_file_at_size (filename, EXAMPLE_WIDTH * XZOOM, EXAMPLE_HEIGHT * YZOOM, error);
+}
+
+static GdkPixbuf *
+pixbuf_from_file_at_max_size (const char *filename, GError **error)
+{
+ return rsvg_pixbuf_from_file_at_max_size (filename, MAX_WIDTH, MAX_HEIGHT, error);
+}
+
+static GdkPixbuf *
+pixbuf_from_file_at_zoom_with_max (const char *filename, GError **error)
+{
+ return rsvg_pixbuf_from_file_at_zoom_with_max (filename,
+ XZOOM, YZOOM,
+ MAX_ZOOMED_WIDTH, MAX_ZOOMED_HEIGHT,
+ error);
+}
+
+typedef GdkPixbuf *(* PixbufCreateFn) (const char *filename, GError **error);
+
+typedef struct {
+ const char *test_name;
+ PixbufCreateFn pixbuf_create_fn;
+ int expected_width;
+ int expected_height;
+} PixbufTest;
+
+static const PixbufTest pixbuf_tests[] = {
+ {
+ "/api/pixbuf_from_file",
+ pixbuf_from_file,
+ EXAMPLE_WIDTH,
+ EXAMPLE_HEIGHT
+ },
+ {
+ "/api/pixbuf_from_file_at_zoom",
+ pixbuf_from_file_at_zoom,
+ EXAMPLE_WIDTH * XZOOM,
+ EXAMPLE_HEIGHT * YZOOM
+ },
+ {
+ "/api/pixbuf_from_file_at_size",
+ pixbuf_from_file_at_size,
+ EXAMPLE_WIDTH * XZOOM,
+ EXAMPLE_HEIGHT * YZOOM
+ },
+ {
+ "/api/pixbuf_from_file_at_max_size",
+ pixbuf_from_file_at_max_size,
+ MAX_WIDTH,
+ MAX_HEIGHT
+ },
+ {
+ "/api/pixbuf_from_file_at_zoom_with_max",
+ pixbuf_from_file_at_zoom_with_max,
+ MAX_ZOOMED_WIDTH,
+ MAX_ZOOMED_HEIGHT
+ },
+};
+
+static void
+test_pixbuf (gconstpointer data)
+{
+ const PixbufTest *test = data;
+
+ char *filename = get_test_filename ("example.svg");
+ GError *error = NULL;
+
+ GdkPixbuf *pixbuf = test->pixbuf_create_fn (filename, &error);
+
+ g_free (filename);
+
+ g_assert_nonnull (pixbuf);
+ g_assert_no_error (error);
+ g_assert_cmpint (gdk_pixbuf_get_width (pixbuf), ==, test->expected_width);
+ g_assert_cmpint (gdk_pixbuf_get_height (pixbuf), ==, test->expected_height);
+
+ g_object_unref (pixbuf);
+}
+
+static void
+pixbuf_overflow (void)
+{
+ char *filename = get_test_filename ("example.svg");
+ GError *error = NULL;
+
+ g_assert (!rsvg_pixbuf_from_file_at_zoom (filename, 1000000.0, 1000000.0, &error));
+ g_assert_error (error, RSVG_ERROR, RSVG_ERROR_FAILED);
+ g_error_free (error);
+ g_free (filename);
+}
+
+static void
+noops (void)
+{
+ /* Just to test that these functions are present in the binary, I guess */
+ rsvg_init ();
+ rsvg_term ();
+ rsvg_cleanup ();
+}
+
+static void
+noops_return_null (void)
+{
+ RsvgHandle *handle = rsvg_handle_new ();
+
+ g_assert_null (rsvg_handle_get_title (handle));
+ g_assert_null (rsvg_handle_get_desc (handle));
+ g_assert_null (rsvg_handle_get_metadata (handle));
+
+ g_object_unref (handle);
+}
+
+static void
+set_dpi (void)
+{
+ RsvgHandle *handle;
+ RsvgDimensionData dim;
+
+ rsvg_set_default_dpi (100.0);
+
+ handle = load_test_document ("dpi.svg");
+
+ rsvg_handle_get_dimensions (handle, &dim);
+ g_assert_cmpint (dim.width, ==, 100);
+ g_assert_cmpint (dim.height, ==, 400);
+
+ rsvg_handle_set_dpi (handle, 200.0);
+ rsvg_handle_get_dimensions (handle, &dim);
+ g_assert_cmpint (dim.width, ==, 200);
+ g_assert_cmpint (dim.height, ==, 800);
+ g_object_unref (handle);
+
+ handle = load_test_document ("dpi.svg");
+
+ rsvg_handle_set_dpi_x_y (handle, 400.0, 300.0);
+ rsvg_handle_get_dimensions (handle, &dim);
+ g_assert_cmpint (dim.width, ==, 400);
+ g_assert_cmpint (dim.height, ==, 1200);
+ g_object_unref (handle);
+}
+
+static void
+base_uri (void)
+{
+ RsvgHandle *handle = rsvg_handle_new ();
+ const char *uri;
+
+ uri = rsvg_handle_get_base_uri (handle);
+ g_assert_null (uri);
+
+ rsvg_handle_set_base_uri (handle, "file:///foo/bar.svg");
+ uri = rsvg_handle_get_base_uri (handle);
+
+ g_assert_cmpstr (uri, ==, "file:///foo/bar.svg");
+
+ g_object_unref (handle);
+}
+
+static void
+base_gfile (void)
+{
+ RsvgHandle *handle = rsvg_handle_new ();
+ GFile *file;
+ const char *uri;
+
+ uri = rsvg_handle_get_base_uri (handle);
+ g_assert_null (uri);
+
+ file = g_file_new_for_uri ("file:///foo/bar.svg");
+
+ rsvg_handle_set_base_gfile (handle, file);
+ uri = rsvg_handle_get_base_uri (handle);
+
+ g_assert_cmpstr (uri, ==, "file:///foo/bar.svg");
+
+ g_object_unref (file);
+ g_object_unref (handle);
+}
+
+static void
+handle_write_close_free (void)
+{
+ char *filename = get_test_filename ("dpi.svg");
+ char *data;
+ gsize length;
+ gsize i;
+ GError *error = NULL;
+
+ g_assert (g_file_get_contents (filename, &data, &length, &error));
+ g_free (filename);
+
+ g_assert_nonnull (data);
+ g_assert_no_error (error);
+
+ RsvgHandle *handle = rsvg_handle_new_with_flags (RSVG_HANDLE_FLAGS_NONE);
+
+ for (i = 0; i < length; i++) {
+ g_assert (rsvg_handle_write (handle, (guchar *) &data[i], 1, &error));
+ g_assert_no_error (error);
+ }
+
+ g_assert (rsvg_handle_close (handle, &error));
+ g_assert_no_error (error);
+
+ /* Test that close() is idempotent in the happy case */
+ g_assert (rsvg_handle_close (handle, &error));
+ g_assert_no_error (error);
+
+ rsvg_handle_free (handle);
+ g_free (data);
+}
+
+static void
+handle_new_from_file (void)
+{
+ char *filename = get_test_filename ("dpi.svg");
+ char *uri = g_strconcat ("file://", filename, NULL);
+
+ RsvgHandle *handle;
+ GError *error = NULL;
+
+ /* rsvg_handle_new_from_file() can take both filenames and URIs */
+
+ handle = rsvg_handle_new_from_file (filename, &error);
+ g_assert_nonnull (handle);
+ g_assert_no_error (error);
+ g_object_unref (handle);
+
+ handle = rsvg_handle_new_from_file (uri, &error);
+ g_assert_nonnull (handle);
+ g_assert_no_error (error);
+ g_object_unref (handle);
+
+ g_free (filename);
+ g_free (uri);
+}
+
+static void
+handle_new_from_data (void)
+{
+ char *filename = get_test_filename ("dpi.svg");
+ char *data;
+ gsize length;
+ GError *error = NULL;
+
+ g_assert (g_file_get_contents (filename, &data, &length, &error));
+ g_free (filename);
+
+ g_assert_nonnull (data);
+ g_assert_no_error (error);
+
+ RsvgHandle *handle = rsvg_handle_new_from_data ((guint8 *) data, length, &error);
+ g_assert_nonnull (handle);
+ g_assert_no_error (error);
+
+ g_object_unref (handle);
+ g_free (data);
+}
+
+static void
+handle_new_from_gfile_sync (void)
+{
+ char *filename = get_test_filename ("dpi.svg");
+ GError *error = NULL;
+ GFile *file = g_file_new_for_path (filename);
+ g_assert_nonnull (file);
+
+ g_free (filename);
+
+ RsvgHandle *handle = rsvg_handle_new_from_gfile_sync (file,
+ RSVG_HANDLE_FLAGS_NONE,
+ NULL,
+ &error);
+
+ g_assert_nonnull (handle);
+ g_assert_no_error (error);
+
+ g_object_unref (handle);
+ g_object_unref (file);
+}
+
+static void
+handle_new_from_stream_sync (void)
+{
+ char *filename = get_test_filename ("dpi.svg");
+ GError *error = NULL;
+ GFile *file = g_file_new_for_path (filename);
+ g_assert_nonnull (file);
+
+ g_free (filename);
+
+ GFileInputStream *stream = g_file_read (file, NULL, &error);
+ g_assert (stream != NULL);
+ g_assert_no_error (error);
+
+ RsvgHandle *handle = rsvg_handle_new_from_stream_sync (G_INPUT_STREAM (stream),
+ file,
+ RSVG_HANDLE_FLAGS_NONE,
+ NULL,
+ &error);
+
+ g_assert_nonnull (handle);
+ g_assert_no_error (error);
+
+ g_object_unref (handle);
+ g_object_unref (file);
+ g_object_unref (stream);
+}
+
+static void
+handle_read_stream_sync (void)
+{
+ char *filename = get_test_filename ("dpi.svg");
+ GError *error = NULL;
+ GFile *file = g_file_new_for_path (filename);
+ g_assert_nonnull (file);
+
+ g_free (filename);
+
+ GFileInputStream *stream = g_file_read (file, NULL, &error);
+ g_assert_nonnull (stream);
+ g_assert_no_error (error);
+
+ RsvgHandle *handle = rsvg_handle_new ();
+
+ g_assert (rsvg_handle_read_stream_sync (handle, G_INPUT_STREAM (stream), NULL, &error));
+ g_assert_no_error (error);
+
+ g_object_unref (handle);
+ g_object_unref (file);
+ g_object_unref (stream);
+}
+
+static void
+handle_has_sub (void)
+{
+ RsvgHandle *handle = load_test_document ("example.svg");
+
+ g_assert (rsvg_handle_has_sub (handle, EXAMPLE_ONE_ID));
+ g_assert (rsvg_handle_has_sub (handle, EXAMPLE_TWO_ID));
+ g_assert (!rsvg_handle_has_sub (handle, "#foo"));
+
+ g_object_unref (handle);
+}
+
+static void
+test_get_pixbuf (gboolean sub)
+{
+ RsvgHandle *handle = load_test_document ("example.svg");
+
+ GdkPixbuf *pixbuf;
+ if (sub) {
+ pixbuf = rsvg_handle_get_pixbuf_sub (handle, EXAMPLE_ONE_ID);
+ } else {
+ pixbuf = rsvg_handle_get_pixbuf (handle);
+ }
+
+ g_assert_nonnull (pixbuf);
+
+ /* Note that rsvg_handle_get_pixbuf_sub() creates a surface the size of the
+ * whole SVG, not just the size of the sub-element.
+ */
+ g_assert_cmpint (gdk_pixbuf_get_width (pixbuf), ==, EXAMPLE_WIDTH);
+ g_assert_cmpint (gdk_pixbuf_get_height (pixbuf), ==, EXAMPLE_HEIGHT);
+
+ cairo_surface_t *surface_a = test_utils_cairo_surface_from_pixbuf (pixbuf);
+ cairo_surface_t *surface_b = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, EXAMPLE_WIDTH, EXAMPLE_HEIGHT);
+ cairo_surface_t *surface_diff = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, EXAMPLE_WIDTH, EXAMPLE_HEIGHT);
+
+ g_object_unref (pixbuf);
+
+ g_assert_nonnull (surface_a);
+ g_assert_nonnull (surface_b);
+ g_assert_nonnull (surface_diff);
+
+ cairo_t *cr = cairo_create (surface_b);
+ if (sub) {
+ g_assert (rsvg_handle_render_cairo_sub (handle, cr, EXAMPLE_ONE_ID));
+ } else {
+ g_assert (rsvg_handle_render_cairo (handle, cr));
+ }
+ cairo_destroy (cr);
+
+ g_object_unref (handle);
+
+ TestUtilsBufferDiffResult result = {0, 0};
+ test_utils_compare_surfaces (surface_a, surface_b, surface_diff, &result);
+
+ if (result.pixels_changed && result.max_diff > 0) {
+ g_test_fail ();
+ }
+
+ cairo_surface_destroy (surface_a);
+ cairo_surface_destroy (surface_b);
+ cairo_surface_destroy (surface_diff);
+}
+
+static void
+handle_get_pixbuf (void)
+{
+ test_get_pixbuf (FALSE);
+}
+
+static void
+handle_get_pixbuf_sub (void)
+{
+ test_get_pixbuf (TRUE);
+}
+
+static void
+dimensions_and_position (void)
+{
+ RsvgHandle *handle = load_test_document ("example.svg");
+ RsvgDimensionData dim;
+
+ g_assert (rsvg_handle_get_dimensions_sub (handle, &dim, EXAMPLE_TWO_ID));
+ g_assert_cmpint (dim.width, ==, EXAMPLE_TWO_W);
+ g_assert_cmpint (dim.height, ==, EXAMPLE_TWO_H);
+
+ RsvgPositionData pos;
+ g_assert (rsvg_handle_get_position_sub (handle, &pos, EXAMPLE_TWO_ID));
+ g_assert_cmpint (pos.x, ==, EXAMPLE_TWO_X);
+ g_assert_cmpint (pos.y, ==, EXAMPLE_TWO_Y);
+
+ g_assert_false (rsvg_handle_get_position_sub (handle, &pos, EXAMPLE_NONEXISTENT_ID));
+ g_assert_false (rsvg_handle_get_dimensions_sub (handle, &dim, EXAMPLE_NONEXISTENT_ID));
+
+ /* Asking for "position of the whole SVG" (id=NULL) always returns (0, 0) */
+ g_assert (rsvg_handle_get_position_sub (handle, &pos, NULL));
+ g_assert_cmpint (pos.x, ==, 0);
+ g_assert_cmpint (pos.y, ==, 0);
+
+ g_object_unref (handle);
+}
+
+struct size_func_data
+{
+ gboolean called;
+ gboolean destroyed;
+ gboolean testing_size_func_calls;
+};
+
+static void
+size_func (gint *width, gint *height, gpointer user_data)
+{
+ struct size_func_data *data = user_data;
+
+ if (data->testing_size_func_calls) {
+ g_assert_false (data->called);
+ data->called = TRUE;
+
+ g_assert_false (data->destroyed);
+ }
+
+ *width = 42;
+ *height = 43;
+}
+
+static void
+size_func_destroy (gpointer user_data)
+{
+ struct size_func_data *data = user_data;
+
+ if (data->testing_size_func_calls) {
+ g_assert_false (data->destroyed);
+ data->destroyed = TRUE;
+ }
+}
+
+static void
+set_size_callback (void)
+{
+ RsvgHandle *handle;
+ struct size_func_data data;
+ RsvgDimensionData dim;
+
+ handle = load_test_document ("example.svg");
+
+ data.called = FALSE;
+ data.destroyed = FALSE;
+ data.testing_size_func_calls = TRUE;
+
+ rsvg_handle_set_size_callback (handle, size_func, &data, size_func_destroy);
+
+ rsvg_handle_get_dimensions (handle, &dim);
+ g_assert_cmpint (dim.width, ==, 42);
+ g_assert_cmpint (dim.height, ==, 43);
+
+ g_object_unref (handle);
+
+ g_assert_true (data.called);
+ g_assert_true (data.destroyed);
+}
+
+static void
+reset_size_callback (void)
+{
+ RsvgHandle *handle;
+ struct size_func_data data_1;
+ struct size_func_data data_2;
+
+ handle = load_test_document ("example.svg");
+
+ data_1.called = FALSE;
+ data_1.destroyed = FALSE;
+ data_1.testing_size_func_calls = TRUE;
+
+ rsvg_handle_set_size_callback (handle, size_func, &data_1, size_func_destroy);
+
+ data_2.called = FALSE;
+ data_2.destroyed = FALSE;
+ data_2.testing_size_func_calls = TRUE;
+
+ rsvg_handle_set_size_callback (handle, size_func, &data_2, size_func_destroy);
+ g_assert_true (data_1.destroyed);
+
+ g_object_unref (handle);
+
+ g_assert_true (data_2.destroyed);
+}
+
+static void
+zero_size_func (gint *width, gint *height, gpointer user_data)
+{
+ *width = 0;
+ *height = 0;
+}
+
+static void
+render_with_zero_size_callback (void)
+{
+ /* gdk_pixbuf_get_file_info() uses a GdkPixbufLoader, but in its
+ * "size-prepared" callback it saves the computed size, and then calls
+ * gdk_pixbuf_loader_set_size(loader, 0, 0). Presumably it does to tell
+ * loaders that it only wanted to know the size, but that they shouldn't
+ * decode or render the image to a pixbuf buffer.
+ *
+ * Librsvg used to panic when getting (0, 0) from the size_callback; this
+ * test is to check that there is no such crash now. Instead, librsvg
+ * will return a 1x1 transparent pixbuf.
+ */
+ RsvgHandle *handle;
+ GdkPixbuf *pixbuf;
+
+ handle = load_test_document ("example.svg");
+
+ rsvg_handle_set_size_callback (handle, zero_size_func, NULL, NULL);
+
+ pixbuf = rsvg_handle_get_pixbuf (handle);
+ g_assert_nonnull (pixbuf);
+ g_assert_cmpint (gdk_pixbuf_get_width (pixbuf), ==, 1);
+ g_assert_cmpint (gdk_pixbuf_get_height (pixbuf), ==, 1);
+
+ g_object_unref (pixbuf);
+ g_object_unref (handle);
+}
+
+static void
+pixbuf_size_func (gint *width, gint *height, gpointer user_data)
+{
+ *width = 420;
+ *height = 430;
+}
+
+static void
+get_pixbuf_with_size_callback (void)
+{
+ RsvgHandle *handle = rsvg_handle_new ();
+
+ rsvg_handle_set_size_callback (handle, pixbuf_size_func, NULL, NULL);
+
+ char *filename = get_test_filename ("example.svg");
+ guchar *data = NULL;
+ gsize length;
+ GError *error = NULL;
+
+ g_assert (g_file_get_contents (filename, (gchar **) &data, &length, &error));
+ g_assert_nonnull (data);
+
+ g_free (filename);
+
+ g_assert (rsvg_handle_write (handle, data, length, &error));
+ g_assert_no_error (error);
+
+ g_assert (rsvg_handle_close (handle, &error));
+ g_assert_no_error (error);
+
+ GdkPixbuf *pixbuf = rsvg_handle_get_pixbuf (handle);
+ g_assert_nonnull (pixbuf);
+ g_assert_cmpint (gdk_pixbuf_get_width (pixbuf), ==, 420);
+ g_assert_cmpint (gdk_pixbuf_get_height (pixbuf), ==, 430);
+
+ g_object_unref (pixbuf);
+ g_free (data);
+ g_object_unref (handle);
+}
+
+static void
+detects_cairo_context_in_error (void)
+{
+ if (g_test_subprocess ()) {
+ RsvgHandle *handle = load_test_document ("example.svg");
+
+ /* this is wrong; it is to simulate creating a surface and a cairo_t in error */
+ cairo_surface_t *surf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, -1, -1);
+ cairo_t *cr = cairo_create (surf);
+ /* rsvg_handle_render_cairo() should return FALSE when it gets a cr in an error state */
+ g_assert_false (rsvg_handle_render_cairo (handle, cr));
+
+ return;
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*WARNING*cannot render on a cairo_t with a failure status*");
+}
+
+static gboolean
+matrixes_are_equal (cairo_matrix_t *a, cairo_matrix_t *b)
+{
+ return (a->xx == b->xx &&
+ a->yx == b->yx &&
+ a->xy == b->xy &&
+ a->yy == b->yy &&
+ a->x0 == b->x0 &&
+ a->y0 == b->y0);
+}
+
+static void
+can_draw_to_non_image_surface (void)
+{
+ cairo_rectangle_t rect;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ RsvgHandle *handle = load_test_document ("example.svg");
+
+ rect.x = 0.0;
+ rect.y = 0.0;
+ rect.width = 100.0;
+ rect.height = 100.0;
+
+ /* We create a surface that is not a Cairo image surface,
+ * so we can test that in fact we can render to non-image surfaces.
+ */
+ surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, &rect);
+ cr = cairo_create (surface);
+
+ cairo_translate (cr, 42.0, 42.0);
+
+ cairo_matrix_t original_affine;
+ cairo_get_matrix (cr, &original_affine);
+
+ g_assert (rsvg_handle_render_cairo (handle, cr));
+
+ cairo_matrix_t new_affine;
+ cairo_get_matrix (cr, &new_affine);
+
+ g_assert (matrixes_are_equal (&original_affine, &new_affine));
+
+ g_object_unref (handle);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+}
+
+/* Test that we preserve the affine transformation in the cr during a call
+ * to rsvg_handle_render_cairo_sub().
+ */
+static void
+render_cairo_sub (void)
+{
+ RsvgHandle *handle = load_test_document ("bug334-element-positions.svg");
+
+ cairo_surface_t *surf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 200, 200);
+ cairo_t *cr = cairo_create (surf);
+
+ cairo_translate (cr, 42.0, 42.0);
+
+ cairo_matrix_t original_affine;
+ cairo_get_matrix (cr, &original_affine);
+
+ g_assert (rsvg_handle_render_cairo_sub (handle, cr, "#button5-leader"));
+
+ cairo_matrix_t new_affine;
+ cairo_get_matrix (cr, &new_affine);
+
+ g_assert (matrixes_are_equal (&original_affine, &new_affine));
+
+ g_object_unref (handle);
+ cairo_destroy (cr);
+ cairo_surface_destroy (surf);
+}
+
+static void
+get_intrinsic_dimensions (void)
+{
+ RsvgHandle *handle = load_test_document ("example.svg");
+
+ gboolean has_width;
+ RsvgLength width;
+ gboolean has_height;
+ RsvgLength height;
+ gboolean has_viewbox;
+ RsvgRectangle viewbox;
+
+ rsvg_handle_get_intrinsic_dimensions (handle, &has_width, &width, &has_height, &height, &has_viewbox, &viewbox);
+
+ g_assert (has_width);
+ g_assert_cmpfloat (width.length, ==, 100.0);
+ g_assert (width.unit == RSVG_UNIT_PX);
+
+ g_assert (has_height);
+ g_assert_cmpfloat (height.length, ==, 400.0);
+ g_assert (height.unit == RSVG_UNIT_PX);
+
+ g_assert (has_viewbox);
+ g_assert_cmpfloat (viewbox.x, ==, 0.0);
+ g_assert_cmpfloat (viewbox.y, ==, 0.0);
+ g_assert_cmpfloat (viewbox.width, ==, 100.0);
+ g_assert_cmpfloat (viewbox.height, ==, 400.0);
+
+ g_object_unref (handle);
+}
+
+static void
+get_intrinsic_dimensions_missing_values (void)
+{
+ RsvgHandle *handle = load_test_document ("no-viewbox.svg");
+
+ gboolean has_width;
+ RsvgLength width;
+ gboolean has_height;
+ RsvgLength height;
+ gboolean has_viewbox;
+ RsvgRectangle viewbox;
+
+ rsvg_handle_get_intrinsic_dimensions (handle, &has_width, &width, &has_height, &height, &has_viewbox, &viewbox);
+ g_assert_true (has_width);
+ g_assert_true (has_height);
+ g_assert_false (has_viewbox);
+ g_object_unref (handle);
+}
+
+static void
+get_intrinsic_size_in_pixels_yes (void)
+{
+ RsvgHandle *handle = load_test_document ("size.svg");
+ gdouble width, height;
+
+ rsvg_handle_set_dpi (handle, 96.0);
+
+ /* Test optional parameters */
+ g_assert (rsvg_handle_get_intrinsic_size_in_pixels (handle, NULL, NULL));
+
+ /* Test the actual result */
+ g_assert (rsvg_handle_get_intrinsic_size_in_pixels (handle, &width, &height));
+ g_assert_cmpfloat (width, ==, 192.0);
+ g_assert_cmpfloat (height, ==, 288.0);
+
+ g_object_unref (handle);
+}
+
+static void
+get_intrinsic_size_in_pixels_no (void)
+{
+ RsvgHandle *handle = load_test_document ("no-size.svg");
+ gdouble width, height;
+
+ rsvg_handle_set_dpi (handle, 96.0);
+ g_assert (!rsvg_handle_get_intrinsic_size_in_pixels (handle, &width, &height));
+ g_assert_cmpfloat (width, ==, 0.0);
+ g_assert_cmpfloat (height, ==, 0.0);
+
+ g_object_unref (handle);
+}
+
+static void
+set_stylesheet (void)
+{
+ const char *css = "rect { fill: #00ff00; }";
+
+ RsvgHandle *handle = load_test_document ("stylesheet.svg");
+ RsvgHandle *ref_handle = load_test_document ("stylesheet-ref.svg");
+
+ cairo_surface_t *output = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 100, 100);
+ cairo_surface_t *reference = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 100, 100);
+
+ RsvgRectangle viewport = { 0.0, 0.0, 100.0, 100.0 };
+
+ cairo_t *output_cr = cairo_create (output);
+ cairo_t *ref_cr = cairo_create (reference);
+
+ GError *error = NULL;
+ g_assert (rsvg_handle_set_stylesheet (handle, (const guint8 *) css, strlen (css), &error));
+ g_assert_no_error (error);
+
+ g_assert (rsvg_handle_render_document (handle, output_cr, &viewport, &error));
+ g_assert_no_error (error);
+
+ g_assert (rsvg_handle_render_document (ref_handle, ref_cr, &viewport, &error));
+ g_assert_no_error (error);
+
+ cairo_surface_t *diff = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 100, 100);
+
+ TestUtilsBufferDiffResult result = {0, 0};
+ test_utils_compare_surfaces (output, reference, diff, &result);
+
+ if (result.pixels_changed && result.max_diff > 0) {
+ g_test_fail ();
+ }
+
+ cairo_surface_destroy (diff);
+ cairo_destroy (ref_cr);
+ cairo_destroy (output_cr);
+ cairo_surface_destroy (reference);
+ cairo_surface_destroy (output);
+ g_object_unref (ref_handle);
+ g_object_unref (handle);
+}
+
+static void
+render_document (void)
+{
+ RsvgHandle *handle = load_test_document ("document.svg");
+
+ cairo_surface_t *output = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 150, 150);
+ cairo_t *cr = cairo_create (output);
+
+ RsvgRectangle viewport = { 50.0, 50.0, 50.0, 50.0 };
+
+ GError *error = NULL;
+ g_assert (rsvg_handle_render_document (handle, cr, &viewport, &error));
+ g_assert_no_error (error);
+
+ cairo_destroy (cr);
+
+ cairo_surface_t *expected = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 150, 150);
+ cr = cairo_create (expected);
+
+ cairo_translate (cr, 50.0, 50.0);
+ cairo_rectangle (cr, 10.0, 10.0, 30.0, 30.0);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.5);
+ cairo_fill (cr);
+ cairo_destroy (cr);
+
+ cairo_surface_t *diff = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 150, 150);
+
+ TestUtilsBufferDiffResult result = {0, 0};
+ test_utils_compare_surfaces (output, expected, diff, &result);
+
+ if (result.pixels_changed && result.max_diff > 0) {
+ g_test_fail ();
+ }
+
+ cairo_surface_destroy (diff);
+ cairo_surface_destroy (expected);
+ cairo_surface_destroy (output);
+ g_object_unref (handle);
+}
+
+static void
+get_geometry_for_layer (void)
+{
+ RsvgHandle *handle = load_test_document ("geometry.svg");
+
+ RsvgRectangle viewport = { 0.0, 0.0, 100.0, 400.0 };
+ RsvgRectangle ink_rect;
+ RsvgRectangle logical_rect;
+
+ GError *error = NULL;
+
+ g_assert_false (rsvg_handle_get_geometry_for_layer (handle, "#nonexistent", &viewport,
+ &ink_rect, &logical_rect, &error));
+ g_assert_nonnull (error);
+
+ g_clear_error (&error);
+
+ g_assert (rsvg_handle_get_geometry_for_layer (handle, "#two", &viewport,
+ &ink_rect, &logical_rect, &error));
+ g_assert_no_error (error);
+
+ g_assert_cmpfloat (ink_rect.x, ==, 5.0);
+ g_assert_cmpfloat (ink_rect.y, ==, 195.0);
+ g_assert_cmpfloat (ink_rect.width, ==, 90.0);
+ g_assert_cmpfloat (ink_rect.height, ==, 110.0);
+
+ g_assert_cmpfloat (logical_rect.x, ==, 10.0);
+ g_assert_cmpfloat (logical_rect.y, ==, 200.0);
+ g_assert_cmpfloat (logical_rect.width, ==, 80.0);
+ g_assert_cmpfloat (logical_rect.height, ==, 100.0);
+
+ g_object_unref (handle);
+}
+
+static void
+render_layer (void)
+{
+ RsvgHandle *handle = load_test_document ("layers.svg");
+
+ cairo_surface_t *output = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 300, 300);
+ cairo_t *cr = cairo_create (output);
+
+ RsvgRectangle viewport = { 100.0, 100.0, 100.0, 100.0 };
+
+ GError *error = NULL;
+
+ g_assert (rsvg_handle_render_layer (handle, cr, "#bar", &viewport, &error));
+ g_assert_no_error (error);
+
+ cairo_destroy (cr);
+
+ cairo_surface_t *expected = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 300, 300);
+ cr = cairo_create (expected);
+
+ cairo_translate (cr, 100.0, 100.0);
+ cairo_rectangle (cr, 20.0, 20.0, 30.0, 30.0);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 1.0);
+ cairo_fill (cr);
+ cairo_destroy (cr);
+
+ cairo_surface_t *diff = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 300, 300);
+
+ TestUtilsBufferDiffResult result = {0, 0};
+ test_utils_compare_surfaces (output, expected, diff, &result);
+
+ if (result.pixels_changed && result.max_diff > 0) {
+ g_test_fail ();
+ }
+
+ cairo_surface_destroy (diff);
+ cairo_surface_destroy (expected);
+ cairo_surface_destroy (output);
+ g_object_unref (handle);
+}
+
+static void
+untransformed_element (void)
+{
+ RsvgHandle *handle = load_test_document ("geometry-element.svg");
+
+ RsvgRectangle ink_rect;
+ RsvgRectangle logical_rect;
+
+ GError *error = NULL;
+
+ g_assert (!rsvg_handle_get_geometry_for_element (handle, "#nonexistent",
+ &ink_rect, &logical_rect, &error));
+ g_assert_nonnull (error);
+
+ g_clear_error (&error);
+
+ g_assert (rsvg_handle_get_geometry_for_element (handle, "#foo",
+ &ink_rect, &logical_rect, &error));
+ g_assert_no_error (error);
+
+ g_assert_cmpfloat (ink_rect.x, ==, 0.0);
+ g_assert_cmpfloat (ink_rect.y, ==, 0.0);
+ g_assert_cmpfloat (ink_rect.width, ==, 40.0);
+ g_assert_cmpfloat (ink_rect.height, ==, 50.0);
+
+ g_assert_cmpfloat (logical_rect.x, ==, 5.0);
+ g_assert_cmpfloat (logical_rect.y, ==, 5.0);
+ g_assert_cmpfloat (logical_rect.width, ==, 30.0);
+ g_assert_cmpfloat (logical_rect.height, ==, 40.0);
+
+ cairo_surface_t *output = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 300, 300);
+ cairo_t *cr = cairo_create (output);
+
+ RsvgRectangle viewport = { 100.0, 100.0, 100.0, 100.0 };
+
+ g_assert (rsvg_handle_render_element (handle, cr, "#foo", &viewport, &error));
+ g_assert_no_error (error);
+
+ cairo_destroy (cr);
+
+ cairo_surface_t *expected = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 300, 300);
+ cr = cairo_create (expected);
+
+ cairo_translate (cr, 100.0, 100.0);
+ cairo_rectangle (cr, 10.0, 10.0, 60.0, 80.0);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 1.0);
+ cairo_fill_preserve (cr);
+
+ cairo_set_line_width (cr, 20.0);
+ cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_stroke (cr);
+
+ cairo_destroy (cr);
+
+ cairo_surface_t *diff = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 300, 300);
+
+ TestUtilsBufferDiffResult result = {0, 0};
+ test_utils_compare_surfaces (output, expected, diff, &result);
+
+ if (result.pixels_changed && result.max_diff > 0) {
+ g_test_fail ();
+ }
+
+ cairo_surface_destroy (diff);
+ cairo_surface_destroy (expected);
+ cairo_surface_destroy (output);
+ g_object_unref (handle);
+}
+
+/* https://gitlab.gnome.org/GNOME/librsvg/issues/385 */
+static void
+no_write_before_close (void)
+{
+ RsvgHandle *handle = rsvg_handle_new();
+ GError *error = NULL;
+
+ g_assert_false (rsvg_handle_close (handle, &error));
+ g_assert_error (error, RSVG_ERROR, RSVG_ERROR_FAILED);
+ g_error_free (error);
+ error = NULL;
+
+ /* Test that close() is idempotent in the error case */
+ g_assert (rsvg_handle_close (handle, &error));
+ g_assert_no_error (error);
+
+ g_object_unref (handle);
+}
+
+static void
+empty_write_close (void)
+{
+ RsvgHandle *handle = rsvg_handle_new();
+ GError *error = NULL;
+ guchar buf = 0;
+
+ g_assert_true (rsvg_handle_write (handle, &buf, 0, &error));
+ g_assert_no_error (error);
+
+ g_assert_false (rsvg_handle_close (handle, &error));
+ g_assert_error (error, RSVG_ERROR, RSVG_ERROR_FAILED);
+
+ g_error_free (error);
+
+ g_object_unref (handle);
+}
+
+static void
+cannot_request_external_elements (void)
+{
+ /* We want to test that using one of the _sub() functions will fail
+ * if the element's id is within an external file.
+ */
+
+ RsvgHandle *handle = load_test_document ("example.svg");
+ RsvgPositionData pos;
+
+ g_assert_false (rsvg_handle_get_position_sub (handle, &pos, "dpi.svg#one"));
+
+ g_object_unref (handle);
+}
+
+static void
+test_flags (RsvgHandleFlags flags)
+{
+ guint read_flags;
+
+ RsvgHandle *handle = g_object_new (RSVG_TYPE_HANDLE,
+ "flags", flags,
+ NULL);
+ g_object_get (handle, "flags", &read_flags, NULL);
+ g_assert (read_flags == flags);
+
+ g_object_unref (handle);
+}
+
+static void
+property_flags (void)
+{
+ test_flags (RSVG_HANDLE_FLAGS_NONE);
+ test_flags (RSVG_HANDLE_FLAG_UNLIMITED);
+ test_flags (RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA);
+ test_flags (RSVG_HANDLE_FLAG_UNLIMITED | RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA);
+}
+
+static void
+property_dpi (void)
+{
+ RsvgHandle *handle = g_object_new (RSVG_TYPE_HANDLE,
+ "dpi-x", 42.0,
+ "dpi-y", 43.0,
+ NULL);
+ double x, y;
+
+ g_object_get (handle,
+ "dpi-x", &x,
+ "dpi-y", &y,
+ NULL);
+
+ g_assert_cmpfloat (x, ==, 42.0);
+ g_assert_cmpfloat (y, ==, 43.0);
+
+ g_object_unref (handle);
+}
+
+static void
+property_base_uri (void)
+{
+ RsvgHandle *handle = g_object_new (RSVG_TYPE_HANDLE,
+ "base-uri", "file:///foo/bar.svg",
+ NULL);
+ char *uri;
+
+ g_object_get (handle,
+ "base-uri", &uri,
+ NULL);
+
+ g_assert_cmpstr (uri, ==, "file:///foo/bar.svg");
+ g_free (uri);
+
+ g_object_unref (handle);
+}
+
+static void
+property_dimensions (void)
+{
+ RsvgHandle *handle = load_test_document ("example.svg");
+
+ int width;
+ int height;
+ double em;
+ double ex;
+
+ g_object_get (handle,
+ "width", &width,
+ "height", &height,
+ "em", &em,
+ "ex", &ex,
+ NULL);
+
+ g_assert_cmpint (width, ==, EXAMPLE_WIDTH);
+ g_assert_cmpint (height, ==, EXAMPLE_HEIGHT);
+
+ g_assert_cmpfloat (em, ==, (double) EXAMPLE_WIDTH);
+ g_assert_cmpfloat (ex, ==, (double) EXAMPLE_HEIGHT);
+
+ g_object_unref (handle);
+}
+
+static void
+property_deprecated (void)
+{
+ RsvgHandle *handle = load_test_document ("example.svg");
+
+ char *title;
+ char *desc;
+ char *metadata;
+
+ g_object_get (handle,
+ "title", &title,
+ "desc", &desc,
+ "metadata", &metadata,
+ NULL);
+
+ g_assert_null (title);
+ g_assert_null (desc);
+ g_assert_null (metadata);
+
+ g_object_unref (handle);
+}
+
+static void
+return_if_fail (void)
+{
+ if (g_test_subprocess ()) {
+ RsvgHandle *handle;
+
+ handle = rsvg_handle_new();
+ g_assert_nonnull (handle);
+
+ /* NULL is an invalid argument... */
+ rsvg_handle_set_base_uri (handle, NULL);
+ g_object_unref (handle);
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ /* ... and here we catch that it was validated */
+ g_test_trap_assert_stderr ("*rsvg_handle_set_base_uri*assertion*failed*");
+}
+
+static void
+return_if_fail_null_check (void)
+{
+ if (g_test_subprocess ()) {
+ /* Pass NULL as an argument, incorrectly... */
+ g_assert_null (rsvg_handle_get_base_uri (NULL));
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ /* ... and here we catch that it was validated */
+ g_test_trap_assert_stderr ("*rsvg_handle_get_base_uri*assertion*handle*failed*");
+}
+
+static void
+return_if_fail_type_check (void)
+{
+ if (g_test_subprocess ()) {
+ /* Create a random GObject that is not an RsvgHandle... */
+ GInputStream *stream = g_memory_input_stream_new();
+
+ /* Feed it to an RsvgHandle function so it will bail out */
+ g_assert_null (rsvg_handle_get_base_uri ((RsvgHandle *) stream));
+
+ g_object_unref (stream);
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ /* ... and here we catch that it was validated */
+ g_test_trap_assert_stderr ("*rsvg_handle_get_base_uri*assertion*handle*failed*");
+}
+
+static void
+library_version_defines (void)
+{
+ gchar *version = g_strdup_printf ("%u.%u.%u",
+ LIBRSVG_MAJOR_VERSION, LIBRSVG_MINOR_VERSION, LIBRSVG_MICRO_VERSION);
+ g_assert_cmpstr (version, ==, LIBRSVG_VERSION);
+ g_free (version);
+}
+
+static void
+library_version_check (void)
+{
+ g_assert_true(LIBRSVG_CHECK_VERSION(1, 99, 9));
+ g_assert_true(LIBRSVG_CHECK_VERSION(2, 0, 0));
+ g_assert_true(LIBRSVG_CHECK_VERSION(2, 50, 7));
+ g_assert_false(LIBRSVG_CHECK_VERSION(2, 99, 0));
+ g_assert_false(LIBRSVG_CHECK_VERSION(3, 0, 0));
+}
+
+static void
+library_version_constants (void)
+{
+ g_assert_cmpuint (rsvg_major_version, ==, LIBRSVG_MAJOR_VERSION);
+ g_assert_cmpuint (rsvg_minor_version, ==, LIBRSVG_MINOR_VERSION);
+ g_assert_cmpuint (rsvg_micro_version, ==, LIBRSVG_MICRO_VERSION);
+}
+
+typedef struct
+{
+ const gchar *test_name;
+ const gchar *file_path;
+ const gchar *id;
+ gdouble x;
+ gdouble y;
+ gdouble width;
+ gdouble height;
+ gboolean has_position;
+ gboolean has_dimensions;
+} DimensionsFixtureData;
+
+static void
+test_dimensions (DimensionsFixtureData *fixture)
+{
+ RsvgHandle *handle;
+ RsvgPositionData position;
+ RsvgDimensionData dimension;
+ gchar *target_file;
+ GError *error = NULL;
+
+ target_file = g_build_filename (test_utils_get_test_data_path (),
+ fixture->file_path, NULL);
+ handle = rsvg_handle_new_from_file (target_file, &error);
+ g_free (target_file);
+ g_assert_no_error (error);
+
+ if (fixture->id) {
+ g_assert (rsvg_handle_has_sub (handle, fixture->id));
+ g_assert (rsvg_handle_get_position_sub (handle, &position, fixture->id));
+ g_assert (rsvg_handle_get_dimensions_sub (handle, &dimension, fixture->id));
+ } else {
+ rsvg_handle_get_dimensions (handle, &dimension);
+ }
+
+ if (fixture->has_position) {
+ g_assert_cmpint (fixture->x, ==, position.x);
+ g_assert_cmpint (fixture->y, ==, position.y);
+ }
+
+ if (fixture->has_dimensions) {
+ g_assert_cmpint (fixture->width, ==, dimension.width);
+ g_assert_cmpint (fixture->height, ==, dimension.height);
+ }
+
+ g_object_unref (handle);
+}
+
+static DimensionsFixtureData dimensions_fixtures[] =
+{
+ {
+ "/dimensions/viewbox_only",
+ "dimensions/bug608102.svg",
+ NULL,
+ 0, 0, 16, 16,
+ FALSE, TRUE
+ },
+ {
+ "/dimensions/hundred_percent_width_and_height",
+ "dimensions/bug612951.svg",
+ NULL,
+ 0, 0, 47, 47.14,
+ FALSE, TRUE
+ },
+ {
+ "/dimensions/viewbox_only_2",
+ "dimensions/bug614018.svg",
+ NULL,
+ 0, 0, 972, 546,
+ FALSE, TRUE
+ },
+ {
+ "/dimensions/sub/rect_no_unit",
+ "dimensions/sub-rect-no-unit.svg",
+ "#rect-no-unit",
+ 0, 0, 44, 45,
+ FALSE, TRUE
+ },
+ {
+ "/dimensions/with_viewbox",
+ "dimensions/bug521-with-viewbox.svg",
+ "#foo",
+ 50.0, 60.0, 70.0, 80.0,
+ TRUE, TRUE
+ },
+ {
+ "/dimensions/sub/823",
+ "dimensions/bug823-position-sub.svg",
+ "#pad_width",
+ 444.0, 139.0, 0.0, 0.0,
+ TRUE, FALSE
+ },
+};
+
+typedef struct
+{
+ const char *test_name;
+ const char *fixture;
+ size_t buf_size;
+} LoadingTestData;
+
+static void
+load_n_bytes_at_a_time (gconstpointer data)
+{
+ const LoadingTestData *fixture_data = data;
+ char *filename = g_build_filename (test_utils_get_test_data_path (), fixture_data->fixture, NULL);
+ guchar *buf = g_new (guchar, fixture_data->buf_size);
+ gboolean done;
+
+ RsvgHandle *handle;
+ FILE *file;
+
+ file = fopen (filename, "rb");
+ g_assert_nonnull (file);
+
+ handle = rsvg_handle_new_with_flags (RSVG_HANDLE_FLAGS_NONE);
+
+ done = FALSE;
+
+ do {
+ size_t num_read;
+
+ num_read = fread (buf, 1, fixture_data->buf_size, file);
+
+ if (num_read > 0) {
+ g_assert_true (rsvg_handle_write (handle, buf, num_read, NULL));
+ } else {
+ g_assert_cmpint (ferror (file), ==, 0);
+
+ if (feof (file)) {
+ done = TRUE;
+ }
+ }
+ } while (!done);
+
+ fclose (file);
+ g_free (filename);
+
+ g_assert_true (rsvg_handle_close (handle, NULL));
+
+ g_object_unref (handle);
+
+ g_free (buf);
+}
+
+static LoadingTestData loading_tests[] = {
+ { "/loading/one-byte-at-a-time", "loading/gnome-cool.svg", 1 },
+ { "/loading/compressed-one-byte-at-a-time", "loading/gnome-cool.svgz", 1 },
+ { "/loading/compressed-two-bytes-at-a-time", "loading/gnome-cool.svgz", 2 } /* to test reading the entire gzip header */
+};
+
+/* Tests for the deprecated GdkPixbuf-based API */
+static void
+add_pixbuf_tests (void)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (pixbuf_tests); i++) {
+ g_test_add_data_func (pixbuf_tests[i].test_name, &pixbuf_tests[i], test_pixbuf);
+ }
+
+ g_test_add_func ("/api/pixbuf_overflow", pixbuf_overflow);
+}
+
+/* Tests for the C API of librsvg*/
+static void
+add_api_tests (void)
+{
+ g_test_add_func ("/api/handle_has_correct_type_info", handle_has_correct_type_info);
+ g_test_add_func ("/api/flags_registration", flags_registration);
+ g_test_add_func ("/api/error_registration", error_registration);
+ g_test_add_func ("/api/noops", noops);
+ g_test_add_func ("/api/noops_return_null", noops_return_null);
+ g_test_add_func ("/api/set_dpi", set_dpi);
+ g_test_add_func ("/api/base_uri", base_uri);
+ g_test_add_func ("/api/base_gfile", base_gfile);
+ g_test_add_func ("/api/handle_write_close_free", handle_write_close_free);
+ g_test_add_func ("/api/handle_new_from_file", handle_new_from_file);
+ g_test_add_func ("/api/handle_new_from_data", handle_new_from_data);
+ g_test_add_func ("/api/handle_new_from_gfile_sync", handle_new_from_gfile_sync);
+ g_test_add_func ("/api/handle_new_from_stream_sync", handle_new_from_stream_sync);
+ g_test_add_func ("/api/handle_read_stream_sync", handle_read_stream_sync);
+ g_test_add_func ("/api/handle_has_sub", handle_has_sub);
+ g_test_add_func ("/api/handle_get_pixbuf", handle_get_pixbuf);
+ g_test_add_func ("/api/handle_get_pixbuf_sub", handle_get_pixbuf_sub);
+ g_test_add_func ("/api/dimensions_and_position", dimensions_and_position);
+ g_test_add_func ("/api/set_size_callback", set_size_callback);
+ g_test_add_func ("/api/reset_size_callback", reset_size_callback);
+ g_test_add_func ("/api/render_with_zero_size_callback", render_with_zero_size_callback);
+ g_test_add_func ("/api/get_pixbuf_with_size_callback", get_pixbuf_with_size_callback);
+ g_test_add_func ("/api/detects_cairo_context_in_error", detects_cairo_context_in_error);
+ g_test_add_func ("/api/can_draw_to_non_image_surface", can_draw_to_non_image_surface);
+ g_test_add_func ("/api/render_cairo_sub", render_cairo_sub);
+ g_test_add_func ("/api/get_intrinsic_dimensions", get_intrinsic_dimensions);
+ g_test_add_func ("/api/get_intrinsic_dimensions_missing_values", get_intrinsic_dimensions_missing_values);
+ g_test_add_func ("/api/get_intrinsic_size_in_pixels/yes", get_intrinsic_size_in_pixels_yes);
+ g_test_add_func ("/api/get_intrinsic_size_in_pixels/no", get_intrinsic_size_in_pixels_no);
+ g_test_add_func ("/api/set_stylesheet", set_stylesheet);
+ g_test_add_func ("/api/render_document", render_document);
+ g_test_add_func ("/api/get_geometry_for_layer", get_geometry_for_layer);
+ g_test_add_func ("/api/render_layer", render_layer);
+ g_test_add_func ("/api/untransformed_element", untransformed_element);
+ g_test_add_func ("/api/no_write_before_close", no_write_before_close);
+ g_test_add_func ("/api/empty_write_close", empty_write_close);
+ g_test_add_func ("/api/cannot_request_external_elements", cannot_request_external_elements);
+ g_test_add_func ("/api/property_flags", property_flags);
+ g_test_add_func ("/api/property_dpi", property_dpi);
+ g_test_add_func ("/api/property_base_uri", property_base_uri);
+ g_test_add_func ("/api/property_dimensions", property_dimensions);
+ g_test_add_func ("/api/property_deprecated", property_deprecated);
+ g_test_add_func ("/api/return_if_fail", return_if_fail);
+ g_test_add_func ("/api/return_if_fail_null_check", return_if_fail_null_check);
+ g_test_add_func ("/api/return_if_fail_type_check", return_if_fail_type_check);
+ g_test_add_func ("/api/library_version_defines", library_version_defines);
+ g_test_add_func ("/api/library_version_check", library_version_check);
+ g_test_add_func ("/api/library_version_constants", library_version_constants);
+}
+
+/* Tests for the deprecated APIs to get geometries */
+static void
+add_geometry_tests (void)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (dimensions_fixtures); i++)
+ g_test_add_data_func (dimensions_fixtures[i].test_name, &dimensions_fixtures[i], (void*)test_dimensions);
+}
+
+/* Tests for the deprecated API for loading bytes at a time */
+static void
+add_loading_tests (void)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (loading_tests); i++) {
+ g_test_add_data_func (loading_tests[i].test_name, &loading_tests[i], load_n_bytes_at_a_time);
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ test_utils_print_dependency_versions ();
+
+ add_pixbuf_tests ();
+ add_api_tests ();
+ add_geometry_tests ();
+ add_loading_tests ();
+
+ return g_test_run ();
+}
diff --git a/librsvg-c/tests-c/test-utils.c b/librsvg-c/tests-c/test-utils.c
new file mode 100644
index 00000000..8c767884
--- /dev/null
+++ b/librsvg-c/tests-c/test-utils.c
@@ -0,0 +1,257 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 sts=4 ts=4 expandtab: */
+
+#include "config.h"
+#include "test-utils.h"
+
+#include <string.h>
+#include <pango/pango.h>
+#include <pango/pangocairo.h>
+
+#if !PANGO_VERSION_CHECK (1, 44, 0)
+# include <hb.h>
+#endif
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+
+/* Compare two buffers, returning the number of pixels that are
+ * different and the maximum difference of any single color channel in
+ * result_ret.
+ *
+ * This function should be rewritten to compare all formats supported by
+ * cairo_format_t instead of taking a mask as a parameter.
+ */
+static void
+buffer_diff_core (unsigned char *_buf_a,
+ unsigned char *_buf_b,
+ unsigned char *_buf_diff,
+ int width,
+ int height,
+ int stride,
+ guint32 mask,
+ TestUtilsBufferDiffResult *result_ret)
+{
+ int x, y;
+ guint32 *row_a, *row_b, *row;
+ TestUtilsBufferDiffResult result = {0, 0};
+ guint32 *buf_a = (guint32 *) _buf_a;
+ guint32 *buf_b = (guint32 *) _buf_b;
+ guint32 *buf_diff = (guint32 *) _buf_diff;
+
+ stride /= sizeof(guint32);
+ for (y = 0; y < height; y++)
+ {
+ row_a = buf_a + y * stride;
+ row_b = buf_b + y * stride;
+ row = buf_diff + y * stride;
+ for (x = 0; x < width; x++)
+ {
+ /* check if the pixels are the same */
+ if ((row_a[x] & mask) != (row_b[x] & mask)) {
+ int channel;
+ guint32 diff_pixel = 0;
+
+ /* calculate a difference value for all 4 channels */
+ for (channel = 0; channel < 4; channel++) {
+ int value_a = (row_a[x] >> (channel*8)) & 0xff;
+ int value_b = (row_b[x] >> (channel*8)) & 0xff;
+ unsigned int diff;
+ diff = abs (value_a - value_b);
+ if (diff > result.max_diff)
+ result.max_diff = diff;
+ diff *= 4; /* emphasize */
+ if (diff)
+ diff += 128; /* make sure it's visible */
+ if (diff > 255)
+ diff = 255;
+ diff_pixel |= diff << (channel*8);
+ }
+
+ result.pixels_changed++;
+ if ((diff_pixel & 0x00ffffff) == 0) {
+ /* alpha only difference, convert to luminance */
+ guint8 alpha = diff_pixel >> 24;
+ diff_pixel = alpha * 0x010101;
+ }
+ row[x] = diff_pixel;
+ } else {
+ row[x] = 0;
+ }
+ row[x] |= 0xff000000; /* Set ALPHA to 100% (opaque) */
+ }
+ }
+
+ *result_ret = result;
+}
+
+void
+test_utils_compare_surfaces (cairo_surface_t *surface_a,
+ cairo_surface_t *surface_b,
+ cairo_surface_t *surface_diff,
+ TestUtilsBufferDiffResult *result)
+{
+ /* Here, we run cairo's old buffer_diff algorithm which looks for
+ * pixel-perfect images.
+ */
+ buffer_diff_core (cairo_image_surface_get_data (surface_a),
+ cairo_image_surface_get_data (surface_b),
+ cairo_image_surface_get_data (surface_diff),
+ cairo_image_surface_get_width (surface_a),
+ cairo_image_surface_get_height (surface_a),
+ cairo_image_surface_get_stride (surface_a),
+ 0xffffffff,
+ result);
+ if (result->pixels_changed == 0)
+ return;
+
+ g_test_message ("%d pixels differ (with maximum difference of %d) from reference image\n",
+ result->pixels_changed, result->max_diff);
+}
+
+/* Copied from gdk_cairo_surface_paint_pixbuf in gdkcairo.c,
+ * we do not want to depend on GDK
+ */
+static void
+test_utils_cairo_surface_paint_pixbuf (cairo_surface_t *surface,
+ const GdkPixbuf *pixbuf)
+{
+ gint width, height;
+ guchar *gdk_pixels, *cairo_pixels;
+ int gdk_rowstride, cairo_stride;
+ int n_channels;
+ int j;
+
+ if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
+ return;
+
+ /* This function can't just copy any pixbuf to any surface, be
+ * sure to read the invariants here before calling it */
+
+ g_assert (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
+ g_assert (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_RGB24 ||
+ cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32);
+ g_assert (cairo_image_surface_get_width (surface) == gdk_pixbuf_get_width (pixbuf));
+ g_assert (cairo_image_surface_get_height (surface) == gdk_pixbuf_get_height (pixbuf));
+
+ cairo_surface_flush (surface);
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ gdk_pixels = gdk_pixbuf_get_pixels (pixbuf);
+ gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+ cairo_stride = cairo_image_surface_get_stride (surface);
+ cairo_pixels = cairo_image_surface_get_data (surface);
+
+ for (j = height; j; j--)
+ {
+ guchar *p = gdk_pixels;
+ guchar *q = cairo_pixels;
+
+ if (n_channels == 3)
+ {
+ guchar *end = p + 3 * width;
+
+ while (p < end)
+ {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ q[0] = p[2];
+ q[1] = p[1];
+ q[2] = p[0];
+#else
+ q[1] = p[0];
+ q[2] = p[1];
+ q[3] = p[2];
+#endif
+ p += 3;
+ q += 4;
+ }
+ }
+ else
+ {
+ guchar *end = p + 4 * width;
+ guint t1,t2,t3;
+
+#define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x80; d = ((t >> 8) + t) >> 8; } G_STMT_END
+
+ while (p < end)
+ {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ MULT(q[0], p[2], p[3], t1);
+ MULT(q[1], p[1], p[3], t2);
+ MULT(q[2], p[0], p[3], t3);
+ q[3] = p[3];
+#else
+ q[0] = p[3];
+ MULT(q[1], p[0], p[3], t1);
+ MULT(q[2], p[1], p[3], t2);
+ MULT(q[3], p[2], p[3], t3);
+#endif
+
+ p += 4;
+ q += 4;
+ }
+
+#undef MULT
+ }
+
+ gdk_pixels += gdk_rowstride;
+ cairo_pixels += cairo_stride;
+ }
+
+ cairo_surface_mark_dirty (surface);
+}
+
+cairo_surface_t *
+test_utils_cairo_surface_from_pixbuf (const GdkPixbuf *pixbuf)
+{
+ cairo_surface_t *surface;
+
+ g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
+ g_return_val_if_fail (gdk_pixbuf_get_n_channels (pixbuf) == 4, NULL);
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+
+ test_utils_cairo_surface_paint_pixbuf (surface, pixbuf);
+
+ return surface;
+}
+
+static gchar *data_path = NULL;
+
+const gchar *
+test_utils_get_test_data_path (void)
+{
+ if (data_path)
+ return data_path;
+
+ data_path = g_test_build_filename (G_TEST_DIST, "../../rsvg/tests/fixtures", NULL);
+
+ return data_path;
+}
+
+void
+test_utils_print_dependency_versions (void)
+{
+ FT_Library ft_lib;
+ FT_Int ft_major = 0;
+ FT_Int ft_minor = 0;
+ FT_Int ft_patch = 0;
+
+ FT_Init_FreeType (&ft_lib);
+ FT_Library_Version (ft_lib, &ft_major, &ft_minor, &ft_patch);
+ FT_Done_FreeType (ft_lib);
+
+ g_test_message ("Cairo version: %s", cairo_version_string ());
+ g_test_message ("Pango version: %s", pango_version_string ());
+ g_test_message ("Freetype version: %d.%d.%d", ft_major, ft_minor, ft_patch);
+#if PANGO_VERSION_CHECK (1, 44, 0)
+ g_test_message ("Harfbuzz version: %s", hb_version_string ());
+#else
+ g_test_message ("Not printing Harfbuzz version since Pango is older than 1.44");
+#endif
+}
diff --git a/librsvg-c/tests-c/test-utils.h b/librsvg-c/tests-c/test-utils.h
new file mode 100644
index 00000000..d71af69d
--- /dev/null
+++ b/librsvg-c/tests-c/test-utils.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 sts=4 ts=4 expandtab: */
+
+#ifndef TEST_UTILS_H
+#define TEST_UTILS_H
+
+#include <cairo.h>
+#include <gio/gio.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ unsigned int pixels_changed;
+ unsigned int max_diff;
+} TestUtilsBufferDiffResult;
+
+void test_utils_compare_surfaces (cairo_surface_t *surface_a,
+ cairo_surface_t *surface_b,
+ cairo_surface_t *surface_diff,
+ TestUtilsBufferDiffResult *result);
+
+cairo_surface_t *test_utils_cairo_surface_from_pixbuf (const GdkPixbuf *pixbuf);
+
+typedef gboolean (* AddTestFunc) (GFile *file);
+
+const gchar *test_utils_get_test_data_path (void);
+
+void test_utils_print_dependency_versions (void);
+
+void test_utils_setup_font_map (void);
+
+G_END_DECLS
+
+#endif /* TEST_UTILS_H */