diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index eeadfe8c68..a5066413d1 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -3006,11 +3006,13 @@ vips_foreign_operation_init( void ) vips_foreign_load_pdf_source_get_type(); #endif /*HAVE_PDFIUM*/ -#ifdef HAVE_RSVG +#if (defined(HAVE_RSVG) || defined(HAVE_RESVG)) && !defined(RESVG_MODULE) vips_foreign_load_svg_file_get_type(); vips_foreign_load_svg_buffer_get_type(); +#ifdef HAVE_RSVG vips_foreign_load_svg_source_get_type(); -#endif /*HAVE_RSVG*/ +#endif +#endif /*defined(HAVE_RSVG) || defined(HAVE_RESVG)*/ #if defined(HAVE_LIBJXL) && !defined(LIBJXL_MODULE) vips_foreign_load_jxl_file_get_type(); diff --git a/libvips/foreign/meson.build b/libvips/foreign/meson.build index a72f9c7867..253faa3d11 100644 --- a/libvips/foreign/meson.build +++ b/libvips/foreign/meson.build @@ -44,7 +44,6 @@ foreign_sources = files( 'rawsave.c', 'spngload.c', 'spngsave.c', - 'svgload.c', 'tiff2vips.c', 'tiff.c', 'tiffload.c', @@ -123,6 +122,14 @@ endif libvips_sources += foreign_sources +resvg_module_sources = files( + 'svgload.c', +) + +if not resvg_module + foreign_sources += resvg_module_sources +endif + foreign_lib = static_library('foreign', foreign_sources, foreign_headers, diff --git a/libvips/foreign/svgload.c b/libvips/foreign/svgload.c index 2eb1541ff1..76b15d4cd4 100644 --- a/libvips/foreign/svgload.c +++ b/libvips/foreign/svgload.c @@ -81,25 +81,38 @@ #include #include -/* Render SVGs with tiles this size. They need to be pretty big to limit - * overcomputation. +/* A handy #define for we-will-handle-svgz. */ -#define TILE_SIZE (2000) +#if LIBRSVG_CHECK_FEATURE(SVGZ) +#define HANDLE_SVGZ +#endif -/* The #define HANDLE_SVGZ + +#endif + +#if defined(HAVE_RSVG) || defined(HAVE_RESVG) + +#ifndef HAVE_ZLIB +#undef HANDLE_SVGZ #endif #ifdef HANDLE_SVGZ #include #endif +/* Render SVGs with tiles this size. They need to be pretty big to limit + * overcomputation. + */ +#define TILE_SIZE (2000) + +/* The page ); +#else + resvg_options_destroy( svg->options ); + VIPS_FREEF( resvg_tree_destroy, svg->tree ); +#endif G_OBJECT_CLASS( vips_foreign_load_svg_parent_class )-> dispose( gobject ); @@ -328,6 +351,7 @@ vips_foreign_load_svg_get_flags( VipsForeignLoad *load ) return( VIPS_FOREIGN_PARTIAL ); } +#ifdef HAVE_RSVG #if LIBRSVG_CHECK_VERSION( 2, 52, 0 ) /* Derived from `CssLength::to_user` in librsvg. * https://gitlab.gnome.org/GNOME/librsvg/-/blob/e6607c9ae8d8409d4efff6b12993717400b3356e/src/length.rs#L368 @@ -386,6 +410,7 @@ svg_css_length_to_pixels( RsvgLength length, double dpi ) return value; } #endif +#endif static int vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg, @@ -396,6 +421,7 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg, double width; double height; +#ifdef HAVE_RSVG #if LIBRSVG_CHECK_VERSION( 2, 52, 0 ) if( !rsvg_handle_get_intrinsic_size_in_pixels( svg->page, @@ -483,6 +509,11 @@ vips_foreign_load_svg_get_natural_size( VipsForeignLoadSvg *svg, } #endif /*LIBRSVG_CHECK_VERSION( 2, 52, 0 )*/ +#else /* HAVE_RSVG */ + resvg_size size = resvg_get_image_size( svg->tree ); + width = size.width; + height = size.height; +#endif /* width or height below 0.5 can't be rounded to 1. */ @@ -505,9 +536,11 @@ vips_foreign_load_svg_get_scaled_size( VipsForeignLoadSvg *svg, double width; double height; +#ifdef HAVE_RSVG /* Get dimensions with the default dpi. */ rsvg_handle_set_dpi( svg->page, 72.0 ); +#endif if( vips_foreign_load_svg_get_natural_size( svg, &width, &height ) ) return( -1 ); @@ -565,23 +598,25 @@ vips_foreign_load_svg_generate( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop ) { const VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) a; - const VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( svg ); const VipsRect *r = &or->valid; - cairo_surface_t *surface; - cairo_t *cr; - int y; - #ifdef DEBUG printf( "vips_foreign_load_svg_generate:\n " "left = %d, top = %d, width = %d, height = %d\n", r->left, r->top, r->width, r->height ); #endif /*DEBUG*/ - /* rsvg won't always paint the background. + /* SVG won't always paint the background. */ vips_region_black( or ); +#ifdef HAVE_RSVG + const VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( svg ); + + cairo_surface_t *surface; + cairo_t *cr; + int y; + surface = cairo_image_surface_create_for_data( VIPS_REGION_ADDR( or, r->left, r->top ), CAIRO_FORMAT_ARGB32, @@ -644,6 +679,42 @@ vips_foreign_load_svg_generate( VipsRegion *or, vips__premultiplied_bgra2rgba( (guint32 *) VIPS_REGION_ADDR( or, r->left, r->top + y ), r->width ); +#else + + VipsPel *q = VIPS_REGION_ADDR( or, r->left, r->top ); + + resvg_render( + svg->tree, + (resvg_fit_to) { .type = RESVG_FIT_TO_TYPE_ORIGINAL }, + (resvg_transform) { + .a = svg->cairo_scale, + .d = svg->cairo_scale, + .e = -r->left, + .f = -r->top, + }, + r->width, + r->height, + (char *) q + ); + + /* Just unpremultiply. + */ + for( int i = 0; i < r->width * r->height; i++ ) { + VipsPel * restrict p = &q[i * 4]; + VipsPel x = p[3]; + + /* Skip transparent and fully opaque pixels. + */ + if( x == 0 || x == 255 ) + continue; + + /* Any compiler will unroll it. + */ + for( int j = 0; j < 3; j++ ) + p[j] = 255 * p[j] / x; + } + +#endif return( 0 ); } @@ -731,8 +802,16 @@ vips_foreign_load_svg_init( VipsForeignLoadSvg *svg ) svg->dpi = 72.0; svg->scale = 1.0; svg->cairo_scale = 1.0; +#ifdef HAVE_RESVG + svg->options = resvg_options_create(); + + /* Get dimensions with the default dpi. + */ + resvg_options_set_dpi( svg->options, 72.0 ); +#endif } +#ifdef HAVE_RSVG typedef struct _VipsForeignLoadSvgSource { VipsForeignLoadSvg parent_object; @@ -783,7 +862,6 @@ vips_foreign_load_svg_source_header( VipsForeignLoad *load ) return( -1 ); } g_object_unref( gstream ); - return( vips_foreign_load_svg_header( load ) ); } @@ -833,6 +911,7 @@ static void vips_foreign_load_svg_source_init( VipsForeignLoadSvgSource *source ) { } +#endif typedef struct _VipsForeignLoadSvgFile { VipsForeignLoadSvg parent_object; @@ -859,11 +938,34 @@ vips_foreign_load_svg_file_is_a( const char *filename ) vips_foreign_load_svg_is_a( buf, bytes ) ); } +#ifdef HAVE_RESVG +static const char * +resvg_error_msg( resvg_error e ) { + switch( e ) { + case RESVG_ERROR_NOT_AN_UTF8_STR: + return "only UTF-8 content is supported"; + case RESVG_ERROR_FILE_OPEN_FAILED: + return "failed to open the provided file"; + case RESVG_ERROR_MALFORMED_GZIP: + return "compressed SVG must use the GZip algorithm"; + case RESVG_ERROR_ELEMENTS_LIMIT_REACHED: + return "we do not allow SVG with more than 1_000_000 elements for security reasons"; + case RESVG_ERROR_INVALID_SIZE: + return "SVG doesn't have a valid size"; + case RESVG_ERROR_PARSING_FAILED: + return "failed to parse SVG data"; + default: + return "unknown error"; + } +} +#endif + static int vips_foreign_load_svg_file_header( VipsForeignLoad *load ) { VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) load; VipsForeignLoadSvgFile *file = (VipsForeignLoadSvgFile *) load; +#ifdef HAVE_RSVG RsvgHandleFlags flags = svg->unlimited ? RSVG_HANDLE_FLAG_UNLIMITED : 0; GError *error = NULL; @@ -878,7 +980,15 @@ vips_foreign_load_svg_file_header( VipsForeignLoad *load ) return( -1 ); } g_object_unref( gfile ); - +#else + resvg_error error = resvg_parse_tree_from_file( + file->filename, svg->options, &svg->tree ); + if( error != RESVG_OK ) { + vips_error( VIPS_OBJECT_GET_CLASS( svg )->nickname, + "%s", resvg_error_msg( error ) ); + return( -1 ); + } +#endif VIPS_SETSTR( load->out->filename, file->filename ); return( vips_foreign_load_svg_header( load ) ); @@ -888,7 +998,7 @@ static const char *vips_foreign_svg_suffs[] = { ".svg", /* librsvg supports svgz directly, no need to check for zlib here. */ -#if LIBRSVG_CHECK_FEATURE(SVGZ) +#ifdef HANDLE_SVGZ ".svgz", ".svg.gz", #endif @@ -948,6 +1058,7 @@ vips_foreign_load_svg_buffer_header( VipsForeignLoad *load ) VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) load; VipsForeignLoadSvgBuffer *buffer = (VipsForeignLoadSvgBuffer *) load; +#ifdef HAVE_RSVG RsvgHandleFlags flags = svg->unlimited ? RSVG_HANDLE_FLAG_UNLIMITED : 0; GError *error = NULL; @@ -963,7 +1074,15 @@ vips_foreign_load_svg_buffer_header( VipsForeignLoad *load ) return( -1 ); } g_object_unref( gstream ); - +#else + resvg_error error = resvg_parse_tree_from_data( + buffer->buf->data, buffer->buf->length, svg->options, &svg->tree ); + if( error != RESVG_OK ) { + vips_error( VIPS_OBJECT_GET_CLASS( svg )->nickname, + "%s", resvg_error_msg( error ) ); + return( -1 ); + } +#endif return( vips_foreign_load_svg_header( load ) ); } @@ -1146,4 +1265,3 @@ vips_svgload_source( VipsSource *source, VipsImage **out, ... ) return( result ); } - diff --git a/libvips/meson.build b/libvips/meson.build index 5db9876607..fb3c86d775 100644 --- a/libvips/meson.build +++ b/libvips/meson.build @@ -170,3 +170,18 @@ if openslide_module install_dir: module_dir ) endif + +if resvg_module + shared_module('vips-resvg', + 'module/resvg.c', + resvg_module_sources, + c_args: module_c_args, + link_args: module_link_args, + name_prefix: '', + name_suffix: module_suffix, + dependencies: [module_dep, resvg_dep], + gnu_symbol_visibility: 'hidden', + install: true, + install_dir: module_dir + ) +endif diff --git a/libvips/module/resvg.c b/libvips/module/resvg.c new file mode 100644 index 0000000000..de53db8960 --- /dev/null +++ b/libvips/module/resvg.c @@ -0,0 +1,70 @@ +/* resvg as a dynamically loadable module + * + * 17/1/23 kleisauke + * - initial + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include + +#include +#include +#include + +#if defined(HAVE_RESVG) && defined(RESVG_MODULE) + +/* This is called on module load. + */ +G_MODULE_EXPORT const gchar * +g_module_check_init( GModule *module ) +{ +#ifdef DEBUG + printf( "vips_resvg: module init\n" ); +#endif /*DEBUG*/ + + extern GType vips_foreign_load_svg_file_get_type( void ); + extern GType vips_foreign_load_svg_buffer_get_type( void ); + + vips_foreign_load_svg_file_get_type(); + vips_foreign_load_svg_buffer_get_type(); + + return( NULL ); +} + +#endif /*defined(HAVE_RESVG) && defined(RESVG_MODULE)*/ diff --git a/meson.build b/meson.build index e67e45ecd1..8a5c783ec0 100644 --- a/meson.build +++ b/meson.build @@ -371,6 +371,23 @@ if librsvg_found cfg_var.set('HAVE_RSVG', '1') endif +# only if librsvg not found +resvg_dep = disabler() +resvg_module = false +if not librsvg_found + # resvg doesn't ship pkg-config files, so we can't use dependency() + resvg_dep = cc.find_library('resvg', has_headers: ['resvg.h'], required: get_option('resvg')) + if resvg_dep.found() + resvg_module = modules_enabled and not get_option('resvg-module').disabled() + if resvg_module + cfg_var.set('RESVG_MODULE', '1') + else + libvips_deps += resvg_dep + endif + cfg_var.set('HAVE_RESVG', '1') + endif +endif + openslide_dep = dependency('openslide', version: '>=3.3.0', required: get_option('openslide')) openslide_module = false if openslide_dep.found() @@ -634,6 +651,7 @@ build_summary = { 'PDF load with PDFium': [pdfium_dep.found()], 'PDF load with poppler-glib': [libpoppler_found, ' (dynamic module: ', libpoppler_module, ')'], 'SVG load with librsvg': [librsvg_found], + 'SVG load with resvg': [resvg_dep.found(), ' (dynamic module: ', resvg_module, ')'], 'EXR load with OpenEXR': [openexr_dep.found()], 'OpenSlide load': [openslide_dep.found(), ' (dynamic module: ', openslide_module, ')'], 'Matlab load with libmatio': [matio_dep.found()], diff --git a/meson_options.txt b/meson_options.txt index 375d1e5f05..022bcb2952 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -188,6 +188,16 @@ option('rsvg', value: 'auto', description: 'Build with rsvg') +option('resvg', + type: 'feature', + value: 'auto', + description: 'Build with resvg') + +option('resvg-module', + type: 'feature', + value: 'auto', + description: 'Build resvg as module') + option('spng', type: 'feature', value: 'auto',