From 4ba1d85363a3b32d24b487cec68555853a3c6735 Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Fri, 21 Jan 2022 09:04:01 +0100 Subject: Add preliminary support for resvg It claims better SVG support, but it sucks for a plethora of reasons. --- fiv-io.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- meson.build | 28 +++++++++++++---- meson_options.txt | 2 ++ 3 files changed, 116 insertions(+), 8 deletions(-) diff --git a/fiv-io.c b/fiv-io.c index 48f862c..ef5c26e 100644 --- a/fiv-io.c +++ b/fiv-io.c @@ -43,6 +43,9 @@ #ifdef HAVE_LIBRAW #include #endif // HAVE_LIBRAW +#ifdef HAVE_RESVG +#include +#endif // HAVE_RESVG #ifdef HAVE_LIBRSVG #include #endif // HAVE_LIBRSVG @@ -92,9 +95,9 @@ const char *fiv_io_supported_media_types[] = { #ifdef HAVE_LIBRAW "image/x-dcraw", #endif // HAVE_LIBRAW -#ifdef HAVE_LIBRSVG +#if defined HAVE_RESVG || defined HAVE_LIBRSVG "image/svg+xml", -#endif // HAVE_LIBRSVG +#endif // HAVE_RESVG || HAVE_LIBRSVG #ifdef HAVE_XCURSOR "image/x-xcursor", #endif // HAVE_XCURSOR @@ -1320,6 +1323,85 @@ open_libraw(const gchar *data, gsize len, GError **error) } #endif // HAVE_LIBRAW --------------------------------------------------------- +#ifdef HAVE_RESVG // ---------------------------------------------------------- + +static const char * +load_resvg_error(int err) +{ + switch (err) { + case RESVG_ERROR_NOT_AN_UTF8_STR: + return "not a UTF-8 string"; + case RESVG_ERROR_FILE_OPEN_FAILED: + return "I/O failure"; + case RESVG_ERROR_MALFORMED_GZIP: + return "malformed gzip"; + case RESVG_ERROR_ELEMENTS_LIMIT_REACHED: + return "element limit reached"; + case RESVG_ERROR_INVALID_SIZE: + return "invalid or unspecified image size"; + case RESVG_ERROR_PARSING_FAILED: + return "parsing failed"; + default: + return "general failure"; + } +} + +static cairo_surface_t * +open_resvg(const gchar *data, gsize len, const gchar *uri, GError **error) +{ + GFile *file = g_file_new_for_uri(uri); + GFile *base_file = g_file_get_parent(file); + g_object_unref(file); + + resvg_options *opt = resvg_options_create(); + resvg_options_load_system_fonts(opt); + resvg_options_set_resources_dir(opt, g_file_peek_path(base_file)); + resvg_render_tree *tree = NULL; + int err = resvg_parse_tree_from_data(data, len, opt, &tree); + resvg_options_destroy(opt); + g_object_unref(base_file); + if (err != RESVG_OK) { + set_error(error, load_resvg_error(err)); + return NULL; + } + + // TODO(p): Support retrieving a scaled-up/down version. + // TODO(p): See if there is a situation for resvg_get_image_viewbox(). + resvg_size size = resvg_get_image_size(tree); + int w = ceil(size.width), h = ceil(size.height); + if (w > SHRT_MAX || h > SHRT_MAX) { + set_error(error, "image dimensions overflow"); + resvg_tree_destroy(tree); + return NULL; + } + + cairo_surface_t *surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); + cairo_status_t surface_status = cairo_surface_status(surface); + if (surface_status != CAIRO_STATUS_SUCCESS) { + set_error(error, cairo_status_to_string(surface_status)); + cairo_surface_destroy(surface); + return NULL; + } + + uint32_t *pixels = (uint32_t *) cairo_image_surface_get_data(surface); + resvg_fit_to fit_to = { RESVG_FIT_TO_TYPE_ORIGINAL, 1. }; + resvg_render(tree, fit_to, resvg_transform_identity(), + cairo_image_surface_get_width(surface), + cairo_image_surface_get_height(surface), (char *) pixels); + resvg_tree_destroy(tree); + + // TODO(p): Also apply colour management, we'll need to un-premultiply. + for (int i = 0; i < w * h; i++) { + uint32_t rgba = g_ntohl(pixels[i]); + pixels[i] = rgba << 24 | rgba >> 8; + } + + cairo_surface_mark_dirty(surface); + return surface; +} + +#endif // HAVE_RESVG ---------------------------------------------------------- #ifdef HAVE_LIBRSVG // -------------------------------------------------------- #ifdef FIV_RSVG_DEBUG @@ -2391,6 +2473,14 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *uri, g_clear_error(error); } #endif // HAVE_LIBRAW --------------------------------------------------------- +#ifdef HAVE_RESVG // ---------------------------------------------------------- + if ((surface = open_resvg(data, len, uri, error))) + break; + if (error) { + g_debug("%s", (*error)->message); + g_clear_error(error); + } +#endif // HAVE_RESVG ---------------------------------------------------------- #ifdef HAVE_LIBRSVG // -------------------------------------------------------- if ((surface = open_librsvg(data, len, uri, error))) break; diff --git a/meson.build b/meson.build index 0ccc8cd..af7a39b 100644 --- a/meson.build +++ b/meson.build @@ -2,17 +2,16 @@ project('fiv', 'c', default_options : ['c_std=gnu99', 'warning_level=2'], version : '0.1.0') + +cc = meson.get_compiler('c') add_project_arguments( - meson.get_compiler('c').get_supported_arguments('-Wno-cast-function-type'), - language : 'c', -) + cc.get_supported_arguments('-Wno-cast-function-type'), language : 'c') # This, annoyingly, enables the leak sanitizer by default, # which requires some tuning to not stand in the way. # Use -Db_sanitize=address,undefined and adjust LSAN_OPTIONS yourself. #if get_option('buildtype').startswith('debug') -# flags = meson.get_compiler('c').get_supported_arguments( -# '-fsanitize=address,undefined') +# flags = cc.get_supported_arguments('-fsanitize=address,undefined') # add_project_arguments(flags, language : ['c']) # add_project_link_arguments(flags, language : ['c']) #endif @@ -23,6 +22,19 @@ libjpegqs = dependency('libjpegqs', allow_fallback : true, ) +# As of writing, the API is unstable, and no pkg-config file is produced. +# Trying to wrap Cargo in Meson is a recipe for pain, so no version pinning. +resvg = disabler() +if not get_option('resvg').disabled() + resvg = dependency('resvg', required : false) + if not resvg.found() + resvg = cc.find_library('libresvg', required : get_option('resvg')) + if resvg.found() and not cc.has_header('resvg.h') + error('resvg.h not found') + endif + endif +endif + lcms2 = dependency('lcms2', required : get_option('lcms2')) # Note that only libraw_r is thread-safe, but we'll just run it out-of process. libraw = dependency('libraw', required : get_option('libraw')) @@ -35,6 +47,7 @@ dependencies = [ dependency('gtk+-3.0'), dependency('pixman-1'), + # Wuffs is included as a submodule. dependency('libturbojpeg'), dependency('libwebp'), dependency('libwebpdemux'), @@ -49,12 +62,14 @@ dependencies = [ lcms2, libjpegqs, libraw, + resvg, librsvg, xcursor, libheif, libtiff, gdkpixbuf, - meson.get_compiler('c').find_library('m', required : false), + + cc.find_library('m', required : false), ] conf = configuration_data() @@ -63,6 +78,7 @@ conf.set_quoted('PROJECT_VERSION', meson.project_version()) conf.set('HAVE_JPEG_QS', libjpegqs.found()) conf.set('HAVE_LCMS2', lcms2.found()) conf.set('HAVE_LIBRAW', libraw.found()) +conf.set('HAVE_RESVG', resvg.found()) conf.set('HAVE_LIBRSVG', librsvg.found()) conf.set('HAVE_XCURSOR', xcursor.found()) conf.set('HAVE_LIBHEIF', libheif.found()) diff --git a/meson_options.txt b/meson_options.txt index d59a568..fb44094 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,6 +4,8 @@ option('libjpegqs', type : 'feature', value : 'auto', description : 'Build with JPEG Quant Smooth integration') option('libraw', type : 'feature', value : 'auto', description : 'Build with raw photo support, requires LibRaw') +option('resvg', type : 'feature', value : 'disabled', + description : 'Build with SVG support via resvg (pre-1.0 unstable API)') option('librsvg', type : 'feature', value : 'auto', description : 'Build with SVG support, requires librsvg') option('xcursor', type : 'feature', value : 'auto', -- cgit v1.2.3-70-g09d2