diff options
author | Marge Bot <marge-bot@gnome.org> | 2022-09-20 23:01:48 +0000 |
---|---|---|
committer | Marge Bot <marge-bot@gnome.org> | 2022-09-20 23:01:48 +0000 |
commit | 1449224882158eef242553c932a281027966751d (patch) | |
tree | 435743f1e1309652ede86220cc45a248d725f8e3 | |
parent | d15bfb9eb7f39ebb58af63c64b1789e16a0f296f (diff) | |
parent | a7879b61ba0fce162ac30434c01ce896997d1533 (diff) | |
download | librsvg-1449224882158eef242553c932a281027966751d.tar.gz |
Merge branch 'wip/aruiz/rust-pixbuf-loader' into 'main'
[RFC] gdk-pixbuf-loader: initial attempt to port to rust
See merge request GNOME/librsvg!722
-rw-r--r-- | gdk-pixbuf-loader/Cargo.toml | 20 | ||||
-rw-r--r-- | gdk-pixbuf-loader/src/lib.rs | 372 | ||||
-rw-r--r-- | src/c_api/pixbuf_utils.rs | 2 |
3 files changed, 393 insertions, 1 deletions
diff --git a/gdk-pixbuf-loader/Cargo.toml b/gdk-pixbuf-loader/Cargo.toml new file mode 100644 index 00000000..b5a2e236 --- /dev/null +++ b/gdk-pixbuf-loader/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pixbufloader-svg" +version = "0.0.1" +authors = ["Alberto Ruiz <aruiz@gnome.org>"] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +librsvg = { path = ".." } +gdk-pixbuf-sys = "0.15" +gdk-pixbuf = "0.15" +libc = "0.2" +glib-sys = "0.15" +gobject-sys = "0.15" +glib = "0.15" +gio = "0.15" +cairo-rs = "0.15" +cstr = "0.2"
\ No newline at end of file diff --git a/gdk-pixbuf-loader/src/lib.rs b/gdk-pixbuf-loader/src/lib.rs new file mode 100644 index 00000000..40d992e3 --- /dev/null +++ b/gdk-pixbuf-loader/src/lib.rs @@ -0,0 +1,372 @@ +use std::ptr::null_mut; + +use gdk_pixbuf_sys::GdkPixbuf; +use gdk_pixbuf_sys::{ + GdkPixbufFormat, GdkPixbufModule, GdkPixbufModulePattern, GdkPixbufModulePreparedFunc, + GdkPixbufModuleSizeFunc, GdkPixbufModuleUpdatedFunc, GDK_PIXBUF_FORMAT_SCALABLE, + GDK_PIXBUF_FORMAT_THREADSAFE, +}; + +use libc::{c_char, c_int, c_uint}; + +use glib::translate::{IntoGlib, ToGlibPtr}; +use glib::Bytes; +use glib_sys::{gboolean, GError}; + +use gio::prelude::MemoryInputStreamExt; +use gio::MemoryInputStream; +use gobject_sys::GObject; + +use librsvg::rsvg_convert_only::LegacySize; +use librsvg::Loader; + +use cstr::cstr; + +struct SvgContext { + size_func: GdkPixbufModuleSizeFunc, + prep_func: GdkPixbufModulePreparedFunc, + update_func: GdkPixbufModuleUpdatedFunc, + user_data: glib_sys::gpointer, + stream: MemoryInputStream, +} + +#[no_mangle] +unsafe extern "C" fn begin_load( + size_func: GdkPixbufModuleSizeFunc, + prep_func: GdkPixbufModulePreparedFunc, + update_func: GdkPixbufModuleUpdatedFunc, + user_data: glib_sys::gpointer, + error: *mut *mut GError, +) -> glib_sys::gpointer { + if error != null_mut() { + *error = null_mut(); + } + + let stream = MemoryInputStream::new(); + let ctx = Box::new(SvgContext { + size_func, + prep_func, + update_func, + user_data, + stream, + }); + + Box::into_raw(ctx) as glib_sys::gpointer +} + +#[no_mangle] +unsafe extern "C" fn load_increment( + user_data: glib_sys::gpointer, + buffer: *const u8, + size: c_uint, + error: *mut *mut GError, +) -> gboolean { + if error != null_mut() { + *error = null_mut(); + } + + let ctx = user_data as *mut SvgContext; + + let data = std::slice::from_raw_parts(buffer, size as usize); + (&*ctx).stream.add_bytes(&Bytes::from(data)); + true.into_glib() +} + +#[no_mangle] +unsafe extern "C" fn stop_load(user_data: glib_sys::gpointer, error: *mut *mut GError) -> gboolean { + let ctx = Box::from_raw(user_data as *mut SvgContext); + if error != null_mut() { + *error = null_mut(); + } + + fn _inner_stop_load(ctx: &Box<SvgContext>) -> Result<gdk_pixbuf::Pixbuf, String> { + let handle = Loader::new() + .read_stream::<_, gio::File, gio::Cancellable>(&ctx.stream, None, None) + .map_err(|e| e.to_string())?; + + let renderer = librsvg::CairoRenderer::new(&handle); + let (w, h) = renderer.legacy_document_size().map_err(|e| e.to_string())?; + let mut w = w.ceil() as c_int; + let mut h = h.ceil() as c_int; + + if let Some(size_func) = ctx.size_func { + let mut tmp_w: c_int = w; + let mut tmp_h: c_int = h; + unsafe { + size_func( + &mut tmp_w as *mut c_int, + &mut tmp_h as *mut c_int, + ctx.user_data, + ) + }; + if tmp_w != 0 && tmp_h != 0 { + w = tmp_w; + h = tmp_h; + } + } + + let pb = librsvg::c_api::pixbuf_utils::render_to_pixbuf_at_size( + &renderer, w as f64, h as f64, w as f64, h as f64, + ) + .map_err(|e| e.to_string())?; + + Ok(pb) + } + + let pixbuf = match _inner_stop_load(&ctx) { + Ok(r) => r, + Err(e) => { + if error != null_mut() { + let gerr = glib::Error::new(gdk_pixbuf::PixbufError::Failed, &e); + *error = gerr.to_glib_full() as *mut GError; + } + return false.into_glib(); + } + }; + + let w = pixbuf.width(); + let h = pixbuf.height(); + let pixbuf: *mut GdkPixbuf = pixbuf.to_glib_full(); + + if let Some(prep_func) = ctx.prep_func { + prep_func(pixbuf, null_mut(), ctx.user_data); + } + if let Some(update_func) = ctx.update_func { + update_func(pixbuf, 0, 0, w, h, ctx.user_data); + } + + // The module loader increases a ref so we drop the pixbuf here + gobject_sys::g_object_unref(pixbuf as *mut GObject); + + true.into_glib() +} + +#[no_mangle] +extern "C" fn fill_vtable(module: &mut GdkPixbufModule) { + module.begin_load = Some(begin_load); + module.stop_load = Some(stop_load); + module.load_increment = Some(load_increment); +} + +const SIGNATURE: [GdkPixbufModulePattern; 3] = [ + GdkPixbufModulePattern { + prefix: cstr!(" <svg").as_ptr() as *mut c_char, + mask: cstr!("* ").as_ptr() as *mut c_char, + relevance: 100, + }, + GdkPixbufModulePattern { + prefix: cstr!(" <!DOCTYPE svg").as_ptr() as *mut c_char, + mask: cstr!("* ").as_ptr() as *mut c_char, + relevance: 100, + }, + GdkPixbufModulePattern { + prefix: std::ptr::null_mut(), + mask: std::ptr::null_mut(), + relevance: 0, + }, +]; + +const MIME_TYPES: [*const c_char; 7] = [ + cstr!("image/svg+xml").as_ptr(), + cstr!("image/svg").as_ptr(), + cstr!("image/svg-xml").as_ptr(), + cstr!("image/vnd.adobe.svg+xml").as_ptr(), + cstr!("text/xml-svg").as_ptr(), + cstr!("image/svg+xml-compressed").as_ptr(), + std::ptr::null(), +]; + +const EXTENSIONS: [*const c_char; 4] = [ + cstr!("svg").as_ptr(), + cstr!("svgz").as_ptr(), + cstr!("svg.gz").as_ptr(), + std::ptr::null(), +]; + +#[no_mangle] +extern "C" fn fill_info(info: &mut GdkPixbufFormat) { + info.name = cstr!("svg").as_ptr() as *mut c_char; + info.signature = SIGNATURE.as_ptr() as *mut GdkPixbufModulePattern; + info.description = cstr!("Scalable Vector Graphics").as_ptr() as *mut c_char; //TODO: Gettext this + info.mime_types = MIME_TYPES.as_ptr() as *mut *mut c_char; + info.extensions = EXTENSIONS.as_ptr() as *mut *mut c_char; + info.flags = GDK_PIXBUF_FORMAT_SCALABLE | GDK_PIXBUF_FORMAT_THREADSAFE; + info.license = cstr!("LGPL").as_ptr() as *mut c_char; +} + +#[cfg(test)] +mod tests { + use gdk_pixbuf_sys::{ + GdkPixbufFormat, GDK_PIXBUF_FORMAT_SCALABLE, GDK_PIXBUF_FORMAT_THREADSAFE, + }; + use glib::translate::IntoGlib; + + use crate::{EXTENSIONS, MIME_TYPES}; + use libc::c_char; + use std::ptr::{null, null_mut}; + + fn pb_format_new() -> GdkPixbufFormat { + let mut info = super::GdkPixbufFormat { + name: null_mut(), + signature: null_mut(), + description: null_mut(), + mime_types: null_mut(), + extensions: null_mut(), + flags: 0, + license: null_mut(), + disabled: false.into_glib(), + domain: null_mut(), + }; + + super::fill_info(&mut info); + info + } + + #[test] + fn fill_info() { + let info = pb_format_new(); + + assert_ne!(info.name, null_mut()); + assert_ne!(info.signature, null_mut()); + assert_ne!(info.description, null_mut()); + assert_ne!(info.mime_types, null_mut()); + assert_ne!(info.extensions, null_mut()); + assert_eq!( + info.flags, + GDK_PIXBUF_FORMAT_SCALABLE | GDK_PIXBUF_FORMAT_THREADSAFE + ); + assert_ne!(info.license, null_mut()); + } + + fn check_null_terminated_arr_cstrings(arr: &[*const c_char]) { + let n_strings = arr + .iter() + .filter(|e| e != &&null()) + .map(|e| { + if e != &null() { + // We use strlen in all of them to ensure some safety + // We could use CStr instead but it'd be a bit more cumbersome + assert!(unsafe { libc::strlen(*e as *const c_char) } > 0) + } + }) + .count(); // Count all non_null items + + // Ensure last item is null and is the only null item + assert_eq!(n_strings, arr.len() - 1); + assert!(arr.last().unwrap() == &null()); + } + + #[test] + fn extensions_bounds() { + check_null_terminated_arr_cstrings(&EXTENSIONS); + } + + #[test] + fn mime_bounds() { + check_null_terminated_arr_cstrings(&MIME_TYPES) + } + + #[test] + fn signature() { + let info = pb_format_new(); + + for i in 0..2 { + let ptr = unsafe { info.signature.offset(i) }; + if i == 2 { + assert_eq!(unsafe { (*ptr).prefix }, null_mut()); + continue; + } else { + assert_ne!(unsafe { (*ptr).prefix }, null_mut()); + if unsafe { (*ptr).mask } != null_mut() { + // Mask can be null + assert_eq!( + unsafe { libc::strlen((*ptr).prefix as *mut c_char) }, + unsafe { libc::strlen((*ptr).mask as *mut c_char) } + ); + } + // Relevance must be 0 to 100 + assert!(unsafe { (*ptr).relevance } >= 0); + assert!(unsafe { (*ptr).relevance } <= 100); + } + } + } + + const SVG_DATA: &'static str = r#"<?xml version="1.0" encoding="UTF-8" standalone="no"?> + <svg + width="100px" + height="150px" + viewBox="0 0 26.458333 26.458333" + version="1.1" + id="svg5" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <rect + style="fill:#aa1144;stroke-width:0.0344347" + width="26.458332" + height="39.6875" + x="4.691162e-07" + y="-6.6145835" + id="rect2" /> + </svg> + "#; + + #[test] + fn minimal_svg() { + unsafe extern "C" fn prep_cb( + pb: *mut gdk_pixbuf_sys::GdkPixbuf, + pba: *mut gdk_pixbuf_sys::GdkPixbufAnimation, + user_data: *mut libc::c_void, + ) { + assert_eq!(user_data, null_mut()); + assert_eq!(pba, null_mut()); + + let w = gdk_pixbuf_sys::gdk_pixbuf_get_width(pb); + let h = gdk_pixbuf_sys::gdk_pixbuf_get_height(pb); + let stride = gdk_pixbuf_sys::gdk_pixbuf_get_rowstride(pb); + assert_eq!(w, 100); + assert_eq!(h, 150); + + let pixels = gdk_pixbuf_sys::gdk_pixbuf_get_pixels(pb); + + // Upper left pixel #aa1144ff + assert_eq!(*pixels, 0xaa); + assert_eq!(*pixels.offset(1), 0x11); + assert_eq!(*pixels.offset(2), 0x44); + assert_eq!(*pixels.offset(3), 0xff); + + // Bottom left pixel + assert_eq!(*pixels.offset((stride * (h - 1)) as isize), 0xaa); + assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 1), 0x11); + assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 2), 0x44); + assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 3), 0xff); + + // Bottom right pixel + assert_eq!( + *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4), + 0xaa + ); + assert_eq!( + *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 1), + 0x11 + ); + assert_eq!( + *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 2), + 0x44 + ); + assert_eq!( + *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 3), + 0xff + ); + } + + let ctx = unsafe { crate::begin_load(None, Some(prep_cb), None, null_mut(), null_mut()) }; + assert_ne!(ctx, null_mut()); + + let inc = unsafe { + crate::load_increment(ctx, SVG_DATA.as_ptr(), SVG_DATA.len() as u32, null_mut()) + }; + assert_ne!(inc, 0); + + unsafe { crate::stop_load(ctx, null_mut()) }; + } +} diff --git a/src/c_api/pixbuf_utils.rs b/src/c_api/pixbuf_utils.rs index c8788bb6..a43518b6 100644 --- a/src/c_api/pixbuf_utils.rs +++ b/src/c_api/pixbuf_utils.rs @@ -105,7 +105,7 @@ fn get_final_size(in_width: f64, in_height: f64, size_mode: &SizeMode) -> (f64, (out_width, out_height) } -fn render_to_pixbuf_at_size( +pub fn render_to_pixbuf_at_size( renderer: &CairoRenderer<'_>, document_width: f64, document_height: f64, |