From 3330b38a3094e09edbb80fb85f3e5c3803f718a1 Mon Sep 17 00:00:00 2001 From: Sophie Herold Date: Sat, 1 Apr 2023 17:23:37 +0200 Subject: meta: Naive move of c_api to own crate Part-of: --- Cargo.lock | 17 + Cargo.toml | 3 +- librsvg-c/Cargo.toml | 19 + librsvg-c/build.rs | 126 ++ librsvg-c/src/c_api/dpi.rs | 66 ++ librsvg-c/src/c_api/handle.rs | 2144 +++++++++++++++++++++++++++++++++++ librsvg-c/src/c_api/messages.rs | 158 +++ librsvg-c/src/c_api/mod.rs | 58 + librsvg-c/src/c_api/pixbuf_utils.rs | 330 ++++++ librsvg-c/src/c_api/sizing.rs | 116 ++ librsvg-c/src/lib.rs | 1 + rsvg/src/c_api/dpi.rs | 66 -- rsvg/src/c_api/handle.rs | 2144 ----------------------------------- rsvg/src/c_api/messages.rs | 158 --- rsvg/src/c_api/mod.rs | 58 - rsvg/src/c_api/pixbuf_utils.rs | 330 ------ rsvg/src/c_api/sizing.rs | 116 -- rsvg/src/lib.rs | 3 - 18 files changed, 3037 insertions(+), 2876 deletions(-) create mode 100644 librsvg-c/Cargo.toml create mode 100644 librsvg-c/build.rs create mode 100644 librsvg-c/src/c_api/dpi.rs create mode 100644 librsvg-c/src/c_api/handle.rs create mode 100644 librsvg-c/src/c_api/messages.rs create mode 100644 librsvg-c/src/c_api/mod.rs create mode 100644 librsvg-c/src/c_api/pixbuf_utils.rs create mode 100644 librsvg-c/src/c_api/sizing.rs create mode 100644 librsvg-c/src/lib.rs delete mode 100644 rsvg/src/c_api/dpi.rs delete mode 100644 rsvg/src/c_api/handle.rs delete mode 100644 rsvg/src/c_api/messages.rs delete mode 100644 rsvg/src/c_api/mod.rs delete mode 100644 rsvg/src/c_api/pixbuf_utils.rs delete mode 100644 rsvg/src/c_api/sizing.rs diff --git a/Cargo.lock b/Cargo.lock index 1cb14730..cb6644ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1162,6 +1162,23 @@ dependencies = [ "yeslogic-fontconfig-sys", ] +[[package]] +name = "librsvg-c" +version = "0.1.0" +dependencies = [ + "cairo-rs", + "cast", + "float-cmp", + "gdk-pixbuf", + "gio", + "glib", + "libc", + "librsvg", + "once_cell", + "regex", + "url", +] + [[package]] name = "link-cplusplus" version = "1.0.8" diff --git a/Cargo.toml b/Cargo.toml index 2e17e6a9..50c5826a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,10 +15,11 @@ overflow-checks = true [workspace] members = [ "gdk-pixbuf-loader", + "librsvg-c", "rsvg", "rsvg-convert", "rsvg-bench", ] default-members = [ "rsvg-convert", -] \ No newline at end of file +] diff --git a/librsvg-c/Cargo.toml b/librsvg-c/Cargo.toml new file mode 100644 index 00000000..cb2209cd --- /dev/null +++ b/librsvg-c/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "librsvg-c" +version = "0.1.0" +edition = "2021" + +[dependencies] +librsvg = { version = "*", path = "../rsvg" } +gio = "0.17" +glib = "0.17" +cairo-rs = { version = "0.17", features=["v1_16", "png", "pdf", "ps", "svg"] } +cast = "0.3.0" +gdk-pixbuf = "0.17" +libc = "0.2" +float-cmp = "0.9.0" +url = "2" +once_cell = "1.2.0" + +[build-dependencies] +regex = "1.7.1" \ No newline at end of file diff --git a/librsvg-c/build.rs b/librsvg-c/build.rs new file mode 100644 index 00000000..7a46d6c6 --- /dev/null +++ b/librsvg-c/build.rs @@ -0,0 +1,126 @@ +use regex::Regex; +use std::env; +use std::fs::File; +use std::io::prelude::*; +use std::io::{BufReader, BufWriter, Write}; +use std::path::Path; +use std::process; + +fn main() { + if let Err(e) = system_deps::Config::new().probe() { + eprintln!("{e}"); + process::exit(1); + } + + generate_srgb_tables(); + write_version(); +} + +/// Converts an sRGB color value to a linear sRGB color value (undoes the gamma correction). +/// +/// The input and the output are supposed to be in the [0, 1] range. +#[inline] +fn linearize(c: f64) -> f64 { + if c <= (12.92 * 0.0031308) { + c / 12.92 + } else { + ((c + 0.055) / 1.055).powf(2.4) + } +} + +/// Converts a linear sRGB color value to a normal sRGB color value (applies the gamma correction). +/// +/// The input and the output are supposed to be in the [0, 1] range. +#[inline] +fn unlinearize(c: f64) -> f64 { + if c <= 0.0031308 { + 12.92 * c + } else { + 1.055 * c.powf(1f64 / 2.4) - 0.055 + } +} + +fn print_table(w: &mut W, name: &str, f: F, len: u32) +where + W: Write, + F: Fn(f64) -> f64, +{ + writeln!(w, "const {name}: [u8; {len}] = [").unwrap(); + + for i in 0..len { + let x = f(i as f64 / 255.0); + let v = (x * 255.0).round() as u8; + writeln!(w, " {v},").unwrap(); + } + + writeln!(w, "];").unwrap(); +} + +fn generate_srgb_tables() { + let path = Path::new(&env::var("OUT_DIR").unwrap()).join("srgb-codegen.rs"); + let mut file = BufWriter::new(File::create(path).unwrap()); + + print_table(&mut file, "LINEARIZE", linearize, 256); + print_table(&mut file, "UNLINEARIZE", unlinearize, 256); +} + +fn write_version() { + let mut major = None; + let mut minor = None; + let mut micro = None; + + { + let file = File::open("../configure.ac") + .expect("builds must take place within the librsvg source tree"); + + let major_regex = Regex::new(r#"^m4_define\(\[rsvg_major_version\],\[(\d+)\]\)"#).unwrap(); + let minor_regex = Regex::new(r#"^m4_define\(\[rsvg_minor_version\],\[(\d+)\]\)"#).unwrap(); + let micro_regex = Regex::new(r#"^m4_define\(\[rsvg_micro_version\],\[(\d+)\]\)"#).unwrap(); + + for line in BufReader::new(file).lines() { + match line { + Ok(line) => { + if let Some(nums) = major_regex.captures(&line) { + major = Some(String::from( + nums.get(1).expect("major_regex matched once").as_str(), + )); + } else if let Some(nums) = minor_regex.captures(&line) { + minor = Some(String::from( + nums.get(1).expect("minor_regex matched once").as_str(), + )); + } else if let Some(nums) = micro_regex.captures(&line) { + micro = Some(String::from( + nums.get(1).expect("micro_regex matched once").as_str(), + )); + } + } + + Err(e) => panic!("could not parse version from configure.ac: {e}"), + } + } + } + + let output = Path::new(&env::var("OUT_DIR").unwrap()).join("version.rs"); + let mut file = File::create(output).expect("open version.rs for writing"); + file.write_all( + format!( + r#" +use std::os::raw::c_uint; + +#[no_mangle] +pub static rsvg_major_version: c_uint = {}; + +#[no_mangle] +pub static rsvg_minor_version: c_uint = {}; + +#[no_mangle] +pub static rsvg_micro_version: c_uint = {}; +"#, + major.expect("major version is set"), + minor.expect("minor version is set"), + micro.expect("micro version is set") + ) + .as_bytes(), + ) + .expect("write version.rs"); +} diff --git a/librsvg-c/src/c_api/dpi.rs b/librsvg-c/src/c_api/dpi.rs new file mode 100644 index 00000000..b2e15eef --- /dev/null +++ b/librsvg-c/src/c_api/dpi.rs @@ -0,0 +1,66 @@ +//! Legacy C API for setting a default DPI (dots per inch = DPI). +//! +//! There are two deprecated functions, `rsvg_set_default_dpi` and +//! `rsvg_set_default_dpi_x_y`, which set global values for the default DPI to be used +//! with `RsvgHandle`. In turn, `RsvgHandle` assumes that when its own DPI value is set +//! to `0.0` (which is in fact its default), it will fall back to the global DPI. +//! +//! This is clearly not thread-safe, but it is the legacy behavior. +//! +//! This module encapsulates that behavior so that the `rsvg_internals` crate +//! can always have immutable DPI values as intended. + +// This is configurable at runtime +const DEFAULT_DPI_X: f64 = 90.0; +const DEFAULT_DPI_Y: f64 = 90.0; + +static mut DPI_X: f64 = DEFAULT_DPI_X; +static mut DPI_Y: f64 = DEFAULT_DPI_Y; + +#[derive(Debug, Copy, Clone, Default)] +pub(crate) struct Dpi { + x: f64, + y: f64, +} + +impl Dpi { + pub(crate) fn new(x: f64, y: f64) -> Dpi { + Dpi { x, y } + } + + pub(crate) fn x(&self) -> f64 { + if self.x <= 0.0 { + unsafe { DPI_X } + } else { + self.x + } + } + + pub(crate) fn y(&self) -> f64 { + if self.y <= 0.0 { + unsafe { DPI_Y } + } else { + self.y + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_set_default_dpi_x_y(dpi_x: libc::c_double, dpi_y: libc::c_double) { + if dpi_x <= 0.0 { + DPI_X = DEFAULT_DPI_X; + } else { + DPI_X = dpi_x; + } + + if dpi_y <= 0.0 { + DPI_Y = DEFAULT_DPI_Y; + } else { + DPI_Y = dpi_y; + } +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_set_default_dpi(dpi: libc::c_double) { + rsvg_set_default_dpi_x_y(dpi, dpi); +} diff --git a/librsvg-c/src/c_api/handle.rs b/librsvg-c/src/c_api/handle.rs new file mode 100644 index 00000000..1698cc17 --- /dev/null +++ b/librsvg-c/src/c_api/handle.rs @@ -0,0 +1,2144 @@ +//! Main API for `RsvgHandle`. +//! +//! The C API of librsvg revolves around an `RsvgHandle` GObject class, which is +//! implemented as follows: +//! +//! * [`RsvgHandle`] and [`RsvgHandleClass`] are derivatives of `GObject` and +//! `GObjectClass`. These are coded explicitly, instead of using +//! [`glib::subclass::prelude::InstanceStruct`] and +//! [`glib::subclass::prelude::ClassStruct`], as the structs need need to be kept +//! ABI-compatible with the traditional C API/ABI. +//! +//! * The actual data for a handle (e.g. the `RsvgHandle`'s private data, in GObject +//! parlance) is in [`CHandle`]. +//! +//! * Public C ABI functions are the `#[no_mangle]` functions with an `rsvg_` prefix. +//! +//! The C API is implemented in terms of the Rust API in `librsvg_crate`. In effect, +//! [`RsvgHandle`] is a rather convoluted builder or adapter pattern that translates all the +//! historical idiosyncrasies of the C API into the simple Rust API. + +use std::cell::{Cell, Ref, RefCell, RefMut}; +use std::ffi::{CStr, CString, OsStr}; +use std::fmt; +use std::path::PathBuf; +use std::ptr; +use std::slice; +use std::str; +use std::{f64, i32}; + +use gdk_pixbuf::Pixbuf; +use gio::prelude::*; +use glib::error::ErrorDomain; +use url::Url; + +use glib::subclass::prelude::*; +use glib::translate::*; +use glib::types::instance_of; +use glib::{ffi::gpointer, gobject_ffi}; +use glib::{Bytes, Cast, StaticType, ToValue}; + +use crate::api::{self, CairoRenderer, IntrinsicDimensions, Loader, LoadingError, SvgHandle}; + +use crate::{ + length::RsvgLength, + rsvg_log, + session::Session, + surface_utils::shared_surface::{SharedImageSurface, SurfaceType}, +}; + +use super::dpi::Dpi; +use super::messages::{rsvg_g_critical, rsvg_g_warning}; +use super::pixbuf_utils::{empty_pixbuf, pixbuf_from_surface}; +use super::sizing::LegacySize; + +// The C API exports global variables that contain the library's version number; +// those get autogenerated from `build.rs` and placed in this `version.rs` file. +include!(concat!(env!("OUT_DIR"), "/version.rs")); + +// This is basically the same as api::RenderingError but with extra cases for +// the peculiarities of the C API. +enum RenderingError { + RenderingError(api::RenderingError), + + // The RsvgHandle is created, but hasn't been loaded yet. + HandleIsNotLoaded, +} + +impl> From for RenderingError { + fn from(e: T) -> RenderingError { + RenderingError::RenderingError(e.into()) + } +} + +impl fmt::Display for RenderingError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + RenderingError::RenderingError(ref e) => e.fmt(f), + RenderingError::HandleIsNotLoaded => write!(f, "SVG data is not loaded into handle"), + } + } +} + +/// Rust version of the `RsvgHandleFlags` enum in C. +#[glib::flags(name = "RsvgHandleFlags")] +pub enum HandleFlags { + #[flags_value(name = "RSVG_HANDLE_FLAGS_NONE", nick = "flags-none")] + NONE = 0, + + #[flags_value(name = "RSVG_HANDLE_FLAG_UNLIMITED", nick = "flag-unlimited")] + UNLIMITED = 1 << 0, + + #[flags_value( + name = "RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA", + nick = "flag-keep-image-data" + )] + KEEP_IMAGE_DATA = 1 << 1, +} + +/// Type alias used to pass flags in the C API functions. +pub type RsvgHandleFlags = u32; + +/// Internal representation of the loading flags, without bitflags. +#[derive(Default, Copy, Clone)] +struct LoadFlags { + unlimited_size: bool, + keep_image_data: bool, +} + +impl From for LoadFlags { + fn from(flags: HandleFlags) -> LoadFlags { + LoadFlags { + unlimited_size: flags.contains(HandleFlags::UNLIMITED), + keep_image_data: flags.contains(HandleFlags::KEEP_IMAGE_DATA), + } + } +} + +impl From for HandleFlags { + fn from(lflags: LoadFlags) -> HandleFlags { + let mut hflags = HandleFlags::empty(); + + if lflags.unlimited_size { + hflags.insert(HandleFlags::UNLIMITED); + } + + if lflags.keep_image_data { + hflags.insert(HandleFlags::KEEP_IMAGE_DATA); + } + + hflags + } +} + +/// GObject class struct for RsvgHandle. +/// +/// This is not done through [`glib::subclass::prelude::ClassStruct`] because we need +/// to include the `_abi_padding` field for ABI compatibility with the C headers, and +/// `simple::ClassStruct` does not allow that. +#[repr(C)] +pub struct RsvgHandleClass { + // Keep this in sync with rsvg.h:RsvgHandleClass + parent: gobject_ffi::GObjectClass, + + _abi_padding: [gpointer; 15], +} + +unsafe impl ClassStruct for RsvgHandleClass { + type Type = imp::CHandle; +} + +/// GObject instance struct for RsvgHandle. +/// +/// This is not done through [`glib::subclass::prelude::InstanceStruct`] because we need +/// to include the `_abi_padding` field for ABI compatibility with the C headers, and +/// `simple::InstanceStruct` does not allow that. +#[repr(C)] +pub struct RsvgHandle { + // Keep this in sync with rsvg.h:RsvgHandle + parent: gobject_ffi::GObject, + + _abi_padding: [gpointer; 16], +} + +unsafe impl InstanceStruct for RsvgHandle { + type Type = imp::CHandle; +} + +/// State machine for `RsvgHandle`. +/// +/// When an `RsvgHandled` is created it is empty / not loaded yet, and it does not know +/// whether the caller will feed it data gradually with the legacy `write()/close()` API, +/// or whether it will be given a `GInputStream` to read in a blocking fashion. After the +/// handle is loaded (e.g. the SVG document is finished parsing), we make sure that no +/// further loading operations can be done. +#[allow(clippy::large_enum_variant)] +enum LoadState { + /// Just created the CHandle; nothing loaded yet. + Start, + + /// Being loaded using the legacy write()/close() API. + /// + /// We buffer all the data from `write()` calls until the time `close()` is called; + /// then we run the buffer through a decompressor in case this is an SVGZ file. + Loading { buffer: Vec }, + + /// Loading finished successfully; the document is in the `SvgHandle`. + ClosedOk { handle: SvgHandle }, + + /// Loaded unsuccessfully. + ClosedError, +} + +impl LoadState { + fn set_from_loading_result( + &mut self, + result: Result, + ) -> Result<(), LoadingError> { + match result { + Ok(handle) => { + *self = LoadState::ClosedOk { handle }; + Ok(()) + } + + Err(e) => { + *self = LoadState::ClosedError; + Err(e) + } + } + } +} + +impl Default for LoadState { + fn default() -> Self { + Self::Start + } +} + +/// Holds the base URL for loading a handle, and the C-accessible version of it +/// +/// There is a public API to query the base URL, and we need to +/// produce a CString with it. However, that API returns a borrowed +/// *const char, so we need to maintain a long-lived CString along with the +/// internal Url. +#[derive(Default)] +struct BaseUrl { + inner: Option, +} + +struct BaseUrlInner { + url: Url, + cstring: CString, +} + +impl BaseUrl { + fn set(&mut self, url: Url) { + let cstring = CString::new(url.as_str()).unwrap(); + + self.inner = Some(BaseUrlInner { url, cstring }); + } + + fn get(&self) -> Option<&Url> { + self.inner.as_ref().map(|b| &b.url) + } + + fn get_gfile(&self) -> Option { + self.get().map(|url| gio::File::for_uri(url.as_str())) + } + + fn get_ptr(&self) -> *const libc::c_char { + self.inner + .as_ref() + .map(|b| b.cstring.as_ptr()) + .unwrap_or_else(ptr::null) + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct RsvgRectangle { + pub x: f64, + pub y: f64, + pub width: f64, + pub height: f64, +} + +impl From for RsvgRectangle { + fn from(r: cairo::Rectangle) -> RsvgRectangle { + RsvgRectangle { + x: r.x(), + y: r.y(), + width: r.width(), + height: r.height(), + } + } +} + +impl From for cairo::Rectangle { + fn from(r: RsvgRectangle) -> cairo::Rectangle { + cairo::Rectangle::new(r.x, r.y, r.width, r.height) + } +} + +mod imp { + use super::*; + use glib::{ParamSpec, ParamSpecDouble, ParamSpecFlags, ParamSpecInt, ParamSpecString}; + use once_cell::sync::Lazy; + + /// Contains all the interior mutability for a RsvgHandle to be called + /// from the C API. + #[derive(Default)] + pub struct CHandle { + pub(super) inner: RefCell, + pub(super) load_state: RefCell, + pub(super) session: Session, + } + + #[derive(Default)] + pub(super) struct CHandleInner { + pub(super) dpi: Dpi, + pub(super) load_flags: LoadFlags, + pub(super) base_url: BaseUrl, + pub(super) size_callback: SizeCallback, + pub(super) is_testing: bool, + } + + #[glib::object_subclass] + impl ObjectSubclass for CHandle { + const NAME: &'static str = "RsvgHandle"; + + type Type = super::CHandle; + + type Instance = RsvgHandle; + type Class = RsvgHandleClass; + } + + impl ObjectImpl for CHandle { + fn properties() -> &'static [ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + ParamSpecFlags::builder::("flags") + .construct_only() + .build(), + ParamSpecDouble::builder("dpi-x").construct().build(), + ParamSpecDouble::builder("dpi-y").construct().build(), + ParamSpecString::builder("base-uri").construct().build(), + ParamSpecInt::builder("width").read_only().build(), + ParamSpecInt::builder("height").read_only().build(), + ParamSpecDouble::builder("em").read_only().build(), + ParamSpecDouble::builder("ex").read_only().build(), + ParamSpecString::builder("title").read_only().build(), + ParamSpecString::builder("desc").read_only().build(), + ParamSpecString::builder("metadata").read_only().build(), + ] + }); + PROPERTIES.as_ref() + } + + fn set_property(&self, id: usize, value: &glib::Value, pspec: &ParamSpec) { + let obj = self.obj(); + match pspec.name() { + "flags" => { + let v: HandleFlags = value.get().expect("flags value has incorrect type"); + obj.set_flags(v); + } + + "dpi-x" => { + let dpi_x: f64 = value.get().expect("dpi-x value has incorrect type"); + obj.set_dpi_x(dpi_x); + } + + "dpi-y" => { + let dpi_y: f64 = value.get().expect("dpi-y value has incorrect type"); + obj.set_dpi_y(dpi_y); + } + + "base-uri" => { + let v: Option = value.get().expect("base-uri value has incorrect type"); + + // rsvg_handle_set_base_uri() expects non-NULL URI strings, + // but the "base-uri" property can be set to NULL due to a missing + // construct-time property. + + if let Some(s) = v { + obj.set_base_url(&s); + } + } + + _ => unreachable!("invalid property id {}", id), + } + } + + fn property(&self, id: usize, pspec: &ParamSpec) -> glib::Value { + let obj = self.obj(); + match pspec.name() { + "flags" => obj.get_flags().to_value(), + "dpi-x" => obj.get_dpi_x().to_value(), + "dpi-y" => obj.get_dpi_y().to_value(), + "base-uri" => obj.get_base_url().to_value(), + "width" => obj.get_dimensions_or_empty().width.to_value(), + "height" => obj.get_dimensions_or_empty().height.to_value(), + "em" => obj.get_dimensions_or_empty().em.to_value(), + "ex" => obj.get_dimensions_or_empty().ex.to_value(), + + // the following three are deprecated + "title" => None::.to_value(), + "desc" => None::.to_value(), + "metadata" => None::.to_value(), + + _ => unreachable!("invalid property id={} for RsvgHandle", id), + } + } + } +} + +glib::wrapper! { + // We don't use subclass:simple::InstanceStruct and ClassStruct + // because we need to maintain the respective _abi_padding of each + // of RsvgHandleClass and RsvgHandle. + pub struct CHandle(ObjectSubclass); +} + +// Keep in sync with tests/src/reference.rs +pub(crate) fn checked_i32(x: f64) -> Result { + cast::i32(x).map_err(|_| cairo::Error::InvalidSize) +} + +// Keep in sync with rsvg.h:RsvgPositionData +#[repr(C)] +pub struct RsvgPositionData { + pub x: libc::c_int, + pub y: libc::c_int, +} + +// Keep in sync with rsvg.h:RsvgDimensionData +#[repr(C)] +pub struct RsvgDimensionData { + pub width: libc::c_int, + pub height: libc::c_int, + pub em: f64, + pub ex: f64, +} + +impl RsvgDimensionData { + // This is not #[derive(Default)] to make it clear that it + // shouldn't be the default value for anything; it is actually a + // special case we use to indicate an error to the public API. + pub fn empty() -> RsvgDimensionData { + RsvgDimensionData { + width: 0, + height: 0, + em: 0.0, + ex: 0.0, + } + } +} + +// Keep in sync with rsvg.h:RsvgSizeFunc +pub type RsvgSizeFunc = Option< + unsafe extern "C" fn( + inout_width: *mut libc::c_int, + inout_height: *mut libc::c_int, + user_data: gpointer, + ), +>; + +struct SizeCallback { + size_func: RsvgSizeFunc, + user_data: gpointer, + destroy_notify: glib::ffi::GDestroyNotify, + in_loop: Cell, +} + +impl SizeCallback { + fn new( + size_func: RsvgSizeFunc, + user_data: gpointer, + destroy_notify: glib::ffi::GDestroyNotify, + ) -> Self { + SizeCallback { + size_func, + user_data, + destroy_notify, + in_loop: Cell::new(false), + } + } + + fn call(&self, width: libc::c_int, height: libc::c_int) -> (libc::c_int, libc::c_int) { + unsafe { + let mut w = width; + let mut h = height; + + if let Some(ref f) = self.size_func { + f(&mut w, &mut h, self.user_data); + }; + + (w, h) + } + } + + fn start_loop(&self) { + assert!(!self.in_loop.get()); + self.in_loop.set(true); + } + + fn end_loop(&self) { + assert!(self.in_loop.get()); + self.in_loop.set(false); + } + + fn get_in_loop(&self) -> bool { + self.in_loop.get() + } +} + +impl Default for SizeCallback { + fn default() -> SizeCallback { + SizeCallback { + size_func: None, + user_data: ptr::null_mut(), + destroy_notify: None, + in_loop: Cell::new(false), + } + } +} + +impl Drop for SizeCallback { + fn drop(&mut self) { + unsafe { + if let Some(ref f) = self.destroy_notify { + f(self.user_data); + }; + } + } +} +pub trait CairoRectangleExt { + fn from_size(width: f64, height: f64) -> Self; +} + +impl CairoRectangleExt for cairo::Rectangle { + fn from_size(width: f64, height: f64) -> Self { + Self::new(0.0, 0.0, width, height) + } +} + +impl CHandle { + fn set_base_url(&self, url: &str) { + let imp = self.imp(); + let session = &imp.session; + let state = imp.load_state.borrow(); + + match *state { + LoadState::Start => (), + _ => { + rsvg_g_critical( + "Please set the base file or URI before loading any data into RsvgHandle", + ); + return; + } + } + + match Url::parse(url) { + Ok(u) => { + rsvg_log!(session, "setting base_uri to \"{}\"", u.as_str()); + let mut inner = imp.inner.borrow_mut(); + inner.base_url.set(u); + } + + Err(e) => { + rsvg_log!( + session, + "not setting base_uri to \"{}\" since it is invalid: {}", + url, + e + ); + } + } + } + + fn set_base_gfile(&self, file: &gio::File) { + self.set_base_url(&file.uri()); + } + + fn get_base_url(&self) -> Option { + let inner = self.imp().inner.borrow(); + inner.base_url.get().map(|url| url.as_str().to_string()) + } + + fn get_base_url_as_ptr(&self) -> *const libc::c_char { + let inner = self.imp().inner.borrow(); + inner.base_url.get_ptr() + } + + fn set_dpi_x(&self, dpi_x: f64) { + let mut inner = self.imp().inner.borrow_mut(); + let dpi = inner.dpi; + inner.dpi = Dpi::new(dpi_x, dpi.y()); + } + + fn set_dpi_y(&self, dpi_y: f64) { + let mut inner = self.imp().inner.borrow_mut(); + let dpi = inner.dpi; + inner.dpi = Dpi::new(dpi.x(), dpi_y); + } + + fn get_dpi_x(&self) -> f64 { + let inner = self.imp().inner.borrow(); + inner.dpi.x() + } + + fn get_dpi_y(&self) -> f64 { + let inner = self.imp().inner.borrow(); + inner.dpi.y() + } + + fn set_flags(&self, flags: HandleFlags) { + let mut inner = self.imp().inner.borrow_mut(); + inner.load_flags = LoadFlags::from(flags); + } + + fn get_flags(&self) -> HandleFlags { + let inner = self.imp().inner.borrow(); + HandleFlags::from(inner.load_flags) + } + + fn set_size_callback( + &self, + size_func: RsvgSizeFunc, + user_data: gpointer, + destroy_notify: glib::ffi::GDestroyNotify, + ) { + let mut inner = self.imp().inner.borrow_mut(); + inner.size_callback = SizeCallback::new(size_func, user_data, destroy_notify); + } + + fn write(&self, buf: &[u8]) { + let mut state = self.imp().load_state.borrow_mut(); + + match *state { + LoadState::Start => { + *state = LoadState::Loading { + buffer: Vec::from(buf), + } + } + + LoadState::Loading { ref mut buffer } => { + buffer.extend_from_slice(buf); + } + + _ => { + rsvg_g_critical("Handle must not be closed in order to write to it"); + } + } + } + + fn close(&self) -> Result<(), LoadingError> { + let imp = self.imp(); + + let inner = imp.inner.borrow(); + let mut state = imp.load_state.borrow_mut(); + + match *state { + LoadState::Start => { + *state = LoadState::ClosedError; + Err(LoadingError::XmlParseError(String::from( + "caller did not write any data", + ))) + } + + LoadState::Loading { ref buffer } => { + let bytes = Bytes::from(buffer); + let stream = gio::MemoryInputStream::from_bytes(&bytes); + + let base_file = inner.base_url.get_gfile(); + self.read_stream(state, &stream.upcast(), base_file.as_ref(), None) + } + + // Closing is idempotent + LoadState::ClosedOk { .. } => Ok(()), + LoadState::ClosedError => Ok(()), + } + } + + fn read_stream_sync( + &self, + stream: &gio::InputStream, + cancellable: Option<&gio::Cancellable>, + ) -> Result<(), LoadingError> { + let imp = self.imp(); + + let state = imp.load_state.borrow_mut(); + let inner = imp.inner.borrow(); + + match *state { + LoadState::Start => { + let base_file = inner.base_url.get_gfile(); + self.read_stream(state, stream, base_file.as_ref(), cancellable) + } + + LoadState::Loading { .. } | LoadState::ClosedOk { .. } | LoadState::ClosedError => { + rsvg_g_critical( + "handle must not be already loaded in order to call \ + rsvg_handle_read_stream_sync()", + ); + Err(LoadingError::Other(String::from("API ordering"))) + } + } + } + + fn read_stream( + &self, + mut load_state: RefMut<'_, LoadState>, + stream: &gio::InputStream, + base_file: Option<&gio::File>, + cancellable: Option<&gio::Cancellable>, + ) -> Result<(), LoadingError> { + let loader = self.make_loader(); + + load_state.set_from_loading_result(loader.read_stream(stream, base_file, cancellable)) + } + + fn get_handle_ref(&self) -> Result, RenderingError> { + let state = self.imp().load_state.borrow(); + + match *state { + LoadState::Start => { + rsvg_g_critical("Handle has not been loaded"); + Err(RenderingError::HandleIsNotLoaded) + } + + LoadState::Loading { .. } => { + rsvg_g_critical("Handle is still loading; call rsvg_handle_close() first"); + Err(RenderingError::HandleIsNotLoaded) + } + + LoadState::ClosedError => { + rsvg_g_critical( + "Handle could not read or parse the SVG; did you check for errors during the \ + loading stage?", + ); + Err(RenderingError::HandleIsNotLoaded) + } + + LoadState::ClosedOk { .. } => Ok(Ref::map(state, |s| match *s { + LoadState::ClosedOk { ref handle } => handle, + _ => unreachable!(), + })), + } + } + + fn make_loader(&self) -> Loader { + let imp = self.imp(); + let inner = imp.inner.borrow(); + let session = imp.session.clone(); + + Loader::new_with_session(session) + .with_unlimited_size(inner.load_flags.unlimited_size) + .keep_image_data(inner.load_flags.keep_image_data) + } + + fn has_sub(&self, id: &str) -> Result { + let handle = self.get_handle_ref()?; + Ok(handle.has_element_with_id(id)?) + } + + fn get_dimensions_or_empty(&self) -> RsvgDimensionData { + self.get_dimensions_sub(None) + .unwrap_or_else(|_| RsvgDimensionData::empty()) + } + + fn get_dimensions_sub(&self, id: Option<&str>) -> Result { + let inner = self.imp().inner.borrow(); + + // This function is probably called from the cairo_render functions, + // or is being erroneously called within the size_func. + // To prevent an infinite loop we are saving the state, and + // returning a meaningless size. + if inner.size_callback.get_in_loop() { + return Ok(RsvgDimensionData { + width: 1, + height: 1, + em: 1.0, + ex: 1.0, + }); + } + + inner.size_callback.start_loop(); + + let res = self + .get_geometry_sub(id) + .and_then(|(ink_r, _)| { + // Keep these in sync with tests/src/reference.rs + let width = checked_i32(ink_r.width().round())?; + let height = checked_i32(ink_r.height().round())?; + + Ok((ink_r, width, height)) + }) + .map(|(ink_r, width, height)| { + let (w, h) = inner.size_callback.call(width, height); + + RsvgDimensionData { + width: w, + height: h, + em: ink_r.width(), + ex: ink_r.height(), + } + }); + + inner.size_callback.end_loop(); + + res + } + + fn get_position_sub(&self, id: Option<&str>) -> Result { + let inner = self.imp().inner.borrow(); + + if id.is_none() { + return Ok(RsvgPositionData { x: 0, y: 0 }); + } + + self.get_geometry_sub(id) + .and_then(|(ink_r, _)| { + let width = checked_i32(ink_r.width().round())?; + let height = checked_i32(ink_r.height().round())?; + + Ok((ink_r, width, height)) + }) + .and_then(|(ink_r, width, height)| { + inner.size_callback.call(width, height); + + Ok(RsvgPositionData { + x: checked_i32(ink_r.x())?, + y: checked_i32(ink_r.y())?, + }) + }) + } + + fn make_renderer<'a>(&self, handle_ref: &'a Ref<'_, SvgHandle>) -> CairoRenderer<'a> { + let inner = self.imp().inner.borrow(); + + CairoRenderer::new(handle_ref) + .with_dpi(inner.dpi.x(), inner.dpi.y()) + .test_mode(inner.is_testing) + } + + fn get_geometry_sub( + &self, + id: Option<&str>, + ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> { + let handle = self.get_handle_ref()?; + let renderer = self.make_renderer(&handle); + + Ok(renderer.legacy_layer_geometry(id)?) + } + + fn set_stylesheet(&self, css: &str) -> Result<(), LoadingError> { + match *self.imp().load_state.borrow_mut() { + LoadState::ClosedOk { ref mut handle } => handle.set_stylesheet(css), + + _ => { + rsvg_g_critical( + "handle must already be loaded in order to call \ + rsvg_handle_set_stylesheet()", + ); + Err(LoadingError::Other(String::from("API ordering"))) + } + } + } + + fn render_cairo_sub( + &self, + cr: *mut cairo::ffi::cairo_t, + id: Option<&str>, + ) -> Result<(), RenderingError> { + let dimensions = self.get_dimensions_sub(None)?; + if dimensions.width == 0 || dimensions.height == 0 { + // nothing to render + return Ok(()); + } + + let viewport = cairo::Rectangle::new( + 0.0, + 0.0, + f64::from(dimensions.width), + f64::from(dimensions.height), + ); + + self.render_layer(cr, id, &viewport) + } + + fn get_pixbuf_sub(&self, id: Option<&str>) -> Result { + let dimensions = self.get_dimensions_sub(None)?; + + if dimensions.width == 0 || dimensions.height == 0 { + return Ok(empty_pixbuf()?); + } + + let surface = cairo::ImageSurface::create( + cairo::Format::ARgb32, + dimensions.width, + dimensions.height, + )?; + + { + let cr = cairo::Context::new(&surface)?; + let cr_raw = cr.to_raw_none(); + self.render_cairo_sub(cr_raw, id)?; + } + + let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?; + + Ok(pixbuf_from_surface(&surface)?) + } + + fn render_document( + &self, + cr: *mut cairo::ffi::cairo_t, + viewport: &cairo::Rectangle, + ) -> Result<(), RenderingError> { + let cr = check_cairo_context(cr)?; + + let handle = self.get_handle_ref()?; + + let renderer = self.make_renderer(&handle); + Ok(renderer.render_document(&cr, viewport)?) + } + + fn get_geometry_for_layer( + &self, + id: Option<&str>, + viewport: &cairo::Rectangle, + ) -> Result<(RsvgRectangle, RsvgRectangle), RenderingError> { + let handle = self.get_handle_ref()?; + let renderer = self.make_renderer(&handle); + + Ok(renderer + .geometry_for_layer(id, viewport) + .map(|(i, l)| (RsvgRectangle::from(i), RsvgRectangle::from(l)))?) + } + + fn render_layer( + &self, + cr: *mut cairo::ffi::cairo_t, + id: Option<&str>, + viewport: &cairo::Rectangle, + ) -> Result<(), RenderingError> { + let cr = check_cairo_context(cr)?; + + let handle = self.get_handle_ref()?; + + let renderer = self.make_renderer(&handle); + + Ok(renderer.render_layer(&cr, id, viewport)?) + } + + fn get_geometry_for_element( + &self, + id: Option<&str>, + ) -> Result<(RsvgRectangle, RsvgRectangle), RenderingError> { + let handle = self.get_handle_ref()?; + + let renderer = self.make_renderer(&handle); + + Ok(renderer + .geometry_for_element(id) + .map(|(i, l)| (RsvgRectangle::from(i), RsvgRectangle::from(l)))?) + } + + fn render_element( + &self, + cr: *mut cairo::ffi::cairo_t, + id: Option<&str>, + element_viewport: &cairo::Rectangle, + ) -> Result<(), RenderingError> { + let cr = check_cairo_context(cr)?; + + let handle = self.get_handle_ref()?; + + let renderer = self.make_renderer(&handle); + + Ok(renderer.render_element(&cr, id, element_viewport)?) + } + + fn get_intrinsic_dimensions(&self) -> Result { + let handle = self.get_handle_ref()?; + let renderer = self.make_renderer(&handle); + Ok(renderer.intrinsic_dimensions()) + } + + fn get_intrinsic_size_in_pixels(&self) -> Result, RenderingError> { + let handle = self.get_handle_ref()?; + let renderer = self.make_renderer(&handle); + Ok(renderer.intrinsic_size_in_pixels()) + } + + fn set_testing(&self, is_testing: bool) { + let mut inner = self.imp().inner.borrow_mut(); + inner.is_testing = is_testing; + } +} + +fn is_rsvg_handle(obj: *const RsvgHandle) -> bool { + unsafe { instance_of::(obj as *const _) } +} + +fn is_input_stream(obj: *mut gio::ffi::GInputStream) -> bool { + unsafe { instance_of::(obj as *const _) } +} + +fn is_gfile(obj: *const gio::ffi::GFile) -> bool { + unsafe { instance_of::(obj as *const _) } +} + +fn is_cancellable(obj: *mut gio::ffi::GCancellable) -> bool { + unsafe { instance_of::(obj as *const _) } +} + +fn get_rust_handle(handle: *const RsvgHandle) -> CHandle { + let handle = unsafe { &*handle }; + handle.imp().obj().to_owned() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_type() -> glib::ffi::GType { + CHandle::static_type().into_glib() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_error_get_type() -> glib::ffi::GType { + Error::static_type().into_glib() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_flags_get_type() -> glib::ffi::GType { + HandleFlags::static_type().into_glib() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_set_base_uri( + handle: *const RsvgHandle, + uri: *const libc::c_char, +) { + rsvg_return_if_fail! { + rsvg_handle_set_base_uri; + + is_rsvg_handle(handle), + !uri.is_null(), + } + + let rhandle = get_rust_handle(handle); + + assert!(!uri.is_null()); + let uri: String = from_glib_none(uri); + + rhandle.set_base_url(&uri); +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_set_base_gfile( + handle: *const RsvgHandle, + raw_gfile: *mut gio::ffi::GFile, +) { + rsvg_return_if_fail! { + rsvg_handle_set_base_gfile; + + is_rsvg_handle(handle), + is_gfile(raw_gfile), + } + + let rhandle = get_rust_handle(handle); + + assert!(!raw_gfile.is_null()); + + let file: gio::File = from_glib_none(raw_gfile); + + rhandle.set_base_gfile(&file); +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_base_uri( + handle: *const RsvgHandle, +) -> *const libc::c_char { + rsvg_return_val_if_fail! { + rsvg_handle_get_base_uri => ptr::null(); + + is_rsvg_handle(handle), + } + + let rhandle = get_rust_handle(handle); + + rhandle.get_base_url_as_ptr() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_set_dpi(handle: *const RsvgHandle, dpi: libc::c_double) { + rsvg_return_if_fail! { + rsvg_handle_set_dpi; + + is_rsvg_handle(handle), + } + + let rhandle = get_rust_handle(handle); + rhandle.set_dpi_x(dpi); + rhandle.set_dpi_y(dpi); +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_set_dpi_x_y( + handle: *const RsvgHandle, + dpi_x: libc::c_double, + dpi_y: libc::c_double, +) { + rsvg_return_if_fail! { + rsvg_handle_set_dpi_x_y; + + is_rsvg_handle(handle), + } + + let rhandle = get_rust_handle(handle); + rhandle.set_dpi_x(dpi_x); + rhandle.set_dpi_y(dpi_y); +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_set_size_callback( + handle: *const RsvgHandle, + size_func: RsvgSizeFunc, + user_data: gpointer, + destroy_notify: glib::ffi::GDestroyNotify, +) { + rsvg_return_if_fail! { + rsvg_handle_set_size_callback; + + is_rsvg_handle(handle), + } + + let rhandle = get_rust_handle(handle); + + rhandle.set_size_callback(size_func, user_data, destroy_notify); +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_internal_set_testing( + handle: *const RsvgHandle, + testing: glib::ffi::gboolean, +) { + rsvg_return_if_fail! { + rsvg_handle_internal_set_testing; + + is_rsvg_handle(handle), + } + + let rhandle = get_rust_handle(handle); + + rhandle.set_testing(from_glib(testing)); +} + +trait IntoGError { + type GlibResult; + + fn into_gerror(self, session: &Session, error: *mut *mut glib::ffi::GError) + -> Self::GlibResult; +} + +impl IntoGError for Result<(), E> { + type GlibResult = glib::ffi::gboolean; + + fn into_gerror( + self, + session: &Session, + error: *mut *mut glib::ffi::GError, + ) -> Self::GlibResult { + match self { + Ok(()) => true.into_glib(), + + Err(e) => { + set_gerror(session, error, 0, &format!("{e}")); + false.into_glib() + } + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_read_stream_sync( + handle: *const RsvgHandle, + stream: *mut gio::ffi::GInputStream, + cancellable: *mut gio::ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_read_stream_sync => false.into_glib(); + + is_rsvg_handle(handle), + is_input_stream(stream), + cancellable.is_null() || is_cancellable(cancellable), + error.is_null() || (*error).is_null(), + } + + let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); + + let stream = gio::InputStream::from_glib_none(stream); + let cancellable: Option = from_glib_none(cancellable); + + rhandle + .read_stream_sync(&stream, cancellable.as_ref()) + .into_gerror(&session, error) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_write( + handle: *const RsvgHandle, + buf: *const u8, + count: usize, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_write => false.into_glib(); + + is_rsvg_handle(handle), + error.is_null() || (*error).is_null(), + !buf.is_null() || count == 0, + } + + let rhandle = get_rust_handle(handle); + let buffer = slice::from_raw_parts(buf, count); + rhandle.write(buffer); + + true.into_glib() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_close( + handle: *const RsvgHandle, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_close => false.into_glib(); + + is_rsvg_handle(handle), + error.is_null() || (*error).is_null(), + } + + let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); + + rhandle.close().into_gerror(&session, error) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_has_sub( + handle: *const RsvgHandle, + id: *const libc::c_char, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_has_sub => false.into_glib(); + + is_rsvg_handle(handle), + } + + let rhandle = get_rust_handle(handle); + + if id.is_null() { + return false.into_glib(); + } + + let id: String = from_glib_none(id); + rhandle.has_sub(&id).unwrap_or(false).into_glib() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_render_cairo( + handle: *const RsvgHandle, + cr: *mut cairo::ffi::cairo_t, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_render_cairo => false.into_glib(); + + is_rsvg_handle(handle), + !cr.is_null(), + } + + let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); + + rhandle + .render_cairo_sub(cr, None) + .into_gerror(&session, ptr::null_mut()) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_render_cairo_sub( + handle: *const RsvgHandle, + cr: *mut cairo::ffi::cairo_t, + id: *const libc::c_char, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_render_cairo_sub => false.into_glib(); + + is_rsvg_handle(handle), + !cr.is_null(), + } + + let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); + + let id: Option = from_glib_none(id); + + rhandle + .render_cairo_sub(cr, id.as_deref()) + .into_gerror(&session, ptr::null_mut()) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_pixbuf( + handle: *const RsvgHandle, +) -> *mut gdk_pixbuf::ffi::GdkPixbuf { + rsvg_return_val_if_fail! { + rsvg_handle_get_pixbuf => ptr::null_mut(); + + is_rsvg_handle(handle), + } + + let rhandle = get_rust_handle(handle); + + match rhandle.get_pixbuf_sub(None) { + Ok(pixbuf) => pixbuf.to_glib_full(), + Err(e) => { + let session = &rhandle.imp().session; + rsvg_log!(session, "could not render: {}", e); + ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_pixbuf_sub( + handle: *const RsvgHandle, + id: *const libc::c_char, +) -> *mut gdk_pixbuf::ffi::GdkPixbuf { + rsvg_return_val_if_fail! { + rsvg_handle_get_pixbuf_sub => ptr::null_mut(); + + is_rsvg_handle(handle), + } + + let rhandle = get_rust_handle(handle); + let id: Option = from_glib_none(id); + + match rhandle.get_pixbuf_sub(id.as_deref()) { + Ok(pixbuf) => pixbuf.to_glib_full(), + Err(e) => { + let session = &rhandle.imp().session; + rsvg_log!(session, "could not render: {}", e); + ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_dimensions( + handle: *const RsvgHandle, + dimension_data: *mut RsvgDimensionData, +) { + rsvg_handle_get_dimensions_sub(handle, dimension_data, ptr::null()); +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_dimensions_sub( + handle: *const RsvgHandle, + dimension_data: *mut RsvgDimensionData, + id: *const libc::c_char, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_get_dimensions_sub => false.into_glib(); + + is_rsvg_handle(handle), + !dimension_data.is_null(), + } + + let rhandle = get_rust_handle(handle); + + let id: Option = from_glib_none(id); + + match rhandle.get_dimensions_sub(id.as_deref()) { + Ok(dimensions) => { + *dimension_data = dimensions; + true.into_glib() + } + + Err(e) => { + let session = &rhandle.imp().session; + rsvg_log!(session, "could not get dimensions: {}", e); + *dimension_data = RsvgDimensionData::empty(); + false.into_glib() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_position_sub( + handle: *const RsvgHandle, + position_data: *mut RsvgPositionData, + id: *const libc::c_char, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_get_position_sub => false.into_glib(); + + is_rsvg_handle(handle), + !position_data.is_null(), + } + + let rhandle = get_rust_handle(handle); + + let id: Option = from_glib_none(id); + + match rhandle.get_position_sub(id.as_deref()) { + Ok(position) => { + *position_data = position; + true.into_glib() + } + + Err(e) => { + let p = &mut *position_data; + + p.x = 0; + p.y = 0; + + let session = &rhandle.imp().session; + rsvg_log!(session, "could not get position: {}", e); + false.into_glib() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_new() -> *const RsvgHandle { + let obj = glib::Object::new::(); + + obj.to_glib_full() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_new_with_flags(flags: RsvgHandleFlags) -> *const RsvgHandle { + let obj = glib::Object::builder::() + .property("flags", &HandleFlags::from_bits_truncate(flags)) + .build(); + + obj.to_glib_full() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_new_from_file( + filename: *const libc::c_char, + error: *mut *mut glib::ffi::GError, +) -> *const RsvgHandle { + rsvg_return_val_if_fail! { + rsvg_handle_new_from_file => ptr::null(); + + !filename.is_null(), + error.is_null() || (*error).is_null(), + } + + let file = match PathOrUrl::new(filename) { + Ok(p) => p.get_gfile(), + + Err(s) => { + // Here we don't have a handle created yet, so it's fine to create a session + // to log the error message. We'll need to change this when we start logging + // API calls, so that we can log the call to rsvg_handle_new_from_file() and + // then pass *that* session to rsvg_handle_new_from_gfile_sync() below. + let session = Session::default(); + set_gerror(&session, error, 0, &s); + return ptr::null_mut(); + } + }; + + rsvg_handle_new_from_gfile_sync(file.to_glib_none().0, 0, ptr::null_mut(), error) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_new_from_gfile_sync( + file: *mut gio::ffi::GFile, + flags: RsvgHandleFlags, + cancellable: *mut gio::ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *const RsvgHandle { + rsvg_return_val_if_fail! { + rsvg_handle_new_from_gfile_sync => ptr::null(); + + is_gfile(file), + cancellable.is_null() || is_cancellable(cancellable), + error.is_null() || (*error).is_null(), + } + + let raw_handle = rsvg_handle_new_with_flags(flags); + + let rhandle = get_rust_handle(raw_handle); + let session = rhandle.imp().session.clone(); + + let file = gio::File::from_glib_none(file); + rhandle.set_base_gfile(&file); + + let cancellable: Option = from_glib_none(cancellable); + + let res = file + .read(cancellable.as_ref()) + .map_err(LoadingError::from) + .and_then(|stream| rhandle.read_stream_sync(&stream.upcast(), cancellable.as_ref())); + + match res { + Ok(()) => raw_handle, + + Err(e) => { + set_gerror(&session, error, 0, &format!("{e}")); + gobject_ffi::g_object_unref(raw_handle as *mut _); + ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_new_from_stream_sync( + input_stream: *mut gio::ffi::GInputStream, + base_file: *mut gio::ffi::GFile, + flags: RsvgHandleFlags, + cancellable: *mut gio::ffi::GCancellable, + error: *mut *mut glib::ffi::GError, +) -> *const RsvgHandle { + rsvg_return_val_if_fail! { + rsvg_handle_new_from_stream_sync => ptr::null(); + + is_input_stream(input_stream), + base_file.is_null() || is_gfile(base_file), + cancellable.is_null() || is_cancellable(cancellable), + error.is_null() || (*error).is_null(), + } + + let raw_handle = rsvg_handle_new_with_flags(flags); + + let rhandle = get_rust_handle(raw_handle); + let session = rhandle.imp().session.clone(); + + let base_file: Option = from_glib_none(base_file); + if let Some(base_file) = base_file { + rhandle.set_base_gfile(&base_file); + } + + let stream: gio::InputStream = from_glib_none(input_stream); + let cancellable: Option = from_glib_none(cancellable); + + match rhandle.read_stream_sync(&stream, cancellable.as_ref()) { + Ok(()) => raw_handle, + + Err(e) => { + set_gerror(&session, error, 0, &format!("{e}")); + gobject_ffi::g_object_unref(raw_handle as *mut _); + ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_new_from_data( + data: *const u8, + data_len: usize, + error: *mut *mut glib::ffi::GError, +) -> *const RsvgHandle { + rsvg_return_val_if_fail! { + rsvg_handle_new_from_data => ptr::null(); + + !data.is_null() || data_len == 0, + data_len <= std::isize::MAX as usize, + error.is_null() || (*error).is_null(), + } + + // We create the MemoryInputStream without the gtk-rs binding because of this: + // + // - The binding doesn't provide _new_from_data(). All of the binding's ways to + // put data into a MemoryInputStream involve copying the data buffer. + // + // - We can't use glib::Bytes from the binding either, for the same reason. + // + // - For now, we are using the other C-visible constructor, so we need a raw pointer to the + // stream, anyway. + + assert!(data_len <= std::isize::MAX as usize); + let data_len = data_len as isize; + + let raw_stream = gio::ffi::g_memory_input_stream_new_from_data(data as *mut u8, data_len, None); + + let ret = rsvg_handle_new_from_stream_sync( + raw_stream, + ptr::null_mut(), // base_file + 0, + ptr::null_mut(), // cancellable + error, + ); + + gobject_ffi::g_object_unref(raw_stream as *mut _); + ret +} + +unsafe fn set_out_param( + out_has_param: *mut glib::ffi::gboolean, + out_param: *mut T, + value: &Option, +) { + let has_value = if let Some(ref v) = *value { + if !out_param.is_null() { + *out_param = *v; + } + + true + } else { + false + }; + + if !out_has_param.is_null() { + *out_has_param = has_value.into_glib(); + } +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_free(handle: *mut RsvgHandle) { + gobject_ffi::g_object_unref(handle as *mut _); +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_set_stylesheet( + handle: *const RsvgHandle, + css: *const u8, + css_len: usize, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_set_stylesheet => false.into_glib(); + + is_rsvg_handle(handle), + !css.is_null() || (css.is_null() && css_len == 0), + error.is_null() || (*error).is_null(), + } + + let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); + + let css = match (css, css_len) { + (p, 0) if p.is_null() => "", + (_, _) => { + let s = slice::from_raw_parts(css, css_len); + match str::from_utf8(s) { + Ok(s) => s, + Err(e) => { + set_gerror(&session, error, 0, &format!("CSS is not valid UTF-8: {e}")); + return false.into_glib(); + } + } + } + }; + + rhandle.set_stylesheet(css).into_gerror(&session, error) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_intrinsic_dimensions( + handle: *const RsvgHandle, + out_has_width: *mut glib::ffi::gboolean, + out_width: *mut RsvgLength, + out_has_height: *mut glib::ffi::gboolean, + out_height: *mut RsvgLength, + out_has_viewbox: *mut glib::ffi::gboolean, + out_viewbox: *mut RsvgRectangle, +) { + rsvg_return_if_fail! { + rsvg_handle_get_intrinsic_dimensions; + + is_rsvg_handle(handle), + } + + let rhandle = get_rust_handle(handle); + + let d = rhandle + .get_intrinsic_dimensions() + .unwrap_or_else(|_| panic!("API called out of order")); + + let w = d.width; + let h = d.height; + let r = d.vbox.map(RsvgRectangle::from); + + set_out_param(out_has_width, out_width, &Into::into(w)); + set_out_param(out_has_height, out_height, &Into::into(h)); + set_out_param(out_has_viewbox, out_viewbox, &r); +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_intrinsic_size_in_pixels( + handle: *const RsvgHandle, + out_width: *mut f64, + out_height: *mut f64, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_get_intrinsic_size_in_pixels => false.into_glib(); + + is_rsvg_handle(handle), + } + + let rhandle = get_rust_handle(handle); + + let dim = rhandle + .get_intrinsic_size_in_pixels() + .unwrap_or_else(|_| panic!("API called out of order")); + + let (w, h) = dim.unwrap_or((0.0, 0.0)); + + if !out_width.is_null() { + *out_width = w; + } + + if !out_height.is_null() { + *out_height = h; + } + + dim.is_some().into_glib() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_render_document( + handle: *const RsvgHandle, + cr: *mut cairo::ffi::cairo_t, + viewport: *const RsvgRectangle, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_render_document => false.into_glib(); + + is_rsvg_handle(handle), + !cr.is_null(), + !viewport.is_null(), + error.is_null() || (*error).is_null(), + } + + let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); + + rhandle + .render_document(cr, &(*viewport).into()) + .into_gerror(&session, error) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_geometry_for_layer( + handle: *mut RsvgHandle, + id: *const libc::c_char, + viewport: *const RsvgRectangle, + out_ink_rect: *mut RsvgRectangle, + out_logical_rect: *mut RsvgRectangle, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_get_geometry_for_layer => false.into_glib(); + + is_rsvg_handle(handle), + !viewport.is_null(), + error.is_null() || (*error).is_null(), + } + + let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); + + let id: Option = from_glib_none(id); + + rhandle + .get_geometry_for_layer(id.as_deref(), &(*viewport).into()) + .map(|(ink_rect, logical_rect)| { + if !out_ink_rect.is_null() { + *out_ink_rect = ink_rect; + } + + if !out_logical_rect.is_null() { + *out_logical_rect = logical_rect; + } + }) + .into_gerror(&session, error) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_render_layer( + handle: *const RsvgHandle, + cr: *mut cairo::ffi::cairo_t, + id: *const libc::c_char, + viewport: *const RsvgRectangle, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_render_layer => false.into_glib(); + + is_rsvg_handle(handle), + !cr.is_null(), + !viewport.is_null(), + error.is_null() || (*error).is_null(), + } + + let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); + + let id: Option = from_glib_none(id); + + rhandle + .render_layer(cr, id.as_deref(), &(*viewport).into()) + .into_gerror(&session, error) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_geometry_for_element( + handle: *const RsvgHandle, + id: *const libc::c_char, + out_ink_rect: *mut RsvgRectangle, + out_logical_rect: *mut RsvgRectangle, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_get_geometry_for_element => false.into_glib(); + + is_rsvg_handle(handle), + error.is_null() || (*error).is_null(), + } + + let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); + + let id: Option = from_glib_none(id); + + rhandle + .get_geometry_for_element(id.as_deref()) + .map(|(ink_rect, logical_rect)| { + if !out_ink_rect.is_null() { + *out_ink_rect = ink_rect; + } + + if !out_logical_rect.is_null() { + *out_logical_rect = logical_rect; + } + }) + .into_gerror(&session, error) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_render_element( + handle: *const RsvgHandle, + cr: *mut cairo::ffi::cairo_t, + id: *const libc::c_char, + element_viewport: *const RsvgRectangle, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + rsvg_return_val_if_fail! { + rsvg_handle_render_element => false.into_glib(); + + is_rsvg_handle(handle), + !cr.is_null(), + !element_viewport.is_null(), + error.is_null() || (*error).is_null(), + } + + let rhandle = get_rust_handle(handle); + let session = rhandle.imp().session.clone(); + + let id: Option = from_glib_none(id); + + rhandle + .render_element(cr, id.as_deref(), &(*element_viewport).into()) + .into_gerror(&session, error) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_desc(handle: *const RsvgHandle) -> *mut libc::c_char { + rsvg_return_val_if_fail! { + rsvg_handle_get_desc => ptr::null_mut(); + + is_rsvg_handle(handle), + } + + ptr::null_mut() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_metadata(handle: *const RsvgHandle) -> *mut libc::c_char { + rsvg_return_val_if_fail! { + rsvg_handle_get_metadata => ptr::null_mut(); + + is_rsvg_handle(handle), + } + + ptr::null_mut() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_handle_get_title(handle: *const RsvgHandle) -> *mut libc::c_char { + rsvg_return_val_if_fail! { + rsvg_handle_get_title => ptr::null_mut(); + + is_rsvg_handle(handle), + } + + ptr::null_mut() +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_init() {} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_term() {} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_cleanup() {} + +/// Detects whether a `*const libc::c_char` is a path or a URI +/// +/// `rsvg_handle_new_from_file()` takes a `filename` argument, and advertises +/// that it will detect either a file system path, or a proper URI. It will then use +/// `gio::File::for_path()` or `gio::File::for_uri()` as appropriate. +/// +/// This enum does the magic heuristics to figure this out. +/// +/// The `from_os_str` version is for using the same logic on rsvg-convert's command-line +/// arguments: we want `rsvg-convert http://example.com/foo.svg` to go to a URL, not to a +/// local file with that name. +#[derive(Clone, Debug)] +pub enum PathOrUrl { + Path(PathBuf), + Url(Url), +} + +impl PathOrUrl { + unsafe fn new(s: *const libc::c_char) -> Result { + let cstr = CStr::from_ptr(s); + + if cstr.to_bytes().is_empty() { + return Err("invalid empty filename".to_string()); + } + + Ok(cstr + .to_str() + .map_err(|_| ()) + .and_then(Self::try_from_str) + .unwrap_or_else(|_| PathOrUrl::Path(PathBuf::from_glib_none(s)))) + } + + fn try_from_str(s: &str) -> Result { + assert!(!s.is_empty()); + + Url::parse(s).map_err(|_| ()).and_then(|url| { + if url.origin().is_tuple() || url.scheme() == "file" { + Ok(PathOrUrl::Url(url)) + } else { + Ok(PathOrUrl::Path(url.to_file_path()?)) + } + }) + } + + pub fn from_os_str(osstr: &OsStr) -> Result { + if osstr.is_empty() { + return Err("invalid empty filename".to_string()); + } + + Ok(osstr + .to_str() + .ok_or(()) + .and_then(Self::try_from_str) + .unwrap_or_else(|_| PathOrUrl::Path(PathBuf::from(osstr.to_os_string())))) + } + + pub fn get_gfile(&self) -> gio::File { + match *self { + PathOrUrl::Path(ref p) => gio::File::for_path(p), + PathOrUrl::Url(ref u) => gio::File::for_uri(u.as_str()), + } + } +} + +impl fmt::Display for PathOrUrl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + PathOrUrl::Path(ref p) => p.display().fmt(f), + PathOrUrl::Url(ref u) => u.fmt(f), + } + } +} + +fn check_cairo_context(cr: *mut cairo::ffi::cairo_t) -> Result { + let status = unsafe { cairo::ffi::cairo_status(cr) }; + + if status == cairo::ffi::STATUS_SUCCESS { + Ok(unsafe { from_glib_none(cr) }) + } else { + let status: cairo::Error = status.into(); + + let msg = format!("cannot render on a cairo_t with a failure status (status={status:?})"); + + rsvg_g_warning(&msg); + + Err(RenderingError::from(status)) + } +} + +pub(crate) fn set_gerror( + session: &Session, + err: *mut *mut glib::ffi::GError, + code: u32, + msg: &str, +) { + unsafe { + // this is RSVG_ERROR_FAILED, the only error code available in RsvgError + assert!(code == 0); + + // Log this, in case the calling program passes a NULL GError, so we can at least + // diagnose things by asking for RSVG_LOG. + // + // See https://gitlab.gnome.org/GNOME/gtk/issues/2294 for an example of code that + // passed a NULL GError and so we had no easy way to see what was wrong. + rsvg_log!(session, "{}", msg); + + glib::ffi::g_set_error_literal( + err, + rsvg_error_quark(), + code as libc::c_int, + msg.to_glib_none().0, + ); + } +} + +#[derive(Debug, Eq, PartialEq, Clone, Copy, glib::Enum)] +#[repr(u32)] +#[enum_type(name = "RsvgError")] +enum Error { + #[enum_value(name = "RSVG_ERROR_FAILED", nick = "failed")] + // Keep in sync with rsvg.h:RsvgError + Failed = 0, +} + +/// Used as a generic error to translate to glib::Error +/// +/// This type implements `glib::error::ErrorDomain`, so it can be used +/// to obtain the error code while calling `glib::Error::new()`. Unfortunately +/// the public librsvg API does not have detailed error codes yet, so we use +/// this single value as the only possible error code to return. +#[derive(Copy, Clone)] +struct RsvgError; + +impl ErrorDomain for RsvgError { + fn domain() -> glib::Quark { + glib::Quark::from_str("rsvg-error-quark") + } + + fn code(self) -> i32 { + Error::Failed as i32 + } + + fn from(_code: i32) -> Option { + // We don't have enough information from glib error codes + Some(RsvgError) + } +} + +#[no_mangle] +pub extern "C" fn rsvg_error_quark() -> glib::ffi::GQuark { + RsvgError::domain().into_glib() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn path_or_url_unix() { + unsafe { + match PathOrUrl::new(rsvg_c_str!("/foo/bar")).unwrap() { + PathOrUrl::Path(_) => (), + _ => panic!("unix filename should be a PathOrUrl::Path"), + } + + match PathOrUrl::new(rsvg_c_str!("foo/bar")).unwrap() { + PathOrUrl::Path(_) => (), + _ => panic!("unix filename should be a PathOrUrl::Path"), + } + } + } + + #[test] + fn path_or_url_windows() { + unsafe { + match PathOrUrl::new(rsvg_c_str!("c:/foo/bar")).unwrap() { + PathOrUrl::Path(_) => (), + _ => panic!("windows filename should be a PathOrUrl::Path"), + } + + match PathOrUrl::new(rsvg_c_str!("C:/foo/bar")).unwrap() { + PathOrUrl::Path(_) => (), + _ => panic!("windows filename should be a PathOrUrl::Path"), + } + + match PathOrUrl::new(rsvg_c_str!("c:\\foo\\bar")).unwrap() { + PathOrUrl::Path(_) => (), + _ => panic!("windows filename should be a PathOrUrl::Path"), + } + + match PathOrUrl::new(rsvg_c_str!("C:\\foo\\bar")).unwrap() { + PathOrUrl::Path(_) => (), + _ => panic!("windows filename should be a PathOrUrl::Path"), + } + } + } + + #[test] + fn path_or_url_unix_url() { + unsafe { + match PathOrUrl::new(rsvg_c_str!("file:///foo/bar")).unwrap() { + PathOrUrl::Url(_) => (), + _ => panic!("file:// unix filename should be a PathOrUrl::Url"), + } + } + } + + #[test] + fn path_or_url_windows_url() { + unsafe { + match PathOrUrl::new(rsvg_c_str!("file://c:/foo/bar")).unwrap() { + PathOrUrl::Url(_) => (), + _ => panic!("file:// windows filename should be a PathOrUrl::Url"), + } + + match PathOrUrl::new(rsvg_c_str!("file://C:/foo/bar")).unwrap() { + PathOrUrl::Url(_) => (), + _ => panic!("file:// windows filename should be a PathOrUrl::Url"), + } + } + } + + #[test] + fn path_or_url_empty_str() { + unsafe { + assert!(PathOrUrl::new(rsvg_c_str!("")).is_err()); + } + + assert!(PathOrUrl::from_os_str(OsStr::new("")).is_err()); + } + + #[test] + fn base_url_works() { + let mut u = BaseUrl::default(); + + assert!(u.get().is_none()); + assert_eq!(u.get_ptr(), ptr::null()); + + u.set(Url::parse("file:///example.txt").unwrap()); + + assert_eq!(u.get().unwrap().as_str(), "file:///example.txt"); + + unsafe { + let p = u.get_ptr(); + let cstr = CStr::from_ptr(p); + assert_eq!(cstr.to_str().unwrap(), "file:///example.txt"); + } + } +} diff --git a/librsvg-c/src/c_api/messages.rs b/librsvg-c/src/c_api/messages.rs new file mode 100644 index 00000000..5b227cb8 --- /dev/null +++ b/librsvg-c/src/c_api/messages.rs @@ -0,0 +1,158 @@ +//! Logging functions, plus Rust versions of `g_return_if_fail()`. +//! +//! Glib's `g_return_if_fail()`, `g_warning()`, etc. are all C macros, so they cannot be +//! used from Rust. This module defines equivalent functions or macros with an `rsvg_` +//! prefix, to be clear that they should only be used from the implementation of the C API +//! and not from the main Rust code of the library. + +use glib::ffi::{g_log_structured_array, GLogField, G_LOG_LEVEL_CRITICAL, G_LOG_LEVEL_WARNING}; +use glib::translate::*; + +/* + G_LOG_LEVEL_CRITICAL = 1 << 3, + G_LOG_LEVEL_WARNING = 1 << 4, + +#define g_critical(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, \ + __FILE__, G_STRINGIFY (__LINE__), \ + G_STRFUNC, __VA_ARGS__) +#define g_warning(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, \ + __FILE__, G_STRINGIFY (__LINE__), \ + G_STRFUNC, __VA_ARGS__) + GLogField fields[] = + { + { "PRIORITY", log_level_to_priority (log_level), -1 }, + { "CODE_FILE", file, -1 }, + { "CODE_LINE", line, -1 }, + { "CODE_FUNC", func, -1 }, + /* Filled in later: */ + { "MESSAGE", NULL, -1 }, + /* If @log_domain is %NULL, we will not pass this field: */ + { "GLIB_DOMAIN", log_domain, -1 }, + }; + + g_log_structured_array (log_level, fields, n_fields); + */ + +/// Helper function for converting string literals to C char pointers. +#[macro_export] +macro_rules! rsvg_c_str { + ($txt:expr) => { + // SAFETY: it's important that the type we pass to `from_bytes_with_nul` is 'static, + // so that the storage behind the returned pointer doesn't get freed while it's still + // being used. We get that by only allowing string literals. + std::ffi::CStr::from_bytes_with_nul(concat!($txt, "\0").as_bytes()) + .unwrap() + .as_ptr() + }; +} + +/// Helper for `rsvg_g_warning` and `rsvg_g_critical` +/// +/// This simulates what in C would be a call to the g_warning() or g_critical() +/// macros, but with the underlying function g_log_structured_array(). +/// +/// If the implementation of g_warning() or g_critical() changes, we'll have +/// to change this function. +fn rsvg_g_log(level: glib::ffi::GLogLevelFlags, msg: &str) { + // stolen from gmessages.c:log_level_to_priority() + let priority = match level { + G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL => rsvg_c_str!("4"), + _ => unreachable!("please add another log level priority to rsvg_g_log()"), + }; + + let c_msg = msg.to_glib_none(); + let c_char_msg: *const libc::c_char = c_msg.0; + + // Glib's g_log_structured_standard() adds a few more fields for the source + // file, line number, etc., but those are not terribly useful without a stack + // trace. So, we'll omit them. + let fields = [ + GLogField { + key: rsvg_c_str!("PRIORITY"), + value: priority as *const _, + length: -1, + }, + GLogField { + key: rsvg_c_str!("MESSAGE"), + value: c_char_msg as *const _, + length: msg.len() as _, + }, + // This is the G_LOG_DOMAIN set from the Makefile + GLogField { + key: rsvg_c_str!("GLIB_DOMAIN"), + value: rsvg_c_str!("librsvg") as *const _, + length: -1, + }, + ]; + + unsafe { + g_log_structured_array(level, fields.as_ptr(), fields.len()); + } +} + +/// Replacement for `g_warning()`. +/// +/// Use this to signal an error condition in the following situations: +/// +/// * The C API does not have an adequate error code for the error in question (and cannot +/// be changed to have one, for ABI compatibility reasons). +/// +/// * Applications using the C API would be prone to ignoring an important error, +/// so it's best to have a warning on the console to at least have a hope of someone +/// noticing the error. +pub(crate) fn rsvg_g_warning(msg: &str) { + rsvg_g_log(glib::ffi::G_LOG_LEVEL_WARNING, msg); +} + +/// Replacement for `g_critical()`. +/// +/// Use this to signal a programming error from the caller of the C API, like passing +/// incorrect arguments or calling the API out of order. Rust code conventionally panics +/// in such situations, but C/Glib code does not, so it's best to "do nothing", print a +/// critical message, and return. Development versions of GNOME will crash the program +/// if this happens; release versions will ignore the error. +pub(crate) fn rsvg_g_critical(msg: &str) { + rsvg_g_log(glib::ffi::G_LOG_LEVEL_CRITICAL, msg); +} + +/// Replacement for `g_return_if_fail()`. +// Once Rust has a function! macro that gives us the current function name, we +// can remove the $func_name argument. +#[macro_export] +macro_rules! rsvg_return_if_fail { + { + $func_name:ident; + $($condition:expr,)+ + } => { + $( + if !$condition { + glib::ffi::g_return_if_fail_warning( + rsvg_c_str!("librsvg"), + rsvg_c_str!(stringify!($func_name)), + rsvg_c_str!(stringify!($condition)), + ); + return; + } + )+ + } +} + +/// Replacement for `g_return_val_if_fail()`. +#[macro_export] +macro_rules! rsvg_return_val_if_fail { + { + $func_name:ident => $retval:expr; + $($condition:expr,)+ + } => { + $( + if !$condition { + glib::ffi::g_return_if_fail_warning( + rsvg_c_str!("librsvg"), + rsvg_c_str!(stringify!($func_name)), + rsvg_c_str!(stringify!($condition)), + ); + return $retval; + } + )+ + } +} diff --git a/librsvg-c/src/c_api/mod.rs b/librsvg-c/src/c_api/mod.rs new file mode 100644 index 00000000..febca033 --- /dev/null +++ b/librsvg-c/src/c_api/mod.rs @@ -0,0 +1,58 @@ +//! C API for librsvg, based on GObject. +//! +//! The main API is in the [`handle`] module. The other modules +//! have utility functions and the legacy [pixbuf-based API][pixbuf_utils]. + +#![allow(clippy::missing_safety_doc)] + +#[rustfmt::skip] +pub use handle::{ + rsvg_error_get_type, + rsvg_handle_close, + rsvg_handle_flags_get_type, + rsvg_handle_get_base_uri, + rsvg_handle_get_dimensions, + rsvg_handle_get_dimensions_sub, + rsvg_handle_get_geometry_for_element, + rsvg_handle_get_geometry_for_layer, + rsvg_handle_get_intrinsic_dimensions, + rsvg_handle_get_intrinsic_size_in_pixels, + rsvg_handle_get_pixbuf_sub, + rsvg_handle_get_position_sub, + rsvg_handle_has_sub, + rsvg_handle_internal_set_testing, + rsvg_handle_new_from_data, + rsvg_handle_new_from_file, + rsvg_handle_new_from_gfile_sync, + rsvg_handle_new_from_stream_sync, + rsvg_handle_new_with_flags, + rsvg_handle_read_stream_sync, + rsvg_handle_render_cairo_sub, + rsvg_handle_render_element, + rsvg_handle_render_document, + rsvg_handle_render_layer, + rsvg_handle_set_base_gfile, + rsvg_handle_set_base_uri, + rsvg_handle_set_dpi_x_y, + rsvg_handle_set_size_callback, + rsvg_handle_write, +}; + +pub use dpi::{rsvg_set_default_dpi, rsvg_set_default_dpi_x_y}; + +#[rustfmt::skip] +pub use pixbuf_utils::{ + rsvg_pixbuf_from_file, + rsvg_pixbuf_from_file_at_max_size, + rsvg_pixbuf_from_file_at_size, + rsvg_pixbuf_from_file_at_zoom, + rsvg_pixbuf_from_file_at_zoom_with_max, +}; + +#[macro_use] +mod messages; + +mod dpi; +pub mod handle; +pub mod pixbuf_utils; +pub mod sizing; diff --git a/librsvg-c/src/c_api/pixbuf_utils.rs b/librsvg-c/src/c_api/pixbuf_utils.rs new file mode 100644 index 00000000..710f2c4b --- /dev/null +++ b/librsvg-c/src/c_api/pixbuf_utils.rs @@ -0,0 +1,330 @@ +//! Legacy C API for functions that render directly to a `GdkPixbuf`. +//! +//! This is the implementation of the `rsvg_pixbuf_*` family of functions. + +use std::path::PathBuf; +use std::ptr; + +use gdk_pixbuf::{Colorspace, Pixbuf}; +use glib::translate::*; + +use super::dpi::Dpi; +use super::handle::{checked_i32, set_gerror}; +use super::sizing::LegacySize; + +use crate::api::{CairoRenderer, Loader}; +use crate::error::RenderingError; +use crate::session::Session; +use crate::surface_utils::shared_surface::{SharedImageSurface, SurfaceType}; + +pub fn empty_pixbuf() -> Result { + // GdkPixbuf does not allow zero-sized pixbufs, but Cairo allows zero-sized + // surfaces. In this case, return a 1-pixel transparent pixbuf. + + let pixbuf = Pixbuf::new(Colorspace::Rgb, true, 8, 1, 1) + .ok_or_else(|| RenderingError::OutOfMemory(String::from("creating a Pixbuf")))?; + pixbuf.put_pixel(0, 0, 0, 0, 0, 0); + + Ok(pixbuf) +} + +pub fn pixbuf_from_surface(surface: &SharedImageSurface) -> Result { + surface + .to_pixbuf() + .ok_or_else(|| RenderingError::OutOfMemory(String::from("creating a Pixbuf"))) +} + +enum SizeKind { + Zoom, + WidthHeight, + WidthHeightMax, + ZoomMax, +} + +struct SizeMode { + kind: SizeKind, + x_zoom: f64, + y_zoom: f64, + width: i32, + height: i32, +} + +fn get_final_size(in_width: f64, in_height: f64, size_mode: &SizeMode) -> (f64, f64) { + if in_width == 0.0 || in_height == 0.0 { + return (0.0, 0.0); + } + + let mut out_width; + let mut out_height; + + match size_mode.kind { + SizeKind::Zoom => { + out_width = size_mode.x_zoom * in_width; + out_height = size_mode.y_zoom * in_height; + } + + SizeKind::ZoomMax => { + out_width = size_mode.x_zoom * in_width; + out_height = size_mode.y_zoom * in_height; + + if out_width > f64::from(size_mode.width) || out_height > f64::from(size_mode.height) { + let zoom_x = f64::from(size_mode.width) / out_width; + let zoom_y = f64::from(size_mode.height) / out_height; + let zoom = zoom_x.min(zoom_y); + + out_width *= zoom; + out_height *= zoom; + } + } + + SizeKind::WidthHeightMax => { + let zoom_x = f64::from(size_mode.width) / in_width; + let zoom_y = f64::from(size_mode.height) / in_height; + + let zoom = zoom_x.min(zoom_y); + + out_width = zoom * in_width; + out_height = zoom * in_height; + } + + SizeKind::WidthHeight => { + if size_mode.width != -1 { + out_width = f64::from(size_mode.width); + } else { + out_width = in_width; + } + + if size_mode.height != -1 { + out_height = f64::from(size_mode.height); + } else { + out_height = in_height; + } + } + } + + (out_width, out_height) +} + +pub fn render_to_pixbuf_at_size( + renderer: &CairoRenderer<'_>, + document_width: f64, + document_height: f64, + desired_width: f64, + desired_height: f64, +) -> Result { + if desired_width == 0.0 + || desired_height == 0.0 + || document_width == 0.0 + || document_height == 0.0 + { + return empty_pixbuf(); + } + + let surface = cairo::ImageSurface::create( + cairo::Format::ARgb32, + checked_i32(desired_width.ceil())?, + checked_i32(desired_height.ceil())?, + )?; + + { + let cr = cairo::Context::new(&surface)?; + cr.scale( + desired_width / document_width, + desired_height / document_height, + ); + + let viewport = cairo::Rectangle::new(0.0, 0.0, document_width, document_height); + + // We do it with a cr transform so we can scale non-proportionally. + renderer.render_document(&cr, &viewport)?; + } + + let shared_surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?; + + pixbuf_from_surface(&shared_surface) +} + +unsafe fn pixbuf_from_file_with_size_mode( + filename: *const libc::c_char, + size_mode: &SizeMode, + error: *mut *mut glib::ffi::GError, +) -> *mut gdk_pixbuf::ffi::GdkPixbuf { + let path = PathBuf::from_glib_none(filename); + + let session = Session::default(); + + let handle = match Loader::new_with_session(session.clone()).read_path(path) { + Ok(handle) => handle, + Err(e) => { + set_gerror(&session, error, 0, &format!("{e}")); + return ptr::null_mut(); + } + }; + + let dpi = Dpi::default(); + let renderer = CairoRenderer::new(&handle).with_dpi(dpi.x(), dpi.y()); + + let (document_width, document_height) = match renderer.legacy_document_size() { + Ok(dim) => dim, + Err(e) => { + set_gerror(&session, error, 0, &format!("{e}")); + return ptr::null_mut(); + } + }; + + let (desired_width, desired_height) = + get_final_size(document_width, document_height, size_mode); + + render_to_pixbuf_at_size( + &renderer, + document_width, + document_height, + desired_width, + desired_height, + ) + .map(|pixbuf| pixbuf.to_glib_full()) + .unwrap_or_else(|e| { + set_gerror(&session, error, 0, &format!("{e}")); + ptr::null_mut() + }) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_pixbuf_from_file( + filename: *const libc::c_char, + error: *mut *mut glib::ffi::GError, +) -> *mut gdk_pixbuf::ffi::GdkPixbuf { + rsvg_return_val_if_fail! { + rsvg_pixbuf_from_file => ptr::null_mut(); + + !filename.is_null(), + error.is_null() || (*error).is_null(), + } + + pixbuf_from_file_with_size_mode( + filename, + &SizeMode { + kind: SizeKind::WidthHeight, + x_zoom: 0.0, + y_zoom: 0.0, + width: -1, + height: -1, + }, + error, + ) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_size( + filename: *const libc::c_char, + width: libc::c_int, + height: libc::c_int, + error: *mut *mut glib::ffi::GError, +) -> *mut gdk_pixbuf::ffi::GdkPixbuf { + rsvg_return_val_if_fail! { + rsvg_pixbuf_from_file_at_size => ptr::null_mut(); + + !filename.is_null(), + (width >= 1 && height >= 1) || (width == -1 && height == -1), + error.is_null() || (*error).is_null(), + } + + pixbuf_from_file_with_size_mode( + filename, + &SizeMode { + kind: SizeKind::WidthHeight, + x_zoom: 0.0, + y_zoom: 0.0, + width, + height, + }, + error, + ) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_zoom( + filename: *const libc::c_char, + x_zoom: libc::c_double, + y_zoom: libc::c_double, + error: *mut *mut glib::ffi::GError, +) -> *mut gdk_pixbuf::ffi::GdkPixbuf { + rsvg_return_val_if_fail! { + rsvg_pixbuf_from_file_at_zoom => ptr::null_mut(); + + !filename.is_null(), + x_zoom > 0.0 && y_zoom > 0.0, + error.is_null() || (*error).is_null(), + } + + pixbuf_from_file_with_size_mode( + filename, + &SizeMode { + kind: SizeKind::Zoom, + x_zoom, + y_zoom, + width: 0, + height: 0, + }, + error, + ) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_zoom_with_max( + filename: *const libc::c_char, + x_zoom: libc::c_double, + y_zoom: libc::c_double, + max_width: libc::c_int, + max_height: libc::c_int, + error: *mut *mut glib::ffi::GError, +) -> *mut gdk_pixbuf::ffi::GdkPixbuf { + rsvg_return_val_if_fail! { + rsvg_pixbuf_from_file_at_zoom_with_max => ptr::null_mut(); + + !filename.is_null(), + x_zoom > 0.0 && y_zoom > 0.0, + max_width >= 1 && max_height >= 1, + error.is_null() || (*error).is_null(), + } + + pixbuf_from_file_with_size_mode( + filename, + &SizeMode { + kind: SizeKind::ZoomMax, + x_zoom, + y_zoom, + width: max_width, + height: max_height, + }, + error, + ) +} + +#[no_mangle] +pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_max_size( + filename: *const libc::c_char, + max_width: libc::c_int, + max_height: libc::c_int, + error: *mut *mut glib::ffi::GError, +) -> *mut gdk_pixbuf::ffi::GdkPixbuf { + rsvg_return_val_if_fail! { + rsvg_pixbuf_from_file_at_max_size => ptr::null_mut(); + + !filename.is_null(), + max_width >= 1 && max_height >= 1, + error.is_null() || (*error).is_null(), + } + + pixbuf_from_file_with_size_mode( + filename, + &SizeMode { + kind: SizeKind::WidthHeightMax, + x_zoom: 0.0, + y_zoom: 0.0, + width: max_width, + height: max_height, + }, + error, + ) +} diff --git a/librsvg-c/src/c_api/sizing.rs b/librsvg-c/src/c_api/sizing.rs new file mode 100644 index 00000000..e0c85bd1 --- /dev/null +++ b/librsvg-c/src/c_api/sizing.rs @@ -0,0 +1,116 @@ +//! Compute an SVG document's size with the legacy logic. +//! +//! See the documentation for [`LegacySize`]. The legacy C API functions like +//! `rsvg_handle_render_cairo()` do not take a viewport argument: they do not know how big +//! the caller would like to render the document; instead they compute a "natural size" +//! for the document based on its `width`/`height`/`viewBox` and some heuristics for when +//! they are missing. +//! +//! The new style C functions like `rsvg_handle_render_document()` actually take a +//! viewport, which indicates how big the result should be. This matches the expectations +//! of the web platform, which assumes that all embedded content goes into a viewport. + +use float_cmp::approx_eq; + +use crate::api::{CairoRenderer, IntrinsicDimensions, RenderingError}; +use crate::dpi::Dpi; +use crate::handle::Handle; +use crate::length::*; + +use super::handle::CairoRectangleExt; + +/// Extension methods to compute the SVG's size suitable for the legacy C API. +/// +/// The legacy C API can compute an SVG document's size from the +/// `width`, `height`, and `viewBox` attributes of the toplevel `` +/// element. If these are not available, then the size must be computed +/// by actually measuring the geometries of elements in the document. +/// +/// See for terminology and logic. +pub trait LegacySize { + fn legacy_document_size(&self) -> Result<(f64, f64), RenderingError> { + let (ink_r, _) = self.legacy_layer_geometry(None)?; + Ok((ink_r.width(), ink_r.height())) + } + + fn legacy_layer_geometry( + &self, + id: Option<&str>, + ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError>; +} + +impl<'a> LegacySize for CairoRenderer<'a> { + fn legacy_layer_geometry( + &self, + id: Option<&str>, + ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> { + match id { + Some(id) => Ok(self.geometry_for_layer(Some(id), &unit_rectangle())?), + + None => { + let size_from_intrinsic_dimensions = + self.intrinsic_size_in_pixels().or_else(|| { + size_in_pixels_from_percentage_width_and_height( + &self.handle.handle, + &self.intrinsic_dimensions(), + self.dpi, + ) + }); + + if let Some((w, h)) = size_from_intrinsic_dimensions { + // We have a size directly computed from the attributes + let rect = cairo::Rectangle::from_size(w, h); + Ok((rect, rect)) + } else { + self.geometry_for_layer(None, &unit_rectangle()) + } + } + } + } +} + +fn unit_rectangle() -> cairo::Rectangle { + cairo::Rectangle::from_size(1.0, 1.0) +} + +/// If the width and height are in percentage units, computes a size equal to the +/// `viewBox`'s aspect ratio if it exists, or else returns None. +/// +/// For example, a `viewBox="0 0 100 200"` will yield `Some(100.0, 200.0)`. +/// +/// Note that this only checks that the width and height are in percentage units, but +/// it actually ignores their values. This is because at the point this function is +/// called, there is no viewport to embed the SVG document in, so those percentage +/// units cannot be resolved against anything in particular. The idea is to return +/// some dimensions with the correct aspect ratio. +fn size_in_pixels_from_percentage_width_and_height( + handle: &Handle, + dim: &IntrinsicDimensions, + dpi: Dpi, +) -> Option<(f64, f64)> { + let IntrinsicDimensions { + width, + height, + vbox, + } = *dim; + + use LengthUnit::*; + + // Unwrap or return None if we don't know the aspect ratio -> Let the caller handle it. + let vbox = vbox?; + + let (w, h) = handle.width_height_to_user(dpi); + + // Avoid division by zero below. If the viewBox is zero-sized, there's + // not much we can do. + if approx_eq!(f64, vbox.width(), 0.0) || approx_eq!(f64, vbox.height(), 0.0) { + return Some((0.0, 0.0)); + } + + match (width.unit, height.unit) { + (Percent, Percent) => Some((vbox.width(), vbox.height())), + (_, Percent) => Some((w, w * vbox.height() / vbox.width())), + (Percent, _) => Some((h * vbox.width() / vbox.height(), h)), + (_, _) => unreachable!("should have been called with percentage units"), + } +} diff --git a/librsvg-c/src/lib.rs b/librsvg-c/src/lib.rs new file mode 100644 index 00000000..ec407516 --- /dev/null +++ b/librsvg-c/src/lib.rs @@ -0,0 +1 @@ +pub use c_api; diff --git a/rsvg/src/c_api/dpi.rs b/rsvg/src/c_api/dpi.rs deleted file mode 100644 index b2e15eef..00000000 --- a/rsvg/src/c_api/dpi.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Legacy C API for setting a default DPI (dots per inch = DPI). -//! -//! There are two deprecated functions, `rsvg_set_default_dpi` and -//! `rsvg_set_default_dpi_x_y`, which set global values for the default DPI to be used -//! with `RsvgHandle`. In turn, `RsvgHandle` assumes that when its own DPI value is set -//! to `0.0` (which is in fact its default), it will fall back to the global DPI. -//! -//! This is clearly not thread-safe, but it is the legacy behavior. -//! -//! This module encapsulates that behavior so that the `rsvg_internals` crate -//! can always have immutable DPI values as intended. - -// This is configurable at runtime -const DEFAULT_DPI_X: f64 = 90.0; -const DEFAULT_DPI_Y: f64 = 90.0; - -static mut DPI_X: f64 = DEFAULT_DPI_X; -static mut DPI_Y: f64 = DEFAULT_DPI_Y; - -#[derive(Debug, Copy, Clone, Default)] -pub(crate) struct Dpi { - x: f64, - y: f64, -} - -impl Dpi { - pub(crate) fn new(x: f64, y: f64) -> Dpi { - Dpi { x, y } - } - - pub(crate) fn x(&self) -> f64 { - if self.x <= 0.0 { - unsafe { DPI_X } - } else { - self.x - } - } - - pub(crate) fn y(&self) -> f64 { - if self.y <= 0.0 { - unsafe { DPI_Y } - } else { - self.y - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_set_default_dpi_x_y(dpi_x: libc::c_double, dpi_y: libc::c_double) { - if dpi_x <= 0.0 { - DPI_X = DEFAULT_DPI_X; - } else { - DPI_X = dpi_x; - } - - if dpi_y <= 0.0 { - DPI_Y = DEFAULT_DPI_Y; - } else { - DPI_Y = dpi_y; - } -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_set_default_dpi(dpi: libc::c_double) { - rsvg_set_default_dpi_x_y(dpi, dpi); -} diff --git a/rsvg/src/c_api/handle.rs b/rsvg/src/c_api/handle.rs deleted file mode 100644 index 1698cc17..00000000 --- a/rsvg/src/c_api/handle.rs +++ /dev/null @@ -1,2144 +0,0 @@ -//! Main API for `RsvgHandle`. -//! -//! The C API of librsvg revolves around an `RsvgHandle` GObject class, which is -//! implemented as follows: -//! -//! * [`RsvgHandle`] and [`RsvgHandleClass`] are derivatives of `GObject` and -//! `GObjectClass`. These are coded explicitly, instead of using -//! [`glib::subclass::prelude::InstanceStruct`] and -//! [`glib::subclass::prelude::ClassStruct`], as the structs need need to be kept -//! ABI-compatible with the traditional C API/ABI. -//! -//! * The actual data for a handle (e.g. the `RsvgHandle`'s private data, in GObject -//! parlance) is in [`CHandle`]. -//! -//! * Public C ABI functions are the `#[no_mangle]` functions with an `rsvg_` prefix. -//! -//! The C API is implemented in terms of the Rust API in `librsvg_crate`. In effect, -//! [`RsvgHandle`] is a rather convoluted builder or adapter pattern that translates all the -//! historical idiosyncrasies of the C API into the simple Rust API. - -use std::cell::{Cell, Ref, RefCell, RefMut}; -use std::ffi::{CStr, CString, OsStr}; -use std::fmt; -use std::path::PathBuf; -use std::ptr; -use std::slice; -use std::str; -use std::{f64, i32}; - -use gdk_pixbuf::Pixbuf; -use gio::prelude::*; -use glib::error::ErrorDomain; -use url::Url; - -use glib::subclass::prelude::*; -use glib::translate::*; -use glib::types::instance_of; -use glib::{ffi::gpointer, gobject_ffi}; -use glib::{Bytes, Cast, StaticType, ToValue}; - -use crate::api::{self, CairoRenderer, IntrinsicDimensions, Loader, LoadingError, SvgHandle}; - -use crate::{ - length::RsvgLength, - rsvg_log, - session::Session, - surface_utils::shared_surface::{SharedImageSurface, SurfaceType}, -}; - -use super::dpi::Dpi; -use super::messages::{rsvg_g_critical, rsvg_g_warning}; -use super::pixbuf_utils::{empty_pixbuf, pixbuf_from_surface}; -use super::sizing::LegacySize; - -// The C API exports global variables that contain the library's version number; -// those get autogenerated from `build.rs` and placed in this `version.rs` file. -include!(concat!(env!("OUT_DIR"), "/version.rs")); - -// This is basically the same as api::RenderingError but with extra cases for -// the peculiarities of the C API. -enum RenderingError { - RenderingError(api::RenderingError), - - // The RsvgHandle is created, but hasn't been loaded yet. - HandleIsNotLoaded, -} - -impl> From for RenderingError { - fn from(e: T) -> RenderingError { - RenderingError::RenderingError(e.into()) - } -} - -impl fmt::Display for RenderingError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - RenderingError::RenderingError(ref e) => e.fmt(f), - RenderingError::HandleIsNotLoaded => write!(f, "SVG data is not loaded into handle"), - } - } -} - -/// Rust version of the `RsvgHandleFlags` enum in C. -#[glib::flags(name = "RsvgHandleFlags")] -pub enum HandleFlags { - #[flags_value(name = "RSVG_HANDLE_FLAGS_NONE", nick = "flags-none")] - NONE = 0, - - #[flags_value(name = "RSVG_HANDLE_FLAG_UNLIMITED", nick = "flag-unlimited")] - UNLIMITED = 1 << 0, - - #[flags_value( - name = "RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA", - nick = "flag-keep-image-data" - )] - KEEP_IMAGE_DATA = 1 << 1, -} - -/// Type alias used to pass flags in the C API functions. -pub type RsvgHandleFlags = u32; - -/// Internal representation of the loading flags, without bitflags. -#[derive(Default, Copy, Clone)] -struct LoadFlags { - unlimited_size: bool, - keep_image_data: bool, -} - -impl From for LoadFlags { - fn from(flags: HandleFlags) -> LoadFlags { - LoadFlags { - unlimited_size: flags.contains(HandleFlags::UNLIMITED), - keep_image_data: flags.contains(HandleFlags::KEEP_IMAGE_DATA), - } - } -} - -impl From for HandleFlags { - fn from(lflags: LoadFlags) -> HandleFlags { - let mut hflags = HandleFlags::empty(); - - if lflags.unlimited_size { - hflags.insert(HandleFlags::UNLIMITED); - } - - if lflags.keep_image_data { - hflags.insert(HandleFlags::KEEP_IMAGE_DATA); - } - - hflags - } -} - -/// GObject class struct for RsvgHandle. -/// -/// This is not done through [`glib::subclass::prelude::ClassStruct`] because we need -/// to include the `_abi_padding` field for ABI compatibility with the C headers, and -/// `simple::ClassStruct` does not allow that. -#[repr(C)] -pub struct RsvgHandleClass { - // Keep this in sync with rsvg.h:RsvgHandleClass - parent: gobject_ffi::GObjectClass, - - _abi_padding: [gpointer; 15], -} - -unsafe impl ClassStruct for RsvgHandleClass { - type Type = imp::CHandle; -} - -/// GObject instance struct for RsvgHandle. -/// -/// This is not done through [`glib::subclass::prelude::InstanceStruct`] because we need -/// to include the `_abi_padding` field for ABI compatibility with the C headers, and -/// `simple::InstanceStruct` does not allow that. -#[repr(C)] -pub struct RsvgHandle { - // Keep this in sync with rsvg.h:RsvgHandle - parent: gobject_ffi::GObject, - - _abi_padding: [gpointer; 16], -} - -unsafe impl InstanceStruct for RsvgHandle { - type Type = imp::CHandle; -} - -/// State machine for `RsvgHandle`. -/// -/// When an `RsvgHandled` is created it is empty / not loaded yet, and it does not know -/// whether the caller will feed it data gradually with the legacy `write()/close()` API, -/// or whether it will be given a `GInputStream` to read in a blocking fashion. After the -/// handle is loaded (e.g. the SVG document is finished parsing), we make sure that no -/// further loading operations can be done. -#[allow(clippy::large_enum_variant)] -enum LoadState { - /// Just created the CHandle; nothing loaded yet. - Start, - - /// Being loaded using the legacy write()/close() API. - /// - /// We buffer all the data from `write()` calls until the time `close()` is called; - /// then we run the buffer through a decompressor in case this is an SVGZ file. - Loading { buffer: Vec }, - - /// Loading finished successfully; the document is in the `SvgHandle`. - ClosedOk { handle: SvgHandle }, - - /// Loaded unsuccessfully. - ClosedError, -} - -impl LoadState { - fn set_from_loading_result( - &mut self, - result: Result, - ) -> Result<(), LoadingError> { - match result { - Ok(handle) => { - *self = LoadState::ClosedOk { handle }; - Ok(()) - } - - Err(e) => { - *self = LoadState::ClosedError; - Err(e) - } - } - } -} - -impl Default for LoadState { - fn default() -> Self { - Self::Start - } -} - -/// Holds the base URL for loading a handle, and the C-accessible version of it -/// -/// There is a public API to query the base URL, and we need to -/// produce a CString with it. However, that API returns a borrowed -/// *const char, so we need to maintain a long-lived CString along with the -/// internal Url. -#[derive(Default)] -struct BaseUrl { - inner: Option, -} - -struct BaseUrlInner { - url: Url, - cstring: CString, -} - -impl BaseUrl { - fn set(&mut self, url: Url) { - let cstring = CString::new(url.as_str()).unwrap(); - - self.inner = Some(BaseUrlInner { url, cstring }); - } - - fn get(&self) -> Option<&Url> { - self.inner.as_ref().map(|b| &b.url) - } - - fn get_gfile(&self) -> Option { - self.get().map(|url| gio::File::for_uri(url.as_str())) - } - - fn get_ptr(&self) -> *const libc::c_char { - self.inner - .as_ref() - .map(|b| b.cstring.as_ptr()) - .unwrap_or_else(ptr::null) - } -} - -#[derive(Clone, Copy)] -#[repr(C)] -pub struct RsvgRectangle { - pub x: f64, - pub y: f64, - pub width: f64, - pub height: f64, -} - -impl From for RsvgRectangle { - fn from(r: cairo::Rectangle) -> RsvgRectangle { - RsvgRectangle { - x: r.x(), - y: r.y(), - width: r.width(), - height: r.height(), - } - } -} - -impl From for cairo::Rectangle { - fn from(r: RsvgRectangle) -> cairo::Rectangle { - cairo::Rectangle::new(r.x, r.y, r.width, r.height) - } -} - -mod imp { - use super::*; - use glib::{ParamSpec, ParamSpecDouble, ParamSpecFlags, ParamSpecInt, ParamSpecString}; - use once_cell::sync::Lazy; - - /// Contains all the interior mutability for a RsvgHandle to be called - /// from the C API. - #[derive(Default)] - pub struct CHandle { - pub(super) inner: RefCell, - pub(super) load_state: RefCell, - pub(super) session: Session, - } - - #[derive(Default)] - pub(super) struct CHandleInner { - pub(super) dpi: Dpi, - pub(super) load_flags: LoadFlags, - pub(super) base_url: BaseUrl, - pub(super) size_callback: SizeCallback, - pub(super) is_testing: bool, - } - - #[glib::object_subclass] - impl ObjectSubclass for CHandle { - const NAME: &'static str = "RsvgHandle"; - - type Type = super::CHandle; - - type Instance = RsvgHandle; - type Class = RsvgHandleClass; - } - - impl ObjectImpl for CHandle { - fn properties() -> &'static [ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ - ParamSpecFlags::builder::("flags") - .construct_only() - .build(), - ParamSpecDouble::builder("dpi-x").construct().build(), - ParamSpecDouble::builder("dpi-y").construct().build(), - ParamSpecString::builder("base-uri").construct().build(), - ParamSpecInt::builder("width").read_only().build(), - ParamSpecInt::builder("height").read_only().build(), - ParamSpecDouble::builder("em").read_only().build(), - ParamSpecDouble::builder("ex").read_only().build(), - ParamSpecString::builder("title").read_only().build(), - ParamSpecString::builder("desc").read_only().build(), - ParamSpecString::builder("metadata").read_only().build(), - ] - }); - PROPERTIES.as_ref() - } - - fn set_property(&self, id: usize, value: &glib::Value, pspec: &ParamSpec) { - let obj = self.obj(); - match pspec.name() { - "flags" => { - let v: HandleFlags = value.get().expect("flags value has incorrect type"); - obj.set_flags(v); - } - - "dpi-x" => { - let dpi_x: f64 = value.get().expect("dpi-x value has incorrect type"); - obj.set_dpi_x(dpi_x); - } - - "dpi-y" => { - let dpi_y: f64 = value.get().expect("dpi-y value has incorrect type"); - obj.set_dpi_y(dpi_y); - } - - "base-uri" => { - let v: Option = value.get().expect("base-uri value has incorrect type"); - - // rsvg_handle_set_base_uri() expects non-NULL URI strings, - // but the "base-uri" property can be set to NULL due to a missing - // construct-time property. - - if let Some(s) = v { - obj.set_base_url(&s); - } - } - - _ => unreachable!("invalid property id {}", id), - } - } - - fn property(&self, id: usize, pspec: &ParamSpec) -> glib::Value { - let obj = self.obj(); - match pspec.name() { - "flags" => obj.get_flags().to_value(), - "dpi-x" => obj.get_dpi_x().to_value(), - "dpi-y" => obj.get_dpi_y().to_value(), - "base-uri" => obj.get_base_url().to_value(), - "width" => obj.get_dimensions_or_empty().width.to_value(), - "height" => obj.get_dimensions_or_empty().height.to_value(), - "em" => obj.get_dimensions_or_empty().em.to_value(), - "ex" => obj.get_dimensions_or_empty().ex.to_value(), - - // the following three are deprecated - "title" => None::.to_value(), - "desc" => None::.to_value(), - "metadata" => None::.to_value(), - - _ => unreachable!("invalid property id={} for RsvgHandle", id), - } - } - } -} - -glib::wrapper! { - // We don't use subclass:simple::InstanceStruct and ClassStruct - // because we need to maintain the respective _abi_padding of each - // of RsvgHandleClass and RsvgHandle. - pub struct CHandle(ObjectSubclass); -} - -// Keep in sync with tests/src/reference.rs -pub(crate) fn checked_i32(x: f64) -> Result { - cast::i32(x).map_err(|_| cairo::Error::InvalidSize) -} - -// Keep in sync with rsvg.h:RsvgPositionData -#[repr(C)] -pub struct RsvgPositionData { - pub x: libc::c_int, - pub y: libc::c_int, -} - -// Keep in sync with rsvg.h:RsvgDimensionData -#[repr(C)] -pub struct RsvgDimensionData { - pub width: libc::c_int, - pub height: libc::c_int, - pub em: f64, - pub ex: f64, -} - -impl RsvgDimensionData { - // This is not #[derive(Default)] to make it clear that it - // shouldn't be the default value for anything; it is actually a - // special case we use to indicate an error to the public API. - pub fn empty() -> RsvgDimensionData { - RsvgDimensionData { - width: 0, - height: 0, - em: 0.0, - ex: 0.0, - } - } -} - -// Keep in sync with rsvg.h:RsvgSizeFunc -pub type RsvgSizeFunc = Option< - unsafe extern "C" fn( - inout_width: *mut libc::c_int, - inout_height: *mut libc::c_int, - user_data: gpointer, - ), ->; - -struct SizeCallback { - size_func: RsvgSizeFunc, - user_data: gpointer, - destroy_notify: glib::ffi::GDestroyNotify, - in_loop: Cell, -} - -impl SizeCallback { - fn new( - size_func: RsvgSizeFunc, - user_data: gpointer, - destroy_notify: glib::ffi::GDestroyNotify, - ) -> Self { - SizeCallback { - size_func, - user_data, - destroy_notify, - in_loop: Cell::new(false), - } - } - - fn call(&self, width: libc::c_int, height: libc::c_int) -> (libc::c_int, libc::c_int) { - unsafe { - let mut w = width; - let mut h = height; - - if let Some(ref f) = self.size_func { - f(&mut w, &mut h, self.user_data); - }; - - (w, h) - } - } - - fn start_loop(&self) { - assert!(!self.in_loop.get()); - self.in_loop.set(true); - } - - fn end_loop(&self) { - assert!(self.in_loop.get()); - self.in_loop.set(false); - } - - fn get_in_loop(&self) -> bool { - self.in_loop.get() - } -} - -impl Default for SizeCallback { - fn default() -> SizeCallback { - SizeCallback { - size_func: None, - user_data: ptr::null_mut(), - destroy_notify: None, - in_loop: Cell::new(false), - } - } -} - -impl Drop for SizeCallback { - fn drop(&mut self) { - unsafe { - if let Some(ref f) = self.destroy_notify { - f(self.user_data); - }; - } - } -} -pub trait CairoRectangleExt { - fn from_size(width: f64, height: f64) -> Self; -} - -impl CairoRectangleExt for cairo::Rectangle { - fn from_size(width: f64, height: f64) -> Self { - Self::new(0.0, 0.0, width, height) - } -} - -impl CHandle { - fn set_base_url(&self, url: &str) { - let imp = self.imp(); - let session = &imp.session; - let state = imp.load_state.borrow(); - - match *state { - LoadState::Start => (), - _ => { - rsvg_g_critical( - "Please set the base file or URI before loading any data into RsvgHandle", - ); - return; - } - } - - match Url::parse(url) { - Ok(u) => { - rsvg_log!(session, "setting base_uri to \"{}\"", u.as_str()); - let mut inner = imp.inner.borrow_mut(); - inner.base_url.set(u); - } - - Err(e) => { - rsvg_log!( - session, - "not setting base_uri to \"{}\" since it is invalid: {}", - url, - e - ); - } - } - } - - fn set_base_gfile(&self, file: &gio::File) { - self.set_base_url(&file.uri()); - } - - fn get_base_url(&self) -> Option { - let inner = self.imp().inner.borrow(); - inner.base_url.get().map(|url| url.as_str().to_string()) - } - - fn get_base_url_as_ptr(&self) -> *const libc::c_char { - let inner = self.imp().inner.borrow(); - inner.base_url.get_ptr() - } - - fn set_dpi_x(&self, dpi_x: f64) { - let mut inner = self.imp().inner.borrow_mut(); - let dpi = inner.dpi; - inner.dpi = Dpi::new(dpi_x, dpi.y()); - } - - fn set_dpi_y(&self, dpi_y: f64) { - let mut inner = self.imp().inner.borrow_mut(); - let dpi = inner.dpi; - inner.dpi = Dpi::new(dpi.x(), dpi_y); - } - - fn get_dpi_x(&self) -> f64 { - let inner = self.imp().inner.borrow(); - inner.dpi.x() - } - - fn get_dpi_y(&self) -> f64 { - let inner = self.imp().inner.borrow(); - inner.dpi.y() - } - - fn set_flags(&self, flags: HandleFlags) { - let mut inner = self.imp().inner.borrow_mut(); - inner.load_flags = LoadFlags::from(flags); - } - - fn get_flags(&self) -> HandleFlags { - let inner = self.imp().inner.borrow(); - HandleFlags::from(inner.load_flags) - } - - fn set_size_callback( - &self, - size_func: RsvgSizeFunc, - user_data: gpointer, - destroy_notify: glib::ffi::GDestroyNotify, - ) { - let mut inner = self.imp().inner.borrow_mut(); - inner.size_callback = SizeCallback::new(size_func, user_data, destroy_notify); - } - - fn write(&self, buf: &[u8]) { - let mut state = self.imp().load_state.borrow_mut(); - - match *state { - LoadState::Start => { - *state = LoadState::Loading { - buffer: Vec::from(buf), - } - } - - LoadState::Loading { ref mut buffer } => { - buffer.extend_from_slice(buf); - } - - _ => { - rsvg_g_critical("Handle must not be closed in order to write to it"); - } - } - } - - fn close(&self) -> Result<(), LoadingError> { - let imp = self.imp(); - - let inner = imp.inner.borrow(); - let mut state = imp.load_state.borrow_mut(); - - match *state { - LoadState::Start => { - *state = LoadState::ClosedError; - Err(LoadingError::XmlParseError(String::from( - "caller did not write any data", - ))) - } - - LoadState::Loading { ref buffer } => { - let bytes = Bytes::from(buffer); - let stream = gio::MemoryInputStream::from_bytes(&bytes); - - let base_file = inner.base_url.get_gfile(); - self.read_stream(state, &stream.upcast(), base_file.as_ref(), None) - } - - // Closing is idempotent - LoadState::ClosedOk { .. } => Ok(()), - LoadState::ClosedError => Ok(()), - } - } - - fn read_stream_sync( - &self, - stream: &gio::InputStream, - cancellable: Option<&gio::Cancellable>, - ) -> Result<(), LoadingError> { - let imp = self.imp(); - - let state = imp.load_state.borrow_mut(); - let inner = imp.inner.borrow(); - - match *state { - LoadState::Start => { - let base_file = inner.base_url.get_gfile(); - self.read_stream(state, stream, base_file.as_ref(), cancellable) - } - - LoadState::Loading { .. } | LoadState::ClosedOk { .. } | LoadState::ClosedError => { - rsvg_g_critical( - "handle must not be already loaded in order to call \ - rsvg_handle_read_stream_sync()", - ); - Err(LoadingError::Other(String::from("API ordering"))) - } - } - } - - fn read_stream( - &self, - mut load_state: RefMut<'_, LoadState>, - stream: &gio::InputStream, - base_file: Option<&gio::File>, - cancellable: Option<&gio::Cancellable>, - ) -> Result<(), LoadingError> { - let loader = self.make_loader(); - - load_state.set_from_loading_result(loader.read_stream(stream, base_file, cancellable)) - } - - fn get_handle_ref(&self) -> Result, RenderingError> { - let state = self.imp().load_state.borrow(); - - match *state { - LoadState::Start => { - rsvg_g_critical("Handle has not been loaded"); - Err(RenderingError::HandleIsNotLoaded) - } - - LoadState::Loading { .. } => { - rsvg_g_critical("Handle is still loading; call rsvg_handle_close() first"); - Err(RenderingError::HandleIsNotLoaded) - } - - LoadState::ClosedError => { - rsvg_g_critical( - "Handle could not read or parse the SVG; did you check for errors during the \ - loading stage?", - ); - Err(RenderingError::HandleIsNotLoaded) - } - - LoadState::ClosedOk { .. } => Ok(Ref::map(state, |s| match *s { - LoadState::ClosedOk { ref handle } => handle, - _ => unreachable!(), - })), - } - } - - fn make_loader(&self) -> Loader { - let imp = self.imp(); - let inner = imp.inner.borrow(); - let session = imp.session.clone(); - - Loader::new_with_session(session) - .with_unlimited_size(inner.load_flags.unlimited_size) - .keep_image_data(inner.load_flags.keep_image_data) - } - - fn has_sub(&self, id: &str) -> Result { - let handle = self.get_handle_ref()?; - Ok(handle.has_element_with_id(id)?) - } - - fn get_dimensions_or_empty(&self) -> RsvgDimensionData { - self.get_dimensions_sub(None) - .unwrap_or_else(|_| RsvgDimensionData::empty()) - } - - fn get_dimensions_sub(&self, id: Option<&str>) -> Result { - let inner = self.imp().inner.borrow(); - - // This function is probably called from the cairo_render functions, - // or is being erroneously called within the size_func. - // To prevent an infinite loop we are saving the state, and - // returning a meaningless size. - if inner.size_callback.get_in_loop() { - return Ok(RsvgDimensionData { - width: 1, - height: 1, - em: 1.0, - ex: 1.0, - }); - } - - inner.size_callback.start_loop(); - - let res = self - .get_geometry_sub(id) - .and_then(|(ink_r, _)| { - // Keep these in sync with tests/src/reference.rs - let width = checked_i32(ink_r.width().round())?; - let height = checked_i32(ink_r.height().round())?; - - Ok((ink_r, width, height)) - }) - .map(|(ink_r, width, height)| { - let (w, h) = inner.size_callback.call(width, height); - - RsvgDimensionData { - width: w, - height: h, - em: ink_r.width(), - ex: ink_r.height(), - } - }); - - inner.size_callback.end_loop(); - - res - } - - fn get_position_sub(&self, id: Option<&str>) -> Result { - let inner = self.imp().inner.borrow(); - - if id.is_none() { - return Ok(RsvgPositionData { x: 0, y: 0 }); - } - - self.get_geometry_sub(id) - .and_then(|(ink_r, _)| { - let width = checked_i32(ink_r.width().round())?; - let height = checked_i32(ink_r.height().round())?; - - Ok((ink_r, width, height)) - }) - .and_then(|(ink_r, width, height)| { - inner.size_callback.call(width, height); - - Ok(RsvgPositionData { - x: checked_i32(ink_r.x())?, - y: checked_i32(ink_r.y())?, - }) - }) - } - - fn make_renderer<'a>(&self, handle_ref: &'a Ref<'_, SvgHandle>) -> CairoRenderer<'a> { - let inner = self.imp().inner.borrow(); - - CairoRenderer::new(handle_ref) - .with_dpi(inner.dpi.x(), inner.dpi.y()) - .test_mode(inner.is_testing) - } - - fn get_geometry_sub( - &self, - id: Option<&str>, - ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> { - let handle = self.get_handle_ref()?; - let renderer = self.make_renderer(&handle); - - Ok(renderer.legacy_layer_geometry(id)?) - } - - fn set_stylesheet(&self, css: &str) -> Result<(), LoadingError> { - match *self.imp().load_state.borrow_mut() { - LoadState::ClosedOk { ref mut handle } => handle.set_stylesheet(css), - - _ => { - rsvg_g_critical( - "handle must already be loaded in order to call \ - rsvg_handle_set_stylesheet()", - ); - Err(LoadingError::Other(String::from("API ordering"))) - } - } - } - - fn render_cairo_sub( - &self, - cr: *mut cairo::ffi::cairo_t, - id: Option<&str>, - ) -> Result<(), RenderingError> { - let dimensions = self.get_dimensions_sub(None)?; - if dimensions.width == 0 || dimensions.height == 0 { - // nothing to render - return Ok(()); - } - - let viewport = cairo::Rectangle::new( - 0.0, - 0.0, - f64::from(dimensions.width), - f64::from(dimensions.height), - ); - - self.render_layer(cr, id, &viewport) - } - - fn get_pixbuf_sub(&self, id: Option<&str>) -> Result { - let dimensions = self.get_dimensions_sub(None)?; - - if dimensions.width == 0 || dimensions.height == 0 { - return Ok(empty_pixbuf()?); - } - - let surface = cairo::ImageSurface::create( - cairo::Format::ARgb32, - dimensions.width, - dimensions.height, - )?; - - { - let cr = cairo::Context::new(&surface)?; - let cr_raw = cr.to_raw_none(); - self.render_cairo_sub(cr_raw, id)?; - } - - let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?; - - Ok(pixbuf_from_surface(&surface)?) - } - - fn render_document( - &self, - cr: *mut cairo::ffi::cairo_t, - viewport: &cairo::Rectangle, - ) -> Result<(), RenderingError> { - let cr = check_cairo_context(cr)?; - - let handle = self.get_handle_ref()?; - - let renderer = self.make_renderer(&handle); - Ok(renderer.render_document(&cr, viewport)?) - } - - fn get_geometry_for_layer( - &self, - id: Option<&str>, - viewport: &cairo::Rectangle, - ) -> Result<(RsvgRectangle, RsvgRectangle), RenderingError> { - let handle = self.get_handle_ref()?; - let renderer = self.make_renderer(&handle); - - Ok(renderer - .geometry_for_layer(id, viewport) - .map(|(i, l)| (RsvgRectangle::from(i), RsvgRectangle::from(l)))?) - } - - fn render_layer( - &self, - cr: *mut cairo::ffi::cairo_t, - id: Option<&str>, - viewport: &cairo::Rectangle, - ) -> Result<(), RenderingError> { - let cr = check_cairo_context(cr)?; - - let handle = self.get_handle_ref()?; - - let renderer = self.make_renderer(&handle); - - Ok(renderer.render_layer(&cr, id, viewport)?) - } - - fn get_geometry_for_element( - &self, - id: Option<&str>, - ) -> Result<(RsvgRectangle, RsvgRectangle), RenderingError> { - let handle = self.get_handle_ref()?; - - let renderer = self.make_renderer(&handle); - - Ok(renderer - .geometry_for_element(id) - .map(|(i, l)| (RsvgRectangle::from(i), RsvgRectangle::from(l)))?) - } - - fn render_element( - &self, - cr: *mut cairo::ffi::cairo_t, - id: Option<&str>, - element_viewport: &cairo::Rectangle, - ) -> Result<(), RenderingError> { - let cr = check_cairo_context(cr)?; - - let handle = self.get_handle_ref()?; - - let renderer = self.make_renderer(&handle); - - Ok(renderer.render_element(&cr, id, element_viewport)?) - } - - fn get_intrinsic_dimensions(&self) -> Result { - let handle = self.get_handle_ref()?; - let renderer = self.make_renderer(&handle); - Ok(renderer.intrinsic_dimensions()) - } - - fn get_intrinsic_size_in_pixels(&self) -> Result, RenderingError> { - let handle = self.get_handle_ref()?; - let renderer = self.make_renderer(&handle); - Ok(renderer.intrinsic_size_in_pixels()) - } - - fn set_testing(&self, is_testing: bool) { - let mut inner = self.imp().inner.borrow_mut(); - inner.is_testing = is_testing; - } -} - -fn is_rsvg_handle(obj: *const RsvgHandle) -> bool { - unsafe { instance_of::(obj as *const _) } -} - -fn is_input_stream(obj: *mut gio::ffi::GInputStream) -> bool { - unsafe { instance_of::(obj as *const _) } -} - -fn is_gfile(obj: *const gio::ffi::GFile) -> bool { - unsafe { instance_of::(obj as *const _) } -} - -fn is_cancellable(obj: *mut gio::ffi::GCancellable) -> bool { - unsafe { instance_of::(obj as *const _) } -} - -fn get_rust_handle(handle: *const RsvgHandle) -> CHandle { - let handle = unsafe { &*handle }; - handle.imp().obj().to_owned() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_type() -> glib::ffi::GType { - CHandle::static_type().into_glib() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_error_get_type() -> glib::ffi::GType { - Error::static_type().into_glib() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_flags_get_type() -> glib::ffi::GType { - HandleFlags::static_type().into_glib() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_set_base_uri( - handle: *const RsvgHandle, - uri: *const libc::c_char, -) { - rsvg_return_if_fail! { - rsvg_handle_set_base_uri; - - is_rsvg_handle(handle), - !uri.is_null(), - } - - let rhandle = get_rust_handle(handle); - - assert!(!uri.is_null()); - let uri: String = from_glib_none(uri); - - rhandle.set_base_url(&uri); -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_set_base_gfile( - handle: *const RsvgHandle, - raw_gfile: *mut gio::ffi::GFile, -) { - rsvg_return_if_fail! { - rsvg_handle_set_base_gfile; - - is_rsvg_handle(handle), - is_gfile(raw_gfile), - } - - let rhandle = get_rust_handle(handle); - - assert!(!raw_gfile.is_null()); - - let file: gio::File = from_glib_none(raw_gfile); - - rhandle.set_base_gfile(&file); -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_base_uri( - handle: *const RsvgHandle, -) -> *const libc::c_char { - rsvg_return_val_if_fail! { - rsvg_handle_get_base_uri => ptr::null(); - - is_rsvg_handle(handle), - } - - let rhandle = get_rust_handle(handle); - - rhandle.get_base_url_as_ptr() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_set_dpi(handle: *const RsvgHandle, dpi: libc::c_double) { - rsvg_return_if_fail! { - rsvg_handle_set_dpi; - - is_rsvg_handle(handle), - } - - let rhandle = get_rust_handle(handle); - rhandle.set_dpi_x(dpi); - rhandle.set_dpi_y(dpi); -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_set_dpi_x_y( - handle: *const RsvgHandle, - dpi_x: libc::c_double, - dpi_y: libc::c_double, -) { - rsvg_return_if_fail! { - rsvg_handle_set_dpi_x_y; - - is_rsvg_handle(handle), - } - - let rhandle = get_rust_handle(handle); - rhandle.set_dpi_x(dpi_x); - rhandle.set_dpi_y(dpi_y); -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_set_size_callback( - handle: *const RsvgHandle, - size_func: RsvgSizeFunc, - user_data: gpointer, - destroy_notify: glib::ffi::GDestroyNotify, -) { - rsvg_return_if_fail! { - rsvg_handle_set_size_callback; - - is_rsvg_handle(handle), - } - - let rhandle = get_rust_handle(handle); - - rhandle.set_size_callback(size_func, user_data, destroy_notify); -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_internal_set_testing( - handle: *const RsvgHandle, - testing: glib::ffi::gboolean, -) { - rsvg_return_if_fail! { - rsvg_handle_internal_set_testing; - - is_rsvg_handle(handle), - } - - let rhandle = get_rust_handle(handle); - - rhandle.set_testing(from_glib(testing)); -} - -trait IntoGError { - type GlibResult; - - fn into_gerror(self, session: &Session, error: *mut *mut glib::ffi::GError) - -> Self::GlibResult; -} - -impl IntoGError for Result<(), E> { - type GlibResult = glib::ffi::gboolean; - - fn into_gerror( - self, - session: &Session, - error: *mut *mut glib::ffi::GError, - ) -> Self::GlibResult { - match self { - Ok(()) => true.into_glib(), - - Err(e) => { - set_gerror(session, error, 0, &format!("{e}")); - false.into_glib() - } - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_read_stream_sync( - handle: *const RsvgHandle, - stream: *mut gio::ffi::GInputStream, - cancellable: *mut gio::ffi::GCancellable, - error: *mut *mut glib::ffi::GError, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_read_stream_sync => false.into_glib(); - - is_rsvg_handle(handle), - is_input_stream(stream), - cancellable.is_null() || is_cancellable(cancellable), - error.is_null() || (*error).is_null(), - } - - let rhandle = get_rust_handle(handle); - let session = rhandle.imp().session.clone(); - - let stream = gio::InputStream::from_glib_none(stream); - let cancellable: Option = from_glib_none(cancellable); - - rhandle - .read_stream_sync(&stream, cancellable.as_ref()) - .into_gerror(&session, error) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_write( - handle: *const RsvgHandle, - buf: *const u8, - count: usize, - error: *mut *mut glib::ffi::GError, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_write => false.into_glib(); - - is_rsvg_handle(handle), - error.is_null() || (*error).is_null(), - !buf.is_null() || count == 0, - } - - let rhandle = get_rust_handle(handle); - let buffer = slice::from_raw_parts(buf, count); - rhandle.write(buffer); - - true.into_glib() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_close( - handle: *const RsvgHandle, - error: *mut *mut glib::ffi::GError, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_close => false.into_glib(); - - is_rsvg_handle(handle), - error.is_null() || (*error).is_null(), - } - - let rhandle = get_rust_handle(handle); - let session = rhandle.imp().session.clone(); - - rhandle.close().into_gerror(&session, error) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_has_sub( - handle: *const RsvgHandle, - id: *const libc::c_char, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_has_sub => false.into_glib(); - - is_rsvg_handle(handle), - } - - let rhandle = get_rust_handle(handle); - - if id.is_null() { - return false.into_glib(); - } - - let id: String = from_glib_none(id); - rhandle.has_sub(&id).unwrap_or(false).into_glib() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_render_cairo( - handle: *const RsvgHandle, - cr: *mut cairo::ffi::cairo_t, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_render_cairo => false.into_glib(); - - is_rsvg_handle(handle), - !cr.is_null(), - } - - let rhandle = get_rust_handle(handle); - let session = rhandle.imp().session.clone(); - - rhandle - .render_cairo_sub(cr, None) - .into_gerror(&session, ptr::null_mut()) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_render_cairo_sub( - handle: *const RsvgHandle, - cr: *mut cairo::ffi::cairo_t, - id: *const libc::c_char, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_render_cairo_sub => false.into_glib(); - - is_rsvg_handle(handle), - !cr.is_null(), - } - - let rhandle = get_rust_handle(handle); - let session = rhandle.imp().session.clone(); - - let id: Option = from_glib_none(id); - - rhandle - .render_cairo_sub(cr, id.as_deref()) - .into_gerror(&session, ptr::null_mut()) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_pixbuf( - handle: *const RsvgHandle, -) -> *mut gdk_pixbuf::ffi::GdkPixbuf { - rsvg_return_val_if_fail! { - rsvg_handle_get_pixbuf => ptr::null_mut(); - - is_rsvg_handle(handle), - } - - let rhandle = get_rust_handle(handle); - - match rhandle.get_pixbuf_sub(None) { - Ok(pixbuf) => pixbuf.to_glib_full(), - Err(e) => { - let session = &rhandle.imp().session; - rsvg_log!(session, "could not render: {}", e); - ptr::null_mut() - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_pixbuf_sub( - handle: *const RsvgHandle, - id: *const libc::c_char, -) -> *mut gdk_pixbuf::ffi::GdkPixbuf { - rsvg_return_val_if_fail! { - rsvg_handle_get_pixbuf_sub => ptr::null_mut(); - - is_rsvg_handle(handle), - } - - let rhandle = get_rust_handle(handle); - let id: Option = from_glib_none(id); - - match rhandle.get_pixbuf_sub(id.as_deref()) { - Ok(pixbuf) => pixbuf.to_glib_full(), - Err(e) => { - let session = &rhandle.imp().session; - rsvg_log!(session, "could not render: {}", e); - ptr::null_mut() - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_dimensions( - handle: *const RsvgHandle, - dimension_data: *mut RsvgDimensionData, -) { - rsvg_handle_get_dimensions_sub(handle, dimension_data, ptr::null()); -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_dimensions_sub( - handle: *const RsvgHandle, - dimension_data: *mut RsvgDimensionData, - id: *const libc::c_char, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_get_dimensions_sub => false.into_glib(); - - is_rsvg_handle(handle), - !dimension_data.is_null(), - } - - let rhandle = get_rust_handle(handle); - - let id: Option = from_glib_none(id); - - match rhandle.get_dimensions_sub(id.as_deref()) { - Ok(dimensions) => { - *dimension_data = dimensions; - true.into_glib() - } - - Err(e) => { - let session = &rhandle.imp().session; - rsvg_log!(session, "could not get dimensions: {}", e); - *dimension_data = RsvgDimensionData::empty(); - false.into_glib() - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_position_sub( - handle: *const RsvgHandle, - position_data: *mut RsvgPositionData, - id: *const libc::c_char, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_get_position_sub => false.into_glib(); - - is_rsvg_handle(handle), - !position_data.is_null(), - } - - let rhandle = get_rust_handle(handle); - - let id: Option = from_glib_none(id); - - match rhandle.get_position_sub(id.as_deref()) { - Ok(position) => { - *position_data = position; - true.into_glib() - } - - Err(e) => { - let p = &mut *position_data; - - p.x = 0; - p.y = 0; - - let session = &rhandle.imp().session; - rsvg_log!(session, "could not get position: {}", e); - false.into_glib() - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_new() -> *const RsvgHandle { - let obj = glib::Object::new::(); - - obj.to_glib_full() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_new_with_flags(flags: RsvgHandleFlags) -> *const RsvgHandle { - let obj = glib::Object::builder::() - .property("flags", &HandleFlags::from_bits_truncate(flags)) - .build(); - - obj.to_glib_full() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_new_from_file( - filename: *const libc::c_char, - error: *mut *mut glib::ffi::GError, -) -> *const RsvgHandle { - rsvg_return_val_if_fail! { - rsvg_handle_new_from_file => ptr::null(); - - !filename.is_null(), - error.is_null() || (*error).is_null(), - } - - let file = match PathOrUrl::new(filename) { - Ok(p) => p.get_gfile(), - - Err(s) => { - // Here we don't have a handle created yet, so it's fine to create a session - // to log the error message. We'll need to change this when we start logging - // API calls, so that we can log the call to rsvg_handle_new_from_file() and - // then pass *that* session to rsvg_handle_new_from_gfile_sync() below. - let session = Session::default(); - set_gerror(&session, error, 0, &s); - return ptr::null_mut(); - } - }; - - rsvg_handle_new_from_gfile_sync(file.to_glib_none().0, 0, ptr::null_mut(), error) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_new_from_gfile_sync( - file: *mut gio::ffi::GFile, - flags: RsvgHandleFlags, - cancellable: *mut gio::ffi::GCancellable, - error: *mut *mut glib::ffi::GError, -) -> *const RsvgHandle { - rsvg_return_val_if_fail! { - rsvg_handle_new_from_gfile_sync => ptr::null(); - - is_gfile(file), - cancellable.is_null() || is_cancellable(cancellable), - error.is_null() || (*error).is_null(), - } - - let raw_handle = rsvg_handle_new_with_flags(flags); - - let rhandle = get_rust_handle(raw_handle); - let session = rhandle.imp().session.clone(); - - let file = gio::File::from_glib_none(file); - rhandle.set_base_gfile(&file); - - let cancellable: Option = from_glib_none(cancellable); - - let res = file - .read(cancellable.as_ref()) - .map_err(LoadingError::from) - .and_then(|stream| rhandle.read_stream_sync(&stream.upcast(), cancellable.as_ref())); - - match res { - Ok(()) => raw_handle, - - Err(e) => { - set_gerror(&session, error, 0, &format!("{e}")); - gobject_ffi::g_object_unref(raw_handle as *mut _); - ptr::null_mut() - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_new_from_stream_sync( - input_stream: *mut gio::ffi::GInputStream, - base_file: *mut gio::ffi::GFile, - flags: RsvgHandleFlags, - cancellable: *mut gio::ffi::GCancellable, - error: *mut *mut glib::ffi::GError, -) -> *const RsvgHandle { - rsvg_return_val_if_fail! { - rsvg_handle_new_from_stream_sync => ptr::null(); - - is_input_stream(input_stream), - base_file.is_null() || is_gfile(base_file), - cancellable.is_null() || is_cancellable(cancellable), - error.is_null() || (*error).is_null(), - } - - let raw_handle = rsvg_handle_new_with_flags(flags); - - let rhandle = get_rust_handle(raw_handle); - let session = rhandle.imp().session.clone(); - - let base_file: Option = from_glib_none(base_file); - if let Some(base_file) = base_file { - rhandle.set_base_gfile(&base_file); - } - - let stream: gio::InputStream = from_glib_none(input_stream); - let cancellable: Option = from_glib_none(cancellable); - - match rhandle.read_stream_sync(&stream, cancellable.as_ref()) { - Ok(()) => raw_handle, - - Err(e) => { - set_gerror(&session, error, 0, &format!("{e}")); - gobject_ffi::g_object_unref(raw_handle as *mut _); - ptr::null_mut() - } - } -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_new_from_data( - data: *const u8, - data_len: usize, - error: *mut *mut glib::ffi::GError, -) -> *const RsvgHandle { - rsvg_return_val_if_fail! { - rsvg_handle_new_from_data => ptr::null(); - - !data.is_null() || data_len == 0, - data_len <= std::isize::MAX as usize, - error.is_null() || (*error).is_null(), - } - - // We create the MemoryInputStream without the gtk-rs binding because of this: - // - // - The binding doesn't provide _new_from_data(). All of the binding's ways to - // put data into a MemoryInputStream involve copying the data buffer. - // - // - We can't use glib::Bytes from the binding either, for the same reason. - // - // - For now, we are using the other C-visible constructor, so we need a raw pointer to the - // stream, anyway. - - assert!(data_len <= std::isize::MAX as usize); - let data_len = data_len as isize; - - let raw_stream = gio::ffi::g_memory_input_stream_new_from_data(data as *mut u8, data_len, None); - - let ret = rsvg_handle_new_from_stream_sync( - raw_stream, - ptr::null_mut(), // base_file - 0, - ptr::null_mut(), // cancellable - error, - ); - - gobject_ffi::g_object_unref(raw_stream as *mut _); - ret -} - -unsafe fn set_out_param( - out_has_param: *mut glib::ffi::gboolean, - out_param: *mut T, - value: &Option, -) { - let has_value = if let Some(ref v) = *value { - if !out_param.is_null() { - *out_param = *v; - } - - true - } else { - false - }; - - if !out_has_param.is_null() { - *out_has_param = has_value.into_glib(); - } -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_free(handle: *mut RsvgHandle) { - gobject_ffi::g_object_unref(handle as *mut _); -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_set_stylesheet( - handle: *const RsvgHandle, - css: *const u8, - css_len: usize, - error: *mut *mut glib::ffi::GError, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_set_stylesheet => false.into_glib(); - - is_rsvg_handle(handle), - !css.is_null() || (css.is_null() && css_len == 0), - error.is_null() || (*error).is_null(), - } - - let rhandle = get_rust_handle(handle); - let session = rhandle.imp().session.clone(); - - let css = match (css, css_len) { - (p, 0) if p.is_null() => "", - (_, _) => { - let s = slice::from_raw_parts(css, css_len); - match str::from_utf8(s) { - Ok(s) => s, - Err(e) => { - set_gerror(&session, error, 0, &format!("CSS is not valid UTF-8: {e}")); - return false.into_glib(); - } - } - } - }; - - rhandle.set_stylesheet(css).into_gerror(&session, error) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_intrinsic_dimensions( - handle: *const RsvgHandle, - out_has_width: *mut glib::ffi::gboolean, - out_width: *mut RsvgLength, - out_has_height: *mut glib::ffi::gboolean, - out_height: *mut RsvgLength, - out_has_viewbox: *mut glib::ffi::gboolean, - out_viewbox: *mut RsvgRectangle, -) { - rsvg_return_if_fail! { - rsvg_handle_get_intrinsic_dimensions; - - is_rsvg_handle(handle), - } - - let rhandle = get_rust_handle(handle); - - let d = rhandle - .get_intrinsic_dimensions() - .unwrap_or_else(|_| panic!("API called out of order")); - - let w = d.width; - let h = d.height; - let r = d.vbox.map(RsvgRectangle::from); - - set_out_param(out_has_width, out_width, &Into::into(w)); - set_out_param(out_has_height, out_height, &Into::into(h)); - set_out_param(out_has_viewbox, out_viewbox, &r); -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_intrinsic_size_in_pixels( - handle: *const RsvgHandle, - out_width: *mut f64, - out_height: *mut f64, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_get_intrinsic_size_in_pixels => false.into_glib(); - - is_rsvg_handle(handle), - } - - let rhandle = get_rust_handle(handle); - - let dim = rhandle - .get_intrinsic_size_in_pixels() - .unwrap_or_else(|_| panic!("API called out of order")); - - let (w, h) = dim.unwrap_or((0.0, 0.0)); - - if !out_width.is_null() { - *out_width = w; - } - - if !out_height.is_null() { - *out_height = h; - } - - dim.is_some().into_glib() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_render_document( - handle: *const RsvgHandle, - cr: *mut cairo::ffi::cairo_t, - viewport: *const RsvgRectangle, - error: *mut *mut glib::ffi::GError, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_render_document => false.into_glib(); - - is_rsvg_handle(handle), - !cr.is_null(), - !viewport.is_null(), - error.is_null() || (*error).is_null(), - } - - let rhandle = get_rust_handle(handle); - let session = rhandle.imp().session.clone(); - - rhandle - .render_document(cr, &(*viewport).into()) - .into_gerror(&session, error) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_geometry_for_layer( - handle: *mut RsvgHandle, - id: *const libc::c_char, - viewport: *const RsvgRectangle, - out_ink_rect: *mut RsvgRectangle, - out_logical_rect: *mut RsvgRectangle, - error: *mut *mut glib::ffi::GError, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_get_geometry_for_layer => false.into_glib(); - - is_rsvg_handle(handle), - !viewport.is_null(), - error.is_null() || (*error).is_null(), - } - - let rhandle = get_rust_handle(handle); - let session = rhandle.imp().session.clone(); - - let id: Option = from_glib_none(id); - - rhandle - .get_geometry_for_layer(id.as_deref(), &(*viewport).into()) - .map(|(ink_rect, logical_rect)| { - if !out_ink_rect.is_null() { - *out_ink_rect = ink_rect; - } - - if !out_logical_rect.is_null() { - *out_logical_rect = logical_rect; - } - }) - .into_gerror(&session, error) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_render_layer( - handle: *const RsvgHandle, - cr: *mut cairo::ffi::cairo_t, - id: *const libc::c_char, - viewport: *const RsvgRectangle, - error: *mut *mut glib::ffi::GError, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_render_layer => false.into_glib(); - - is_rsvg_handle(handle), - !cr.is_null(), - !viewport.is_null(), - error.is_null() || (*error).is_null(), - } - - let rhandle = get_rust_handle(handle); - let session = rhandle.imp().session.clone(); - - let id: Option = from_glib_none(id); - - rhandle - .render_layer(cr, id.as_deref(), &(*viewport).into()) - .into_gerror(&session, error) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_geometry_for_element( - handle: *const RsvgHandle, - id: *const libc::c_char, - out_ink_rect: *mut RsvgRectangle, - out_logical_rect: *mut RsvgRectangle, - error: *mut *mut glib::ffi::GError, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_get_geometry_for_element => false.into_glib(); - - is_rsvg_handle(handle), - error.is_null() || (*error).is_null(), - } - - let rhandle = get_rust_handle(handle); - let session = rhandle.imp().session.clone(); - - let id: Option = from_glib_none(id); - - rhandle - .get_geometry_for_element(id.as_deref()) - .map(|(ink_rect, logical_rect)| { - if !out_ink_rect.is_null() { - *out_ink_rect = ink_rect; - } - - if !out_logical_rect.is_null() { - *out_logical_rect = logical_rect; - } - }) - .into_gerror(&session, error) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_render_element( - handle: *const RsvgHandle, - cr: *mut cairo::ffi::cairo_t, - id: *const libc::c_char, - element_viewport: *const RsvgRectangle, - error: *mut *mut glib::ffi::GError, -) -> glib::ffi::gboolean { - rsvg_return_val_if_fail! { - rsvg_handle_render_element => false.into_glib(); - - is_rsvg_handle(handle), - !cr.is_null(), - !element_viewport.is_null(), - error.is_null() || (*error).is_null(), - } - - let rhandle = get_rust_handle(handle); - let session = rhandle.imp().session.clone(); - - let id: Option = from_glib_none(id); - - rhandle - .render_element(cr, id.as_deref(), &(*element_viewport).into()) - .into_gerror(&session, error) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_desc(handle: *const RsvgHandle) -> *mut libc::c_char { - rsvg_return_val_if_fail! { - rsvg_handle_get_desc => ptr::null_mut(); - - is_rsvg_handle(handle), - } - - ptr::null_mut() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_metadata(handle: *const RsvgHandle) -> *mut libc::c_char { - rsvg_return_val_if_fail! { - rsvg_handle_get_metadata => ptr::null_mut(); - - is_rsvg_handle(handle), - } - - ptr::null_mut() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_handle_get_title(handle: *const RsvgHandle) -> *mut libc::c_char { - rsvg_return_val_if_fail! { - rsvg_handle_get_title => ptr::null_mut(); - - is_rsvg_handle(handle), - } - - ptr::null_mut() -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_init() {} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_term() {} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_cleanup() {} - -/// Detects whether a `*const libc::c_char` is a path or a URI -/// -/// `rsvg_handle_new_from_file()` takes a `filename` argument, and advertises -/// that it will detect either a file system path, or a proper URI. It will then use -/// `gio::File::for_path()` or `gio::File::for_uri()` as appropriate. -/// -/// This enum does the magic heuristics to figure this out. -/// -/// The `from_os_str` version is for using the same logic on rsvg-convert's command-line -/// arguments: we want `rsvg-convert http://example.com/foo.svg` to go to a URL, not to a -/// local file with that name. -#[derive(Clone, Debug)] -pub enum PathOrUrl { - Path(PathBuf), - Url(Url), -} - -impl PathOrUrl { - unsafe fn new(s: *const libc::c_char) -> Result { - let cstr = CStr::from_ptr(s); - - if cstr.to_bytes().is_empty() { - return Err("invalid empty filename".to_string()); - } - - Ok(cstr - .to_str() - .map_err(|_| ()) - .and_then(Self::try_from_str) - .unwrap_or_else(|_| PathOrUrl::Path(PathBuf::from_glib_none(s)))) - } - - fn try_from_str(s: &str) -> Result { - assert!(!s.is_empty()); - - Url::parse(s).map_err(|_| ()).and_then(|url| { - if url.origin().is_tuple() || url.scheme() == "file" { - Ok(PathOrUrl::Url(url)) - } else { - Ok(PathOrUrl::Path(url.to_file_path()?)) - } - }) - } - - pub fn from_os_str(osstr: &OsStr) -> Result { - if osstr.is_empty() { - return Err("invalid empty filename".to_string()); - } - - Ok(osstr - .to_str() - .ok_or(()) - .and_then(Self::try_from_str) - .unwrap_or_else(|_| PathOrUrl::Path(PathBuf::from(osstr.to_os_string())))) - } - - pub fn get_gfile(&self) -> gio::File { - match *self { - PathOrUrl::Path(ref p) => gio::File::for_path(p), - PathOrUrl::Url(ref u) => gio::File::for_uri(u.as_str()), - } - } -} - -impl fmt::Display for PathOrUrl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - PathOrUrl::Path(ref p) => p.display().fmt(f), - PathOrUrl::Url(ref u) => u.fmt(f), - } - } -} - -fn check_cairo_context(cr: *mut cairo::ffi::cairo_t) -> Result { - let status = unsafe { cairo::ffi::cairo_status(cr) }; - - if status == cairo::ffi::STATUS_SUCCESS { - Ok(unsafe { from_glib_none(cr) }) - } else { - let status: cairo::Error = status.into(); - - let msg = format!("cannot render on a cairo_t with a failure status (status={status:?})"); - - rsvg_g_warning(&msg); - - Err(RenderingError::from(status)) - } -} - -pub(crate) fn set_gerror( - session: &Session, - err: *mut *mut glib::ffi::GError, - code: u32, - msg: &str, -) { - unsafe { - // this is RSVG_ERROR_FAILED, the only error code available in RsvgError - assert!(code == 0); - - // Log this, in case the calling program passes a NULL GError, so we can at least - // diagnose things by asking for RSVG_LOG. - // - // See https://gitlab.gnome.org/GNOME/gtk/issues/2294 for an example of code that - // passed a NULL GError and so we had no easy way to see what was wrong. - rsvg_log!(session, "{}", msg); - - glib::ffi::g_set_error_literal( - err, - rsvg_error_quark(), - code as libc::c_int, - msg.to_glib_none().0, - ); - } -} - -#[derive(Debug, Eq, PartialEq, Clone, Copy, glib::Enum)] -#[repr(u32)] -#[enum_type(name = "RsvgError")] -enum Error { - #[enum_value(name = "RSVG_ERROR_FAILED", nick = "failed")] - // Keep in sync with rsvg.h:RsvgError - Failed = 0, -} - -/// Used as a generic error to translate to glib::Error -/// -/// This type implements `glib::error::ErrorDomain`, so it can be used -/// to obtain the error code while calling `glib::Error::new()`. Unfortunately -/// the public librsvg API does not have detailed error codes yet, so we use -/// this single value as the only possible error code to return. -#[derive(Copy, Clone)] -struct RsvgError; - -impl ErrorDomain for RsvgError { - fn domain() -> glib::Quark { - glib::Quark::from_str("rsvg-error-quark") - } - - fn code(self) -> i32 { - Error::Failed as i32 - } - - fn from(_code: i32) -> Option { - // We don't have enough information from glib error codes - Some(RsvgError) - } -} - -#[no_mangle] -pub extern "C" fn rsvg_error_quark() -> glib::ffi::GQuark { - RsvgError::domain().into_glib() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn path_or_url_unix() { - unsafe { - match PathOrUrl::new(rsvg_c_str!("/foo/bar")).unwrap() { - PathOrUrl::Path(_) => (), - _ => panic!("unix filename should be a PathOrUrl::Path"), - } - - match PathOrUrl::new(rsvg_c_str!("foo/bar")).unwrap() { - PathOrUrl::Path(_) => (), - _ => panic!("unix filename should be a PathOrUrl::Path"), - } - } - } - - #[test] - fn path_or_url_windows() { - unsafe { - match PathOrUrl::new(rsvg_c_str!("c:/foo/bar")).unwrap() { - PathOrUrl::Path(_) => (), - _ => panic!("windows filename should be a PathOrUrl::Path"), - } - - match PathOrUrl::new(rsvg_c_str!("C:/foo/bar")).unwrap() { - PathOrUrl::Path(_) => (), - _ => panic!("windows filename should be a PathOrUrl::Path"), - } - - match PathOrUrl::new(rsvg_c_str!("c:\\foo\\bar")).unwrap() { - PathOrUrl::Path(_) => (), - _ => panic!("windows filename should be a PathOrUrl::Path"), - } - - match PathOrUrl::new(rsvg_c_str!("C:\\foo\\bar")).unwrap() { - PathOrUrl::Path(_) => (), - _ => panic!("windows filename should be a PathOrUrl::Path"), - } - } - } - - #[test] - fn path_or_url_unix_url() { - unsafe { - match PathOrUrl::new(rsvg_c_str!("file:///foo/bar")).unwrap() { - PathOrUrl::Url(_) => (), - _ => panic!("file:// unix filename should be a PathOrUrl::Url"), - } - } - } - - #[test] - fn path_or_url_windows_url() { - unsafe { - match PathOrUrl::new(rsvg_c_str!("file://c:/foo/bar")).unwrap() { - PathOrUrl::Url(_) => (), - _ => panic!("file:// windows filename should be a PathOrUrl::Url"), - } - - match PathOrUrl::new(rsvg_c_str!("file://C:/foo/bar")).unwrap() { - PathOrUrl::Url(_) => (), - _ => panic!("file:// windows filename should be a PathOrUrl::Url"), - } - } - } - - #[test] - fn path_or_url_empty_str() { - unsafe { - assert!(PathOrUrl::new(rsvg_c_str!("")).is_err()); - } - - assert!(PathOrUrl::from_os_str(OsStr::new("")).is_err()); - } - - #[test] - fn base_url_works() { - let mut u = BaseUrl::default(); - - assert!(u.get().is_none()); - assert_eq!(u.get_ptr(), ptr::null()); - - u.set(Url::parse("file:///example.txt").unwrap()); - - assert_eq!(u.get().unwrap().as_str(), "file:///example.txt"); - - unsafe { - let p = u.get_ptr(); - let cstr = CStr::from_ptr(p); - assert_eq!(cstr.to_str().unwrap(), "file:///example.txt"); - } - } -} diff --git a/rsvg/src/c_api/messages.rs b/rsvg/src/c_api/messages.rs deleted file mode 100644 index 5b227cb8..00000000 --- a/rsvg/src/c_api/messages.rs +++ /dev/null @@ -1,158 +0,0 @@ -//! Logging functions, plus Rust versions of `g_return_if_fail()`. -//! -//! Glib's `g_return_if_fail()`, `g_warning()`, etc. are all C macros, so they cannot be -//! used from Rust. This module defines equivalent functions or macros with an `rsvg_` -//! prefix, to be clear that they should only be used from the implementation of the C API -//! and not from the main Rust code of the library. - -use glib::ffi::{g_log_structured_array, GLogField, G_LOG_LEVEL_CRITICAL, G_LOG_LEVEL_WARNING}; -use glib::translate::*; - -/* - G_LOG_LEVEL_CRITICAL = 1 << 3, - G_LOG_LEVEL_WARNING = 1 << 4, - -#define g_critical(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, \ - __FILE__, G_STRINGIFY (__LINE__), \ - G_STRFUNC, __VA_ARGS__) -#define g_warning(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, \ - __FILE__, G_STRINGIFY (__LINE__), \ - G_STRFUNC, __VA_ARGS__) - GLogField fields[] = - { - { "PRIORITY", log_level_to_priority (log_level), -1 }, - { "CODE_FILE", file, -1 }, - { "CODE_LINE", line, -1 }, - { "CODE_FUNC", func, -1 }, - /* Filled in later: */ - { "MESSAGE", NULL, -1 }, - /* If @log_domain is %NULL, we will not pass this field: */ - { "GLIB_DOMAIN", log_domain, -1 }, - }; - - g_log_structured_array (log_level, fields, n_fields); - */ - -/// Helper function for converting string literals to C char pointers. -#[macro_export] -macro_rules! rsvg_c_str { - ($txt:expr) => { - // SAFETY: it's important that the type we pass to `from_bytes_with_nul` is 'static, - // so that the storage behind the returned pointer doesn't get freed while it's still - // being used. We get that by only allowing string literals. - std::ffi::CStr::from_bytes_with_nul(concat!($txt, "\0").as_bytes()) - .unwrap() - .as_ptr() - }; -} - -/// Helper for `rsvg_g_warning` and `rsvg_g_critical` -/// -/// This simulates what in C would be a call to the g_warning() or g_critical() -/// macros, but with the underlying function g_log_structured_array(). -/// -/// If the implementation of g_warning() or g_critical() changes, we'll have -/// to change this function. -fn rsvg_g_log(level: glib::ffi::GLogLevelFlags, msg: &str) { - // stolen from gmessages.c:log_level_to_priority() - let priority = match level { - G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL => rsvg_c_str!("4"), - _ => unreachable!("please add another log level priority to rsvg_g_log()"), - }; - - let c_msg = msg.to_glib_none(); - let c_char_msg: *const libc::c_char = c_msg.0; - - // Glib's g_log_structured_standard() adds a few more fields for the source - // file, line number, etc., but those are not terribly useful without a stack - // trace. So, we'll omit them. - let fields = [ - GLogField { - key: rsvg_c_str!("PRIORITY"), - value: priority as *const _, - length: -1, - }, - GLogField { - key: rsvg_c_str!("MESSAGE"), - value: c_char_msg as *const _, - length: msg.len() as _, - }, - // This is the G_LOG_DOMAIN set from the Makefile - GLogField { - key: rsvg_c_str!("GLIB_DOMAIN"), - value: rsvg_c_str!("librsvg") as *const _, - length: -1, - }, - ]; - - unsafe { - g_log_structured_array(level, fields.as_ptr(), fields.len()); - } -} - -/// Replacement for `g_warning()`. -/// -/// Use this to signal an error condition in the following situations: -/// -/// * The C API does not have an adequate error code for the error in question (and cannot -/// be changed to have one, for ABI compatibility reasons). -/// -/// * Applications using the C API would be prone to ignoring an important error, -/// so it's best to have a warning on the console to at least have a hope of someone -/// noticing the error. -pub(crate) fn rsvg_g_warning(msg: &str) { - rsvg_g_log(glib::ffi::G_LOG_LEVEL_WARNING, msg); -} - -/// Replacement for `g_critical()`. -/// -/// Use this to signal a programming error from the caller of the C API, like passing -/// incorrect arguments or calling the API out of order. Rust code conventionally panics -/// in such situations, but C/Glib code does not, so it's best to "do nothing", print a -/// critical message, and return. Development versions of GNOME will crash the program -/// if this happens; release versions will ignore the error. -pub(crate) fn rsvg_g_critical(msg: &str) { - rsvg_g_log(glib::ffi::G_LOG_LEVEL_CRITICAL, msg); -} - -/// Replacement for `g_return_if_fail()`. -// Once Rust has a function! macro that gives us the current function name, we -// can remove the $func_name argument. -#[macro_export] -macro_rules! rsvg_return_if_fail { - { - $func_name:ident; - $($condition:expr,)+ - } => { - $( - if !$condition { - glib::ffi::g_return_if_fail_warning( - rsvg_c_str!("librsvg"), - rsvg_c_str!(stringify!($func_name)), - rsvg_c_str!(stringify!($condition)), - ); - return; - } - )+ - } -} - -/// Replacement for `g_return_val_if_fail()`. -#[macro_export] -macro_rules! rsvg_return_val_if_fail { - { - $func_name:ident => $retval:expr; - $($condition:expr,)+ - } => { - $( - if !$condition { - glib::ffi::g_return_if_fail_warning( - rsvg_c_str!("librsvg"), - rsvg_c_str!(stringify!($func_name)), - rsvg_c_str!(stringify!($condition)), - ); - return $retval; - } - )+ - } -} diff --git a/rsvg/src/c_api/mod.rs b/rsvg/src/c_api/mod.rs deleted file mode 100644 index febca033..00000000 --- a/rsvg/src/c_api/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! C API for librsvg, based on GObject. -//! -//! The main API is in the [`handle`] module. The other modules -//! have utility functions and the legacy [pixbuf-based API][pixbuf_utils]. - -#![allow(clippy::missing_safety_doc)] - -#[rustfmt::skip] -pub use handle::{ - rsvg_error_get_type, - rsvg_handle_close, - rsvg_handle_flags_get_type, - rsvg_handle_get_base_uri, - rsvg_handle_get_dimensions, - rsvg_handle_get_dimensions_sub, - rsvg_handle_get_geometry_for_element, - rsvg_handle_get_geometry_for_layer, - rsvg_handle_get_intrinsic_dimensions, - rsvg_handle_get_intrinsic_size_in_pixels, - rsvg_handle_get_pixbuf_sub, - rsvg_handle_get_position_sub, - rsvg_handle_has_sub, - rsvg_handle_internal_set_testing, - rsvg_handle_new_from_data, - rsvg_handle_new_from_file, - rsvg_handle_new_from_gfile_sync, - rsvg_handle_new_from_stream_sync, - rsvg_handle_new_with_flags, - rsvg_handle_read_stream_sync, - rsvg_handle_render_cairo_sub, - rsvg_handle_render_element, - rsvg_handle_render_document, - rsvg_handle_render_layer, - rsvg_handle_set_base_gfile, - rsvg_handle_set_base_uri, - rsvg_handle_set_dpi_x_y, - rsvg_handle_set_size_callback, - rsvg_handle_write, -}; - -pub use dpi::{rsvg_set_default_dpi, rsvg_set_default_dpi_x_y}; - -#[rustfmt::skip] -pub use pixbuf_utils::{ - rsvg_pixbuf_from_file, - rsvg_pixbuf_from_file_at_max_size, - rsvg_pixbuf_from_file_at_size, - rsvg_pixbuf_from_file_at_zoom, - rsvg_pixbuf_from_file_at_zoom_with_max, -}; - -#[macro_use] -mod messages; - -mod dpi; -pub mod handle; -pub mod pixbuf_utils; -pub mod sizing; diff --git a/rsvg/src/c_api/pixbuf_utils.rs b/rsvg/src/c_api/pixbuf_utils.rs deleted file mode 100644 index 710f2c4b..00000000 --- a/rsvg/src/c_api/pixbuf_utils.rs +++ /dev/null @@ -1,330 +0,0 @@ -//! Legacy C API for functions that render directly to a `GdkPixbuf`. -//! -//! This is the implementation of the `rsvg_pixbuf_*` family of functions. - -use std::path::PathBuf; -use std::ptr; - -use gdk_pixbuf::{Colorspace, Pixbuf}; -use glib::translate::*; - -use super::dpi::Dpi; -use super::handle::{checked_i32, set_gerror}; -use super::sizing::LegacySize; - -use crate::api::{CairoRenderer, Loader}; -use crate::error::RenderingError; -use crate::session::Session; -use crate::surface_utils::shared_surface::{SharedImageSurface, SurfaceType}; - -pub fn empty_pixbuf() -> Result { - // GdkPixbuf does not allow zero-sized pixbufs, but Cairo allows zero-sized - // surfaces. In this case, return a 1-pixel transparent pixbuf. - - let pixbuf = Pixbuf::new(Colorspace::Rgb, true, 8, 1, 1) - .ok_or_else(|| RenderingError::OutOfMemory(String::from("creating a Pixbuf")))?; - pixbuf.put_pixel(0, 0, 0, 0, 0, 0); - - Ok(pixbuf) -} - -pub fn pixbuf_from_surface(surface: &SharedImageSurface) -> Result { - surface - .to_pixbuf() - .ok_or_else(|| RenderingError::OutOfMemory(String::from("creating a Pixbuf"))) -} - -enum SizeKind { - Zoom, - WidthHeight, - WidthHeightMax, - ZoomMax, -} - -struct SizeMode { - kind: SizeKind, - x_zoom: f64, - y_zoom: f64, - width: i32, - height: i32, -} - -fn get_final_size(in_width: f64, in_height: f64, size_mode: &SizeMode) -> (f64, f64) { - if in_width == 0.0 || in_height == 0.0 { - return (0.0, 0.0); - } - - let mut out_width; - let mut out_height; - - match size_mode.kind { - SizeKind::Zoom => { - out_width = size_mode.x_zoom * in_width; - out_height = size_mode.y_zoom * in_height; - } - - SizeKind::ZoomMax => { - out_width = size_mode.x_zoom * in_width; - out_height = size_mode.y_zoom * in_height; - - if out_width > f64::from(size_mode.width) || out_height > f64::from(size_mode.height) { - let zoom_x = f64::from(size_mode.width) / out_width; - let zoom_y = f64::from(size_mode.height) / out_height; - let zoom = zoom_x.min(zoom_y); - - out_width *= zoom; - out_height *= zoom; - } - } - - SizeKind::WidthHeightMax => { - let zoom_x = f64::from(size_mode.width) / in_width; - let zoom_y = f64::from(size_mode.height) / in_height; - - let zoom = zoom_x.min(zoom_y); - - out_width = zoom * in_width; - out_height = zoom * in_height; - } - - SizeKind::WidthHeight => { - if size_mode.width != -1 { - out_width = f64::from(size_mode.width); - } else { - out_width = in_width; - } - - if size_mode.height != -1 { - out_height = f64::from(size_mode.height); - } else { - out_height = in_height; - } - } - } - - (out_width, out_height) -} - -pub fn render_to_pixbuf_at_size( - renderer: &CairoRenderer<'_>, - document_width: f64, - document_height: f64, - desired_width: f64, - desired_height: f64, -) -> Result { - if desired_width == 0.0 - || desired_height == 0.0 - || document_width == 0.0 - || document_height == 0.0 - { - return empty_pixbuf(); - } - - let surface = cairo::ImageSurface::create( - cairo::Format::ARgb32, - checked_i32(desired_width.ceil())?, - checked_i32(desired_height.ceil())?, - )?; - - { - let cr = cairo::Context::new(&surface)?; - cr.scale( - desired_width / document_width, - desired_height / document_height, - ); - - let viewport = cairo::Rectangle::new(0.0, 0.0, document_width, document_height); - - // We do it with a cr transform so we can scale non-proportionally. - renderer.render_document(&cr, &viewport)?; - } - - let shared_surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?; - - pixbuf_from_surface(&shared_surface) -} - -unsafe fn pixbuf_from_file_with_size_mode( - filename: *const libc::c_char, - size_mode: &SizeMode, - error: *mut *mut glib::ffi::GError, -) -> *mut gdk_pixbuf::ffi::GdkPixbuf { - let path = PathBuf::from_glib_none(filename); - - let session = Session::default(); - - let handle = match Loader::new_with_session(session.clone()).read_path(path) { - Ok(handle) => handle, - Err(e) => { - set_gerror(&session, error, 0, &format!("{e}")); - return ptr::null_mut(); - } - }; - - let dpi = Dpi::default(); - let renderer = CairoRenderer::new(&handle).with_dpi(dpi.x(), dpi.y()); - - let (document_width, document_height) = match renderer.legacy_document_size() { - Ok(dim) => dim, - Err(e) => { - set_gerror(&session, error, 0, &format!("{e}")); - return ptr::null_mut(); - } - }; - - let (desired_width, desired_height) = - get_final_size(document_width, document_height, size_mode); - - render_to_pixbuf_at_size( - &renderer, - document_width, - document_height, - desired_width, - desired_height, - ) - .map(|pixbuf| pixbuf.to_glib_full()) - .unwrap_or_else(|e| { - set_gerror(&session, error, 0, &format!("{e}")); - ptr::null_mut() - }) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_pixbuf_from_file( - filename: *const libc::c_char, - error: *mut *mut glib::ffi::GError, -) -> *mut gdk_pixbuf::ffi::GdkPixbuf { - rsvg_return_val_if_fail! { - rsvg_pixbuf_from_file => ptr::null_mut(); - - !filename.is_null(), - error.is_null() || (*error).is_null(), - } - - pixbuf_from_file_with_size_mode( - filename, - &SizeMode { - kind: SizeKind::WidthHeight, - x_zoom: 0.0, - y_zoom: 0.0, - width: -1, - height: -1, - }, - error, - ) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_size( - filename: *const libc::c_char, - width: libc::c_int, - height: libc::c_int, - error: *mut *mut glib::ffi::GError, -) -> *mut gdk_pixbuf::ffi::GdkPixbuf { - rsvg_return_val_if_fail! { - rsvg_pixbuf_from_file_at_size => ptr::null_mut(); - - !filename.is_null(), - (width >= 1 && height >= 1) || (width == -1 && height == -1), - error.is_null() || (*error).is_null(), - } - - pixbuf_from_file_with_size_mode( - filename, - &SizeMode { - kind: SizeKind::WidthHeight, - x_zoom: 0.0, - y_zoom: 0.0, - width, - height, - }, - error, - ) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_zoom( - filename: *const libc::c_char, - x_zoom: libc::c_double, - y_zoom: libc::c_double, - error: *mut *mut glib::ffi::GError, -) -> *mut gdk_pixbuf::ffi::GdkPixbuf { - rsvg_return_val_if_fail! { - rsvg_pixbuf_from_file_at_zoom => ptr::null_mut(); - - !filename.is_null(), - x_zoom > 0.0 && y_zoom > 0.0, - error.is_null() || (*error).is_null(), - } - - pixbuf_from_file_with_size_mode( - filename, - &SizeMode { - kind: SizeKind::Zoom, - x_zoom, - y_zoom, - width: 0, - height: 0, - }, - error, - ) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_zoom_with_max( - filename: *const libc::c_char, - x_zoom: libc::c_double, - y_zoom: libc::c_double, - max_width: libc::c_int, - max_height: libc::c_int, - error: *mut *mut glib::ffi::GError, -) -> *mut gdk_pixbuf::ffi::GdkPixbuf { - rsvg_return_val_if_fail! { - rsvg_pixbuf_from_file_at_zoom_with_max => ptr::null_mut(); - - !filename.is_null(), - x_zoom > 0.0 && y_zoom > 0.0, - max_width >= 1 && max_height >= 1, - error.is_null() || (*error).is_null(), - } - - pixbuf_from_file_with_size_mode( - filename, - &SizeMode { - kind: SizeKind::ZoomMax, - x_zoom, - y_zoom, - width: max_width, - height: max_height, - }, - error, - ) -} - -#[no_mangle] -pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_max_size( - filename: *const libc::c_char, - max_width: libc::c_int, - max_height: libc::c_int, - error: *mut *mut glib::ffi::GError, -) -> *mut gdk_pixbuf::ffi::GdkPixbuf { - rsvg_return_val_if_fail! { - rsvg_pixbuf_from_file_at_max_size => ptr::null_mut(); - - !filename.is_null(), - max_width >= 1 && max_height >= 1, - error.is_null() || (*error).is_null(), - } - - pixbuf_from_file_with_size_mode( - filename, - &SizeMode { - kind: SizeKind::WidthHeightMax, - x_zoom: 0.0, - y_zoom: 0.0, - width: max_width, - height: max_height, - }, - error, - ) -} diff --git a/rsvg/src/c_api/sizing.rs b/rsvg/src/c_api/sizing.rs deleted file mode 100644 index e0c85bd1..00000000 --- a/rsvg/src/c_api/sizing.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Compute an SVG document's size with the legacy logic. -//! -//! See the documentation for [`LegacySize`]. The legacy C API functions like -//! `rsvg_handle_render_cairo()` do not take a viewport argument: they do not know how big -//! the caller would like to render the document; instead they compute a "natural size" -//! for the document based on its `width`/`height`/`viewBox` and some heuristics for when -//! they are missing. -//! -//! The new style C functions like `rsvg_handle_render_document()` actually take a -//! viewport, which indicates how big the result should be. This matches the expectations -//! of the web platform, which assumes that all embedded content goes into a viewport. - -use float_cmp::approx_eq; - -use crate::api::{CairoRenderer, IntrinsicDimensions, RenderingError}; -use crate::dpi::Dpi; -use crate::handle::Handle; -use crate::length::*; - -use super::handle::CairoRectangleExt; - -/// Extension methods to compute the SVG's size suitable for the legacy C API. -/// -/// The legacy C API can compute an SVG document's size from the -/// `width`, `height`, and `viewBox` attributes of the toplevel `` -/// element. If these are not available, then the size must be computed -/// by actually measuring the geometries of elements in the document. -/// -/// See for terminology and logic. -pub trait LegacySize { - fn legacy_document_size(&self) -> Result<(f64, f64), RenderingError> { - let (ink_r, _) = self.legacy_layer_geometry(None)?; - Ok((ink_r.width(), ink_r.height())) - } - - fn legacy_layer_geometry( - &self, - id: Option<&str>, - ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError>; -} - -impl<'a> LegacySize for CairoRenderer<'a> { - fn legacy_layer_geometry( - &self, - id: Option<&str>, - ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> { - match id { - Some(id) => Ok(self.geometry_for_layer(Some(id), &unit_rectangle())?), - - None => { - let size_from_intrinsic_dimensions = - self.intrinsic_size_in_pixels().or_else(|| { - size_in_pixels_from_percentage_width_and_height( - &self.handle.handle, - &self.intrinsic_dimensions(), - self.dpi, - ) - }); - - if let Some((w, h)) = size_from_intrinsic_dimensions { - // We have a size directly computed from the attributes - let rect = cairo::Rectangle::from_size(w, h); - Ok((rect, rect)) - } else { - self.geometry_for_layer(None, &unit_rectangle()) - } - } - } - } -} - -fn unit_rectangle() -> cairo::Rectangle { - cairo::Rectangle::from_size(1.0, 1.0) -} - -/// If the width and height are in percentage units, computes a size equal to the -/// `viewBox`'s aspect ratio if it exists, or else returns None. -/// -/// For example, a `viewBox="0 0 100 200"` will yield `Some(100.0, 200.0)`. -/// -/// Note that this only checks that the width and height are in percentage units, but -/// it actually ignores their values. This is because at the point this function is -/// called, there is no viewport to embed the SVG document in, so those percentage -/// units cannot be resolved against anything in particular. The idea is to return -/// some dimensions with the correct aspect ratio. -fn size_in_pixels_from_percentage_width_and_height( - handle: &Handle, - dim: &IntrinsicDimensions, - dpi: Dpi, -) -> Option<(f64, f64)> { - let IntrinsicDimensions { - width, - height, - vbox, - } = *dim; - - use LengthUnit::*; - - // Unwrap or return None if we don't know the aspect ratio -> Let the caller handle it. - let vbox = vbox?; - - let (w, h) = handle.width_height_to_user(dpi); - - // Avoid division by zero below. If the viewBox is zero-sized, there's - // not much we can do. - if approx_eq!(f64, vbox.width(), 0.0) || approx_eq!(f64, vbox.height(), 0.0) { - return Some((0.0, 0.0)); - } - - match (width.unit, height.unit) { - (Percent, Percent) => Some((vbox.width(), vbox.height())), - (_, Percent) => Some((w, w * vbox.height() / vbox.width())), - (Percent, _) => Some((h * vbox.width() / vbox.height(), h)), - (_, _) => unreachable!("should have been called with percentage units"), - } -} diff --git a/rsvg/src/lib.rs b/rsvg/src/lib.rs index 345c559e..bfe2e73d 100644 --- a/rsvg/src/lib.rs +++ b/rsvg/src/lib.rs @@ -175,7 +175,6 @@ mod angle; mod api; mod aspect_ratio; mod bbox; -pub mod c_api; mod color; mod cond; mod css; @@ -240,8 +239,6 @@ pub mod doctest_only { #[doc(hidden)] pub mod rsvg_convert_only { pub use crate::aspect_ratio::AspectRatio; - pub use crate::c_api::handle::PathOrUrl; - pub use crate::c_api::sizing::LegacySize; pub use crate::dpi::Dpi; pub use crate::error::ParseError; pub use crate::length::{ -- cgit v1.2.1