summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarge Bot <marge-bot@gnome.org>2022-09-20 23:01:48 +0000
committerMarge Bot <marge-bot@gnome.org>2022-09-20 23:01:48 +0000
commit1449224882158eef242553c932a281027966751d (patch)
tree435743f1e1309652ede86220cc45a248d725f8e3
parentd15bfb9eb7f39ebb58af63c64b1789e16a0f296f (diff)
parenta7879b61ba0fce162ac30434c01ce896997d1533 (diff)
downloadlibrsvg-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.toml20
-rw-r--r--gdk-pixbuf-loader/src/lib.rs372
-rw-r--r--src/c_api/pixbuf_utils.rs2
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,