Skip to content

Commit

Permalink
Add SVG load support via resvg (#2)
Browse files Browse the repository at this point in the history
Co-authored-by: Kleis Auke Wolthuizen <github@kleisauke.nl>
  • Loading branch information
RReverser and kleisauke committed Jul 16, 2023
1 parent d17c296 commit 284de6a
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 22 deletions.
6 changes: 4 additions & 2 deletions libvips/foreign/foreign.c
Original file line number Diff line number Diff line change
Expand Up @@ -3018,11 +3018,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();
Expand Down
9 changes: 8 additions & 1 deletion libvips/foreign/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ foreign_sources = files(
'rawsave.c',
'spngload.c',
'spngsave.c',
'svgload.c',
'tiff2vips.c',
'tiff.c',
'tiffload.c',
Expand Down Expand Up @@ -122,6 +121,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,
Expand Down
157 changes: 138 additions & 19 deletions libvips/foreign/svgload.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,25 +81,38 @@
#include <cairo.h>
#include <librsvg/rsvg.h>

/* 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 <svg tag must appear within this many bytes of the start of the file.
*/
#define SVG_HEADER_SIZE (1000)
#elif defined(HAVE_RESVG)

/* A handy #define for we-will-handle-svgz.
*/
#if LIBRSVG_CHECK_FEATURE(SVGZ) && defined(HAVE_ZLIB)
#include <resvg.h>
#define HANDLE_SVGZ

#endif

#if defined(HAVE_RSVG) || defined(HAVE_RESVG)

#ifndef HAVE_ZLIB
#undef HANDLE_SVGZ
#endif

#ifdef HANDLE_SVGZ
#include <zlib.h>
#endif

/* Render SVGs with tiles this size. They need to be pretty big to limit
* overcomputation.
*/
#define TILE_SIZE (2000)

/* The <svg tag must appear within this many bytes of the start of the file.
*/
#define SVG_HEADER_SIZE (1000)

typedef struct _VipsForeignLoadSvg {
VipsForeignLoad parent_object;

Expand All @@ -119,7 +132,12 @@ typedef struct _VipsForeignLoadSvg {
*/
gboolean unlimited;

#ifdef HAVE_RSVG
RsvgHandle *page;
#else
resvg_options *options;
resvg_render_tree *tree;
#endif

} VipsForeignLoadSvg;

Expand Down Expand Up @@ -308,7 +326,12 @@ vips_foreign_load_svg_dispose(GObject *gobject)
{
VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) gobject;

#ifdef HAVE_RSVG
VIPS_UNREF(svg->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);
}
Expand All @@ -327,6 +350,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
Expand Down Expand Up @@ -385,6 +409,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,
Expand All @@ -395,6 +420,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,
Expand Down Expand Up @@ -482,6 +508,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.
*/
Expand All @@ -504,9 +535,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;

Expand Down Expand Up @@ -563,24 +596,26 @@ vips_foreign_load_svg_generate(VipsRegion *out_region,
void *seq, void *a, void *b, gboolean *stop)
{
const VipsForeignLoadSvg *svg = (VipsForeignLoadSvg *) a;
const VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(svg);
const VipsRect *r = &out_region->valid;

cairo_surface_t *surface;
cairo_t *cr;
int y;

#ifdef DEBUG
printf("vips_foreign_load_svg_generate: %p \n "
"left = %d, top = %d, width = %d, height = %d\n",
svg,
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(out_region);

#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(out_region, r->left, r->top),
CAIRO_FORMAT_ARGB32,
Expand Down Expand Up @@ -643,6 +678,41 @@ vips_foreign_load_svg_generate(VipsRegion *out_region,
vips__premultiplied_bgra2rgba(
(guint32 *) VIPS_REGION_ADDR(out_region, r->left, r->top + y),
r->width);
#else

VipsPel *q = VIPS_REGION_ADDR(out_region, 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;
}
Expand Down Expand Up @@ -729,8 +799,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;

Expand Down Expand Up @@ -781,7 +859,6 @@ vips_foreign_load_svg_source_header(VipsForeignLoad *load)
return -1;
}
g_object_unref(gstream);

return vips_foreign_load_svg_header(load);
}

Expand Down Expand Up @@ -830,6 +907,7 @@ static void
vips_foreign_load_svg_source_init(VipsForeignLoadSvgSource *source)
{
}
#endif

typedef struct _VipsForeignLoadSvgFile {
VipsForeignLoadSvg parent_object;
Expand All @@ -856,11 +934,35 @@ 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;
Expand All @@ -875,7 +977,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);
Expand All @@ -885,7 +995,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
Expand Down Expand Up @@ -944,6 +1054,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;
Expand All @@ -959,7 +1070,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);
}

Expand Down
11 changes: 11 additions & 0 deletions libvips/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,14 @@ if openslide_module
install_dir: module_dir
)
endif

if resvg_module
shared_module('vips-resvg',
'module/resvg.c',
resvg_module_sources,
name_prefix: '',
dependencies: [libvips_dep, resvg_dep],
install: true,
install_dir: module_dir
)
endif
Loading

0 comments on commit 284de6a

Please sign in to comment.